Metadata-Version: 2.4
Name: chidoribt
Version: 0.3.2
Summary: Multi-asset unified portfolio backtesting framework
License: MIT
Project-URL: Repository, https://github.com/MioQuant/chidoribt
Project-URL: Bug Tracker, https://github.com/MioQuant/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 ⚡

**超高速多資產回測框架 / High-Performance Multi-Asset Backtesting Framework**

[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/)



---

## 專案名稱由來

> **千鳥（Chidori）** — 精準、迅速，如閃電貫穿黑暗。

「千鳥」是忍術中以**極致速度**與**精準集中**著稱的雷遁之術。ChidoriBT 的設計哲學正是如此：

- **快**：批量參數掃描（240 組合）比 vectorbt serial 快 **624×**；單次 fast 淨值篩選快 **22×**
- **省**：批量掃描記憶體僅需 vectorbt 的 **1/26**（7.5 MB vs 194.7 MB，240 組合）
- **彈性**：事件驅動模式可在每個時步嵌入任意 ML 模型推論

---

## ChidoriBT vs vectorbt


| 功能                           | **ChidoriBT**  | vectorbt（free） |
| ---------------------------- | -------------- | -------------- |
| 單次 fast（1 組，僅淨值）             | **2.6 ms**     | 58 ms          |
| 單次 full（1 組，含 sharpe/trades） | **119 ms**     | 177 ms         |
| 批量掃描 fast（240 組合）            | **23.3 ms**    | 14.55 s        |
| 參數掃描（240 組合，12 核 MP）         | **23.3 ms**    | 989.7 ms       |
| 參數掃描記憶體（240 組合）              | **7.5 MB**     | 194.7 MB       |
| ML 模型動態插入                    | ✅              | ❌              |
| 整數股數（台股張數）                   | ✅              | ❌              |
| 內建多核並行掃描                     | ✅ ParamScanner | ❌              |


> 測試規模：100 支台股 × 726 交易日（2023–2025），子母線型態策略。公平性說明見 [docs/benchmark_paths.md](docs/benchmark_paths.md)。

---

## 安裝

**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 內 | 用途                                       |
| -------------------------- | ---------- | ---------------------------------------- |
| `examples/prepare_data.py` | ✅          | 準備測試資料（CSV / DB / 合成）                    |
| `examples/test_scan.py`    | ✅          | 240 組合批量掃描效能測試                           |
| `examples/test.py`         | ✅          | 單組 fast 淨值篩選效能測試                         |
| `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
```

**已有 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
```

兩支測試腳本皆透過 `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 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 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 等 |


> **Numba 並行**：若環境變數 `NUMBA_NUM_THREADS=1`（IDE 常見），`import chidoribt.benchmark` 會自動改為 CPU 核心數。

---

## 自訂策略

以下為程式碼片段範例（需自行建立 `.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")` 可批量掃描 |


```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)   # 等權再平衡
```

**回傳欄位：** `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` 為對稱費率，與 vectorbt benchmark 對齊，**尚未模擬賣出證交稅**。

---

## 套件架構

```
chidoribt/
├── benchmark.py             # Numba 設定、掃描基準工具（DB 載入為可選）
├── engine.py                # 主引擎
├── core/
│   ├── panel.py             # PanelData（T×N NumPy 面板）
│   ├── numba_kernels.py     # @njit kernel
│   └── scanner.py           # ParamScanner（批量 prange 掃描）
├── strategies/              # MultiAssetStrategy + 範例策略
├── analytics/               # 績效指標
└── data/                    # PostgreSQL / Mini-TSDB 載入
```

---

## ML 模型整合（`fast=False`）

事件驅動模式允許在每個時步動態呼叫 ML 模型推論，vectorbt 向量化架構無法支援。以下為程式碼片段範例（需自行建立 `.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()
```

---

