策略模块详解

source: wtpy/folder2/file1.md

策略基本结构

WonderTrader的策略非常简单,一共就4个接口函数on_initon_baron_tickon_calculate,核心逻辑只需要写入on_calculate里即可。

from wtpy import Context

class BaseCtaStrategy:
    '''
    策略基础类,所有的策略都从该类派生\n
    包含了策略的基本开发框架
    '''
    def __init__(self, name):
        self.__name__ = name
        
    
    def name(self):
        return self.__name__


    def on_init(self, context:Context):
        '''
        策略初始化,启动的时候调用\n
        用于加载自定义数据\n
        @context    策略运行上下文
        '''
        return

    
    def on_calculate(self, context:Context):
        '''
        K线闭合时调用,一般作为策略的核心计算模块\n
        @context    策略运行上下文
        '''
        return


    def on_tick(self, context:Context, stdCode:str, newTick:dict):
        '''
        逐笔数据进来时调用\n
        生产环境中,每笔行情进来就直接调用\n
        回测环境中,是模拟的逐笔数据\n
        @context    策略运行上下文\n
        @code       合约代码
        @newTick    最新逐笔
        '''
        return

    def on_bar(self, context:Context, stdCode:str, period:str, newBar:dict):
        '''
        K线闭合时回调
        @context    策略上下文\n
        @code       合约代码
        @period     K线周期
        @newBar     最新闭合的K线
        '''
        return

回调函数详解


为了详细的介绍各个接口函数的作用,我们以demo中的DualThrust策略为例,来参照介绍

from wtpy import BaseCtaStrategy
from wtpy import Context

class StraDualThrust(BaseCtaStrategy):
    
    def __init__(self, name:str, code:str, barCnt:int, period:str, days:int, k1:float, k2:float, isForStk:bool = False):
        BaseCtaStrategy.__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"

        #这里演示了品种信息获取的接口
        #pInfo = context.stra_get_comminfo(code)
        #print(pInfo)

        context.stra_get_bars(code, self.__period__, self.__bar_cnt__, isMain = True)
        context.stra_log_text("DualThrust inited")

        #读取存储的数据
        self.xxx = context.user_load_data('xxx',1)

    
    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"]

        '''
        !!!!!这里是重点
        1、首先根据最后一条K线的时间,计算当前的日期
        2、根据当前的日期,对日线进行切片,并截取所需条数
        3、最后在最终切片内计算所需数据
        '''

        #确定上轨和下轨
        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

    def on_bar(self, context:Context, stdCode:str, period:str, newBar:dict):
        return
  • 初始化接口 on_init 初始化接口,是一个重要的接口,最大的作用是通过拉取系统所需要的历史K线,向系统注册一个主K线的闭合回调,以及tick数据的回调。
      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")
    

    从上面的代码片段可以看到,DualThrust策略在初始化的时候,拉取了K线数据。WonderTrader里的K线数据都不用自己管理,开发策略的时候,直接调用接口就可以了。WonderTrader后台做了一系列的工作,保证K线处理的速度和准确度,用户不需要担心数据拷贝等细节问题。 调用了拉取历史K线的接口以后,系统就会自动注册对应K线的闭合事件回调以及tick数据回调,即激活了on_baron_tick事件。

  • K线闭合接口 on_bar K线闭合接口,K线闭合的时候系统会自动调用该接口。对于多条同周期K线的策略,比如配对交易,用到了两个关联品种的1分钟线K线。那么系统在1分钟线闭合的时候会自动触发两次on_bar事件,通知策略K线闭合。 如果策略对于K线闭合有特殊的响应,则可以重写该接口。K线闭合的闭合,一般来说按照on_init里读取K线数据的先后顺序来触发响应。如果主K线本轮也闭合了,当所有闭合K线回调完成以后,系统就会调用on_calculate接口触发策略主逻辑重算。
      def on_bar(self, context:Context, stdCode:str, period:str, newBar:dict):
          return
    
    • context是策略的环境,系统提供的所有接口都在context
    • stdCode是标准品种代码,如CFFEX.IF.HOT,这个代码和用户使用的代码一致,即使是主力合约代码,也不会转换成分月合约,方便用户处理
    • period是K线周期,和用户拉取的K线周期一致,格式如m1m3d1
    • newBar是闭合的K线的数据,是一个dict,字段如下
        {
        "time":202005121055,  #如果是日线,就是yyyyMMdd格式
        "open":3963.2,
        "high":3986.4,
        "low":3956.6,
        "close":3974.0,
        "volumn":1342
        }
      
  • tick回调接口 on_tick tick回调接口在收到新的tick数据的时候触发。对于使用分钟线的策略来说,一个交易日内K线会多次闭合触发on_bar。但是对于使用日线的策略来说,因为当日的K线并没有闭合,所以历史K线中是没有当日K线的数据的,这个时候就需要在on_tick中来读取最新交易日的数据了。
      def on_tick(self, context:Context, stdCode:str, newTick:dict):
          return
    
    • context是策略的环境,和on_bar一致的
    • stdCode是标准合约代码,同on_bar中的stdCode
    • newTick是最新的tick数据,是一个dict,字段如下
        {
        "time": 20200512111053700,  #tick时间,精确到毫秒,yyyyMMddhhmmssmmm
        "open": 3963.2,
        "high":3986.4,
        "low":3956.6,
        "price":3974.0
        }
      
  • 策略重算接口 on_calculate on_calculate是策略的核心逻辑所在,是整个策略的重中之重。on_calculate是以主K线的闭合来触发的。当主K线闭合后,并且所有的K线的闭合事件都已经回调以后,就会触发on_calculate进行主逻辑重算。context具体接口和用法我们在后面会详细介绍,这里就不作赘述了。
      def on_calculate(self, context:Context):
          '''
          主K线闭合后调用,一般作为策略的核心计算模块\n
          @context    策略运行上下文
          '''
          return