quant-ai / src /models /tree_models.py
Ypeng12's picture
feat: 10/10 HRT style point-in-time alpha research platform, purged walk-forward CV, risk parity & test suite
b54319d
Raw
History Blame Contribute Delete
5.45 kB
import pandas as pd
import numpy as np
from typing import List, Optional, Dict, Any
class LightGBMModel:
"""
Tree Model 1: LightGBM Regressor / Ranker with constrained depth to prevent overfitting.
Falls back gracefully to sklearn HistGradientBoostingRegressor if lightgbm is not installed.
"""
def __init__(
self,
max_depth: int = 3,
num_leaves: int = 7,
learning_rate: float = 0.01,
n_estimators: int = 500,
mode: str = "regressor",
):
self.max_depth = max_depth
self.num_leaves = num_leaves
self.learning_rate = learning_rate
self.n_estimators = n_estimators
self.mode = mode
self.model = None
def fit(self, df: pd.DataFrame, feature_cols: List[str], target_col: str):
clean_df = df[feature_cols + [target_col, "date"]].dropna()
if len(clean_df) < 50:
return
X = clean_df[feature_cols]
y = clean_df[target_col]
try:
import lightgbm as lgb
if self.mode == "ranker":
clean_df = clean_df.sort_values("date")
group_sizes = clean_df.groupby("date").size().values
y_quant = clean_df.groupby("date")[target_col].transform(
lambda s: pd.qcut(s.rank(method="first"), 5, labels=False)
)
self.model = lgb.LGBMRanker(
max_depth=self.max_depth,
num_leaves=self.num_leaves,
learning_rate=self.learning_rate,
n_estimators=self.n_estimators,
subsample=0.7,
colsample_bytree=0.7,
random_state=42,
verbosity=-1,
)
self.model.fit(clean_df[feature_cols], y_quant, group=group_sizes)
else:
self.model = lgb.LGBMRegressor(
max_depth=self.max_depth,
num_leaves=self.num_leaves,
learning_rate=self.learning_rate,
n_estimators=self.n_estimators,
subsample=0.7,
colsample_bytree=0.7,
random_state=42,
verbosity=-1,
)
self.model.fit(X, y)
except ImportError:
# Fallback to sklearn HistGradientBoostingRegressor
from sklearn.ensemble import HistGradientBoostingRegressor
self.model = HistGradientBoostingRegressor(
max_depth=self.max_depth,
learning_rate=self.learning_rate,
max_iter=min(200, self.n_estimators),
random_state=42,
)
self.model.fit(X, y)
def predict(self, df: pd.DataFrame, feature_cols: List[str]) -> pd.Series:
if self.model is None:
return pd.Series(0.0, index=df.index)
X = df[feature_cols].fillna(0.0)
preds = self.model.predict(X)
return pd.Series(preds, index=df.index)
def feature_importances(self, feature_cols: List[str]) -> Dict[str, float]:
if self.model is None:
return {}
if hasattr(self.model, "feature_importances_"):
imp = self.model.feature_importances_
total = float(imp.sum()) + 1e-8
return {f: float(i / total) for f, i in zip(feature_cols, imp)}
return {f: 1.0 / len(feature_cols) for f in feature_cols}
class XGBoostModel:
"""
Tree Model 2: XGBoost Regressor with L2 regularization.
Falls back gracefully to sklearn GradientBoostingRegressor if xgboost is not installed.
"""
def __init__(
self,
max_depth: int = 3,
learning_rate: float = 0.01,
n_estimators: int = 500,
reg_lambda: float = 1.0,
):
self.max_depth = max_depth
self.learning_rate = learning_rate
self.n_estimators = n_estimators
self.reg_lambda = reg_lambda
self.model = None
def fit(self, df: pd.DataFrame, feature_cols: List[str], target_col: str):
clean_df = df[feature_cols + [target_col]].dropna()
if len(clean_df) < 50:
return
X = clean_df[feature_cols]
y = clean_df[target_col]
try:
import xgboost as xgb
self.model = xgb.XGBRegressor(
max_depth=self.max_depth,
learning_rate=self.learning_rate,
n_estimators=self.n_estimators,
reg_lambda=self.reg_lambda,
subsample=0.7,
colsample_bytree=0.7,
random_state=42,
verbosity=0,
)
self.model.fit(X, y)
except ImportError:
from sklearn.ensemble import GradientBoostingRegressor
self.model = GradientBoostingRegressor(
max_depth=self.max_depth,
learning_rate=self.learning_rate,
n_estimators=min(200, self.n_estimators),
subsample=0.7,
random_state=42,
)
self.model.fit(X, y)
def predict(self, df: pd.DataFrame, feature_cols: List[str]) -> pd.Series:
if self.model is None:
return pd.Series(0.0, index=df.index)
X = df[feature_cols].fillna(0.0)
preds = self.model.predict(X)
return pd.Series(preds, index=df.index)