Files
XauBot/backtests/v0.6.0_fixed/run_backtest.py
T

214 lines
8.3 KiB
Python
Raw Normal View History

"""
Quick runner for v0.6.0 FIXED backtest
======================================
Usage:
python backtests/v0.6.0_fixed/run_backtest.py --days 90
python backtests/v0.6.0_fixed/run_backtest.py --days 30 --save
"""
import sys
import os
from dotenv import load_dotenv
# Add parent to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
# Load .env file
load_dotenv()
from datetime import datetime, timedelta
from loguru import logger
from src.mt5_connector import MT5Connector
from src.feature_eng import FeatureEngineer
from backtest_v0_6_0_fixed import BacktestFixed
import argparse
def main():
parser = argparse.ArgumentParser(description="Run XAUBot AI v0.6.0 FIXED Backtest")
parser.add_argument("--days", type=int, default=90, help="Days to backtest (default: 90)")
parser.add_argument("--save", action="store_true", help="Save results to CSV")
args = parser.parse_args()
logger.info("=" * 80)
logger.info("XAUBOT AI v0.6.0 FIXED - BACKTEST RUNNER")
logger.info("=" * 80)
logger.info("")
logger.info("PROFESSOR'S FIXES APPLIED:")
logger.info(" [FIX 1] Fuzzy Thresholds: 70-90% tiered (was fixed 90%)")
logger.info(" [FIX 2] Trajectory Calibration: regime penalty + uncertainty")
logger.info(" [FIX 3] Session Filter: Sydney/Tokyo DISABLED (00:00-10:00)")
logger.info(" [FIX 4] Unicode Fix: ASCII only (no emojis)")
logger.info(" [FIX 5] Max Loss: $25/trade (was $50)")
logger.info("")
logger.info(f"Backtest Period: {args.days} days")
logger.info("=" * 80)
logger.info("")
# Load MT5 credentials from environment
mt5_login = int(os.getenv("MT5_LOGIN", "0"))
mt5_password = os.getenv("MT5_PASSWORD", "")
mt5_server = os.getenv("MT5_SERVER", "")
mt5_path = os.getenv("MT5_PATH", "")
if mt5_login == 0 or not mt5_password or not mt5_server:
logger.error("MT5 credentials not found in .env file")
logger.error("Please set: MT5_LOGIN, MT5_PASSWORD, MT5_SERVER")
return
# Load data
logger.info("Step 1/4: Connecting to MT5...")
connector = MT5Connector(
login=mt5_login,
password=mt5_password,
server=mt5_server,
path=mt5_path if mt5_path else None
)
if not connector.connect():
logger.error("Failed to connect to MT5")
return
end_date = datetime.now()
start_date = end_date - timedelta(days=args.days)
# Calculate bars needed: 90 days × 24 hours × 4 (M15) = ~8640 bars
bars_needed = args.days * 24 * 4
logger.info(f"Step 2/4: Loading XAUUSD M15 data (last {args.days} days, ~{bars_needed} bars)...")
df = connector.get_market_data("XAUUSD", "M15", count=bars_needed)
if df is None or len(df) == 0:
logger.error("Failed to load data")
connector.disconnect()
return
logger.info(f" Loaded {len(df)} bars")
logger.info(f" Date range: {df['time'].min()} to {df['time'].max()}")
# Add features
logger.info("Step 3/4: Engineering features...")
features = FeatureEngineer()
df = features.calculate_all(df)
# Add missing SMC and regime features with defaults (for TESTING MODE)
import polars as pl
missing_features = ['swing_high', 'swing_low', 'fvg_signal', 'ob', 'bos', 'choch', 'market_structure']
for feat in missing_features:
if feat not in df.columns:
df = df.with_columns([pl.lit(0).alias(feat)])
# Add regime if missing (will be filled by regime detector later)
if 'regime' not in df.columns:
df = df.with_columns([pl.lit(0).alias("regime")]) # 0=ranging (numeric)
else:
# Encode regime strings to numbers if exists
regime_map = {"ranging": 0, "trending": 1, "volatile": 2}
df = df.with_columns([
pl.col("regime").map_dict(regime_map, default=0).alias("regime")
])
logger.info(f" Added {len(df.columns)} features (including {len(missing_features)} SMC placeholders)")
# Run backtest
logger.info("Step 4/4: Running backtest with FIXED logic...")
logger.info("")
bt = BacktestFixed(
ml_threshold=0.30, # TESTING: Relaxed for more signals
signal_confirmation=1, # TESTING: Accept signal immediately
max_loss_per_trade=25.0, # FIX 5
trade_cooldown_bars=5, # TESTING: Reduced cooldown
)
stats = bt.run(df)
# Print detailed results
print("\n" + "=" * 80)
print("BACKTEST RESULTS - XAUBot AI v0.6.0 FIXED")
print("=" * 80)
print(f"\nPERFORMANCE METRICS:")
print(f" Total Trades: {stats.total_trades}")
print(f" Wins: {stats.wins}")
print(f" Losses: {stats.losses}")
print(f" Win Rate: {stats.win_rate:.1f}%")
print(f"\nPROFIT ANALYSIS:")
print(f" Avg Win: ${stats.avg_win:.2f}")
print(f" Avg Loss: ${stats.avg_loss:.2f}")
print(f" Win/Loss Ratio: 1:{stats.avg_loss/stats.avg_win:.2f}" if stats.avg_win > 0 else " Win/Loss Ratio: N/A")
print(f" Micro Profits (<$1): {stats.micro_profits}/{stats.wins} ({stats.micro_profit_pct:.0f}%)")
print(f"\nRISK METRICS:")
print(f" Sharpe Ratio: {stats.sharpe_ratio:.2f}")
print(f" Profit Factor: {stats.profit_factor:.2f}")
print(f" Expectancy: ${stats.expectancy:.2f}/trade")
print(f" Max Drawdown: {stats.max_drawdown:.1f}% (${stats.max_drawdown_usd:.2f})")
print(f"\nNET RESULTS:")
net_profit = stats.total_profit - stats.total_loss
print(f" Total Profit: ${stats.total_profit:.2f}")
print(f" Total Loss: -${stats.total_loss:.2f}")
print(f" Net P/L: ${net_profit:.2f}")
# Target comparison
print(f"\n" + "-" * 80)
print("PROFESSOR'S TARGET COMPARISON:")
print("-" * 80)
print(f"{'Metric':<25} | {'Target':>12} | {'Actual':>12} | {'Status':>10}")
print("-" * 80)
targets = [
("Avg Win", "$8-12", f"${stats.avg_win:.2f}", stats.avg_win >= 8),
("RR Ratio", "1.5:1 or better", f"1:{stats.avg_loss/stats.avg_win:.2f}" if stats.avg_win > 0 else "N/A",
(stats.avg_loss/stats.avg_win <= 1.5) if stats.avg_win > 0 else False),
("Micro Profits", "<20%", f"{stats.micro_profit_pct:.0f}%", stats.micro_profit_pct < 20),
("Win Rate", "62-65%", f"{stats.win_rate:.1f}%", 62 <= stats.win_rate <= 67),
("Sharpe Ratio", "1.5+", f"{stats.sharpe_ratio:.2f}", stats.sharpe_ratio >= 1.5),
]
for name, target, actual, met in targets:
status = "PASS" if met else "FAIL"
status_symbol = "[OK]" if met else "[X]"
print(f"{name:<25} | {target:>12} | {actual:>12} | {status_symbol:>10}")
print("=" * 80)
# Exit reason breakdown
print(f"\nEXIT REASON BREAKDOWN:")
exit_reasons = {}
for trade in stats.trades:
reason = trade.exit_reason.value
if reason not in exit_reasons:
exit_reasons[reason] = []
exit_reasons[reason].append(trade.profit_usd)
for reason, profits in sorted(exit_reasons.items(), key=lambda x: len(x[1]), reverse=True):
count = len(profits)
avg_profit = sum(profits) / count
print(f" {reason:<20}: {count:>3} trades (avg ${avg_profit:>6.2f})")
# Save if requested
if args.save:
import csv
output_file = f"backtests/v0.6.0_fixed/results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
os.makedirs(os.path.dirname(output_file), exist_ok=True)
with open(output_file, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow([
'Ticket', 'Entry Time', 'Exit Time', 'Direction', 'Entry Price', 'Exit Price',
'Profit USD', 'Profit Pips', 'Result', 'Exit Reason', 'Fuzzy Conf',
'Trajectory Pred', 'Peak Profit', 'Regime', 'Session'
])
for t in stats.trades:
writer.writerow([
t.ticket, t.entry_time, t.exit_time, t.direction, t.entry_price, t.exit_price,
f"{t.profit_usd:.2f}", f"{t.profit_pips:.1f}", t.result.value, t.exit_reason.value,
f"{t.fuzzy_confidence:.3f}", f"{t.trajectory_predicted:.2f}", f"{t.peak_profit:.2f}",
t.regime, t.session
])
logger.info(f"\nResults saved to: {output_file}")
connector.disconnect()
logger.info("\nBacktest completed!")
if __name__ == "__main__":
main()