一个完整策略只需要两步:
设置初始化函数: initialize,上面的例子中, 只操作一支股票: ‘000001.XSHE’, 平安银行
实现一个函数, 来根据历史数据调整仓位.
示例如下
def initialize(context):
# 定义一个全局变量, 保存要操作的股票,如000001平安银行
g.security = '000001.XSHE'
# 运行函数
run_daily(market_open, time='every_bar')
def market_open(context):
if g.security not in context.portfolio.positions:
order(g.security, 1000)
else:
order(g.security, -800)
数据
股票数据:我们拥有所有A股上市公司2005年以来的股票行情数据、市值数据、财务数据、上市公司基本信息、融资融券信息等。为了避免幸存者偏差,我们包括了已经退市的股票数据。
商品期货:我们支持从2005年以来上海国际能源交易中心、上期所、郑商所、大商所的行情数据,并包含历史产品的数据。
基金数据:我们目前提供了600多种在交易所上市的基金的行情、净值等数据,包含ETF、LOF、分级A/B基金以及货币基金的完整的行情、净值数据等,请点击基金数据查看。
金融期货数据:我们提供中金所推出的所有金融期货产品的行情数据,并包含历史产品的数据。
股票指数:我们支持近600种股票指数数据,包括指数的行情数据以及成分股数据。为了避免未来函数,我们支持获取历史任意时刻的指数成分股信息,具体见get_index_stocks。注意:指数不能买卖
行业板块:我们支持按行业、按板块选股,具体见get_industry_stocks
概念板块:我们支持按概念板块选股,具体见get_concept_stocks
宏观数据:我们提供全方位的宏观数据,为投资者决策提供有力数据支持。
所有的行情数据我们均已处理好前复权信息。
我们当日的回测数据会在收盘后通过多数据源进行校验,并在T+1(第二天)的00:01更新。
回测过程
准备好您的策略, 选择要操作的股票池, 实现handle_data函数
选定一个回测开始和结束日期, 选择初始资金、调仓间隔(每天还是每分钟), 开始回测
引擎根据您选择的股票池和日期, 取得股票数据, 然后每一天或者每一分钟调用一次您的handle_data函数, 同时告诉您现金、持仓情况和股票在上一天或者分钟的数据. 在此函数中, 您还可以调用函数获取任何多天的历史数据, 然后做出调仓决定.
当您下单后, 我们会根据接下来时间的实际交易情况, 处理您的订单. 具体细节参见订单处理
下单后您可以调用get_open_orders取得所有未完成的订单, 调用cancel_order取消订单
您可以在handle_data里面调用record()函数记录某些数据, 我们会以图表的方式显示在回测结果页面
您可以在任何时候调用log.info/debug/warn/error函数来打印一些日志
回测结束后我们会画出您的收益和基准(参见set_benchmark)收益的曲线, 列出每日持仓,每日交易和一系列风险数据。
使用Bar撮合
- 市价单:
- 模拟交易 - 当 “最新价+滑点” 在涨跌停范围内,则进行撮合,反之撤销
- 交易价格: 最新价 + 滑点
- 最大交易量: 不管是按天, 还是按分钟, 由于市价单都是同步完成, 下单那一刻无法知道当天成交量, 所以市价单都不考虑成交量, 全量成交.
- 所有市价单下单之后同步完成(也即 order_XXX 系列函数返回时完成), context.portfolio 会同步变化
- 限价单:
- 模拟交易(天、分钟): - 当 委托价 > 最新价+滑点,按市价单模式撮合
- 当 委托价 <= 最新价+滑点,则挂单,在Bar结束时按照Bar信息进行撮合:
- 当 委托价 > bar 的最低价,则成交价为委托价,成交量不超过 Bar 成交量 * order_volume_ratio
- 天、分钟相同
- 注意:如模拟盘下限价委托单,订单信息会在收盘后更新。
- 不是立即完成, 下单之后 context.portfolio.cash 和 context.portfolio.positions 不会同步变化.
- 按天模拟交易暂时不支持限价单
- 上述过程中, 如果实际价格已经涨停或者跌停, 则相对应的买单或卖单不成交, 市价单直接取消(log中有警告信息), 限价单会挂单直到可以成交.
- 一天结束后, 所有未完成的订单会被取消
- 每次订单完成(完全成交)或者取消后, 我们会根据成交量计算手续费(参见set_order_cost), 减少您的现金
- 更多细节, 请看order函数
注意: 无论是回测还是模拟, 所有风险指标(年化收益/alpha/beta/sharpe/max_drawdown等指标)都只会每天更新一次, 也只根据每天收盘后的收益计算, 并不考虑每天盘中的收益情况. 例外:
分钟模拟盘每分钟会更新策略收益和基准收益
按天模拟盘每天开盘后和收盘后会更新策略收益和基准收益
那么可能会造成这种现象: 模拟时收益曲线中有回撤, 但是 max_drawdown 可能为0.
拆分合并与分红
- 传统前复权回测模式:当股票发生拆分,合并或者分红时,股票价格会受到影响,为了保证价格的连续性, 我们使用前复权来处理之前的股票价格,给您的所有股票价格已经是前复权的价格。
- 真实价格(动态复权)回测模式:当股票发生拆分,合并或者分红时,会按照历史情况,对账户进行处理,会在账户账户中增加现金或持股数量发生变化,并会有日志提示。
使用前复权价格,不论回测开始时间、结束时间是何时,使用的数据都是基于今天(回测当天)或某个时间的复权因子进行前复权获得的价格,因此使用前复权价格进行回测,回测结果肯定有问题。示意图如下: 不论历史时刻1或历史时刻2,拿到的数据都是基于未来某一天的前复权价格,使用这样的数据存在未来函数(未来函数是回测最大的敌人之一) 使用真实价格回测模式,回测到历史时刻1,使用历史时刻1的真实价格撮合成交;如果需要复权,会使用的历史时刻1的复权因子,对“历史时刻1"之前的价格进行前复权,这样有效避免了未来函数,因为回测全程都不可能使用未来的数据。 你可能没有看懂,下面举个例子: 如现有一只股票,股价一直没有波动,只进行了拆分。
传统前复权回测模式 与 真实价格(动态复权)回测模式 区别
是否开启动态复权(真实价格)模式对模拟交易的影响
- 前复权回测模式 - 站在“历史时刻3”看历史数据:因为使用今天的复权因子,“历史时刻3”之前的股价均为2;
- 站在“历史时刻2”看历史数据:因为使用今天的复权因子,“历史时刻3”的股价是2;
- 站在“历史时刻1”看历史数据,因为使用今天的复权因子,“历史时刻2”和“历史时刻3”的股价是2;
- 真实价格回测模式
- 站在“历史时刻3”看历史数据:因为使用历史时刻3的复权因子,“历史时刻3”之前的股价均为8
- 站在“历史时刻2”看历史数据:因为使用历史时刻2的复权因子,“历史时刻3”的股价是4;
- 站在“历史时刻1”看历史数据:因为使用历史时刻1的复权因子,“历史时刻2”和“历史时刻3”的股价是2;
- 因为使用了未来的复权因子,前复权回测模式,回测过程中使用的价格是不正确的。
- 下面再举一个真实的例子,比较一下前复权回测模式和真实价格回测模式的区别 2007-01-30,波导股份的真实股价(绿色曲线)是低于格力电器(黑色曲线)的;但使用前复权价格,波导股份的价格会高于格力电器。 采用最简单的交易思路,购买股价低的股票并持有,前复权模式会买入格力电器,真实价格回测模式会买入波导股份。 下面我们进行回测,根据2007-01-30当天格力电器与波导股份的收盘价,买入低价位股票并持有到现在。回测结果如下所示: 前复权回测模式的回测结果: 初始资金:100,000 策略收益:776.54% 沪深300收益:21.98% 最大回撤:64.98% 真实价格回测模式的回测结果: 初始资金:100,000 策略收益:78.35% 沪深300收益:21.98% 最大回撤:79.78% 由回测结果不难看出,前复权回测模式因为存在未来函数,结果是不准确的,使用前复权回测模式可能会让你获得非常高的收益,但实盘时,效果却非常一般;在某些策略中,如使用到价格因子,前复权模式会导致回测中买卖信号与实际中不一致,从而导致回测结果不准确,影响策略在真实场景中的应用。 开启真实价格回测功能 其实很简单,只需一步即可搞定: 在initialize中使用set_option即可,如下所示:
def initialize(context):
set_option('use_real_price', True)
是否开启动态复权(真实价格)模式对模拟交易的影响
近来,很多用户反馈在模拟盘看到的有些股票价格与在炒股软件上看到的不一样,对此表示很疑惑。 这是因为在模拟交易中,在未开启动态复权(真实价格)模式时,我们是使用基于模拟交易创建日期的后复权价格。 后复权模式示意图如下图所示: 不开启真实价格模拟盘的运算结果是没有错误,只是会让您理解起来更费劲一些。 用户如果想知道今天的真实价格,还需知道模拟创建的日期,并进行复权计算。 为了让用户使用更便于理解、更真实的模拟系统,我们强烈建议您开启动态复权(真实价格)模式。开启方式:用户可在代码中调用set_option('use_real_price', True)
. 开启动态复权(真实价格)模式示意图如下图所示: 开启动态复权(真实)模式后,您看到的价格都是最新的,每到新的一天, 如果持仓中有股票发生了拆合或者分红或者其他可能影响复权因子的情形, 我们会根据复权因子自动调整股票的数量. 但不要跨日期缓存这些 API 返回的结果
我们强烈建议您开启动态复权(真实价格)模式,进行模拟与回测!
注意:
- 开启真实价格回测之后,为了让编写代码简单, 通过history/attribute_history/get_price/SecurityUnitData.mavg/vwap 等 API 拿到的都是基于当天日期的前复权价格. 另一方面, 你在不同日期调用 history/attribute_history/get_price/SecurityUnitData.mavg/vwap 返回的价格可能是不一样的, 因为我们在不同日期看到的前复权价格是不一样的. 所以不要跨日期缓存这些 API 返回的结果.
- 每到新的一天, 如果持仓中有股票发生了拆合或者分红或者其他可能影响复权因子的情形, 我们会根据复权因子自动调整股票的数量, 如果调整后的数量是小数, 则向下取整到整数, 最后为了保证context.portfolio.portfolio_value不变, context.portfolio.cash可能有略微调整.
- 更多详情请见 API - set_option