会计与执行路线图
语言:简体中文 · English
截至 2026-10-16:订单级公司行动账本和按生效日期匹配费率的通用机制已实现,并有合成回归覆盖。它们不提供经核实的历史公司行动输入或市场费率。仍待推进的是有来源的历史事件与费率覆盖、临时/永久冲击和机会成本等校准,以及执行价与估值价的前视检查和最低佣金与资金缩放的组合验证。
长期约束
各类回测最终应遵守同一组约束:
- 目标权重生成带方向的订单。
- 订单生成零笔或多笔成交。
- 成交更新持股数量和现金。
- 每日净值等于现金与持仓市值之和。
- 显式费用和隐含执行成本分别报告。
- 所有入口使用有文档说明的统一换手率公式。
- 已配置的执行输入缺失时终止计算,不能静默关闭约束。
- 第三方框架对象不能进入公开结果或跨仓库产物。
- 后端必须声明订单、部分成交、每日账本和多空能力,不能伪造不具备的输出。
第零阶段:执行契约与后端边界
当前已经完成:
- 定义
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)保留原有的ExecutionModelfacade 与五元素返回契约。新增可选ledger开关,开启时复用引擎累积的每期目标权重(target_weights),通过simulate_ideal_daily_nav产出统一账本,并在返回包末尾追加UnifiedLedger元素。默认关闭,旧签名与固定场景差分不受影响。
backtest_topk、run_position_backtest、理想净值和容量调整净值通过可选 ledger 模式共用以下账本链路:
目标持仓 -> 订单 -> 成交 -> 持股与现金 -> 每日净值 -> 报告
信号回测负责构造目标持仓,会计计算交给与外部持仓回放相同的引擎。统一输出包括:
targetsordersfillsdaily_positionsdaily_cashdaily_navcost_breakdownturnover_breakdown
实施顺序记录:
- 目标权重生成带方向的订单。订单可产生零笔或多笔成交。
- 成交更新现金与持仓,每日净值由现金和持仓估值共同构成。
- 显式费用与隐含执行成本分开报告,已配置但缺失的执行输入必须使计算失败。
- 后端明确声明订单、部分成交、每日账本和多空等能力,不输出不支持的数据。
第三阶段:成本拆分
已完成(执行路径已真实拆分,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与获准适配器的固定场景差分