Metadata-Version: 2.4
Name: frostgbm
Version: 0.1.1
Summary: Finance Robust Ordered Split Trees for Gradient Boosting
Author: FROST-GBM Authors
License: MIT
Keywords: gradient boosting,finance,machine learning,quant
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Intended Audience :: Financial and Insurance Industry
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Requires-Dist: scikit-learn>=1.3
Requires-Dist: pandas>=2.0
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: lightgbm>=4; extra == "dev"

# frostgbm

**FROST-GBM** (Finance Robust Ordered Split Trees for Gradient Boosting) — 金融時系列データ向けの、scikit-learn 互換な勾配ブースティングライブラリです。

通常の GBDT は「全サンプルを平均的に説明できる分割」を選びます。金融データではこれが問題になります。ある特定の期間だけ強烈に効く特徴量が、平均で見ると最良の分割に見えてしまうためです。FROST-GBM は分割の評価軸を **期間（era）をまたいだ安定性** に置き換えます。平均ゲインから分散・CVaR・集中度（HHI）を差し引いた Q-score で分割を選ぶため、「どの期間でも一貫して効く」構造が優先されます。

`era` を渡さなければ、通常の GBDT としても動作します。

## インストール

```bash
pip install frostgbm
```

macOS では OpenMP による並列化を有効にするため、別途 libomp が必要です。

```bash
brew install libomp   # macOS のみ。未導入でも動作しますが 8 倍ほど遅くなります
```

ビルド済みホイールは Apple Silicon 向けです。Intel Mac では sdist からのビルドになるため C++ コンパイラが必要です。

## 主な機能

| 機能 | 概要 |
|---|---|
| era-robust 分割 | Q-score = 加重平均ゲイン − 分散 − CVaR − HHI |
| era カバレッジ考慮の葉収縮 | 少数の era しか含まない葉ほど強く正則化 |
| DRO 目的関数 | era 分布上の KL 制約付き分布的ロバスト最適化 |
| 因子中立化ペナルティ | Ridge 射影で特定因子へのエクスポージャを抑制 |
| ターンオーバー正則化 | 連続する era 間のスコア変化に平滑 L1 罰則 |
| Ordered CTR/VCTR | CatBoost 方式の時系列フィルトレーションに基づくカテゴリ統計 |
| ロバスト分位点 (AST) | 非対称 Student-t NLL による外れ値耐性のある予測区間 |

## 基本的な使い方

`era` は各サンプルが属する時間区間のラベル（日付・週など）です。dtype は任意で、内部で factorize されます。

```python
import numpy as np
from frostgbm import FrostGBMRegressor
from frostgbm.metrics import era_ic

era = np.repeat(np.arange(40), 50)          # 40 era × 50 サンプル
X = np.random.randn(2000, 20)
y = X[:, 0] + 0.5 * X[:, 1] + np.random.randn(2000) * 0.5

tr, te = era < 30, era >= 30                 # 時系列で分割

reg = FrostGBMRegressor(n_estimators=100, learning_rate=0.05, max_depth=6)
reg.fit(X[tr], y[tr], era=era[tr])

pred = reg.predict(X[te])
print(era_ic(y[te], pred, era[te]))
# {'mean': ..., 'std': ..., 'sharpe': ..., 'min': ..., 'worst_decile': ..., 'cvar_10': ...}
```

`predict()` が返すのは era ごとに中央値/MAD で正規化されたスコアです。絶対値そのものではなく、順位・相関ベースの評価に用いてください。

分類とランキングも同じ形です。

```python
from frostgbm import FrostGBMClassifier, FrostGBMRanker

clf = FrostGBMClassifier(n_estimators=100, max_depth=6)
clf.fit(X[tr], (y[tr] > 0).astype(int), era=era[tr])
proba = clf.predict_proba(X[te])            # (n_samples, n_classes)

rnk = FrostGBMRanker(n_estimators=100, max_depth=6)   # era 内ペアワイズ順位学習
rnk.fit(X[tr], y[tr], era=era[tr])
```

## 金融向けの追加引数

`fit()` は `era` のほかに 2 つの任意引数を取ります。

```python
reg.fit(
    X, y,
    era=era,
    sample_ids=asset_ids,            # ターンオーバー計算用の銘柄 ID
    factor_matrices=factor_by_era,   # {era ラベル: (n_e, K) 配列} 中立化したい因子
)
```

これらは対応するペナルティ係数と組で効きます。`lambda_T`（ターンオーバー）には `sample_ids` が、`lambda_N`（因子中立化）には `factor_matrices` が必要で、**データを渡さずに係数だけ設定してもエラーにならず単に無視されます**。`lambda_S` は era ごとの効用の分散に罰則を課します。既定値はいずれも 0（無効）です。

DRO は `dro_eps`（KL 半径、既定 0.1）で制御します。

## 予測区間とリスク境界

```python
from frostgbm import FrostGBMIntervalRegressor

iv = FrostGBMIntervalRegressor(quantiles=(0.05, 0.5, 0.95))
iv.fit(X[tr], y[tr], era=era[tr])

q = iv.predict_quantiles(X[te])                          # (n_samples, n_quantiles)
lower, upper = iv.predict_interval(X[te], coverage=0.9)  # 各 (n_samples,)
var95 = iv.predict_risk_bound(X[te], level=0.05, side="lower")

iv.calibrate(X_valid, y_valid, era_valid)   # ホールドアウトで分割共形補正
```

## マルチ目的学習

複数の目的関数が 1 つのフォレストを共有します。分割ゲインは Q-score を取る前に全ヘッドで合算されるため、採用される分割は全ての目的に対して有効である必要があり、目的固有のノイズが除去されます。

```python
from frostgbm import FrostGBMMultiObjectiveRegressor

mo = FrostGBMMultiObjectiveRegressor(objectives=["regression", "rank"])
mo.fit(X[tr], y[tr], era=era[tr])

mo.predict(X[te])              # primary ヘッド
mo.predict_heads(X[te])        # (n_samples, n_objectives)
```

目的関数が 1 つの場合、対応する単一タスク推定器とビット単位で同一の結果になります。

## scikit-learn との連携

標準的な推定器 API（`get_params` / `set_params` / `feature_importances_`）に準拠しているため、そのまま組み合わせられます。

```python
from sklearn.model_selection import GridSearchCV, cross_val_score

cross_val_score(FrostGBMRegressor(n_estimators=50), X, y, cv=3)
GridSearchCV(FrostGBMRegressor(), {"max_depth": [4, 6], "learning_rate": [0.05, 0.1]})
```

ただし `era` を伴う運用では、`cross_val_score` の無作為分割は era をまたいで情報を漏洩させます。実運用の評価には時系列分割を使ってください。
