CTP商品期貨多品種海龜交易策略 (註釋版)

CTP商品期貨多品種海龜交易策略 (註釋版)

  • 只支持操作CTP商品期貨
  • 支持自動或手動恢復進度
  • 可同時操作多個不同品種
  • 增加時間段區分與各種網絡錯誤問題的應對處理
  • 移倉功能目前正在加入中

請下載最新託管者並比較版本號是否最新

$ ./robot -v
BotVS docker 3.0 compiled at 2016-07-05T09:56:18+0800


代碼逐行翻譯了一邊,適合量化程序化初學者。

聽說看註釋前, 熟悉一下海龜交易法,效果會更好哦!
/*
參數:
Instruments             合約列表                  字符串(string)      MA701,CF701,zn1701,SR701,pp1701,l1701,hc1610,ni1701,i1701,v1701,rb1610,jm1701,ag1612,al1701,jd1701,cs1701,p1701
LoopInterval            輪詢週期(秒)              數字型(number)       3
RiskRatio               % Risk Per N ( 0 - 100) 數字型(number)       1
ATRLength               ATR計算週期               數字型(number)      20
EnterPeriodA            系統一入市週期             數字型(number)      20
LeavePeriodA            系統一離市週期             數字型(number)      10
EnterPeriodB            系統二入市週期             數字型(number)      55
LeavePeriodB            系統二離市週期             數字型(number)      20
UseEnterFilter          使用入市過濾              布爾型(true/false)   true
IncSpace                加倉間隔(N的倍數)          數字型(number)      0.5
StopLossRatio           止損係數(N的倍數)          數字型(number)      2
MaxLots                 單品種加倉次數             數字型(number)      4
RMode                   進度恢復模式              下拉框(selected)     自動|手動
VMStatus@RMode==1       手動恢復字符串             字符串(string)      {}
WXPush                  推送交易信息              布爾型(true/false)   true
MaxTaskRetry            開倉最多重試次數           數字型(number)       5
KeepRatio               預留保證金比例             數字型(number)      10
*/

var _bot = $.NewPositionManager();                                                          // 調用CTP商品期貨交易類庫 的導出函數 生成一個用於單個品種交易的對象 

