#!/usr/bin/env python3
"""
Walk-Forward Optimization: Optimize on Dec 2025, validate on Jan 2026.
Sweeps RECOVERY_LOT_MULTIPLIER, THREAD_PROFIT_TARGET, RECOVERY_PROFIT_TARGET, LOT_SIZE.
"""

import subprocess
import json
import re
import sys
import time
import os

SETTINGS_FILE = "/var/www/html/crpytotradingbot/settings.py"
RESULTS_DIR = "/var/www/html/crpytotradingbot/results"

# Parameter grid to sweep
SWEEP_GRID = [
    # (recovery_lot_mult, thread_profit_target, recovery_profit_target, lot_size, label)
    (1.0,  15, 30, 0.03, "baseline"),
    (1.1,  15, 30, 0.03, "mult1.1"),
    (1.15, 15, 30, 0.03, "mult1.15"),
    (1.2,  15, 30, 0.03, "mult1.2"),
    (1.25, 15, 30, 0.03, "mult1.25"),
    (1.3,  15, 30, 0.03, "mult1.3"),
    (1.5,  15, 30, 0.03, "mult1.5"),
    # Thread profit variants
    (1.0,  10, 30, 0.03, "tp10"),
    (1.0,  20, 30, 0.03, "tp20"),
    (1.0,  25, 30, 0.03, "tp25"),
    (1.25, 10, 30, 0.03, "mult1.25_tp10"),
    (1.25, 20, 30, 0.03, "mult1.25_tp20"),
    # Recovery profit target variants
    (1.0,  15, 20, 0.03, "rpt20"),
    (1.0,  15, 40, 0.03, "rpt40"),
    (1.25, 15, 20, 0.03, "mult1.25_rpt20"),
    (1.25, 15, 40, 0.03, "mult1.25_rpt40"),
    # Lot size variants
    (1.0,  15, 30, 0.02, "lot0.02"),
    (1.0,  15, 30, 0.04, "lot0.04"),
    (1.25, 15, 30, 0.04, "mult1.25_lot0.04"),
]

def read_settings():
    with open(SETTINGS_FILE, 'r') as f:
        return f.read()

def write_settings(content):
    with open(SETTINGS_FILE, 'w') as f:
        f.write(content)

def set_param(content, param, value):
    """Replace a parameter value in settings.py content."""
    if isinstance(value, float):
        # Match the param followed by = and a number
        pattern = rf'^({param}\s*=\s*)[\d.]+(.*)$'
        replacement = rf'\g<1>{value}\2'
    elif isinstance(value, int):
        pattern = rf'^({param}\s*=\s*)[\d.]+(.*)$'
        replacement = rf'\g<1>{value}.0\2'
    else:
        pattern = rf'^({param}\s*=\s*).*$'
        replacement = rf'\g<1>{value}'
    
    new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
    return new_content

def run_backtest(period, max_rows=2000000):
    """Run a backtest and return parsed results."""
    cmd = f"cd /var/www/html/crpytotradingbot && nice -n 15 python3 bot_runner.py --csv={period} --max-rows={max_rows} --timeout=480 --throttle=2"
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=500)
    output = result.stdout + result.stderr
    
    # Parse results from the JSON file (most recent)
    json_files = sorted([f for f in os.listdir(RESULTS_DIR) if f.startswith('result_') and f.endswith('.json')])
    if json_files:
        with open(os.path.join(RESULTS_DIR, json_files[-1]), 'r') as f:
            return json.load(f), output
    return None, output

def parse_key_metrics(data):
    """Extract key metrics from result JSON."""
    if not data:
        return None
    return {
        'net_profit': data.get('net_profit', 0),
        'return_pct': data.get('return_pct', 0),
        'max_dd_pct': data.get('max_dd_pct', 0),
        'win_rate': data.get('win_rate', 0),
        'total_trades': data.get('total_trades', 0),
        'profit_factor': data.get('gross_profit', 0) / max(abs(data.get('gross_loss', 1)), 0.01),
        'commission': data.get('total_commission', 0),
        'max_recovery': data.get('max_recovery_level', 0),
        'final_balance': data.get('final_balance', 0),
    }

