[量化学堂-新手专区]BigQuant回测模块详解


(iQuant) #1

导语:不熟悉BigQuant平台的回测机制,可能使刚接触BigQuant平台的小伙伴有些困惑,不知该如何编写策略。当使用某一回测平台时,如果不能对其回测处理机制了解清楚,我们很可能出现偷价漏价、未来函数等问题,这些问题对策略的影响是致命的。即使不出现这样的问题,很多时候,用户可能写的策略并没有达到预期的目的,因此了解回测机制非常重要。


作者:bigquant
阅读时间:10分钟
本文由BigQuant宽客学院推出,难度标签:☆☆☆

事件驱动机制

在策略回测中应用最为广泛的就是 事件驱动机制。先看定义:当某个新的事件被推送到程序中时,程序立即调用和这个事件相对应的处理函数进行相关的操作。 举个“栗子”让大家更好理解。

比如开发一个股指策略,交易程序对股指TICK数据进行监听,当没有新的行情过来时,程序保持监听状态不进行任何操作;当收到新的数据时,数据处理函数立即更新K线和其他技术指标,并检查是否满足策略的下单条件,如果满足条件就执行下单。

BigQuant平台的回测机制就是事件驱动机制。因为用户分析的数据是股票、期货等交易数据,这类数据的一个很大特点就是本身是标准化的时间序列数据,在各个交易软件上也是以K线的形式呈现,K线包含了交易的开盘价、最高价、最低价、收盘价。如图1所示:

$$图1 \ \ K线示意图$$

BigQuant平台回测机制是 把每一个K线当做一个事件,按照时间发生先后顺序,即从左往右依次运行。 在新的事件发生时,即出现了包含高开低收四个价格的K线,回测程序会调用这个K线的数据,如果没有触发策略信号,就什么也不做,如果触发了交易信号,则产生订单。

为了避免未来函数,即在产生订单的时候不使用未来数据,只能使用当前能够获得的数据,BigQuant平台进行了如下处理:当产生订单时,只能在下一个K线上完成订单成交,这样就能避免未来函数的问题,同时也能更加逼近现实真实情况,因为很多时候,当某根K线发出交易信号时,用户只能在下一个K线上成交订单。不仅如此,为了控制订单以某个预期的价格成交,还可以设置成交价格,最为常见的就是设置订单价格是下一个K线的开盘价。

了解BigQuant回测机制

先看BigQuant回测机制的概览图(图2):

$$图2 \ \ BigQuant回测机制概览图$$
BigQuant平台回测主体有两个使用频率很高的函数:initialize函数和handle_data函数,理解了这两个函数开发策略就再也不是什么难事了,结合上面K线图来理解这两个函数(如图3)。

$$图3 \ \ 两个函数与K线的关系示意图$$

从图中可以看出,其实一共有26个事件,即26根K线,第一根K线既对应黑色箭头,又对应灰色箭头,其余都只对应灰色箭头。回测主体函数的initialize函数只在第一个事件上调用,即第一根K线,因此很多初始设置可以放在initialize函数里面。每个K线都对应灰色箭头,表示每个事件都会调用handle_data函数,即从第一根K线到最后一根K线都会运行handle_data一次,于是很多策略逻辑部分就可以放在handle_data里。不知道这样解释大家是否能够更容易理解?

订单撮合顺序

通常开发策略时,先执行卖出操作,然后执行买入操作。
BigQuant交易引擎沿袭了策略编制习惯,优先撮合卖单,然后再撮合执行买单,在撮合买单时会比对可用现金和买单所需的金额,如果可用现金不足则会自动缩减买入股票数量。
模拟交易和回测中先撮合卖单后再撮合买单,这个撮合顺序是一致的。
撮合时显示的时间和价格是根据设置的买入价和卖出价决定的,例如如果设置买入价为open,则交易记录会显示买单的执行时间是9:30价格是当日的开盘价。

简单策略开发

本文策略案例位于文后,可以克隆参考

step1:新建可视化空白策略

由于BigQuant提供的是提供在线的Notebook云端研究平台,因此在首页点击“编写策略”按钮就进入个人账户页面,然后点击左上角的添加按钮(“+”)新建一可视化空白策略

step 2:设置股票池和回测起止时间

