商品期貨套利 - 多品種網格對沖模型 註釋版

商品期貨套利 - 多品種網格對沖模型 註釋版

  • #### 代碼:
// 商品期貨套利 - 多品種網格對沖模型 註釋版
function Hedge(q, e, positions, symbolA, symbolB, hedgeSpread) { // 對沖對象 生成函數, 參數q 爲最開始調用 商品期貨交易類庫模板 的 導出函數 生成的 交易隊列控制對象, 參數e 爲交易所對象,positions 爲初始倉位
                                                                 // symbolA 爲第一個合約類型,  symbolB 爲第二個合約類型, hedgeSpread 爲對沖 交易量控制表 字符串。
    var self = {}                                                // 聲明一個 空對象 用於給空對象 初始化 後返回 (即對沖對象)
    self.q = q                                                   // 給對沖對象 添加 屬性 q ,並用參數的q 初始賦值。
    self.symbolA = symbolA                                       // 給對沖對象self 添加屬性 symbolA ,儲存 第一個合約類型
    self.symbolB = symbolB                                       // ...
    self.name = symbolA + " & " + symbolB                        // 對沖對象的 名稱 即對沖組合
    self.e = e                                                   // 對沖對象 中儲存交易所對象 的引用 
    self.isBusy = false                                          // 繁忙 標記 初始爲 不繁忙。
    self.diffA = 0                                               // 差價A ,  即 symbolA買一 - symbolB賣一 
    self.diffB = 0                                               // 差價B ,  即 symbolB賣一 - symbolA買一
    self.update = _D()                                           // 記錄 更新時間
    var arr = hedgeSpread.split(';')                             // 把傳入的  交易量控制表 字符串 按照 ';' 字符 分割。
    self.dic = []                                                // 網格節點 對象 數組
    var n = 0                                                    // 
    var coefficient = 1                                          // 係數 初始1
    for (var i = 0; i < positions.length; i++) {                 // 遍歷持倉信息。
        if (positions[i].ContractType == symbolA) {              // 如果遍歷當前的 持倉信息 的合約類型 和 當前對沖對象的 第一個合約類型 相同
            n += positions[i].Amount                             // 累計 類型爲 symbolA 的合約的持倉量
            if (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) {   // 如果 持倉類型是 多倉 , coefficient 賦值爲 -1 ,即 代表 反對沖: 多 symbolA  空 symbolB
                coefficient = -1
            }
        }
    }
    _.each(arr, function(pair) {                                 // 把 控制表字符串 數組的  每一個單元  迭代傳遞到 匿名函數 作爲參數 pair
        var tmp = pair.split(':');                               // 把每個 網格節點的 單元 按照':'字符 分割成 參數 數組 tmp
        if (tmp.length != 3) {                                   // 由於 格式 是  30:15:1  即   開倉差價: 平倉差價: 下單量    ,所以 用 ':' 分割後 tmp長度 不是3的 即 格式錯誤
            throw "開倉表不正確";                                  // 拋出異常  格式錯誤
        }
        var st = {                                               // 每次迭代的時候 構造一個對象 
            open: Number(tmp[0]),                                // 開倉  把 tmp[0]  即 30:15:1 按 ':' 分割後 生成的數組中的 第一個元素  30 , 通過 Number 函數 轉換爲數值
            cover: Number(tmp[1]),                               // 平倉..
            amount: Number(tmp[2]),                              // 量..
            hold: 0                                              // 持倉 初始爲0
        }
        if (n > 0) {                                             // 如果 n 大於0  ,即 開始的時候 有持倉。
            var m = Math.min(n, st.amount)                       // 取 當前合約組合中 symbolA的 持倉量   和  網格節點 中的開倉量  二者的 最小值 賦值給 m 變量
            n -= m                                               // 在持倉累計數量  n  中 減去 m
            st.hold = m * coefficient                            // 正對沖 coefficient 這個係數 爲1  ,  如果 symbolA 爲 合約的持倉 類型爲  多倉, 則是反對沖 那麼 coefficient 在之前 就會被賦值爲 -1
                                                                 // 在迭代過程中  n 被 分散到各個 網格節點  恢復網格 持倉數據。
            Log("恢復", self.name, st)                            // 輸出本次的恢復信息。
        }
        self.dic.push(st)                                        // 把恢復好的節點 壓入 dic數組 。
    });
    if (n > 0) {                                                 // 如果 迭代完成後  n 值 依然大於0  即 還有倉位沒有分配恢復 , 拋出錯誤
        throw "恢復失敗, 有多餘倉位 " + n;
    }

    self.poll = function() {                                     // 給 self 對沖對象  添加 屬性poll  並用一個 匿名函數 初始化 賦值
        if (self.isBusy || (!$.IsTrading(self.symbolA))) {       // 如果 self 對象的屬性 isBusy 爲true 即繁忙 ,或者 合約類型 symbolA 不在交易時間內 (通過調用$.IsTrading這個模板導出函數獲取) ,則調用return返回
            return
        }
        var insDetailA = exchange.SetContractType(self.symbolA)  // 設置 合約類型 symbolA 
        if (!insDetailA) {                                       // 返回 null 則 調用 return 返回 
            return
        }
        var tickerA = exchange.GetTicker()                       // 獲取 symbolA 合約的行情信息
        if (!tickerA) {                                          // 獲取失敗 調用return
            return
        }
        var insDetailB = exchange.SetContractType(self.symbolB)  // 設置 合約類型 symbolB 
        if (!insDetailB) {
            return
        }
        var tickerB = exchange.GetTicker()                       // 獲取 symbolB 合約的行情信息
        if (!tickerB) {
            return
        }

        self.update = _D(tickerA.Time)                           // 更新時間

        var action = null                                        // 動作變量
        var diffA = _N(tickerA.Buy - tickerB.Sell)               // A合約的 買一  減去  B合約的 賣一
        var diffB = _N(tickerA.Sell - tickerB.Buy)               // A合約的 賣一  減去  B合約的 買一
        self.diffA = diffA                                       // 賦值 給 對象 的 成員diffA
        self.diffB = diffB                                       // 賦值 ..

        for (var i = 0; i < self.dic.length && !action; i++) {   // 遍歷 網格的  節點 ,直到 遍歷結束  或者  有action 執行。
            if (self.dic[i].hold == 0) {                         // 如果 網格 節點的持倉量 爲 0
                if (self.dic[i].open <= diffA) {                 // 如果 網格 節點的開倉價 小於等於 差價A (即 A合約買一 減去 B合約的賣一 的差價 突破 網格節點的 開倉價)
                    action = [i, "sell", "buy", self.dic[i].amount]    // action 記錄下 網格節點   索引 、symbolA sell ,symbolB buy ,操作量 。
                } else if (self.dic[i].open <= -diffB) {               // -diffB 實際就是  tickerB.Buy - tickerA.Sell ,也就是 B合約買一 減去 A合約的賣一 的差價 突破 網格節點的 開倉價
                    action = [i, "buy", "sell", -self.dic[i].amount]   // action 記錄..
                }
            } else {                                                   // 網格節點的 持倉量 不爲0
                if (self.dic[i].hold > 0 && self.dic[i].cover >= diffB) {             // 如果 節點的持倉量 大於0  即 正對沖 持倉 A合約持空,B合約持多。 並且 平倉差價(合約A sell ,合約B buy)小於平倉線
                    action = [i, "closesell", "closebuy", self.dic[i].hold]           // action 記錄下 網格節點  索引 , 合約A 平空倉 , 合約B 平多倉, 按照節點持倉量 平。
                } else if (self.dic[i].hold < 0 && self.dic[i].cover >= -diffA) {     // 如果持倉量 小於0 , 並且 -diffA 即 tickerB.Sell - tickerA.Buy ,小於平倉線
                    action = [i, "closebuy", "closesell", self.dic[i].hold]           // action 記錄下 ..
                }
            }
        }

        if (!action) {                                                                // 如果 action  爲初始賦值的 null ,即沒有 被 賦值操作。 調用 return 返回
            return
        }

        Log("A賣B買: " + _N(diffA) + ", A買B賣: " + _N(diffB), ", Action: " + JSON.stringify(action))  // 如果 action 有值 ,輸出信息 差價A  差價B 和  action 儲存的數據。

        self.isBusy = true                                                                            // 有操作 即 鎖定, 爲繁忙狀態, 處理完成前 在該函數開始處都會觸發 return 

        self.q.pushTask(self.e, self.symbolA, action[1], self.dic[action[0]].amount, function(task, ret) {     // 調用 交易隊列對象q 的成員函數 把 具體操作 參數傳入 , 壓任務進隊列 等待處理。
            if (!ret) {                                                                                        // 回調函數, 如果完成的 返回值 ret 爲false 即 操作失敗, 
                self.isBusy = false                                                                            // 重新把 isBusy 賦值爲 false ,解除鎖定
                return                                                                                         // 返回
            }
            self.q.pushTask(self.e, self.symbolB, action[2], self.dic[action[0]].amount, function(task, ret) { // A 合約 操作 成功 則,壓入B合約的 操作任務  進 任務隊列 等待處理。
                if (!ret) {                                                                                    // 如果 A合約操作完成 B 合約 操作失敗 則 拋出異常
                    throw "開倉失敗..."
                }
                self.isBusy = false                                                                            // 解除鎖定
                if (task.action != "buy" && task.action != "sell") {                                           // 如果 調用該回調函數的 任務 不是開倉 操作
                    self.dic[action[0]].hold = 0;                                                              // 當前 操作的網格 節點 的持倉量 重新賦值爲 0
                } else {                                                                                       // 如果是 開倉操作
                    self.dic[action[0]].hold = action[3];                                                      // 把 當前任務 的下單量 參數 賦值給 當前網格節點的持倉量
                }
            })
        })
    }
    return self                                                                                                // 該函數 返回一個 對沖組合 對象
}

