10分钟打造WonderTrader上的期货日内交易策略

WonderTrader2.png

今天咱们来用WonderTraderpython子框架wtpy来实际编写一个期货日内交易的策略。而后咱们会先设定一组参数进行第一轮测试,再根据第一轮测试的结果,调整好参数之后,再进行第二轮测试。借此来演示一下wtpy中策略如何编写以及回测。python

准备工做


  • 安装wtpy。在安装了python3.6以上的计算机上执行一下命令。git

    $ pip install wtpy

    或者直接下载whl文件到本地进行安装
    阿里云镜像地址:https://mirrors.aliyun.com/py...
    pipy地址:https://pypi.org/project/wtpy...github

  • github复制demo
    咱们选用期货回测demo来做为基准的demo进行修改。
    期货回测demo下载地址:https://github.com/wondertrad...
  • 准备历史回测数据
    咱们直接使用demo中自带的股指期货主力合约5分钟数据进行回测,文件名为CFFEX.IF.HOT_m5.csv。为了提升测试效率,咱们只选取最后近两个月时间的数据进行回测,具体为2019年9月10日到2019年10月31日。
    股指期货主力合约2019年9月10日-2019年10月31日走势
    IF主力走势
    2019年9月10日的开盘价为3976.2,2019年10月31日收盘价为3879.0,区间涨幅为-2.44%。

肯定策略算法


  • 选择策略算法
    咱们选择比较经典的DualThrust做为咱们策略的算法。一方面DualThrust流传了好久了,曾经有不少大机构都用这个模型获取到了足够多的收益;另外一方面,DualThrust的算法复杂度比较低,比较适合咱们做为演示策略来使用。
  • DualThrust的算法逻辑以下
    DualThrust.jpg
    MAX(HH-LC,HC-LL),做为计算上下边界的基准值,用今日开盘价做为基准价,而后用上边界系数下边界系数,分别计算出上边界的价格和下边界的价格,当最新价突破上边界或者下边界的时候,就是咱们发出信号的时候。
    可是在策略的实现中,咱们还须要考虑到已有持仓的时候如何处理,因此最终的策略逻辑以下:算法

    当持仓为0的时候,价格突破上边界时,开多进场,价格突破下边界时,开空进场
    当持仓为多的时候,价格突破上边界时,保持仓位,价格突破下边界时,多反空
    当持仓为空的时候,价格突破上边界时,空反多,价格突破下边界时,保持仓位