拖入证券代码列表模块,并设置回测起止时间,以及股票池范围。本例设置为贵州茅台600519.SHA,回测起止时间为2014-01-01~2017-12-31

step 3:设置回测模块交易逻辑

拖入Trade回测模块并连接证券代码模块。

我们点击Trade模块,可以看到右侧的属性栏,属性栏中可以设置策略基本参数

  • 成交率限制
  • 初始资金
  • 买入点
  • 卖出点
  • 回测采用的价格模式

此外可以看到,Trade模块属性栏中还包含了主函数数据准备函数初始化函数盘前处理函数。这些函数通常需要传入contextdata对象。

如下图结构所示回测模块由四个重要函数和两个关键对象组成。

在初始化函数中通常会设置手续费、滑点和全局变量。

在数据准备函数中通常计算技术指标或每日买卖列表。

主函数中通常编写策略的每日交易逻辑。

盘前处理函数中通常编写订单管理逻辑

我们可以在这四个个函数的代码框中看到context.xxxdata.xxx类似的代码,这些都是context和data对象的默认属性和方法。想要读懂代码,有必要参考文档认真学习这些对象的方法和含义,这里帮大家简单梳理一下这两个关键对象。

点击查看context对象详解

context对象中定义了标的对象、订单对象、交易账户对象和持仓对象,分别用于记录策略的标的、订单、账户信息和持仓信息。
image



点击查看data对象详解

data对象中定义了一些常用函数方法,如下图所示:

在了解熟悉回测模块的函数和关键对象后,我们可以通过调用关键对象查询持仓信息、适当修改买卖逻辑来实现自定义策略。

此例中的每日交易逻辑如下:

# 回测引擎:每日数据处理函数,每天执行一次
def bigquant_run(context, data):
    sid = context.symbol(context.instruments[0])
    cur_position = context.portfolio.positions[sid].amount
    if cur_position == 0:
        order(sid, 100)
    elif cur_position > 0:
        order(sid, -100)

即如果没有持仓则买入100股,如果已经有持仓则卖出100股。
可以参照回测文档常用模板中的策略尝试修改不同的策略逻辑。

最后我们在回测模块中设置:

  • 成交率:允许买入/卖出的金额占股票当日总成交额比例,如果设置成较小值例如0.000002则订单会部分成交。
  • 买入点、卖出点:策略买入/卖出时采用的价格,可以分别设置为开盘价/收盘价。
  • 初始资金:根据需要策略设置。
  • 自动取消无法成交订单:勾选后会根据股票涨跌停、停牌、成交率等状态判断能否买入/卖出来模拟真实交易环境。
  • 回测价格类型:真实价格/后复权价格,回测所采用的价格类型。回测时选择真实价格与后复权价格的结果一致,选择真实价格时会有除权派息等信息的输出提示。选择后复权价格时,模拟交易系统提示的下单量与策略下单量会存在差异,建议模拟交易时采用真实价格。
  • 回测产品类型:目前支持股票、期货等品种,根据回测需求进行选择。

image

step 4:运行策略

直接点击运行就可以让策略跑起来,回测速度非常快,直接输出回测结果,图表也是相当友好。

运行后可以查看策略的收益概况、交易详情、每日持仓以及日志输出等信息。

不知道大家现在对BigQuant的回测机制是不是更了解一些了,快克隆下策略自己试试吧。

小结:只有了解了平台的回测机制,才能写出更为灵活丰富的策略。


本文由BigQuant宽客学院推出,版权归BigQuant所有,转载请注明出处。

附件:一个简单的策略

