在 CI 跑 PineScript 回測(GitHub Actions、GitLab、凡支援 Docker 者)
策略 repo 每次 commit 觸發回測。對齊被主張,迴歸就擋 build。與本機同一個 Docker 映像,在 runner 執行。
為何在 CI 做對齊
策略跟任何程式碼一樣會漂移。改參數名、重構 helper、沒注意到的 Pine v6 API 變更 — 都可能悄悄挪動訊號卻不打破既有測試。CI 沒對齊門檻時,兩個月前的 commit 可能改掉上週成交列表,你要等實盤損益怪了才發現。
把參考成交列表跟策略原始碼一起 commit 的紀律,就是讓軟體可靠的同一套:描述預期輸出,實際一偏 build 就炸。對回測而言預期輸出就是成交列表 — 進場棒、出場棒、方向、部位、成交價 — 對釘死的歷史資料集。
每次 commit 都比對,就形成棘輪:策略歷史行為只能改善,不會意外退步。你能 exactly 哪個 commit 改了輸出,因為該 commit build 失敗。權益曲線也有 git blame。
多數量化人一開始低估這件事。「我測過這策略」與「這策略每個歷史訊號都有可重現、版控紀錄」差距巨大。前者是截圖;後者是稽核軌跡。CI 讓你在每次 push 不必手動維持後者。
PineForge 讓 Pine 策略做得到,因為 runtime 是 Docker 映像:Docker 能跑就能跑、同輸入給決定性輸出、JSON schema 穩定適合 shell 解析。無瀏覽器、無認證流程、回測執行本身無流量上限。
GitHub Actions 範例
完整流程:checkout 策略、經 codegen API 轉成共享物件、對釘死的 OHLCV CSV 跑回測、用 jq 解析 JSON 報告,與已 commit 的 baseline 比較,若淨損益差超過門檻就讓 build 失敗。
把 PineForge API key 設成 repo secret,名稱 PINEFORGE_API_KEY,再建立 .github/workflows/backtest.yml:
name: Backtest parity
on:
push:
branches: [main, "feat/**"]
pull_request:
jobs:
backtest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Transpile strategy to .so
run: |
curl -s https://api.pineforge.io/v1/codegen \
-H "Authorization: Bearer ${{ secrets.PINEFORGE_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"source": "'"$(cat strategy.pine | jq -Rs .)"'"}' \
| jq -r '.artifact_url' \
| xargs curl -sL -o strategy.so
- name: Pull PineForge runtime
run: docker pull ghcr.io/pineforge/runtime:latest
- name: Run backtest
run: |
docker run --rm \
-v "$PWD/strategy.so":/strategy.so \
-v "$PWD/data/ohlcv.csv":/data.csv \
ghcr.io/pineforge/runtime:latest \
run /strategy.so --data /data.csv --output json \
> report.json
- name: Assert parity
run: |
ACTUAL=$(jq '.summary.net_pnl' report.json)
BASELINE=$(jq '.summary.net_pnl' baseline/report.json)
DELTA=$(echo "$ACTUAL $BASELINE" | awk '{d=$1-$2; print (d<0)?-d:d}')
THRESHOLD="0.01"
if awk "BEGIN{exit !($DELTA > $THRESHOLD)}"; then
echo "Parity drift: net_pnl delta $DELTA exceeds threshold $THRESHOLD"
diff <(jq '.trades' baseline/report.json) \
<(jq '.trades' report.json)
exit 1
fi
echo "Parity OK — delta $DELTA (threshold $THRESHOLD)"把 baseline 報告放在 repo 的 baseline/report.json,與策略原始碼一起 commit。PR 若刻意改行為,作者在 PR 內更新 baseline。該 diff 成為永久紀錄:改了什麼、為什麼。
同一流程在 GitLab CI、Bitbucket Pipelines 或任何支援 Docker 的 runner 等同運行 — 換 YAML 語法,shell 指令保留。
退出碼與對齊主張
PineForge runtime 乾淨執行時退出碼為 0;若發生引擎錯誤(畸形 Pine、不支援內建、資料解析失敗)則為非零。CI 會自動讀取這些退出碼 — 引擎級失敗無需額外處理。
對齊主張另議,由你的工作流實作。JSON 報告 schema 在 patch 版本間穩定:
{
"summary": {
"total_trades": 47,
"net_pnl": 12483.50,
"max_drawdown": 3201.00,
"profit_factor": 1.82,
"sharpe_ratio": 1.34,
"win_rate": 0.617
},
"trades": [
{
"entry_bar": 142,
"exit_bar": 156,
"direction": "long",
"size": 1.0,
"entry_price": 42310.5,
"exit_price": 44820.0,
"pnl": 2509.50,
"exit_reason": "strategy.close"
}
]
}簡單數值對齊可比較 summary.net_pnl 與 summary.total_trades 對 baseline。嚴格逐筆對齊則對完整 trades 陣列 diff — 進出場棒索引、方向、成交價皆應匹配。不匹配時 diff 會指出哪筆成交偏了、在哪根棒、價差多少。
數值欄位實務門檻:浮點差容忍到 0.01(一美分,或正規化序列上一個 bp)以上才失敗。逐筆 diff 時, entry_bar 或 exit_bar 任一不匹配即硬失敗,無論損益多近 — 成交落在不同棒代表訊號邏輯已變。
採用對齊 CI 的團隊通常把 baseline 更新做成獨立 workflow 步驟,並用 PR label 控制。刻意的策略改善會留在 PR 時間軸 — baseline diff 與造成變更的 Pine 一起在 code review 現身。