var TTManager = {                                                                           // 海龜策略 控制器
    New: function(needRestore, symbol, keepBalance, riskRatio, atrLen, enterPeriodA, leavePeriodA, enterPeriodB, leavePeriodB, useFilter,
        multiplierN, multiplierS, maxLots) {
        // 該控制器對象 TTManager 的屬性 New 賦值一個 匿名函數(構造海龜的函數,即:構造函數),用於創建 海龜任務,參數分別是:
        // needRestore: 是否需要恢復,symbol:合約代碼,keepBalance:必要的預留的資金,riskRatio:風險係數, atrLen:ATR指標(參數)週期。enterPeriodA:入市週期A
        // leavePeriodA:離市週期A , enterPeriodB:入市週期B, leavePeriodB:離市週期B,useFilter:使用過濾,multiplierN:加倉係數,multiplierS:止損係數,maxLots:最大加倉次數

        // subscribe
        var symbolDetail = _C(exchange.SetContractType, symbol);                            
        // 聲明一個局部變量 symbolDetail 用於接受API SetContractType 函數的返回值(值爲symbol的合約的詳細信息,symbol 是 "MA709",返回的就是甲醇709合約的詳細信息),
        // 調用API SetContractType 訂閱並切換合約爲 symbol 變量值的合約。 _C() 函數的作用是 對 SetContractType 合約容錯處理,即如果 SetContractType返回null 會循環重試。
        if (symbolDetail.VolumeMultiple == 0 || symbolDetail.MaxLimitOrderVolume == 0 || symbolDetail.MinLimitOrderVolume == 0 || symbolDetail.LongMarginRatio == 0 || symbolDetail.ShortMarginRatio == 0) {
        // 如果 返回的合約信息對象symbolDetail 中 VolumeMultiple、MaxLimitOrderVolume 等數據異常,則調用 throw 拋出錯誤,終止程序。
            Log(symbolDetail);
            throw "合約信息異常";
        } else {                                                                             // 檢索的數據沒有異常則,輸出部分合約信息。
            Log("合約", symbolDetail.InstrumentName, "一手", symbolDetail.VolumeMultiple, "份, 最大下單量", symbolDetail.MaxLimitOrderVolume, "保證金率:", _N(symbolDetail.LongMarginRatio), _N(symbolDetail.ShortMarginRatio), "交割日期", symbolDetail.StartDelivDate);
        }

        var ACT_IDLE = 0;                                                                    // 定義一些宏 (標記)
        var ACT_LONG = 1;
        var ACT_SHORT = 2;
        var ACT_COVER = 3;                                                                   // 動作宏


        var ERR_SUCCESS = 0;                                                                 // 錯誤宏
        var ERR_SET_SYMBOL = 1;
        var ERR_GET_ORDERS = 2;
        var ERR_GET_POS = 3;
        var ERR_TRADE = 4;
        var ERR_GET_DEPTH = 5;
        var ERR_NOT_TRADING = 6;
        var errMsg = ["成功", "切換合約失敗", "獲取訂單失敗", "獲取持倉失敗", "交易下單失敗", "獲取深度失敗", "不在交易時間"];  // 錯誤宏的值 對應該數組的索引,對應索引的值就是翻譯

        var obj = {                                // 聲明一個對象,構造完成後返回。單個的海龜策略控制對象。
            symbol: symbol,                        // 合約代碼         構造函數執行時的參數傳入
            keepBalance: keepBalance,              // 預留的資金       構造函數執行時的參數傳入
            riskRatio: riskRatio,                  // 風險係數         構造函數執行時的參數傳入
            atrLen: atrLen,                        // ATR 長度         構造函數執行時的參數傳入
            enterPeriodA: enterPeriodA,            // 入市週期A        構造函數執行時的參數傳入
            leavePeriodA: leavePeriodA,            // 離市週期A        構造函數執行時的參數傳入
            enterPeriodB: enterPeriodB,            // 入市週期B        構造函數執行時的參數傳入
            leavePeriodB: leavePeriodB,            // 離市週期B        構造函數執行時的參數傳入
            useFilter: useFilter,                  // 使用入市過濾條件  構造函數執行時的參數傳入
            multiplierN: multiplierN,              // 加倉係數 基於N   構造函數執行時的參數傳入
            multiplierS: multiplierS               // 止損係數 基於N   構造函數執行時的參數傳入
        };
        obj.task = {                               // 給 obj對象添加一個 task 屬性(值也是一個對象),用來保存 海龜的任務狀態數據。
            action: ACT_IDLE,                      // 執行動作
            amount: 0,                             // 操作量
            dealAmount: 0,                         // 已經處理的操作量 
            avgPrice: 0,                           // 成交均價
            preCost: 0,                            // 前一次交易成交的額度
            preAmount: 0,                          // 前一次成交的量
            init: false,                           // 是否初始化
            retry: 0,                              // 重試次數
            desc: "空閒",                           // 描述信息
            onFinish: null                         // 處理完成時的 回調函數,即可以自行設定一個 回調函數在完成當前 action 記錄的任務後執行的代碼。
        }
        obj.maxLots = maxLots;                     // 賦值 最大加倉次數  構造函數執行時的參數傳入
        obj.lastPrice = 0;                         // 最近成交價,用於計算 持倉盈虧。
        obj.symbolDetail = symbolDetail;           // 儲存 合約的詳細信息 到obj 對象的 symbolDetail 屬性
        obj.status = {                             // 狀態數據
            symbol: symbol,                        // 合約代碼
            recordsLen: 0,                         // K線長度
            vm: [],                                // 持倉狀態 , 用來儲存 每個品種的 ,手動恢復字符串。
            open: 0,                               // 開倉次數
            cover: 0,                              // 平倉次數
            st: 0,                                 // 止損平倉次數
            marketPosition: 0,                     // 加倉次數
            lastPrice: 0,                          // 最近成交價價格
            holdPrice: 0,                          // 持倉均價
            holdAmount: 0,                         // 持倉數量
            holdProfit: 0,                         // 浮動持倉盈虧
            N: 0,                                  // N值 ,  即ATR
            upLine: 0,                             // 上線
            downLine: 0,                           // 下線
            symbolDetail: symbolDetail,            // 合約詳細信息
            lastErr: "",                           // 上次錯誤
            lastErrTime: "",                       // 上次錯誤時間信息
            stopPrice: '',                         // 止損價格
            leavePrice: '',                        // 
            isTrading: false                       // 是否在交易時間
        };

        obj.setLastError = function(err) {         // 給obj對象添加方法,設置 最近一次的錯誤信息
            if (typeof(err) === 'undefined' || err === '') {                                     // 如果參數未傳入,或者 錯誤信息爲 空字符串
                obj.status.lastErr = "";                                                         // 清空 obj 對象的 status 屬性的 對象的lastErr屬性
                obj.status.lastErrTime = "";                                                     // 清空
                return;                                                                          // 返回
            }
            var t = new Date();                                                                  // 獲取新時間
            obj.status.lastErr = err;                                                            // 設置錯誤信息
            obj.status.lastErrTime = t.toLocaleString();                                         // toLocaleString()    根據本地時間格式,把 Date 對象轉換爲字符串。
        };
        obj.reset = function(marketPosition, openPrice, N, leavePeriod, preBreakoutFailure) {    // 給obj對象添加方法,恢復倉位。
            // 參數,marketPosition:加倉次數,openPrice:最後一次加倉價, N:N值, leavePeriod:離市週期,preBreakoutFailure:是否上次突破失敗
            if (typeof(marketPosition) !== 'undefined') {                                        // 如果 第一個參數不是未定義 ,傳入參數
                obj.marketPosition = marketPosition;                                             // 給obj 添加屬性 marketPosition : 加倉次數 正數爲多倉,負數爲空倉
                obj.openPrice = openPrice;                                                       // 最後一次加倉價
                obj.preBreakoutFailure = preBreakoutFailure;                                     // 是否上次突破失敗
                obj.N = N;                                                                       // N值
                obj.leavePeriod = leavePeriod;                                                   // 離市週期
                var pos = _bot.GetPosition(obj.symbol, marketPosition > 0 ? PD_LONG : PD_SHORT); // 調用 模板類庫生成的 交易控制對象的成員函數GetPosition 獲取 持倉信息
                if (pos) {                                                                       // 如果獲取到持倉信息
                    obj.holdPrice = pos.Price;                                                   // 根據獲取的持倉信息 給obj 屬性賦值
                    obj.holdAmount = pos.Amount;                                                 // 同上
                    Log(obj.symbol, "倉位", pos);                                                 // 輸出顯示當前倉位
                } else {                                                                         // 如果GetPosition 返回null ,沒有找到持倉信息。
                    throw "恢復" + obj.symbol + "的持倉狀態出錯, 沒有找到倉位信息";                    // 拋出異常
                }
                Log("恢復", obj.symbol, "加倉次數", obj.marketPosition, "持倉均價:", obj.holdPrice, "持倉數量:", obj.holdAmount, "最後一次加倉價", obj.openPrice, "N值", obj.N, "離市週期:", leavePeriod, "上次突破:", obj.preBreakoutFailure ? "失敗" : "成功");
                // 輸出恢復的 相關參數,數據。
                obj.status.open = 1;                                                              // 設置 開倉 計數爲1
                obj.status.vm = [obj.marketPosition, obj.openPrice, obj.N, obj.leavePeriod, obj.preBreakoutFailure];  // 儲存 手動恢復字符串 數據。
            } else {                                                                              // 沒有傳入參數,即不恢復, 全部初始化。
                obj.marketPosition = 0;                                                           // 初始化各項變量
                obj.holdPrice = 0;
                obj.openPrice = 0;
                obj.holdAmount = 0;
                obj.holdProfit = 0;
                obj.preBreakoutFailure = true; // test system A                                   // 此處設置true  會使策略 嘗試 突破系統A 
                obj.N = 0;
                obj.leavePeriod = leavePeriodA;                                                   // 用系統A 的離市週期 賦值
            }
            obj.holdProfit = 0;                                                                   // 初始化
            obj.lastErr = "";
            obj.lastErrTime = "";
        };

        obj.Status = function() {                                                                 // 給Obj 添加 Status 函數, 把Obj 的一些屬性值 賦值給 Obj.status 同樣意義的屬性
            obj.status.N = obj.N;                                                                 // 給 obj.status 賦值
            obj.status.marketPosition = obj.marketPosition;
            obj.status.holdPrice = obj.holdPrice;
            obj.status.holdAmount = obj.holdAmount;
            obj.status.lastPrice = obj.lastPrice;
            if (obj.lastPrice > 0 && obj.holdAmount > 0 && obj.marketPosition !== 0) {            // 如果有持倉
                obj.status.holdProfit = _N((obj.lastPrice - obj.holdPrice) * obj.holdAmount * symbolDetail.VolumeMultiple, 4) * (obj.marketPosition > 0 ? 1 : -1);
                // 計算持倉盈虧 = (最近成交價 - 持倉價格)* 持倉量 * 一手合約份數 , 計算出來 保留4位小數, 用 obj.marketPosition(加倉次數) 屬性的 正負 去修正,計算結果的正負(做空按照這個算法是相反的負數,所以要用-1修正)。
            } else {
                // 如果沒有持倉,浮動盈虧賦值爲0
                obj.status.holdProfit = 0;
            }
            return obj.status;                                                                    // 返回這個 obj.status 對象(用於顯示在界面狀態欄?)
        };
        obj.setTask = function(action, amount, onFinish) {                                        // 給obj 對象添加 方法,設置任務
            // 參數,action:執行動作,amount:數量,onFinish: 回調函數
            obj.task.init = false;                                                                // 重置 初次執行標記 爲false 
            obj.task.retry = 0;                                                                   // 重置..
            obj.task.action = action;                                                             // 參數傳來的 動作指令 賦值
            obj.task.preAmount = 0;                                                               // 重置
            obj.task.preCost = 0;
            obj.task.amount = typeof(amount) === 'number' ? amount : 0;                           // 如果沒傳入參數 ,設置 0
            obj.task.onFinish = onFinish;
            if (action == ACT_IDLE) {                                                             // 如果 動作指令是 空閒
                obj.task.desc = "空閒";                                                            // 描述變量  賦值爲  “空閒”
                obj.task.onFinish = null;                                                         // 賦值爲 null
            } else {                                                                               // 其他動作
                if (action !== ACT_COVER) {                                                        // 如果不等於 平倉動作
                    obj.task.desc = (action == ACT_LONG ? "加多倉" : "加空倉") + "(" + amount + ")"; // 根據 action 設置描述 信息
                } else {                                                                           // 如果是平倉 動作 設置描述信息爲 “平倉”
                    obj.task.desc = "平倉";
                }
                Log("接收到任務", obj.symbol, obj.task.desc);                                        // 輸出日誌 顯示 接收到任務。
                // process immediately
                obj.Poll(true);                                                                    // 調用 obj 對象的方法 處理 任務,參數是 true , 參數爲true ,控制Poll 只執行 一部分(子過程)
            }
        };
        obj.processTask = function() {                                                              // 處理 交易任務 
            var insDetail = exchange.SetContractType(obj.symbol);                                   // 切換 要操作的合約
            if (!insDetail) {                                                                       // 切換失敗 返回錯誤
                return ERR_SET_SYMBOL;
            }
            var SlideTick = 1;                                                                      // 滑價設置爲1 個 PriceTick
            var ret = false;                                                                        // 聲明返回值  初始false
            if (obj.task.action == ACT_COVER) {                                                     // 處理 指令爲全平的 任務,這部分處理 類似 商品期貨交易類庫 不再贅述,可以參見 商品期貨交易類庫註釋版
                var hasPosition = false;
                do {
                    if (!$.IsTrading(obj.symbol)) {
                        return ERR_NOT_TRADING;
                    }
                    hasPosition = false;
                    var positions = exchange.GetPosition();
                    if (!positions) {
                        return ERR_GET_POS;
                    }
                    var depth = exchange.GetDepth();
                    if (!depth) {
                        return ERR_GET_DEPTH;
                    }
                    var orderId = null;
                    for (var i = 0; i < positions.length; i++) {
                        if (positions[i].ContractType !== obj.symbol) {
                            continue;
                        }
                        var amount = Math.min(insDetail.MaxLimitOrderVolume, positions[i].Amount);
                        if (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) {
                            exchange.SetDirection(positions[i].Type == PD_LONG ? "closebuy_today" : "closebuy");
                            orderId = exchange.Sell(_N(depth.Bids[0].Price - (insDetail.PriceTick * SlideTick), 2), Math.min(amount, depth.Bids[0].Amount), obj.symbol, positions[i].Type == PD_LONG ? "平今" : "平昨", 'Bid', depth.Bids[0]);
                            hasPosition = true;
                        } else if (positions[i].Type == PD_SHORT || positions[i].Type == PD_SHORT_YD) {
                            exchange.SetDirection(positions[i].Type == PD_SHORT ? "closesell_today" : "closesell");
                            orderId = exchange.Buy(_N(depth.Asks[0].Price + (insDetail.PriceTick * SlideTick), 2), Math.min(amount, depth.Asks[0].Amount), obj.symbol, positions[i].Type == PD_SHORT ? "平今" : "平昨", 'Ask', depth.Asks[0]);
                            hasPosition = true;
                        }
                    }
                    if (hasPosition) {
                        if (!orderId) {
                            return ERR_TRADE;
                        }
                        Sleep(1000);
                        while (true) {
                            // Wait order, not retry
                            var orders = exchange.GetOrders();
                            if (!orders) {
                                return ERR_GET_ORDERS;
                            }
                            if (orders.length == 0) {
                                break;
                            }
                            for (var i = 0; i < orders.length; i++) {
                                exchange.CancelOrder(orders[i].Id);
                                Sleep(500);
                            }
                        }
                    }
                } while (hasPosition);
                ret = true;
            } else if (obj.task.action == ACT_LONG || obj.task.action == ACT_SHORT) {                   // 處理 建/加多倉 任務  或者  處理 建/加空倉 任務,這部分處理 類似 商品期貨交易類庫 不再贅述,可以參見 商品期貨交易類庫註釋版。(此策略沒有使用商品期貨交易類庫的交易功能,在次直接植入了處理代碼)
                do {
                    if (!$.IsTrading(obj.symbol)) {
                        return ERR_NOT_TRADING;
                    }
                    Sleep(1000);
                    while (true) {
                        // Wait order, not retry
                        var orders = exchange.GetOrders();
                        if (!orders) {
                            return ERR_GET_ORDERS;
                        }
                        if (orders.length == 0) {
                            break;
                        }
                        for (var i = 0; i < orders.length; i++) {
                            exchange.CancelOrder(orders[i].Id);
                            Sleep(500);
                        }
                    }
                    var positions = exchange.GetPosition();
                    // Error
                    if (!positions) {
                        return ERR_GET_POS;
                    }
                    // search position
                    var pos = null;
                    for (var i = 0; i < positions.length; i++) {
                        if (positions[i].ContractType == obj.symbol && (((positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) && obj.task.action == ACT_LONG) || ((positions[i].Type == PD_SHORT || positions[i].Type == PD_SHORT_YD) && obj.task.action == ACT_SHORT))) {
                            if (!pos) {
                                pos = positions[i];
                                pos.Cost = positions[i].Price * positions[i].Amount;
                            } else {
                                pos.Amount += positions[i].Amount;
                                pos.Profit += positions[i].Profit;
                                pos.Cost += positions[i].Price * positions[i].Amount;
                            }
                        }
                    }
                    // record pre position
                    if (!obj.task.init) {
                        obj.task.init = true;
                        if (pos) {
                            obj.task.preAmount = pos.Amount;
                            obj.task.preCost = pos.Cost;
                        } else {
                            obj.task.preAmount = 0;
                            obj.task.preCost = 0;
                        }
                    }
                    var remain = obj.task.amount;
                    if (pos) {
                        obj.task.dealAmount = pos.Amount - obj.task.preAmount;
                        remain = parseInt(obj.task.amount - obj.task.dealAmount);
                        if (remain <= 0 || obj.task.retry >= MaxTaskRetry) {
                            ret = {
                                price: (pos.Cost - obj.task.preCost) / (pos.Amount - obj.task.preAmount),
                                amount: (pos.Amount - obj.task.preAmount),
                                position: pos
                            };
                            break;
                        }
                    } else if (obj.task.retry >= MaxTaskRetry) {
                        ret = null;
                        break;
                    }

                    var depth = exchange.GetDepth();
                    if (!depth) {
                        return ERR_GET_DEPTH;
                    }
                    var orderId = null;
                    if (obj.task.action == ACT_LONG) {
                        exchange.SetDirection("buy");
                        orderId = exchange.Buy(_N(depth.Asks[0].Price + (insDetail.PriceTick * SlideTick), 2), Math.min(remain, depth.Asks[0].Amount), obj.symbol, 'Ask', depth.Asks[0]);
                    } else {
                        exchange.SetDirection("sell");
                        orderId = exchange.Sell(_N(depth.Bids[0].Price - (insDetail.PriceTick * SlideTick), 2), Math.min(remain, depth.Bids[0].Amount), obj.symbol, 'Bid', depth.Bids[0]);
                    }
                    // symbol not in trading or other else happend
                    if (!orderId) {
                        obj.task.retry++;
                        return ERR_TRADE;
                    }
                } while (true);
            }
            if (obj.task.onFinish) {
                obj.task.onFinish(ret);
            }
            obj.setTask(ACT_IDLE);                                                                 // 任務執行完成(中間沒有被 錯誤 return),重設爲 空閒任務
            return ERR_SUCCESS;
        };
        obj.Poll = function(subroutine) {                                                           // 處理海龜交易法  策略邏輯, 參數:  子程序?
            obj.status.isTrading = $.IsTrading(obj.symbol);                                         // 調用 模板的導出函數 $.IsTrading 檢測 obj.symbol 記錄的品種是否在交易時間,結果賦值給obj.status.isTrading
            if (!obj.status.isTrading) {                                                            // 如果 obj.status.isTrading 是 false 即 不在交易時間內, return 返回
                return;
            }
            if (obj.task.action != ACT_IDLE) {                                                      // 如果 任務屬性的 執行動作屬性 不等於 等待標記(宏)
                var retCode = obj.processTask();                                                    // 就調用 當前obj 對象的processTask函數 執行 task 記錄的任務。 
                if (obj.task.action != ACT_IDLE) {                                                  // 如果 調用 processTask 函數後 task屬性的action 屬性不等於 等待標記,即證明任務沒有處理成功。
                    obj.setLastError("任務沒有處理成功: " + errMsg[retCode] + ", " + obj.task.desc + ", 重試: " + obj.task.retry);
                    // 此時調用 setLastError 記錄 並 顯示 任務 沒有處理成功, 錯誤代碼, 任務描述、重試次數
                } else {
                    obj.setLastError();                                                             // 調用 setLastError 不傳參數, 不傳參數 用空內容(字符串,詳見函數setLastError)刷新。
                }
                return;                                                                             // 執行完 任務 返回
            }
            if (typeof(subroutine) !== 'undefined' && subroutine) {                                 // 參數 subroutine 不爲null 且 已定義, 比如在調用 setTask 後會執行Poll,到此就返回
                return;                                                                             // 返回
            }
            // Loop
            var suffix = WXPush ? '@' : '';                                                         // 界面參數如果開啓 微信推送, suffix 會被賦值 "@"(微信推送功能 只用在API: Log函數後加 "@"字符即可), 否則空字符。
            // switch symbol
            _C(exchange.SetContractType, obj.symbol);                                               // 切換 合約 爲 obj.symbol 記錄的合約代碼
            var records = exchange.GetRecords();                                                    // 獲取K線數據
            if (!records) {                                                                         // 如果 K線獲取到  null 值
                obj.setLastError("獲取K線失敗");                                                      // 設置失敗信息,並返回。
                return;
            }
            obj.status.recordsLen = records.length;                                                 // 記錄K線長度
            if (records.length < obj.atrLen) {                                                      // 如果 K線長度小於  ATR指標參數(小於的話 無法計算出ATR指標 即N值)
                obj.setLastError("K線長度小於 " + obj.atrLen);                                        // 設置錯誤信息,並返回。
                return;
            }
            var opCode = 0; // 0: IDLE, 1: LONG, 2: SHORT, 3: CoverALL                              // 聲明一個臨時變量  操作代碼 有4種操作
            var lastPrice = records[records.length - 1].Close;                                      // 聲明一個臨時變量  用K線 最後一個柱 的收盤價給其賦值,(K線最後一個柱的收盤價是實時更新的是最新價格)
            obj.lastPrice = lastPrice;                                                              // 賦值給  obj.lastPrice
            if (obj.marketPosition === 0) {                                                         // 如果當前 海龜策略 控制對象的加倉次數 爲0 ,即沒持倉。
                obj.status.stopPrice = '--';                                                        // 給止損價 賦值 '--'
                obj.status.leavePrice = '--';                                                       // 用於顯示 狀態的表格 對象 status的 leavePrice屬性賦值 "--"  (因爲沒有持倉,所以沒有 離市價)
                obj.status.upLine = 0;                                                              // 賦值 上線,(這裏如果不明白 這些變量控制那些顯示,可以實際運行一個模擬盤 ,看下界面對比分析更好理解。)
                obj.status.downLine = 0;                                                            // 賦值 下線
                for (var i = 0; i < 2; i++) {                                                       // 在當前的分支條件內,是沒有持倉的,這裏循環兩次,用來檢測2個突破系統的觸發。
                    if (i == 0 && obj.useFilter && !obj.preBreakoutFailure) {                       // 如果是第一次循環,並且啓用了入市條件過濾,並且上次突破沒有失敗。
                        continue;                                                                   // 跳過本次循環
                    }
                    var enterPeriod = i == 0 ? obj.enterPeriodA : obj.enterPeriodB;                 // 用 ? :  三元條件表達式,選擇使用的  突破系統 參數,即當 i == 0 時 使用 系統A
                    if (records.length < (enterPeriod + 1)) {                                       // 限制 當前 K線週期 bar 長度 必須大於 突破系統的入市週期加1
                        continue;                                                                   // 跳過本次循環
                    }
                    var highest = TA.Highest(records, enterPeriod, 'High');                         // 計算enterPeriod週期內所有最高價的 最大值
                    var lowest = TA.Lowest(records, enterPeriod, 'Low');                            // 計算enterPeriod週期內所有最低價的 最小值
                    obj.status.upLine = obj.status.upLine == 0 ? highest : Math.min(obj.status.upLine, highest);      // 取兩次 系統A 和系統B 獲取的 highest中 最小的值
                    obj.status.downLine = obj.status.downLine == 0 ? lowest : Math.max(obj.status.downLine, lowest);  // 取兩次 系統A 和系統B 獲取的 lowest中 最大的值
                    if (lastPrice > highest) {                                                                        // 最新的 價格 如果向上突破 對應週期內的最高價
                        opCode = 1;                                                                                   // 操作值 賦值1
                    } else if (lastPrice < lowest) {                                                                  // 最新的 價格 如果向下突破 對應週期內的最低價
                        opCode = 2;                                                                                   // 操作值 賦值2
                    }
                    obj.leavePeriod = (enterPeriod == obj.enterPeriodA) ? obj.leavePeriodA : obj.leavePeriodB;        // 更新 最終確定使用 哪個系統?問題1
                }
            } else {                                                                                                  // 如果持有倉位
                var spread = obj.marketPosition > 0 ? (obj.openPrice - lastPrice) : (lastPrice - obj.openPrice);      // 計算單價盈虧 做多 盈利是負值 虧損是正值,因爲要做和止損單價的對比,所以取反, 做空同理
                obj.status.stopPrice = _N(obj.openPrice + (obj.N * StopLossRatio * (obj.marketPosition > 0 ? -1 : 1)));  // 計算止損價 做多的時候: 用開倉價 減去 N值 乘 止損係數, 做空: 用開倉價 加上 N值 乘止 損係數。
                if (spread > (obj.N * StopLossRatio)) {                                                                  // 檢測 單價盈虧 是否大於 設定的 盈虧限制(即 止損係數 * N值)
                    opCode = 3;                                                                                          // 觸發 止損 操作代碼 賦值 3
                    obj.preBreakoutFailure = true;                                                                       // 觸發止損  ,標記 上次突破失敗爲真
                    Log(obj.symbolDetail.InstrumentName, "止損平倉", suffix);                                             // 打印 該品種 合約名 止損, 如果開啓微信推送,則推送到微信。
                    obj.status.st++;                                                                                     // 止損計數 累計
                } else if (-spread > (IncSpace * obj.N)) {                                                               // 如果單價盈虧(取反 得 正 盈利數,負虧損數) 大於加倉係數 * N值, 觸發加倉操作
                    opCode = obj.marketPosition > 0 ? 1 : 2;                                                             // 0: IDLE, 1: LONG, 2: SHORT, 3: CoverALL 
                } else if (records.length > obj.leavePeriod) {                                                           // 只要 K線週期 長度大於 離市 週期,可以計算離市價格
                    obj.status.leavePrice = TA.Lowest(records, obj.leavePeriod, obj.marketPosition > 0 ? 'Low' : 'High') // 問題2
                    if ((obj.marketPosition > 0 && lastPrice < obj.status.leavePrice) ||                                 // 做多 或者 做空 如果觸發了  離市價
                        (obj.marketPosition < 0 && lastPrice > obj.status.leavePrice)) {
                        obj.preBreakoutFailure = false;                                                                  // 上次突破失敗 賦值爲 false ,即 沒失敗
                        Log(obj.symbolDetail.InstrumentName, "正常平倉", suffix);                                         //  打印信息 平倉,可微信推送
                        opCode = 3;                                                                                      // 給操作 賦值 3 
                        obj.status.cover++;                                                                               // 平倉計數累計
                    }
                }
            }

            if (opCode == 0) {                                                                                            // 如果是  等待 代碼  則返回
                return;
            }
            if (opCode == 3) {                                                                                            // 如果是 全平倉  代碼 
                obj.setTask(ACT_COVER, 0, function(ret) {                                                                 // 調用 obj 海龜控制對象的成員函數 setTask 設置任務 (全平倉)並自定義一個回調函數(第三個參數 function(ret){...} 就是匿名函數。)
                    obj.reset();                                                                                          // 回調函數 會在setTask 函數中 設置任務後 調用的 Poll 的函數中 通過 processTask 函數 執行該任務完成後 ,觸發回調函數。
                    _G(obj.symbol, null);                                                                                 // 回調函數 調用了 不傳參數的 reset函數,執行控制對象 變量重置工作,清空 _G 保存的 本地永久 數據(用於恢復,因爲已經平倉了,所以需要清空)
                });
                return;                                                                                                   // 回調函數是在任務完成後(即 全部海龜頭寸 平倉後 才觸發,此處只是預設)
            }
            // Open
            if (Math.abs(obj.marketPosition) >= obj.maxLots) {                                                            // 建倉 或者 加倉處理, 這裏判斷如果 加倉次數 大於等於 最大允許加倉次數
                obj.setLastError("禁止開倉, 超過最大持倉 " + obj.maxLots);                                                    // 設置錯誤信息,然後返回。
                return;
            }
            var atrs = TA.ATR(records, atrLen);                                                                            // 計算ATR 指標
            var N = _N(atrs[atrs.length - 1], 4);                                                                          // 獲取 當前ATR指標值 ,即 N值

            var account = _bot.GetAccount();                                                                               // 調用 模板 生成的 交易控制對象的 成員函數 GetAccount
            var currMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;                                                 // 獲取當前 保證金數值
            var unit = parseInt((account.Balance+currMargin-obj.keepBalance) * (obj.riskRatio / 100) / N / obj.symbolDetail.VolumeMultiple);  
            // 計算 總 可用資金對應 N值 計算出的  一個頭寸的 大小(手數)。可以看原版的海龜交易法  關於 unit 的計算,知乎上也有相關文章。 
            var canOpen = parseInt((account.Balance-obj.keepBalance) / (opCode == 1 ? obj.symbolDetail.LongMarginRatio : obj.symbolDetail.ShortMarginRatio) / (lastPrice * 1.2) / obj.symbolDetail.VolumeMultiple);
            // 根據 要做 多倉 或者 空倉 的保證金率 計算 可用資金 可以開 的手數,可開量。
            unit = Math.min(unit, canOpen);                                                                                // 最終頭寸大小 取 unit, canOpen 中最小值
            if (unit < obj.symbolDetail.MinLimitOrderVolume) {                                                             // 如果 計算出的 頭寸大小  小於 合約規定的限價單 最小下單量,則
                obj.setLastError("可開 " + unit + " 手 無法開倉, " + (canOpen >= obj.symbolDetail.MinLimitOrderVolume ? "風控觸發" : "資金限制"));  // 設置最新錯誤信息
                return;                                                                                                                        // 返回
            }
            obj.setTask((opCode == 1 ? ACT_LONG : ACT_SHORT), unit, function(ret) {                                        // 根據  opCode 設定,  調用 setTask 函數 設定任務
                if (!ret) {                                                                                                // 同樣 第三個參數 是回調函數,回調函數中 ret 是觸發 調用回調函數時傳入的參數,任務的執行返回值。 
                    obj.setLastError("下單失敗");
                    return;
                }
                Log(obj.symbolDetail.InstrumentName, obj.marketPosition == 0 ? "開倉" : "加倉", "離市週期", obj.leavePeriod, suffix);  // 任務成功完成,回調函數會執行此 輸出
                obj.N = N;                                                                                                          // 開倉 或者 加倉後 更新N值 
                obj.openPrice = ret.price;                                                                                          // 更新 開倉價格
                obj.holdPrice = ret.position.Price;                                                                                 // 更新持倉均價,根據 任務執行的ret。
                if (obj.marketPosition == 0) {                                                                                      // 如果此時 加倉次數是0, 即代表本次是 建倉
                    obj.status.open++;                                                                                              // 開倉計數 累計
                }
                obj.holdAmount = ret.position.Amount;                                                                               // 更新持倉量
                obj.marketPosition += opCode == 1 ? 1 : -1;                                                                         // 根據 做多 或者 做空 累計 加倉次數
                obj.status.vm = [obj.marketPosition, obj.openPrice, N, obj.leavePeriod, obj.preBreakoutFailure];                    // 更新 用於恢復的 字符串 ,屬性vm
                _G(obj.symbol, obj.status.vm);                                                                                      // 本地持久化儲存 當前持倉信息。
            });
        };                                                                                                                          // Poll 函數結束
        var vm = null;                                                                                                              // 在New 構造函數中 聲明一個 局部變量 vm 區別於obj.vm
        if (RMode === 0) {                                                                                                          // 如果進度恢復模式爲 自動,下拉框第一個索引是0 ,設置爲第一個時 下拉框參數就返回0 ,第二個 返回下一個索引1,以此類推。
            vm = _G(obj.symbol);                                                                                                    // 取回 持久化儲存的數據 賦值給 局部變量vm
        } else {                                                                                                                    // 否則 恢復模式爲 手動
            vm = JSON.parse(VMStatus)[obj.symbol];                                                                                  // 取手動恢復字符串 JSON解析後的數組中的對應於合約類型 obj.symbol 的 數據。
        }
        if (vm) {                                                                                                                   // 如果獲取的有 數據
            Log("準備恢復進度, 當前合約狀態爲", vm);                                                                                     // 輸出恢復的 合約狀態
            obj.reset(vm[0], vm[1], vm[2], vm[3], vm[4]);                                                                            // 調用重設 函數 重新設置 恢復狀態
        } else {                                                                                                                     // 如果vm 沒有數據
            if (needRestore) {                                                                                                       // 需要恢復 則輸出 沒找到進度的信息, (有可能是 合約列表 中 有新的合約代碼,則不需要恢復)
                Log("沒有找到" + obj.symbol + "的進度恢復信息");
            }
            obj.reset();                                                                                                             // reset 不傳參數 ,即重置
        }
        return obj;                                                                                                                  // 返回 構造完成的對象。
    }
};