克隆策略

    {"Description":"实验创建于2019/1/23","Summary":"","Graph":{"EdgesInternal":[{"DestinationInputPortId":"-16:instruments","SourceOutputPortId":"-4:data"}],"ModuleNodes":[{"Id":"-4","ModuleId":"BigQuantSpace.instruments.instruments-v2","ModuleParameters":[{"Name":"start_date","Value":"2014-01-01","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"2017-12-31","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"market","Value":"CN_STOCK_A","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"instrument_list","Value":"600519.SHA","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"max_count","Value":0,"ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"rolling_conf","NodeId":"-4"}],"OutputPortsInternal":[{"Name":"data","NodeId":"-4","OutputType":null}],"UsePreviousResults":false,"moduleIdForCode":1,"Comment":"","CommentCollapsed":true},{"Id":"-16","ModuleId":"BigQuantSpace.trade.trade-v4","ModuleParameters":[{"Name":"start_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"end_date","Value":"","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"handle_data","Value":"# 回测引擎:每日数据处理函数,每天执行一次\ndef bigquant_run(context, data):\n sid = context.symbol(context.instruments[0])\n cur_position = context.portfolio.positions[sid].amount\n if cur_position == 0:\n order(sid, 100)\n elif cur_position > 0:\n order(sid, -100)","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"prepare","Value":"# 回测引擎:准备数据,只执行一次\ndef bigquant_run(context):\n pass\n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"initialize","Value":"# 回测引擎:初始化函数,只执行一次\ndef bigquant_run(context):\n pass\n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"before_trading_start","Value":"# 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。\ndef bigquant_run(context, data):\n pass\n","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"volume_limit","Value":0.025,"ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"order_price_field_buy","Value":"open","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"order_price_field_sell","Value":"close","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"capital_base","Value":"100000","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"auto_cancel_non_tradable_orders","Value":"True","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"data_frequency","Value":"daily","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"price_type","Value":"真实价格","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"product_type","Value":"股票","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"plot_charts","Value":"True","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"backtest_only","Value":"False","ValueType":"Literal","LinkedGlobalParameter":null},{"Name":"benchmark","Value":"","ValueType":"Literal","LinkedGlobalParameter":null}],"InputPortsInternal":[{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"instruments","NodeId":"-16"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"options_data","NodeId":"-16"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"history_ds","NodeId":"-16"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"benchmark_ds","NodeId":"-16"},{"DataSourceId":null,"TrainedModelId":null,"TransformModuleId":null,"Name":"trading_calendar","NodeId":"-16"}],"OutputPortsInternal":[{"Name":"raw_perf","NodeId":"-16","OutputType":null}],"UsePreviousResults":false,"moduleIdForCode":2,"Comment":"","CommentCollapsed":true}],"SerializedClientData":"<?xml version='1.0' encoding='utf-16'?><DataV1 xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'><Meta /><NodePositions><NodePosition Node='-4' Position='156.03817749023438,189.13194274902344,200,200'/><NodePosition Node='-16' Position='296.0381774902344,351.2430419921875,200,200'/></NodePositions><NodeGroups /></DataV1>"},"IsDraft":true,"ParentExperimentId":null,"WebService":{"IsWebServiceExperiment":false,"Inputs":[],"Outputs":[],"Parameters":[{"Name":"交易日期","Value":"","ParameterDefinition":{"Name":"交易日期","FriendlyName":"交易日期","DefaultValue":"","ParameterType":"String","HasDefaultValue":true,"IsOptional":true,"ParameterRules":[],"HasRules":false,"MarkupType":0,"CredentialDescriptor":null}}],"WebServiceGroupId":null,"SerializedClientData":"<?xml version='1.0' encoding='utf-16'?><DataV1 xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'><Meta /><NodePositions></NodePositions><NodeGroups /></DataV1>"},"DisableNodesUpdate":false,"Category":"user","Tags":[],"IsPartialRun":true}
    In [10]:
    # 本代码由可视化策略环境自动生成 2019年1月23日 14:37
    # 本代码单元只能在可视化模式下编辑。您也可以拷贝代码,粘贴到新建的代码单元或者策略,然后修改。
    
    
    # 回测引擎:每日数据处理函数,每天执行一次
    def m2_handle_data_bigquant_run(context, data):
        sid = context.symbol(context.instruments[0])
        cur_position = context.portfolio.positions[sid].amount
        if cur_position == 0:
            order(sid, 100)
        elif cur_position > 0:
            order(sid, -100)
    # 回测引擎:准备数据,只执行一次
    def m2_prepare_bigquant_run(context):
        pass
    
    # 回测引擎:初始化函数,只执行一次
    def m2_initialize_bigquant_run(context):
        pass
    
    # 回测引擎:每个单位时间开始前调用一次,即每日开盘前调用一次。
    def m2_before_trading_start_bigquant_run(context, data):
        pass
    
    
    m1 = M.instruments.v2(
        start_date='2014-01-01',
        end_date='2017-12-31',
        market='CN_STOCK_A',
        instrument_list='600519.SHA',
        max_count=0,
        m_cached=False
    )
    
    m2 = M.trade.v4(
        instruments=m1.data,
        start_date='',
        end_date='',
        handle_data=m2_handle_data_bigquant_run,
        prepare=m2_prepare_bigquant_run,
        initialize=m2_initialize_bigquant_run,
        before_trading_start=m2_before_trading_start_bigquant_run,
        volume_limit=0.025,
        order_price_field_buy='open',
        order_price_field_sell='close',
        capital_base=100000,
        auto_cancel_non_tradable_orders=True,
        data_frequency='daily',
        price_type='真实价格',
        product_type='股票',
        plot_charts=True,
        backtest_only=False,
        benchmark=''
    )
    
    [2019-01-23 14:37:31.500136] INFO: bigquant: instruments.v2 开始运行..
    [2019-01-23 14:37:31.589817] INFO: bigquant: instruments.v2 运行完成[0.089704s].
    [2019-01-23 14:37:31.613632] INFO: bigquant: backtest.v8 开始运行..
    [2019-01-23 14:37:31.615855] INFO: bigquant: biglearning backtest:V8.1.6
    [2019-01-23 14:37:31.616644] INFO: bigquant: product_type:stock by specified
    [2019-01-23 14:37:39.423512] INFO: bigquant: 读取股票行情完成:1215
    [2019-01-23 14:37:39.465563] INFO: algo: TradingAlgorithm V1.4.2
    [2019-01-23 14:37:39.662095] INFO: algo: trading transform...
    [2019-01-23 14:37:40.026560] INFO: algo: handle_splits get splits [dt:2014-06-25 00:00:00+00:00] [asset:Equity(0 [600519.SHA]), ratio:0.8847588756860948]
    [2019-01-23 14:37:40.840372] INFO: algo: handle_splits get splits [dt:2015-07-17 00:00:00+00:00] [asset:Equity(0 [600519.SHA]), ratio:0.8932786981805351]
    [2019-01-23 14:37:40.841839] INFO: Position: position stock handle split[sid:0, orig_amount:100, new_amount:111.0, orig_cost:251.26000877036404, new_cost:224.45, ratio:0.8932786981805351, last_sale_price:224.73998440395653]
    [2019-01-23 14:37:40.842875] INFO: Position: after split: asset: Equity(0 [600519.SHA]), amount: 111.0, cost_basis: 224.45,             last_sale_price: 251.58999633789062
    [2019-01-23 14:37:40.843767] INFO: Position: returning cash: 212.86
    [2019-01-23 14:37:41.558310] INFO: algo: handle_splits get splits [dt:2016-07-01 00:00:00+00:00] [asset:Equity(0 [600519.SHA]), ratio:0.9788640205792929]
    [2019-01-23 14:37:42.274936] INFO: algo: handle_splits get splits [dt:2017-07-07 00:00:00+00:00] [asset:Equity(0 [600519.SHA]), ratio:0.9851089933732362]
    [2019-01-23 14:37:42.276213] INFO: Position: position stock handle split[sid:0, orig_amount:100, new_amount:101.0, orig_cost:458.10998437747764, new_cost:451.29, ratio:0.9851089933732362, last_sale_price:449.1899795579182]
    [2019-01-23 14:37:42.277220] INFO: Position: after split: asset: Equity(0 [600519.SHA]), amount: 101.0, cost_basis: 451.29,             last_sale_price: 455.97998046875
    [2019-01-23 14:37:42.278082] INFO: Position: returning cash: 229.81
    [2019-01-23 14:37:42.677791] INFO: Performance: Simulated 977 trading days out of 977.
    [2019-01-23 14:37:42.679052] INFO: Performance: first open: 2014-01-02 09:30:00+00:00
    [2019-01-23 14:37:42.680056] INFO: Performance: last close: 2017-12-29 15:00:00+00:00
    
    • 收益率40.74%
    • 年化收益率9.22%
    • 基准收益率73.0%
    • 阿尔法0.04
    • 贝塔0.13
    • 夏普比率0.81
    • 胜率0.56
    • 盈亏比1.22
    • 收益波动率7.63%
    • 信息比率-0.02
    • 最大回撤7.72%
    [2019-01-23 14:37:44.560988] INFO: bigquant: backtest.v8 运行完成[12.947315s].
    


    AI量化策略开发第六步:回测
    如何编辑回测模块——以羊驼策略复现为例