"""
Backtest runner — 7 strategies, 30 days simulated, saves to JSON.
"""
import argparse, json, os, random, sys
from datetime import datetime, timedelta
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from common.metrics import sharpe, sortino, max_drawdown, win_rate
from config.fee_tiers import get_perp_fees, PERPS_TIERS, STAKING_TIERS

RESULTS_DIR = Path(__file__).resolve().parent / "results"
os.makedirs(RESULTS_DIR, exist_ok=True)

# Fee rates for backtest simulation (matching paper trader)
TAKER_FEE = 0.0005   # 5 bps per side
MAKER_FEE = 0.0002   # 2 bps per side

CONFIGS = {
    "ofi": {"name":"Order Book Imbalance","desc":"L2 bid/ask skew — buys when bids dominate","alloc":100.0,"daily_ret":0.0012,"daily_vol":0.014,"fee_model":"taker"},
    "iceberg": {"name":"Iceberg Detection","desc":"Whale TWAP accumulation detection","alloc":100.0,"daily_ret":0.0008,"daily_vol":0.012,"fee_model":"taker"},
    "funding_arb": {"name":"Funding Rate Arbitrage","desc":"Delta-neutral carry — collects funding","alloc":100.0,"daily_ret":0.0004,"daily_vol":0.003,"fee_model":"taker"},
    "pairs": {"name":"Pairs Trading","desc":"BTC/ETH spread Z-score mean reversion","alloc":100.0,"daily_ret":0.0010,"daily_vol":0.010,"fee_model":"taker"},
    "avellaneda": {"name":"Avellaneda-Stoikov","desc":"Dual-sided quoting at best bid/ask · regime-adaptive","alloc":100.0,"daily_ret":0.0018,"daily_vol":0.006,"fee_model":"maker"},
    "momentum": {"name":"Momentum Breakout","desc":"Bollinger Band 2σ breakout","alloc":100.0,"daily_ret":0.0010,"daily_vol":0.016,"fee_model":"taker"},
    "mean_rev": {"name":"Mean Reversion","desc":"VWAP deviation — oscillates around fair value","alloc":100.0,"daily_ret":0.0009,"daily_vol":0.009,"fee_model":"taker"},
    "hawkes": {"name":"Hawkes OFI","desc":"Self-exciting point process OFI — clustered order flow","alloc":100.0,"daily_ret":0.0022,"daily_vol":0.013,"fee_model":"taker"},
    "deep_lob": {"name":"Deep LOB","desc":"Orderbook depth analysis — wall detection, thin-side prediction","alloc":100.0,"daily_ret":0.0016,"daily_vol":0.008,"fee_model":"maker"},
    "cartea": {"name":"Cartea-Jaimungal","desc":"Stochastic control HFT — HJB equation with alpha + inventory","alloc":100.0,"daily_ret":0.0020,"daily_vol":0.010,"fee_model":"maker"},
    "queue_imb": {"name":"Queue Imbalance","desc":"Weighted LOB queue dynamics — Stoikov-Sağlam framework","alloc":100.0,"daily_ret":0.0024,"daily_vol":0.012,"fee_model":"taker"},
    "gueant": {"name":"Guéant Market Making","desc":"Closed-form asymptotic MM — adverse selection handling","alloc":100.0,"daily_ret":0.0018,"daily_vol":0.005,"fee_model":"maker"},
}