function onexit() {                                                                // 策略程序 退出時執行。
    Log("已退出策略...");
}

function main() {
    if (exchange.GetName().indexOf('CTP') == -1) {                                 // 限定 連接的交易所 必須是 CTP 商品期貨 
        throw "只支持商品期貨CTP";
    }
    SetErrorFilter("login|ready|流控|連接失敗|初始|Timeout");                         // 過濾常規錯誤
    var mode = exchange.IO("mode", 0);                                             // 設定行情模式 爲立即返回模式 參看 API 文檔: https://www.botvs.com/api
    if (typeof(mode) !== 'number') {                                               // 如果 切換模式 的API 返回的 不是 數值,即切換失敗。
        throw "切換模式失敗, 請更新到最新託管者!";                                      // 拋出異常
    }
    while (!exchange.IO("status")) {                                               // 檢測 與 行情、交易服務器連接,直到  API  函數  exchange.IO("status") 返回true 連接上,退出循環
        Sleep(3000);
        LogStatus("正在等待與交易服務器連接, " + new Date());                          // 在未連接上時  輸出 文本和 當前時間。
    }
    var positions = _C(exchange.GetPosition);                                      // 調用API  GetPosition 函數 獲取 持倉信息
    if (positions.length > 0) {                                                    // 返回的數組不是空數組 ,即有持倉
        Log("檢測到當前持有倉位, 系統將開始嘗試恢復進度...");
        Log("持倉信息", positions);
    }
    Log("風險係數:", RiskRatio, "N值週期:", ATRLength, "系統1: 入市週期", EnterPeriodA, "離市週期", LeavePeriodA, "系統二: 入市週期", EnterPeriodB, "離市週期", LeavePeriodB, "加倉係數:", IncSpace, "止損係數:", StopLossRatio, "單品種最多開倉:", MaxLots, "次");
    // 輸出 參數信息。
    var initAccount = _bot.GetAccount();                                           // 獲取賬戶信息
    var initMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;                 // 調用 API GetRawJSON 函數 獲取  : "CurrMargin": "當前保證金總額",
    var keepBalance = _N((initAccount.Balance + initMargin) * (KeepRatio/100), 3); // 根據預留保證金比例 計算出 需要預留的資金。
    Log("資產信息", initAccount, "保留資金:", keepBalance);                           // 輸出信息

    var tts = [];
    var filter = [];                                                               // 過濾用數組
    var arr = Instruments.split(',');                                              // 合約列表按照逗號分隔 成數組
    for (var i = 0; i < arr.length; i++) {                                         // 遍歷分隔後的數組
        var symbol = arr[i].replace(/^\s+/g, "").replace(/\s+$/g, "");             // 正則表達式 匹配 操作, 得出  合約代碼
        if (typeof(filter[symbol]) !== 'undefined') {                              // 如果 在過濾數組中 存在 名爲 symbol的屬性,則顯示信息 並跳過。
            Log(symbol, "已經存在, 系統已自動過濾");
            continue;
        }
        filter[symbol] = true;                                                     // 給過濾數組 添加 名爲 symbol 的 屬性,下次 同樣的 合約代碼 會被過濾
        var hasPosition = false;                                                   // 初始化 hasPosition 變量 false 代表沒有持倉 
        for (var j = 0; j < positions.length; j++) {                               // 遍歷 獲取到的持倉信息
            if (positions[j].ContractType == symbol) {                             // 如果有持倉信息 合約 名稱 和 symbol一樣的, 給hasPosition 賦值true 代表有持倉
                hasPosition = true;
                break;
            }
        }
        var obj = TTManager.New(hasPosition, symbol, keepBalance, RiskRatio, ATRLength, EnterPeriodA, LeavePeriodA, EnterPeriodB, LeavePeriodB, UseEnterFilter, IncSpace, StopLossRatio, MaxLots);
        // 根據界面參數 使用 構造函數 New 構造  一個品種的海龜交易策略控制對象
        tts.push(obj);                                                             // 把該對象壓入 tts 數組, 最終根據合約列表 ,生成了若干個品種的 控制對象儲存在tts數組 
    }


    var preTotalHold = -1;
    var lastStatus = '';
    while (true) {                                                                 // 主要循環
        if (GetCommand() === "暫停/繼續") {                                         // API GetCommand 函數 獲取 程序界面上的 命令。此處 如果 點擊了界面上的“暫停/繼續”按鈕
            Log("暫停交易中...");
            while (GetCommand() !== "暫停/繼續") {                                  // 進入等待循環 ,直到再次點擊  “暫停/繼續” 按鈕 退出 等待循環
                Sleep(1000);
            }
            Log("繼續交易中...");
        }
        while (!exchange.IO("status")) {                                           // 一旦斷開服務器的連接,則嘗試重連 並等待。
            Sleep(3000);
            LogStatus("正在等待與交易服務器連接, " + new Date() + "\n" + lastStatus);  // 輸出上一次的 狀態欄 內容,並 更新時間。
        }
        var tblStatus = {                                                          // 用於顯示在狀態欄表格上的  持倉信息  對象
            type: "table",
            title: "持倉信息",
            cols: ["合約名稱", "持倉方向", "持倉均價", "持倉數量", "持倉盈虧", "加倉次數", "開倉次數", "止損次數", "成功次數", "當前價格", "N"],
            rows: []
        };
        var tblMarket = {                                                          // 用於顯示在狀態欄表格上的 市場信息 對象
            type: "table",
            title: "運行狀態",
            cols: ["合約名稱", "合約乘數", "保證金率", "交易時間", "柱線長度", "上線", "下線", "止損價", "離市價", "異常描述", "發生時間"],
            rows: []
        };
        var totalHold = 0;
        var vmStatus = {};
        var ts = new Date().getTime();                                             // 當前時間戳
        var holdSymbol = 0;                                                        // 持有的合約量
        for (var i = 0; i < tts.length; i++) {                                     // 遍歷tts數組
            tts[i].Poll();                                                         // 調用每個 合約的海龜管理對象的 Poll 函數
            var d = tts[i].Status();                                               // 更新每個 海龜管理對象的  狀態 屬性 status 並返回。
            if (d.holdAmount > 0) {                                                // 如果當前索引的對象 有 持倉
                vmStatus[d.symbol] = d.vm;                                         // 給空對象 vmStatus 添加合約名稱 爲屬性名 的屬性,並給其賦值 持倉信息vm
                holdSymbol++;                                                      // 給持有的合約品種數量  累計  
            }
            tblStatus.rows.push([d.symbolDetail.InstrumentName, d.holdAmount == 0 ? '--' : (d.marketPosition > 0 ? '多' : '空'), d.holdPrice, d.holdAmount, d.holdProfit, Math.abs(d.marketPosition), d.open, d.st, d.cover, d.lastPrice, d.N]);
            // 壓入當前 索引 的 海龜管理對象 的信息 到狀態分頁表格
            tblMarket.rows.push([d.symbolDetail.InstrumentName, d.symbolDetail.VolumeMultiple, _N(d.symbolDetail.LongMarginRatio, 4) + '/' + _N(d.symbolDetail.ShortMarginRatio, 4), (d.isTrading ? '是#0000ff' : '否#ff0000'), d.recordsLen, d.upLine, d.downLine, d.stopPrice, d.leavePrice, d.lastErr, d.lastErrTime]);
            // 壓入當前 索引 的 海龜管理對象 的信息 到行情分頁表格
            totalHold += Math.abs(d.holdAmount);                                   // 值爲回調函數 的參數ret 的屬性 更新,可以參見 回調函數的 傳入實參。processTask 函數中的 ret
            // 累計 總持倉手數
        }
        var now = new Date();                                                      // 獲取最新時間
        var elapsed = now.getTime() - ts;                                          // 計算主要耗時代碼 , 迭代 執行 Poll 函數的 開始與結束的 時間差。
        var tblAssets = _bot.GetAccount(true);                                     // 獲取賬戶詳細信息並返回一個表格對象。(因爲參數傳遞的是true, 參見 模板的 GetAccount 函數的 getTable 參數)
        var nowAccount = _bot.Account();                                           // 獲取賬戶信息

        if (tblAssets.rows.length > 10) {                                          // 如果獲取的 表格的 行數 大於10
            // replace AccountId
            tblAssets.rows[0] = ["InitAccount", "初始資產", initAccount];           // 設置 索引 0 的行數 爲 初始資金信息。
        } else {
            tblAssets.rows.unshift(["NowAccount", "當前可用", nowAccount], ["InitAccount", "初始資產", initAccount]); // 往 rows 數組 中開始的位置插入2個元素
        }
        lastStatus = '`' + JSON.stringify([tblStatus, tblMarket, tblAssets]) + '`\n輪詢耗時: ' + elapsed + ' 毫秒, 當前時間: ' + now.toLocaleString() + ', 星期' + ['日', '一', '二', '三', '四', '五', '六'][now.getDay()] + ", 持有品種個數: " + holdSymbol;
        // 組合 各種 用於顯示在界面的信息。
        if (totalHold > 0) {                                                       // 在有持倉時才 顯示 手動恢復字符串(vmStatus JSON序列化)
            lastStatus += "\n手動恢復字符串: " + JSON.stringify(vmStatus);
        }
        LogStatus(lastStatus);                                                     // 調用API 顯示在 狀態欄
        if (preTotalHold > 0 && totalHold == 0) {                                  // 當全部持倉 平掉 沒有持倉時
            LogProfit(nowAccount.Balance - initAccount.Balance - initMargin);      // 輸出 盈利, 顯示到收益曲線(此種情況 出現概率較低,很難有同時全部都未持倉的狀態,所以收益都是 動態的,可以看 賬戶詳細信息分析當前狀況)
        }
        preTotalHold = totalHold;                                                  // 每次都更新  確保 輸出收益只顯示一次。 
        Sleep(LoopInterval * 1000);                                                // 輪詢等待。避免API 訪問過於頻繁
    }
}

菜鳥程序員註釋,如有錯誤,歡迎斧正,感謝支持! ^。^

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章