Dual Thrust 商品期貨 (註釋版)
- 基本原理
在當天收盤,計算兩個值: 最高價-收盤價,和收盤價-最低價。然後取這兩個值較大的那個,乘以k值,結果稱爲觸發值。
在第二天開盤,記錄開盤價,然後在價格超過(開盤+觸發值)時馬上買入,或者價格低於(開盤-觸發值)時馬上賣空。
這個系統是反轉系統,沒有單獨止損。也就是說,反向信號也同時就是平倉信號。
圖解
Dual Thrust 策略包含完整的圖表顯示, 圖表動態更新,模板引用等功能, 可做學習模板使用.
策略的詳細介紹 : http://xueqiu.com/5256769224/32429363
註釋版 源碼:
/* 引用的類庫
《商品期貨交易類庫》 引用了這個 模板,具體模板代碼 可以在 策略廣場 找到,也有註釋版的(註釋版在論壇,即 交流社區)
*/
/* 參數名稱(全局變量) 中文含義 數據類型 值
ContractTypeName 合約品種 字符串(string) MA701
NPeriod 計算週期 數字型(number) 4
Ks 上軌係數 數字型(number) 0.5
Kx 下軌係數 數字型(number) 0.5
AmountOP 開倉合約張數 數字型(number) 1
Interval 重試間隔(毫秒) 數字型(number) 2000
LoopInterval 輪詢間隔(秒) 數字型(number) 3
PeriodShow 圖表最大顯示K線柱數 數字型(number) 500
NotifyWX 下單微信通知 布爾型(true/false) true
CoverAll 啓動策略時清空合約倉位 布爾型(true/false) false
*/
var ChartCfg = { // ChartCfg 是一個 全局 JS 對象, 用來 初始化 策略圖表 設置。 對象有很多關於圖表功能的屬性。 圖表庫爲:HighCharts
__isStock: true, // 該屬性用於 控制 是否顯示爲 單獨控制 數據序列(可以在圖表上取消單獨一個 數據序列的顯示),如果指定__isStock: false, 則顯示爲普通圖表
title: { // title : 圖表的主要 標題
text: 'Dual Thrust 上下軌圖' // title 的一個屬性 text : 標題的 文本, 這裏 設置爲 'Dual Thrust 上下軌圖' 該文本就會顯示在標題位置
},
yAxis: { // 圖表 座標Y軸的 相關設置
plotLines: [{ // Y軸上的 水平線 (和Y軸垂直) ,該屬性的值是一個數組,即 多條水平線的設置
value: 0, // 水平線 在Y軸上的座標值
color: 'red', // 水平線的顏色
width: 2, // 水平線的 線寬
label: { // 水平線 上的 標籤
text: '上軌', // 標籤的 文本
align: 'center' // 標籤 的顯示位置,這裏設置 爲 居中(即 :'center')
},
}, { // 第二條 水平線 ( [{...},{...}] 數組中的第二個 元素)
value: 0, // 水平線 在Y軸上的座標值
color: 'green', // 水平線的顏色
width: 2, // 水平線的 線寬
label: { // 標籤
text: '下軌',
align: 'center'
},
}]
},
series: [{ // 數據序列, 即用來 在圖表上顯示 數據線 、K線、標記 等等 內容的 數據。 也是一個數組 第一個 索引爲 0 。
type: 'candlestick', // 索引爲0 數據序列的 類型: 'candlestick' 表示爲 K線圖
name: '當前週期', // 數據序列的 名稱
id: 'primary', // 數據序列的ID ,用於 下一個數據序列相關設置。
data: [] // 數據序列的 數組, 用於儲存具體的 K線數據
}, {
type: 'flags', // 數據序列 ,類型 : 'flags',在圖表上顯示 標籤,表示 做多 和 做空。 索引爲 1 。
onSeries: 'primary', // 這個屬性 表示 標籤 顯示在 id 爲 'primary' 上。
data: [], // 保存 標籤數據的 數組。
}]
};
var STATE_IDLE = 0; // 狀態常量, 表示 空閒
var STATE_LONG = 1; // 狀態常量, 表示 持多倉
var STATE_SHORT = 2; // 狀態常量, 表示 持空倉
var State = STATE_IDLE; // 表示當前程序狀態 ,初始 賦值爲空閒
var LastBarTime = 0; // K線最後一柱的時間戳(單位爲 毫秒, 1000毫秒 等於 1秒, 時間戳是 1970年1月1日 到現在時刻的 毫秒數是一個很大的 正整數)
var UpTrack = 0; // 上軌 值
var BottomTrack = 0; // 下軌 值
var chart = null; // 用於接受 Chart 這個 API 函數返回的 圖表控制對象。用該對象(chart) 可以調用其成員函數 向圖表內寫入數據。
var Counter = { // 計數器, 用於記錄 盈虧次數
w: 0, // 贏次數
l: 0 // 虧次數
};
var manager = null; // 用於儲存 商品期貨交易類庫 模板導出函數 返回的 交易對象。該對象用於下單交易邏輯處理
var logSuffix = NotifyWX ? '@' : ''; // 微信開關, 界面上的參數NotifyWX 如果爲true , logSuffix 就被初始化 爲 '@' , 在程序中Log的時候 就會推送綁定的微信
// 具體可以參見Log 函數 這個API。
function onTick(exchange) { // 程序 主要函數,程序主要邏輯都是在該函數內處理。
if (!manager) { // 判斷 manager 是否初始化,如果 爲null 即 沒有初始化,則執行 if 模塊 ( {..} 內 )。
manager = $.NewPositionManager(); // 調用 商品期貨交易類庫 的導出函數 $.NewPositionManager , 該函數返回一個 用於控制具體下單交易的對象 給 manager。
if (_C(exchange.GetPosition).length > 0) { // 調用 exchange.GetPosition 函數獲取 當前持倉信息, _C 是容錯函數,用來重試(即 返回null了就會重試)。
// exchange.GetPosition 函數 返回一個數組, 訪問 這個返回值的 length 屬性,如果大於0,即,返回的數組內有元素,代表當前有持倉。
if (CoverAll) { // 如果 界面參數 CoverAll 被設置爲 true ,即:啓動策略時清空合約倉位
manager.Cover(ContractTypeName); // 調用 商品期貨交易類庫 生成的 底層交易控制對象 manager 的成員函數Cover ,參數爲 界面參數 上設置的合約代碼,
// 平掉所有合約爲 ContractTypeName 的持倉, 回測時注意 該合約ContractTypeName是不是 在回測時間段內存在。
Log("已清空所有相關倉位"); // 輸出日誌 提示信息
} else { // CoverAll 被設置爲false 則執行以下
throw "策略啓動前不能有持倉."; // 拋出錯誤 “策略啓動前不能有持倉”
}
}
Log('交易平臺:', exchange.GetName(), _C(exchange.GetAccount)); // 輸出日誌, 顯示當前設定的交易所對象的名稱(調用 exchange.GetName獲取),顯示當前
// 交易所對象的賬戶信息(調用 exchange.GetAccount獲取)
var insDetail = _C(exchange.SetContractType, ContractTypeName); // 獲取 ContractTypeName 合約的 詳細信息 ,並訂閱切換爲ContractTypeName合約。
Log("合約", insDetail.InstrumentName, "一手", insDetail.VolumeMultiple, "份, 最大下單量", insDetail.MaxLimitOrderVolume, "保證金率:", insDetail.LongMarginRatio.toFixed(4), insDetail.ShortMarginRatio.toFixed(4), "交割日期", insDetail.StartDelivDate);
// 輸出ContractTypeName 合約的詳細信息。
}
var records = _C(exchange.GetRecords); // 調用 exchange.GetRecords 函數 獲取K線數據
if (!records || records.length <= NPeriod) { // 如果 records 爲 null 或者 records 數組長度 小於等於 界面參數 NPeriod (計算週期)執行if 塊內代碼
LogStatus("Calc Bars..."); // 調用 LogStatus 在狀態欄 輸出 提示信息 : 意思爲 正在收集K線數據
return; // 返回 即 onTick 函數本次運行結束。用此處 判斷確保 K線數量足夠(超過 設置的 NPeriod 參數)
}
var Bar = records[records.length - 1]; // 當K線長度條件符合執行到此時, 取K線數據 records 的最後一個數據(即倒數第一根K線柱)
if (LastBarTime !== Bar.Time) { // 全局變量LastBarTime 記錄的時間戳 如果不等於當前的K線數據最後一個Bar的時間戳,即 :當前的K線數據最後一個Bar 是更新的(新出現的Bar)。
var HH = TA.Highest(records, NPeriod, 'High'); // 聲明HH 變量 ,調用 TA.Highest 函數計算 當前K線數據 NPeriod 週期內最高價 的最大值 賦值給HH。
var HC = TA.Highest(records, NPeriod, 'Close'); // 聲明HC 變量 ,獲取 NPeriod 週期內的 收盤價的最大值。
var LL = TA.Lowest(records, NPeriod, 'Low'); // 聲明LL 變量 ,獲取 NPeriod 週期內的 最低價的最小值。
var LC = TA.Lowest(records, NPeriod, 'Close'); // 聲明LC 變量 ,獲取 NPeriod 週期內的 收盤價的最小值。 具體 TA 指標API 參見平臺API 指標頁面。
var Range = Math.max(HH - LC, HC - LL); // 計算出 範圍
UpTrack = _N(Bar.Open + (Ks * Range)); // 根據 界面參數的 上軌係數 Ks 最新K線柱的 開盤價 等,計算出 上軌值。
DownTrack = _N(Bar.Open - (Kx * Range)); // 計算下軌值
if (LastBarTime > 0) { // 由於LastBarTime 該變量初始化設置的 值爲 0 ,所以第一次運行到此處 LastBarTime > 0 必定是 false ,不會執行if 塊內的代碼,而是會執行else 塊內的代碼
var PreBar = records[records.length - 2]; // 聲明一個變量 含義是 “前一個Bar” 把當前K線的 倒數第二Bar 賦值給它。
chart.add(0, [PreBar.Time, PreBar.Open, PreBar.High, PreBar.Low, PreBar.Close], -1); // 調用 chart 圖標控制對象的 add函數 更新K線數據(用獲取的K線數據的倒數第二Bar 去更新 圖標的倒數第一個Bar,因爲有新的K線Bar 生成)
// chart.add 函數的具體用法 參見API 文檔,和論壇裏的文章。
} else { // 程序第一次運行到此 必定執行 else 塊內代碼,主要作用是 把第一次獲取的K線一次性全部添加到圖表上。
for (var i = Math.min(records.length, NPeriod * 3); i > 1; i--) { // 此處執行一個 for 循環, 循環次數 使用 K線長度和NPeriod 3倍 二者中最小的值,可以保證初始的K線不會畫的太多太長。索引是從大到小的。
var b = records[records.length - i]; // 聲明一個 臨時 變量b 用來取每次循環 索引爲 records.length - i 的K線柱 數據。
chart.add(0, [b.Time, b.Open, b.High, b.Low, b.Close]); // 調用 chart.add 函數 向圖表添加K線柱,注意 add函數最後一個參數如果傳入-1 就是更新圖表上最後一個Bar(柱),如果沒傳參數,就是向最後添加Bar
// 執行完 i 等於 2 這次循環後(i-- 了已經,此時爲1了),就會觸發 i > 1 爲false 停止循環, 可見 此處代碼只處理到 records.length - 2 這個Bar
// 最後一個Bar 沒有處理。
}
}
chart.add(0, [Bar.Time, Bar.Open, Bar.High, Bar.Low, Bar.Close]); // 由於以上 if 的2個分支 都沒處理 records.length - 1這個Bar,所以 此處 處理。添加最新出現的Bar 到圖表中。
ChartCfg.yAxis.plotLines[0].value = UpTrack; // 把計算出來的 上軌值 賦值給 圖表對象(區別於 圖表控制對象chart),用於稍後顯示。
ChartCfg.yAxis.plotLines[1].value = DownTrack; // 賦值下軌值
ChartCfg.subtitle = { // 設置副標題
text: '上軌: ' + UpTrack + ' 下軌: ' + DownTrack // 副標題文本 設置 ,在副標題上顯示出 上軌 下軌 值。
};
chart.update(ChartCfg); // 用圖表對象 ChartCfg 更新圖表
chart.reset(PeriodShow); // 刷新 根據界面參數 設置的 PeriodShow 變量 ,只保留PeriodShow 的值數量的 K線柱。
LastBarTime = Bar.Time; // 此次新產生的 Bar 的時間戳 更新給 LastBarTime 用於判斷 下次循環 獲取的K線數據最後一個Bar 是否是新產生的。
} else { // 如果 LastBarTime 等於 Bar.Time 即: 沒有新的K線Bar 產生。 則執行一下 {..} 內代碼
chart.add(0, [Bar.Time, Bar.Open, Bar.High, Bar.Low, Bar.Close], -1); // 用當前K線數據的最後一個Bar(K線的最後一個Bar 即當前週期的Bar是不斷在變化的),更新圖表上的最後一個K線柱。
}
LogStatus("Price:", Bar.Close, "Up:", UpTrack, "Down:", DownTrack, "Wins: ", Counter.w, "Losses:", Counter.l, "Date:", new Date()); // 調用LogStatus 函數顯示 當前策略的數據在狀態欄上。
var msg; // 定義一個 變量msg 意義是 消息。
if (State === STATE_IDLE || State === STATE_SHORT) { // 判斷 當前狀態變量 State 是否等於 空閒 或者 State 是否等於 持空倉, 在空閒狀態下可以觸發做多, 在持空倉狀態下可以觸發 平多倉,並反手。
if (Bar.Close >= UpTrack) { // 如果當前K線的 收盤價 大於 上軌值 ,執行 if 塊內代碼。
msg = '做多 觸發價: ' + Bar.Close + ' 上軌:' + UpTrack; // 給 msg 賦值 ,把需要顯示的 數值 組合成字符串。
if (State !== STATE_IDLE) { // 如果 當前狀態不爲空閒(只有空閒和持空倉可以進入此處代碼,若不爲空閒,必定是持空倉),所以以下{..}內 處理 平空倉工作。
manager.Cover(ContractTypeName); // 調用 manager 的 Cover 函數 參數傳入 ContractTypeName 確定平倉 品種。執行 平倉具體操作。
var profit = manager.Profit(); // 聲明 profit 變量 儲存 manager 對象的成員函數 Profit 函數返回的 收益值。(Profit函數用於計算收益。)
LogProfit(profit); // 調用LogProfit 輸出收益並打印收益曲線, 此函數 可詳見API文檔。
msg += ' 平倉利潤: ' + profit; // 在msg 消息字符串後 添加收益信息。
}
Log(msg + logSuffix); // 輸出 日誌信息, 會根據logSuffix 的值(是否是 "@")進行微信同步消息推送, "@"的作用具體參見 API文檔 中的Log 函數。
manager.OpenLong(ContractTypeName, AmountOP); // 開多 或 反手 : 調用manager 對象的OpenLong 根據界面參數AmountOP設置的手數下單開多倉。
State = STATE_LONG; // 無論 開多倉 還是 反手 ,此刻程序狀態 要更新爲 持多倉。
chart.add(1, {x:Bar.Time, color: 'red', shape: 'flag', title: '多', text: msg}); // 在K線相應的位置 添加一個 標記 顯示 開多。
}
}
if (State === STATE_IDLE || State === STATE_LONG) { // 做空方向 與 以上 同理,不在贅述。代碼完全一致。
if (Bar.Close <= DownTrack) {
msg = '做空 觸發價: ' + Bar.Close + ' 下軌:' + DownTrack;
if (State !== STATE_IDLE) {
manager.Cover(ContractTypeName);
var profit = manager.Profit();
LogProfit(profit);
msg += ' 平倉利潤: ' + profit;
}
Log(msg + logSuffix);
manager.OpenShort(ContractTypeName, AmountOP);
chart.add(1, {x:Bar.Time, color: 'green', shape: 'circlepin', title: '空', text: msg});
State = STATE_SHORT;
}
}
}
function onexit() { // 掃尾函數,停止 程序時會觸發該函數執行。
var pos = _C(exchange.GetPosition); // 獲取持倉信息。
if (pos.length > 0) { // 如果有持倉信息
Log("警告, 退出時有持倉", pos); // 輸出 日誌 提示。
}
}
function main() { // 策略程序的 主函數。(入口函數)
if (exchange.GetName() !== 'Futures_CTP') { // 判斷添加的交易所對象 的名稱(通過exchange.GetName函數獲取) 如果不等於 'Futures_CTP' 即:添加的不是傳統期貨交易所對象。
throw "只支持傳統商品期貨(CTP)"; // 拋出異常
}
SetErrorFilter("login|ready"); // 設置錯誤過濾
LogStatus("Ready..."); // 狀態欄顯示 “準備..”文本
LogProfitReset(); // 清空之前的收益日誌
chart = Chart(ChartCfg); // 調用API Chart 函數, 用圖表對象ChartCfg初始化,返回 圖表控制對象。
chart.reset(); // 調用圖表控制對象的成員函數 reset 清空所有圖表上的 數據(數據序列的內容)
LoopInterval = Math.max(LoopInterval, 1); // 設定循環 時間,最小 不小於1.
while (true) { // 主要循環
if (exchange.IO("status") && $.IsTrading(ContractTypeName)) { // 必須確保 與交易所服務器 連接的狀態下(exchange.IO("status") 返回true 就是鏈接上了) 並且 在該品種(ContractTypeName)的交易時間內 (通過IsTrading函數判斷) 纔可執行 onTick 函數
onTick(exchange); // 執行 onTick 函數,即: 策略主要邏輯
} else { // exchange.IO("status") 返回 false 即: 未連接
LogStatus("未登錄狀態或不在交易時間段內"); // 在狀態欄 顯示 提示信息。
}
Sleep(LoopInterval * 1000); // 根據參數 LoopInterval 輪訓等待, 避免程序訪問 交易所 API 過於頻繁導致 問題。
}
}