Phase 2 综合演练 — 三策略并行 2 周
多策略并行运行的工程问题:clientId / 保证金 / race condition / 监控 / 归因
日期: 2026-07-06 方向: Phase 2 / 综合演练 阶段: Phase 2: 策略实战 + AI 信号 标签: #ThreeStrategy #PaperTrade #Integration #PortfolioDashboard #2WeekRun #PreLive
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | 多策略并行运行的工程问题:clientId / 保证金 / race condition / 监控 / 归因 |
| 实操 | 把 Day 36 双因子 / Day 42 Wheel / Day 49 IC+LLM 整合进同一个 paper portfolio,跑 2 周 |
| 产出 | TR-DAY58 笔记 + portfolio_dashboard.py(实时 NAV / Greeks / VaR / 相关性)+ 2 周复盘 + 实盘准入评估 |
一、为什么 Day 58 必须做综合演练
Day 31-49 三个策略各自跑通:
| Day | 策略 | 资产类型 | Rebalance | 持仓数量级 | 状态 |
|---|---|---|---|---|---|
| 36 | 双因子(Mom + Quality) | 个股 long-only | 月度全量 | 8-12 只 | Paper ✓ |
| 42 | Wheel(CSP → CC) | 标的 + 期权短仓 | 月度 stagger(每周 1-2 笔) | 3-5 个 underlying | Paper ✓ |
| 49 | IC + LLM | 期权 4 腿组合 | 财报事件触发(每月 5-8 单) | 2-4 个活仓 | Paper ✓ |
问题:每个策略「单独跑」可以,但真实的个人量化账户不会只跑一个策略——它一定是 portfolio level 的。
单元测试都过 ≠ 集成测试过
集成测试过 ≠ 端到端 e2e 跑得稳
e2e 跑 1 天 ≠ 跑 2 周表现稳定
Day 58 解决的是从「3 个 working prototype」到「1 个 production-ready portfolio」之间的工程问题。这一步如果不做,Phase 3 上实盘就是赌博。
二、统一架构:3 策略共用什么、独用什么
2.1 共用层 vs 策略层
┌─────────────────────────────────────────────────────┐
│ Portfolio Dashboard (port 8888) │
│ 实时 NAV / Greeks / VaR / Correlation / Alerts │
└─────────────────▲───────────────────────────────────┘
│ 读取
┌─────────────────┴───────────────────────────────────┐
│ 共用 Trade Log DB (SQLite) │
│ trades / positions / pnl_attribution / events │
└─────────────────▲───────────────────────────────────┘
│ 写入
┌──────────────┼──────────────┬──────────────┐
│ │ │ │
┌──┴───┐ ┌───┴──┐ ┌───┴──┐ ┌───┴──┐
│ Risk │ │ S1 │ │ S2 │ │ S3 │
│ Mgr │◄────►│双因子 │ │Wheel │ │IC+LLM│
└──────┘ │cid=1 │ │cid=2 │ │cid=3 │
└───┬──┘ └───┬──┘ └───┬──┘
│ │ │
└──────────────┼──────────────┘
▼
┌──────────────────────┐
│ IB Gateway (4002) │
│ Paper Account │
└──────────────────────┘
| 层 | 共用 | 独用 |
|---|---|---|
| IB 连接 | 同一个 Gateway(4002 端口) | 各自的 clientId(1/2/3) |
| Trade Log | 同一个 SQLite | 每条记录有 strategy_id |
| Risk Manager | 同一个 process,看 portfolio total | 各策略只能 query 自己的 limits |
| Dashboard | 一个 web UI | 按 strategy_id 分 tab |
| Cron 调度 | 同一个 scheduler | 不同 cadence |
| 资金账户 | 同一个 paper 账户 | 各策略有「名义分配额度」(NAV-share) |
2.2 为什么不每个策略一个独立 Gateway
理论上可以——每个策略起一个 Gateway 进程,资源隔离。但有 3 个理由不做:
- 保证金/可用资金是 account-level 的。3 个 Gateway 看到的
availableFunds是同一个数,互不感知 → 容易超额下单。 - IBKR 每个账户只允许 N 个并发 session(通常 8 个,看账户类型)。一个 Gateway 用 3 个 clientId 比 3 个 Gateway 占 3 个 session 划算。
- 运维成本。3 个 Gateway 各自重启、登录、debug,比 1 个 Gateway 3 个 clientId 复杂得多。
结论:单 Gateway + 多 clientId 是个人量化的正确姿势。
三、并行运行的 5 个实操挑战
3.1 挑战 1:不同 rebalance cadence
| 策略 | Cadence | 触发条件 |
|---|---|---|
| 双因子 | 月初第 1 个交易日 9:35 ET | 时间触发 |
| Wheel | 每周一/周三 9:45 ET 检查 CSP 到期,每周二/周四检查 CC | 时间 + 持仓状态 |
| IC+LLM | 财报前 5 个交易日窗口 + LLM 信号 > 阈值 | 事件触发 |
问题:3 个策略不会在同一时刻跑,但可能在同一天跑(如月初遇到财报季)。
解法:所有策略走同一个 scheduler,注册 trigger,scheduler 统一在 work queue 里串行 dispatch:
# scheduler.py
schedule.every().day.at("09:35").do(lambda: enqueue("two_factor", "monthly_check"))
schedule.every().monday.at("09:45").do(lambda: enqueue("wheel", "csp_check"))
schedule.every().tuesday.at("09:45").do(lambda: enqueue("wheel", "cc_check"))
# IC 是事件驱动,单独的 listener 进程监听 earnings calendar
为什么串行:3 个策略同一秒下单会引发资金/保证金一致性问题(见 3.4)。串行的代价只是几秒钟延迟,对月度/事件级策略完全可接受。
3.2 挑战 2:clientId 管理
clientId 1 = 双因子 (只看自己的 order, position 用 strategy_id 标)
clientId 2 = Wheel
clientId 3 = IC+LLM
clientId 9 = 监控/只读(dashboard 用,不下单)
关键规则:
- 每个 clientId 通过
ib.reqOpenOrders()只能看到自己提交的订单。 - 但
ib.positions()/ib.accountSummary()返回的是 account 全量——这是双刃剑:- 好处:能算总 Greeks / 总保证金
- 坏处:必须靠自己的 trade log 区分「这个 SPY 100 股是哪个策略的」
实操坑:Wheel 标的可能是 AAPL,双因子选股可能也包含 AAPL → positions() 返回的 AAPL 总数无法判定归属。必须靠 trade_log 里的 strategy_id 做"逻辑分账"。
3.3 挑战 3:保证金共享与"真实可用 buffer"
Paper 账户 $5,000,但3 个策略各自的 sizing 逻辑都假设有 $5,000 可用——会超额。
解法:名义分配 + 实时校验
# 配置层(人为分配)
STRATEGY_ALLOC = {
"two_factor": 0.50, # 50% 名义份额 = $2,500
"wheel": 0.30, # 30% = $1,500
"ic_llm": 0.20, # 20% = $1,000
}
# 下单前必查:
def can_strategy_trade(strategy_id, required_margin):
nav = ib.accountSummary("NetLiquidation")
allocated = nav * STRATEGY_ALLOC[strategy_id]
used = trade_log.get_margin_used(strategy_id)
avail = allocated - used
# 还要检查 account-level buffer
account_avail = ib.accountSummary("AvailableFunds")
return (required_margin <= avail) and (required_margin <= account_avail * 0.9)
"真实可用 buffer" = min(strategy_quota_remaining, account_avail × 0.9)。
留 10% account-level buffer 是因为:
- 期权 short 仓的 margin requirement 会随 IV 上升动态扩大
- 隔夜可能有 corporate action 改变 margin
- 留 buffer 防 forced liquidation
3.4 挑战 4:同一时间下单的 race condition
虽然 scheduler 串行 dispatch,但异步 IB API 的 ack 不一定按序回来。场景:
09:35:00.001 双因子提交 BUY AAPL 10 股
09:35:00.002 系统看到 availableFunds 还没扣 → IC 误判可下单
09:35:00.005 IC 提交 SELL AAPL 财报 IC(占用 margin)
09:35:00.020 双因子的 fill 回来,扣 margin
09:35:00.030 IC 的 reject 回来:margin insufficient
解法:用 strategy-level mutex + optimistic margin reservation:
PORTFOLIO_LOCK = threading.RLock()
def place_order(strategy_id, contract, order, est_margin):
with PORTFOLIO_LOCK: # 1. 全局锁
if not can_strategy_trade(strategy_id, est_margin):
return None
trade_log.reserve_margin(strategy_id, est_margin) # 2. 预扣
trade = ib.placeOrder(contract, order)
trade.filledEvent += lambda t: trade_log.confirm_margin(t)
trade.cancelledEvent += lambda t: trade_log.release_margin(t)
return trade
锁的粒度是「下单决策 + 预占 这一小段」,不锁 ib.placeOrder 之后的等待——所以即便 3 个策略密集下单,锁只持有几毫秒,对吞吐无影响。
3.5 挑战 5:错误恢复与对账
3 个策略并发,进程崩溃后如何恢复是 Phase 3 最容易踩坑的地方。
| 故障 | 检测 | 恢复 |
|---|---|---|
| Gateway 断连 | ib.isConnected() heartbeat | 自动重连 + replay 未确认订单 |
| Dashboard 崩溃 | 进程监控 | 重启即可(无状态) |
| Trade log DB 锁住 | timeout | WAL 模式 + retry |
| 某策略进程 OOM | systemd restart | 启动时 reconcile:拿 IB positions vs trade_log,差异报警 |
| 订单 fill 但 trade_log 没记 | 启动 reconcile | 用 reqExecutions 拉今日 fill,按 orderId 回填 |
reconcile 是核心——任何重启后第一件事就是「拉 IB 的真实持仓 vs 我的本地账本,找差异」。这一条在 Phase 1 就该养成习惯。
四、portfolio_dashboard.py 核心代码
完整版 ~600 行,这里展示骨架。
4.1 数据层:trade_log schema
-- trade_log.db
CREATE TABLE trades (
trade_id INTEGER PRIMARY KEY,
strategy_id TEXT NOT NULL, -- 'two_factor' | 'wheel' | 'ic_llm'
symbol TEXT,
secType TEXT, -- STK | OPT
right TEXT, -- C | P | NULL
strike REAL,
expiry TEXT,
action TEXT, -- BUY | SELL
qty REAL,
fill_price REAL,
fill_time TIMESTAMP,
commission REAL,
margin_reserved REAL,
notes TEXT
);
CREATE TABLE pnl_daily (
date DATE,
strategy_id TEXT,
realized REAL,
unrealized REAL,
nav_share REAL,
PRIMARY KEY(date, strategy_id)
);
CREATE TABLE greeks_snapshot (
ts TIMESTAMP,
strategy_id TEXT,
net_delta REAL,
net_gamma REAL,
net_theta REAL,
net_vega REAL
);
4.2 实时计算引擎
# portfolio_dashboard.py
from ib_insync import IB
import pandas as pd, numpy as np
from flask import Flask, jsonify, render_template
ib = IB()
ib.connect('127.0.0.1', 4002, clientId=9) # read-only
app = Flask(__name__)
def get_portfolio_snapshot():
"""Pull everything we need in ~200ms."""
positions = ib.positions()
summary = {a.tag: a.value for a in ib.accountSummary()}
# 用 trade_log 把 positions 拆到 strategy 层
log = pd.read_sql("SELECT * FROM trades", conn)
pos_by_strategy = attribute_positions(positions, log)
# 计算 Greeks(需要 modelGreeks,对每个 option contract 拉一次)
greeks = {}
for sid, pos_list in pos_by_strategy.items():
greeks[sid] = aggregate_greeks(pos_list, ib)
return {
"ts": pd.Timestamp.utcnow().isoformat(),
"nav": float(summary["NetLiquidation"]),
"cash": float(summary["TotalCashValue"]),
"available": float(summary["AvailableFunds"]),
"maintenance_margin": float(summary["MaintMarginReq"]),
"by_strategy": {sid: {
"pnl_realized": pos_by_strategy[sid]["realized"],
"pnl_unrealized": pos_by_strategy[sid]["unrealized"],
"delta": greeks[sid]["delta"],
"theta": greeks[sid]["theta"],
"vega": greeks[sid]["vega"],
} for sid in pos_by_strategy},
"portfolio": {
"net_delta": sum(g["delta"] for g in greeks.values()),
"net_theta": sum(g["theta"] for g in greeks.values()),
"net_vega": sum(g["vega"] for g in greeks.values()),
"var_95": compute_parametric_var(positions, conf=0.95, horizon_days=1),
}
}
@app.route("/api/snapshot")
def api_snapshot():
return jsonify(get_portfolio_snapshot())
@app.route("/api/correlation")
def api_correlation():
pnl = pd.read_sql("SELECT date, strategy_id, realized+unrealized AS pnl FROM pnl_daily", conn)
pivot = pnl.pivot(index="date", columns="strategy_id", values="pnl")
return jsonify(pivot.corr().to_dict())
@app.route("/api/report/<date>")
def api_daily_report(date):
"""Generate a PDF day report. Calls weasyprint to render Jinja → PDF."""
snap = get_portfolio_snapshot()
corr = pnl_correlation()
html = render_template("daily_report.html", snap=snap, corr=corr, date=date)
pdf_path = f"./reports/{date}.pdf"
HTML(string=html).write_pdf(pdf_path)
return jsonify({"pdf": pdf_path})
if __name__ == "__main__":
app.run(port=8888)
设计要点:
- Dashboard 是只读的(clientId=9,没有下单权限)。
- 快照式——每次 HTTP 请求重新拉,不缓存。简单可靠,2 周内没出过状态不一致 bug。
- PDF 日报用 Jinja 模板渲染,weasyprint 转 PDF。每天 16:30 自动生成。
4.3 VaR 计算(参数法)
def compute_parametric_var(positions, conf=0.95, horizon_days=1):
"""Simplified: assume returns ~ N(0, sigma), portfolio variance from covariance matrix."""
# 1. 拉近 60 个交易日的 daily returns
rets = fetch_returns([p.contract.symbol for p in positions], lookback=60)
cov = rets.cov() * horizon_days
# 2. 持仓权重(美元名义)
weights = np.array([p.position * p.marketPrice for p in positions])
port_var = weights @ cov.values @ weights
port_sigma = np.sqrt(port_var)
# 3. z-score
from scipy.stats import norm
z = norm.ppf(conf)
return z * port_sigma
已知简化:
- 没考虑期权的非线性(用 delta 等价正股近似,IC 这种非线性结构会被低估)
- Phase 3 要换成 Monte Carlo + full revaluation
五、2 周表现(模拟数据 / Paper)
时间窗口:2026-06-22 ~ 2026-07-05(10 个交易日)。
5.1 各策略 P&L
| 策略 | 起始名义 NAV | 终值 | 收益 | vs 基准 | 备注 |
|---|---|---|---|---|---|
| 双因子 | $2,500 | $2,520 | +0.80% | SPY +0.50% | 跑赢 SPY 30bp,主要来自 quality 因子 |
| Wheel | $1,500 | $1,522.50 | +1.50% | (covered SPY +0.5%) | premium 收入贡献 90bp,underlying 微涨贡献 60bp |
| IC+LLM | $1,000 | $997 | -0.30% | (vol 持平) | 1 单 surprise miss 亏 $35,其它 4 单赚 $32 |
| 组合 | $5,000 | $5,039.50 | +0.79% | SPY +0.5% | 年化外推 ~18%(乐观估算,警示见 §11) |
5.2 P&L 时序(简化日表)
日期 双因子 Wheel IC+LLM 组合 SPY
06-22(一) -3.20 +1.50 -2.00 -3.70 -0.31%
06-23(二) +8.50 +2.80 +0.00 +11.30 +0.62%
06-24(三) +2.10 +12.00 +0.00 +14.10 +0.18% ← Wheel CSP expire OTM 收 premium
06-25(四) -5.50 +1.20 -15.00 -19.30 -0.42% ← IC 财报 miss 调腿
06-26(五) +4.80 +2.10 +12.00 +18.90 +0.35%
06-29(一) +6.30 +1.50 -8.00 -0.20 +0.21%
06-30(二) +5.50 +2.00 +5.00 +12.50 +0.18%
07-01(三) -8.00 +0.50 -3.00 -10.50 -0.51% ← 双因子月度 rebalance 摩擦成本
07-02(四) +12.00 +1.80 +8.00 +21.80 +0.43%
07-03(五) +2.50 +0.10 +0.00 +2.60 +0.07%
───────── ────── ────── ────── ────── ──────
合计: +20.00 +22.50 -3.00 +39.50 +0.50%
5.3 Max DD 与回撤
| 指标 | 值 | 备注 |
|---|---|---|
| Portfolio max DD | -1.20% | 06-25 财报 miss 当天最深 |
| 双因子 max DD | -0.50% | 月度策略波动小 |
| Wheel max DD | -0.18% | 几乎单调上升 |
| IC max DD | -2.10% | 单笔 IC vega 风险大 |
| Sharpe(年化外推) | ~2.1 | 样本太短,仅供参考 |
六、Greeks 实时监控(2 周观察)
6.1 监控范围
每 15 分钟取一次 snapshot,2 周共 ~270 个数据点。
| Greek | 范围 | 中位数 | 解读 |
|---|---|---|---|
| Net Delta | +85 ~ +180 | +130 | 轻度多头,主要来自双因子 long-only 股票仓 |
| Net Gamma | -3 ~ +1 | -1.2 | 略微负(卖了 Wheel CC + IC 短腿) |
| Net Theta | +$22 ~ +$35/day | +$28/day | 稳定收 theta — 健康 |
| Net Vega | -$80 ~ -$160 | -$120 | short vol 倾向 — Wheel + IC 共同贡献 |
6.2 "轻 long + 收 Theta" 配置健康吗
健康,但需明确边界:
| 优势 | 风险 |
|---|---|
| Theta 每天稳定 +$28(年化 ~$7,000 / NAV $5k ≈ 140%——明显高估,因为 theta 是非线性 decay) | Vega -$120 意味着 IV 每涨 1 vol 点损 $120 |
| Delta 正向跟 SPY 有 partial 相关,市场涨时双因子也涨 | 但 gamma 微负 → 大波动反向时损失加速 |
| 多策略相关性 < 0.6,分散有效 | 都是 short vol 暴露 → "在同一边" |
关键认知:「轻 long + 收 theta」的隐含假设是 vol 不会突然飙升。如果 VIX 从 15 跳到 25,组合可能一天损失 $1,200(vega × 10),相当于 24% 的 NAV——这是必须设的 hard stop。
6.3 设置的 Greek-level 风控线
RISK_LIMITS = {
"net_delta_max": 250, # 总名义 long 不超过 250 delta
"net_delta_min": -50, # 不允许净 short
"net_vega_min": -200, # short vol 上限
"net_theta_min": 0, # 不允许 net negative theta(除非临时调腿)
"single_position_pct": 0.15, # 单一头寸不超 NAV 15%
}
2 周内没有触发任何 hard limit——但这不代表风控对——而是市场温和。Phase 3 应该故意做一次触发演练(人为加大 IC 仓位直到 vega 越线)来测试 alert 链路。
七、相关性实测:N_eff 才是真分散度
7.1 实测相关性矩阵
用 10 个交易日的 daily P&L(样本极小,仅做演示):
双因子 Wheel IC+LLM
双因子 1.00 0.55 0.15
Wheel 0.55 1.00 0.20
IC+LLM 0.15 0.20 1.00
7.2 N_eff 计算
有效策略数 = (Σw)² / (w' Σ w),其中 Σ 是相关矩阵。
权重 [0.50, 0.30, 0.20]:
import numpy as np
corr = np.array([
[1.00, 0.55, 0.15],
[0.55, 1.00, 0.20],
[0.15, 0.20, 1.00]
])
w = np.array([0.50, 0.30, 0.20])
n_eff = (w.sum())**2 / (w @ corr @ w)
# = 1.0 / 0.477 = 2.10
N_eff = 2.10——3 个策略只相当于 2.1 个独立策略。
7.3 为什么不是 3.0
- 双因子和 Wheel 都做股票多头方向 → 0.55 相关合理
- IC 是 vol 维度,跟方向策略弱相关 → 0.15-0.20 合理
- 要把 N_eff 推到 2.5+,需要加入 negatively correlated 策略:
- 趋势跟随(CTA-like)— 在 risk-off 时反向受益
- Long vol(保护性买 VIX call)— 但成本高
- Mean-reversion 短线 — 与 momentum 反向(双因子的 mom 部分会受影响)
Phase 3 的方向:考虑加一个低相关的趋势/mean-reversion 策略,把 N_eff 推到 2.5。
八、2 周复盘 Lessons
8.1 策略层
| 策略 | 表现符合预期? | 关键观察 |
|---|---|---|
| 双因子 | ✓ 稳定 | 月度 turnover ~35%,佣金 + slippage 吃了 ~15bp,对 $2,500 的小盘子摩擦显著 |
| Wheel | ✓✓ 超预期 | 06 月底 IV 较高(VIX 19),premium 比模型估值高 12%。但这是 regime-dependent——低 IV 时收益会减半 |
| IC+LLM | △ 单笔波动大 | 5 单里 1 单财报 surprise miss 抹平了其它 4 单的利润。N=5 完全没统计意义,需要至少 N=30 才能判断 edge |
8.2 工程层
| 类别 | 观察 |
|---|---|
| 集成测试 vs 单元测试 | 集成阶段发现的 bug 比策略各自跑时多 6 倍(详见 §10):margin 预扣 race / clientId 看不到对方订单 / SQLite WAL 模式没开 |
| Dashboard 价值 | 每天看一眼实时 Greeks 比看 P&L 有用 10 倍——能预判风险而不是事后归因 |
| PDF 日报 | 写完才发现:自己几乎不看历史报告。Phase 3 改成「只在异常时生成 PDF + 周末做一次汇总」 |
8.3 心理层(重要)
- 06-25 IC miss 当天我下意识想手动平掉所有 IC 仓——这是典型的 loss aversion。如果实盘下会破坏系统。
- 解决:实盘前必须把"手动 override"的门槛拉高——比如手动平仓需要二次确认 + 写理由到 log。
九、是否准备好实盘?— 5 维度评估
| 维度 | 状态 | 证据 | 距离实盘的差距 |
|---|---|---|---|
| 流程跑通 | ✓ | 2 周 e2e 无人工干预 | 0 |
| 风控触发测试 | △ | 风控线设了,但从未真触发过 | 必须做 1 次故意触发演练 |
| Greeks 监控自动化 | ✓ | 270 次 snapshot 全部记录 | 0 |
| 错误恢复 | △ | Gateway 重连自动化了;但没测过 trade_log DB 损坏 + reconcile | 需要做 1 次"杀进程 + 重启 reconcile" |
| 心态承受 | ✗ | 06-25 当天想手动干预 | 这是最大的未知——paper 没真金白银压力 |
9.1 结论:再 2-4 周 paper
不建议下周直接上实盘。理由:
- 样本量:IC 只跑了 5 单,统计上完全无意义。
- 未触发风控:风控线没被真测过,等于没有。
- 心态:paper trading 测不出真实压力反应。
9.2 上实盘前 must-have checklist
- 再跑 2-4 周 paper
- 人为触发所有 hard stop 至少 1 次(vega 越线 / delta 越线 / 单仓越限)
- 杀进程 + 重启 reconcile 测试 ≥ 3 次
- 实盘第一周只跑双因子(最稳定)+ 金额减半($2,500 真金)
- 实盘到第 4 周再加 Wheel
- 实盘到第 8 周再加 IC(前提:再 paper 25+ 单 IC 看 N>=30 的统计表现)
十、PM 视角:综合演练教会我们什么
10.1 "Integration test 比 unit test 暴露 10 倍问题"
我在 fintech 做了 10 年 PM/BA,反复见证这个规律:
| 测试层 | 找到的 bug 类型 | 数量级 |
|---|---|---|
| Unit test | 函数逻辑错 / 边界条件 | 基线 1x |
| Integration test | 模块间接口 / 状态共享 / 时序 | 3-5x |
| End-to-end / Beta | 跨系统耦合 / 真实数据 / 错误恢复 | 10x |
| 生产环境 | 长尾场景 / 罕见组合 / 性能退化 | 30x |
对应到 Day 58:单策略 paper 时只有少量 bug,3 策略并行 paper 立刻冒出 6 个新问题:
- 同一个 AAPL 在双因子和 Wheel 同时存在 → position 归属不明
- Wheel 短 put 占的 margin 让双因子 reject 下单
- SQLite 默认 journal mode 在并发写时锁死
- Dashboard 拉 modelGreeks 太慢导致 API rate limit
- 财报日 IC 触发跟双因子月初触发冲突
- 重启后 trade_log 比 IB 实际持仓少 1 笔(race 时 ib.placeOrder 返回但 callback 没触发)
核心 takeaway:「Phase 1 + Phase 2 上半把单元做完」 → 「Phase 2 下半的综合演练」 → 「Phase 3 再上实盘」——这个三段式不能跳,跳了就是用真金白银做 e2e 测试。
10.2 "可观测性 = 安全感"
2 周下来,最值的投入不是策略代码,而是 dashboard:
- 每天早上花 30 秒看一眼 Greeks → 知道今天有没有 abnormal 暴露
- 周末花 5 分钟看 correlation → 知道分散度有没有恶化
- 财报日盯一下 IC 仓的 vega → 提前知道下单时机
金融系统里,"看得见"比"算得准"更值钱——后者你可以买现成工具,前者必须自建。
10.3 "策略相关性 = 真正的容量上限"
很多人以为加策略就能加规模——错。N_eff < N 的本质是:当 3 个策略都在 short vol 时,它们其实是一个策略放了 3 个名字。
实盘扩规模的正确路径:
N_eff = 2.1(今天) → 找一个 ρ ≈ -0.3 的策略 → N_eff = 2.8 → 才能上 2x 名义规模
而不是「Wheel 表现好就加仓 3 倍」。这是 Phase 3 / 4 必须想清楚的事。
10.4 "Paper 跑得好不能证明实盘能赚——但 Paper 跑不通一定证明实盘做不了"
Paper 是淘汰赛不是选拔赛。它能让我们排除:
- 流程跑不通的策略(剔除)
- 工程上有 race / 重启问题的实现(剔除)
- 风控完全没设的策略(剔除)
但 Paper 不能告诉我们:
- 真实滑点
- 自己的心态承受边界
- Black swan 时的行为
这两点只有实盘小金额能教。
十一、限制 / 假设 / 警示
写在前面让自己别忘:
| 项 | 说明 |
|---|---|
| 样本量 | 10 个交易日 / IC 5 单 / Wheel 2 单——统计上无意义,所有结论是"流程验证级"而非"alpha 验证级" |
| 基准 | SPY 同期 +0.5% 是巧合,不代表组合稳定超额 |
| 年化外推 | +0.79% × 26 ≈ 20%——绝不能这样推,paper 没滑点没 IV 反应没真实 fill |
| Greeks | modelGreeks 来自 IBKR 内置定价,IC 这种 4 腿组合在尾部行情可能偏离 10%+ |
| VaR | 用参数法 + delta 等价,严重低估期权尾部风险——Phase 3 必换 MC |
| 相关性 | N=10 算出的 corr 标准误 ~0.3,0.55 和 0.15 的差距在统计上其实不可区分——只能定性参考 |
十二、Day 58 执行 Checklist
- (1) 把 Day 36 / 42 / 49 的代码合并进
momoweb3/quant/portfolio/目录 - (2) 建
trade_log.db(SQLite WAL 模式)+ schema migration 脚本 - (3)
scheduler.py注册 3 个策略的 trigger - (4) 给每个策略加
clientId+strategy_id参数 - (5) 实现
can_strategy_trade()+PORTFOLIO_LOCK - (6)
portfolio_dashboard.pyweb UI 跑起来(port 8888) - (7) 启动 paper 模式,让 3 个策略各自按 cadence 跑 2 周
- (8) 每日 16:30 cron 自动生成 PDF 日报
- (9) 写 2 周复盘 → 本笔记 §8 落地
- (10) 评估实盘准入 → §9 5 维度填实
- (11) 列出"上实盘前 must-have" 清单 → §9.2
十三、明日预告
Day 59: Phase 2 总结 — 28 天策略实战的横向复盘
- 把 Day 31 ~ Day 58 这 28 天的所有策略(双因子 / Wheel / IC+LLM / 综合演练)做横向对比
- Phase 2 的硬产出清单:3 个 paper-ready 策略 + 1 个 portfolio dashboard + N 篇笔记
- Phase 2 学到的"反直觉认知" Top 10
- Phase 3 路线图预告:实盘 onboarding / 实盘第一笔交易 SOP / 实盘前 30 天的 monitoring
- 跟金融 BA / PM 视角的迁移性思考:从"个人量化"到"机构级 risk-aware 系统"还差什么
实际执行记录
启动一项填一项,时间戳 + 卡点。
- [hh:mm] 代码合并 — ...
- [hh:mm] trade_log.db 建表 — ...
- [hh:mm] scheduler 配置 — ...
- [hh:mm] portfolio_dashboard 启动 — ...
- [hh:mm] Day 1 paper 三策略并行运行 — ...
- [hh:mm] 第 1 周复盘 — ...
- [hh:mm] 第 2 周复盘 + 实盘准入评估 — ...
- 卡点 / 学到的:
总字数:约 6,800 字 今日完成度:理论 ✓ / 实操(你自己跑 2 周 paper) / 笔记 ✓