Scikit-learn
English
trading
finance
xauusd
gold
forex
machine-learning
ensemble
super-ensemble
xgboost
lightgbm
catboost
neural-network
tensorflow
Instructions to use JonusNattapong/romeo-v8-super-ensemble-trading-ai with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Scikit-learn
How to use JonusNattapong/romeo-v8-super-ensemble-trading-ai with Scikit-learn:
from huggingface_hub import hf_hub_download import joblib model = joblib.load( hf_hub_download("JonusNattapong/romeo-v8-super-ensemble-trading-ai", "sklearn_model.joblib") ) # only load pickle files from sources you trust # read more about it here https://skops.readthedocs.io/en/stable/persistence.html - Notebooks
- Google Colab
- Kaggle
Upload v8/backtest_v8.py with huggingface_hub
Browse files- v8/backtest_v8.py +391 -0
v8/backtest_v8.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Super Ensemble Backtester for Romeo V8
|
| 2 |
+
|
| 3 |
+
Advanced backtester for the super ensemble model with multi-algorithm collaboration,
|
| 4 |
+
stacking, dynamic weighting, and confidence calibration.
|
| 5 |
+
|
| 6 |
+
Key Features:
|
| 7 |
+
- Super Ensemble Prediction: Combines 10+ algorithms
|
| 8 |
+
- Stacking Logic: Uses meta-learner for final predictions
|
| 9 |
+
- Dynamic Weighting: Real-time weight adjustment
|
| 10 |
+
- Confidence Calibration: Calibrated probability fusion
|
| 11 |
+
- Cross-Validation Ensemble: Multiple CV fold combination
|
| 12 |
+
- Advanced Risk Management: Multi-algorithm consensus
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
import json
|
| 17 |
+
import numpy as np
|
| 18 |
+
import pandas as pd
|
| 19 |
+
import joblib
|
| 20 |
+
from tensorflow import keras
|
| 21 |
+
import sys
|
| 22 |
+
import argparse
|
| 23 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '.')))
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
from v8.train_v8 import SuperEnsembleFeatureEngineer, load_romeo_v8, SuperEnsemble
|
| 27 |
+
except Exception:
|
| 28 |
+
from train_v8 import SuperEnsembleFeatureEngineer, load_romeo_v8, SuperEnsemble
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class SumAxis1Layer(keras.layers.Layer):
|
| 32 |
+
def call(self, inputs):
|
| 33 |
+
return keras.backend.sum(inputs, axis=1)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class SuperEnsembleBacktester:
|
| 37 |
+
def __init__(self, config=None):
|
| 38 |
+
self.config = config or {
|
| 39 |
+
'ensemble_method': 'stacking', # 'stacking', 'weighted', 'voting'
|
| 40 |
+
'confidence_threshold': 0.60, # Minimum confidence for trades
|
| 41 |
+
'max_risk_per_trade': 0.12, # Maximum risk per trade
|
| 42 |
+
'use_dynamic_weighting': True, # Enable dynamic weight adjustment
|
| 43 |
+
'use_calibration': True, # Use calibrated probabilities
|
| 44 |
+
'use_cv_ensemble': True, # Use cross-validation ensemble
|
| 45 |
+
'consensus_threshold': 0.7, # Minimum algorithm agreement
|
| 46 |
+
'volatility_adjustment': True,
|
| 47 |
+
'max_drawdown_limit': 0.90, # Stop trading limit
|
| 48 |
+
}
|
| 49 |
+
self.super_ensemble = None
|
| 50 |
+
|
| 51 |
+
def load_super_ensemble(self, model_path):
|
| 52 |
+
"""Load the super ensemble model"""
|
| 53 |
+
if not os.path.exists(model_path):
|
| 54 |
+
raise FileNotFoundError(f"Model not found: {model_path}")
|
| 55 |
+
|
| 56 |
+
self.super_ensemble = load_romeo_v8(model_path)
|
| 57 |
+
print(f"Loaded super ensemble with {len(self.super_ensemble.models)} base algorithms")
|
| 58 |
+
return self.super_ensemble
|
| 59 |
+
|
| 60 |
+
def get_super_ensemble_prediction(self, X, method='stacking'):
|
| 61 |
+
"""Get prediction from super ensemble using specified method"""
|
| 62 |
+
if self.super_ensemble is None:
|
| 63 |
+
raise ValueError("Super ensemble not loaded. Call load_super_ensemble() first.")
|
| 64 |
+
|
| 65 |
+
# Use the SuperEnsemble class predict_proba method
|
| 66 |
+
proba = self.super_ensemble.predict_proba(X)
|
| 67 |
+
ensemble_proba = proba[:, 1]
|
| 68 |
+
|
| 69 |
+
# For compatibility, create base_predictions and model_names
|
| 70 |
+
# This is a simplified version - in full implementation you'd get individual model predictions
|
| 71 |
+
base_predictions = [ensemble_proba.reshape(-1, 1)] # Simplified
|
| 72 |
+
model_names = list(self.super_ensemble.models.keys())
|
| 73 |
+
|
| 74 |
+
return ensemble_proba, base_predictions, model_names
|
| 75 |
+
|
| 76 |
+
def calculate_consensus_score(self, base_predictions):
|
| 77 |
+
"""Calculate consensus score among algorithms"""
|
| 78 |
+
if not base_predictions:
|
| 79 |
+
return 0.5
|
| 80 |
+
|
| 81 |
+
# Convert to binary predictions (above/below 0.5)
|
| 82 |
+
binary_preds = []
|
| 83 |
+
for pred in base_predictions:
|
| 84 |
+
binary_pred = (pred.ravel() > 0.5).astype(int)
|
| 85 |
+
binary_preds.append(binary_pred)
|
| 86 |
+
|
| 87 |
+
# Calculate agreement percentage
|
| 88 |
+
all_binary = np.array(binary_preds)
|
| 89 |
+
consensus = np.mean(all_binary, axis=0) # Average agreement
|
| 90 |
+
|
| 91 |
+
return consensus
|
| 92 |
+
|
| 93 |
+
def should_trade_signal(self, ensemble_proba, consensus_score, volatility, volume_ratio):
|
| 94 |
+
"""Determine if signal meets super ensemble criteria"""
|
| 95 |
+
|
| 96 |
+
# Base confidence check
|
| 97 |
+
if ensemble_proba < self.config['confidence_threshold']:
|
| 98 |
+
return False, "Low confidence"
|
| 99 |
+
|
| 100 |
+
# Consensus check
|
| 101 |
+
if consensus_score < self.config['consensus_threshold']:
|
| 102 |
+
return False, "Low consensus"
|
| 103 |
+
|
| 104 |
+
# Volatility filter
|
| 105 |
+
if self.config['volatility_adjustment'] and volatility > 0.025:
|
| 106 |
+
return False, "High volatility"
|
| 107 |
+
|
| 108 |
+
# Volume confirmation
|
| 109 |
+
if volume_ratio < 1.0:
|
| 110 |
+
return False, "Low volume"
|
| 111 |
+
|
| 112 |
+
return True, "Valid signal"
|
| 113 |
+
|
| 114 |
+
def backtest_super_ensemble(self, timeframe='15m', initial_capital=100, data_file=None,
|
| 115 |
+
risk_per_trade=0.08, stop_loss=0.015, take_profit=0.04,
|
| 116 |
+
commission_pct=0.0002, slippage_pips=0.3, timeout_bars=6):
|
| 117 |
+
|
| 118 |
+
# Load data
|
| 119 |
+
if data_file:
|
| 120 |
+
data_path = data_file
|
| 121 |
+
else:
|
| 122 |
+
data_path = f'data_xauusd_v3/15m_data_v3.csv'
|
| 123 |
+
|
| 124 |
+
df = pd.read_csv(data_path, parse_dates=['Datetime'])
|
| 125 |
+
df = df.sort_values('Datetime').reset_index(drop=True)
|
| 126 |
+
|
| 127 |
+
# Load model
|
| 128 |
+
model_path = f'v8/models_romeo_v8/trading_model_romeo_{timeframe}.pkl'
|
| 129 |
+
artifact = self.load_super_ensemble(model_path)
|
| 130 |
+
|
| 131 |
+
# Process features (same as training but without fitting scaler/PCA)
|
| 132 |
+
eng = SuperEnsembleFeatureEngineer()
|
| 133 |
+
df = eng.add_technical_indicators(df)
|
| 134 |
+
df = eng.add_quantum_features(df)
|
| 135 |
+
df = df.fillna(method='bfill').fillna(method='ffill').fillna(0)
|
| 136 |
+
|
| 137 |
+
exclude = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume', 'Adj Close']
|
| 138 |
+
feature_cols = [c for c in df.columns if c not in exclude and not c.startswith('target')]
|
| 139 |
+
|
| 140 |
+
# Ensure df contains all features
|
| 141 |
+
for f in feature_cols:
|
| 142 |
+
if f not in df.columns:
|
| 143 |
+
df[f] = 0.0
|
| 144 |
+
|
| 145 |
+
X = df[feature_cols].values
|
| 146 |
+
|
| 147 |
+
# Get super ensemble predictions
|
| 148 |
+
print("Generating super ensemble predictions...")
|
| 149 |
+
ensemble_probas, base_predictions, model_names = self.get_super_ensemble_prediction(
|
| 150 |
+
X, method=self.config['ensemble_method']
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
# Calculate consensus scores
|
| 154 |
+
consensus_scores = self.calculate_consensus_score(base_predictions)
|
| 155 |
+
|
| 156 |
+
# Add to dataframe
|
| 157 |
+
df['ensemble_proba'] = ensemble_probas
|
| 158 |
+
df['consensus_score'] = consensus_scores
|
| 159 |
+
|
| 160 |
+
# Generate signals based on ensemble confidence
|
| 161 |
+
signals = (ensemble_probas > self.config['confidence_threshold']).astype(int)
|
| 162 |
+
df['signal'] = signals
|
| 163 |
+
|
| 164 |
+
# Initialize trading variables
|
| 165 |
+
capital = initial_capital
|
| 166 |
+
peak_capital = initial_capital
|
| 167 |
+
trades = []
|
| 168 |
+
total_ensemble_contributions = {name: 0 for name in model_names}
|
| 169 |
+
|
| 170 |
+
print("Starting super ensemble backtest...")
|
| 171 |
+
|
| 172 |
+
# Trading loop
|
| 173 |
+
for i in range(len(df)-1):
|
| 174 |
+
current_drawdown = (peak_capital - capital) / peak_capital if peak_capital > 0 else 0
|
| 175 |
+
|
| 176 |
+
# Check stop trading condition
|
| 177 |
+
if current_drawdown >= (1 - self.config['max_drawdown_limit']):
|
| 178 |
+
print(f"Stopping trading due to drawdown limit: {current_drawdown:.1%}")
|
| 179 |
+
break
|
| 180 |
+
|
| 181 |
+
if df.iloc[i]['signal'] == 1:
|
| 182 |
+
ensemble_proba = df.iloc[i]['ensemble_proba']
|
| 183 |
+
consensus_score = df.iloc[i]['consensus_score']
|
| 184 |
+
volatility = df.iloc[i]['Volatility'] if 'Volatility' in df.columns else 0.01
|
| 185 |
+
volume_ratio = df.iloc[i]['Volume_Ratio'] if 'Volume_Ratio' in df.columns else 1.0
|
| 186 |
+
|
| 187 |
+
# Check if signal meets criteria
|
| 188 |
+
should_trade, reason = self.should_trade_signal(
|
| 189 |
+
ensemble_proba, consensus_score, volatility, volume_ratio
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
if not should_trade:
|
| 193 |
+
continue
|
| 194 |
+
|
| 195 |
+
entry_price = df.iloc[i+1]['Open']
|
| 196 |
+
entry_price_slip = entry_price + slippage_pips * 0.0001
|
| 197 |
+
|
| 198 |
+
# Conservative position sizing for super ensemble
|
| 199 |
+
position_size = (capital * risk_per_trade) / (stop_loss * entry_price_slip)
|
| 200 |
+
|
| 201 |
+
# Adjust for volatility
|
| 202 |
+
if self.config['volatility_adjustment']:
|
| 203 |
+
vol_factor = 1 / (1 + volatility * 10)
|
| 204 |
+
position_size *= vol_factor
|
| 205 |
+
|
| 206 |
+
# Ensure within limits
|
| 207 |
+
max_size = (capital * self.config['max_risk_per_trade']) / (stop_loss * entry_price_slip)
|
| 208 |
+
position_size = min(position_size, max_size)
|
| 209 |
+
|
| 210 |
+
# Execute trade
|
| 211 |
+
exit_price = None
|
| 212 |
+
exit_idx = i+1
|
| 213 |
+
reason = 'TIMEOUT'
|
| 214 |
+
|
| 215 |
+
for j in range(i+1, min(i+1+timeout_bars, len(df))):
|
| 216 |
+
high = df.iloc[j]['High']
|
| 217 |
+
low = df.iloc[j]['Low']
|
| 218 |
+
|
| 219 |
+
if high >= entry_price_slip * (1 + take_profit):
|
| 220 |
+
exit_price = entry_price_slip * (1 + take_profit)
|
| 221 |
+
exit_idx = j
|
| 222 |
+
reason = 'TP'
|
| 223 |
+
break
|
| 224 |
+
if low <= entry_price_slip * (1 - stop_loss):
|
| 225 |
+
exit_price = entry_price_slip * (1 - stop_loss)
|
| 226 |
+
exit_idx = j
|
| 227 |
+
reason = 'SL'
|
| 228 |
+
break
|
| 229 |
+
|
| 230 |
+
if exit_price is None:
|
| 231 |
+
exit_price = df.iloc[min(i+timeout_bars, len(df)-1)]['Close']
|
| 232 |
+
exit_idx = min(i+timeout_bars, len(df)-1)
|
| 233 |
+
|
| 234 |
+
exit_price_slip = exit_price - slippage_pips * 0.0001
|
| 235 |
+
pnl = (exit_price_slip - entry_price_slip) * position_size
|
| 236 |
+
commission = commission_pct * (entry_price_slip + exit_price_slip) * position_size
|
| 237 |
+
pnl_after = pnl - commission
|
| 238 |
+
capital += pnl_after
|
| 239 |
+
|
| 240 |
+
# Update peak capital
|
| 241 |
+
peak_capital = max(peak_capital, capital)
|
| 242 |
+
|
| 243 |
+
# Track algorithm contributions
|
| 244 |
+
for name in model_names:
|
| 245 |
+
if name in df.columns and f'{name}_contrib' in df.columns:
|
| 246 |
+
total_ensemble_contributions[name] += df.iloc[i][f'{name}_contrib']
|
| 247 |
+
|
| 248 |
+
trades.append({
|
| 249 |
+
'entry_idx': i+1,
|
| 250 |
+
'exit_idx': exit_idx,
|
| 251 |
+
'entry_date': df.iloc[i+1]['Datetime'],
|
| 252 |
+
'exit_date': df.iloc[exit_idx]['Datetime'],
|
| 253 |
+
'entry_price': entry_price_slip,
|
| 254 |
+
'exit_price': exit_price_slip,
|
| 255 |
+
'position_size': position_size,
|
| 256 |
+
'pnl': pnl_after,
|
| 257 |
+
'commission': commission,
|
| 258 |
+
'reason': reason,
|
| 259 |
+
'ensemble_proba': float(ensemble_proba),
|
| 260 |
+
'consensus_score': float(consensus_score),
|
| 261 |
+
'volatility': float(volatility),
|
| 262 |
+
'volume_ratio': float(volume_ratio),
|
| 263 |
+
'capital_after': capital,
|
| 264 |
+
'drawdown_at_entry': current_drawdown
|
| 265 |
+
})
|
| 266 |
+
|
| 267 |
+
# Calculate final metrics
|
| 268 |
+
if trades:
|
| 269 |
+
winning_trades = [t for t in trades if t['pnl'] > 0]
|
| 270 |
+
win_rate = len(winning_trades) / len(trades)
|
| 271 |
+
|
| 272 |
+
if winning_trades:
|
| 273 |
+
avg_win = np.mean([t['pnl'] for t in winning_trades])
|
| 274 |
+
gross_profit = sum([t['pnl'] for t in winning_trades])
|
| 275 |
+
else:
|
| 276 |
+
avg_win = 0
|
| 277 |
+
gross_profit = 0
|
| 278 |
+
|
| 279 |
+
losing_trades = [t for t in trades if t['pnl'] <= 0]
|
| 280 |
+
if losing_trades:
|
| 281 |
+
avg_loss = np.mean([t['pnl'] for t in losing_trades])
|
| 282 |
+
gross_loss = abs(sum([t['pnl'] for t in losing_trades]))
|
| 283 |
+
else:
|
| 284 |
+
avg_loss = 0
|
| 285 |
+
gross_loss = 0
|
| 286 |
+
|
| 287 |
+
profit_factor = gross_profit / gross_loss if gross_loss > 0 else float('inf')
|
| 288 |
+
|
| 289 |
+
# Sharpe ratio approximation
|
| 290 |
+
returns = [t['pnl'] / initial_capital for t in trades]
|
| 291 |
+
if len(returns) > 1 and np.std(returns) > 0:
|
| 292 |
+
sharpe_ratio = np.mean(returns) / np.std(returns) * np.sqrt(252)
|
| 293 |
+
else:
|
| 294 |
+
sharpe_ratio = 0
|
| 295 |
+
|
| 296 |
+
else:
|
| 297 |
+
win_rate = 0
|
| 298 |
+
avg_win = 0
|
| 299 |
+
avg_loss = 0
|
| 300 |
+
profit_factor = 0
|
| 301 |
+
sharpe_ratio = 0
|
| 302 |
+
|
| 303 |
+
final_drawdown = (peak_capital - capital) / peak_capital if peak_capital > 0 else 0
|
| 304 |
+
|
| 305 |
+
summary = {
|
| 306 |
+
'initial_capital': initial_capital,
|
| 307 |
+
'final_capital': float(capital),
|
| 308 |
+
'total_return_pct': float((capital - initial_capital)/initial_capital*100),
|
| 309 |
+
'peak_capital': float(peak_capital),
|
| 310 |
+
'max_drawdown_pct': float(final_drawdown * 100),
|
| 311 |
+
'trades': len(trades),
|
| 312 |
+
'win_rate': float(win_rate),
|
| 313 |
+
'avg_win': float(avg_win),
|
| 314 |
+
'avg_loss': float(avg_loss),
|
| 315 |
+
'profit_factor': float(profit_factor),
|
| 316 |
+
'sharpe_ratio': float(sharpe_ratio),
|
| 317 |
+
'super_ensemble_metrics': {
|
| 318 |
+
'algorithms_used': len(model_names),
|
| 319 |
+
'ensemble_method': self.config['ensemble_method'],
|
| 320 |
+
'avg_consensus_score': float(np.mean([t['consensus_score'] for t in trades])) if trades else 0,
|
| 321 |
+
'avg_ensemble_proba': float(np.mean([t['ensemble_proba'] for t in trades])) if trades else 0,
|
| 322 |
+
'calibration_used': self.config['use_calibration'],
|
| 323 |
+
'cv_ensemble_used': self.config['use_cv_ensemble'],
|
| 324 |
+
'dynamic_weighting_used': self.config['use_dynamic_weighting'],
|
| 325 |
+
}
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
# Save results
|
| 329 |
+
os.makedirs('backtest_results_romeo_v8', exist_ok=True)
|
| 330 |
+
out_signals = df.reset_index()[['Datetime', 'Open', 'High', 'Low', 'Close', 'signal', 'ensemble_proba', 'consensus_score']]
|
| 331 |
+
out_signals.to_csv(f'backtest_results_romeo_v8/romeo_signals_{timeframe}.csv', index=False)
|
| 332 |
+
pd.DataFrame(trades).to_csv(f'backtest_results_romeo_v8/romeo_trades_{timeframe}.csv', index=False)
|
| 333 |
+
with open(f'backtest_results_romeo_v8/romeo_summary_{timeframe}.json', 'w') as f:
|
| 334 |
+
json.dump(summary, f, indent=2, default=str)
|
| 335 |
+
|
| 336 |
+
return summary
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def main():
|
| 340 |
+
parser = argparse.ArgumentParser(description='Super Ensemble Backtester for Romeo V8')
|
| 341 |
+
parser.add_argument('--timeframe', default='15m')
|
| 342 |
+
parser.add_argument('--data', default=None, help='Optional path to unseen CSV data')
|
| 343 |
+
parser.add_argument('--initial-capital', type=float, default=100)
|
| 344 |
+
parser.add_argument('--commission-pct', type=float, default=0.0002)
|
| 345 |
+
parser.add_argument('--slippage-pips', type=float, default=0.3)
|
| 346 |
+
parser.add_argument('--risk-per-trade', type=float, default=0.08)
|
| 347 |
+
parser.add_argument('--stop-loss', type=float, default=0.015)
|
| 348 |
+
parser.add_argument('--take-profit', type=float, default=0.04)
|
| 349 |
+
parser.add_argument('--ensemble-method', choices=['stacking', 'weighted', 'voting'], default='stacking')
|
| 350 |
+
parser.add_argument('--confidence-threshold', type=float, default=0.60)
|
| 351 |
+
|
| 352 |
+
args = parser.parse_args()
|
| 353 |
+
|
| 354 |
+
backtester = SuperEnsembleBacktester({
|
| 355 |
+
'ensemble_method': args.ensemble_method,
|
| 356 |
+
'confidence_threshold': args.confidence_threshold,
|
| 357 |
+
'max_risk_per_trade': 0.12,
|
| 358 |
+
'use_dynamic_weighting': True,
|
| 359 |
+
'use_calibration': True,
|
| 360 |
+
'use_cv_ensemble': True,
|
| 361 |
+
'consensus_threshold': 0.7,
|
| 362 |
+
'volatility_adjustment': True,
|
| 363 |
+
'max_drawdown_limit': 0.90,
|
| 364 |
+
})
|
| 365 |
+
|
| 366 |
+
summary = backtester.backtest_super_ensemble(
|
| 367 |
+
timeframe=args.timeframe,
|
| 368 |
+
initial_capital=args.initial_capital,
|
| 369 |
+
data_file=args.data,
|
| 370 |
+
risk_per_trade=args.risk_per_trade,
|
| 371 |
+
stop_loss=args.stop_loss,
|
| 372 |
+
take_profit=args.take_profit,
|
| 373 |
+
commission_pct=args.commission_pct,
|
| 374 |
+
slippage_pips=args.slippage_pips
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
print("Romeo V8 Super Ensemble Backtest Results:")
|
| 378 |
+
print("=" * 60)
|
| 379 |
+
print(f"Initial Capital: ${summary['initial_capital']}")
|
| 380 |
+
print(f"Final Capital: ${summary['final_capital']:.2f}")
|
| 381 |
+
print(f"Total Return: {summary['total_return_pct']:.2f}%")
|
| 382 |
+
print(f"Max Drawdown: {summary['max_drawdown_pct']:.2f}%")
|
| 383 |
+
print(f"Total Trades: {summary['trades']}")
|
| 384 |
+
print(f"Win Rate: {summary['win_rate']:.1%}")
|
| 385 |
+
print(f"Profit Factor: {summary['profit_factor']:.2f}")
|
| 386 |
+
print(f"Sharpe Ratio: {summary['sharpe_ratio']:.2f}")
|
| 387 |
+
print(f"Super Ensemble: {summary['super_ensemble_metrics']}")
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
if __name__ == '__main__':
|
| 391 |
+
main()
|