Multi-asset unified portfolio backtesting framework
Project description
ChidoriBT ⚡
超高速多資產回測框架 / Multi-Asset Backtesting for Self-Hosted Quant Systems
PyPI version Python 3.9+ License: MIT Numba
專案定位
ChidoriBT 是面向自建量化交易系統的多資產組合回測引擎:以 (T, N) 面板承載 OHLCV 與 metadata,在同一資金池上跑現股、多空與期貨策略,並支援 RL 軌跡重放與大量參數掃描。
核心思路借鑑 vectorbt 一貫採用的 向量化面板 + Numba JIT 路線——先在 NumPy 上表達訊號與目標倉位,再把熱路徑編譯成 @njit kernel。ChidoriBT 在此基礎上,針對台股實務(整數股、統一資金池、期貨保證金)與自建系統整合(RL slow path、批量 prange 掃描)做專項擴展,而非重新發明回測範式。
vectorbt 在指標分析、互動式研究與
Portfolio.from_signals等場景非常成熟;ChidoriBT 則專注於「已固定 panel/軌跡 → 大量掃參 → 嵌入自有 gym/ledger」這條管線。兩者可以並用,benchmark 僅供同場景參考,見 docs/benchmark_paths.md。
設計哲學(千鳥)
千鳥(Chidori) — 精準、迅速,如閃電貫穿黑暗。
- 向量化優先:訊號、目標倉位、權益曲線以
(T, N)/(T,)陣列流動,避免 Python 逐筆迴圈 - JIT 編譯熱路徑:成交模擬、再平衡、批量掃描走
numba_kernels.py - 雙路徑依情境取捨:要審計走 slow path;要掃參走 fast path(見下表)
- 可嵌入自建系統:
fast=False逐步 callback;mode="target"對接 RL 與期貨帳務
依情境的效能設計
同一套 panel 資料,依任務選不同執行路徑——不是「永遠比較快」,而是在對的場景做對的取捨:
| 應用情境 | API / 模式 | 做了什麼 | 適合 |
|---|---|---|---|
| 參數粗篩 | ParamScanner.scan_fast() / run(fast=True) |
Numba kernel 只算 final_equity;不建 fills、orders、逐筆 log |
數百~數千組參數 grid search |
| 批量並行掃描 | batch_scan_* + prange |
多組參數在 Numba prange 內並行,共用同一 (T,N) panel |
多核 CPU 上跑大量組合 |
| 完整審計 | scan_full() / run(fast=False) |
Python 事件驅動,產出 trades / fills / audit log | 策略驗證、對帳、除錯 |
| RL / ML 逐步推論 | run(fast=False) + MultiAssetStrategy.next() |
每個 t 可呼叫 外部 model.predict |
PPO 等需與 gym 一致的 slow path |
| 等權再平衡 | mode="rebalance" + fast |
向量化權重 → Numba 再平衡 loop | 因子/配置型策略 |
| 多空/期貨目標倉位 | mode="target" + fast / slow |
slow:PortfolioLedger / FuturesPortfolioLedger;fast:run_target_rebalance_loop 或 run_futures_target_loop |
自建 gym 軌跡重放、保證金掃參 |
Numba 並行:若環境變數 NUMBA_NUM_THREADS=1(IDE 常見),import chidoribt.benchmark 會自動改為 CPU 核心數。
0.4.x:期貨回測擴展(自建量化系統)
針對已有 gym + 期貨 ledger 的自建系統,ChidoriBT 提供可對照的 fast path,讓「軌跡已固定、反覆掃 commission / margin 參數」不必每輪重跑 env.step():
| 模組 | 職責 |
|---|---|
PositionIntent / action_codec |
PPO 離散動作 → 目標倉位(7 / 13 檔) |
panel.extra + AssetMeta |
instrument_type、multiplier、margin_ratio、roll_flag、close_next_month |
FuturesPortfolioLedger |
Python golden reference(保證金、換月語意) |
run_futures_target_loop |
Numba fast path;instrument_type=1 自動切換 |
batch_scan_target_futures |
期貨批量 prange 掃描 |
| parity 測試 | Numba vs Python 逐日一致(rtol 1e-6) |
parity 測試見 tests/test_futures_numba_parity.py、tests/test_chidoribt_parity.py;範例見 examples/futures_target_scan.py。
與 vectorbt 的定位差異
兩者都善用向量化與 Numba;差異在預設解決的問題不同:
| 面向 | vectorbt | ChidoriBT |
|---|---|---|
| 強項 | 豐富 analytics、指標、互動研究、from_signals 工作流 |
統一資金池多資產、台股整數股、內建 ParamScanner prange 掃描 |
| 批量掃參 | 需自行組裝迴圈或 Portfolio 廣播 | scan_fast / batch_scan_* 內建,只回傳淨值矩陣 |
| ML / RL | 以向量化訊號為主 | fast=False 事件驅動 + mode="target" 軌跡重放 |
| 期貨保證金 / 換月 | 需自行建模 | mode="target" + 期貨 Numba kernel(0.4.x) |
同場景 benchmark(100 支台股 × 726 日、子母線策略;vectorbt 以 Portfolio.from_signals 對齊),ChidoriBT 在「只算淨值的批量掃描」上記憶體與耗時較省——詳見 docs/benchmark_paths.md。此結果反映任務定義不同(fast 不算 trades),不代表 vectorbt 在通用分析上較弱。
安裝
Python ≥ 3.9,< 3.13
pip install "chidoribt[benchmark]"
快速開始:參數掃描
典型流程:clone repo → 準備資料 CSV → 載入面板 → JIT 暖機 → 掃描參數。
pip install chidoribt只安裝套件本身;以下範例腳本在 repo 的**examples/** 目錄,需先git clone取得。
git clone https://github.com/backtestdog/chidoribt.git
cd chidoribt
pip install -e ".[benchmark]"
cd examples
範例檔案一覽(examples/)
| 檔案 | 是否在 repo 內 | 用途 |
|---|---|---|
chidoribt.prepare_data |
✅ pip 內建 | 準備測試資料(CSV / DB / 合成) |
examples/prepare_data.py |
✅ | 同上 CLI 入口(向後相容) |
examples/test_scan.py |
✅ | 240 組合批量掃描效能測試 |
examples/test.py |
✅ | 單組 fast 淨值篩選效能測試 |
examples/test.ipynb |
✅ | 同上流程的 Notebook 版(支援 Google Colab) |
examples/futures_target_scan.py |
✅ | 期貨多空目標倉位批量掃描(mode="target") |
examples/data/panel.csv |
❌ 本地產生 | 測試用 OHLCV 長表(執行 prepare_data.py 後才會出現) |
1. 準備測試資料 — examples/prepare_data.py
CSV 為 stacked 長表,欄位:stock_id, date, Open, High, Low, Close, Volume。
無外部資料(產生合成 OHLCV,建議首次使用):
cd examples
python prepare_data.py --source synthetic --export data/panel.csv
合成資料會嵌入母子/吞噬型態,確保 ChildParentStrategy 有進場訊號。若 final_equity 等於初始資金(如 1,000,000),代表資料中無有效訊號,請重新執行上述指令產生 CSV。
已有 CSV:
python prepare_data.py --source csv --csv /path/to/your.csv --export data/panel.csv
從 PostgreSQL 一次性匯出(可選;需自行設定 DB_HOST 等環境變數或 .env):
python prepare_data.py --source db --export data/panel.csv
2. 執行測試
**examples/test_scan.py** — 240 組合批量掃描:
python test_scan.py
**examples/test.py** — 單組 fast 篩選:
python test.py
examples/test.ipynb — Notebook 版(含 Google Colab):
在 Colab 開啟(從上而下執行各儲存格;首格會 pip install "chidoribt[benchmark]")
兩支測試腳本皆透過 prepare_panel(source="auto") 載入:有 data/panel.csv 就用 CSV,否則嘗試 DB。也可用環境變數覆寫:
set CHIDORI_DATA_SOURCE=csv
set CHIDORI_DATA_CSV=data/panel.csv
python test_scan.py
3. 程式碼對照 — examples/test_scan.py
# examples/test_scan.py
from chidoribt.benchmark import (
DEFAULT_SCAN_GRID,
bench_param_scan,
print_bench_report,
print_numba_info,
warmup_numba_kernels,
)
from chidoribt.prepare_data import prepare_panel
print_numba_info()
panel, meta = prepare_panel(source="auto", stocks=100)
warmup_numba_kernels(panel)
timer, summary = bench_param_scan(
panel=panel, n=3,
initial_cash=1_000_000, max_positions=20,
**DEFAULT_SCAN_GRID,
)
print_bench_report(timer, extra={
"n_combos": summary["n_combos"],
"best_final_equity": f"{summary['best_final_equity']:,.0f}",
})
4. 程式碼對照 — examples/test.py
# examples/test.py
from chidoribt.benchmark import (
bench_single_fast,
print_bench_report,
print_numba_info,
warmup_numba_kernels,
)
from chidoribt.prepare_data import prepare_panel
print_numba_info()
panel, meta = prepare_panel(source="auto", stocks=100)
warmup_numba_kernels(panel, stop_loss=0.05, take_profit=0.10)
timer, summary = bench_single_fast(
panel=panel, n=3,
hold_days=5, stop_loss_pct=0.05, take_profit_pct=0.10, trade_size=50_000,
initial_cash=1_000_000, max_positions=20,
)
print_bench_report(timer, extra={"final_equity": f"{summary['final_equity']:,.0f}"})
DEFAULT_SCAN_GRID(240 組合,test_scan.py 使用):hold [3,5,7,10,15] × sl [0.03…0.10] × tp [0.08…0.20] × trade_size [20k,30k,50k]
scan_fast vs scan_full
| 模式 | 方法 | 回傳 |
|---|---|---|
| fast | scan_fast() / run_single_fast() |
僅 final_equity(極速篩選) |
| full | scan_full() / run_single_full() |
含 sharpe、equity_curve、trades 等完整輸出 |
自訂策略
以下為程式碼片段範例(需自行建立 .py 檔或於 notebook 中使用)。
# my_strategy.py(範例,需自行建立)
import pandas as pd
from chidoribt.strategies.base import MultiAssetStrategy
from chidoribt.core import PanelData
class MyStrategy(MultiAssetStrategy):
def init(self):
pass
def next(self, timestamp: pd.Timestamp, panel: PanelData, t: int) -> dict[str, bool]:
"""每個交易日回傳 {stock_id: True/False} 進場訊號。"""
signals = {}
for i, stock_id in enumerate(panel.stock_ids):
close = panel.close[t, i]
ma20 = panel.close[max(0, t-20):t, i].mean() if t >= 20 else close
if close > ma20:
signals[stock_id] = True
return signals
搭配 ChidoriBT 執行完整回測:
# run_backtest.py(範例,需自行建立)
from chidoribt import ChidoriBT
bt = ChidoriBT(data=panel, strategy=MyStrategy, cash=1_000_000, commission=0.001425)
result = bt.run()
print(result["metrics"])
執行模式
| 模式 | 參數 | 說明 |
|---|---|---|
| JIT 加速(預設) | fast=True |
Numba JIT,最快 |
| 事件驅動 | fast=False |
含完整 fills/orders;支援 ML 插入 |
| 等權再平衡 | mode="rebalance" |
每日等權分配;ParamScanner(mode="rebalance") 可批量掃描 |
| 目標倉位多空 | mode="target" |
PositionIntent 多空;現股 / 期貨皆支援 fast path;RL 見下方文件 |
# 執行模式範例(程式碼片段)
result = bt.run(mode="portfolio", fast=True) # 預設
result = bt.run(mode="portfolio", fast=False) # audit log + ML
result = bt.run(mode="rebalance", fast=True) # 等權再平衡
result = bt.run(mode="target", fast=False) # RL / 多空 slow path(逐步 audit)
result = bt.run(mode="target", fast=True) # 多空 / 期貨 Numba fast path
多空/期貨回測(mode="target")
panel.extra 帶入期貨 metadata(皆為 (T, N) 矩陣)後,fast path 自動切換期貨保證金 kernel:
# futures_scan.py(節錄自 examples/futures_target_scan.py)
import numpy as np
from chidoribt import ParamScanner, PanelData
from chidoribt.core.futures_margin import FuturesMarginSettings
extra = {
"instrument_type": np.ones((T, N), dtype=np.int8), # 1 = 期貨,0 = 現股
"multiplier": np.full((T, N), 50.0), # 契約乘數
"margin_ratio": np.full((T, N), 0.08), # 保證金比例
"roll_flag": roll_mask, # 換月日
"close_next_month": next_month_close, # 次月收盤價
}
panel = PanelData.from_dataframe(df, extra=extra)
# 目標倉位矩陣:target_side (T,N) ∈ {1, -1, 0}、target_pct (T,N) ∈ [0, 1]
scanner = ParamScanner(
panel, mode="target",
target_side=target_side, target_pct=target_pct, trade_mask=trade_mask,
margin_settings=FuturesMarginSettings().to_numba_vector(),
initial_cash=2_000_000, commission=0.0005,
)
df_result = scanner.scan_fast(pct_scale=[0.4, 0.6, 0.8, 1.0])
- 換月:
roll_flag[t,i]=True時近月平倉,若close_next_month[t,i]有效且現金足夠手續費,以次月價同口數重建。 - 單次回測:
ChidoriBT(panel, strategy=..., fast=True).run(mode="target", margin_settings=...),策略以PositionIntent表達目標倉位。 - RL 整合:以
actions_to_intents將 PPO 離散動作(7 / 13 檔)轉成PositionIntent,或匯出軌跡後走 fast path 掃參數。 - Parity 保證:Numba kernel 與
FuturesPortfolioLedger(Python golden reference)逐日一致(rtol 1e-6)。
詳細路徑與 benchmark 說明見 docs/benchmark_paths.md。
回傳欄位: equity_curve、trades、fills(fast=False)、metrics(total_return、sharpe、max_drawdown、win_rate、n_trades)
台股費率說明
| 方向 | 費用 |
|---|---|
| 買進 | 手續費 0.1425% |
| 賣出 | 手續費 0.1425% + 證交稅 0.3% |
目前 commission=0.001425 為對稱費率(benchmark 對照用),尚未模擬賣出證交稅。
套件架構
chidoribt/
├── benchmark.py # Numba 設定、掃描基準工具(DB 載入為可選)
├── engine.py # 主引擎
├── action_codec.py # PPO 離散動作 → PositionIntent
├── core/
│ ├── panel.py # PanelData(T×N NumPy 面板 + extra metadata)
│ ├── numba_kernels.py # @njit kernel(現股 / 期貨 target loop、批量掃描)
│ ├── scanner.py # ParamScanner(批量 prange 掃描;mode="target" 支援期貨)
│ ├── intent.py # PositionIntent / Side / HoldIntent
│ ├── asset_meta.py # 從 panel.extra 解析期貨 metadata
│ ├── portfolio_ledger.py # 現股多空帳本(slow path)
│ ├── futures_margin.py # FuturesMarginSettings(保證金常數向量)
│ └── futures_ledger_ref.py# 期貨帳本 Python golden reference
├── adapters/ # ledger / panel_futures / trajectory_export 等
├── strategies/ # MultiAssetStrategy + 範例策略
├── analytics/ # 績效指標
└── data/ # PostgreSQL / Mini-TSDB 載入
ML 模型整合(fast=False)
事件驅動模式(fast=False)在每個時步呼叫 strategy.next(),適合在回測迴圈內嵌入 ML 推論——這與 Numba fast path「一次跑完整段 (T,N) 陣列」的設計互補。以下為程式碼片段範例(需自行建立 .py 檔)。
# ml_strategy.py(範例,需自行建立)
import numpy as np
from chidoribt import ChidoriBT
from chidoribt.strategies.base import MultiAssetStrategy
from chidoribt.core import PanelData
class MLStrategy(MultiAssetStrategy):
def __init__(self, model, params=None):
super().__init__(params or {})
self.model = model
def init(self):
self.window = 20
def next(self, timestamp, panel: PanelData, t: int) -> dict[str, bool]:
if t < self.window:
return {}
window_close = panel.close[t - self.window:t, :]
returns = np.diff(window_close, axis=0) / (window_close[:-1] + 1e-9)
X = np.hstack([
returns.mean(axis=0, keepdims=True).T,
returns.std(axis=0, keepdims=True).T,
(returns[-5:].sum(axis=0, keepdims=True)).T,
])
probs = self.model.predict_proba(X)[:, 1]
return {panel.stock_ids[i]: True for i, p in enumerate(probs) if p > 0.6}
bt = ChidoriBT(
data=panel,
strategy=MLStrategy(model=trained_model),
cash=1_000_000,
commission=0.001425,
fast=False, # 必須使用事件驅動模式
)
result = bt.run()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file chidoribt-0.4.3.tar.gz.
File metadata
- Download URL: chidoribt-0.4.3.tar.gz
- Upload date:
- Size: 109.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b99829e75925708e33790b420fe2203d89d5c40a435352ae343a0696f5326a2
|
|
| MD5 |
baad84ecf4ada50752329ea3dfdbea7b
|
|
| BLAKE2b-256 |
9ca2a526a2682fe91a9f7d00710bd301606729df42f22519f24d750606cd2ab7
|
File details
Details for the file chidoribt-0.4.3-py3-none-any.whl.
File metadata
- Download URL: chidoribt-0.4.3-py3-none-any.whl
- Upload date:
- Size: 112.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
84af9edaec274b2a47d23d4b7f80501738911f77d4b72e2a7fab24694a7e2fa8
|
|
| MD5 |
e35c23805552feccd49ba9b39fe12b8f
|
|
| BLAKE2b-256 |
849f4d79f78045c769874ceaae0a9a59bb6cb4d4ce8c6ed6ef12055a8859dfaf
|