跳转至

会计与执行路线图

语言:简体中文 · English

截至 2026-10-16:订单级公司行动账本和按生效日期匹配费率的通用机制已实现,并有合成回归覆盖。它们不提供经核实的历史公司行动输入或市场费率。仍待推进的是有来源的历史事件与费率覆盖、临时/永久冲击和机会成本等校准,以及执行价与估值价的前视检查和最低佣金与资金缩放的组合验证。

长期约束

各类回测最终应遵守同一组约束:

  1. 目标权重生成带方向的订单。
  2. 订单生成零笔或多笔成交。
  3. 成交更新持股数量和现金。
  4. 每日净值等于现金与持仓市值之和。
  5. 显式费用和隐含执行成本分别报告。
  6. 所有入口使用有文档说明的统一换手率公式。
  7. 已配置的执行输入缺失时终止计算,不能静默关闭约束。
  8. 第三方框架对象不能进入公开结果或跨仓库产物。
  9. 后端必须声明订单、部分成交、每日账本和多空能力,不能伪造不具备的输出。

第零阶段:执行契约与后端边界

当前已经完成:

  • 定义 Instrument、Target、OrderIntent、OrderEvent、Fill 和 LedgerSnapshot。
  • 定义 created、submitted、accepted、partial、filled、cancelled、expired 和 rejected 状态。
  • 使用稳定事件 ID 去重,并对乱序订单事件做确定性归约。
  • 定义 BacktestBackend、BackendCapabilities 和 CanonicalBacktestResult。
  • 提供 NativePositionReplayBackend 作为现有周期回放的安全适配器。
  • 用机器可读账本分别记录当前内置后端、历史候选、规划项和范围外项目。
  • 增加充分流动性的 native 固定对照样例和本地质量门禁。

该阶段只建立稳定边界,没有把周期回放升级为订单级或每日账本。orders、fills 和 daily_ledger 在 native 周期回放中保持为空,并通过 capabilities 明确声明不可用。

第一阶段:术语与结果契约

当前已经完成:

  • 区分持仓名称替换率和权重换手率
  • 输出买入、卖出、总交易权重、半 L1 换手率和单边换手率
  • 保留历史建仓成本约定,同时单独披露半 L1 换手率
  • 在单腿和分期结果中提供有类型约束的成本明细
  • 在用户文档中说明会计公式

第二阶段:共享每日账本

状态:已完成(可选 ledger 模式,默认关闭以保旧契约)。已落地独立的订单级账本引擎 execution_sim/,core.py 真实产出 orders 与 fills,并已接入 run_position_backtest(_evaluation_positions.py 把 sim_result.orders/fills/daily 写入 execution_sim_* 字段,simulate_ideal_daily_nav 也已实现)。同时新增了 to_unified_ledger() 适配方法,把执行引擎的 daily/orders/fills 规范映射为路线图定义的八个统一账本字段:targets、orders、fills、daily_positions、daily_cash、daily_nav、cost_breakdown、turnover_breakdown。两项原本规划、尚未完成的内容现已通过可选 ledger 开关接入,具体如下。

  • 旧 native 周期回放后端保留历史契约(默认 orders/fills/daily_ledger 仍声明为 not_available)。新增可选 ledger 开关(NativePositionReplayRequest.ledger),开启时调用 execution_sim 的 simulate_execution_adjusted_nav,把 orders/fills/daily_ledger 填入 CanonicalBacktestResult 并翻转对应 capabilities(含 order_id、fill_id,满足框架中立账本契约)。默认关闭,固定对照测试不受影响。
  • backtest_topk(api.py)保留原有的 ExecutionModel facade 与五元素返回契约。新增可选 ledger 开关,开启时复用引擎累积的每期目标权重(target_weights),通过 simulate_ideal_daily_nav 产出统一账本,并在返回包末尾追加 UnifiedLedger 元素。默认关闭,旧签名与固定场景差分不受影响。

backtest_topk、run_position_backtest、理想净值和容量调整净值通过可选 ledger 模式共用以下账本链路:

目标持仓 -> 订单 -> 成交 -> 持股与现金 -> 每日净值 -> 报告

信号回测负责构造目标持仓,会计计算交给与外部持仓回放相同的引擎。统一输出包括:

  • targets
  • orders
  • fills
  • daily_positions
  • daily_cash
  • daily_nav
  • cost_breakdown
  • turnover_breakdown

实施顺序记录:

  1. 目标权重生成带方向的订单。订单可产生零笔或多笔成交。
  2. 成交更新现金与持仓,每日净值由现金和持仓估值共同构成。
  3. 显式费用与隐含执行成本分开报告,已配置但缺失的执行输入必须使计算失败。
  4. 后端明确声明订单、部分成交、每日账本和多空等能力,不输出不支持的数据。

第三阶段:成本拆分

已完成(执行路径已真实拆分,8 个互不重复子项全部接入。无模型的子项用 0 占位)。

