股票与 ETF / 策略结构与回调

股票与 ETF

策略结构与回调

股票与 ETF 回测策略的基本骨架:导入、STRATEGY_PARAMS 参数写法、自定义状态与日志,回调函数的调用时机,context 与 run_info 常用属性,时间判断方法与数据时点。

股票和 ETF 可以写在同一份策略里,共用回测任务设置的证券账户、现金和持仓;ETF 一级申赎和证券两融也用这个账户编号。本页讲一份策略的骨架,后面几页依次展开行情、账户、下单与特殊业务。日线和分钟回测的写法相同。

一、创建一份策略

在回测任务中选择国内股票账户,设置回测日期、初始资金、频率、基准、费用和滑点,再填写策略代码。频率日线为 1d、分钟为 1M

  • 账户:通过 context.run_info.stock_account 读取,不在代码里硬编码账号。沪、深、北交所的股票和 ETF 都在这一个账户里,现金和持仓共用。
  • 资金与授信:初始资金和两融额度都在任务设置里填写。给 context 加自定义变量只是记录策略状态,不会替账户入金,也不会改变授信。
  • 代码后缀:证券代码写完整后缀,沪市 .SH、深市 .SZ、北交所 .BJ,例如 600000.SH159919.SZ920001.BJ
  • 单位:股票数量单位为股,ETF 为份,价格为元/股或元/份,金额为人民币元,不按「手」传参。

第一次运行,可以选日线 1d、初始资金 10 万元,直接用股票与 ETF 混合买卖这份完整策略。一级申赎通常需要更大的整篮资金,不能沿用二级示例的资金设置。

二、导入

python
from panda_backtest.api.api import *

需要查询历史数据时再加 import panda_data;数值计算和日期处理按需导入 mathdatetimepandas

一份完整策略至少定义 initialize(context)handle_data(context, data),其他回调按需添加。同名回调只保留一份。

三、策略参数

可调参数集中写在 STRATEGY_PARAMS 字典里。每个参数值只用标量或字符串列表字面量:整数、小数、布尔值、字符串和证券代码列表。不要放嵌套字典、数字列表、函数调用或计算表达式;数量映射这类复杂结构在初始化函数里建立。

python
STRATEGY_PARAMS = {
    'stock_symbols': ['600000.SH', '000001.SZ'],
    'etf_symbols': ['510300.SH', '159919.SZ'],
    'stock_buy_quantity': 100,
    'etf_buy_quantity': 1000,
    'cash_buffer': 100.0,
}


def configure_symbols(context):
    context.symbols = (
        list(STRATEGY_PARAMS['stock_symbols'])
        + list(STRATEGY_PARAMS['etf_symbols'])
    )
    context.buy_quantities = {}
    for symbol in STRATEGY_PARAMS['stock_symbols']:
        context.buy_quantities[symbol] = STRATEGY_PARAMS['stock_buy_quantity']
    for symbol in STRATEGY_PARAMS['etf_symbols']:
        context.buy_quantities[symbol] = STRATEGY_PARAMS['etf_buy_quantity']

initialize 中调用 configure_symbols(context)。上面是参数片段,不是完整策略。

四、自定义状态与日志

策略自己创建的属性都挂在 context 上,例如证券池、历史表、当日操作标记、待处理订单和成交去重集合。资金、持仓和信用负债要通过账户对象读取,不直接赋值。

日志用 print。委托日志记录订单号、状态和原因;实际成交数量、价格和费用在成交回调里记录。账户不存在、参数非法等错误应明确报出;暂时没有行情时等下一次回调即可。

五、回调函数

