Metadata-Version: 2.4
Name: chidoribt
Version: 0.2.8
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"
Provides-Extra: tsdb
Requires-Dist: mini-tsdb>=0.1.0; extra == "tsdb"
Provides-Extra: postgres
Requires-Dist: psycopg2-binary>=2.9; extra == "postgres"
Provides-Extra: full
Requires-Dist: numba>=0.56; extra == "full"
Requires-Dist: mini-tsdb>=0.1.0; extra == "full"
Requires-Dist: psycopg2-binary>=2.9; extra == "full"
Dynamic: license-file

# ChidoriBT ⚡

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

[![PyPI version](https://badge.fury.io/py/chidoribt.svg)](https://pypi.org/project/chidoribt/)
[![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://pypi.org/project/chidoribt/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Numba](https://img.shields.io/badge/accelerated%20by-Numba-orange.svg)](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>

---

## 專案名稱由來

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

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

- **快**：單次回測與 vectorbt 速度相當；批量參數掃描比 vectorbt serial 快 **624×**、比 vectorbt multiprocessing（12 核）快 **42×**，比 multibacktesting Broker 快 **300 ～ 700 倍**
- **省**：參數掃描記憶體僅需 vectorbt serial 的 **1/26**（7.5 MB vs 194.7 MB，240 組合）
- **彈性**：事件驅動模式可在每個時步嵌入任意 ML 模型推論，vectorbt 向量化架構無法支援此設計

---

## ChidoriBT vs vectorbt

| 功能 | **ChidoriBT** | vectorbt（free） |
|------|:-------------:|:----------------:|
| 參數掃描速度（240 組合，單核） | **23.3 ms** | 14.55 s |
| 參數掃描速度（240 組合，12 核） | **23.3 ms** | 989.7 ms |
| 參數掃描記憶體 | **7.5 MB** | 194.7 MB（serial）|
| ML 模型動態插入 | ✅ 事件驅動模式 | ❌ |
| 整數股數（台股張數） | ✅ | ❌ |
| 完整訂單簿（fills/orders） | ✅ | ❌ |
| 內建多核並行掃描 | ✅ ParamScanner | ❌（需自行 multiprocessing）|
| 內建技術指標數量 | 基本 | 60+ |

---

## 效能基準（Benchmark）

> 測試規模：**100 支台股 × 756 個交易日**  
> 策略：子母線型態（ChildParentStrategy）+ 止損 / 止盈  
> 計時範圍：含訊號計算，不含 DB 載入與 PanelData 建構  
> *批量參數掃描效能請見下方「參數掃描 vs vectorbt」章節*

### 單次回測速度

| 路徑 | 說明 | 均值 | vs Broker | vs vectorbt |
|------|------|:----:|:---------:|:-----------:|
| **A** PatternBacktester | 純 Python 事件迴圈 | 63 ms | **~460×** | 0.7× |
| **B** Numba 固定倉位 | JIT machine code | 41 ms | **~710×** | **1.1×** |
| **D** NumPy + Numba rebalance | 向量化等權再平衡 | 53 ms | **~550×** | 0.9× |
| **G** ChidoriBT JIT | `fast=True`，端到端 | 90 ms | **~320×** | 0.5× |
| **H** ChidoriBT 事件驅動 | `fast=False`，含完整 order log | 239 ms | **~120×** | 0.2× |
| **I** vectorbt `from_signals` | `cash_sharing=True` | 46 ms | ~630× | 1× |
| **E** Broker（基準）¹ | multibacktesting 事件迴圈 | ~29 s | 1× | — |

> ¹ 基準引擎（Path E）為作者基於 [`backtesting.py`](https://github.com/kernc/backtesting.py) 改寫的多資產版本 **multibacktesting**，其逐 bar 事件迴圈在大規模資產時效能受限，是 ChidoriBT 的優化起點。

**單次回測小結：** ChidoriBT JIT kernel（B/D）與 vectorbt 速度相當（±10%）。ChidoriBT 的主要優勢在於**批量參數掃描**（見下方）、**記憶體效率**，以及 `fast=False` 模式可嵌入 ML 模型推論。

### 參數掃描 vs vectorbt（240 組合，100 股 × 726 日）

> 執行：`python run_speed_benchmark.py --scan --stocks 100 --scan-hold-days "3,5,7,10,15" --scan-stop-loss "0.03,0.05,0.08,0.10" --scan-take-profit "0.08,0.12,0.15,0.20" --scan-trade-size "20000,30000,50000"`

| 引擎 | 均值（240 組合） | 吞吐量 | 峰值記憶體 | vs ChidoriBT |
|------|:-----------:|:------:|:----------:|:------------:|
| **ChidoriBT** ParamScanner | **23.3 ms** | **10,284 組合/s** | **7.5 MB** | — |
| vectorbt serial（單核） | 14.55 s | 16 組合/s | 194.7 MB | 624× 慢 |
| vectorbt multiprocessing（12 核）¹ | 989.7 ms | 242 組合/s | — ² | 42× 慢 |

> ¹ Pool 預熱（含模組載入）不計入計時，與 ChidoriBT 排除 JIT 暖機的做法一致。
> ² 各 worker 記憶體未納入追蹤；主進程僅傳輸資料（1.4 MB）。

**為何 vectorbt multiprocessing 仍慢 42×？**
ChidoriBT 批量掃描在單進程內以共享記憶體並行計算，所有組合共用同一份資料，無跨進程傳輸開銷。vectorbt 每個組合需透過 IPC 傳送資料、在 worker 內重建物件再回傳結果，這些固定開銷無法透過增加 worker 數消除。

---

ChidoriBT is a high-performance Python backtesting library for multi-asset portfolio strategies. It supports vectorized (NumPy/Numba) execution, flexible data ingestion (DataFrame, PostgreSQL, Mini-TSDB), and produces detailed trade/equity analytics.

---

## Requirements

- **Python** ≥ 3.9, < 3.13
- **numpy** ≥ 1.23、**pandas** ≥ 1.5（core，無額外依賴）
- **numba** ≥ 0.56（選用，建議安裝以啟用 JIT 加速）

---

## Installation

**Core（DataFrame 傳入，無額外依賴）：**

```bash
pip install chidoribt
```

**含 Numba JIT 加速（建議）：**

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

**含 PostgreSQL 資料載入：**

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

**含 Mini-TSDB 時序資料庫：**

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

**全功能安裝：**

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

---

## 快速開始

### 方式一：直接傳入 DataFrame（OHLCV 格式，與原版 backtesting.py 欄位相容）

```python
import pandas as pd
from chidoribt import ChidoriBT

# 單資產 OHLCV DataFrame（欄位：Open, High, Low, Close, Volume）
ohlcv_df = pd.read_csv("AAPL.csv", index_col="date", parse_dates=True)

bt = ChidoriBT(data=ohlcv_df, cash=100_000, commission=0.001)
result = bt.run()

print(result["metrics"])
# {'total_return': 0.42, 'sharpe': 1.35, 'max_drawdown': -0.18, ...}
```

### 方式二：多資產 stacked DataFrame

```python
from chidoribt import ChidoriBT
from chidoribt.core import PanelData

# stacked DataFrame 欄位：stock_id, date, Open, High, Low, Close, Volume
panel = PanelData.from_dataframe(stacked_df)

bt = ChidoriBT(data=panel, strategy=MyStrategy, cash=100_000)
result = bt.run()
equity_curve = result["equity_curve"]   # pd.Series，index 為日期
trades_df    = result["trades"]         # pd.DataFrame，每筆交易記錄
```

### 方式三：Mini-TSDB 載入（需 `pip install "chidoribt[tsdb]"`）

```python
from chidoribt.data import StockTSDBLoader
from chidoribt import ChidoriBT
from chidoribt.core import PanelData

loader = StockTSDBLoader("./data/tsdb")
raw_df = loader.load(start_date="2023-01-01", end_date="2025-12-31")

bt = ChidoriBT(
    data=PanelData.from_dataframe(raw_df),
    strategy=MyStrategy,
    cash=100_000,
)
result = bt.run()
```

### 方式四：PostgreSQL 載入（需 `pip install "chidoribt[postgres]"`）

```python
from chidoribt.data import StockPostgresLoader
from chidoribt import ChidoriBT
from chidoribt.core import PanelData

loader = StockPostgresLoader()  # 讀取環境變數 DB_HOST / DB_PORT / DB_NAME
raw_df = loader.load(start_date="2023-01-01", end_date="2025-12-31")

bt = ChidoriBT(
    data=PanelData.from_dataframe(raw_df),
    strategy=MyStrategy,
    cash=100_000,
)
result = bt.run()
```

---

## 自訂策略

```python
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} 代表當日進場訊號；
        未包含在回傳 dict 中的股票視為無訊號。
        """
        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
```

---

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

`fast=False` 事件驅動模式允許在每個時步動態呼叫任意 Python 物件，包括 ML 模型推論。vectorbt 的向量化架構無法支援此設計。

```python
import pandas as pd
import numpy as np
from chidoribt import ChidoriBT
from chidoribt.strategies.base import MultiAssetStrategy
from chidoribt.core import PanelData

class MLStrategy(MultiAssetStrategy):
    """在每個時步動態呼叫 ML 模型推論（vectorbt 無法支援此模式）"""

    def __init__(self, model, params=None):
        super().__init__(params or {})
        self.model = model   # 任何 sklearn / xgboost / lightgbm 模型

    def init(self):
        self.window = 20     # 特徵窗口

    def next(self, timestamp: pd.Timestamp, panel: PanelData, t: int) -> dict[str, bool]:
        if t < self.window:
            return {}

        # 即時特徵計算（僅使用 t 之前的資料，無 look-ahead bias）
        window_close = panel.close[t - self.window:t, :]  # shape: (window, N)
        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,  # 近 5 日動能
        ])  # shape: (N, 3)

        probs = self.model.predict_proba(X)[:, 1]   # 上漲機率

        return {
            panel.stock_ids[i]: True
            for i, p in enumerate(probs) if p > 0.6
        }


# 使用方式
from sklearn.ensemble import GradientBoostingClassifier

model = GradientBoostingClassifier().fit(X_train, y_train)

bt = ChidoriBT(
    data=panel,
    strategy=MLStrategy(model=model),
    cash=1_000_000,
    commission=0.001425,
    fast=False,   # ← 必須使用事件驅動模式以支援動態推論
)
result = bt.run()
print(result["metrics"])
```

---

## 參數掃描（ParamScanner）

`ParamScanner` 使用 `@njit(parallel=True)` + `prange` 批量執行，所有參數組合共享同一份 price matrix，無需重建 DataFrame。

```python
from chidoribt import PanelData, ParamScanner

panel = PanelData.from_dataframe(stacked_df)

scanner = ParamScanner(panel, mode="fixed")   # "fixed" | "rebalance"
results = scanner.scan(
    hold_days_list   = [3, 5, 7, 10, 15],
    stop_loss_list   = [0.03, 0.05, 0.08, 0.10],
    take_profit_list = [0.08, 0.12, 0.15, 0.20],
    trade_size_list  = [20_000, 30_000, 50_000],
    initial_cash     = 1_000_000,
    max_positions    = 20,
)

# results: list[dict] — 每組合的 final_equity / sharpe / max_drawdown
best = max(results, key=lambda r: r["final_equity"])
print(best)
```

---

## 執行模式

| 模式 | `mode` 參數 | `fast` 參數 | 說明 |
|------|-------------|-------------|------|
| JIT 加速（預設） | `"portfolio"` | `True` | Numba JIT，最快；無中間物件 |
| 事件驅動 | `"portfolio"` | `False` | 純 Python 迴圈，含完整 fills/orders log；**支援 ML 插入** |
| 等權再平衡 | `"rebalance"` | — | 每日依等權分配再平衡 |

```python
# Numba JIT 加速（預設 fast=True）
result = bt.run(mode="portfolio", fast=True)

# 純 Python 路徑（含完整 audit log，適合嵌入 ML 模型）
result = bt.run(mode="portfolio", fast=False)
fills_df  = result["fills"]   # 每筆成交記錄
orders_df = result["orders"]  # 每筆下單記錄
```

---

## 回傳結果欄位

```python
result = bt.run()

result["equity_curve"]  # pd.Series — 每日資金曲線
result["trades"]        # pd.DataFrame — 每筆已平倉交易
result["fills"]         # pd.DataFrame — 每筆成交（fast=False 時）
result["metrics"]       # dict — 績效指標
```

**`metrics` 常用欄位：**

| 欄位 | 說明 |
|------|------|
| `total_return` | 總報酬率 |
| `sharpe` | 年化 Sharpe ratio |
| `max_drawdown` | 最大回撤 |
| `win_rate` | 勝率 |
| `n_trades` | 總交易筆數 |

---

## Mini-TSDB 資料遷移

若已有 PostgreSQL 歷史資料，可使用遷移腳本一次性搬移：

```bash
# 初次遷移
python migrate_pg_to_tsdb.py --tsdb-dir ./data/tsdb

# 清除舊資料後重新遷移
python migrate_pg_to_tsdb.py --tsdb-dir ./data/tsdb --clean

# 查看 TSDB 內容
python migrate_pg_to_tsdb.py --tsdb-dir ./data/tsdb --list
```

---

## 套件架構

```
chidoribt/
├── __init__.py              # ChidoriBT, PanelData, ParamScanner（公開 API）
├── engine.py                # 主引擎（fast=True / fast=False 路由）
├── core/
│   ├── panel.py             # PanelData（T×N NumPy 面板，C-contiguous）
│   ├── numba_kernels.py     # 所有 @njit kernel（核心加速層）
│   ├── scanner.py           # ParamScanner（批量 prange 掃描）
│   ├── portfolio.py         # UnifiedPortfolio（事件驅動回測）
│   └── trade_reconstructor.py
├── strategies/              # MultiAssetStrategy base + 範例策略
├── analytics/               # 績效指標計算
├── data/                    # 資料載入子套件
│   ├── _postgres.py         # StockPostgresLoader（psycopg2，optional）
│   └── _tsdb.py             # StockTSDBLoader（mini-tsdb，optional）
├── adapters/                # 內部 adapter 層
└── utils/                   # trade_logger 等工具
```

---
