vnpy station支持图形化的操作界面,然而我们在进行模板调试的时候更多地需要在命令行下进行debug,为此需要在python运行环境中编制程序,调用vnpy回测模块。
一、真相只有一个
在一个稍微大一些的面向对象程序中,总会有俄罗斯套娃似的多层封装,找到真正的功能代码并不总是那么容易。
在Qt图形界面,开启回测的命令是在/vnpy/app/ui/widget.py的start_backtesting函数中启动的,在这个函数中,调用了对应的engine的start_backtesting函数,相关文件在/vnpy/app/cta_壯陽藥 backtester/engine.py。
我们找到BacktesterEigine类的start_backtesting方法,这个方法中,启用一个新的线程来运行同类的run_backtesting方法:
self.thread = Thread(
target=self.run_backtesting,
args=(
class_name,
vt_symbol,
interval,
……
)
)
而run_backtesting方法仍然是一个壳,它调用了另外一个类/vnpy/app/cta_strategy/BacktestingEngine,我们继续寻找。终于在/cnpy/app/cta_strategy/backtesting.py中找到了函数run_backtesting:
def run_backtesting(self):
""""""
if self.mode == BacktestingMode.BAR:
func = self.new_bar
else:
func = self.new_tick
self.strategy.on_init()
# Use the first [days] of history data for initializin
day_count = 0
ix = 0
for ix, data in enumerate(self.history_data):
if self.datetime and data.datetime.day != self.dat
day_count += 1
if day_count >= self.days:
break
self.datetime = data.datetime
self.callback(data)
self.strategy.inited = True
self.output("策略初始化完成")
self.strategy.on_start()
self.strategy.trading = True
self.output("开始回放历史数据")
# Use the rest of history data for running backtesting
for data in self.history_data[ix:]:
func(data)
self.output("历史数据回放结束")
从这里就可看得出回测原理,实际上是吧策略重新跑了一遍。func函数根据回测的数据类别(bar或是tick)而不同。这里的new_bar也好,new_tick也好都是重新实例化一个数据信号,跟实盘没有什么不同。
壯陽藥 class=”wp-block-quote”>小技巧:为了不把函数前面的空格复制进来,可以用vim的块复制命令,光标移动到def run_backtesting的“d”那里,按下Ctrl+V,通过键盘移动光标到要复制的末尾,按下y。疯狂按Page down,到文件末尾,按p进行粘贴。最后再按i进入插入模式,重新选择那些行复制。
二、为调用做好准备
engine与widget是一个完整地前后台关系。从/vnpy/app/cta_backtester/ui/widget/py图形界面中我们可以找到运行run_backtesting的代码:
result = self.backtester_engine.start_backtesting(
class_name, #策略名
vt_symbol, #品种
interval,
start,
end,
rate,
slippage,
size,
pricetick,
capital,
inverse,
new_setting
)
这里需要特殊注意的是new_setting这个参数,它是一个dict。指的是一个策略的具体参数,例如在双均线策略中,它是快慢两条均线的窗口大小。在图形界面中,它是在对话框中获得的:
old_setting = self.settings[class_name]
dialog = BacktestingSettingEditor(class_name, old_setting)
new_setting = dialog.get_setting()
这里的self是一个widget。看来settings是一个狠角色,它保存了每个策略的参数。我们不得不继续追问,最初的settings是从哪里来的呢。
整个widget.py文件,维护的就是一个叫做BacktesterManager的类。在这个类里,首先就是定义了成员变量setting_filename:
setting_filename = “cta_backtester_setting.json”
然而,这个json文件存储的是对于回测的设置,也就是class_name,vt_symbol,interval这些默认的配置,如下图。
真正的对于每个策略的参数设置是在构造函数中被调用的init_strategy_settings函数:
def init_strategy_settings(self):
“”””””
self.class_names = self.backtester_engine.get_strategy_class_names()
for class_name in self.class_names:
setting = self.backtester_engine.get_default_setting(class_name)
self.settings[class_name] = setting
self.class_combo.addItems(self.class_names)
三、正向调用
经过了前面两节的大量分析,我们终于开始着手调用start_backtesting了。首先需要从json文件中获取回测参数,之后使用get_default_setting函数获取策略参数。
3.1回测参数
关于回测的参数,照猫画虎,程序中写入:
strategy_setting = back_engine.get_default_setting(‘AtrRsiStrategy’)
相应的终端打印出来输出:
{‘class_name’: ‘AtrRsiStrategy’, ‘vt_symbol’: ‘rb1910.SHFE’, ‘interval’: ‘d’, ‘rate’: 2.5e-05, ‘slippage’: 5.0, ‘size’: 10.0, ‘pricetick’: 0.2, ‘capital’: 1000000.0, ‘inverse’: False}
3.2策略参数
策略参数需要调用函数
strategy_setting = back_engine.get_default_setting('AtrRsiStrategy')
终端打印输出:
{‘atr_length’: 21, ‘atr_ma_length’: 10, ‘rsi_length’: 5, ‘rsi_entry’: 16, ‘trailing_percent’: 0.8, ‘fixed_size’: 1}
3.3启动回测
终于可以开始调试回测了,好激动:
back_engine.start_backtesting(
back_setting['class_name'],
back_setting['vt_symbol'],
back_setting['interval'],
datetime(2019,6,1),
datetime(2019,8,1),
back_setting['rate'],
back_setting['slippage'],
back_setting['size'],
back_setting['pricetick'],
back_setting['capital'],
back_setting['inverse'],
strategy_setting
)
我们对程序进行debug,程序能够正确地进行读取数据(在sqlite数据库已经有数据的前提下,如果没有请去前文《vnpy不使用rqdata,尝试tushare》准备数据)
我们选用的是AtrRsiStrategy这个策略,起始日期是2019年3月1日,结束日期是2019年6月1日,回测的合约是rb1910.SHFE,经过调试在5月23日出现了交易信号:
之前的文章中,经常会出现命令行无法退出的情况,今天实在忍无可忍,故去寻找原因:main_engine没有退出,可以通过下面的方法轻松解决:
main_engine.close()
附录:完整代码
from vnpy.event import EventEngine
from vnpy.trader.engine import MainEngine
from vnpy.app.cta_backtester import CtaBacktesterApp
from datetime import datetime
from vnpy.trader.utility import load_json
def main():
event_engine = EventEngine()
main_engine = MainEngine(event_engine)
app = CtaBacktesterApp
main_engine.add_app(app)
print(main_engine.apps)
back_engine = main_engine.get_engine("CtaBacktester")
print(back_engine)
back_engine.init_engine()
vt_symbol = "rb1909.SHFE" #实际上下载的数据要跟回测匹配,
interval = "d" #因为我已经提前下好了,所以这里就可以把这一部分注释
start = datetime(2019,5,1)
end = datetime(2019,8,1)
back_engine.start_downloading(vt_symbol,interval,start,end)
back_setting = load_json("cta_backtester_setting.json")
print(back_setting)
strategy_setting = back_engine.get_default_setting('AtrRsiStrategy')
print(strategy_setting)
back_engine.start_backtesting(
back_setting['class_name'],
back_setting['vt_symbol'],
back_setting['interval'],
datetime(2019,3,1),
datetime(2019,6,1),
back_setting['rate'],
back_setting['slippage'],
back_setting['size'],
back_setting['pricetick'],
back_setting['capital'],
back_setting['inverse'],
strategy_setting
)
main_engine.close()
print('all over')
return
if __name__ == "__main__":
main()