314 lines
11 KiB
Python
314 lines
11 KiB
Python
|
|
#!/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)
|