901 KiB
901 KiB
In [ ]:
# REGIME DETECTION + LabelEncoder approach for SHIFTED labels
import sys
import os
import warnings
from pathlib import Path
# ---------------------------------------------------------------------------
# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY
# ---------------------------------------------------------------------------
project_root = Path.cwd().parent.parent # Adjust if your notebook is in notebooks/time_series
sys.path.append(str(project_root))
os.chdir(str(project_root))
warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import MetaTrader5 as mt5
import vectorbt as vbt
import joblib
# Our modules
from data.data_loader import get_data_mt5
from features.feature_engineering import add_all_ta_features
from models.model_training import (
select_features_rf_reg,
walk_forward_splits
)
from backtests.simple_backtest import simulate_trading, calculate_sharpe_ratio
from features.labeling_schemes import create_labels_regime_detection # or define inline
# Sklearn / Models
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
from sklearn.naive_bayes import GaussianNB, BernoulliNB
###########################################################
# 1) DATA LOADING & FEATURE ENGINEERING
###########################################################
if not mt5.initialize():
print("Failed to initialize MT5")
else:
data = get_data_mt5(symbol="BTCUSD", timeframe=mt5.TIMEFRAME_H4, n_bars=2000, start_pos=2000)
mt5.shutdown()
df = add_all_ta_features(data)
###########################################################
# 2) Regime Detection Labeling
###########################################################
df_lbl = create_labels_regime_detection(df, short_window=20, long_window=50)
# X => all features except the label columns
X = df_lbl.drop(columns=["regime_label", "ma_short", "ma_long"])
y = df_lbl["regime_label"] # in {-1,0,+1}
###########################################################
# SHIFT LABELS?
# We'll SHIFT them to [0,1,2] in each fold with LabelEncoder
# so we can handle folds with only two classes, e.g. {0,2}.
###########################################################
###########################################################
# 3) WALK-FORWARD SPLITS
###########################################################
folds = walk_forward_splits(X, y, n_splits=3)
print(f"Number of folds created: {len(folds)}")
###########################################################
# 4) DEFINE CLASSIFICATION MODELS
###########################################################
models = {
"RandomForestClassifier": RandomForestClassifier(n_estimators=100, random_state=42),
"GradientBoostingClassifier": GradientBoostingClassifier(n_estimators=100, learning_rate=0.1, max_depth=5, random_state=42),
"SVC": SVC(C=1.0, kernel='rbf', probability=True),
"XGBClassifier": XGBClassifier(n_estimators=100, learning_rate=0.1, random_state=42),
"LGBMClassifier": LGBMClassifier(n_estimators=100, learning_rate=0.1, random_state=42),
"GaussianNB": GaussianNB(), # <-- Added Bayesian Classification Model
"BernoulliNB": BernoulliNB() # <-- Another Bayesian Model (good for binary data)
}
###########################################################
# 5) LOOP OVER FOLDS + SIMPLE BACKTEST (with LabelEncoder)
###########################################################
fold_results = {}
for fold_i, (X_train_fold, y_train_fold, X_test_fold, y_test_fold) in enumerate(folds, start=1):
print(f"\n===== Fold {fold_i} =====")
print(f"Train size: {len(X_train_fold)}, Test size: {len(X_test_fold)}")
# 5.1 SHIFT labels with LabelEncoder so classes become consecutive integers
le = LabelEncoder()
# Fit on the training labels (which might be e.g. {-1,+1}, => SHIFT with le)
# or {0,1} => SHIFT, etc. This ensures e.g. {0,2} becomes {0,1}.
le.fit(y_train_fold)
y_train_enc = le.transform(y_train_fold)
# 5.2 Feature selection
# We can pass the encoded y to select_features_rf_reg
# because it just needs an estimator with .fit(X, y).
# SHIFT isn't strictly necessary for random forest's .feature_importances_,
# but let's be consistent.
rf_for_fs = RandomForestClassifier(n_estimators=100, random_state=42)
X_train_sel, selected_idx = select_features_rf_reg(
X_train_fold, y_train_enc, estimator=rf_for_fs, max_features=20
)
feats = X_train_fold.columns[selected_idx]
print(f"Selected features for Fold {fold_i}: {feats.tolist()}")
X_test_sel = X_test_fold[feats]
# 5.3 Scale
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train_sel)
X_test_scaled = scaler.transform(X_test_sel)
fold_results[fold_i] = {}
for model_name, model in models.items():
# 5.4 Train on the encoded training labels
model.fit(X_train_scaled, y_train_enc)
# 5.5 Predict encoded classes on test
preds_enc = model.predict(X_test_scaled)
# 5.6 Decode them back to the original label set (which might be e.g. [-1,0,+1])
preds_fold = le.inverse_transform(preds_enc)
# 5.7 Evaluate accuracy on the unencoded test labels
acc = accuracy_score(y_test_fold, preds_fold)
# 5.8 Convert classification => signals
# If your original labels are: +1 => up, -1 => down, 0 => sideways,
# then we interpret them as: +1 => buy, -1 => sell, 0 => no trade
signals = preds_fold
# Align with test portion
df_test_fold = df_lbl.loc[X_test_fold.index].copy()
# 5.9 Simple backtest with cost
daily_returns, total_return = simulate_trading(signals, df_test_fold, cost=0.0002)
sr = calculate_sharpe_ratio(np.array(daily_returns))
fold_results[fold_i][model_name] = {
"Accuracy": acc,
"TotalReturn": total_return,
"Sharpe": sr
}
###########################################################
# 6) PRINT RESULTS
###########################################################
for fold_i, model_dict in fold_results.items():
print(f"\n=== Fold {fold_i} Results ===")
for model_name, stats in model_dict.items():
acc = stats["Accuracy"]
ret = stats["TotalReturn"]
sr = stats["Sharpe"]
print(f"{model_name}: ACC={acc:.2f}, Return={ret:.2f}%, Sharpe={sr:.2f}")
Number of folds created: 3 ===== Fold 1 ===== Train size: 237, Test size: 237 Selected features for Fold 1: ['trend_kst_sig', 'trend_macd_signal', 'momentum_ppo_signal', 'trend_visual_ichimoku_b', 'momentum_tsi', 'trend_kst', 'trend_trix', 'trend_aroon_up', 'trend_visual_ichimoku_a', 'trend_macd', 'momentum_ppo', 'momentum_stoch_rsi_k', 'volatility_ui', 'trend_ichimoku_b', 'momentum_stoch_rsi_d', 'trend_ichimoku_conv', 'trend_mass_index', 'trend_adx_neg', 'momentum_wr', 'momentum_stoch_rsi']
File "c:\Users\moham\miniconda3\envs\ml\Lib\site-packages\joblib\externals\loky\backend\context.py", line 257, in _count_physical_cores
cpu_info = subprocess.run(
^^^^^^^^^^^^^^^
File "c:\Users\moham\miniconda3\envs\ml\Lib\subprocess.py", line 548, in run
with Popen(*popenargs, **kwargs) as process:
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "c:\Users\moham\miniconda3\envs\ml\Lib\subprocess.py", line 1026, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "c:\Users\moham\miniconda3\envs\ml\Lib\subprocess.py", line 1538, in _execute_child
hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[LightGBM] [Info] Number of positive: 166, number of negative: 71 [LightGBM] [Info] Auto-choosing col-wise multi-threading, the overhead of testing was 0.000173 seconds. You can set `force_col_wise=true` to remove the overhead. [LightGBM] [Info] Total Bins 1418 [LightGBM] [Info] Number of data points in the train set: 237, number of used features: 20 [LightGBM] [Info] [binary:BoostFromScore]: pavg=0.700422 -> initscore=0.849308 [LightGBM] [Info] Start training from score 0.849308 [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf ===== Fold 2 ===== Train size: 474, Test size: 237 Selected features for Fold 2: ['trend_kst_sig', 'trend_macd_signal', 'momentum_ppo_signal', 'trend_trix', 'trend_kst', 'momentum_tsi', 'momentum_ppo', 'trend_macd', 'momentum_stoch_rsi', 'momentum_ppo_hist', 'momentum_stoch_rsi_k', 'trend_macd_diff', 'trend_visual_ichimoku_a', 'trend_kst_diff', 'momentum_stoch_rsi_d', 'volume_nvi', 'trend_aroon_up', 'trend_aroon_ind', 'trend_aroon_down', 'trend_adx_neg'] [LightGBM] [Info] Number of positive: 347, number of negative: 127 [LightGBM] [Info] Auto-choosing col-wise multi-threading, the overhead of testing was 0.000195 seconds. You can set `force_col_wise=true` to remove the overhead. [LightGBM] [Info] Total Bins 2730 [LightGBM] [Info] Number of data points in the train set: 474, number of used features: 20 [LightGBM] [Info] [binary:BoostFromScore]: pavg=0.732068 -> initscore=1.005138 [LightGBM] [Info] Start training from score 1.005138 [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf ===== Fold 3 ===== Train size: 711, Test size: 237 Selected features for Fold 3: ['trend_kst_sig', 'trend_macd_signal', 'momentum_ppo_signal', 'trend_kst', 'trend_trix', 'momentum_tsi', 'momentum_ppo', 'trend_macd', 'momentum_stoch_rsi_k', 'momentum_ppo_hist', 'trend_aroon_up', 'trend_macd_diff', 'momentum_stoch_rsi', 'momentum_stoch_rsi_d', 'volatility_ui', 'trend_aroon_down', 'trend_kst_diff', 'momentum_rsi', 'momentum_ao', 'trend_stc'] [LightGBM] [Info] Number of positive: 470, number of negative: 241 [LightGBM] [Info] Auto-choosing col-wise multi-threading, the overhead of testing was 0.000307 seconds. You can set `force_col_wise=true` to remove the overhead. [LightGBM] [Info] Total Bins 4326 [LightGBM] [Info] Number of data points in the train set: 711, number of used features: 20 [LightGBM] [Info] [binary:BoostFromScore]: pavg=0.661041 -> initscore=0.667936 [LightGBM] [Info] Start training from score 0.667936 [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf [LightGBM] [Warning] No further splits with positive gain, best gain: -inf === Fold 1 Results === RandomForestClassifier: ACC=0.95, Return=-2.82%, Sharpe=-0.00 GradientBoostingClassifier: ACC=0.97, Return=-6.91%, Sharpe=-0.02 SVC: ACC=0.89, Return=14.37%, Sharpe=0.06 XGBClassifier: ACC=0.96, Return=-3.55%, Sharpe=-0.01 LGBMClassifier: ACC=0.97, Return=-8.13%, Sharpe=-0.03 === Fold 2 Results === RandomForestClassifier: ACC=0.95, Return=-25.27%, Sharpe=-0.11 GradientBoostingClassifier: ACC=0.97, Return=-18.73%, Sharpe=-0.08 SVC: ACC=0.92, Return=-15.33%, Sharpe=-0.06 XGBClassifier: ACC=0.97, Return=-22.06%, Sharpe=-0.09 LGBMClassifier: ACC=0.97, Return=-22.06%, Sharpe=-0.09 === Fold 3 Results === RandomForestClassifier: ACC=0.92, Return=-14.90%, Sharpe=-0.05 GradientBoostingClassifier: ACC=0.86, Return=-16.71%, Sharpe=-0.06 SVC: ACC=0.91, Return=-30.49%, Sharpe=-0.13 XGBClassifier: ACC=0.89, Return=-8.63%, Sharpe=-0.03 LGBMClassifier: ACC=0.89, Return=-14.26%, Sharpe=-0.05
In [ ]:
# Code 2: Hyperparameter Tuning for a Classification Model (Regime Detection)
import sys
import os
import warnings
from pathlib import Path
# ---------------------------------------------------------------------------
# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY
# ---------------------------------------------------------------------------
project_root = Path.cwd().parent.parent # Adjust if your notebook is in notebooks/time_series
sys.path.append(str(project_root))
os.chdir(str(project_root))
warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import MetaTrader5 as mt5
import joblib
# Sklearn / Models
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import TimeSeriesSplit, RandomizedSearchCV
from sklearn.metrics import accuracy_score, make_scorer
from sklearn.pipeline import Pipeline
# Your modules
from data.data_loader import get_data_mt5
from features.feature_engineering import add_all_ta_features
from features.labeling_schemes import create_labels_regime_detection # or define inline
###########################################################
# 1) DATA LOADING & FEATURE ENGINEERING
###########################################################
if not mt5.initialize():
print("Failed to initialize MT5")
else:
data = get_data_mt5(symbol="BTCUSD", timeframe=mt5.TIMEFRAME_H4, n_bars=2000, start_pos=2000)
mt5.shutdown()
df = add_all_ta_features(data)
###########################################################
# 2) Regime Detection Labeling
###########################################################
# Create classification labels for regime detection
df_lbl = create_labels_regime_detection(df, short_window=20, long_window=50)
# Now we have columns: 'ma_short', 'ma_long', 'regime_label' in {-1,0,+1}
X_full = df_lbl.drop(columns=["regime_label", "ma_short", "ma_long"])
y_full = df_lbl["regime_label"]
# SHIFT LABELS from [-1,0,+1] => [0,1,2]
# so the classifier won't complain about negative labels
y_full_shifted = y_full + 1 # -1->0, 0->1, +1->2
print("Unique classes in y_full:", y_full.unique())
print("Unique classes in y_full_shifted:", y_full_shifted.unique())
# Ensure chronological order if needed
# X_full = X_full.sort_index()
# y_full_shifted = y_full_shifted.loc[X_full.index]
###########################################################
# 3) DEFINE YOUR TRAIN PORTION
###########################################################
# e.g., first 80% for hyperparam tuning
split_idx = int(len(X_full) * 0.8)
X_tune = X_full.iloc[:split_idx].copy()
y_tune_shifted = y_full_shifted.iloc[:split_idx].copy()
print(f"Tuning portion size: {len(X_tune)}")
###########################################################
# 4) TIME-BASED CV (TimeSeriesSplit)
###########################################################
tscv = TimeSeriesSplit(n_splits=3)
# We'll define a scoring for classification (accuracy)
scorer = make_scorer(accuracy_score)
###########################################################
# 5) BUILD A PIPELINE
###########################################################
pipeline = Pipeline([
("scaler", StandardScaler()),
("clf", RandomForestClassifier(random_state=42))
])
###########################################################
# 6) DEFINE PARAM DISTRIBUTIONS FOR RandomForestClassifier
###########################################################
param_distributions = {
"clf__n_estimators": [100, 200, 300],
"clf__max_depth": [None, 5, 10, 15],
"clf__min_samples_split": [2, 5, 10],
"clf__max_features": ["auto", "sqrt", 0.5]
}
###########################################################
# 7) SET UP RandomizedSearchCV
###########################################################
random_search = RandomizedSearchCV(
estimator=pipeline,
param_distributions=param_distributions,
n_iter=10, # how many random combos
scoring=scorer, # 'accuracy' metric
cv=tscv, # time-based folds
random_state=42,
n_jobs=-1,
verbose=2
)
###########################################################
# 8) FIT ON TUNING PORTION
###########################################################
random_search.fit(X_tune, y_tune_shifted)
print("Best params:", random_search.best_params_)
print("Best score (accuracy):", random_search.best_score_)
best_estimator = random_search.best_estimator_
###########################################################
# 9) SAVE THE BEST ESTIMATOR
###########################################################
joblib.dump(best_estimator, "models/saved_models/best_rf_rd_pipeline.pkl")
print("Saved best estimator to 'best_rf_rd_pipeline.pkl'")
Unique classes in y_full: [-1 1]
Unique classes in y_full_shifted: [0 2]
Tuning portion size: 1560
Fitting 3 folds for each of 10 candidates, totalling 30 fits
Best params: {'clf__n_estimators': 200, 'clf__min_samples_split': 5, 'clf__max_features': 'sqrt', 'clf__max_depth': 10}
Best score (accuracy): 0.9290598290598292
Saved best estimator to 'best_rf_rd_pipeline.pkl'
In [ ]:
import sys
import os
import warnings
from pathlib import Path
# ---------------------------------------------------------------------------
# 1) SET PROJECT ROOT AND UPDATE PATH/WORKING DIRECTORY
# ---------------------------------------------------------------------------
project_root = Path.cwd().parent.parent
sys.path.append(str(project_root))
os.chdir(str(project_root))
warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import MetaTrader5 as mt5
import vectorbt as vbt
import joblib
# Our modules
from data.data_loader import get_data_mt5
from features.feature_engineering import add_all_ta_features
from features.labeling_schemes import create_labels_regime_detection
# Sklearn
from sklearn.metrics import accuracy_score
###########################################################
# 1) DATA LOADING & FEATURE ENGINEERING
###########################################################
if not mt5.initialize():
print("Failed to initialize MT5")
else:
# Fetch 2000 most recent bars for backtesting
data = get_data_mt5(symbol="BTCUSD", timeframe=mt5.TIMEFRAME_H4, n_bars=2000, start_pos=0)
mt5.shutdown()
# Add technical features
df = add_all_ta_features(data)
# Create regime detection labels
df_lbl = create_labels_regime_detection(df, short_window=20, long_window=50)
# Separate features and labels
X = df_lbl.drop(columns=["regime_label", "ma_short", "ma_long"])
y = df_lbl["regime_label"] # in {-1, 0, +1}
y_shifted = y + 1 # in {0,1,2} if your classifier used shifted labels
###########################################################
# 2) LOAD PRE-TRAINED CLASSIFICATION MODEL (NO RETRAINING)
###########################################################
best_pipeline = joblib.load("models/saved_models/best_rf_rd_pipeline.pkl")
print("Loaded best classification model from 'best_rf_rd_pipeline.pkl'")
# Identify the columns the pipeline was trained on
trained_columns = best_pipeline["scaler"].feature_names_in_ # Adjust if your pipeline step name differs
# Subset X to match these columns
X_test = X[trained_columns]
# Predict on the new dataset
preds_shifted = best_pipeline.predict(X_test)
# Shift predictions back to {-1, 0, +1}
preds = preds_shifted - 1
# Accuracy on the exact prediction rows
accuracy = accuracy_score(y.loc[X_test.index], preds)
print(f"\nOut-of-Sample Accuracy: {accuracy:.4f}")
###########################################################
# 3) BACKTEST via target exposure (-1, 0, +1) << UPDATED
###########################################################
# predictions already in {-1, 0, +1}
exposure = pd.Series(preds.astype(float), index=X_test.index)
# Align prices exactly to prediction rows (no padding)
close = df_lbl.loc[X_test.index, "close"]
# Optional: trade on next bar to avoid look-ahead (set to 0 for same-bar)
execution_lag = 1
if execution_lag > 0:
exposure = exposure.shift(execution_lag).fillna(0.0)
fees = 0.0002 # 0.02% transaction cost per trade
pf = vbt.Portfolio.from_orders(
close=close,
size=exposure, # -1 short, 0 flat, +1 long
size_type='targetpercent',
init_cash=10000,
freq='4H',
fees=fees
)
total_return = pf.total_return()
sharpe_ratio = pf.sharpe_ratio()
print("\nFull Backtest Results:")
print(f"Accuracy={accuracy:.4f}, Return={total_return:.2f}%, Sharpe={sharpe_ratio:.2f}")
print(pf.stats())
# Optional: Plot the backtest
fig = pf.plot()
fig.show()
Loaded best classification model from 'best_rf_rd_pipeline.pkl' Out-of-Sample Accuracy: 0.9821 Running Full Backtest on the Last 2000 Bars... Full Backtest Results: Accuracy=0.98, Return=-0.17%, Sharpe=-0.39 Start 2024-04-11 16:00:00 End 2025-03-02 16:00:00 Period 325 days 04:00:00 Start Value 10000.0 End Value 8340.15852 Total Return [%] -16.598415 Benchmark Return [%] 21.623307 Max Gross Exposure [%] 100.0 Total Fees Paid 94.120141 Max Drawdown [%] 29.083683 Max Drawdown Duration 213 days 12:00:00 Total Trades 25 Total Closed Trades 25 Total Open Trades 0 Open Trade PnL 0.0 Win Rate [%] 40.0 Best Trade [%] 22.932124 Worst Trade [%] -9.560755 Avg Winning Trade [%] 5.949059 Avg Losing Trade [%] -4.782133 Avg Winning Trade Duration 11 days 14:23:59.999999999 Avg Losing Trade Duration 3 days 21:20:00 Profit Factor 0.760015 Expectancy -66.393659 Sharpe Ratio -0.389969 Calmar Ratio -0.633769 Omega Ratio 0.966828 Sortino Ratio -0.54345 dtype: object
[Data output - unsupported data type map[string]interface {} for mime type application/vnd.plotly.v1+json]