Dual Thrust 商品期货 (注释版)

Author: 扫地僧, Created: 2017-05-04 00:36:24, Updated: 2017-10-11 10:40:01

Dual Thrust 商品期货 (注释版)

  • 基本原理

在当天收盘,计算两个值: 最高价-收盘价,和收盘价-最低价。然后取这两个值较大的那个,乘以k值,结果称为触发值。 在第二天开盘,记录开盘价,然后在价格超过(开盘+触发值)时马上买入,或者价格低于(开盘-触发值)时马上卖空。 这个系统是反转系统,没有单独止损。也就是说,反向信号也同时就是平仓信号。

  • 图解

    img

    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 过于频繁导致 问题。
    }
}

相关内容

更多内容

期货程序化 python的呢