函数 调用时机 适合放什么
initialize(context) 开始时执行一次,必需 参数、账户、股票池、状态变量,以及研究所需的历史数据
before_trading(context) 每个交易日盘前 查询历史数据、生成当日目标、重置每日标记
handle_data(context, data) 每次行情回调,必需 读取价格、现金和可卖量,提交委托
after_trading(context) 每个交易日盘后 记录资金、持仓、债务;不在这里下普通买卖单
on_stock_order_rtn(context, order) 股票、ETF 二级委托状态变化 记录订单号、未完成委托和终态
on_stock_trade_rtn(context, trade) 股票、ETF 二级实际成交 按成交编号去重,记录本次成交
stock_order_cancel(context, order) 二级撤单、拒单或部分成交后撤余 清理委托状态、打印原因
on_etf_cr_order_rtn(context, order) ETF 一级申赎订单状态变化 ETF 申购与赎回
on_etf_cr_confirm(context, result) ETF 一级申赎结果通知 收到后仍需检查状态
etf_cr_order_cancel(context, order) ETF 一级撤单通知 更新一级申请记录

二级回报对象的字段见下单、撤单与回报。两融交易走同一组二级回调,不另定义成交函数。

日线任务每个交易日调用一次 handle_data,分钟任务同一天会调用多次。「只买入一次」「每天调仓一次」都需要策略自己记状态:每日一次的逻辑保存日期标记,用 str(context.trade_date) 作为每日去重键;按五分钟间隔交易则用 context.trade_time 计算时间差。

注意

回报可能在下单函数返回之前就触发。回报里要用到的状态,都要在初始化时先建好;每次交易的防重复标记,要在调用下单函数之前设置。

六、context 常用属性

属性 类型 用法
context.run_info 对象 本次任务参数,见下一节
context.stock_account_dict 字典 按账户编号获取股票与 ETF 共用的证券账户
context.trade_date 字符串 当前交易日,YYYYMMDD
context.trade_time datetime 当前回测时间,用于比较时点、计算间隔
context.hms 字符串 当前时分秒,六位 HHMMSS
context.now 字符串 当前自然日,YYYYMMDD,不是日期时间对象
context.trade_date_list 字符串列表 本次回测的交易日列表
context.trade_date_len 整数 上述列表的长度,不是已经运行的天数

不要用本机日期或系统时间代替回测时间,也不要手动调用回调来推进交易日。

七、run_info 常用属性

属性 类型 含义
stock_account 字符串 本次任务的证券账户编号
stock_starting_cash 数值 初始资金,元
start_dateend_date 字符串 回测起止日期,YYYYMMDD
frequency 字符串 1d 日线、1M 分钟
matching_type 整数 1 使用开盘参考价,0 使用收盘参考价
slippage 数值 任务设置的滑点比例;0.001 表示 0.1%
commission_multiplier 数值 证券佣金参数,普通股票和 ETF 二级成交都会使用
benchmark 字符串 比较基准代码,不会因此自动买入
securities_margin_enabled 布尔值 本次任务是否启用证券两融
margin_credit_limit 数值,元 融资总额度,默认 0;股票与 ETF 共用,不是初始现金或当前可用额度

这些参数只用来读取本次任务设置,不应在策略中修改。两融相关的其余任务设置见融资融券

八、时间判断方法

调用 返回与用法
context.is_stock_trade(symbol=None) 布尔值;当前是否处于证券交易时段,可指定证券代码;不是资金或停牌检查
context.is_trade_date() 布尔值;当前自然日是否为交易日
context.is_last_trade_date() 布尔值;是否已到本次回测最后一个交易日
context.is_trade_date_end() 布尔值;当前时分秒是否等于证券收盘时刻
context.get_next_count_date(date, count) 日期字符串;在本次交易日列表中偏移 count 个交易日,可传负数

get_next_count_datedateYYYYMMDD,且必须在本次交易日列表内。偏移越界时返回 '99990101',使用前要检查;不要用它查询回测区间之前的预热日期,那种情况用 panda_data.get_prev_trade_date(见行情与历史数据)。

九、数据时点与撮合价格

matching_type=1 用当前 Bar 的开盘价作为撮合参考,其他值用当前 Bar 的收盘价;最终价格还会受滑点、限价和涨跌停范围约束。

日线 Bar 包含当日完整行情:按开盘价成交的策略,只能用此前已知的信息做决策;当日收盘产生的信号,也不代表现实中必能按同一个收盘价成交。用收盘数据生成的信号,应在之后的行情时点交易,不能按同一根行情的开盘价「回头」成交。

历史行情、因子和股票池查询放在初始化或盘前,避免每根分钟 Bar 重复请求。