CostBreakdown(packages/portfolio-backtester/src/portfolio_backtester/types.py)包含 8 个互不重复的子项:佣金、印花税、过户费、价差成本、临时冲击、永久冲击、机会成本、融资成本。聚合关系保持为 fee_cost = 佣金 + 印花税 + 过户费,slippage_cost = 价差成本 + 临时冲击 + 永久冲击 + 机会成本 + 融资成本,total_cost = fee_cost + slippage_cost(即 8 个子项之和)。原有 fee_cost/slippage_cost/total_cost 语义与 to_dict 全部保留。from_components 在有分项数据时构造,to_unified_ledger() 的 cost_breakdown 现在输出各子项,并额外给出 fee_cost/slippage_cost/transaction_cost 聚合列。

执行路径真实接入(本阶段落地)

  • DetailedTradeFeeModel(packages/portfolio-backtester/src/portfolio_backtester/_execution_models.py)新增 notional_cost_breakdown(notional, side),按 A 股口径返回 commission/stamp_tax/transfer_fee/spread_cost 四项,且四项之和恒等于 notional_cost(守恒)。
  • 执行引擎的每笔成交成本由单值 _trade_fee 改为返回 CostBreakdown:orders_ideal.py、orders_nav.py 的 ideal/adjusted-nav 路径均按子项累加,并通过 _record_nav_fill 把子项写入 fills 的 cost_* 列。每日 daily 行也带 cost_* 子项。
  • UnifiedLedger.to_unified_ledger()(execution_sim/results.py)的 cost_breakdown 改为按买卖方向聚合并填入子项,再派生 fee_cost/slippage_cost/transaction_cost,保证 8 子项之和 == 旧 fee_cost + slippage_cost == 旧 transaction_cost。

子项现状

  • 佣金、印花税、过户费、价差成本:已真实拆分(带 DetailedTradeFeeModel 时非零。无费率模型时按 notional * cost_rate 整体归入 spread_cost,保持总现金扣减不变)。
  • 临时冲击、永久冲击、机会成本、融资成本:执行引擎尚无对应模型,统一以 0 占位(诚实占位,不编造数值)。

最低佣金需要明确计费单位。默认建议按股票、买卖方向和交易日分别计费,同时允许券商专用规则覆盖。

现金缩放与最低佣金的非线性组合已用合成成交验证:账户资金为 1,005.01 元时,100 股成交支付 5 元最低佣金和 0.01 元过户费。账户资金为 500,000 元时,499,000 元成交按费率支付 99.80 元佣金和 4.99 元过户费。两种规模均保留整手约束,成交后现金不为负。这只验证调用方提供的费率下的现金预算和最低佣金切换,不证明历史费率来源或校准准确。

第四阶段:市场规则与时间戳

已完成(opt-in 契约,默认全部关闭以保持现有固定对照场景基线)。

市场规则契约通过 ExecutionSimConfig 新增开关接入,默认不约束,现有 7 个固定对照场景数值不变:

  • 买入整手(round_lot,非 None 时买入数量向下取整到整手,不足一手当日不买)、零股卖出(卖出路径不受整手约束)。
  • T+1 可卖数量(enforce_t1,当日可卖数量取自当日开盘前持仓快照 t1_available,排除当日新买)。
  • 涨跌停与方向相关可交易状态(enforce_price_limits + limit_up_col/limit_down_col 布尔列,涨停跳过买入、跌停跳过卖出)。
  • 上市、停牌、退市(enforce_listing_status + listing_status_col,非 listed 状态当日跳过成交)。
  • 企业行动(分红、拆股、送转):通用账本已支持调用方传入事件,并记录应收现金、待解禁股份及来源回执,详见公司行动账本。历史事件来源、覆盖率和逐项核实仍由调用方负责,目前没有据此声称已还原真实历史公司行动。

约束遵守严格按路线图长期约束 #7:任何市场规则若开启但所需输入列缺失,运行终止(ValueError)而非静默降级。同时运行 summary 增加 warnings 字段,当引擎启用但全部市场规则关闭时记录 market_rules_inactive,满足'不静默关闭约束'的可见性要求。

信号时间、决策时间、下单时间、成交时间和估值时间已分别记录在每笔 fill 行(signal_time/decision_time/order_time/fill_time/valuation_time),统一带 Asia/Shanghai 时区。成交价/估值价区分与前视偏差检查仍待细化。带生效日期的费用表已经实现为 DatedFeeSchedule / DatedTradeFeeModel,可选接入执行 NAV 账本,详见分期执行费率。该机制只应用调用方提供的费率,不证明历史市场费率已完整核实。

第五阶段:容量与冲击校准(已完成)

容量约束应直接限制成交量。超过参与率上限的部分需要形成未成交订单,或明确标记为外推结果。

本阶段在既有容量网格(portfolio_value 乘以 participation_rate 的二维扫描)之上增加校准与集中度两层输出,复用已有网格行,不引入任何额外仿真成本。

