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