// 註釋版
function main() { 
    SetErrorFilter("ready|login|timeout")       // 過濾常規錯誤
    Log("正在與交易服務器連接...")                 
    while (!exchange.IO("status")) Sleep(1000); // 一個循環 直到 IO函數返回 true 連接上服務器後 跳出
    Log("與交易服務器連接成功")                    // 顯示與服務器連接 
    var mode = exchange.IO("mode", 0);          // 調整行情獲取模式。立即返回模式。
    if (typeof(mode) !== 'number') {            // 調整行情模式函數 如果返回的不是數值類型
        throw "切換模式失敗, 請更新到最新託管者!";   // 拋出錯誤異常
    } else {
        Log("已切換到適合多品種價格查詢的立即模式");
    }

    if (CoverAll) {                             // 如果 開啓平掉初始倉位的功能 CoverAll  啓動時平掉所有倉位(界面上的參數)
        Log("開始平掉所有殘餘倉位...");            // 顯示信息  平掉所有初始倉位 
        $.NewPositionManager().CoverAll();      // 調用商品期貨模板的導出函數生成一個對象,並調用該對象的CoverAll方法,平掉所有初始倉位。
        Log("操作完成");                         // CoverAll函數執行完 打印 日誌信息,顯示操作完成。
    }
    LogStatus("嘗試獲取持倉狀態")                 // 狀態欄 顯示嘗試獲取持倉狀態的信息
    var positions = _C(exchange.GetPosition)   // 獲取所有持倉信息。
    LogStatus("Ready")

    if (positions.length > 0 && !AutoRestore) { // 如果持倉信息 數組 positions 長度大於0 並且界面參數沒有開啓自動恢復
        throw "發現持倉, 請勾選自動恢復"            // 拋出錯誤異常
    }

    var pairs = []                              // 聲明一個數組, 用來儲存 處理對沖組合的對象。
    var q = $.NewTaskQueue(function(task, ret) { // 調用商品期貨模板導出函數,生成一個 隊列對象,用來處理併發的交易操作
        Log(task.desc, ret ? "成功" : "失敗")     // 匿名函數(回調) 在隊列中的任務完成後打印 顯示任務的描述,返回狀態。 
    })
    var arr = HedgeTable.split('(');            // 處理 界面參數 交易參考表, 按照 '('符號 分割字符串,初步 把多種合約控制表混合的字符串分割成 每一個組合爲一個元素的字符串數組。
    var tbl = {                                 // 聲明一個  tbl 對象 用來記錄 顯示在狀態欄的數據
        type: 'table',                          // 調用LogStatus 時 ,讓狀態欄顯示爲表格,詳見API , 此處類型需要設置爲 table
        title: 'Runtime',                       // 狀態欄表格的 標題
        cols: ['Pair', 'Open', 'Cover', 'Hold', 'DiffA', 'DiffB', 'Time'], // 表格的每列的表頭。
        rows: []                                                           // 表格的每行數據,初始時是一個空數組。
    };
    _.each(arr, function(item) {                                           // 一個JS 庫的迭代 函數, 把arr中的每個元素  作爲 (第二個參數) 匿名函數的參數 ,迭代執行匿名函數。
        if (item != '') {                                                  // 忽略 item 爲空字符串的 情況。item 即爲 每個合約組合 以及他們的 開倉控制表。
            var tmp = item.split(')');                                     // 根據 ')' 字符 分割 合約組合  和  控制表 。
            var pair = tmp[0].replace('(', '').split('&');                 // 合約組合字符串中存在 '('字符替換爲'' 空字符, 再進行按 '&' 字符分割爲 字符串數組。分別儲存 合約類型。
            var symbolDetail = _C(exchange.SetContractType, pair[0])       // 使用 第一個 合約類型 設置合約類型,然後Log 顯示 該合約的一些信息。
            Log("合約", symbolDetail.InstrumentName, "一手", symbolDetail.VolumeMultiple, "份, 最大下單量", symbolDetail.MaxLimitOrderVolume, "保證金率:", _N(symbolDetail.LongMarginRatio), _N(symbolDetail.ShortMarginRatio), "交割日期", symbolDetail.StartDelivDate);
            symbolDetail = _C(exchange.SetContractType, pair[1])           // 使用 第二個 合約類型 設置合約類型,然後Log 顯示 該合約的一些信息。
            Log("合約", symbolDetail.InstrumentName, "一手", symbolDetail.VolumeMultiple, "份, 最大下單量", symbolDetail.MaxLimitOrderVolume, "保證金率:", _N(symbolDetail.LongMarginRatio), _N(symbolDetail.ShortMarginRatio), "交割日期", symbolDetail.StartDelivDate);
            pairs.push(Hedge(q, exchanges[0], positions, pair[0], pair[1], tmp[1]))  // 用以上處理好的數據:q處理交易對象,exchanges[0]交易所對象,當前迭代的 合約組合中的第一個合約類型,第二個類型,交易控制表 這幾個變量作爲參數 傳入 Hedge 函數 ,Hedge函數返回一個對象 ,
                                                                                     //把該對象壓入pairs數組
        }
    });

    var ts = 0
    var lastUpdate = 0
    while (true) {                                                                 // 策略 主循環
        if (!exchange.IO("status")) {                                              // 如果 連接服務器狀態 爲false 則 程序進入睡眠1秒 跳過一下代碼,重複主循環。
            Sleep(1000)
            continue
        }

        var now = new Date().getTime()                                             // 獲取當前時間戳
        if (now - ts > (CalcPeriod * 60000)) {                                     // 如果 當前時間 距離上次 賬戶權益統計 的時間 大於 賬戶權益統計週期(分) 參數設定的值,執行 if 內代碼。
            var account = exchange.GetAccount()                                    // 獲取賬戶信息
            if (account) {                                                         // 如果正常獲取 account 數據 
                var obj = JSON.parse(exchange.GetRawJSON())                        // 調用 GetRawJSON API  獲取account 的詳細信息JSON 格式 用parse 函數解析爲 JS 對象。返回一個 對象給 obj
                $.PlotLine('賬戶權益', obj['Balance'] + obj['PositionProfit']);     // 調用 畫線模板的導出函數 $.PlotLine 畫賬戶權益曲線, 數值爲: obj的Balance屬性 加 PositionProfit 屬性
                ts = now                                                           // 把本次更新 賬戶權益 的時間戳 記錄在ts 變量 用於比較。
            }
        }
        // IO("wait") 會一直等待收到任何一個品種的行情推送信息, 返回收到行情的真實時間
        var n = exchange.IO("wait")                                                // IO("wait") 函數 返回的也是一個納秒級的 時間戳。
        // 計算行情信息傳到策略層花費的時間
        var idle = UnixNano() - n                                                  // 最新託管者 增加的內置函數,獲取納秒 時間

        if (now - lastUpdate > 5000) {                                             // 當前的時間戳如果 比上次記錄的更新時間(lastUpdate)值大 超過5000(5秒) 則執行 if 分支代碼
            tbl.rows = []                                                          // tbl 對象 的行設置 爲空數組,即清空。
            _.each(pairs, function(t) {                                            // 儲存對沖對象的數組 內的元素迭代 傳遞到匿名函數 作爲參數t。
                for (var i = 0; i < t.dic.length; i++) {                           // 遍歷當前對沖對象 的 交易量 控制數組 dic 
                    tbl.rows.push([t.name, t.dic[i].open, t.dic[i].cover, t.dic[i].hold, t.diffA, t.diffB, t.update])   // 把每一個 差價的 開平倉 值  持倉、當前的差價值 等信息 壓入到表格 行數組中
                }
            });
            LogStatus('`' + JSON.stringify(tbl) + '`\nUpdate: ' + _D() + ', Idle: ' + (idle/1000000) + ' ms')           // 把更新過的表格顯示在  狀態欄。顯示上更新時間。
            lastUpdate = now                                                                                            // 把當前的時間戳(毫秒) 更新給lastUpdate 變量 用於下一次比較。
        }

        _.each(pairs, function(t) {                                                                                     // 迭代 對沖對象 作爲 匿名函數的參數 t ,迭代執行。
            t.poll()                                                                                                    // 執行當前的 對沖對象的 成員函數  poll()
        });

        q.poll()                                                                                                        // 調用模板生成的  交易隊列控制對象 q 的成員函數 poll() 模擬併發執行交易任務
    }
}

可以複製到自己的編輯器裏面方便 查找變量名,如果有解釋不對的地方、理解錯誤、BUG 等 ,歡迎留言,QQ單M我,不勝感謝! ^_^

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