股票与 ETF
策略结构与回调
股票与 ETF 回测策略的基本骨架:导入、STRATEGY_PARAMS 参数写法、自定义状态与日志,回调函数的调用时机,context 与 run_info 常用属性,时间判断方法与数据时点。
股票和 ETF 可以写在同一份策略里,共用回测任务设置的证券账户、现金和持仓;ETF 一级申赎和证券两融也用这个账户编号。本页讲一份策略的骨架,后面几页依次展开行情、账户、下单与特殊业务。日线和分钟回测的写法相同。
一、创建一份策略
在回测任务中选择国内股票账户,设置回测日期、初始资金、频率、基准、费用和滑点,再填写策略代码。频率日线为 1d、分钟为 1M。
- 账户:通过
context.run_info.stock_account读取,不在代码里硬编码账号。沪、深、北交所的股票和 ETF 都在这一个账户里,现金和持仓共用。 - 资金与授信:初始资金和两融额度都在任务设置里填写。给
context加自定义变量只是记录策略状态,不会替账户入金,也不会改变授信。 - 代码后缀:证券代码写完整后缀,沪市
.SH、深市.SZ、北交所.BJ,例如600000.SH、159919.SZ、920001.BJ。 - 单位:股票数量单位为股,ETF 为份,价格为元/股或元/份,金额为人民币元,不按「手」传参。
第一次运行,可以选日线 1d、初始资金 10 万元,直接用股票与 ETF 混合买卖这份完整策略。一级申赎通常需要更大的整篮资金,不能沿用二级示例的资金设置。
二、导入
from panda_backtest.api.api import *
需要查询历史数据时再加 import panda_data;数值计算和日期处理按需导入 math、datetime、pandas。
一份完整策略至少定义 initialize(context) 和 handle_data(context, data),其他回调按需添加。同名回调只保留一份。
三、策略参数
可调参数集中写在 STRATEGY_PARAMS 字典里。每个参数值只用标量或字符串列表字面量:整数、小数、布尔值、字符串和证券代码列表。不要放嵌套字典、数字列表、函数调用或计算表达式;数量映射这类复杂结构在初始化函数里建立。
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_date、end_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_date 的 date 用 YYYYMMDD,且必须在本次交易日列表内。偏移越界时返回 '99990101',使用前要检查;不要用它查询回测区间之前的预热日期,那种情况用 panda_data.get_prev_trade_date(见行情与历史数据)。
九、数据时点与撮合价格
matching_type=1 用当前 Bar 的开盘价作为撮合参考,其他值用当前 Bar 的收盘价;最终价格还会受滑点、限价和涨跌停范围约束。
日线 Bar 包含当日完整行情:按开盘价成交的策略,只能用此前已知的信息做决策;当日收盘产生的信号,也不代表现实中必能按同一个收盘价成交。用收盘数据生成的信号,应在之后的行情时点交易,不能按同一根行情的开盘价「回头」成交。
历史行情、因子和股票池查询放在初始化或盘前,避免每根分钟 Bar 重复请求。