策略实现


  • 参数说明
    肯定了策略的算法之后,咱们须要肯定策略模块的参数。参数的设置,要综合考虑策略自己的参数,以及模块使用的参数。最终咱们肯定了以下的参数:json

    name        策略实例名称
    code        回测使用的合约代码
    barCnt      要拉取的K线条数
    period      要使用的K线周期,采用周期类型+周期倍数的形式,如m5表示5分钟线,d3表示3日线
    days        策略算法参数,算法引用的历史数据条数
    k1          策略算法参数,上边界系数
    k2          策略算法参数,下边界系数
    isForStk    DualThrust策略用于控制交易品种的代码

    咱们还能够将基本手数做为参数传递给策略模型,这样的话通用性更强。不过咱们这里就再也不增设参数了,默认手数都是1手。api

  • 最终策略源码以下框架

    from wtpy import BaseStrategy
    from wtpy import Context
    
    class StraDualThrust(BaseStrategy):
    
        def __init__(self, name:str, code:str, barCnt:int, period:str, days:int, k1:float, k2:float, isForStk:bool = False):
            BaseStrategy.__init__(self, name)
    
            self.__days__ = days
            self.__k1__ = k1
            self.__k2__ = k2
    
            self.__period__ = period
            self.__bar_cnt__ = barCnt
            self.__code__ = code
    
            self.__is_stk__ = isForStk
    
        def on_init(self, context:Context):
            code = self.__code__    #品种代码
            if self.__is_stk__:
                code = code + "Q"
    
            context.stra_get_bars(code, self.__period__, self.__bar_cnt__, isMain = True)
            context.stra_log_text("DualThrust inited")
    
        def on_calculate(self, context:Context):
            '''
            策略主调函数,全部的计算逻辑都在这里完成
            '''
            code = self.__code__    #品种代码
            
            # 交易单位,主要用于股票的适配
            trdUnit = 1
            if self.__is_stk__:
                trdUnit = 100
    
            #读取最近50条1分钟线(dataframe对象)
            theCode = code
            if self.__is_stk__:
                theCode = theCode + "Q"
            df_bars = context.stra_get_bars(theCode, self.__period__, self.__bar_cnt__, isMain = True)
    
            #把策略参数读进来,做为临时变量,方便引用
            days = self.__days__
            k1 = self.__k1__
            k2 = self.__k2__
    
            #平仓价序列、最高价序列、最低价序列
            closes = df_bars["close"]
            highs = df_bars["high"]
            lows = df_bars["low"]
    
            #读取days天以前到上一个交易日位置的数据
            hh = highs[-days:-1].max()
            hc = closes[-days:-1].max()
            ll = lows[-days:-1].min()
            lc = closes[-days:-1].min()
    
            #读取今天的开盘价、最高价和最低价
            lastBar = df_bars.iloc[-1]
            openpx = lastBar["open"]
            highpx = lastBar["high"]
            lowpx = lastBar["low"]
    
            '''
            !!!!!这里是重点
            一、首先根据最后一条K线的时间,计算当前的日期
            二、根据当前的日期,对日线进行切片,并截取所需条数
            三、最后在最终切片内计算所需数据
            '''
    
            #肯定上轨和下轨
            upper_bound = openpx + k1* max(hh-lc,hc-ll)
            lower_bound = openpx - k2* max(hh-lc,hc-ll)
    
            #读取当前仓位
            curPos = context.stra_get_position(code)/trdUnit
    
            if curPos == 0:
                if highpx >= upper_bound:
                    context.stra_enter_long(code, 1*trdUnit, 'enterlong')
                    context.stra_log_text("向上突破%.2f>=%.2f,多仓进场" % (highpx, upper_bound))
                    #修改并保存
                    self.xxx = 1
                    context.user_save_data('xxx', self.xxx)
                    return
    
                if lowpx <= lower_bound and not self.__is_stk__:
                    context.stra_enter_short(code, 1*trdUnit, 'entershort')
                    context.stra_log_text("向下突破%.2f<=%.2f,空仓进场" % (lowpx, lower_bound))
                    return
            elif curPos > 0:
                if lowpx <= lower_bound:
                    context.stra_exit_long(code, 1*trdUnit, 'exitlong')
                    context.stra_log_text("向下突破%.2f<=%.2f,多仓出场" % (lowpx, lower_bound))
                    #raise Exception("except on purpose")
                    return
            else:
                if highpx >= upper_bound and not self.__is_stk__:
                    context.stra_exit_short(code, 1*trdUnit, 'exitshort')
                    context.stra_log_text("向上突破%.2f>=%.2f,空仓出场" % (highpx, upper_bound))
                    return
    
        def on_tick(self, context:Context, stdCode:str, newTick:dict):
            return