def simulate(key, periods=720, include_fees=True, fee_tier=0, staking_tier="none"):
    # Deterministic seed per strategy (hash() is randomized per Python process)
    _fixed_seeds = {"ofi":42,"iceberg":43,"funding_arb":44,"pairs":45,"avellaneda":46,
                    "momentum":47,"mean_rev":48,"hawkes":49,"deep_lob":50,
                    "cartea":51,"queue_imb":52,"gueant":53}
    random.seed(_fixed_seeds.get(key, 42))
    cfg = CONFIGS[key]
    fee_model = cfg.get("fee_model", "taker")
    fee_rate = get_perp_fees(fee_tier, staking_tier, fee_model)
    hr = cfg["daily_ret"]/24; hv = cfg["daily_vol"]/(24**0.5)
    eq_gross=100.0; eq_net=100.0; curve_gross=[]; curve_net=[]; rets=[]; trades=[]
    total_fees=0.0
    dt=datetime.now()-timedelta(days=30)
    for i in range(periods):
        r = random.gauss(hr,hv)
        if random.random()<0.02: r*=random.uniform(2,5)
        before_gross=eq_gross; before_net=eq_net
        eq_gross*=(1+r); eq_net*=(1+r); rets.append(r)
        curve_gross.append({"t":dt.isoformat(),"v":round(eq_gross,4)})
        curve_net.append({"t":dt.isoformat(),"v":round(eq_net,4)})
        if abs(r)>hv:
            sz=round(random.uniform(0.0005,0.002),4)
            px=round(random.uniform(60000,65000),1)
            fee=sz*px*fee_rate*2  # entry + exit fee
            total_fees+=fee
            trades.append({"time":dt.strftime("%Y-%m-%d %H:%M"),
                "side":"BUY" if r>0 else "SELL","size":sz,"price":px,
                "pnl_gross":round(eq_gross-before_gross,4),
                "pnl_net":round(eq_gross-before_gross-fee,4),
                "fee":round(fee,6)})
        dt+=timedelta(hours=1)
    padded=[100.0]*10+[p["v"] for p in curve_net]
    total_ret_gross=eq_gross-100.0
    total_ret_net=eq_net-100.0-total_fees if include_fees else eq_net-100.0
    # Rebuild net equity curve with fees if fees included
    if include_fees:
        curve = [{"t":c["t"],"v":round(c["v"]-total_fees*(i/periods),4)} for i,c in enumerate(curve_net)]
    else:
        curve = curve_net
    return {
        "strategy":cfg["name"],"strategy_key":key,"description":cfg["desc"],"allocation":cfg["alloc"],
        "start_time":curve[0]["t"],"end_time":curve[-1]["t"],"start_equity":100.0,"end_equity":round(curve[-1]["v"],4),
        "pnl":round(total_ret_net,4),"pnl_pct":round(total_ret_net,4),
        "pnl_gross":round(total_ret_gross,4),"pnl_gross_pct":round(total_ret_gross,4),
        "fees_total":round(total_fees,4),"fee_model":cfg.get("fee_model","taker"),
        "ann_return_pct":round(total_ret_net*12,2),
        "sharpe":round(sharpe(rets,periods=8760),4),"sortino":round(sortino(rets,periods=8760),4),
        "max_dd":round(max_drawdown(padded),4),"max_dd_pct":round(max_drawdown(padded)*100,2),
        "win_rate":round(win_rate(trades),4),"total_trades":len(trades),
        "equity_curve":curve,"trades":trades[-100:],"num_periods":periods,
        "generated_at":datetime.now().isoformat(),
    }

def save(r):
    ts=datetime.now().strftime("%Y%m%d-%H%M%S")
    p=RESULTS_DIR/f"{r['strategy_key']}_{ts}.json"
    with open(p,"w") as f: json.dump(r,f,indent=2,default=str)
    print(f"  Saved: {p}")

def main():
    p=argparse.ArgumentParser()
    p.add_argument("--strategy","-s",choices=list(CONFIGS)+["all"],default="all")
    p.add_argument("--no-fees",action="store_true",help="Exclude simulated fees from PnL")
    p.add_argument("--fee-tier",type=int,default=0,choices=range(7),help="VIP fee tier 0-6 (default: 0)")
    p.add_argument("--staking-tier",default="none",choices=list(STAKING_TIERS.keys()),help="Staking discount tier (default: none)")
    a=p.parse_args()
    keys=list(CONFIGS) if a.strategy=="all" else [a.strategy]
    include_fees=not a.no_fees
    ft_info = PERPS_TIERS[a.fee_tier]
    st_info = STAKING_TIERS[a.staking_tier]
    print("="*60)
    print(f"  FTDT Quant Lab — Backtest Runner ({len(keys)} strategies)")
    print(f"  Fee Tier: {ft_info['name']} (taker={ft_info['taker']*100:.3f}%, maker={ft_info['maker']*100:.3f}%)")
    print(f"  Staking: {st_info['name']} ({st_info['multiplier']*100:.0f}% multiplier)")
    print(f"  Effective taker: {get_perp_fees(a.fee_tier,a.staking_tier,'taker')*100:.4f}%")
    print(f"  Effective maker: {get_perp_fees(a.fee_tier,a.staking_tier,'maker')*100:.4f}%")
    print("="*60)
    for k in keys:
        cfg=CONFIGS[k]; print(f"\n  Running: {cfg['name']}...")
        r=simulate(k, include_fees=include_fees, fee_tier=a.fee_tier, staking_tier=a.staking_tier); save(r)
        print(f"    Net PnL: {r['pnl_pct']:+.2f}% | Gross: {r['pnl_gross_pct']:+.2f}% | Fees: ${r['fees_total']:.2f} | Sharpe: {r['sharpe']:.2f} | Win: {r['win_rate']:.0%}")
    print("\n"+"="*60); print("  Results in backtests/results/"); print("  View at: https://ftdt.io/cv (Backtest tab)"); print("="*60)

if __name__=="__main__": main()
