成交时点失真
信号用收盘价算出来,就用同一根 K 线的收盘价成交。这在实盘里做不到,而日线频率下框架恰恰只能这样撮合——本文第二节给出源码依据。
RQAlpha 项目研究站 · 回测可信度审计
同一个策略、同一段历史,成交价的取法不同,收益就可能差出一个量级。这一页不教怎么把回测跑起来,而是回答一个更关键的问题:这条净值曲线里,有多少来自策略,有多少来自你没注意到的口径假设。下面按成交时点、A 股交易制度、成本与价格口径、统计纪律四条线逐项审计,并给出可复现的检查清单、失败模式,以及本机实测到的默认值与命令面。
依据官方文档与本机源码核验整理的四条审计线示意;非官方流程图。
-me/--match-engine 在当前版本已不存在,以帮助信息为准一张漂亮的收益曲线的来源只有两种:策略真的有效,或者口径不对。下面四类失真是 RQAlpha 回测里最常见的「看起来有效」。
信号用收盘价算出来,就用同一根 K 线的收盘价成交。这在实盘里做不到,而日线频率下框架恰恰只能这样撮合——本文第二节给出源码依据。
T+1、涨跌停、停牌、退市、100 股整手,任何一项没建模,都会让回测成交在现实中无法成交的价格上。
佣金费率、最低手续费、印花税、复权方式都有确切的默认值,默认值不等于你的真实成本——本文第四节把默认值逐项列出来。
样本内挑出的最优参数、只在存活股票上回测的股票池、没有基准的「绝对收益」,都是统计层面的失真。
这是回测可信度审计的入口。本站实测发现的首要问题是:很多人以为能选的「下一根开盘成交」,在日线回测里其实选不了——框架会放弃它、回落成当前 K 线收盘价撮合,并在日志里打印一行警告。
| 取值 | 成交价与时点 | 1d 频率下是否可用 | 与实盘的差距 | 适用场景 | 注意点 |
|---|---|---|---|---|---|
current_bar | 当前 Bar 收盘价成交 | 可用(也是默认) | 收盘价已知才成交,偏乐观 | 日线研究、先跑通流程 | 日线回测里通常能用的常规撮合方式 |
next_bar | 下一根 Bar 开盘价成交 | 会被放弃:源码里判断 frequency == "1d" 且撮合类型为 next_bar 时,改回 current_bar 并打警告 | 本意更接近实盘,但日线场景拿不到 | 分钟级 / tick 级回测 | 传了这个参数的结果与不传完全一样,别据此下结论 |
vwap | 按成交量加权平均价成交 | 可用 | 比收盘价温和,但仍假设收盘时能拿到当日均价 | 想降低单一价格点影响的日线研究 | 均价成交在实盘同样需要算法单配合 |
last | 按最新价成交 | 不可用(tick 频率专用) | — | tick 级策略 | 在非 tick 频率下传入会被拒绝 |
best_own / best_counterparty | 按本方/对手方最优价成交 | 不可用(tick 频率专用) | — | tick 级撮合研究 | 需要 tick 行情数据 |
counterparty_offer | 按 tick 多档挂单逐档成交 | 不可用(tick 频率专用) | 最接近真实撮合,对数据要求最高 | tick 回测、大单冲击研究 | 只有 tick 行情下才有意义 |
| 不传参数 | 按频率自动选择:日/分钟回测取 current_bar,tick 回测取 last | 等效于 current_bar | 等于用了偏乐观的那一档 | — | 想知道自己用的是哪一档,看结果文件里的撮合类型字段 |
-mt / --matching-type,可选值共七个;官方文档仓里的 rst 写的是 -me / --match-engine,与当前 rqalpha run --help 输出不一致,以帮助信息为准。② 非 tick 频率只允许三种撮合:当前 Bar 收盘价、下一根 Bar 开盘价、VWAP;tick 频率另有一套。③ 日线回测里 next_bar 会被替换为 current_bar,并打印 matching_type = 'next_bar' is abandoned when frequency == '1d'。-fq 1m 并用你自己的分钟数据(开源版不提供分钟数据源),这时 next_bar 才真的生效。-mt next_bar 跑出来的总收益、最大回撤、Sharpe 三项读数完全相同,日志里那行「日回测 next_bar 撮合方式已废弃」就是原因。制度约束决定「这笔单子能不能成交」。RQAlpha 把股票账户的相关行为放在 sys_accounts Mod 里,下表的默认值与字段名都来自本机安装实例的源码核验,可以直接对照你自己的运行环境。
| 制度约束 | 框架里的字段/接口 | 默认行为(实测) | 不建模的后果 | 怎么核对 | 注意点 |
|---|---|---|---|---|---|
| T+1(当日买入不可卖) | 账户配置 stock_t1;持仓对象有 sellable 字段 | stock_t1 默认 True;可卖数量 = 持仓 − 今日买入 − 已冻结 | 日内回转策略会凭空多出一批本不可行的交易 | 打印 get_position(id).quantity 与 sellable 对比 | 命令行可用 --no-stock-t1 关闭它;关掉之后的结果不能当 A 股结论 |
| T+1 的市场标记 | 合约字段 market_tplus(A 股为 1;公募基金默认 0) | 由合约属性决定是否累加「不可平仓」数量 | 把 T+0 标的的规则套到 A 股上 | 打印 instruments(id).market_tplus | 框架按合约属性判断,而不是写死市场 |
| 涨跌停 | bar.limit_up / bar.limit_down;判定按最小变动单位做容差 | 买卖方向触及涨跌停会被拒单,拒单原因文案形如 cannot buy due to limit up | 一字板里也能成交,策略会买到实际买不到的价格 | 打印信号日的涨跌停价与 trades 明细逐笔比对 | 涨跌停价为 NaN 时按「该标的没有涨跌停限制」处理 |
| 停牌 | is_suspended(order_book_id, count=1);bar 对象有 suspended 属性 | 前端风控对股票标的会拦截停牌委托;取数接口默认跳过停牌数据 | 停牌期间仍按上一价格成交,净值走势被抹平 | 对候选标的在信号日调用 is_suspended,与成交记录比对 | 停牌期的持仓市值如何计算,会直接影响回撤读数 |
| 退市与代码转换 | cash_return_by_stock_delisted;instrument_history(s);持仓结算时处理代码转换 | 退市默认按退市价返还现金(默认开启) | 股票池只含现存股票 → 幸存者偏差 | 检查股票池构造时点,以及是否使用含退市合约的接口取池子 | 要主动用带历史的合约接口做池子,才能消掉偏差 |
| 分红与红利税 | dividend_reinvestment;dividend_tax_enabled / dividend_tax_rate;结果里的 dividend_receivable | 分红再投资默认关闭;红利税默认关闭、税率 0 | 长期持有策略少算分红收益,或把红利税算成 0 | 选一只高分红标的,比对除权日前后持仓成本与现金 | 官方多次修过分红计算(未到账平仓、登记日与到账日跨多日等),跨版本比较要留意 |
| 100 股整手与价格取整 | 合约字段 round_lot(A 股 100);配置 round_price | 整手按合约属性;round_price 默认关闭(命令行为 -rp) | 小资金账户会算出买不到的股数 | 用 order_value 下单后检查数量是否为 100 的整数倍 | 整手进位会放大资金占用,是回测与实盘资金曲线常见的差距来源 |
价格口径与成本口径决定「每一笔交易赚了多少」。下面每个数字都来自 RQAlpha 本机安装实例的源码与默认配置,不是从文章里抄的——其中两项(最低手续费、资本利得税提示)与很多教程的描述不一致。
| 口径 | 默认值与设置方式(实测) | 对收益的影响 | 怎么核对 | 适用场景 | 注意点 |
|---|---|---|---|---|---|
| 复权方式 | history_bars 的 adjust_type 默认 pre(前复权),可选 pre / none / post | 口径不一致时长周期策略的收益率会被系统性算偏 | 显式传 adjust_type 并记录你的取值 | 长周期、高分红标的 | 基准收益按前复权价格序列计算,两者口径要先对齐 |
| 股票佣金 | 费率 0.0008(万分之八)乘以佣金乘数;乘数默认 1 | 费率越低,短线策略收益越虚高 | 拿成交金额乘费率,与结果里的累计交易成本对照 | 所有股票策略 | 命令行 -cm 已被标记为 deprecated,推荐用 -scm |
| 最低手续费 | 默认 5 元,且按订单累计:同一订单的首笔不足最低值时先按最低值收 | 小额多笔交易的真实成本被低估 | 小额订单逐笔手算,与结果里的交易成本比对 | 小资金、高频调仓 | 这套累计逻辑在文档里几乎不会写,属于源码级细节 |
| 印花税 | 卖出方向、股票品种才收,税率 0.0005(万分之五);开启 --pit-tax 后按历史税率,某切换日之前为 0.001 | 不考虑它时,高换手策略的成本被低估 | 把累计交易成本除以总成交额,看比例是否合理 | 所有 A 股股票策略 | 基金/ETF 不收这项;买入方向也不收 |
| 费用乘数开关 | -scm(股票佣金乘数)、-tm(税乘数);-cm / -smc 已 deprecated | 乘数是比例调节,不能替代真实费率结构 | 确认你用的参数在当前版本里是否仍被支持 | 费率与模型不一致时 | 把 deprecated 参数当正式用法,升级时容易踩空 |
| 滑点 | --slippage 传比例值;--slippage-model 可选滑点模型 | 滑点为 0 时,策略可以永远按理想价格成交 | 同一策略扫一组滑点值,看收益衰减曲线 | 中高频、流动性一般的标的 | 有涨跌停价的合约,滑点不会超出涨跌停范围(官方修复记录) |
| 资本利得税 | capital_gain_tax_rate 默认 0;未显式配置时框架会打印警告,并提示该默认值将在后续版本改为非零值 | 默认等于不计这块成本;换到新版本可能明显降低收益 | 先看运行日志里有没有这条警告,再决定是否显式配置 | 持有期跨月、有卖出实现的策略 | 该税按月结算,税基是年内可抵扣余额加当月实现盈亏 |
| 基准 | 配置项 benchmark 默认 None(没有基准参照) | 没有基准时,牛市里任何多头策略都像是有效策略 | 看结果文件里的基准字段是否为空 | 所有宣称「有超额收益」的结论 | 想要 alpha/beta 类指标,必须先显式设置基准 |
| 期货结算价类型 | futures_settlement_price_type 默认 close | 期货逐日结算的取值口径影响保证金与净值 | 对照你自己的结算规则 | 商品/股指期货策略 | 与交易所结算价口径可能不同,跨版本比较前先确认 |
按顺序过一遍:前 4 项是 RQAlpha 的框架口径(改配置),中间 4 项是成本与数据(改模型),后 4 项是统计纪律(改研究方法)。
| # | 审计项 | 怎么看 | 不合格的典型信号 |
|---|---|---|---|
| 1 | 成交时点是否写清 | 记录你用的是哪种撮合类型,并确认在 1d 下它是否真的生效 | 以为在用下一根开盘成交,实际被回落成当前 K 线收盘价 |
| 2 | 取数窗口是否包含当前 bar | 确认取数接口的窗口语义:日线回测下当前 bar 在什么阶段可见 | 在盘前拿到当日数据做决策,或用当日最高/最低价做阈值判断 |
| 3 | T+1 是否生效 | 打印当日买入后持仓的可卖数量 | 当日买入当日卖出,回测里毫无阻碍 |
| 4 | 涨跌停与停牌是否阻挡成交 | 对信号日查涨跌停价与停牌状态,与成交记录逐笔比对 | 一字板/停牌期间照常成交 |
| 5 | 复权口径是否统一 | 行情、基准、持仓成本用的是不是同一复权口径 | 基准与策略的取数口径不同,相对指标不可解释 |
| 6 | 佣金与最低手续费是否贴近真实 | 用自己的实际费率与最低收费复算一遍成交成本 | 手续费按 0 计,或忽略了 5 元的最低收费 |
| 7 | 印花税与资本利得税是否包含 | 看累计交易成本占成交额的比例;看日志里有没有资本利得税警告 | 高换手策略的成本占比明显偏低 |
| 8 | 滑点是否反映标的流动性 | 同一策略扫一组滑点,看收益衰减曲线 | 收益对滑点极度敏感,却只报了滑点为 0 的结果 |
| 9 | 基准是否设置 | 看结果里的基准字段与相对指标是否为空 | 只有绝对收益,没有相对基准的超额收益 |
| 10 | 股票池是否无幸存者偏差 | 股票池是否按回测起点的可得信息构造,是否包含后来退市的标的 | 只测今天仍在交易的股票,且时间跨度很长 |
| 11 | 参数是否做过样本外检验 | 把参数按时间切两段,看后一段是否还成立 | 参数在整段历史上表现极好,但没做过任何切分 |
| 12 | 结论是否写明版本与环境 | 页面或报告里有没有记下框架版本、撮合方式与费用设置 | 只说「收益率 X%」,不写任何前提 |
下表是在本机安装实例上跑出来的真实读数:策略是自写的 MA5/MA20 双均线,标的 000001.XSHE,回测区间 2021-01-04 至 2021-06-30,股票账户初始资金 10 万,期间共 6 笔成交。这些数字只用来演示口径敏感性,不代表策略有效性;换区间、换标的都会变。交易成本从结果文件的成交流水(佣金 + 税)汇总,不是估的。
| 变体 | 改动的口径 | 说明 | 总收益 | 最大回撤 | Sharpe | 累计交易成本 | 结果文件里的撮合类型 |
|---|---|---|---|---|---|---|---|
| V1 | 默认(不传撮合参数) | 不传 -mt:1d 频率自动选中当前 K 线收盘价撮合 | 12.06% | 16.77% | 0.873 | 663 元 | current_bar(默认) |
| V2 | 显式 -mt next_bar | 期望「下一根开盘成交」——日志打印「日回测 next_bar 撮合方式已废弃」,结果与 V1 完全一致 | 12.06% | 16.77% | 0.873 | 663 元 | current_bar(被回落) |
| V3 | 显式 -mt vwap | 按成交量加权平均价成交(1d 频率下允许的三种之一) | 13.46% | 15.23% | 0.960 | 673 元 | vwap |
| V4 | 加基准 -bm 000300.XSHG | 打开相对指标;默认无基准时这些字段是 NaN | 12.06% | 16.77% | 0.873 | 663 元 | current_bar;同期基准 +0.24%,alpha 0.259 |
| V5 | 加滑点与二倍佣金 | --slippage 0.002 -scm 2:成本上升,收益下降、回撤变大 | 10.12% | 17.45% | 0.754 | 1,150 元 | current_bar |
| V6 | 关闭 T+1 | --no-stock-t1:该策略当期没有日内回转交易,故读数不变 | 12.06% | 16.77% | 0.873 | 663 元 | current_bar |
15:00:00、价格即当日收盘价——这就是「当前 K 线收盘价撮合」的直接证据。如果你的策略靠尾盘或收盘价信号取胜,那它赚的很可能正是这个假设。下面每一条都对应 RQAlpha 里一个具体机制、本机实测现象,或一次官方修复记录。
| 现象 | 根因 | 怎么确认 | 怎么改 |
|---|---|---|---|
按旧文档写 --match-engine 报「No such option」 | 文档仓里的 rst 与当前命令行参数面不一致(现在是 -mt/--matching-type) | 执行 rqalpha run --help 看当期选项 | 所有参数以当期帮助信息为准,不照抄文章 |
按文档写 --no-plot 报「No such option」 | 当前版本只有 --plot 开关,没有反向开关 | 同上 | 不加该参数即可;出图相关选项见帮助信息里的绘图分组 |
跑官方示例报 No module named 'talib' | 官方示例里有策略依赖 TA-Lib,它不是默认安装的依赖 | 看报错里的策略文件与行号(框架会把用户策略的异常原样带出来) | 装 TA-Lib 后重跑,或改用不依赖它的示例与自写策略(本站实测用的是自写的双均线) |
以为在用 next_bar,实际是 current_bar | 日线频率下框架主动放弃 next_bar 并打印警告 | 看运行日志里的 warning 与结果文件里的撮合类型字段 | 改用策略内延后一根 bar 下单,或换分钟级回测 |
| 收益异常高、回撤异常小 | 用了信号模式(不经过撮合引擎) | 看帮助信息里的信号模式说明与运行日志 | 去掉信号模式,改用常规撮合重跑 |
| 一字板股票也能买到 | 涨跌停判定未生效,或用了更早的版本 | 打印信号日涨跌停价与成交记录比对 | 升级到按最小变动单位做容差的版本,并复核结论 |
| 长周期回测收益远高于实盘体验 | 股票池只含现存股票 → 幸存者偏差 | 检查股票池构造时点与是否包含退市标的 | 用带历史合约信息的接口按当时可得集合构造池子 |
| 小额账户回测赚很多、实盘买不进 | 资金不足与整手进位、最低手续费没有对齐 | 用同样的初始资金手算一笔成交的股数与成本 | 对齐 100 股整手与最低手续费,并检查现金是否为负 |
| 升级框架后同一策略结果变化 | 撮合默认值、税费默认值、复权与结算口径在不同版本被调整过 | 对比两个版本的变更记录与帮助信息 | 把版本号与撮合类型写进结论,升级后重跑基线再比较 |
用官方示例里的双均线策略做一次完整的 RQAlpha 回测审计。策略逻辑不是重点,重点是每一步都在记录口径。下面的命令都能直接照抄。
rqalpha run -f dual_ma.py -s 2021-01-01 -e 2021-06-30 --account stock 100000 -mt current_bar -bm 000300.XSHG -o result.pkl。(预期输出:回测日志 + 指标摘要)--slippage 0.002 -scm 2。(预期输出:累计交易成本上升、收益下降,两个结果文件的差额就是成本假设的影响)print(结果字典.keys()) 再写读取代码,并把交易成本从成交流水的佣金与税两列汇总。(预期输出:成本合计与结构清单)回测结论的差异,一部分来自策略,另一部分来自 RQAlpha 的版本。下表既有官方变更记录,也有本站实测到的当前参数面。
| 版本 / 时点 | 变更内容 | 对回测结论的影响 | 你该怎么核对 | 注意点 |
|---|---|---|---|---|
| 当前版本(实测) | 结果文件的字典键与旧文档示例不一致:实测键为 portfolio / positions_weight / stock_account / stock_positions / summary / trades / yearly_risk_free_rates(设基准后多 benchmark_portfolio);summary 里没有 transaction_cost、matching_type、slippage 这些口径字段 | 按旧文档示例写的读取代码会取不到字段(拿到空值) | 先打印一次结果文件的键,再写读取代码;交易成本从成交流水的佣金与税两列汇总 | 口径字段不回显在 summary 里,意味着口径必须自己记录,不能指望结果文件替你留痕 |
| 当前版本(实测) | 命令行选项是 -mt/--matching-type;-cm、-smc 标注 deprecated,推荐 -scm、-cnsmc | 按旧文章写的参数名可能报错或静默失效 | 执行 rqalpha run --help 逐项对照 | deprecated 的参数现在还能用,但升级后有被移除的风险 |
| 当前版本(实测) | 日线频率下 next_bar 被放弃并回落为当前 K 线收盘价 | 「更保守的撮合」在日线场景其实拿不到 | 看运行日志的 warning 与结果文件的撮合类型字段 | 这条差异会直接影响你对结论乐观程度的判断 |
| 6.1.3 | 涨跌停边界撮合修正:是否触及涨跌停改为按最小变动单位做容差;涨跌停价为 NaN 时视为无涨跌停限制 | 升级后打板类策略的可成交性与收益会变化 | 打印信号日涨跌停价与成交记录比对 | 涉及涨跌停的策略必须标注框架版本 |
| 4.11.1 | matching_type 为 None 时按回测频率自动选择(日/分钟为当前 K 线收盘价,tick 为新价) | 不显式指定撮合方式时,等于默认使用偏乐观的成交假设 | 每次都显式传 -mt,并把取值写进结论 | 同一策略在两台机器上结果不同时,先对比这一项 |
| 4.7.0 | 信号模式下开启涨跌停选项时,超出涨跌停价格范围的订单会被拒绝 | 信号模式的成交集合变小,与更早版本结果不可比 | 改用常规撮合模式复核结论 | 信号模式的定位是验证信号方向,不是估值 |
| 3.x | 重构 A 股 T+1 相关逻辑、移除 hard code;滑点计算增加涨跌停价判断 | 升级后日内交易类策略的结果会出现结构性变化 | 打印持仓可卖数量验证 T+1 行为 | 跨大版本升级时,先把基线重跑一遍 |
| 4.x | 年化类指标改用交易日计算;基准收益改用前复权价格序列计算 | 同一段历史算出的年化收益与基准曲线都会变 | 与自己的手算结果比对年化口径 | 跨版本比较收益前,先统一这两项口径 |
| 长期 | 不再支持 Python 2.7;--strategy-type 已废弃 | 老环境直接装不上或跑不起来 | 用 rqalpha version 与 rqalpha run --help 确认当前可用项 | 遇到「按教程写却报错」,先看版本再改代码 |
RQAlpha 自带的下一个 Bar 撮合在日线频率下会被放弃并回落为当前 K 线收盘价撮合(本机实测:传入 next_bar 与不传参数的读数完全相同)。要做到延后成交,需要在策略代码里自己把信号推迟一根 K 线执行;换到分钟级回测时该撮合方式才会真正生效。
关键是看清窗口语义:日线回测下取数接口的被包含标志会被强制关闭(源码注释写明「日回测不支持」),盘前阶段取到的日线还会被回退到上一交易日。真正要防的不是「盘前看到当日收盘价」,而是「与当前 K 线同一价格成交」这类假设——那是本文第二节的口径问题。
取决于策略类型,事件驱动、打板、追涨类受影响最大。做法是把信号日的涨跌停价与停牌状态打印出来,与成交明细逐笔比对,而不是凭印象估计。框架对股票标的在停牌时会拦截委托,涨跌停则按最小变动单位做容差判定。
满仓下单叠加手续费会出现资金透支,官方示例输出里就有现金为负、持仓占比略大于 1 的情况。这说明资金约束与真实账户不一致,解读收益前应先确认这一点是否符合你的真实交易规则。
本机实测的默认值:股票佣金费率万分之八(可乘乘数调节),单笔最低 5 元;印花税率万分之五,只在卖出方向、只对股票收取(基金/ETF 不收)。开启按历史税率的开关后,较早年份的印花税率更高。具体以你所在版本与券商的真实费率为准。
默认值是 0,但框架在未显式配置时会打印一条警告,提示该默认值将在后续版本改为非零值。这项税按月结算,税基是年内可抵扣余额加当月实现盈亏。如果你的策略有跨月的卖出实现,建议显式配置并记录取值。
不一定是。撮合默认值、涨跌停判定口径、税费默认值、年化与基准算法都在不同版本被调整过,命令行参数也有废弃与新增。正确做法是把版本号写进结论,升级后重跑基线并记录差异。
分三级:官方文档与仓库文件(①②级)、本机安装实例上的命令读数与源码核验(③级)、以及在此基础上整理的排查与决策表(④级)。实测覆盖到安装、命令行参数面、数据包下载与源码口径;没有对任何策略的收益能力做验证,也没有把本机实测读数当作可复现的业绩结论。RQAlpha 与 EasyClaw 无已证实集成,本站内容用于研究与技术选型判断,不构成投资建议。