10分钟打造WonderTrader上的期货日内交易策略
今天我们来用WonderTrader的python
子框架wtpy
来实际编写一个期货日内交易的策略。然后我们会先设定一组参数进行第一轮测试,再根据第一轮测试的结果,调整好参数以后,再进行第二轮测试。借此来演示一下wtpy
中策略如何编写以及回测。
准备工作
- 安装
wtpy
。在安装了python3.6
以上的计算机上执行一下命令。
$ pip install wtpy
或者直接下载whl
文件到本地进行安装
阿里云镜像地址: https://mirrors.aliyun.com/pypi/simple/wtpy/
pipy
地址: https://pypi.org/project/wtpy/#files
- 从
github
复制demo
我们选用期货回测demo来作为基准的demo进行修改。
期货回测demo下载地址: https://github.com/wondertrader/wondertrader/tree/master/demos/py/backtest_fut
- 准备历史回测数据
我们直接使用demo中自带的股指期货主力合约5分钟数据进行回测,文件名为CFFEX.IF.HOT_m5.csv
。为了提高测试效率,我们只选取最后近两个月时间的数据进行回测,具体为2019年9月10日到2019年10月31日。
2019年9月10日的开盘价为3976.2,2019年10月31日收盘价为3879.0,区间涨幅为-2.44%。
确定策略算法
- 选择策略算法
我们选择比较经典的DualThrust
作为我们策略的算法。一方面DualThrust
流传了很久了,曾经有很多大机构都用这个模型获取到了足够多的收益;另一方面,DualThrust
的算法复杂度比较低,比较适合我们作为演示策略来使用。
DualThrust
的算法逻辑如下
用MAX(HH-LC,HC-LL)
,作为计算上下边界的基准值,用今日开盘价
作为基准价,然后用上边界系数
和下边界系数
,分别计算出上边界的价格和下边界的价格,当最新价突破上边界或者下边界的时候,就是我们发出信号的时候。 但是在策略的实现中,我们还需要考虑到已有持仓的时候如何处理,所以最终的策略逻辑如下:
- 当持仓为0的时候,价格突破上边界时,开多进场,价格突破下边界时,开空进场
- 当持仓为多的时候,价格突破上边界时,保持仓位,价格突破下边界时,多仓出场
- 当持仓为空的时候,价格突破上边界时,空仓出场,价格突破下边界时,保持仓位
策略实现
- 参数说明
确定了策略的算法以后,我们需要确定策略模块的参数。参数的设置,要综合考虑策略本身的参数,以及模块使用的参数。最终我们确定了如下的参数:
name 策略实例名称
code 回测使用的合约代码
barCnt 要拉取的K线条数
period 要使用的K线周期,采用周期类型+周期倍数的形式,如m5表示5分钟线,d3表示3日线
days 策略算法参数,算法引用的历史数据条数
k1 策略算法参数,上边界系数
k2 策略算法参数,下边界系数
isForStk DualThrust策略用于控制交易品种的代码
- 我们还可以将基本手数作为参数传递给策略模型,这样的话通用性更强。不过我们这里就不再增设参数了,默认手数都是1手。
- 最终策略源码如下
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"]
'''
!!!!!这里是重点
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
第一轮回测
- 确定参数
我们采用股指期货主力合约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()
- 回测执行结束以后,打开自从生成的绩效分析报告文件,查看绩效分析结果
然后打开成交日志文件,查看成交明细
……
- 回测结果分析
从上面的绩效报告可以看出,在这组参数下,时间从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)
- 再查看绩效报告
- 再分析结果
从第二轮的绩效报告可以看出,当边界拓宽以后,交易信号减少到了14个回合,但是交易收益却达到了24,060.00,约为第一轮收益的5倍,而佣金却只有1,453.02元,比第一轮的佣金低了两个量级。最终累计收益也由第一轮的-19.96%提升到盈利4.31%,相对于-2.44%的基准收益率,相对收益率达到6.75%。(这个算法对不对?:orz:)
结束语
上面演示了在WonderTrader
上构建一个期货日内交易策略的基本过程。回测稳定以后,策略就可以不作任何修改的直接放到实盘里去运行了。希望能够对大家有所启发。
WonderTrader是我们团队刚刚开源的量化交易平台。文档和demo方便还有待完善,期待有更多的人来使用并提出宝贵意见。
最后再打一波广告:
WonderTrader
的github
地址: https://github.com/wondertrader/wondertrader
WonderTrader
官网地址: https://wondertrader.github.io