Dual Thrust 商品期貨 (註釋版)

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 過於頻繁導致 問題。
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章