Spaces:
Running
Running
| """ | |
| Model construction and training. | |
| build_pipeline_map() -> dict of {ticker: (feature_type, fresh_model_clone)} | |
| train_models() -> dict of {ticker: (feature_type, fitted_model)} | |
| """ | |
| import pandas as pd | |
| from sklearn.ensemble import ( | |
| RandomForestClassifier, | |
| HistGradientBoostingClassifier, | |
| VotingClassifier, | |
| ) | |
| from sklearn.linear_model import LogisticRegression | |
| from sklearn.preprocessing import StandardScaler | |
| from sklearn.pipeline import Pipeline | |
| from sklearn.feature_selection import SelectKBest, f_classif | |
| from sklearn.base import clone | |
| from core.config import TICKERS, DATA_DIR, PIPELINE_MAP | |
| from core.features import extract_semantic_features, extract_sequential_features | |
| # ββ Base model templates βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _RF = RandomForestClassifier( | |
| random_state=42, n_estimators=300, max_depth=8, | |
| min_samples_leaf=5, n_jobs=-1, | |
| ) | |
| _GBM = HistGradientBoostingClassifier( | |
| random_state=42, max_iter=300, l2_regularization=1.0, max_depth=8, | |
| ) | |
| _ENSEMBLE = VotingClassifier( | |
| estimators=[("rf", _RF), ("gbm", _GBM)], voting="soft", | |
| ) | |
| _LR_PIPELINE = Pipeline([ | |
| ("scaler", StandardScaler()), | |
| ("lr", LogisticRegression(C=0.1, max_iter=1000)), | |
| ]) | |
| _KBEST15_LR = Pipeline([ | |
| ("scaler", StandardScaler()), | |
| ("kbest", SelectKBest(f_classif, k=15)), | |
| ("lr", LogisticRegression(C=0.1, max_iter=1000)), | |
| ]) | |
| _MODEL_TEMPLATES = { | |
| "ensemble": _ENSEMBLE, | |
| "lr_pipeline": _LR_PIPELINE, | |
| "kbest15_lr": _KBEST15_LR, | |
| } | |
| # ββ Public API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def build_pipeline_map(): | |
| """ | |
| Return a dict of {ticker: (feature_type, fresh_model_clone)}. | |
| Uses PIPELINE_MAP from config to look up the architecture per ticker. | |
| """ | |
| result = {} | |
| for ticker in TICKERS: | |
| feat_type, model_key = PIPELINE_MAP[ticker] | |
| result[ticker] = (feat_type, clone(_MODEL_TEMPLATES[model_key])) | |
| return result | |
| def train_models(log_fn=None): | |
| """ | |
| Load parquet data, extract features, and train all ticker models. | |
| Parameters | |
| ---------- | |
| log_fn : callable(str), optional | |
| Logging function (e.g. logger.info). Falls back to print. | |
| Returns | |
| ------- | |
| dict { ticker: (feature_type, fitted_model) } | |
| """ | |
| if log_fn is None: | |
| log_fn = print | |
| pipeline_map = build_pipeline_map() | |
| models = {} | |
| for ticker in TICKERS: | |
| fpath = DATA_DIR / f"{ticker}_minute.parquet" | |
| if not fpath.exists(): | |
| log_fn(f"[{ticker}] Parquet file not found: {fpath}") | |
| continue | |
| df = pd.read_parquet(fpath) | |
| df["date"] = pd.to_datetime(df["date"]) | |
| df.set_index("date", inplace=True) | |
| df.sort_index(inplace=True) | |
| # Keep at most 300 trading days | |
| unique_days = df.index.normalize().unique() | |
| if len(unique_days) > 300: | |
| df = df[df.index.normalize().isin(unique_days[-300:])] | |
| feat_type, clf = pipeline_map[ticker] | |
| if feat_type == "semantic": | |
| X, y, _ = extract_semantic_features(df) | |
| else: | |
| X, y, _ = extract_sequential_features(df) | |
| if X is None or X.empty: | |
| log_fn(f"[{ticker}] Feature extraction returned empty!") | |
| continue | |
| clf.fit(X, y) | |
| models[ticker] = (feat_type, clf) | |
| log_fn(f"[{ticker}] Model trained ({feat_type}) | {len(X)} samples") | |
| return models | |