Source code for models.meta_model


"""
Cerebrum Forex - Meta-Labeling Model (The Guard)
SECONDARY MODEL to filter Primary Model execution.
"""
import logging
import joblib
import pandas as pd
import numpy as np
from pathlib import Path
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_score, accuracy_score

logger = logging.getLogger(__name__)

[docs] class MetaModel: """ Learns 'When is the Primary Model wrong?'. Inputs: - Primary Model Probability/Confidence - Market Regime - Volatility State - Time of Day Target: - 1 if Primary Trade was Profitable - 0 if Primary Trade was Loss """ def __init__(self, timeframe: str, model_dir: Path): self.timeframe = timeframe self.model_dir = model_dir self.model_path = model_dir / f"meta_{timeframe}.pkl" # Meta-Model: Random Forest is excellent for this tabular "filtering" logic # Robust directly out of box, interprets interactions well. self.model = RandomForestClassifier( n_estimators=100, max_depth=5, # Keep it simple to prevent overfitting meta-noise min_samples_leaf=10, class_weight='balanced', random_state=42 ) self.is_trained = False self.threshold = 0.55 # Minimum probability to allow trade
[docs] def prepare_meta_features(self, df: pd.DataFrame, primary_signals: pd.Series, primary_confs: pd.Series, regime_series: pd.Series) -> pd.DataFrame: """ Construct features for the Meta-Model. """ meta_X = pd.DataFrame(index=df.index) # 1. Primary Model Info meta_X['primary_signal'] = primary_signals # 1=BUY, -1=SELL, 0=NEUTRAL (Mapped) meta_X['primary_conf'] = primary_confs # 2. Context meta_X['regime'] = regime_series # 3. Market State (Volatility) # Assuming df has 'close' returns = df['close'].pct_change() meta_X['volatility'] = returns.rolling(10).std() # 4. Time meta_X['hour'] = df.index.hour return meta_X.fillna(0)
[docs] def train(self, X_meta: pd.DataFrame, y_meta: pd.Series) -> float: """ Train the Meta-Filter. y_meta should be: 1 (Trade Won), 0 (Trade Lost). """ if len(X_meta) < 100: logger.warning(f"[{self.timeframe}] Not enough samples for Meta-Model ({len(X_meta)}). Skipping.") return 0.0 try: # Split X_train, X_val, y_train, y_val = train_test_split(X_meta, y_meta, test_size=0.3, shuffle=False) self.model.fit(X_train, y_train) self.is_trained = True # Evaluate preds = self.model.predict(X_val) acc = accuracy_score(y_val, preds) prec = precision_score(y_val, preds, zero_division=0) logger.info(f"[{self.timeframe}] 🛡️ Meta-Model Accuracy: {acc:.2%}, Precision: {prec:.2%}") self.save() return acc except Exception as e: logger.error(f"Meta-Model training failed: {e}") return 0.0
[docs] def predict_viability(self, signal: str, conf: float, regime: int, volatility: float, hour: int) -> float: """ Returns Probability of SUCCESS (0.0 to 1.0). """ if not self.is_trained and not self.load(): return 1.0 # Default to "Allow" if no meta model (Pass-through) # Map signal sig_map = {'BUY': 1, 'SELL': -1, 'NEUTRAL': 0} s_val = sig_map.get(signal, 0) # Create single row DF features = pd.DataFrame([{ 'primary_signal': s_val, 'primary_conf': conf, 'regime': regime, 'volatility': volatility, 'hour': hour }]) try: # Predict Proba of Class 1 (Success) probs = self.model.predict_proba(features) success_prob = probs[0][1] return success_prob except Exception as e: logger.error(f"Meta prediction failed: {e}") return 1.0 # Fail open
[docs] def save(self): joblib.dump(self.model, self.model_path) logger.info(f"Meta-Model saved to {self.model_path}")
[docs] def load(self) -> bool: if not self.model_path.exists(): return False try: self.model = joblib.load(self.model_path) self.is_trained = True return True except Exception: return False