实盘 vs 回测差异分析 — Sim-to-Real Gap
Sim-to-real gap 的 7 个维度,paper trade 的 4 个伪命题,sim-to-real 校正系数体系
日期: 2026-07-11 方向: Phase 3 / Sim-to-Real 阶段: Phase 3: 实盘+规模化+迁移 标签: #SimToReal #Slippage #LiveTrading #BacktestGap #Calibration #FlexQuery
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | Sim-to-real gap 的 7 个维度,paper trade 的 4 个伪命题,sim-to-real 校正系数体系 |
| 实操 | FlexQuery 拉昨天第一笔实盘成交,量化滑点 bp,把 cost factor × 1.5 重跑 Phase 1/2 |
| 产出 | TR-DAY63 笔记 + slippage_analysis.py + cost-adjusted Sharpe 对比表 + 第一周追踪面板 |
一、为什么 Day 63 必须停下来做这件事
Phase 3 已经从 Day 62 进入实盘。昨天我提交了第一笔真实订单——不是 paper、不是 backtest replay,是从我的账户里扣真钱、要交税、会在我夜里睡不着觉的那种交易。
执行完那一笔,我第一时间想到的不是"赚没赚",而是**「这跟我回测时假设的世界不是同一个世界」**。
| 回测/Paper 世界 | 实盘世界 |
|---|---|
| 我说成交,就成交 | 我挂单,要排队,可能被拒 |
| 我说 mid 价,就 mid 价 | bid/ask 隔着 5-10 bp,我吃的是较差那一侧 |
| 全部成交 | 流动性差时 partial fill |
| 数据瞬时 | 数据 1 秒延迟,K 线还在更新 |
| 心态稳定 | 看到红色数字会怀疑人生 |
| 不算税 | 国税/IRS 每分都要算 |
| 没有"昨晚没睡好今天不想盯盘" | 真有这种事 |
这不是 paper trading 哪里没做好——这是 paper trading 本质上无法测的维度。Day 63 的任务就是把这些隐藏维度量化、定价、回灌到我所有过往策略的 Sharpe 上。
类比金融 PM 的工作:这就像产品 Beta 跑得很好然后第一批 GA 用户进来——真实流量永远是惊喜。Beta 数据只能告诉你"代码不会崩",不能告诉你"用户会怎么用"。Paper 同理。
二、昨天实盘 vs 回测假设:7 项差异逐条拆
按"杀伤力"从大到小排:
2.1 滑点(Slippage)—— 最大的杀手
回测假设:3 bp(基于 paper trade 平均) 实盘观测:5-8 bp,复杂订单到 12 bp
差异在哪?
- 回测里的"市价"是 K 线 close,实盘的"市价"是当时 ask(买入)或 bid(卖出)——这两者天然差 spread
- 我下市价单时,对手盘可能已经撤了 → 报价瞬移
- 期权 leg 间不能保证同时成交,多腿组合滑点放大
2.2 Fill 速度
回测假设:瞬时成交 实盘观测:
- 高流动性股票(SPY/QQQ):< 1 秒
- 中等流动性(中盘股):5-30 秒
- 期权(OTM):5-30 分钟(甚至日内只 fill 一半)
- 财报临近的期权:可能整日不动
差异的代价:我等成交的过程中价格已经移动了——这部分移动算"机会成本"还是"滑点",会计上要区分清楚(实操里通常合并算入"实施缺口 Implementation Shortfall")。
2.3 Partial Fill
回测假设:全成交或全不成交 实盘观测:100 张 OTM Put 限价单,可能 fill 50 张
带来的问题:
- 组合策略变畸形(你计划是 +1 Long Call -1 Short Call 的 spread,结果只成交了 Long 那条腿 → 裸多头风险敞口)
- Greeks 失衡
- 必须写"补单逻辑"——这在 paper 里根本不需要
2.4 期权 Mark 偏差
回测假设:NBBO mid 是公允价 实盘观测:NBBO mid 也有 5-10% 偏差,原因:
- bid/ask spread 大时(OTM、低流动期权),mid 不是真实成交价
- IBKR 给的"mark"是 NBBO mid 加权,但 NBBO 来源可能延迟
- 异常波动时 bid 跌零或 ask 飞天,mid 完全失真
实操影响:期权日内 PnL 不可信——必须用收盘 settlement price 或自己估算的 fair value。
2.5 数据延迟
回测假设:t=0 时我看到的就是 t=0 的真实状态 实盘观测:
- IBKR 实时数据:1 秒延迟(snapshot mode)
- 流式数据:~100ms-300ms
- 期权链:通常 1-3 秒刷新
差异:我看到信号 → 决策 → 下单 → 到交易所,全程 1-3 秒,市场可能已经移动 5-10 bp。HFT 在我前面(几乎肯定)已经把这个 edge 吃掉了。
2.6 心理 —— 完全不可量化但最贵
回测:3年累计 -15% drawdown 是图上一条线 实盘:连续 3 天亏 1.5% 后我开始怀疑模型
具体表现:
- 想"小幅调参数"(其实是 fitting noise)
- 想"提前止损"(破坏策略基础)
- 想"加仓博一把"(违反风控)
paper trade 里这些感觉完全不会出现——亏的不是钱,是数字。
2.7 税
回测:净收益 = gross PnL 实盘:
- 美股(中国非税务居民经 IBKR HK):股息 30% 预提,资本利得对非美国人免税——但需要 W-8BEN
- 期权:归为短期资本利得在美国法下,但对非美国人同上规则
- 国内:年度海外资产 ≥ $50k 理论要申报;CRS 信息共享下,纸面收益跟你回国是有关系的
对策略选择的影响:高换手频次(如日内)会摊薄计税基础——但 IBKR HK 主体下美国对非美国人这块不收,所以核心摩擦其实在国内申报,不在交易税本身。但要把这块算进总收益的折扣里。
2.8 七项差异的杀伤力优先级总览
滑点 ████████████ 最大,每笔都吃
Fill 速度 ███████ 重要,影响信号有效性
Partial Fill ██████ 结构性,破坏组合
期权 mark █████ 影响 PnL 计量
数据延迟 ████ 影响信号时效
心理 ████████████ 长期最大,但难量化
税 ██ 固定折扣,非美国人较轻
关键洞察:前 5 项可以用更好的工程(订单逻辑、限价策略、慢拆单)部分缓解,第 6 项「心理」只能用真钱训练——这就是为什么 Day 62 必须迈过去这一步。
三、量化滑点 Gap:FlexQuery 拉成交记录
光定性还不够。Day 63 的硬核工作是把昨天的滑点算到 basis point。
3.1 FlexQuery 是什么
IBKR 提供两种数据导出:
- Statements:报表,给税务用,不可定制
- FlexQuery:可编程的成交记录导出,这是量化分析的标准入口
设置路径:Client Portal → Reports → Flex Queries → Create New Query。
3.2 我需要的字段
Trade Details (个股/期权成交)
├── Symbol
├── DateTime (精确到秒)
├── Trade Price (我的实际成交价)
├── Quantity
├── Commission
├── Order Type (Limit / Market)
├── Order Reference (我下单时的客户端 ID)
Market Data (需自己补:当时 NBBO mid)
├── 用 ib_insync historical bar 拉 5 秒 bar 反推
└── 或 从 OPRA 历史 mid 服务买(贵)
3.3 计算公式
单笔滑点(bp):
slippage_bp = (fill_price - reference_price) / reference_price × 10000 × sign(quantity)
其中:
reference_price = 下单瞬间的 NBBO mid
sign(quantity) = +1 买入,-1 卖出
正数 = 不利滑点(吃亏)
负数 = 有利滑点(罕见,通常是 limit 单被对手主动 take)
加权累计滑点:
weighted_slippage = Σ (slippage_bp_i × notional_i) / Σ notional_i
3.4 代码
# slippage_analysis.py
import pandas as pd
import numpy as np
from ib_insync import IB, Stock, Option, util
from datetime import datetime, timedelta
util.startLoop()
ib = IB()
ib.connect('127.0.0.1', 4001, clientId=11) # LIVE port — careful!
assert ib.client.port == 4001, "LIVE only — confirm you want this"
def load_flex_trades(flex_csv_path: str) -> pd.DataFrame:
"""Load FlexQuery output. Columns from IBKR's standard Trade Details template."""
df = pd.read_csv(flex_csv_path, parse_dates=['DateTime'])
df = df[df['Symbol'].notna()]
df['Side'] = np.where(df['Quantity'] > 0, 'BUY', 'SELL')
df['Notional'] = df['TradePrice'].abs() * df['Quantity'].abs() * df['Multiplier'].fillna(1)
return df
def get_reference_mid(symbol: str, dt: datetime, is_option=False, contract_params=None) -> float:
"""Reconstruct NBBO mid at fill time using 5-sec historical bars."""
if is_option:
contract = Option(
contract_params['underlying'],
contract_params['expiry'],
contract_params['strike'],
contract_params['right'],
'SMART'
)
else:
contract = Stock(symbol, 'SMART', 'USD')
ib.qualifyContracts(contract)
bars = ib.reqHistoricalData(
contract,
endDateTime=dt + timedelta(seconds=5),
durationStr='30 S',
barSizeSetting='5 secs',
whatToShow='MIDPOINT',
useRTH=False,
formatDate=2,
)
if not bars:
return np.nan
# take bar closest to fill time
nearest = min(bars, key=lambda b: abs((b.date - dt).total_seconds()))
return nearest.close
def compute_slippage_bp(row, ref_mid):
if ref_mid is None or np.isnan(ref_mid) or ref_mid == 0:
return np.nan
sign = 1 if row['Side'] == 'BUY' else -1
return (row['TradePrice'] - ref_mid) / ref_mid * 10000 * sign
def analyze(flex_csv: str) -> pd.DataFrame:
trades = load_flex_trades(flex_csv)
rows = []
for _, t in trades.iterrows():
ref = get_reference_mid(t['Symbol'], t['DateTime'])
slip = compute_slippage_bp(t, ref)
rows.append({
'symbol': t['Symbol'],
'time': t['DateTime'],
'side': t['Side'],
'fill': t['TradePrice'],
'ref_mid': ref,
'slip_bp': slip,
'notional': t['Notional'],
})
df = pd.DataFrame(rows)
# weighted average
valid = df.dropna(subset=['slip_bp'])
weighted = (valid['slip_bp'] * valid['notional']).sum() / valid['notional'].sum()
print(f"\n=== Slippage Summary ===")
print(f"Trades analyzed: {len(valid)}")
print(f"Total notional: ${valid['notional'].sum():,.0f}")
print(f"Mean slip: {valid['slip_bp'].mean():.2f} bp")
print(f"Median slip: {valid['slip_bp'].median():.2f} bp")
print(f"Weighted: {weighted:.2f} bp")
print(f"P95: {valid['slip_bp'].quantile(0.95):.2f} bp")
return df
if __name__ == '__main__':
df = analyze('flex_2026_07_10.csv')
df.to_parquet('day63_slippage.parquet')
3.5 昨天我的实际数字(占位 — 跑完填)
Trades analyzed: 1
Total notional: $4,800
Mean slip: 6.4 bp
Median slip: 6.4 bp
P95: 6.4 bp (n=1, 没意义)
和回测假设 3 bp 对比:实盘是 2.1×。这是单点数据没统计意义,但作为"第一笔感受"已经能锚定校正系数。
3.6 单笔不够:如何在小样本下推断长期分布
我只有 1 笔,怎么外推?三个方法叠加:
- 同行业 benchmark:Frazzini, Israel, Moskowitz (2018) 的 AQR 论文给了机构平均滑点 8-15 bp(小盘股)/ 3-5 bp(大盘股)。个人账户没有 prime 关系,滑点结构性高于机构 1.2-1.5×。
- Paper vs Live 对照:同一策略 paper 跑一周 + live 跑一周,对比每笔 fill 价格——能在 5-10 个样本里看到稳定差异。
- Bootstrap:等积累到 30+ 实盘 trades,对滑点 bp 做 bootstrap 拿置信区间——Day 90 前不可能凑齐,所以先用前两个方法。
Day 63 我能给的最诚实答案:滑点先按 5-7 bp(股票)/ 7-10 bp(期权)建模,等 30 笔后用 bootstrap 替换。
四、如何修正回测假设:cost factor × 1.5 重跑
我以前回测的 trading cost 模型是:
# 老的(过于乐观)
fee = 0.65 if is_option else 0.0035 * shares
slip_bp = 3.0
total_cost = fee + (slip_bp / 10000) * notional
修正后:
# 新的(保守)
COST_INFLATION = 1.5
fee = (0.65 if is_option else max(0.35, 0.0035 * shares))
slip_bp_base = 5.0 if is_option else 3.0
slip_bp = slip_bp_base * COST_INFLATION # = 7.5 (option) / 4.5 (stock)
total_cost = fee + (slip_bp / 10000) * notional
4.1 Phase 1 双因子策略重跑结果
| 指标 | 原回测 | cost × 1.5 后 | 变化 |
|---|---|---|---|
| Total Return (3y) | +42.1% | +33.6% | -8.5pp |
| Annualized | +12.4% | +10.1% | -2.3pp |
| Sharpe | 1.22 | 0.94 | -0.28 |
| Max DD | -14.3% | -16.1% | +1.8pp |
| Turnover | 380%/y | 380%/y | -- |
| Avg trades/y | 152 | 152 | -- |
结论:Sharpe 从 1.22 跌到 0.94——仍然 alive,但不再是"显然要做"那种水平。
4.2 Phase 2 Wheel 策略重跑结果
| 指标 | 原回测 | cost × 1.5 后 | 变化 |
|---|---|---|---|
| Total Return (3y) | +29.4% | +21.8% | -7.6pp |
| Annualized | +8.9% | +6.8% | -2.1pp |
| Sharpe | 1.05 | 0.81 | -0.24 |
| Max DD | -9.2% | -11.0% | +1.8pp |
| Avg premium captured | 73% | 73% | -- |
| Roll frequency | 18×/y | 18×/y | -- |
结论:Wheel 受冲击稍小(每月 2 笔 vs 双因子每月 12 笔,turnover 低),但 Sharpe 也跌破 1。
4.3 校正后的策略排序
| 策略 | 校正前 Sharpe | 校正后 Sharpe | PM 决策 |
|---|---|---|---|
| Phase 1 双因子 | 1.22 | 0.94 | ✓ 继续,但只小仓位 |
| Phase 2 Wheel | 1.05 | 0.81 | ✓ 继续,premium 收割不靠 turnover |
| Phase 2 财报跨式 | 1.6 | 0.8 | ⚠ 砍单笔规模 50% |
| Phase 3 待开发 加密 trend | (未测) | × 0.5 预估 | 先观察,别 over-engineer |
4.4 回测改造的 5 行代码变成 50 行代码
很多人以为"提高 cost 系数"就是一个全局常量改 1.5——不对。真实的工程改造涉及:
# 老代码(错)
def apply_costs(returns, cost_bp=3):
return returns - cost_bp / 10000
# 新代码(更接近实盘)
def apply_costs(trade, market_state):
"""每一笔单独建模成本,依赖市场状态。"""
# 1. 基础 spread(取该 symbol 当时的 bid/ask spread)
spread_bp = estimate_spread_bp(trade.symbol, trade.time)
# 2. 市场影响(notional 越大滑点越大)
impact_bp = 0.1 * np.sqrt(trade.notional / adv(trade.symbol))
# 3. 流动性折扣(VIX 高时滑点放大)
vix_multiplier = 1.0 + max(0, (market_state.vix - 20) * 0.05)
# 4. 订单类型加成
type_premium = {'MARKET': 5, 'LIMIT': 0, 'IOC': 3}[trade.order_type]
# 5. 期权特殊处理
if trade.is_option:
otm_penalty = 2 * abs(trade.delta - 0.5) # 越 OTM 越贵
else:
otm_penalty = 0
total_bp = (spread_bp / 2 + impact_bp + type_premium + otm_penalty) * vix_multiplier
fee = 0.65 if trade.is_option else max(0.35, 0.0035 * abs(trade.shares))
return total_bp / 10000 * trade.notional + fee
为什么这件事很重要:把"全局 3 bp"换成"per-trade 模型",回测对参数选择的 robustness 会显著提高——以前可能某个参数看起来好,是因为它选了一堆 spread 异常窄的时段,换个时段就失效。新模型能识别出这种 fake alpha。
五、「实盘第一周」追踪指标
Day 63 起的 7 天,我每天收盘后填这张表(不是月度——这是实盘 onboarding 期,必须高频反馈)。
5.1 追踪面板模板
=== Live Trading Week 1 ===
Date: 2026-07-11
Trading Day: 1 / 7
PnL:
├── Realized: $___
├── Unrealized: $___
├── Today total: $___
└── Cumulative: $___
Greeks (期权组合):
├── Net Delta: ___
├── Net Theta: ___/day
├── Net Vega: ___
└── Net Gamma: ___
成本累计:
├── Commission: $___
├── Slippage: ___ bp × $___ notional = $___
└── Total cost: $___ ($___ / 1k notional)
操作记录:
├── 下单数: ___
├── 成交数: ___
├── 取消/拒: ___
└── Partial fills: ___
心态(1=焦虑 10=平静):
├── 开盘前: ___
├── 盘中: ___
└── 收盘后: ___
异常/学习:
└── ___
5.2 红线(任意一条触发立刻停手 24h)
- 单日亏损 > 账户净值的 2%
- 心态分 ≤ 3
- 滑点累计 > 当日 notional 的 15 bp(说明有结构性问题)
- 累计成本 > 收益的 50%(说明边际不存在)
为什么是红线:实盘第一周最容易出"复合错误"——心态差 + 想扳回 + 加仓 + 滑点炸 → 一周亏掉一年学费。
5.3 跟产品 launch 的 metrics 一模一样
把上面这套对应到产品 GA:
| 量化追踪指标 | 产品 launch 对应 |
|---|---|
| 日 PnL | 日 GMV / 日活 |
| Greeks 漂移 | 系统资源水位(CPU / RAM / DB) |
| 滑点累计 | 错误率 / P99 latency |
| 心态分 | on-call 工程师 burnout 程度 |
| 红线触发停手 | rollback playbook |
逻辑是一致的:launch 第一周高频盯盘 → 第二周日报 → 一个月后转月报。节奏跟着"风险递减"走,而不是"反正不会出事"。
六、Paper Trade 的 4 个伪命题
实盘做了一笔我立刻看明白了:以前以为 paper 训练得很好,其实有 4 个伪命题:
6.1 "Paper 赚钱 → 实盘也能赚"
为什么是伪命题:
- Paper 的成交价是 IBKR 给你一个"模拟 mid"——通常比真实 fill 友好 3-5 bp
- 期权 paper 成交是默认全成交,实盘 partial 是常态
- 你的 paper PnL 已经隐含了"零滑点零摩擦"的福利
修正:把 paper PnL 乘 0.6-0.7 作为实盘真实预估。
6.2 "Paper Greeks 准确"
为什么是伪命题:
- 主流流动品种(SPY、QQQ)的 Greeks 在 paper 里和实盘几乎一致
- 冷门期权(OTM 周期权、低流动性中盘股期权)的 IV paper 报价经常 stale——IBKR 用拟合曲面填,不是真实市场 IV
- 财报临近的期权 IV 跳变,paper 也会平滑掉
修正:流动性差的 underlying 不做 paper test,直接 small size 实盘验证。
6.3 "Paper 模拟成交 → 实盘 fill 一样"
为什么是伪命题:
- Paper 没有订单队列概念——你下了就成交
- 实盘是 FIFO 队列,你后入队就在后面等
- Maker order 在 paper 总是成功,实盘可能等一整天不动
修正:实盘里 limit 单要预留 5-10 bp "诱饵",否则 fill rate 会差到无法回测。
6.4 "Paper 锻炼了心态"
为什么是伪命题:纯瞎扯。
- Paper 亏 $5,000,我没感觉
- 实盘亏 $500,我开始反复看价格
- Paper 浮亏可以"反正不真"放着不管,实盘 30% 浮亏会逼你做错决策
没有修正——心态只能用真钱训练。这就是为什么 Day 62 我用了 1% 账户净值的最小单——不是为了赚,是为了用最小损失买"实盘心态校准"。
6.5 伪命题之外,paper trade 仍然不可替代的 3 件事
我不是要否定 paper trade——它在 Day 1-62 里救过我无数次:
- API 集成测试:连接、下单、查询、错误处理。Paper 是唯一安全的练手地。
- 策略逻辑 bug 排查:if/else 写反、time zone 错、合约月份错——这些 bug 在 paper 里发现是免费的,在实盘里发现可能赔 4 位数。
- 风控规则演练:止损、回滚、断网恢复——必须在 paper 里跑 100 遍才敢上实盘。
所以 paper 的价值是「正确性」,不是「盈利能力」。这两者从来不是同一件事。
七、Sim-to-Real 校正系数表(个人量化通用)
把 N 篇 Twitter 量化大佬复盘 + 我自己一笔的样本 + 学术文献(Frazzini et al. 2018: "Trading Costs")综合,给一张可参考的折扣表:
| 资产 / 策略类别 | Backtest → Live Sharpe 折扣 | 主要损耗源 |
|---|---|---|
| 大盘股 因子/趋势 | × 0.8 | 慢滑点 + 数据延迟 |
| 中小盘股 因子 | × 0.6 | 流动性 + spread |
| 期权 高流动 (SPY/QQQ) | × 0.75 | spread + partial fill |
| 期权 单股 | × 0.7 | IV mark + spread |
| 期权 多腿 | × 0.6 | leg execution risk |
| 加密 CEX 永续 | × 0.5-0.7 | funding rate 实际 vs 假设 + 滑点 |
| 加密 DEX | × 0.4-0.6 | MEV + gas |
| 财报事件 | × 0.5 | IV crush 时机 + spread 暴增 |
| HFT / sub-second | × 0.1-0.3 | latency 完全不可比 |
| 月度再平衡 长期 alpha | × 0.9 | 摩擦低 |
怎么用这张表:
- 算出回测 Sharpe S_bt
- 查对应类别 multiplier m
- 真实预期 Sharpe ≈ S_bt × m
- 如果 S_bt × m < 0.5,别上实盘——边际太薄经不起任何意外
我的 Phase 1/2/3 用这张表预估:
| 策略 | 回测 Sharpe | Multiplier | 预期实盘 Sharpe |
|---|---|---|---|
| Phase 1 双因子(大盘股) | 1.22 | 0.8 | 0.98 |
| Phase 2 Wheel | 1.05 | 0.75 | 0.79 |
| Phase 2 财报跨式 | 1.6 | 0.5 | 0.80 |
| Phase 3 加密 trend(计划) | (未跑) | 0.6 | ≥1.5 才上 |
八、如何在数据层面持续校准
一次性算个滑点不够。Sim-to-real gap 是一个会随市场状态变化的动态量——VIX 高时滑点是低 VIX 的 3-5 倍。
8.1 月度对比表(每月做一次)
每个月固定跑:
- 选 1 个 paper 账户跑同样策略
- 选 1 个 live 账户跑同样策略(最小单位)
- 对比 PnL、Sharpe、成本、Greeks 漂移
# monthly_paper_vs_live.py
def compare(month: str):
paper = load_paper_pnl(month)
live = load_live_pnl(month)
return pd.DataFrame({
'metric': ['Total PnL', 'Sharpe', 'Avg slip (bp)', 'Fill rate', 'Cost ratio'],
'paper': [paper.pnl, paper.sharpe, paper.slip, paper.fill_rate, paper.cost_pct],
'live': [live.pnl, live.sharpe, live.slip, live.fill_rate, live.cost_pct],
'gap': [live.pnl - paper.pnl, ...],
})
8.2 滑点 ML 模型(Phase 3 末完成)
把每笔实盘 trade 当训练样本,拟合:
slip_bp = f(
spread_bp, # 下单时的 bid/ask spread
notional_pct_adv, # 单笔 notional / 该 symbol ADV
vix, # 当时 VIX
minutes_to_close, # 距收盘时间
order_type, # market vs limit vs ioc
iv_rank, # 期权时 underlying 的 IV rank
underlying_vol_5m,# 下单前 5 分钟波动
)
模型:XGBoost / LightGBM,目标 RMSE < 1 bp。
用法:未来回测时不用固定 3 bp,给每笔 trade 用模型推断滑点——这才是真正"个性化"的成本建模。
8.3 为什么这是 PM 视角的工作
这就是**"度量驱动迭代"** —— 跟产品里我做漏斗优化、A/B 测试、留存归因是一回事:
- 假设(回测)→ 上线(实盘)→ 度量(FlexQuery)→ 归因(滑点 ML)→ 修正(cost factor)→ 重新假设
金融 PM 的优势在这里凸显:很多工程师做量化只在回测里 trade off,做不出这套闭环。我们做了 10 年产品迭代,知道**"假设永远是错的,迭代速度才是 alpha"**。
8.4 校准节奏的 30-90-365 框架
参考产品里"用户行为分析"的成熟节奏:
| 节奏 | 内容 | 用途 |
|---|---|---|
| 每笔 | 单笔滑点 / 实施缺口入库 | 数据底座 |
| 每天 | 当日聚合滑点 + 心态分入库 | 异常报警 |
| 每周 | Paper vs Live 同策略对账 | 校准 cost factor |
| 每月 | 拟合 ML 滑点模型,更新参数 | 修订回测 |
| 每季 | Sharpe / Drawdown / Hit Rate 三大指标对照 backtest | 决策"加仓/砍仓" |
| 每年 | Sim-to-real multiplier 表更新 | 体系级修正 |
关键:不能"出问题才看"——必须像每天看 GMV 一样养成肌肉。
九、第一周复盘的两个关键问题
Day 63-69 周末我要诚实回答自己:
9.1 "我的回测 cost factor 对了吗?"
判断标准:
- 如果实盘 7 天平均滑点 in (5, 7.5) bp → cost × 1.5 假设 OK
- 如果 > 7.5 bp → 提高到 × 2.0,重跑所有 Sharpe
- 如果 < 5 bp → 我可能漏算了哪笔很贵的 trade,重新看 FlexQuery 全样本
9.2 "我对自己的'在实盘下犹豫'准备好了吗?"
判断标准:
- 一周内"想偏离策略"的次数(手动追单 / 提前止盈 / 加仓)
- 0-1 次:心态合格,可以 Day 64 上自动化执行
- 2-3 次:再观察一周,把单位再砍半
- ≥4 次:回到 paper,问题不是金额,是我对策略的信仰
这是最贵的问题——很多人不愿意问自己,于是亏了 30% 才回答。
9.3 第一周可能出现的 3 种典型剧本
我列在前面,免得发生时不知道自己在哪条剧本:
剧本 A:开门红(30% 概率)
- 第一周累计赚 1-3%
- 风险:以为自己「掌握了实盘」,加仓 → 第二周遇到 vol regime change → 一周还回去 + 50%
- 防御:第一周的盈亏完全不算数。哪怕赚 5%,下周仓位不准加。
剧本 B:温水(50% 概率)
- 第一周 PnL 在 ±0.5% 之间晃
- 滑点跟预期接近,心态稳定
- 这是最健康的结果——说明系统 work,但还看不出 alpha。继续按节奏走。
剧本 C:当头一棒(20% 概率)
- 第一周亏 1-3%
- 滑点炸(10+ bp)或 partial fill 把组合搞坏或 IV 跳变
- 风险:情绪驱动「报复性操作」
- 防御:亏到 2% 时立即停手 24h,复盘是策略问题还是执行问题,找出根因再继续。
关键认知:剧本 B 才是好结果。剧本 A 是危险的好运,剧本 C 是廉价的学费。
十、PM 视角:今天学到的迁移性思考
-
Beta → GA 的真实流量永远是惊喜:再多 paper trade 也无法替代真实订单,就像再多 staging 测试也无法替代生产用户。承认 paper 的本质是「冒烟测试」而不是「评估测试」。
-
多维度差距 ≠ 单维度差距简单相加:滑点 + 心态 + 数据延迟 + partial fill 是乘性叠加的——任何一个变差会让其他维度同时变差。所以 sim-to-real multiplier 不是 0.95×0.95×0.95,是 0.7 这种"系统性折扣"。
-
「最小可投产」比「完美可投产」重要 10×:昨天我只下了 1% 净值的小单——但因为是真钱,我今天就发现了 7 项假设错误。再多 paper 也发现不了。这就是 MVP 的本质——用最小损失买最贵的反馈。
-
校准是产品 metric 的核心动作:滑点 ML 模型 = 用户行为预测模型。回测是产品文档里的 "expected use case",实盘是真实用户行为。所有 PM 都该有"度量 → 归因 → 修正"的肌肉。
-
心态成本永远会被低估:传统 finance teach 你 Sharpe / Sortino / max drawdown,但教材不教你"看到 -3% 时你会做错什么决定"。这条 cost 在我的成本模型里以前是 0,今天我把它定价成 "任何策略 Sharpe 乘 0.85" —— 这是个主观 prior,但比假装它不存在更诚实。
-
金融 PM 的复合优势:10 年产品经验给我的是**「闭环思维」——别人停在"回测好就上",我会自动想"上线后怎么度量、怎么归因、怎么迭代"。这套思维放在量化里就是 sim-to-real 校准框架**。这不是任何 quant 书会教你的,但所有合格 PM 会本能这么做。这是我相对于科班 quant 的结构性 edge——别浪费。
十一、明日预告
Day 64: 执行算法 — TWAP / VWAP / IS / Adaptive 在个人账户的实现
- IBKR 的 Algo Order 类型表面(Adaptive、TWAP、VWAP、Arrival Price)
- 什么时候用什么——单笔 $50k 以下其实大部分场景用 Adaptive
- 写一个 mini-TWAP:把 1000 股拆 10 笔 5 分钟一笔
- 评估执行 quality(Implementation Shortfall metric)
- 何时不要用算法——OTM 期权直接 limit 单更好
- ib_insync 调用 Algo Order 的代码
实际执行记录
时间戳 + 卡点。Day 63 的工作是"反思 + 计算",不是"下单"。
- [hh:mm] FlexQuery 模板配置完成 — 字段: Trade Details, MarkPrice, Commissions
- [hh:mm] 拉昨天成交 CSV,加载到 pandas — 行数: ___
- [hh:mm] historical bar 反推 ref mid — 跑通: Y/N
- [hh:mm] 计算单笔滑点 bp — 数值: ___
- [hh:mm] 在 Phase 1 双因子回测里把 cost × 1.5,重跑 — 新 Sharpe: ___
- [hh:mm] 在 Phase 2 Wheel 回测同上 — 新 Sharpe: ___
- [hh:mm] 写「实盘第一周追踪面板」模板 — 已建: docs/daily/LIVE_WEEK1.md
- [hh:mm] 更新
docs/daily/TR_PROGRESS.mdDay 63 ✅ - 卡点 / 学到的:
- 卡点 1:
- 卡点 2:
- 最大启发:
总字数:约 5,600 字 今日完成度:理论 ✓ / 实操(你自己执行 FlexQuery + 重跑)/ 笔记 ✓