商品期貨套利 - 多品種網格對沖模型 註釋版
- #### 代碼:
// 商品期貨套利 - 多品種網格對沖模型 註釋版
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我,不勝感謝! ^_^