chundev
日期:2026-03-17 技術棧: TypeScript(回測引擎 + Next.js API)、Python(Executor)、Docker + crontab
回測引擎的加碼/保本減碼用「當天收盤價即時執行」,但實盤 executor 只能在隔天開盤下單(T+1)。這個時序差異讓回測虛高 10%。修正方式:引擎改成 T+1 模型,加碼改走夜盤 open(當日 17:21 執行),最終回收 57% 的損失,且三方邏輯完全一致。
一個期貨策略系統有三個執行層:
三者應該在「什麼時候做什麼」上完全一致,否則回測績效就不可信。
回測引擎的加碼邏輯:
D 日收盤 → 判斷浮盈 ≥ 5% → 立刻加碼(用 D 日 close 價)
但 executor 的實際流程:
D 日 13:45 日盤收盤
D 日 15:30 資料更新(API 才看得到 D 日數據)
D+1 08:30 executor 讀到 D 日訊號 → 用 D+1 open 價下單
時序差距約 18 小時,在趨勢延續中 D+1 open 通常比 D close 更高,加碼成本更貴。
同樣的問題也出現在保本減碼:引擎即時砍半,但 executor 只能隔天砍。
把引擎所有操作改成 T+1 open 後:
| 指標 | 即時執行(舊) | T+1 open(修正後) |
|---|---|---|
| 年化報酬 | 67.4% | 57.3% |
| 最終資金 | 2,055 萬 | 1,233 萬 |
| 最大回撤 | 31.9% | 32.2% |
年化差 10.1%,最終資金差 40%。 原因是加碼成本更高 × 8 年複利放大。
分析訊號日的夜盤 open vs 隔日日盤 open:
| 訊號日 | 夜盤 open | T+1 日盤 open | 差值 |
|---|---|---|---|
| 2024-02-15 | 647 | 698 | +51 |
| 2024-06-12 | 884 | 927 | +43 |
| 2025-12-08 | 1465 | 1505 | +40 |
| 2026-01-05 | 1605 | 1675 | +70 |
| 2026-02-10 | 1825 | 1890 | +65 |
夜盤 open 永遠比 T+1 日盤 open 便宜(平均差 43 點 ≈ 8.6 萬/口)。
因為夜盤 15:00 開盤,資料 15:30 更新後立刻可以判斷,17:21 下單距離收盤只有 ~2 小時,價格漂移最小。而 T+1 日盤 open 距離收盤 18 小時,趨勢延續讓價格更高。
但保本減碼是賣出,賣越高越好。所以保本維持 T+1 日盤 open,讓賣出價格更高。
最終方案:
| 操作 | 執行時機 | 理由 |
|---|---|---|
| 進場 | T+1 日盤 open(08:30) | 已對齊 |
| 出場 | T+1 日盤 open(08:30) | 已對齊 |
| 加碼 | D 夜盤 open(17:21) | 買得便宜 |
| 保本 | T+1 日盤 open(08:30) | 賣得更高 |
結果:
| 指標 | 全 T+1 | 夜盤加碼 + T+1 保本 |
|---|---|---|
| 年化報酬 | 57.3% | 63.1% |
| 最終資金 | 1,233 萬 | 1,659 萬 |
回收了 57% 的 T+1 損失。
db push vs migrate deploy開發時用 db push 新增的 model 沒有 migration 檔案。Docker entrypoint 跑 migrate deploy 是空操作 → 新的 table 在 runtime 不存在 → API 500。
❌ entrypoint: prisma migrate deploy → 無 migration 可跑
✅ entrypoint: prisma db push --skip-generate → 直接同步 schema
教訓: 如果團隊用 db push 開發,部署也要用 db push,不能混用 migrate deploy。
loadProductionConfig() 只合併了 params(flat key-value),但 dynamicPositionSizing 和 entryConfidence 是頂層物件,沒被合併到 config → 前端用的 tiers 跟 CLI 不同。
// 修正前:只覆蓋 entry/exit params
const config = applyOptimizedParams(file.params, defaultConfig);
// 修正後:額外合併頂層區塊
if (file.dynamicPositionSizing) {
config.dynamicPositionSizing = { ...config.dynamicPositionSizing, ...file.dynamicPositionSizing };
}
保本減碼(砍半口數)觸發後,同筆交易不應再加碼回去。否則等於「砍完又買回來」,失去保本意義。
❌ D: 加碼 8→9 口 + 保本 9→5 口 → D+1: 又加碼回 9 口
✅ D: 加碼 8→9 口 + 保本 9→5 口 → 之後禁止加碼直到出場
用 capitalProtectionTriggered flag 控制,一旦設為 true,整筆交易不再觸發加碼。
部署只檢查 /api/health 不夠。加碼/保本的 API 可能因為 Prisma Client 沒有新 model 而 500,但 health check 通過。
smoke-test:
curl /api/health # 基本健康
curl /api/executor/signal # 策略訊號 API
curl /api/executor/report # 回報 API(POST hold)
三個都通過才算部署成功。
回測的「即時執行」假設是隱性的未來函數。 只要實盤不能在收盤價下單,就要用 T+1 open 模型。這個差異在高頻加碼的策略中會被複利放大。
夜盤是 T+1 gap 的天然解藥。 台灣期貨夜盤 15:00 開盤,收盤資料 15:30 更新,17:21 下單只差 2 小時。比等到隔天 08:30 省 43 點/口。
「買」和「賣」的最佳執行時機不同。 加碼(買入)越早越便宜,保本(賣出)越晚越高。分開處理比統一 T+1 更優。
Prisma db push 和 migrate deploy 不能混用。 開發用 push、部署用 migrate → 新 model 不會出現在 production。要嘛全用 push,要嘛嚴格要求 migration 檔。
部署 smoke test 要測業務 API,不只測 health。 Schema 變更造成的 runtime error 不會被 health check 抓到。