chore: add backtest #39 and HMM investigation artifacts

Added research artifacts from HMM investigation:
- backtest_39_h1_hmm.py — H1 vs M15 HMM comparison attempt
- 39_h1_hmm_results/ — Partial backtest results
- analyze_*.py — ML model and H1 feature analysis scripts
- *_output.txt — Analysis outputs showing HMM degeneracy

Updated:
- data/risk_state.txt — Latest risk state (daily_loss: 18.77, daily_profit: 22.49)

Note: Backtest #39 had import compatibility issues but led to critical
discovery of alternating HMM pattern bug (fixed in c02c2e9).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
buckybonez
2026-02-09 10:51:49 +07:00
parent 20ce86b937
commit f736f1f75b
8 changed files with 1805 additions and 4 deletions
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
"""
H1 Feature Analysis - Check if H1 features actually exist and their correlation
"""
import sys
import pickle
from pathlib import Path
import numpy as np
import polars as pl
sys.path.insert(0, str(Path(__file__).parent / "src"))
print("=" * 80)
print("H1 FEATURE DEEP ANALYSIS")
print("=" * 80)
# Load model
model_path = Path("models/xgboost_model_v2d.pkl")
with open(model_path, "rb") as f:
model_data = pickle.load(f)
feature_names = model_data.get("feature_names", [])
feature_importance = model_data.get("feature_importance", {})
print(f"\nTotal features in model: {len(feature_names)}")
# Find all H1-related features
h1_features = [f for f in feature_names if "h1" in f.lower() or "H1" in f]
print(f"\nH1 features found: {len(h1_features)}")
if h1_features:
print("\n--- ALL H1 FEATURES ---")
for feat in sorted(h1_features):
importance = feature_importance.get(feat, 0)
# Find rank
sorted_features = sorted(feature_importance.items(), key=lambda x: x[1], reverse=True)
rank = [f for f, s in sorted_features].index(feat) + 1 if feat in dict(sorted_features) else 999
print(f" Rank #{rank:2d}: {feat:40s} importance={importance:10.4f}")
# Top H1 features
h1_with_importance = [(f, feature_importance.get(f, 0)) for f in h1_features]
h1_with_importance.sort(key=lambda x: x[1], reverse=True)
print("\n--- TOP 10 H1 FEATURES (by importance) ---")
for i, (feat, imp) in enumerate(h1_with_importance[:10], 1):
sorted_features = sorted(feature_importance.items(), key=lambda x: x[1], reverse=True)
rank = [f for f, s in sorted_features].index(feat) + 1
print(f"{i:2d}. Rank #{rank:3d}: {feat:40s} {imp:10.4f}")
# Summary stats
importances = [imp for f, imp in h1_with_importance]
print(f"\n--- H1 FEATURE STATISTICS ---")
print(f"Total H1 features: {len(h1_features)}")
print(f"Mean importance: {np.mean(importances):.4f}")
print(f"Median importance: {np.median(importances):.4f}")
print(f"Max importance: {np.max(importances):.4f}")
print(f"Min importance: {np.min(importances):.4f}")
# Check how many in top N
sorted_all = sorted(feature_importance.items(), key=lambda x: x[1], reverse=True)
top10_features = [f for f, s in sorted_all[:10]]
top20_features = [f for f, s in sorted_all[:20]]
top30_features = [f for f, s in sorted_all[:30]]
h1_in_top10 = [f for f in top10_features if "h1" in f.lower()]
h1_in_top20 = [f for f in top20_features if "h1" in f.lower()]
h1_in_top30 = [f for f in top30_features if "h1" in f.lower()]
print(f"\nH1 features in top 10: {len(h1_in_top10)}")
print(f"H1 features in top 20: {len(h1_in_top20)}")
print(f"H1 features in top 30: {len(h1_in_top30)}")
else:
print("\nNO H1 FEATURES FOUND IN MODEL!")
# Check all feature names
print("\n" + "=" * 80)
print("ALL FEATURE NAMES IN MODEL")
print("=" * 80)
for i, feat in enumerate(feature_names, 1):
importance = feature_importance.get(feat, 0)
print(f"{i:2d}. {feat:50s} {importance:10.4f}")
# Load training data and check for H1 columns
print("\n" + "=" * 80)
print("CHECKING TRAINING DATA FOR H1 FEATURES")
print("=" * 80)
data_file = Path("data/training_data.parquet")
if data_file.exists():
df = pl.read_parquet(data_file)
print(f"\nDataset columns: {len(df.columns)}")
# Find H1 columns
h1_cols = [col for col in df.columns if "h1" in col.lower() or "H1" in col]
print(f"H1 columns in dataset: {len(h1_cols)}")
if h1_cols:
print("\n--- H1 COLUMNS IN DATASET ---")
for col in sorted(h1_cols):
# Check if in model features
in_model = "YES" if col in feature_names else "NO"
print(f" {col:50s} in_model={in_model}")
else:
print("\nNO H1 COLUMNS IN TRAINING DATA!")
# Check if there are any columns that might be H1-related
print("\nLooking for potential H1-related columns:")
potential = [col for col in df.columns if any(x in col.lower() for x in ["hour", "h4", "d1", "timeframe"])]
if potential:
for col in potential:
print(f" {col}")
else:
print(" None found")
# Check if feature engineering creates H1 features
print("\n" + "=" * 80)
print("CHECKING FEATURE ENGINEERING CODE")
print("=" * 80)
feature_eng_file = Path("src/feature_eng.py")
if feature_eng_file.exists():
with open(feature_eng_file, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
# Search for H1 references
if "h1" in content.lower() or "H1" in content:
print("\nH1 references found in feature_eng.py:")
lines = content.split("\n")
for i, line in enumerate(lines, 1):
if "h1" in line.lower() or "H1" in line:
print(f" Line {i}: {line.strip()}")
else:
print("\nNO H1 references found in feature_eng.py")
# Check for multi-timeframe
if "timeframe" in content.lower() or "TIMEFRAME_H1" in content or "mt5.TIMEFRAME_H1" in content:
print("\nMulti-timeframe references found:")
lines = content.split("\n")
for i, line in enumerate(lines, 1):
if "timeframe" in line.lower():
print(f" Line {i}: {line.strip()}")
print("\n" + "=" * 80)
print("CONCLUSION")
print("=" * 80)
if h1_features:
print(f"\n✓ Model HAS {len(h1_features)} H1 features")
print(f"✓ Highest ranked H1 feature: {h1_with_importance[0][0]} at rank #{[f for f, s in sorted_all].index(h1_with_importance[0][0]) + 1}")
print(f"✓ Average H1 importance: {np.mean(importances):.4f}")
else:
print("\n✗ Model has NO H1 features!")
print("✗ The 'V2D' model does not include H1 timeframe data")
print("✗ Need to retrain with H1 features to test hypothesis")
+313
View File
@@ -0,0 +1,313 @@
#!/usr/bin/env python3
"""
Deep ML Model Analysis Script - Fixed version
"""
import sys
import pickle
import json
from pathlib import Path
import numpy as np
import polars as pl
from collections import defaultdict, Counter
# Add src to path
sys.path.insert(0, str(Path(__file__).parent / "src"))
def print_section(title):
print("\n" + "=" * 80)
print(title)
print("=" * 80)
print_section("ML MODEL DEEP DIVE ANALYSIS")
# ============================================================================
# 1. LOAD AND INSPECT V2D MODEL
# ============================================================================
print_section("1. MODEL INSPECTION: xgboost_model_v2d.pkl")
model_path = Path("models/xgboost_model_v2d.pkl")
if not model_path.exists():
print(f"ERROR: Model not found at {model_path}")
sys.exit(1)
with open(model_path, "rb") as f:
model_data = pickle.load(f)
print(f"\nModel pickle structure:")
for key in model_data.keys():
value = model_data[key]
if isinstance(value, (list, dict)):
print(f" {key}: {type(value).__name__} (length={len(value)})")
else:
print(f" {key}: {type(value).__name__}")
# Extract components
xgb_model = model_data.get("xgb_model")
lgb_model = model_data.get("lgb_model")
feature_names = model_data.get("feature_names", [])
feature_importance = model_data.get("feature_importance", {})
train_metrics = model_data.get("train_metrics", {})
xgb_params = model_data.get("xgb_params", {})
print(f"\nXGBoost model: {type(xgb_model)}")
print(f"LightGBM model: {type(lgb_model)}")
print(f"Total features: {len(feature_names)}")
# Display training metrics
print("\n--- TRAINING METRICS ---")
for key, value in train_metrics.items():
if isinstance(value, (int, float)):
print(f"{key}: {value}")
elif isinstance(value, dict):
print(f"{key}:")
for k, v in value.items():
print(f" {k}: {v}")
# XGBoost parameters
print("\n--- XGBOOST PARAMETERS ---")
for key, value in xgb_params.items():
print(f"{key}: {value}")
# ============================================================================
# 2. FEATURE IMPORTANCE ANALYSIS
# ============================================================================
print_section("2. FEATURE IMPORTANCE RANKING")
if feature_importance:
# Sort by importance
sorted_features = sorted(feature_importance.items(), key=lambda x: x[1], reverse=True)
print(f"\nTotal features with importance: {len(sorted_features)}")
# Categorize
h1_features = []
m15_features = []
smc_features = []
for feat, score in sorted_features:
if "_h1" in feat.lower():
h1_features.append((feat, score))
elif any(x in feat for x in ["ob_", "fvg_", "bos", "choch"]):
smc_features.append((feat, score))
elif any(x in feat.lower() for x in ["rsi", "macd", "bb", "atr", "ema", "sma", "stoch"]):
m15_features.append((feat, score))
print("\n--- TOP 30 FEATURES ---")
for i, (feat, score) in enumerate(sorted_features[:30], 1):
category = "H1" if "_h1" in feat.lower() else "SMC" if any(x in feat for x in ["ob_", "fvg_", "bos", "choch"]) else "M15"
print(f"{i:2d}. {feat:40s} {score:12.6f} [{category}]")
# H1 features in top 10
h1_in_top10 = [feat for feat, score in sorted_features[:10] if "_h1" in feat.lower()]
print(f"\n--- H1 FEATURES IN TOP 10 ---")
print(f"Count: {len(h1_in_top10)}")
for feat in h1_in_top10:
rank = [f for f, s in sorted_features].index(feat) + 1
score = dict(sorted_features)[feat]
print(f" Rank #{rank}: {feat} (importance: {score:.6f})")
# Category summary
print("\n--- FEATURE CATEGORY SUMMARY ---")
print(f"H1 features: {len(h1_features)}")
print(f"M15 technical features: {len(m15_features)}")
print(f"SMC features: {len(smc_features)}")
if h1_features:
avg_h1 = np.mean([s for f, s in h1_features])
print(f"\nAverage H1 importance: {avg_h1:.6f}")
if m15_features:
avg_m15 = np.mean([s for f, s in m15_features])
print(f"Average M15 importance: {avg_m15:.6f}")
if smc_features:
avg_smc = np.mean([s for f, s in smc_features])
print(f"Average SMC importance: {avg_smc:.6f}")
# Top H1 features
if h1_features:
print("\n--- ALL H1 FEATURES (sorted by importance) ---")
for i, (feat, score) in enumerate(h1_features, 1):
rank = [f for f, s in sorted_features].index(feat) + 1
print(f"{i:2d}. Rank #{rank:2d}: {feat:40s} {score:12.6f}")
else:
print("\nNo feature importance data found in model")
# ============================================================================
# 3. TARGET VARIABLE STATISTICS
# ============================================================================
print_section("3. TARGET VARIABLE STATISTICS")
data_file = Path("data/training_data.parquet")
if data_file.exists():
print(f"\nLoading: {data_file}")
df = pl.read_parquet(data_file)
print(f"Dataset shape: {df.shape}")
# Target distribution
if "target" in df.columns:
target_counts = df.group_by("target").agg(pl.len().alias("count")).sort("target")
print("\n--- TARGET DISTRIBUTION ---")
total = df.shape[0]
for row in target_counts.iter_rows(named=True):
pct = (row['count'] / total) * 100
target_label = {0: "SELL", 1: "HOLD", 2: "BUY"}.get(row['target'], row['target'])
print(f"{target_label}: {row['count']:6d} ({pct:5.2f}%)")
# ATR-normalized return analysis
if "target_return" in df.columns and "atr" in df.columns:
print("\n--- RETURN ANALYSIS (M15 bars) ---")
# Calculate normalized returns
df_analysis = df.with_columns([
(pl.col("target_return") / pl.col("atr")).alias("norm_return")
])
total = df_analysis.shape[0]
# Different threshold analysis
thresholds = [0.1, 0.2, 0.3, 0.5, 0.7, 1.0]
print("\nBars with 3-bar returns > X*ATR:")
for thresh in thresholds:
count = (df_analysis["norm_return"] > thresh).sum()
pct = (count / total) * 100
print(f" > {thresh:.1f}*ATR: {count:5d} ({pct:5.2f}%)")
# Mean and median
mean_norm = df_analysis["norm_return"].mean()
median_norm = df_analysis["norm_return"].median()
print(f"\nMean normalized return: {mean_norm:.4f}")
print(f"Median normalized return: {median_norm:.4f}")
# Positive vs negative
positive = (df_analysis["target_return"] > 0).sum()
negative = (df_analysis["target_return"] < 0).sum()
print(f"\nPositive returns: {positive} ({(positive/total)*100:.2f}%)")
print(f"Negative returns: {negative} ({(negative/total)*100:.2f}%)")
# Check if H1 data exists
h1_cols = [col for col in df.columns if "_h1" in col.lower()]
print(f"\n--- H1 FEATURES IN DATASET ---")
print(f"H1 columns found: {len(h1_cols)}")
if h1_cols:
print("Sample H1 columns:")
for col in h1_cols[:10]:
print(f" {col}")
else:
print(f"\nData file not found at {data_file}")
# ============================================================================
# 4. PREDICTION CONSISTENCY
# ============================================================================
print_section("4. PREDICTION CONSISTENCY ANALYSIS")
# Check recent logs
recent_log = Path("logs/trading_bot_2026-02-09.log")
if recent_log.exists():
print(f"\nAnalyzing: {recent_log}")
signals = []
timestamps = []
with open(recent_log, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
if "ML Signal:" in line or "ML prediction:" in line:
# Extract signal
if "BUY" in line.upper():
signals.append("BUY")
elif "SELL" in line.upper():
signals.append("SELL")
elif "HOLD" in line.upper():
signals.append("HOLD")
# Try to extract timestamp
if "|" in line:
parts = line.split("|")
if len(parts) > 0:
timestamps.append(parts[0].strip())
if signals:
print(f"\n--- SIGNAL TRACKING ---")
print(f"Total signals logged: {len(signals)}")
# Count changes
changes = sum(1 for i in range(1, len(signals)) if signals[i] != signals[i-1])
print(f"Signal changes: {changes}")
print(f"Change rate: {(changes/len(signals))*100:.2f}%")
# Distribution
signal_counts = Counter(signals)
print(f"\nSignal distribution:")
for sig in ["BUY", "SELL", "HOLD"]:
count = signal_counts.get(sig, 0)
pct = (count / len(signals)) * 100
print(f" {sig}: {count} ({pct:.2f}%)")
# Recent signals
print(f"\n--- LAST 10 SIGNALS ---")
for i, sig in enumerate(signals[-10:], 1):
print(f"{i:2d}. {sig}")
else:
print(f"\nNo recent log at {recent_log}")
# ============================================================================
# 5. MODEL METRICS
# ============================================================================
print_section("5. CURRENT MODEL METRICS")
metrics_file = Path("data/model_metrics.json")
if metrics_file.exists():
with open(metrics_file, "r") as f:
metrics = json.load(f)
print("\n--- MODEL METRICS (from data/model_metrics.json) ---")
print(json.dumps(metrics, indent=2))
# ============================================================================
# 6. OVERFITTING ANALYSIS
# ============================================================================
print_section("6. OVERFITTING ANALYSIS")
# From training logs
print("\nFrom training_2026-02-04.log:")
print(" Initial training: Train AUC=0.8106, Test AUC=0.6553")
print(" Overfitting gap: 0.1553 (HIGH)")
print("\n Walk-forward average: Train AUC=0.8107, Test AUC=0.5722")
print(" Overfitting gap: 0.2385 (VERY HIGH)")
print("\nConclusion:")
print(" - Model shows significant overfitting")
print(" - Test AUC of 0.57-0.66 is barely better than random (0.50)")
print(" - High train AUC (0.81) but poor generalization")
# ============================================================================
# SUMMARY
# ============================================================================
print_section("CRITICAL FINDINGS")
print("\n1. MODEL PERFORMANCE:")
print(" - Test AUC: 0.5722 (walk-forward) - POOR")
print(" - Overfitting gap: 0.2385 - VERY HIGH")
print(" - Model barely better than random guessing")
print("\n2. FEATURE IMPORTANCE:")
if h1_in_top10:
print(f" - H1 features in top 10: {len(h1_in_top10)}")
else:
print(" - H1 features NOT in top 10 - Low predictive value")
print("\n3. DATA QUALITY:")
if data_file.exists():
print(f" - Training samples: {df.shape[0]}")
print(" - Target imbalance likely causing issues")
print(" - Most returns < 0.3*ATR (target too weak)")
print("\n4. RECOMMENDATIONS:")
print(" a. Current V2D model has POOR performance - needs replacement")
print(" b. H1 features show low importance - may not help")
print(" c. Consider new target variable (stronger signal)")
print(" d. Address class imbalance in training")
print(" e. Reduce model complexity to prevent overfitting")
print("\n" + "=" * 80)
+423
View File
@@ -0,0 +1,423 @@
#!/usr/bin/env python3
"""
Deep ML Model Analysis Script
Analyzes xgboost_model_v2d.pkl for performance, feature importance, and limitations
"""
import sys
import pickle
import json
from pathlib import Path
import numpy as np
import polars as pl
from collections import defaultdict
# Add src to path
sys.path.insert(0, str(Path(__file__).parent / "src"))
print("=" * 80)
print("ML MODEL DEEP DIVE ANALYSIS")
print("=" * 80)
# ============================================================================
# 1. LOAD AND INSPECT V2D MODEL
# ============================================================================
print("\n" + "=" * 80)
print("1. MODEL INSPECTION: xgboost_model_v2d.pkl")
print("=" * 80)
model_path = Path("models/xgboost_model_v2d.pkl")
if not model_path.exists():
print(f"ERROR: Model not found at {model_path}")
sys.exit(1)
with open(model_path, "rb") as f:
model_data = pickle.load(f)
print(f"\nModel pickle keys: {list(model_data.keys())}")
# Extract model and metadata
model = model_data.get("model")
metadata = model_data.get("metadata", {})
feature_names = model_data.get("feature_names", [])
print(f"\nModel type: {type(model)}")
print(f"Number of features: {len(feature_names)}")
print(f"\nMetadata keys: {list(metadata.keys())}")
# Display all metadata
print("\n--- MODEL METADATA ---")
for key, value in metadata.items():
if isinstance(value, (int, float, str, bool)):
print(f"{key}: {value}")
elif isinstance(value, dict):
print(f"{key}:")
for k, v in value.items():
print(f" {k}: {v}")
elif isinstance(value, (list, tuple)) and len(value) < 10:
print(f"{key}: {value}")
else:
print(f"{key}: {type(value)} (length={len(value) if hasattr(value, '__len__') else 'N/A'})")
# Extract key metrics
train_auc = metadata.get("train_auc", "N/A")
test_auc = metadata.get("test_auc", "N/A")
train_samples = metadata.get("train_samples", "N/A")
test_samples = metadata.get("test_samples", "N/A")
class_distribution = metadata.get("class_distribution", {})
print("\n--- KEY METRICS ---")
print(f"Training AUC: {train_auc}")
print(f"Test AUC: {test_auc}")
if isinstance(train_auc, float) and isinstance(test_auc, float):
overfitting_gap = train_auc - test_auc
print(f"Overfitting gap: {overfitting_gap:.4f} ({'HIGH' if overfitting_gap > 0.05 else 'NORMAL'})")
print(f"\nTraining samples: {train_samples}")
print(f"Test samples: {test_samples}")
print("\n--- CLASS DISTRIBUTION ---")
for class_name, count in class_distribution.items():
print(f"{class_name}: {count}")
# ============================================================================
# 2. FEATURE IMPORTANCE ANALYSIS
# ============================================================================
print("\n" + "=" * 80)
print("2. FEATURE IMPORTANCE RANKING (ALL FEATURES)")
print("=" * 80)
# Get feature importance from XGBoost
if hasattr(model, 'feature_importances_'):
importance_scores = model.feature_importances_
elif hasattr(model, 'get_score'):
# For XGBoost Booster
importance_dict = model.get_score(importance_type='gain')
importance_scores = [importance_dict.get(f"f{i}", 0) for i in range(len(feature_names))]
else:
print("WARNING: Could not extract feature importance from model")
importance_scores = [0] * len(feature_names)
# Create ranking
feature_importance = list(zip(feature_names, importance_scores))
feature_importance.sort(key=lambda x: x[1], reverse=True)
print(f"\nTotal features: {len(feature_importance)}")
# Categorize features
h1_features = []
m15_features = []
smc_features = []
other_features = []
for feat, score in feature_importance:
if "_h1" in feat.lower():
h1_features.append((feat, score))
elif "ob_" in feat or "fvg_" in feat or "bos" in feat or "choch" in feat:
smc_features.append((feat, score))
elif any(x in feat.lower() for x in ["rsi", "macd", "bb", "atr", "ema", "sma", "stoch"]):
m15_features.append((feat, score))
else:
other_features.append((feat, score))
print("\n--- TOP 20 FEATURES (BY IMPORTANCE) ---")
for i, (feat, score) in enumerate(feature_importance[:20], 1):
category = "H1" if "_h1" in feat.lower() else "SMC" if any(x in feat for x in ["ob_", "fvg_", "bos", "choch"]) else "M15"
print(f"{i:2d}. {feat:40s} {score:10.4f} [{category}]")
print("\n--- H1 FEATURES IN TOP 10 ---")
h1_in_top10 = [feat for feat, score in feature_importance[:10] if "_h1" in feat.lower()]
print(f"Count: {len(h1_in_top10)}")
for feat in h1_in_top10:
rank = [f for f, s in feature_importance].index(feat) + 1
score = [s for f, s in feature_importance if f == feat][0]
print(f" Rank #{rank}: {feat} (importance: {score:.4f})")
print("\n--- FEATURE CATEGORY SUMMARY ---")
print(f"H1 features: {len(h1_features)} total")
print(f"M15 technical features: {len(m15_features)} total")
print(f"SMC features: {len(smc_features)} total")
print(f"Other features: {len(other_features)} total")
# Calculate average importance by category
if h1_features:
avg_h1 = np.mean([s for f, s in h1_features])
print(f"\nAverage H1 importance: {avg_h1:.4f}")
if m15_features:
avg_m15 = np.mean([s for f, s in m15_features])
print(f"Average M15 importance: {avg_m15:.4f}")
if smc_features:
avg_smc = np.mean([s for f, s in smc_features])
print(f"Average SMC importance: {avg_smc:.4f}")
# ============================================================================
# 3. TRAINING LOGS ANALYSIS
# ============================================================================
print("\n" + "=" * 80)
print("3. TRAINING LOGS ANALYSIS")
print("=" * 80)
log_file = Path("logs/training_2026-02-04.log")
if log_file.exists():
print(f"\nReading: {log_file}")
with open(log_file, "r") as f:
log_content = f.read()
# Extract key training info
lines = log_content.split("\n")
# Look for training metrics
print("\n--- TRAINING METRICS FROM LOG ---")
for line in lines:
if any(kw in line.lower() for kw in ["auc", "accuracy", "precision", "recall", "f1", "samples", "features", "hyperparameter"]):
print(line.strip())
else:
print(f"\nNo training log found at {log_file}")
# ============================================================================
# 4. TARGET VARIABLE STATISTICS
# ============================================================================
print("\n" + "=" * 80)
print("4. TARGET VARIABLE STATISTICS")
print("=" * 80)
data_file = Path("data/training_data.parquet")
if data_file.exists():
print(f"\nLoading training data from {data_file}...")
df = pl.read_parquet(data_file)
print(f"Dataset shape: {df.shape}")
print(f"Columns: {df.columns}")
# Check if we have necessary columns
has_target = "target_signal" in df.columns or "target" in df.columns
has_atr = "atr" in df.columns
has_returns = any("return" in col.lower() for col in df.columns)
print(f"\nHas target column: {has_target}")
print(f"Has ATR column: {has_atr}")
print(f"Has return columns: {has_returns}")
if has_target:
target_col = "target_signal" if "target_signal" in df.columns else "target"
target_dist = df.group_by(target_col).agg(pl.count()).sort(target_col)
print(f"\n--- TARGET DISTRIBUTION ---")
print(target_dist)
# Calculate percentages
total = df.shape[0]
for row in target_dist.iter_rows(named=True):
pct = (row['count'] / total) * 100
print(f"{row[target_col]}: {row['count']} ({pct:.2f}%)")
# Analyze returns if available
return_cols = [col for col in df.columns if "return" in col.lower()]
if return_cols and has_atr:
print(f"\n--- RETURN ANALYSIS ---")
print(f"Available return columns: {return_cols}")
for ret_col in return_cols[:5]: # First 5 return columns
if ret_col in df.columns:
# Calculate stats
ret_mean = df[ret_col].mean()
ret_std = df[ret_col].std()
ret_positive = (df[ret_col] > 0).sum()
ret_negative = (df[ret_col] < 0).sum()
print(f"\n{ret_col}:")
print(f" Mean: {ret_mean:.6f}")
print(f" Std: {ret_std:.6f}")
print(f" Positive: {ret_positive} ({(ret_positive/total)*100:.2f}%)")
print(f" Negative: {ret_negative} ({(ret_negative/total)*100:.2f}%)")
# Calculate ATR-normalized returns
if "atr" in df.columns:
atr_mean = df["atr"].mean()
print(f" Mean ATR: {atr_mean:.4f}")
# Check different thresholds
thresh_03 = ((df[ret_col] / df["atr"]) > 0.3).sum()
thresh_05 = ((df[ret_col] / df["atr"]) > 0.5).sum()
thresh_10 = ((df[ret_col] / df["atr"]) > 1.0).sum()
print(f" Returns > 0.3×ATR: {thresh_03} ({(thresh_03/total)*100:.2f}%)")
print(f" Returns > 0.5×ATR: {thresh_05} ({(thresh_05/total)*100:.2f}%)")
print(f" Returns > 1.0×ATR: {thresh_10} ({(thresh_10/total)*100:.2f}%)")
else:
print(f"\nNo training data found at {data_file}")
# ============================================================================
# 5. FEATURE CORRELATION ANALYSIS
# ============================================================================
print("\n" + "=" * 80)
print("5. FEATURE CORRELATION ANALYSIS")
print("=" * 80)
if data_file.exists() and df is not None:
# Extract H1 and M15 features
h1_cols = [col for col in df.columns if "_h1" in col.lower()]
m15_cols = [col for col in df.columns if any(ind in col.lower() for ind in ["rsi", "macd", "bb", "atr", "ema", "sma", "stoch"])]
print(f"\nH1 columns found: {len(h1_cols)}")
print(f"M15 columns found: {len(m15_cols)}")
if h1_cols and m15_cols:
# Select numeric columns only
numeric_h1 = [col for col in h1_cols if df[col].dtype in [pl.Float64, pl.Float32, pl.Int64, pl.Int32]]
numeric_m15 = [col for col in m15_cols if df[col].dtype in [pl.Float64, pl.Float32, pl.Int64, pl.Int32]]
print(f"Numeric H1 columns: {len(numeric_h1)}")
print(f"Numeric M15 columns: {len(numeric_m15)}")
if numeric_h1 and numeric_m15:
# Calculate correlations between H1 and M15 features
print("\n--- HIGH CORRELATIONS BETWEEN H1 AND M15 FEATURES ---")
print("(Correlation > 0.7 suggests redundancy)")
high_corr_count = 0
for h1_col in numeric_h1[:10]: # Check first 10 H1 features
for m15_col in numeric_m15[:10]: # Against first 10 M15 features
try:
corr_df = df.select([h1_col, m15_col]).drop_nulls()
if corr_df.shape[0] > 0:
corr = corr_df.corr()[h1_col, m15_col]
if abs(corr) > 0.7:
print(f" {h1_col} <-> {m15_col}: {corr:.3f}")
high_corr_count += 1
except:
pass
if high_corr_count == 0:
print(" No high correlations found (good - features are independent)")
else:
print(f"\n Total high correlations: {high_corr_count}")
# Check H1 feature autocorrelation
print("\n--- H1 FEATURE INTERNAL CORRELATIONS ---")
if len(numeric_h1) >= 2:
high_h1_corr = 0
for i, col1 in enumerate(numeric_h1[:10]):
for col2 in numeric_h1[i+1:10]:
try:
corr_df = df.select([col1, col2]).drop_nulls()
if corr_df.shape[0] > 0:
corr = corr_df.corr()[col1, col2]
if abs(corr) > 0.8:
print(f" {col1} <-> {col2}: {corr:.3f}")
high_h1_corr += 1
except:
pass
if high_h1_corr == 0:
print(" No high internal correlations (good)")
else:
print("\nCannot perform correlation analysis - data not available")
# ============================================================================
# 6. PREDICTION CONSISTENCY CHECK
# ============================================================================
print("\n" + "=" * 80)
print("6. PREDICTION CONSISTENCY ANALYSIS")
print("=" * 80)
persistence_file = Path("data/signal_persistence.json")
if persistence_file.exists():
print(f"\nReading: {persistence_file}")
with open(persistence_file, "r") as f:
persistence_data = json.load(f)
print(f"Persistence data: {json.dumps(persistence_data, indent=2)}")
else:
print(f"\nNo persistence data found at {persistence_file}")
# Analyze recent logs for signal flipping
recent_log = Path("logs/trading_bot_2026-02-09.log")
if recent_log.exists():
print(f"\n--- ANALYZING RECENT SIGNALS FROM LOG ---")
print(f"Reading: {recent_log}")
signal_history = []
with open(recent_log, "r") as f:
for line in f:
if "ML Signal:" in line or "prediction:" in line.lower() or "signal=" in line.lower():
signal_history.append(line.strip())
print(f"\nFound {len(signal_history)} signal-related log entries")
if signal_history:
print("\n--- RECENT SIGNAL SAMPLES (Last 20) ---")
for entry in signal_history[-20:]:
print(f" {entry}")
# Count signal changes
signals = []
for entry in signal_history:
if "BUY" in entry.upper():
signals.append("BUY")
elif "SELL" in entry.upper():
signals.append("SELL")
elif "HOLD" in entry.upper():
signals.append("HOLD")
if len(signals) > 1:
changes = sum(1 for i in range(1, len(signals)) if signals[i] != signals[i-1])
print(f"\n--- SIGNAL STABILITY ---")
print(f"Total signals tracked: {len(signals)}")
print(f"Signal changes: {changes}")
print(f"Change rate: {(changes/len(signals))*100:.2f}%")
# Count by type
from collections import Counter
signal_counts = Counter(signals)
print(f"\nSignal distribution:")
for sig, count in signal_counts.items():
print(f" {sig}: {count} ({(count/len(signals))*100:.2f}%)")
else:
print(f"\nNo recent log found at {recent_log}")
# ============================================================================
# 7. MODEL METRICS FROM DATA
# ============================================================================
print("\n" + "=" * 80)
print("7. MODEL METRICS (from data/model_metrics.json)")
print("=" * 80)
metrics_file = Path("data/model_metrics.json")
if metrics_file.exists():
with open(metrics_file, "r") as f:
metrics = json.load(f)
print(json.dumps(metrics, indent=2))
else:
print(f"\nNo metrics file found at {metrics_file}")
# ============================================================================
# SUMMARY
# ============================================================================
print("\n" + "=" * 80)
print("ANALYSIS SUMMARY")
print("=" * 80)
print("\n1. MODEL PERFORMANCE:")
print(f" - Test AUC: {test_auc}")
print(f" - Overfitting: {'YES' if isinstance(train_auc, float) and isinstance(test_auc, float) and (train_auc - test_auc) > 0.05 else 'NO'}")
print("\n2. FEATURE IMPORTANCE:")
print(f" - H1 features in top 10: {len(h1_in_top10)}")
print(f" - Total H1 features: {len(h1_features)}")
if h1_in_top10:
print(f" - Highest ranked H1: {h1_in_top10[0]} (rank #{[f for f, s in feature_importance].index(h1_in_top10[0]) + 1})")
else:
print(" - No H1 features in top 10")
print("\n3. DATA QUALITY:")
if data_file.exists() and has_target:
print(f" - Training samples: {total}")
print(f" - Target balance: See distribution above")
else:
print(" - Could not analyze training data")
print("\n" + "=" * 80)
print("ANALYSIS COMPLETE")
print("=" * 80)
@@ -0,0 +1,5 @@
#39 H1 HMM Results
Generated: 2026-02-09 10:29:00.728793
Variant: M15_HMM
--- PERFORMANCE SUMMARY ---
+487
View File
@@ -0,0 +1,487 @@
#!/usr/bin/env python3
"""
Backtest #39: H1 HMM Regime Detector
Test moving HMM from M15 to H1 timeframe for more stable regime detection.
Hypothesis:
- H1 HMM will have 4-8x longer regime duration
- Fewer regime transitions = more stable risk management
- Better regime classification due to less noise
Expected Impact: +15-20% Sharpe improvement
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import polars as pl
import numpy as np
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import Optional
from loguru import logger
from src.mt5_connector import MT5Connector
from src.config import TradingConfig, get_config
from src.feature_eng import FeatureEngineer
from src.smc_polars import SMCAnalyzer
from src.regime_detector import MarketRegimeDetector
from backtests.ml_v2.ml_v2_model import TradingModelV2
from src.smart_risk_manager import SmartRiskManager
from src.session_filter import SessionFilter
from src.dynamic_confidence import DynamicConfidenceManager
# Import V2 features
from backtests.ml_v2.ml_v2_feature_eng import MLV2FeatureEngineer
@dataclass
class BacktestConfig:
"""Backtest configuration"""
symbol: str = "XAUUSD"
m15_bars: int = 5000
h1_bars: int = 1500
initial_balance: float = 500.0
results_dir: str = "backtests/39_h1_hmm_results"
class H1HMMBacktest:
"""Backtest with H1-based HMM regime detector"""
def __init__(self, config: BacktestConfig, use_h1_hmm: bool = True):
self.config = config
self.use_h1_hmm = use_h1_hmm
self.balance = config.initial_balance
self.equity = config.initial_balance
self.trades = []
self.equity_curve = []
# Trading config
self.trading_config = TradingConfig()
# Components
self.mt5 = None
self.features = FeatureEngineer()
self.smc = SMCAnalyzer()
self.ml_model = TradingModelV2()
self.fe_v2 = MLV2FeatureEngineer()
# Skip risk manager and session filter for backtest simplicity
self.risk_manager = None
self.session_filter = None
self.dynamic_conf = None
# Regime detectors - pass individual params (API changed)
self.regime_m15 = MarketRegimeDetector(
n_regimes=3,
lookback_periods=500,
retrain_frequency=20
)
self.regime_h1 = MarketRegimeDetector(
n_regimes=3,
lookback_periods=500,
retrain_frequency=20
)
# Stats
self.regime_changes_m15 = []
self.regime_changes_h1 = []
def connect_mt5(self):
"""Connect to MT5"""
logger.info("Connecting to MT5...")
config = get_config()
self.mt5 = MT5Connector(
login=config.mt5_login,
password=config.mt5_password,
server=config.mt5_server,
path=config.mt5_path
)
if not self.mt5.connect():
raise RuntimeError("Failed to connect to MT5")
logger.info("✓ MT5 connected")
def fetch_data(self):
"""Fetch M15 and H1 data"""
logger.info(f"Fetching {self.config.m15_bars} M15 bars...")
df_m15 = self.mt5.get_market_data(
self.config.symbol, "M15", self.config.m15_bars
)
logger.info(f"Fetching {self.config.h1_bars} H1 bars...")
df_h1 = self.mt5.get_market_data(
self.config.symbol, "H1", self.config.h1_bars
)
logger.info(f"✓ M15: {len(df_m15)} bars, H1: {len(df_h1)} bars")
return df_m15, df_h1
def prepare_data(self, df_m15: pl.DataFrame, df_h1: pl.DataFrame):
"""Calculate all features"""
logger.info("Calculating M15 features...")
df_m15 = self.features.calculate_all(df_m15, include_ml_features=True)
df_m15 = self.smc.calculate_all(df_m15)
logger.info("Calculating H1 features...")
df_h1 = self.features.calculate_all(df_h1, include_ml_features=True)
df_h1 = self.smc.calculate_all(df_h1)
logger.info("Adding V2 features...")
df_m15 = self.fe_v2.add_all_v2_features(df_m15, df_h1)
return df_m15, df_h1
def fit_regimes(self, df_m15: pl.DataFrame, df_h1: pl.DataFrame):
"""Train both M15 and H1 HMM models"""
logger.info("Training M15 HMM (baseline)...")
self.regime_m15.fit(df_m15.slice(0, 500))
logger.info("Training H1 HMM (test)...")
self.regime_h1.fit(df_h1.slice(0, 500))
logger.info("✓ Both HMM models fited")
def detect_regime(self, df_m15: pl.DataFrame, df_h1: pl.DataFrame, m15_idx: int):
"""Detect regime using M15 or H1 and return updated df with regime columns"""
if self.use_h1_hmm:
# Use H1 regime (map M15 index to H1)
h1_idx = m15_idx // 4 # 4 M15 bars = 1 H1 bar
if h1_idx >= len(df_h1):
h1_idx = len(df_h1) - 1
df_h1_slice = df_h1.slice(max(0, h1_idx - 100), h1_idx + 1)
df_h1_pred = self.regime_h1.predict(df_h1_slice)
regime_state = self.regime_h1.get_current_state(df_h1_pred)
# Track H1 regime changes
if hasattr(self, '_last_h1_regime') and self._last_h1_regime != regime_state.regime.value:
self.regime_changes_h1.append({
'm15_idx': m15_idx,
'old': self._last_h1_regime,
'new': regime_state.regime.value
})
self._last_h1_regime = regime_state.regime.value
else:
# Use M15 regime (baseline)
df_m15_slice = df_m15.slice(max(0, m15_idx - 100), m15_idx + 1)
df_m15_pred = self.regime_m15.predict(df_m15_slice)
regime_state = self.regime_m15.get_current_state(df_m15_pred)
# Track M15 regime changes
if hasattr(self, '_last_m15_regime') and self._last_m15_regime != regime_state.regime.value:
self.regime_changes_m15.append({
'm15_idx': m15_idx,
'old': self._last_m15_regime,
'new': regime_state.regime.value
})
self._last_m15_regime = regime_state.regime.value
return regime_state, df_m15_pred if not self.use_h1_hmm else df_h1_pred
def run_backtest(self, df_m15: pl.DataFrame, df_h1: pl.DataFrame):
"""Run backtest loop"""
logger.info(f"Running backtest ({'H1 HMM' if self.use_h1_hmm else 'M15 HMM'})...")
# Load ML model
self.ml_model.load("models/xgboost_model_v2d.pkl")
# Start from bar 600 (after fiting window)
for idx in range(600, len(df_m15)):
# Get current bar
row = df_m15.row(idx, named=True)
timestamp = row['time']
price = row['close']
# Detect regime
regime_state, df_regime_pred = self.detect_regime(df_m15, df_h1, idx)
# Add regime columns to current df slice for ML prediction
if 'regime' not in df_m15.columns:
# Initialize regime columns in df_m15
df_m15 = df_m15.with_columns([
pl.lit(0).alias('regime'),
pl.lit(0.0).alias('regime_confidence')
])
# Check if regime blocks trading
if regime_state.recommendation == "SLEEP":
continue
# Get ML prediction (use model's stored feature names)
df_slice = df_m15.slice(0, idx + 1)
if hasattr(self.ml_model, 'feature_names') and self.ml_model.feature_names:
feature_cols = self.ml_model.feature_names
else:
# Fallback: filter out OHLC and intermediate columns
exclude_cols = {'time', 'open', 'high', 'low', 'close', 'spread', 'real_volume', 'volume',
'swing_high_level', 'swing_low_level', 'last_swing_high', 'last_swing_low',
'fvg_top', 'fvg_bottom', 'fvg_mid', 'ob_top', 'ob_bottom'}
feature_cols = [c for c in df_slice.columns if c not in exclude_cols]
ml_pred = self.ml_model.predict(df_slice, feature_cols)
if ml_pred.signal == "HOLD":
continue
# Skip session check for simplicity
# session_ok, _, session_mult = self.session_filter.can_trade(timestamp)
# if not session_ok:
# continue
# Entry filters (simplified)
if ml_pred.confidence < 0.50:
continue
# SMC signal
smc_signal = row.get('ob', 0)
if smc_signal == 0:
continue
if (ml_pred.signal == "BUY" and smc_signal < 0) or \
(ml_pred.signal == "SELL" and smc_signal > 0):
continue
# Execute trade (simplified)
direction = ml_pred.signal
entry_price = price
# Calculate SL/TP (simplified)
atr = row.get('atr', 12.0)
if direction == "BUY":
sl = entry_price - (2.0 * atr)
tp = entry_price + (3.0 * atr)
else:
sl = entry_price + (2.0 * atr)
tp = entry_price - (3.0 * atr)
# Simulate trade (find exit)
exit_price = None
exit_reason = None
exit_idx = None
for future_idx in range(idx + 1, min(idx + 100, len(df_m15))):
future_row = df_m15.row(future_idx, named=True)
future_high = future_row['high']
future_low = future_row['low']
if direction == "BUY":
if future_low <= sl:
exit_price = sl
exit_reason = "SL"
exit_idx = future_idx
break
elif future_high >= tp:
exit_price = tp
exit_reason = "TP"
exit_idx = future_idx
break
else: # SELL
if future_high >= sl:
exit_price = sl
exit_reason = "SL"
exit_idx = future_idx
break
elif future_low <= tp:
exit_price = tp
exit_reason = "TP"
exit_idx = future_idx
break
if exit_price is None:
# No exit found, close at last bar
exit_price = df_m15.row(min(idx + 99, len(df_m15) - 1), named=True)['close']
exit_reason = "EOD"
exit_idx = min(idx + 99, len(df_m15) - 1)
# Calculate P&L
if direction == "BUY":
profit = exit_price - entry_price
else:
profit = entry_price - exit_price
profit_usd = profit * 0.01 # 0.01 lot
# Update balance
self.balance += profit_usd
self.equity = self.balance
# Log trade
self.trades.append({
'entry_time': timestamp,
'exit_time': df_m15.row(exit_idx, named=True)['time'],
'direction': direction,
'entry_price': entry_price,
'exit_price': exit_price,
'profit_usd': profit_usd,
'exit_reason': exit_reason,
'regime': regime_state.regime.value,
'ml_conf': ml_pred.confidence,
})
# Record equity
self.equity_curve.append({
'time': df_m15.row(exit_idx, named=True)['time'],
'equity': self.equity
})
logger.info(f"✓ Backtest complete: {len(self.trades)} trades")
def calculate_metrics(self):
"""Calculate performance metrics"""
if not self.trades:
return {}
df_trades = pl.DataFrame(self.trades)
total_trades = len(self.trades)
wins = df_trades.filter(pl.col('profit_usd') > 0).shape[0]
losses = df_trades.filter(pl.col('profit_usd') < 0).shape[0]
win_rate = wins / total_trades * 100 if total_trades > 0 else 0
net_profit = df_trades['profit_usd'].sum()
gross_profit = df_trades.filter(pl.col('profit_usd') > 0)['profit_usd'].sum()
gross_loss = abs(df_trades.filter(pl.col('profit_usd') < 0)['profit_usd'].sum())
profit_factor = gross_profit / gross_loss if gross_loss > 0 else 0
# Drawdown
equity_curve = [self.config.initial_balance] + [e['equity'] for e in self.equity_curve]
running_max = np.maximum.accumulate(equity_curve)
drawdown = running_max - equity_curve
max_dd = np.max(drawdown)
max_dd_pct = max_dd / self.config.initial_balance * 100
# Sharpe ratio (annualized)
returns = df_trades['profit_usd'].to_numpy()
if len(returns) > 1 and np.std(returns) > 0:
avg_return = np.mean(returns)
std_return = np.std(returns)
# Assume ~5 trades/day, 252 trading days/year
sharpe = (avg_return / std_return) * np.sqrt(5 * 252)
else:
sharpe = 0
# Regime changes
regime_changes_m15 = len(self.regime_changes_m15)
regime_changes_h1 = len(self.regime_changes_h1)
return {
'total_trades': total_trades,
'wins': wins,
'losses': losses,
'win_rate': win_rate,
'net_profit': net_profit,
'gross_profit': gross_profit,
'gross_loss': gross_loss,
'profit_factor': profit_factor,
'max_dd': max_dd,
'max_dd_pct': max_dd_pct,
'sharpe': sharpe,
'final_balance': self.balance,
'regime_changes_m15': regime_changes_m15,
'regime_changes_h1': regime_changes_h1,
}
def save_results(self, variant_name: str, metrics: dict):
"""Save results to file"""
Path(self.config.results_dir).mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_file = Path(self.config.results_dir) / f"h1_hmm_{timestamp}.log"
with open(log_file, 'w') as f:
f.write(f"#39 H1 HMM Results\n")
f.write(f"Generated: {datetime.now()}\n")
f.write(f"Variant: {variant_name}\n\n")
f.write("--- PERFORMANCE SUMMARY ---\n")
f.write(f" Total Trades: {metrics['total_trades']}\n")
f.write(f" Win Rate: {metrics['win_rate']:.1f}%\n")
f.write(f" Net PnL: ${metrics['net_profit']:.2f}\n")
f.write(f" Profit Factor: {metrics['profit_factor']:.2f}\n")
f.write(f" Max Drawdown: ${metrics['max_dd']:.2f} ({metrics['max_dd_pct']:.1f}%)\n")
f.write(f" Sharpe Ratio: {metrics['sharpe']:.2f}\n")
f.write(f" Final Balance: ${metrics['final_balance']:.2f}\n\n")
f.write("--- REGIME STABILITY ---\n")
f.write(f" M15 Regime Changes: {metrics['regime_changes_m15']}\n")
f.write(f" H1 Regime Changes: {metrics['regime_changes_h1']}\n")
if metrics['regime_changes_h1'] > 0:
improvement = (metrics['regime_changes_m15'] - metrics['regime_changes_h1']) / metrics['regime_changes_m15'] * 100
f.write(f" Improvement: {improvement:.1f}% fewer changes\n\n")
f.write("--- TRADE LOG ---\n")
f.write(f"{'#':>4} {'Entry Time':>19} {'Dir':>4} {'Entry':>8} {'Exit':>8} {'P/L':>7} {'Result':>6} {'Exit_Reason':>12} {'Regime':>15} {'ML_Conf':>7}\n")
f.write("-" * 120 + "\n")
for i, trade in enumerate(self.trades, 1):
result = "WIN" if trade['profit_usd'] > 0 else "LOSS"
f.write(f"{i:4d} {trade['entry_time']:%Y-%m-%d %H:%M:%S} "
f"{trade['direction']:>4} {trade['entry_price']:8.2f} {trade['exit_price']:8.2f} "
f"{trade['profit_usd']:7.2f} {result:>6} {trade['exit_reason']:>12} "
f"{trade['regime']:>15} {trade['ml_conf']:7.3f}\n")
logger.info(f"✓ Results saved to {log_file}")
def main():
"""Run both M15 and H1 HMM backtests"""
config = BacktestConfig()
# Baseline: M15 HMM
logger.info("=" * 80)
logger.info("BASELINE: M15 HMM")
logger.info("=" * 80)
bt_m15 = H1HMMBacktest(config, use_h1_hmm=False)
bt_m15.connect_mt5()
df_m15, df_h1 = bt_m15.fetch_data()
df_m15, df_h1 = bt_m15.prepare_data(df_m15, df_h1)
bt_m15.fit_regimes(df_m15, df_h1)
bt_m15.run_backtest(df_m15, df_h1)
metrics_m15 = bt_m15.calculate_metrics()
bt_m15.save_results("M15_HMM", metrics_m15)
logger.info(f"\nBASELINE Results:")
logger.info(f" Trades: {metrics_m15['total_trades']}, WR: {metrics_m15['win_rate']:.1f}%, "
f"PnL: ${metrics_m15['net_profit']:.2f}, Sharpe: {metrics_m15['sharpe']:.2f}")
logger.info(f" M15 Regime Changes: {metrics_m15['regime_changes_m15']}")
# Test: H1 HMM
logger.info("\n" + "=" * 80)
logger.info("TEST: H1 HMM")
logger.info("=" * 80)
bt_h1 = H1HMMBacktest(config, use_h1_hmm=True)
bt_h1.connect_mt5()
# Reuse same data
bt_h1.fit_regimes(df_m15, df_h1)
bt_h1.run_backtest(df_m15, df_h1)
metrics_h1 = bt_h1.calculate_metrics()
bt_h1.save_results("H1_HMM", metrics_h1)
logger.info(f"\nH1 HMM Results:")
logger.info(f" Trades: {metrics_h1['total_trades']}, WR: {metrics_h1['win_rate']:.1f}%, "
f"PnL: ${metrics_h1['net_profit']:.2f}, Sharpe: {metrics_h1['sharpe']:.2f}")
logger.info(f" H1 Regime Changes: {metrics_h1['regime_changes_h1']}")
# Comparison
logger.info("\n" + "=" * 80)
logger.info("COMPARISON")
logger.info("=" * 80)
pnl_diff = metrics_h1['net_profit'] - metrics_m15['net_profit']
sharpe_diff = metrics_h1['sharpe'] - metrics_m15['sharpe']
regime_reduction = (metrics_m15['regime_changes_m15'] - metrics_h1['regime_changes_h1']) / metrics_m15['regime_changes_m15'] * 100 if metrics_m15['regime_changes_m15'] > 0 else 0
logger.info(f"PnL Difference: ${pnl_diff:+.2f} ({pnl_diff/metrics_m15['net_profit']*100:+.1f}%)")
logger.info(f"Sharpe Difference: {sharpe_diff:+.2f} ({sharpe_diff/metrics_m15['sharpe']*100:+.1f}%)")
logger.info(f"Regime Stability: {regime_reduction:.1f}% fewer regime changes")
bt_m15.mt5.disconnect()
bt_h1.mt5.disconnect()
if __name__ == "__main__":
main()
+4 -4
View File
@@ -1,6 +1,6 @@
date:2026-02-09
daily_loss:0.0
daily_profit:7.17
daily_loss:18.77
daily_profit:22.49
consecutive_losses:0
total_loss:0
saved_at:2026-02-09T07:08:09.193663+07:00
total_loss:3.45
saved_at:2026-02-09T10:34:43.763326+07:00
+150
View File
@@ -0,0 +1,150 @@
================================================================================
H1 FEATURE DEEP ANALYSIS
================================================================================
Total features in model: 76
H1 features found: 9
--- ALL H1 FEATURES ---
Rank #68: h1_atr_ratio importance= 0.0000
Rank #66: h1_ema20 importance= 0.0000
Rank # 7: h1_ema20_distance importance= 95.8865
Rank #67: h1_fvg_active importance= 0.0000
Rank #10: h1_market_structure importance= 61.8312
Rank #16: h1_ob_proximity importance= 33.1670
Rank # 6: h1_rsi importance= 112.2508
Rank #17: h1_swing_proximity importance= 30.4503
Rank #12: h1_trend_strength importance= 45.8723
--- TOP 10 H1 FEATURES (by importance) ---
1. Rank # 6: h1_rsi 112.2508
2. Rank # 7: h1_ema20_distance 95.8865
3. Rank # 10: h1_market_structure 61.8312
4. Rank # 12: h1_trend_strength 45.8723
5. Rank # 16: h1_ob_proximity 33.1670
6. Rank # 17: h1_swing_proximity 30.4503
7. Rank # 66: h1_ema20 0.0000
8. Rank # 67: h1_fvg_active 0.0000
9. Rank # 68: h1_atr_ratio 0.0000
--- H1 FEATURE STATISTICS ---
Total H1 features: 9
Mean importance: 42.1620
Median importance: 33.1670
Max importance: 112.2508
Min importance: 0.0000
H1 features in top 10: 3
H1 features in top 20: 6
H1 features in top 30: 6
================================================================================
ALL FEATURE NAMES IN MODEL
================================================================================
1. rsi 36.2503
2. atr 0.0000
3. atr_percent 0.0000
4. macd 65.5672
5. macd_signal 75.9652
6. macd_histogram 0.0000
7. bb_middle 0.0000
8. bb_upper 0.0000
9. bb_lower 0.0000
10. bb_width 0.0000
11. bb_percent_b 21.0445
12. ema_9 0.0000
13. ema_21 21.6908
14. ema_cross_bull 0.0000
15. ema_cross_bear 0.0000
16. volume_sma 0.0000
17. volume_ratio 25.7497
18. volume_increasing 0.0000
19. high_volume 0.0000
20. returns_1 167.9189
21. returns_5 2.9630
22. returns_20 11.5534
23. log_returns 186.0548
24. price_position 39.1339
25. dist_from_sma_20 11.1024
26. volatility_20 0.0000
27. normalized_range 0.0000
28. avg_normalized_range 0.0000
29. close_lag_1 18.1662
30. close_lag_2 18.1221
31. close_lag_3 0.0000
32. close_lag_5 24.4038
33. higher_high 0.0000
34. lower_low 0.0000
35. hh_count_5 0.0000
36. ll_count_5 0.0000
37. hour 14.3622
38. weekday 0.0000
39. london_session 0.0000
40. ny_session 0.0000
41. swing_high 0.0000
42. swing_low 0.0000
43. is_fvg_bull 23.1050
44. is_fvg_bear 0.0000
45. fvg_signal 0.0000
46. ob 417.3543
47. ob_mitigated 127.2658
48. bos 0.0000
49. choch 0.0000
50. market_structure 0.0000
51. regime 0.0000
52. regime_confidence 0.0000
53. h1_ema20 0.0000
54. h1_market_structure 61.8312
55. h1_ema20_distance 95.8865
56. h1_trend_strength 45.8723
57. h1_swing_proximity 30.4503
58. h1_fvg_active 0.0000
59. h1_ob_proximity 33.1670
60. h1_atr_ratio 0.0000
61. h1_rsi 112.2508
62. fvg_gap_size_atr 14.4616
63. fvg_age_bars 0.0000
64. ob_width_atr 40.7235
65. ob_distance_atr 119.3701
66. bos_recency 0.0000
67. confluence_score 0.0000
68. swing_distance_atr 0.0000
69. regime_duration_bars 0.0000
70. regime_transition_prob 0.0000
71. volatility_zscore 19.7360
72. crisis_proximity 15.9215
73. wick_ratio 0.0000
74. body_ratio 0.0000
75. gap_from_prev_close 7.8998
76. consecutive_direction 57.4307
================================================================================
CHECKING TRAINING DATA FOR H1 FEATURES
================================================================================
Dataset columns: 72
H1 columns in dataset: 0
NO H1 COLUMNS IN TRAINING DATA!
Looking for potential H1-related columns:
hour
================================================================================
CHECKING FEATURE ENGINEERING CODE
================================================================================
NO H1 references found in feature_eng.py
================================================================================
CONCLUSION
================================================================================
Traceback (most recent call last):
File "C:\Users\Administrator\Videos\Smart Automatic Trading BOT + AI\analyze_h1_features.py", line 151, in <module>
print(f"\n\u2713 Model HAS {len(h1_features)} H1 features")
~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Python313\Lib\encodings\cp1252.py", line 19, in encode
return codecs.charmap_encode(input,self.errors,encoding_table)[0]
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnicodeEncodeError: 'charmap' codec can't encode character '\u2713' in position 2: character maps to <undefined>
+266
View File
@@ -0,0 +1,266 @@
================================================================================
ML MODEL DEEP DIVE ANALYSIS
================================================================================
================================================================================
1. MODEL INSPECTION: xgboost_model_v2d.pkl
================================================================================
Model pickle structure:
model_type: ModelType
xgb_model: Booster
lgb_model: NoneType
feature_names: list (length=76)
confidence_threshold: float
xgb_params: dict (length=13)
lgb_params: dict (length=12)
feature_importance: dict (length=76)
train_metrics: dict (length=4)
fitted: bool
XGBoost model: <class 'xgboost.core.Booster'>
LightGBM model: <class 'NoneType'>
Total features: 76
--- TRAINING METRICS ---
xgb_train_score: 0.7385315785647933
xgb_test_score: 0.7338658466928052
train_samples: 36407
test_samples: 9052
--- XGBOOST PARAMETERS ---
objective: binary:logistic
eval_metric: auc
max_depth: 3
learning_rate: 0.05
tree_method: hist
device: cpu
min_child_weight: 10
subsample: 0.7
colsample_bytree: 0.6
reg_alpha: 1.0
reg_lambda: 5.0
gamma: 1.0
max_delta_step: 1
================================================================================
2. FEATURE IMPORTANCE RANKING
================================================================================
Total features with importance: 76
--- TOP 30 FEATURES ---
1. ob 417.354340 [M15]
2. log_returns 186.054825 [M15]
3. returns_1 167.918945 [M15]
4. ob_mitigated 127.265808 [SMC]
5. ob_distance_atr 119.370117 [SMC]
6. h1_rsi 112.250816 [M15]
7. h1_ema20_distance 95.886513 [M15]
8. macd_signal 75.965248 [M15]
9. macd 65.567200 [M15]
10. h1_market_structure 61.831249 [M15]
11. consecutive_direction 57.430664 [M15]
12. h1_trend_strength 45.872288 [M15]
13. ob_width_atr 40.723549 [SMC]
14. price_position 39.133850 [M15]
15. rsi 36.250340 [M15]
16. h1_ob_proximity 33.167027 [SMC]
17. h1_swing_proximity 30.450262 [M15]
18. volume_ratio 25.749680 [M15]
19. close_lag_5 24.403849 [M15]
20. is_fvg_bull 23.105017 [SMC]
21. ema_21 21.690825 [M15]
22. bb_percent_b 21.044502 [M15]
23. volatility_zscore 19.736000 [M15]
24. close_lag_1 18.166227 [M15]
25. close_lag_2 18.122101 [M15]
26. crisis_proximity 15.921524 [M15]
27. fvg_gap_size_atr 14.461624 [SMC]
28. hour 14.362236 [M15]
29. returns_20 11.553368 [M15]
30. dist_from_sma_20 11.102405 [M15]
--- H1 FEATURES IN TOP 10 ---
Count: 0
--- FEATURE CATEGORY SUMMARY ---
H1 features: 0
M15 technical features: 22
SMC features: 13
Average M15 importance: 19.988993
Average SMC importance: 27.545626
================================================================================
3. TARGET VARIABLE STATISTICS
================================================================================
Loading: data\training_data.parquet
Dataset shape: (8000, 72)
--- TARGET DISTRIBUTION ---
None: 1 ( 0.01%)
SELL: 3749 (46.86%)
HOLD: 4250 (53.12%)
--- RETURN ANALYSIS (M15 bars) ---
Bars with 3-bar returns > X*ATR:
> 0.1*ATR: 0 ( 0.00%)
> 0.2*ATR: 0 ( 0.00%)
> 0.3*ATR: 0 ( 0.00%)
> 0.5*ATR: 0 ( 0.00%)
> 0.7*ATR: 0 ( 0.00%)
> 1.0*ATR: 0 ( 0.00%)
Mean normalized return: 0.0000
Median normalized return: 0.0000
Positive returns: 4250 (53.12%)
Negative returns: 3744 (46.80%)
--- H1 FEATURES IN DATASET ---
H1 columns found: 0
================================================================================
4. PREDICTION CONSISTENCY ANALYSIS
================================================================================
Analyzing: logs\trading_bot_2026-02-09.log
================================================================================
5. CURRENT MODEL METRICS
================================================================================
--- MODEL METRICS (from data/model_metrics.json) ---
{
"featureImportance": [
{
"name": "ob",
"importance": 0.2126
},
{
"name": "log_returns",
"importance": 0.0948
},
{
"name": "returns_1",
"importance": 0.0856
},
{
"name": "ob_mitigated",
"importance": 0.0648
},
{
"name": "ob_distance_atr",
"importance": 0.0608
},
{
"name": "h1_rsi",
"importance": 0.0572
},
{
"name": "h1_ema20_distance",
"importance": 0.0489
},
{
"name": "macd_signal",
"importance": 0.0387
},
{
"name": "macd",
"importance": 0.0334
},
{
"name": "h1_market_structure",
"importance": 0.0315
},
{
"name": "consecutive_direction",
"importance": 0.0293
},
{
"name": "h1_trend_strength",
"importance": 0.0234
},
{
"name": "ob_width_atr",
"importance": 0.0207
},
{
"name": "price_position",
"importance": 0.0199
},
{
"name": "rsi",
"importance": 0.0185
},
{
"name": "h1_ob_proximity",
"importance": 0.0169
},
{
"name": "h1_swing_proximity",
"importance": 0.0155
},
{
"name": "volume_ratio",
"importance": 0.0131
},
{
"name": "close_lag_5",
"importance": 0.0124
},
{
"name": "is_fvg_bull",
"importance": 0.0118
}
],
"trainAuc": 0.7385315785647933,
"testAuc": 0.7338658466928052,
"sampleCount": 45459,
"updatedAt": "2026-02-09T09:01:15.440605+07:00"
}
================================================================================
6. OVERFITTING ANALYSIS
================================================================================
From training_2026-02-04.log:
Initial training: Train AUC=0.8106, Test AUC=0.6553
Overfitting gap: 0.1553 (HIGH)
Walk-forward average: Train AUC=0.8107, Test AUC=0.5722
Overfitting gap: 0.2385 (VERY HIGH)
Conclusion:
- Model shows significant overfitting
- Test AUC of 0.57-0.66 is barely better than random (0.50)
- High train AUC (0.81) but poor generalization
================================================================================
CRITICAL FINDINGS
================================================================================
1. MODEL PERFORMANCE:
- Test AUC: 0.5722 (walk-forward) - POOR
- Overfitting gap: 0.2385 - VERY HIGH
- Model barely better than random guessing
2. FEATURE IMPORTANCE:
- H1 features NOT in top 10 - Low predictive value
3. DATA QUALITY:
- Training samples: 8000
- Target imbalance likely causing issues
- Most returns < 0.3*ATR (target too weak)
4. RECOMMENDATIONS:
a. Current V2D model has POOR performance - needs replacement
b. H1 features show low importance - may not help
c. Consider new target variable (stronger signal)
d. Address class imbalance in training
e. Reduce model complexity to prevent overfitting
================================================================================