容量报告新增 capacity_calibration 字段(primary participation_rate 取网格中由调用方指定的一档):

  • 盈亏平衡资金规模(break_even_capacity):exec_total_return 首次由负转非负的 portfolio_value,单段线性插值。
  • 成交率达到 95% 时的资金规模(fill_rate_95_capacity):fill_ratio 达到 0.95 的 portfolio_value,单段线性插值。
  • 达到指定 alpha 保留率时的资金规模(alpha_retention_90_capacity):return_retention 达到 0.90 的 portfolio_value,单段线性插值(sharpe_retention_90_capacity 同法,阈值 0.90)。
  • 每增加一单位资金的边际冲击(marginal_impact):取网格中相邻最大区间的斜率,输出 marginal_return_per_unit_capital 与 marginal_sharpe_retention_per_unit_capital,用于刻画资金规模扩张对收益与夏普保留率的边际侵蚀。

集中度统计由 concentration_by_group 产出,按成交名义额聚合,无外部数据源依赖:

  • 按股票(by_symbol):恒有输出,返回各标的成交名义额占比与 HHI 集中度。
  • 按流动性(by_liquidity):当定价表存在流动性列时输出,按低/中/高三档分组统计占比与 HHI。
  • 按行业(by_industry):当且仅当 positions 数据含行业列时输出,否则为 None,不引入新依赖(沿用用户在晨报 dailywatch20 中已有的行业占比思路,但容量报告本身不依赖外部行业源)。

concentration 默认计入报告(无开关),满足按默认计入的可见性要求。容量网格每行仍保留 order 状态、参与率分位等原始诊断字段,便于回溯源。

策略只在较窄时段交易时,应优先使用对应执行窗口的流动性,避免直接使用全日成交额。本阶段流动性分桶仅依赖定价表既有流动性列,未假设全天成交额。

第六阶段:指标与复现(已完成)

收益和风险指标应统一从每日净值推导。自然周期汇总需要先在周期内复利,并保留年份维度。

指标归一化:现有 summarize_period_returns 已统一由 period 收益复利为 NAV 再推导 total_return/max_drawdown/sharpe 等指标,调用方传入的收益源自 simulate_ideal_daily_nav 的每日净值,满足统一从每日净值推导的契约。本阶段新增 yearly_compounded_returns 与 summarize_period_returns 的年份维度字段(yearly_returns/years_count/best_year/worst_year),按日历年 resample 并在年内复利,保留年份维度,不改动既有 NAV 推导逻辑。

每次运行保存以下复现元数据,由 collect_reproducibility 统一采集并写入 run_metadata.json:

  • 仓库提交号(git rev-parse HEAD,不可得时回退 unknown)
  • 后端名称和 capability 快照(调用方传入)
  • 配置哈希(sha256_file)
  • 输入数据哈希(positions 与 pricing 合并哈希)
  • 股票池指纹(由 positions 文件哈希派生,不伪造版本号)
  • 交易日历窗口(由 pricing 数据起止日期代理,仓库未跟踪命名日历版本)
  • 费率表与滑点校准版本(从配置键提取,缺失时记 unversioned,不编造)
  • 依赖包版本(importlib.metadata,含 portfolio-backtester/pandas/numpy)
  • 随机种子(实际传入值,未使用则 None)
  • 运行时间(ISO 8601 带时区)

复现元数据作为顶层 reproducibility 字段注入容量报告与 run_backtest 返回的 stats bundle,且不改变既有报告契约键名,确保前序锁定的固定对照场景零回归。

后续增强项

  • 用经过来源核验的历史公司行动事件验证通用账本。当前事件机制已实现,历史数据覆盖尚未证明。
  • 核验日期有效费率输入的历史来源与覆盖率。通用费率表和执行接入已实现。
  • 为临时冲击、永久冲击、机会成本和融资成本提供经校准的模型。
  • 细化成交价、估值价和时间戳的前视偏差检查。
  • 按具体需求评审 Backtrader 差分后端,不替换权威 native 路径。

外部后端评审原则

当前没有外部后端适配器。Qlib 与 LEAN 的历史候选没有进入 main,LEAN 只保留架构参考用途。Backtrader 仍处于规划阶段。vn.py 属于本仓库范围外。

未来的 Backtrader 适配器需要保持可删除,并通过规范化结果转换进入现有后端协议。native.position_replay 在对照证据被接受前保持权威实现。

删除重复 native 通用实现前必须同时满足:

  • 覆盖率达到 framework-integration-ledger.yml 的门槛。
  • 固定对照场景全部通过。
  • 日期、持仓、换手、成本、成交和 PnL 差异已分类。
  • A 股领域语义无损。
  • 性能、迁移说明和回滚路径达到约定。

验证要求

统一账本的持续回归至少覆盖以下测试:

  • 现金与持仓市值守恒
  • 零收益、零成本下的净值恒等关系
  • 成本和容量的单调性
  • Top-K 目标持仓与持仓回放结果等价
  • 订单级最低佣金
  • 人工核对过的每日现金、持股和净值样例
  • 稀疏权重、价格缺失和延迟成交的性质测试
  • 重复和乱序订单事件的幂等归约
  • 可选适配器不泄露第三方对象
  • native 与获准适配器的固定场景差分