Metadata-Version: 2.4
Name: chidoribt
Version: 0.4.3
Summary: Multi-asset unified portfolio backtesting framework
License: MIT
Project-URL: Repository, https://github.com/backtestdog/chidoribt.git
Project-URL: Bug Tracker, https://github.com/backtestdog/chidoribt/issues
Keywords: backtesting,finance,trading,portfolio,quantitative
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.23.0
Requires-Dist: pandas>=1.5.0
Provides-Extra: numba
Requires-Dist: numba>=0.56; extra == "numba"
Requires-Dist: scipy>=1.10; extra == "numba"
Provides-Extra: postgres
Requires-Dist: psycopg2-binary>=2.9; extra == "postgres"
Provides-Extra: tsdb
Requires-Dist: mini-tsdb>=0.1.0; extra == "tsdb"
Provides-Extra: benchmark
Requires-Dist: numba>=0.56; extra == "benchmark"
Requires-Dist: scipy>=1.10; extra == "benchmark"
Requires-Dist: psycopg2-binary>=2.9; extra == "benchmark"
Requires-Dist: python-dotenv>=1.0; extra == "benchmark"
Provides-Extra: full
Requires-Dist: numba>=0.56; extra == "full"
Requires-Dist: scipy>=1.10; extra == "full"
Requires-Dist: mini-tsdb>=0.1.0; extra == "full"
Requires-Dist: psycopg2-binary>=2.9; extra == "full"
Requires-Dist: python-dotenv>=1.0; extra == "full"
Dynamic: license-file

# ChidoriBT ⚡

**超高速多資產回測框架 / Multi-Asset Backtesting for Self-Hosted Quant Systems**

[PyPI version](https://pypi.org/project/chidoribt/)
[Python 3.9+](https://pypi.org/project/chidoribt/)
[License: MIT](https://opensource.org/licenses/MIT)
[Numba](https://numba.pydata.org/)

<p align="center">
  <img src="https://tse2.mm.bing.net/th/id/OIP.tzQLNs50GB5WLS5qufp1gAAAAA?cb=thfc1falcon&rs=1&pid=ImgDetMain&o=7&rm=3" alt="Chidori — 千鳥" width="200"/>
</p>

---

## 專案定位

ChidoriBT 是面向**自建量化交易系統**的多資產組合回測引擎：以 `(T, N)` 面板承載 OHLCV 與 metadata，在同一資金池上跑現股、多空與期貨策略，並支援 RL 軌跡重放與大量參數掃描。

核心思路借鑑 [vectorbt](https://github.com/polakowo/vectorbt) 一貫採用的 **向量化面板 + Numba JIT** 路線——先在 NumPy 上表達訊號與目標倉位，再把熱路徑編譯成 `@njit` kernel。ChidoriBT 在此基礎上，針對**台股實務**（整數股、統一資金池、期貨保證金）與**自建系統整合**（RL slow path、批量 prange 掃描）做專項擴展，而非重新發明回測範式。

> vectorbt 在指標分析、互動式研究與 `Portfolio.from_signals` 等場景非常成熟；ChidoriBT 則專注於「已固定 panel／軌跡 → 大量掃參 → 嵌入自有 gym／ledger」這條管線。兩者可以並用，benchmark 僅供同場景參考，見 [docs/benchmark_paths.md](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](docs/benchmark_paths.md)。此結果反映**任務定義不同**（fast 不算 trades），不代表 vectorbt 在通用分析上較弱。

---

## 安裝

**Python** ≥ 3.9，< 3.13

```bash
pip install "chidoribt[benchmark]"
```

---

## 快速開始：參數掃描

典型流程：**clone repo → 準備資料 CSV → 載入面板 → JIT 暖機 → 掃描參數**。

> `pip install chidoribt` 只安裝套件本身；以下範例腳本在 repo 的 `**examples/`** 目錄，需先 `git clone` 取得。

```bash
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，建議首次使用）：

```bash
cd examples
python prepare_data.py --source synthetic --export data/panel.csv
```

合成資料會嵌入母子/吞噬型態，確保 `ChildParentStrategy` 有進場訊號。若 `final_equity` 等於初始資金（如 1,000,000），代表資料中無有效訊號，請重新執行上述指令產生 CSV。

**已有 CSV**：

```bash
python prepare_data.py --source csv --csv /path/to/your.csv --export data/panel.csv
```

**從 PostgreSQL 一次性匯出**（可選；需自行設定 `DB_HOST` 等環境變數或 `.env`）：

```bash
python prepare_data.py --source db --export data/panel.csv
```

### 2. 執行測試

`**examples/test_scan.py**` — 240 組合批量掃描：

```bash
python test_scan.py
```

`**examples/test.py**` — 單組 fast 篩選：

```bash
python test.py
```

**`examples/test.ipynb`** — Notebook 版（含 Google Colab）：

[在 Colab 開啟](https://colab.research.google.com/github/backtestdog/chidoribt/blob/main/examples/test.ipynb)（從上而下執行各儲存格；首格會 `pip install "chidoribt[benchmark]"`）

兩支測試腳本皆透過 `prepare_panel(source="auto")` 載入：有 `data/panel.csv` 就用 CSV，否則嘗試 DB。也可用環境變數覆寫：

```bash
set CHIDORI_DATA_SOURCE=csv
set CHIDORI_DATA_CSV=data/panel.csv
python test_scan.py
```

### 3. 程式碼對照 — `examples/test_scan.py`

```python
# 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`

```python
# 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 中使用）。

```python
# 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` 執行完整回測：

```python
# 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 見下方文件 |


```python
# 執行模式範例（程式碼片段）
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：

```python
# 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](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` 檔）。

```python
# 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()
```

---