第一轮回测


  • 肯定参数
    咱们采用股指期货主力合约5分钟K线进行回测,每次读取50条历史K线,用最近30条K线计算上下突破的边界,上边界系数初步定为0.1,下边界系数也初步定为0.1
  • 修改runBT.py中策略的参数,而后运行runBT.py函数

    from wtpy import WtBtEngine
    from wtpy.backtest import WtBtAnalyst
    
    from Strategies.DualThrust import StraDualThrust
    
    if __name__ == "__main__":
        #建立一个运行环境,并加入策略
        engine = WtBtEngine()
        engine.init('.\\Common\\', "configbt.json")
        engine.configBacktest(201909100930,201910311500)
        engine.configBTStorage(mode="csv", path=".\\storage\\")
        engine.commitBTConfig() #代码里的配置项,会覆盖配置文件configbt.json里的配置项
    
        '''
        建立DualThrust策略的一个实例
        name    策略实例名称
        code    回测使用的合约代码
        barCnt  要拉取的K线条数
        period  要使用的K线周期,m表示分钟线
        days    策略算法参数,算法引用的历史数据条数
        k1      策略算法参数,上边界系数
        k2      策略算法参数,下边界系数
        isForStk    DualThrust策略用于控制交易品种的代码
        '''
        straInfo = StraDualThrust(name='pydt_IF', code="CFFEX.IF.HOT", barCnt=50, period="m5", days=30, k1=0.1, k2=0.1, isForStk=False)
        engine.set_strategy(straInfo)
    
        #开始运行回测
        engine.run_backtest()
    
        #建立绩效分析模块
        analyst = WtBtAnalyst()
        #将回测的输出数据目录传递给绩效分析模块
        #init_capital为初始资金规模
        #rf为无风险收益率
        #annual_trading_days为每一年的交易日天数,用于计算年化收益
        analyst.add_strategy("pydt_IF", folder="./outputs_bt/pydt_IF/", init_capital=500000, rf=0.02, annual_trading_days=240)
        #运行绩效模块
        analyst.run()
    
        kw = input('press any key to exit\n')
        engine.release_backtest()
  • 回测执行结束之后,打开自从生成的绩效分析报告文件,查看绩效分析结果
    回测绩效概览
    回测绩效概览
    而后打开成交日志文件,查当作交明细
    成交明细1
    ……
    成交明细2
  • 回测结果分析
    从上面的绩效报告能够看出,在这组参数下,时间从2019年9月10日到2019年10月31日下午收盘截止,总共产生了370笔交易,即完整的开平370个回合,换算成成交的话,就是740次成交。
    虽然交易次数不少,可是收益却很不理想,2个月不到的时间,50w的资金,总共亏损了近10w,约20%。从上面的截图,咱们能够看到,在最后一笔出场的时候,总盈亏是4,980.00,也就是说策略的逻辑到最后是盈利的,可是盈利的金额很小。让咱们再看一下天天结算的资金状况。
    每日资金结算
    从上图咱们能够看到,2个月的时间,佣金一共花费了104,775.82,因此帐户总盈亏是-99,795.82。这样咱们就能够大体得出一个结论:由于上下边界不够宽,因此有不少噪音信号,也触发了买卖的逻辑,从而致使买卖频繁,佣金太高,最终致使亏损

第二轮回测


  • 从新调整参数
    根据上一轮的结果分析,咱们须要把上边界和下边界拓宽,从而过滤掉一些噪声波动,减小信号个数。因此咱们第二轮回测,将上边界系数改为0.5,将下边界系数改为0.3
  • 修改runBT.py,而后运行runBT.py进行回测测试

    straInfo = StraDualThrust(name='pydt_IF', code="CFFEX.IF.HOT", barCnt=50, period="m5", days=30, k1=0.5, k2=0.3, isForStk=False)
  • 再查看绩效报告
    回测绩效概览
    回测绩效概览
    成交明细
    成交明细
    每日资金结算
    image.png
  • 再分析结果
    从第二轮的绩效报告能够看出,当边界拓宽之后,交易信号减小到了14个回合,可是交易收益却达到了24,060.00,约为第一轮收益的5倍,而佣金却只有1,453.02元,比第一轮的佣金低了两个量级。最终累计收益也由第一轮的-19.96%提高到盈利4.31%,相对于-2.44%的基准收益率,相对收益率达到6.75%。(这个算法对不对?:orz:)

结束语


上面演示了在WonderTrader上构建一个期货日内交易策略的基本过程。回测稳定之后,策略就能够不做任何修改的直接放到实盘里去运行了。但愿可以对你们有所启发。
最后再打一波广告:
WonderTradergithub地址:https://github.com/wondertrad...
WonderTrader官网地址:https://wondertrader.github.io阿里云

相关文章
相关标签/搜索