- Docker: Delete 23 deprecated Dockerfiles, fix CI/CD to use Dockerfile.foxhunt-build - Config: Remove 36 .env files, keep 4 essential, delete config/environments/ - Docs: Archive 614 Wave D files to docs/archive/wave_d/, 95% reduction in root - Scripts: Delete 56 deprecated scripts, keep 58 production-critical (49% reduction) - Python: Organize 37 scripts into scripts/python/ subdirectories, delete ml/python/ - Build: Remove 1GB artifacts, delete old venvs, clean Python cache from git - Migrations: Delete deprecated directory (4,432 lines), remove duplicate database/migrations/ - Infrastructure: Delete deployment/ (61 files), docs/scripts/ (8 files) Total impact: ~2,500 files cleaned, 750MB+ space freed, zero production impact All deleted scripts backed up to archives. runpod/ and tests/runpod/ preserved. data_acquisition_service retained per user request.
335 lines
16 KiB
Python
335 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Deep Analysis of Comprehensive Backtest Results
|
|
Analyzes 100 checkpoint backtest data for trading insights
|
|
"""
|
|
|
|
import json
|
|
import statistics
|
|
from datetime import datetime
|
|
from collections import defaultdict
|
|
|
|
# Load the backtest results
|
|
with open('results/comprehensive_backtest_results_20251014_143309.json', 'r') as f:
|
|
results = json.load(f)
|
|
|
|
# Categorization functions
|
|
def categorize_by_performance(result):
|
|
"""Categorize models as High/Medium/Low performers"""
|
|
sharpe = result['sharpe_ratio']
|
|
win_rate = result['win_rate']
|
|
pnl = result['total_pnl']
|
|
|
|
# High performer: Sharpe > 5, Win Rate > 55%, PnL > 50
|
|
if sharpe > 5 and win_rate > 55 and pnl > 50:
|
|
return 'High'
|
|
# Low performer: Sharpe < 0 or Win Rate < 35% or PnL < -50
|
|
elif sharpe < 0 or win_rate < 35 or pnl < -50:
|
|
return 'Low'
|
|
else:
|
|
return 'Medium'
|
|
|
|
def analyze_by_model_type():
|
|
"""Analyze performance by model type (DQN vs PPO)"""
|
|
dqn_results = [r for r in results if r['model_type'] == 'DQN']
|
|
ppo_results = [r for r in results if r['model_type'] == 'PPO']
|
|
|
|
print("\n" + "="*80)
|
|
print("MODEL TYPE ANALYSIS")
|
|
print("="*80)
|
|
|
|
for model_type, model_results in [('DQN', dqn_results), ('PPO', ppo_results)]:
|
|
# Filter out models with no trades
|
|
active_models = [r for r in model_results if r['total_trades'] > 0]
|
|
|
|
if not active_models:
|
|
print(f"\n{model_type}: No active models")
|
|
continue
|
|
|
|
avg_sharpe = statistics.mean([r['sharpe_ratio'] for r in active_models])
|
|
avg_win_rate = statistics.mean([r['win_rate'] for r in active_models])
|
|
avg_pnl = statistics.mean([r['total_pnl'] for r in active_models])
|
|
avg_trades = statistics.mean([r['total_trades'] for r in active_models])
|
|
|
|
profitable_models = len([r for r in active_models if r['total_pnl'] > 0])
|
|
|
|
print(f"\n{model_type} Models:")
|
|
print(f" Total Models: {len(model_results)} ({len(active_models)} active)")
|
|
print(f" Profitable Models: {profitable_models}/{len(active_models)} ({profitable_models/len(active_models)*100:.1f}%)")
|
|
print(f" Average Sharpe Ratio: {avg_sharpe:.2f}")
|
|
print(f" Average Win Rate: {avg_win_rate:.2f}%")
|
|
print(f" Average PnL: ${avg_pnl:.2f}")
|
|
print(f" Average Total Trades: {avg_trades:.0f}")
|
|
|
|
def analyze_by_epoch():
|
|
"""Analyze how performance changes with training epochs"""
|
|
print("\n" + "="*80)
|
|
print("EPOCH PROGRESSION ANALYSIS")
|
|
print("="*80)
|
|
|
|
for model_type in ['DQN', 'PPO']:
|
|
model_results = [r for r in results if r['model_type'] == model_type]
|
|
|
|
# Group by epoch ranges
|
|
epoch_ranges = {
|
|
'Early (10-100)': [r for r in model_results if 10 <= r['epoch'] <= 100 and r['total_trades'] > 0],
|
|
'Mid (110-300)': [r for r in model_results if 110 <= r['epoch'] <= 300 and r['total_trades'] > 0],
|
|
'Late (310-500)': [r for r in model_results if 310 <= r['epoch'] <= 500 and r['total_trades'] > 0]
|
|
}
|
|
|
|
print(f"\n{model_type} Epoch Progression:")
|
|
for range_name, range_results in epoch_ranges.items():
|
|
if not range_results:
|
|
continue
|
|
|
|
avg_sharpe = statistics.mean([r['sharpe_ratio'] for r in range_results])
|
|
avg_win_rate = statistics.mean([r['win_rate'] for r in range_results])
|
|
avg_pnl = statistics.mean([r['total_pnl'] for r in range_results])
|
|
profitable = len([r for r in range_results if r['total_pnl'] > 0])
|
|
|
|
print(f" {range_name}: {len(range_results)} models, {profitable} profitable")
|
|
print(f" Avg Sharpe: {avg_sharpe:.2f}, Win Rate: {avg_win_rate:.1f}%, PnL: ${avg_pnl:.2f}")
|
|
|
|
def analyze_trade_characteristics():
|
|
"""Analyze trade patterns and characteristics"""
|
|
print("\n" + "="*80)
|
|
print("TRADE CHARACTERISTICS ANALYSIS")
|
|
print("="*80)
|
|
|
|
active_models = [r for r in results if r['total_trades'] > 10]
|
|
|
|
# Trade frequency analysis
|
|
high_freq = [r for r in active_models if r['trade_frequency'] > 50]
|
|
low_freq = [r for r in active_models if r['trade_frequency'] < 20]
|
|
|
|
print(f"\nTrade Frequency Impact:")
|
|
print(f" High Frequency (>50 trades/day): {len(high_freq)} models")
|
|
if high_freq:
|
|
avg_pnl_high = statistics.mean([r['total_pnl'] for r in high_freq])
|
|
avg_sharpe_high = statistics.mean([r['sharpe_ratio'] for r in high_freq])
|
|
profitable_high = len([r for r in high_freq if r['total_pnl'] > 0])
|
|
print(f" Profitable: {profitable_high}/{len(high_freq)} ({profitable_high/len(high_freq)*100:.1f}%)")
|
|
print(f" Avg PnL: ${avg_pnl_high:.2f}, Avg Sharpe: {avg_sharpe_high:.2f}")
|
|
|
|
print(f" Low Frequency (<20 trades/day): {len(low_freq)} models")
|
|
if low_freq:
|
|
avg_pnl_low = statistics.mean([r['total_pnl'] for r in low_freq])
|
|
avg_sharpe_low = statistics.mean([r['sharpe_ratio'] for r in low_freq])
|
|
profitable_low = len([r for r in low_freq if r['total_pnl'] > 0])
|
|
print(f" Profitable: {profitable_low}/{len(low_freq)} ({profitable_low/len(low_freq)*100:.1f}%)")
|
|
print(f" Avg PnL: ${avg_pnl_low:.2f}, Avg Sharpe: {avg_sharpe_low:.2f}")
|
|
|
|
# Hold time analysis
|
|
short_hold = [r for r in active_models if r['avg_trade_duration'] < 20]
|
|
long_hold = [r for r in active_models if r['avg_trade_duration'] > 60]
|
|
|
|
print(f"\nAverage Hold Time Impact:")
|
|
print(f" Short Hold (<20 bars): {len(short_hold)} models")
|
|
if short_hold:
|
|
avg_pnl_short = statistics.mean([r['total_pnl'] for r in short_hold])
|
|
avg_win_rate_short = statistics.mean([r['win_rate'] for r in short_hold])
|
|
profitable_short = len([r for r in short_hold if r['total_pnl'] > 0])
|
|
print(f" Profitable: {profitable_short}/{len(short_hold)} ({profitable_short/len(short_hold)*100:.1f}%)")
|
|
print(f" Avg PnL: ${avg_pnl_short:.2f}, Win Rate: {avg_win_rate_short:.1f}%")
|
|
|
|
print(f" Long Hold (>60 bars): {len(long_hold)} models")
|
|
if long_hold:
|
|
avg_pnl_long = statistics.mean([r['total_pnl'] for r in long_hold])
|
|
avg_win_rate_long = statistics.mean([r['win_rate'] for r in long_hold])
|
|
profitable_long = len([r for r in long_hold if r['total_pnl'] > 0])
|
|
print(f" Profitable: {profitable_long}/{len(long_hold)} ({profitable_long/len(long_hold)*100:.1f}%)")
|
|
print(f" Avg PnL: ${avg_pnl_long:.2f}, Win Rate: {avg_win_rate_long:.1f}%")
|
|
|
|
# Win rate distribution
|
|
print(f"\nWin Rate Distribution:")
|
|
win_rate_bins = {
|
|
'<30%': [r for r in active_models if r['win_rate'] < 30],
|
|
'30-45%': [r for r in active_models if 30 <= r['win_rate'] < 45],
|
|
'45-55%': [r for r in active_models if 45 <= r['win_rate'] < 55],
|
|
'55-65%': [r for r in active_models if 55 <= r['win_rate'] < 65],
|
|
'>65%': [r for r in active_models if r['win_rate'] >= 65]
|
|
}
|
|
|
|
for bin_name, bin_results in win_rate_bins.items():
|
|
if not bin_results:
|
|
continue
|
|
avg_pnl = statistics.mean([r['total_pnl'] for r in bin_results])
|
|
profitable = len([r for r in bin_results if r['total_pnl'] > 0])
|
|
print(f" {bin_name}: {len(bin_results)} models, {profitable} profitable, Avg PnL: ${avg_pnl:.2f}")
|
|
|
|
def find_top_performers():
|
|
"""Identify top performing models"""
|
|
print("\n" + "="*80)
|
|
print("TOP PERFORMERS")
|
|
print("="*80)
|
|
|
|
# Filter active models with significant trades
|
|
active_models = [r for r in results if r['total_trades'] > 50]
|
|
|
|
# Sort by different metrics
|
|
by_sharpe = sorted(active_models, key=lambda x: x['sharpe_ratio'], reverse=True)[:10]
|
|
by_pnl = sorted(active_models, key=lambda x: x['total_pnl'], reverse=True)[:10]
|
|
by_profit_factor = sorted([r for r in active_models if r['profit_factor'] and r['profit_factor'] > 0],
|
|
key=lambda x: x['profit_factor'], reverse=True)[:10]
|
|
|
|
print("\nTop 10 by Sharpe Ratio (>50 trades):")
|
|
for i, model in enumerate(by_sharpe, 1):
|
|
print(f" {i}. {model['model_name']}: Sharpe {model['sharpe_ratio']:.2f}, "
|
|
f"Win Rate {model['win_rate']:.1f}%, PnL ${model['total_pnl']:.2f}, "
|
|
f"Trades {model['total_trades']}")
|
|
|
|
print("\nTop 10 by Total PnL (>50 trades):")
|
|
for i, model in enumerate(by_pnl, 1):
|
|
print(f" {i}. {model['model_name']}: PnL ${model['total_pnl']:.2f}, "
|
|
f"Sharpe {model['sharpe_ratio']:.2f}, Win Rate {model['win_rate']:.1f}%, "
|
|
f"Trades {model['total_trades']}")
|
|
|
|
print("\nTop 10 by Profit Factor (>50 trades):")
|
|
for i, model in enumerate(by_profit_factor, 1):
|
|
print(f" {i}. {model['model_name']}: Profit Factor {model['profit_factor']:.2f}, "
|
|
f"PnL ${model['total_pnl']:.2f}, Win Rate {model['win_rate']:.1f}%")
|
|
|
|
def analyze_risk_metrics():
|
|
"""Analyze risk-adjusted performance"""
|
|
print("\n" + "="*80)
|
|
print("RISK-ADJUSTED PERFORMANCE ANALYSIS")
|
|
print("="*80)
|
|
|
|
active_models = [r for r in results if r['total_trades'] > 10 and r['max_drawdown'] > 0]
|
|
|
|
# Sort by Calmar ratio (return/max drawdown)
|
|
positive_calmar = [r for r in active_models if r['calmar_ratio'] > 0]
|
|
by_calmar = sorted(positive_calmar, key=lambda x: x['calmar_ratio'], reverse=True)[:10]
|
|
|
|
print("\nTop 10 by Calmar Ratio (return/max drawdown):")
|
|
for i, model in enumerate(by_calmar, 1):
|
|
print(f" {i}. {model['model_name']}: Calmar {model['calmar_ratio']:.2f}, "
|
|
f"Max DD {model['max_drawdown']*100:.4f}%, PnL ${model['total_pnl']:.2f}")
|
|
|
|
# Analyze drawdown patterns
|
|
small_dd = [r for r in active_models if r['max_drawdown'] < 0.001] # <0.1%
|
|
large_dd = [r for r in active_models if r['max_drawdown'] > 0.05] # >5%
|
|
|
|
print(f"\nDrawdown Distribution:")
|
|
print(f" Small Drawdown (<0.1%): {len(small_dd)} models")
|
|
if small_dd:
|
|
avg_pnl_small = statistics.mean([r['total_pnl'] for r in small_dd])
|
|
profitable_small = len([r for r in small_dd if r['total_pnl'] > 0])
|
|
print(f" Profitable: {profitable_small}/{len(small_dd)} ({profitable_small/len(small_dd)*100:.1f}%)")
|
|
print(f" Avg PnL: ${avg_pnl_small:.2f}")
|
|
|
|
print(f" Large Drawdown (>5%): {len(large_dd)} models")
|
|
if large_dd:
|
|
avg_pnl_large = statistics.mean([r['total_pnl'] for r in large_dd])
|
|
profitable_large = len([r for r in large_dd if r['total_pnl'] > 0])
|
|
print(f" Profitable: {profitable_large}/{len(large_dd)} ({profitable_large/len(large_dd)*100:.1f}%)")
|
|
print(f" Avg PnL: ${avg_pnl_large:.2f}")
|
|
|
|
def generate_actionable_insights():
|
|
"""Generate actionable insights for production"""
|
|
print("\n" + "="*80)
|
|
print("ACTIONABLE INSIGHTS FOR PRODUCTION")
|
|
print("="*80)
|
|
|
|
active_models = [r for r in results if r['total_trades'] > 10]
|
|
|
|
insights = []
|
|
|
|
# Insight 1: Optimal epoch range
|
|
dqn_profitable = [r for r in active_models if r['model_type'] == 'DQN' and r['total_pnl'] > 50]
|
|
ppo_profitable = [r for r in active_models if r['model_type'] == 'PPO' and r['total_pnl'] > 50]
|
|
|
|
if dqn_profitable:
|
|
dqn_epochs = [r['epoch'] for r in dqn_profitable]
|
|
insights.append(f"1. DQN Optimal Epochs: {min(dqn_epochs)}-{max(dqn_epochs)} "
|
|
f"(found {len(dqn_profitable)} profitable models)")
|
|
|
|
if ppo_profitable:
|
|
ppo_epochs = [r['epoch'] for r in ppo_profitable]
|
|
insights.append(f"2. PPO Optimal Epochs: {min(ppo_epochs)}-{max(ppo_epochs)} "
|
|
f"(found {len(ppo_profitable)} profitable models)")
|
|
|
|
# Insight 2: Trade frequency sweet spot
|
|
high_sharpe = [r for r in active_models if r['sharpe_ratio'] > 5]
|
|
if high_sharpe:
|
|
avg_freq = statistics.mean([r['trade_frequency'] for r in high_sharpe])
|
|
avg_hold = statistics.mean([r['avg_trade_duration'] for r in high_sharpe])
|
|
insights.append(f"3. High Sharpe Models (>5): Trade frequency ~{avg_freq:.1f} trades/day, "
|
|
f"Hold time ~{avg_hold:.1f} bars")
|
|
|
|
# Insight 3: Win rate vs profit factor correlation
|
|
high_win_rate = [r for r in active_models if r['win_rate'] > 55]
|
|
if high_win_rate:
|
|
profitable_high_wr = len([r for r in high_win_rate if r['total_pnl'] > 0])
|
|
insights.append(f"4. Win Rate >55%: {profitable_high_wr}/{len(high_win_rate)} models profitable "
|
|
f"({profitable_high_wr/len(high_win_rate)*100:.1f}%)")
|
|
|
|
# Insight 4: Risk control
|
|
low_dd_profitable = [r for r in active_models if r['max_drawdown'] < 0.01 and r['total_pnl'] > 20]
|
|
insights.append(f"5. Low Drawdown Winners (<1% DD, >$20 PnL): {len(low_dd_profitable)} models - "
|
|
f"excellent risk control")
|
|
|
|
# Insight 5: Model type recommendation
|
|
dqn_active = [r for r in active_models if r['model_type'] == 'DQN']
|
|
ppo_active = [r for r in active_models if r['model_type'] == 'PPO']
|
|
|
|
dqn_profit_rate = len([r for r in dqn_active if r['total_pnl'] > 0]) / len(dqn_active) if dqn_active else 0
|
|
ppo_profit_rate = len([r for r in ppo_active if r['total_pnl'] > 0]) / len(ppo_active) if ppo_active else 0
|
|
|
|
better_model = 'DQN' if dqn_profit_rate > ppo_profit_rate else 'PPO'
|
|
insights.append(f"6. Model Type Preference: {better_model} ({max(dqn_profit_rate, ppo_profit_rate)*100:.1f}% "
|
|
f"profitability vs {min(dqn_profit_rate, ppo_profit_rate)*100:.1f}%)")
|
|
|
|
# Insight 7: Trading frequency
|
|
high_freq_models = [r for r in active_models if r['trade_frequency'] > 50]
|
|
high_freq_profitable = len([r for r in high_freq_models if r['total_pnl'] > 0])
|
|
insights.append(f"7. High Frequency Trading (>50/day): {high_freq_profitable}/{len(high_freq_models)} "
|
|
f"profitable - {'recommended' if high_freq_profitable/len(high_freq_models) > 0.5 else 'not recommended'}")
|
|
|
|
# Insight 8: Extreme performers
|
|
extreme_sharpe = [r for r in active_models if r['sharpe_ratio'] > 8]
|
|
insights.append(f"8. Extreme Sharpe Ratio (>8): {len(extreme_sharpe)} models - potential overfitting "
|
|
f"or exceptional performance")
|
|
|
|
# Insight 9: Consistency
|
|
consistent = [r for r in active_models if r['win_rate'] > 50 and r['profit_factor'] and
|
|
r['profit_factor'] > 2 and r['calmar_ratio'] > 5]
|
|
insights.append(f"9. Consistent Performers (50%+ WR, PF>2, Calmar>5): {len(consistent)} models - "
|
|
f"best candidates for production")
|
|
|
|
# Insight 10: No-trade models
|
|
no_trade = len([r for r in results if r['total_trades'] == 0])
|
|
insights.append(f"10. No-Trade Models: {no_trade}/100 - indicates poor training or overfitting")
|
|
|
|
# Insight 11: Trade count vs performance
|
|
optimal_trade_count = [r for r in active_models if 100 <= r['total_trades'] <= 500 and r['total_pnl'] > 0]
|
|
insights.append(f"11. Optimal Trade Count (100-500): {len(optimal_trade_count)} profitable models - "
|
|
f"good balance of activity and selectivity")
|
|
|
|
# Insight 12: Hold time patterns
|
|
short_hold_profitable = [r for r in active_models if r['avg_trade_duration'] < 20 and r['total_pnl'] > 20]
|
|
insights.append(f"12. Short-Term Trading (<20 bars): {len(short_hold_profitable)} highly profitable models - "
|
|
f"scalping strategy viable")
|
|
|
|
for insight in insights:
|
|
print(f"\n{insight}")
|
|
|
|
# Run all analyses
|
|
if __name__ == '__main__':
|
|
print("\n" + "="*80)
|
|
print("COMPREHENSIVE BACKTEST ANALYSIS")
|
|
print("100 Checkpoint Models (DQN + PPO)")
|
|
print("Date Range: 2025-07-16 to 2025-10-14")
|
|
print("="*80)
|
|
|
|
analyze_by_model_type()
|
|
analyze_by_epoch()
|
|
analyze_trade_characteristics()
|
|
find_top_performers()
|
|
analyze_risk_metrics()
|
|
generate_actionable_insights()
|
|
|
|
print("\n" + "="*80)
|
|
print("ANALYSIS COMPLETE")
|
|
print("="*80 + "\n")
|