def score_config(metrics):
    """Score a config for ranking. Higher = better.
    Balances return vs drawdown (RAR-like) with penalty for deep DD."""
    if not metrics or metrics['max_dd_pct'] == 0:
        return -9999
    rar = metrics['return_pct'] / max(metrics['max_dd_pct'], 0.1)
    # Penalty for DD > 15%
    dd_penalty = max(0, (metrics['max_dd_pct'] - 15) * 2)
    # Bonus for positive return
    pos_bonus = 5 if metrics['net_profit'] > 0 else 0
    return rar - dd_penalty + pos_bonus

def main():
    phase = sys.argv[1] if len(sys.argv) > 1 else "both"
    max_rows = int(sys.argv[2]) if len(sys.argv) > 2 else 2000000
    
    original_settings = read_settings()
    results = []
    
    print("=" * 80)
    print(f"WALK-FORWARD OPTIMIZATION")
    print(f"Optimize: Dec 2025 | Validate: Jan 2026 | Max rows: {max_rows:,}")
    print(f"Configs to test: {len(SWEEP_GRID)}")
    print("=" * 80)
    
    if phase in ("optimize", "both"):
        print(f"\n{'='*80}")
        print("PHASE 1: OPTIMIZATION (Dec 2025)")
        print(f"{'='*80}\n")
        
        for i, (rlm, tpt, rpt, lot, label) in enumerate(SWEEP_GRID):
            print(f"\n[{i+1}/{len(SWEEP_GRID)}] Testing: {label} (RLM={rlm}, TPT=${tpt}, RPT=${rpt}, LOT={lot})")
            
            content = original_settings
            content = set_param(content, 'RECOVERY_LOT_MULTIPLIER', rlm)
            content = set_param(content, 'THREAD_PROFIT_TARGET', float(tpt))
            content = set_param(content, '_REF_RECOVERY_PROFIT_TARGET', float(rpt))
            content = set_param(content, 'LOT_SIZE', lot)
            write_settings(content)
            
            t0 = time.time()
            data, output = run_backtest("202512", max_rows)
            elapsed = time.time() - t0
            
            metrics = parse_key_metrics(data)
            score = score_config(metrics)
            
            if metrics:
                print(f"  → Net: ${metrics['net_profit']:+,.2f} | DD: {metrics['max_dd_pct']:.1f}% | WR: {metrics['win_rate']:.1f}% | PF: {metrics['profit_factor']:.2f} | Score: {score:.2f} | {elapsed:.0f}s")
                results.append({
                    'label': label,
                    'params': {'rlm': rlm, 'tpt': tpt, 'rpt': rpt, 'lot': lot},
                    'metrics': metrics,
                    'score': score,
                })
            else:
                print(f"  → FAILED ({elapsed:.0f}s)")
                results.append({
                    'label': label,
                    'params': {'rlm': rlm, 'tpt': tpt, 'rpt': rpt, 'lot': lot},
                    'metrics': None,
                    'score': -9999,
                })
        
        # Rank results
        results.sort(key=lambda x: x['score'], reverse=True)
        
        print(f"\n{'='*80}")
        print("OPTIMIZATION RESULTS — RANKED")
        print(f"{'='*80}")
        print(f"{'Rank':<5} {'Label':<20} {'Net P&L':>10} {'DD%':>7} {'WR%':>6} {'PF':>6} {'Trades':>7} {'Score':>7}")
        print("-" * 72)
        for i, r in enumerate(results):
            m = r['metrics']
            if m:
                print(f"{i+1:<5} {r['label']:<20} ${m['net_profit']:>+9,.2f} {m['max_dd_pct']:>6.1f}% {m['win_rate']:>5.1f}% {m['profit_factor']:>5.2f} {m['total_trades']:>7} {r['score']:>+7.2f}")
            else:
                print(f"{i+1:<5} {r['label']:<20} {'FAILED':>10}")
        
        # Save optimization results
        opt_file = os.path.join(RESULTS_DIR, "walk_forward_optimization.json")
        with open(opt_file, 'w') as f:
            json.dump(results, f, indent=2)
        print(f"\nOptimization saved to: {opt_file}")
        
        winner = results[0]
        print(f"\n★ WINNER: {winner['label']} (Score: {winner['score']:.2f})")
        print(f"  Params: RLM={winner['params']['rlm']}, TPT=${winner['params']['tpt']}, RPT=${winner['params']['rpt']}, LOT={winner['params']['lot']}")
    
    if phase in ("validate", "both"):
        if phase == "validate":
            # Load saved optimization results
            opt_file = os.path.join(RESULTS_DIR, "walk_forward_optimization.json")
            with open(opt_file, 'r') as f:
                results = json.load(f)
        
        winner = results[0]
        
        print(f"\n{'='*80}")
        print("PHASE 2: WALK-FORWARD VALIDATION (Jan 2026)")
        print(f"{'='*80}")
        
        # Run baseline on Jan
        print(f"\nRunning BASELINE on Jan 2026...")
        write_settings(original_settings)
        base_data, _ = run_backtest("202601", max_rows)
        base_metrics = parse_key_metrics(base_data)
        
        # Run winner on Jan
        wp = winner['params']
        print(f"\nRunning WINNER ({winner['label']}) on Jan 2026...")
        content = original_settings
        content = set_param(content, 'RECOVERY_LOT_MULTIPLIER', wp['rlm'])
        content = set_param(content, 'THREAD_PROFIT_TARGET', float(wp['tpt']))
        content = set_param(content, '_REF_RECOVERY_PROFIT_TARGET', float(wp['rpt']))
        content = set_param(content, 'LOT_SIZE', wp['lot'])
        write_settings(content)
        win_data, _ = run_backtest("202601", max_rows)
        win_metrics = parse_key_metrics(win_data)
        
        # Also run top 3 for comparison
        top3_results = []
        for r in results[:3]:
            p = r['params']
            print(f"\nRunning #{results.index(r)+1} ({r['label']}) on Jan 2026...")
            content = original_settings
            content = set_param(content, 'RECOVERY_LOT_MULTIPLIER', p['rlm'])
            content = set_param(content, 'THREAD_PROFIT_TARGET', float(p['tpt']))
            content = set_param(content, '_REF_RECOVERY_PROFIT_TARGET', float(p['rpt']))
            content = set_param(content, 'LOT_SIZE', p['lot'])
            write_settings(content)
            data, _ = run_backtest("202601", max_rows)
            m = parse_key_metrics(data)
            top3_results.append({'label': r['label'], 'params': p, 'dec_score': r['score'], 'jan_metrics': m})
        
        print(f"\n{'='*80}")
        print("WALK-FORWARD RESULTS")
        print(f"{'='*80}")
        print(f"\n{'Config':<20} {'Dec Score':>10} {'Jan Net':>10} {'Jan DD%':>8} {'Jan WR%':>8} {'Jan PF':>7}")
        print("-" * 65)
        if base_metrics:
            print(f"{'baseline':<20} {'N/A':>10} ${base_metrics['net_profit']:>+9,.2f} {base_metrics['max_dd_pct']:>7.1f}% {base_metrics['win_rate']:>7.1f}% {base_metrics['profit_factor']:>6.2f}")
        for r in top3_results:
            m = r['jan_metrics']
            if m:
                print(f"{r['label']:<20} {r['dec_score']:>+10.2f} ${m['net_profit']:>+9,.2f} {m['max_dd_pct']:>7.1f}% {m['win_rate']:>7.1f}% {m['profit_factor']:>6.2f}")
    
    # Restore original settings
    write_settings(original_settings)
    print(f"\n✅ Settings restored to original defaults.")
    print("Done!")

if __name__ == "__main__":
    main()
