"""Model training on GP-mined or qlib features.""" from __future__ import annotations from pathlib import Path import lightgbm as lgb import numpy as np import pandas as pd from sklearn.metrics import mean_squared_error def prepare_ml_matrix(df: pd.DataFrame, feature_prefix: str = "factor_", target_col: str = "target_return"): feature_cols = [c for c in df.columns if c.startswith(feature_prefix)] work = df.copy() for col in feature_cols: work[col] = work.groupby("date")[col].transform(lambda x: x.rank(pct=True)) work["target_rank"] = work.groupby("date")[target_col].transform(lambda x: x.rank(pct=True)) work = work.replace([np.inf, -np.inf], np.nan).dropna() return work, feature_cols def train_lightgbm( train_df: pd.DataFrame, valid_df: pd.DataFrame, feature_cols: list[str], target_col: str = "target_rank", model_path: Path | None = None, ) -> lgb.Booster: train_set = lgb.Dataset(train_df[feature_cols], label=train_df[target_col]) valid_set = lgb.Dataset(valid_df[feature_cols], label=valid_df[target_col], reference=train_set) params = { "objective": "regression", "metric": "mse", "learning_rate": 0.05, "num_leaves": 31, "feature_fraction": 0.8, "bagging_fraction": 0.8, "bagging_freq": 5, "verbose": -1, "seed": 42, } model = lgb.train( params, train_set, num_boost_round=500, valid_sets=[valid_set], callbacks=[lgb.early_stopping(50, verbose=False)], ) if model_path is not None: model_path.parent.mkdir(parents=True, exist_ok=True) model.save_model(str(model_path)) valid_pred = model.predict(valid_df[feature_cols]) rmse = mean_squared_error(valid_df[target_col], valid_pred, squared=False) print(f"LightGBM valid RMSE: {rmse:.6f}") return model