mirror of
https://github.com/Arianhgh/fx-quant-research.git
synced 2026-08-01 04:57:44 +00:00
563 lines
23 KiB
Python
563 lines
23 KiB
Python
"""Stacked tree-ensemble predictor for the 5-minute timeframe.
|
|
|
|
A binary (up/down) classifier that stacks five gradient-boosted / bagged tree
|
|
models with two meta-models and an HMM market-regime filter.
|
|
"""
|
|
import numpy as np
|
|
import pandas as pd
|
|
from sklearn.preprocessing import RobustScaler
|
|
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier
|
|
from sklearn.linear_model import LogisticRegression
|
|
from sklearn.metrics import roc_auc_score
|
|
from sklearn.model_selection import TimeSeriesSplit
|
|
import xgboost as xgb
|
|
import lightgbm as lgb
|
|
from catboost import CatBoostClassifier
|
|
from hmmlearn import hmm
|
|
import talib
|
|
|
|
|
|
class TreeEnsemblePredictor:
|
|
def __init__(self, forecast_bars=24, confidence_threshold=0.65):
|
|
"""
|
|
Optimized gold price prediction model for 5-minute timeframe
|
|
|
|
Parameters:
|
|
forecast_bars (int): Number of future 5-min bars to predict (default=24, which is 2 hours)
|
|
confidence_threshold (float): Minimum probability threshold for signal generation
|
|
"""
|
|
self.forecast_bars = forecast_bars
|
|
self.confidence_threshold = confidence_threshold
|
|
self.scaler = RobustScaler()
|
|
self.regime_model = hmm.GaussianHMM(n_components=3, random_state=42)
|
|
|
|
# Tree-based models with parameters optimized for 5-min timeframe
|
|
self.models = {
|
|
'xgboost': xgb.XGBClassifier(
|
|
colsample_bytree=0.85,
|
|
learning_rate=0.05,
|
|
max_depth=6, # Reduced depth for faster market dynamics
|
|
min_child_weight=3,
|
|
subsample=0.8,
|
|
n_estimators=100,
|
|
random_state=42,
|
|
eval_metric='logloss',
|
|
use_label_encoder=False
|
|
),
|
|
'lightgbm': lgb.LGBMClassifier(
|
|
feature_fraction=0.9,
|
|
learning_rate=0.02,
|
|
max_depth=5,
|
|
min_child_samples=20, # Smaller sample for 5-min data
|
|
subsample=0.8,
|
|
n_estimators=100,
|
|
random_state=44,
|
|
boosting_type='dart' # More robust to noise in high-frequency data
|
|
),
|
|
'catboost': CatBoostClassifier(
|
|
depth=4,
|
|
learning_rate=0.07,
|
|
subsample=0.85,
|
|
n_estimators=100,
|
|
random_state=45,
|
|
verbose=0
|
|
),
|
|
'randomforest': RandomForestClassifier(
|
|
n_estimators=100,
|
|
max_depth=5,
|
|
max_features='sqrt',
|
|
min_samples_leaf=5, # Captures more granular patterns
|
|
random_state=46
|
|
),
|
|
'extratrees': ExtraTreesClassifier(
|
|
n_estimators=100,
|
|
max_depth=5,
|
|
max_features='sqrt',
|
|
min_samples_leaf=5,
|
|
random_state=47
|
|
)
|
|
}
|
|
|
|
# Primary meta-model
|
|
self.meta_model = lgb.LGBMClassifier(
|
|
n_estimators=100,
|
|
max_depth=3,
|
|
learning_rate=0.03,
|
|
random_state=48,
|
|
boosting_type='gbdt'
|
|
)
|
|
|
|
# Secondary meta-model for consensus validation
|
|
self.meta_model_backup = LogisticRegression(
|
|
C=0.1,
|
|
solver='liblinear',
|
|
random_state=49
|
|
)
|
|
|
|
# Track feature importance
|
|
self.feature_importances = {}
|
|
self.feature_names = []
|
|
|
|
def detect_market_regime(self, data):
|
|
"""
|
|
Detects market regimes using HMM on returns and volatility
|
|
Optimized for 5-minute data with shorter lookback windows
|
|
|
|
Returns regime classifications (0=low vol, 1=normal, 2=high vol)
|
|
"""
|
|
# Calculate returns and volatility
|
|
returns = np.log(data['close'] / data['close'].shift(1))
|
|
# Shorter window for 5-minute data (60 periods = 5 hours)
|
|
volatility = returns.rolling(window=60).std()
|
|
combined = pd.DataFrame({'returns': returns, 'volatility': volatility}).dropna()
|
|
|
|
# Fit HMM model if we have enough data
|
|
if len(combined) > 100:
|
|
self.regime_model.fit(combined.values)
|
|
regimes = self.regime_model.predict(combined.values)
|
|
regime_series = pd.Series(index=data.index, dtype='float64')
|
|
regime_series.iloc[len(data)-len(regimes):] = regimes
|
|
return regime_series
|
|
else:
|
|
# Default to moderate regime if not enough data
|
|
return pd.Series(1, index=data.index)
|
|
|
|
def create_advanced_features(self, df):
|
|
"""
|
|
Create features optimized for 5-minute gold price prediction
|
|
|
|
Features are organized in categories:
|
|
1. Market regime
|
|
2. Time-based features
|
|
3. Price action features
|
|
4. Volatility indicators
|
|
5. Momentum indicators
|
|
6. Volume indicators
|
|
7. Support/Resistance
|
|
8. Pattern recognition
|
|
"""
|
|
data = df.copy()
|
|
|
|
# 1. Market regime detection
|
|
data['market_regime'] = self.detect_market_regime(data)
|
|
|
|
# 2. Time-based features for intraday seasonality
|
|
data['hour'] = data.index.hour
|
|
data['minute'] = data.index.minute
|
|
data['day_of_week'] = data.index.dayofweek
|
|
# Cyclical encoding of time (circular features)
|
|
data['hour_sin'] = np.sin(2 * np.pi * data['hour']/24)
|
|
data['hour_cos'] = np.cos(2 * np.pi * data['hour']/24)
|
|
|
|
# 3. Short-term price action features
|
|
# Moving averages adapted for 5-min timeframe
|
|
for period in [12, 24, 48, 96, 144]: # 1h, 2h, 4h, 8h, 12h in 5-minute bars
|
|
# Exponential moving averages
|
|
data[f'ema_{period}'] = talib.EMA(data['close'], timeperiod=period)
|
|
|
|
# Price relative to moving average (normalized distance)
|
|
data[f'price_to_ema_{period}'] = data['close'] / data[f'ema_{period}'] - 1
|
|
|
|
# Trend strength
|
|
data[f'trend_{period}'] = (data[f'ema_{period}'] - data[f'ema_{period}'].shift(period//4)) / data[f'ema_{period}'].shift(period//4)
|
|
|
|
# 4. Volatility indicators
|
|
# ATR with periods suitable for 5-minute bars
|
|
for period in [12, 24, 48, 96]: # 1h, 2h, a
|
|
data[f'atr_{period}'] = talib.ATR(data['high'], data['low'], data['close'], timeperiod=period)
|
|
data[f'atr_ratio_{period}'] = data[f'atr_{period}'] / data['close']
|
|
|
|
# Bollinger Bands - essential for mean-reversion detection
|
|
for period in [24, 48, 96]:
|
|
upper, middle, lower = talib.BBANDS(data['close'], timeperiod=period, nbdevup=2, nbdevdn=2)
|
|
data[f'bb_width_{period}'] = (upper - lower) / middle
|
|
data[f'bb_position_{period}'] = (data['close'] - lower) / (upper - lower)
|
|
|
|
# 5. Momentum indicators
|
|
# RSI with different lookback periods for 5-min data
|
|
for period in [12, 24, 48, 96]:
|
|
data[f'rsi_{period}'] = talib.RSI(data['close'], timeperiod=period)
|
|
|
|
# MACD for 5-minute data (faster parameters)
|
|
macd, macd_signal, macd_hist = talib.MACD(
|
|
data['close'],
|
|
fastperiod=6, # Faster for 5-min data
|
|
slowperiod=19, # Faster for 5-min data
|
|
signalperiod=5 # Faster for 5-min data
|
|
)
|
|
data['macd'] = macd
|
|
data['macd_signal'] = macd_signal
|
|
data['macd_hist'] = macd_hist
|
|
|
|
# 6. Volume indicators (crucial for 5-minute signals)
|
|
# Volume relative to moving average
|
|
for period in [12, 24, 48]:
|
|
data[f'volume_ma_{period}'] = talib.SMA(data['volume'], timeperiod=period)
|
|
data[f'volume_ratio_{period}'] = data['volume'] / data[f'volume_ma_{period}']
|
|
|
|
# On-balance volume - good for measuring buying/selling pressure
|
|
data['obv'] = talib.OBV(data['close'], data['volume'])
|
|
data['obv_ma'] = talib.SMA(data['obv'], timeperiod=24)
|
|
data['obv_ratio'] = data['obv'] / data['obv_ma']
|
|
|
|
# 7. Support/Resistance levels
|
|
# Pivot points for 5-min (using 96 periods = 8 hours)
|
|
data['pivot'] = (data['high'].rolling(96).max() + data['low'].rolling(96).min() + data['close'].rolling(96).mean()) / 3
|
|
data['dist_to_pivot'] = (data['close'] - data['pivot']) / data['close']
|
|
|
|
# 8. Candlestick pattern features
|
|
# Candle size metrics
|
|
data['candle_range'] = (data['high'] - data['low']) / data['close']
|
|
data['candle_body'] = abs(data['open'] - data['close']) / data['close']
|
|
|
|
# Rate of change - important for 5-min momentum
|
|
for period in [6, 12, 24]:
|
|
data[f'roc_{period}'] = talib.ROC(data['close'], timeperiod=period)
|
|
|
|
# Target definition for 5-minute timeframe
|
|
# Using appropriate thresholds for smaller price moves
|
|
future_return = data['close'].shift(-self.forecast_bars) / data['close'] - 1
|
|
# Lower threshold for 5-minute bars (approximately 0.1-0.15% move)
|
|
data['target'] = np.where(future_return > 0.0012, 1, np.where(future_return < -0.0012, 0, None))
|
|
|
|
# Drop rows with missing data
|
|
return data.dropna()
|
|
|
|
def prepare_features(self, data):
|
|
"""
|
|
Prepare and select optimal features for the model
|
|
Features are grouped by category for easier selection
|
|
"""
|
|
# Most important features for 5-minute gold prediction
|
|
feature_columns = [
|
|
# Market regime
|
|
'market_regime',
|
|
|
|
# Time features for intraday patterns
|
|
'hour_sin', 'hour_cos', 'day_of_week',
|
|
|
|
# Price action features
|
|
'price_to_ema_12', 'price_to_ema_24', 'price_to_ema_48',
|
|
'trend_24', 'trend_48', 'trend_96',
|
|
|
|
# Volatility indicators
|
|
'atr_ratio_12', 'atr_ratio_24',
|
|
'bb_width_24', 'bb_width_48',
|
|
'bb_position_24', 'bb_position_48',
|
|
|
|
# Momentum indicators
|
|
'rsi_12', 'rsi_24', 'rsi_48',
|
|
'macd', 'macd_hist',
|
|
|
|
# Volume indicators
|
|
'volume_ratio_12', 'volume_ratio_24',
|
|
'obv_ratio',
|
|
|
|
# Support/Resistance
|
|
'dist_to_pivot',
|
|
|
|
# Pattern recognition
|
|
'candle_range', 'candle_body',
|
|
'roc_6', 'roc_12'
|
|
]
|
|
|
|
X = data[feature_columns]
|
|
|
|
if 'target' in data.columns:
|
|
y = data['target'].astype(int)
|
|
return X, y
|
|
else:
|
|
return X, None
|
|
|
|
def fit(self, train_data):
|
|
"""Train the ensemble model on historical data"""
|
|
print("Creating features...")
|
|
processed_data = self.create_advanced_features(train_data)
|
|
X, y = self.prepare_features(processed_data)
|
|
|
|
# Store feature names for importance tracking
|
|
self.feature_names = X.columns.tolist()
|
|
|
|
# Scale features
|
|
X_scaled = self.scaler.fit_transform(X)
|
|
X_scaled = pd.DataFrame(X_scaled, columns=X.columns)
|
|
|
|
# Time series cross-validation
|
|
print("Performing time series cross-validation...")
|
|
tscv = TimeSeriesSplit(n_splits=5)
|
|
oof_preds = np.zeros((len(X_scaled), len(self.models)))
|
|
|
|
for fold, (train_idx, val_idx) in enumerate(tscv.split(X_scaled)):
|
|
X_train, X_val = X_scaled.iloc[train_idx], X_scaled.iloc[val_idx]
|
|
y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
|
|
|
|
for i, (name, model) in enumerate(self.models.items()):
|
|
print(f"Training {name} for fold {fold+1}/5...")
|
|
|
|
if name == 'xgboost':
|
|
model.fit(X_train, y_train, eval_set=[(X_val, y_val)], verbose=False)
|
|
elif name == 'lightgbm':
|
|
model.fit(X_train, y_train, eval_set=[(X_val, y_val)])
|
|
elif name == 'catboost':
|
|
model.fit(X_train, y_train, eval_set=(X_val, y_val))
|
|
else:
|
|
model.fit(X_train, y_train)
|
|
|
|
# Store out-of-fold predictions
|
|
oof_preds[val_idx, i] = model.predict_proba(X_val)[:, 1]
|
|
|
|
# Track feature importance
|
|
if hasattr(model, 'feature_importances_'):
|
|
if name not in self.feature_importances:
|
|
self.feature_importances[name] = np.zeros(len(self.feature_names))
|
|
self.feature_importances[name] += model.feature_importances_
|
|
|
|
# Evaluate base models
|
|
for i, name in enumerate(self.models.keys()):
|
|
auc = roc_auc_score(y, oof_preds[:, i])
|
|
print(f"{name} Out-of-fold AUC: {auc:.4f}")
|
|
|
|
# Train meta-models on out-of-fold predictions
|
|
print("Training meta-models...")
|
|
# Weight recent data more heavily for financial time series
|
|
sample_weights = np.exp(np.linspace(-1, 0, len(y)))
|
|
|
|
# Train primary meta-model
|
|
self.meta_model.fit(oof_preds, y, sample_weight=sample_weights)
|
|
|
|
# Train backup meta-model for consensus
|
|
self.meta_model_backup.fit(oof_preds, y, sample_weight=sample_weights)
|
|
|
|
# Display feature importance summary
|
|
self._print_feature_importance()
|
|
|
|
# Final training of all models on full dataset
|
|
print("Final training on complete dataset...")
|
|
for name, model in self.models.items():
|
|
if name in ['xgboost']:
|
|
model.fit(X_scaled, y, eval_set=[(X_scaled, y)], verbose=False)
|
|
elif name in ['lightgbm']:
|
|
model.fit(X_scaled, y, eval_set=[(X_scaled, y)])
|
|
elif name in ['catboost']:
|
|
model.fit(X_scaled, y, eval_set=(X_scaled, y))
|
|
else:
|
|
model.fit(X_scaled, y)
|
|
|
|
return processed_data
|
|
|
|
def _print_feature_importance(self):
|
|
"""Display top features by importance for each model"""
|
|
print("\n=== Feature Importance Analysis ===")
|
|
for name, importances in self.feature_importances.items():
|
|
# Normalize importances to percentages
|
|
importances = importances / np.sum(importances) * 100
|
|
# Sort by importance
|
|
sorted_idx = np.argsort(importances)[::-1]
|
|
print(f"\n{name.upper()} Top 10 Features:")
|
|
for i in range(min(10, len(sorted_idx))):
|
|
idx = sorted_idx[i]
|
|
print(f" {self.feature_names[idx]}: {importances[idx]:.2f}%")
|
|
|
|
def predict(self, data):
|
|
"""
|
|
Generate trading signals for 5-minute gold price data
|
|
|
|
Returns:
|
|
DataFrame with columns:
|
|
- signal: Trading signal (-1=short, 0=neutral, 1=long)
|
|
- strength: Signal strength (0-100%)
|
|
- proba_up: Probability of price increase
|
|
- proba_down: Probability of price decrease
|
|
- model_agreement: Agreement level between meta-models
|
|
- market_regime: Detected market regime
|
|
"""
|
|
processed_data = self.create_advanced_features(data)
|
|
X, _ = self.prepare_features(processed_data)
|
|
X_scaled = self.scaler.transform(X)
|
|
X_scaled = pd.DataFrame(X_scaled, columns=X.columns)
|
|
|
|
# Get predictions from base models
|
|
base_preds = np.zeros((len(X_scaled), len(self.models)))
|
|
for i, (name, model) in enumerate(self.models.items()):
|
|
base_preds[:, i] = model.predict_proba(X_scaled)[:, 1]
|
|
|
|
# Get predictions from both meta-models
|
|
meta_proba = self.meta_model.predict_proba(base_preds)
|
|
backup_proba = self.meta_model_backup.predict_proba(base_preds)
|
|
|
|
# Calculate consensus level between meta-models (0-1)
|
|
models_agreement = 1 - np.abs(meta_proba[:, 1] - backup_proba[:, 1])
|
|
|
|
# Initialize signals
|
|
signals = pd.Series(0, index=processed_data.index)
|
|
|
|
# Generate signals with sophisticated filtering
|
|
# Long signal: high probability of price increase + high model agreement
|
|
long_mask = (meta_proba[:, 1] > self.confidence_threshold) & (models_agreement > 0.8)
|
|
|
|
# Short signal: high probability of price decrease + high model agreement
|
|
short_mask = (meta_proba[:, 0] > self.confidence_threshold) & (models_agreement > 0.8)
|
|
|
|
# Add market regime filter - only take signals in appropriate regimes
|
|
market_regimes = processed_data['market_regime']
|
|
|
|
# Only generate long signals in trending or normal regimes (0 or 1)
|
|
long_regime_mask = (market_regimes == 0) | (market_regimes == 1)
|
|
|
|
# Only generate short signals in trending or high volatility regimes (0 or 2)
|
|
short_regime_mask = (market_regimes == 0) | (market_regimes == 2)
|
|
|
|
# Apply regime filters to signals
|
|
signals.loc[long_mask & long_regime_mask] = 1
|
|
signals.loc[short_mask & short_regime_mask] = -1
|
|
|
|
# Calculate signal strength (0-100%) based on prediction confidence
|
|
signal_strength = pd.Series(0.0, index=processed_data.index)
|
|
signal_strength.loc[long_mask] = (meta_proba[long_mask, 1] - self.confidence_threshold) * (1 / (1 - self.confidence_threshold)) * 100
|
|
signal_strength.loc[short_mask] = (meta_proba[short_mask, 0] - self.confidence_threshold) * (1 / (1 - self.confidence_threshold)) * 100
|
|
|
|
# Risk management: additional signal filters
|
|
# 1. Minimum signal duration (prevent rapid flipping)
|
|
min_bars = 3 # Minimum 15 minutes
|
|
for i in range(min_bars, len(signals)):
|
|
if signals.iloc[i] != 0 and signals.iloc[i] == -signals.iloc[i-1]:
|
|
# If signal flips too soon, maintain previous signal
|
|
if sum(signals.iloc[i-min_bars:i] == signals.iloc[i-1]) >= min_bars-1:
|
|
signals.iloc[i] = signals.iloc[i-1]
|
|
|
|
# 2. Filter out signals during extreme volatility
|
|
high_vol_mask = processed_data['atr_ratio_24'] > processed_data['atr_ratio_24'].quantile(0.95)
|
|
signals.loc[high_vol_mask] = 0
|
|
|
|
# Combine results into a DataFrame
|
|
results = pd.DataFrame({
|
|
'signal': signals,
|
|
'strength': signal_strength,
|
|
'proba_up': meta_proba[:, 1],
|
|
'proba_down': meta_proba[:, 0],
|
|
'model_agreement': models_agreement,
|
|
'market_regime': processed_data['market_regime']
|
|
}, index=processed_data.index)
|
|
|
|
return results
|
|
|
|
def evaluate_performance(self, test_data):
|
|
"""
|
|
Evaluate model performance with trading simulation - corrected version
|
|
"""
|
|
results = self.predict(test_data)
|
|
|
|
# Calculate forward returns for evaluation period
|
|
close_prices = test_data['close']
|
|
forward_returns = close_prices.shift(-self.forecast_bars) / close_prices - 1
|
|
|
|
# Apply signals to returns (long = 1x return, short = -1x return)
|
|
strategy_returns = results['signal'] * forward_returns
|
|
|
|
# Properly handle NaN values that might appear from shifts
|
|
strategy_returns = strategy_returns.dropna()
|
|
|
|
# Calculate performance metrics
|
|
total_return = strategy_returns.sum()
|
|
|
|
# Correct annualization factor: 252 trading days, 12 hours per day, 12 bars per hour
|
|
annualization_factor = np.sqrt(252 * 12 * 12)
|
|
sharpe_ratio = strategy_returns.mean() / strategy_returns.std() * annualization_factor
|
|
|
|
# Correct win rate calculation - account for signal direction
|
|
wins = ((strategy_returns > 0) & (results['signal'] != 0)).sum()
|
|
total_trades = (results['signal'] != 0).sum()
|
|
win_rate = wins / total_trades if total_trades > 0 else 0
|
|
|
|
# Correct drawdown calculation
|
|
cumulative_returns = strategy_returns.cumsum()
|
|
drawdowns = cumulative_returns - cumulative_returns.cummax()
|
|
max_drawdown = drawdowns.min()
|
|
|
|
# Signal statistics
|
|
signal_count = (results['signal'] != 0).sum()
|
|
signal_changes = results['signal'].diff().abs()
|
|
signal_changes = signal_changes[signal_changes > 0].sum() / 2 # Each change counts twice in diff
|
|
|
|
# Correct trading days calculation (typically 5 days a week for forex)
|
|
# Assuming 12 hours of active trading per day and 12 5-min bars per hour
|
|
trading_days = len(results) / (12 * 12)
|
|
avg_signals_per_day = signal_count / trading_days
|
|
|
|
# Calculate profit factor
|
|
profitable_trades = strategy_returns[strategy_returns > 0].sum()
|
|
losing_trades = abs(strategy_returns[strategy_returns < 0].sum())
|
|
profit_factor = profitable_trades / losing_trades if losing_trades != 0 else float('inf')
|
|
|
|
# Calculate average profit per trade
|
|
avg_profit_per_trade = total_return / total_trades if total_trades > 0 else 0
|
|
|
|
# Compile metrics
|
|
metrics = {
|
|
'total_return': total_return,
|
|
'annualized_return': total_return * (252 / trading_days),
|
|
'sharpe_ratio': sharpe_ratio,
|
|
'win_rate': win_rate,
|
|
'max_drawdown': max_drawdown,
|
|
'profit_factor': profit_factor,
|
|
'signal_count': signal_count,
|
|
'signal_changes': signal_changes,
|
|
'avg_signals_per_day': avg_signals_per_day,
|
|
'avg_profit_per_trade': avg_profit_per_trade
|
|
}
|
|
|
|
print("\n=== Performance Evaluation ===")
|
|
print(f"Total Return: {total_return:.2%}")
|
|
print(f"Annualized Return: {metrics['annualized_return']:.2%}")
|
|
print(f"Annualized Sharpe Ratio: {sharpe_ratio:.2f}")
|
|
print(f"Win Rate: {win_rate:.2%}")
|
|
print(f"Maximum Drawdown: {max_drawdown:.2%}")
|
|
print(f"Profit Factor: {profit_factor:.2f}")
|
|
print(f"Total Signals: {signal_count}")
|
|
print(f"Signal Changes: {signal_changes}")
|
|
print(f"Average Signals Per Day: {avg_signals_per_day:.2f}")
|
|
print(f"Average Profit Per Trade: {avg_profit_per_trade:.4%}")
|
|
|
|
return metrics, results, strategy_returns
|
|
|
|
# Example usage
|
|
def run_model(data_path, forecast_bars=24, confidence_threshold=0.65):
|
|
"""
|
|
Run the model on input data
|
|
|
|
Parameters:
|
|
data_path: Path to CSV file with OHLCV data
|
|
forecast_bars: Number of 5-min bars to forecast
|
|
confidence_threshold: Threshold for signal generation
|
|
|
|
Returns:
|
|
predictor: Trained model
|
|
metrics: Performance metrics
|
|
results: Signal results
|
|
returns: Strategy returns
|
|
"""
|
|
# Load and prepare data
|
|
data = pd.read_csv(data_path)
|
|
data['timestamp'] = pd.to_datetime(data['timestamp'])
|
|
data = data.drop_duplicates(subset=['timestamp'])
|
|
data.set_index('timestamp', inplace=True)
|
|
|
|
# Split into train/test
|
|
#pick the last 40% of data before the last 80% from 40% to 80%
|
|
train_size = int(len(data) * 0.95)
|
|
train_data = data[int(len(data) * 0.80):train_size].copy()
|
|
test_data = data[train_size:].copy()
|
|
|
|
# Create and train model
|
|
predictor = TreeEnsemblePredictor(
|
|
forecast_bars=forecast_bars,
|
|
confidence_threshold=confidence_threshold
|
|
)
|
|
print(f"Training model with forecast_bars={forecast_bars}, confidence_threshold={confidence_threshold}")
|
|
predictor.fit(train_data)
|
|
|
|
# Evaluate model
|
|
metrics, results, returns = predictor.evaluate_performance(test_data)
|
|
|
|
return predictor, metrics, results, returns
|