手把手教你写一个商品期货多品种Dual Thrust策略

Author: 雨幕(youquant), Created: 2019-08-22 11:27:23, Updated: 2023-12-11 20:39:16

img

商品期货多品种Dual Thrust策略

Dual Thrust 是一个经典的趋势策略,众所周知,趋势策略在震荡行情下表现很不好。 如果改造成一个多品种的策略,是否会有好的表现呢?

  • 策略架构

    策略原理很简单,我们仅仅是研究如何把一个单品种的策略改造成一个多品种策略, 在发明者量化交易平台上,编写这样的策略也并不复杂。

    首先,商品期货策略经典的架构是:

    function main(){
        while(true){
            if(exchange.IO("status")){
                LogStatus(_D(), "已经连接CTP !")
            } else {
                LogStatus(_D(), "未连接CTP !")
            }
            Sleep(1000)
        }
    }
    

    我们无非是需要把 Dual Thrust 策略的逻辑迭代 到每个设置的合约下去执行。 这样我们就要先构造策略要执行哪些合约,并且设置每个合约在执行Dual Thrust 策略逻辑时的参数。

    不了解 Dual Thrust 策略的小伙伴可以先看下这个策略:

    img

    策略地址:https://www.youquant.com/strategy/13011

  • 策略参数设计

    可以看到原版的Dual Thrust 策略中,和交易逻辑相关的参数为: img

    我们保留这些参数名称,但是我们要把它们封装起来:

    var _Symbols = [
        {
            ContractTypeName : "rb1910",
            NPeriod : 4,
            Ks : 0.5,
            Kx : 0.5,
            AmountOP : 1,
        },
        {
            ContractTypeName : "i1909",
            NPeriod : 4,
            Ks : 0.5,
            Kx : 0.5,
            AmountOP : 1,
        },
        {
            ContractTypeName : "pp1909",
            NPeriod : 4,
            Ks : 0.5,
            Kx : 0.5,
            AmountOP : 1,        
        },
        {
            ContractTypeName : "MA909",
            NPeriod : 4,
            Ks : 0.5,
            Kx : 0.5,
            AmountOP : 1,        
        },
    ]
    

    这样我们把策略的参数写在策略代码中,当然这样是为了方便理解策略参数在代码中的体现形式,在自己实际开发策略时也可以把策略参数配置成为策略界面上的参数,这里为了让策略和界面无关,索性直接把策略参数写入代码中。

    可以看到我们将在策略代码中声明一个全局变量 _Symbols 用来储存策略参数,这个_Symbols变量是一个数组结构,其中每个元素都是一个对象,作为 Dual Thrust 交易逻辑 即将执行的合约品种的参数。

  • 可迭代的交易逻辑

    接下来重点的就是我们如何实现一个可迭代的 Dual Thrust 策略交易逻辑。

    观察了原版的Dual Thrust 策略的全局变量,我们发现,策略下单是通过「商品期货交易类库」构造的交易控制对象下单。 我们的策略依然使用商品期货交易类库封装好的函数下单,所以声明一个全局变量 manager。

    var manager = $.NewPositionManager()   
    

    通过调用 manager 的成员函数

    • manager.OpenShort : 开空仓
    • manager.OpenLong : 开多仓
    • manager.Cover : 平仓

    声明三种状态:

    var STATE_IDLE = 0;     # 空闲不持仓的状态
    var STATE_LONG = 1;     # 持多仓的状态
    var STATE_SHORT = 2;    # 持空仓的状态
    

    然后, 写一个初始化数值的函数:

    function init () {
        for (var i = 0 ; i < _Symbols.length ; i++) {
            _Symbols[i].State = STATE_IDLE
            _Symbols[i].LastBarTime = 0
            _Symbols[i].UpTrack = 0
            _Symbols[i].DownTrack = 0
        }
    }
    

    因为通过观察原版的Dual Thrust策略中,有一些需要参与交易逻辑计算、判断的数值,每个即将参与交易逻辑进行交易的合约都需要记录这些数值,例如:

    • State : 持仓状态
    • LastBarTime : K线数据中,最后一个K线bar的时间戳
    • UpTrack : 上轨数值
    • DownTrack : 下轨数值

    最后,就剩下编写出迭代交易逻辑的部分。

    function DualThrustProcess (symbols) {
        for (var i = 0 ; i < symbols.length ; i++) {
            var ContractTypeName = symbols[i].ContractTypeName
            var NPeriod = symbols[i].NPeriod
            var Ks = symbols[i].Ks
            var Kx = symbols[i].Kx
            var AmountOP = symbols[i].AmountOP  
    
            // 首先判断是不是在交易时间
            if (!$.IsTrading(ContractTypeName)) {
                continue
            }  
    
            // 切换为当前 symbol 参数的合约
            var insDetail = _C(exchange.SetContractType, ContractTypeName)
            
            // 判断K线长度
            var records = _C(exchange.GetRecords);
            if (!records || records.length <= NPeriod) {
                LogStatus("Calc Bars...");
                continue
            }  
    
            var Bar = records[records.length - 1]
            if (symbols[i].LastBarTime !== Bar.Time) {
                var HH = TA.Highest(records, NPeriod, 'High')
                var HC = TA.Highest(records, NPeriod, 'Close')
                var LL = TA.Lowest(records, NPeriod, 'Low')
                var LC = TA.Lowest(records, NPeriod, 'Close') 
                var Range = Math.max(HH - LC, HC - LL)  
    
                symbols[i].UpTrack = _N(Bar.Open + (Ks * Range))
                symbols[i].DownTrack = _N(Bar.Open - (Kx * Range))   
    
                symbols[i].LastBarTime = Bar.Time;
            }  
    
            if (symbols[i].State === STATE_IDLE || symbols[i].State === STATE_SHORT) {
                if (Bar.Close >= symbols[i].UpTrack) {
                    if (symbols[i].State !== STATE_IDLE) {
                        Log(ContractTypeName, "平空仓")
                        manager.Cover(ContractTypeName);
                    }
                    Log(ContractTypeName, "开多仓")
                    manager.OpenLong(ContractTypeName, AmountOP);
                    symbols[i].State = STATE_LONG;
                }
            }      
    
            if (symbols[i].State === STATE_IDLE || symbols[i].State === STATE_LONG) {
                if (Bar.Close <= symbols[i].DownTrack) {
                    if (symbols[i].State !== STATE_IDLE) {
                        Log(ContractTypeName, "平多仓")
                        manager.Cover(ContractTypeName);
                    }
                    Log(ContractTypeName, "开空仓")
                    manager.OpenShort(ContractTypeName, AmountOP);
                    symbols[i].State = STATE_SHORT;
                }
            }
        }
    }
    

    可以看到我们用了一个函数封装了交易逻辑,函数名为: DualThrustProcess 。 函数参数就传入 我们上边定义好的 参数数组 _Symbols

    这个函数调用就写在上文「策略架构」中讲的主循环中。

    function main(){
        while(true){
            if(exchange.IO("status")){
                LogStatus(_D(), "已经连接CTP !")
                DualThrustProcess(_Symbols)        // 封装好的交易逻辑函数,写在主循环中。
            } else {
                LogStatus(_D(), "未连接CTP !")
            }
            Sleep(1000)
        }
    }
    
  • 策略地址

    https://www.youquant.com/strategy/163212

  • 回测

    img

    img

    这样一个多品种的 Dual Thrust 策略就完成了,当然为了学习主要逻辑,这个策略减去了很多状态信息,日志输出, 有兴趣的可以增加上收益统计,图表显示,状态栏实时显示等功能。

    在发明者量化交易平台上开发,改写策略,是非常便捷、迅速的。 各种策略思路、创新的交易方法等你来发现、实现。


更多内容