"""
PolyEdge v2 — Realistic Backtest

Instead of simulated market prices, this version models what actually happens:

1. Polymarket sets a strike price at the start of each 5-min period
2. Market makers quote YES/NO prices based on recent vol + order flow
3. We model the "crowd" as using a SIMPLER vol model (historical, not EWMA)
4. Our edge: we use a BETTER vol model (EWMA + momentum) 
5. The "mispricing" = difference between crowd's model and our model

This is more realistic because:
- We're not assuming random noise
- The edge comes from a genuine modeling advantage
- Edge is small and realistic (1-5% typical)
"""

import math
import csv
import os
import json
import hashlib
from datetime import datetime
from typing import List, Dict, Any

from fair_value import calc_fair_value, realized_vol, momentum_drift, norm_cdf
from risk import RiskManager, calc_taker_fee
from config import (
    INITIAL_BANKROLL, SLIPPAGE_PCT, VOL_LOOKBACK_BARS,
    MOMENTUM_LOOKBACK, MOMENTUM_WEIGHT, MIN_EDGE,
    KELLY_FRACTION, MAX_BET_PCT, MIN_BET_USD, MAX_BET_USD,
    USE_LIMIT_ORDERS, MAX_MARKET_PRICE, MIN_MARKET_PRICE,
)


def load_m5_candles(filepath: str, max_rows: int = 0) -> List[Dict]:
    """Load tick data and resample to M5 OHLC candles."""
    prices, times = [], []
    with open(filepath, 'r') as f:
        reader = csv.reader(f)
        next(reader)
        count = 0
        for row in reader:
            if max_rows and count >= max_rows:
                break
            try:
                t = datetime.strptime(row[0][:17], "%Y%m%d %H:%M:%S")
                prices.append(float(row[1]))
                times.append(t)
                count += 1
            except:
                continue
    
    candles = []
    current_start = None
    o = h = l = c = 0
    for t, p in zip(times, prices):
        bar_start = t.replace(minute=(t.minute // 5) * 5, second=0, microsecond=0)
        if bar_start != current_start:
            if current_start is not None:
                candles.append({'time': current_start, 'open': o, 'high': h, 'low': l, 'close': c})
            current_start = bar_start
            o = h = l = c = p
        else:
            h = max(h, p)
            l = min(l, p)
            c = p
    if current_start:
        candles.append({'time': current_start, 'open': o, 'high': h, 'low': l, 'close': c})
    return candles


class CrowdModel:
    """
    Simulates what the 'crowd' (other Polymarket traders) would price the binary at.
    Uses a simpler model: simple historical vol, no momentum adjustment.
    """
    def __init__(self):
        self.prices: List[float] = []
        self.returns: List[float] = []
    
    def update(self, price: float):
        if self.prices:
            self.returns.append(math.log(price / self.prices[-1]))
        self.prices.append(price)
        if len(self.prices) > 200:
            self.prices = self.prices[-200:]
        if len(self.returns) > 200:
            self.returns = self.returns[-200:]
    
    def price_binary(self, current_price: float, strike: float, time_min: float = 5.0) -> float:
        """Price the binary using simple historical vol (no momentum, no EWMA)."""
        if len(self.returns) < 30:
            return 0.50
        
        # Simple historical vol (last 30 bars, not EWMA)
        recent = self.returns[-30:]
        var = sum(r**2 for r in recent) / len(recent)
        bars_per_year = 288 * 365
        vol = math.sqrt(var * bars_per_year)
        vol = max(vol, 0.05)
        
        # No momentum adjustment (crowd is slower to react)
        T = time_min / (365 * 24 * 60)
        sigma_sqrt_t = vol * math.sqrt(T)
        
        if sigma_sqrt_t < 1e-10:
            return 1.0 if current_price > strike else 0.0
        
        d2 = (math.log(current_price / strike) + (-0.5 * vol**2) * T) / sigma_sqrt_t
        return max(0.02, min(0.98, norm_cdf(d2)))


class SmartModel:
    """
    Our model: EWMA vol + momentum adjustment.
    Has a genuine edge over the crowd model.
    """
    def __init__(self):
        self.prices: List[float] = []
        self.returns: List[float] = []
    
    def update(self, price: float):
        if self.prices:
            self.returns.append(math.log(price / self.prices[-1]))
        self.prices.append(price)
        if len(self.prices) > 200:
            self.prices = self.prices[-200:]
        if len(self.returns) > 200:
            self.returns = self.returns[-200:]
    
    def price_binary(self, current_price: float, strike: float, time_min: float = 5.0) -> float:
        """Price binary using EWMA vol + momentum."""
        if len(self.returns) < VOL_LOOKBACK_BARS:
            return 0.50
        
        vol = realized_vol(self.returns[-VOL_LOOKBACK_BARS:], ewma_span=20)
        drift = momentum_drift(self.prices, MOMENTUM_LOOKBACK)
        
        return calc_fair_value(
            current_price=current_price,
            strike=strike,
            vol=vol,
            time_to_expiry_min=time_min,
            drift=drift,
            momentum_weight=MOMENTUM_WEIGHT,
        )


def run_backtest_v2(
    filepath: str,
    bankroll: float = 1000,
    max_rows: int = 2000000,
    verbose: bool = True,
) -> Dict[str, Any]:
    """
    Run v2 backtest with crowd vs smart model.
    """
    candles = load_m5_candles(filepath, max_rows)
    if verbose:
        print(f"Loaded {len(candles)} M5 candles")
    
    crowd = CrowdModel()
    smart = SmartModel()
    risk = RiskManager(bankroll)
    
    warmup = max(VOL_LOOKBACK_BARS, 30) + 10
    trades = []
    edge_distribution = []
    
    current_day = None
    
    for i, candle in enumerate(candles):
        # Day change
        cd = candle['time'].date()
        if cd != current_day:
            current_day = cd
            risk.new_day()
        
        risk.tick()
        
        # Feed both models
        crowd.update(candle['close'])
        smart.update(candle['close'])
        
        if i < warmup:
            continue
        
        # === BINARY MARKET ===
        strike = candle['open']
        prev_close = candles[i-1]['close']
        
        # What the crowd prices it at
        crowd_price = crowd.price_binary(prev_close, strike, 5.0)
        # What we think it's worth
        smart_price = smart.price_binary(prev_close, strike, 5.0)
        
        # Edge = our model vs crowd
        edge = smart_price - crowd_price
        edge_distribution.append(edge)
        
        # Determine direction and check minimum edge
        if abs(edge) < MIN_EDGE:
            continue
        
        if edge > 0:
            direction = "buy_yes"
            buy_price = crowd_price  # We buy YES at crowd's price
            if buy_price > MAX_MARKET_PRICE:
                continue
        else:
            direction = "buy_no"
            buy_price = 1 - crowd_price  # We buy NO
            if buy_price > MAX_MARKET_PRICE:
                continue
            edge = abs(edge)
        
        if buy_price < MIN_MARKET_PRICE:
            continue
        
        # Can we trade?
        allowed, reason = risk.can_trade()
        if not allowed:
            continue
        
        # Size with Kelly
        p = buy_price + edge  # Our estimated win probability
        p = max(0.01, min(0.99, p))
        b = (1 - buy_price) / buy_price  # Payout odds
        if b <= 0:
            continue
        kelly_f = (p * b - (1 - p)) / b
        if kelly_f <= 0:
            continue
        
        bet = kelly_f * KELLY_FRACTION * risk.bankroll
        bet = max(MIN_BET_USD, min(bet, MAX_BET_USD, risk.bankroll * MAX_BET_PCT))
        
        # Slippage
        actual_price = min(buy_price + SLIPPAGE_PCT, 0.99)
        shares = bet / actual_price
        
        # Resolution
        btc_above = candle['close'] > strike
        won = (direction == "buy_yes" and btc_above) or (direction == "buy_no" and not btc_above)
        
        if won:
            pnl = shares * (1 - actual_price)
        else:
            pnl = -bet
        
        fee = calc_taker_fee(actual_price, bet) if not USE_LIMIT_ORDERS else 0
        
        trade = {
            'bar': i,
            'time': str(candle['time']),
            'direction': direction,
            'bet_size': bet,
            'buy_price': actual_price,
            'won': won,
            'pnl': pnl,
            'fee': fee,
            'net_pnl': pnl - fee,
            'edge': edge,
            'crowd_price': crowd_price,
            'smart_price': smart_price,
            'btc': prev_close,
            'strike': strike,
        }
        
        risk.record_trade(trade)
        trades.append(trade)
    
    stats = risk.get_stats()
    stats['total_bars'] = len(candles) - warmup
    
    # Edge stats
    if edge_distribution:
        abs_edges = [abs(e) for e in edge_distribution]
        stats['avg_edge'] = sum(edge_distribution) / len(edge_distribution)
        stats['avg_abs_edge'] = sum(abs_edges) / len(abs_edges)
        stats['max_edge'] = max(abs_edges)
        stats['edge_above_threshold'] = sum(1 for e in abs_edges if e >= MIN_EDGE)
        stats['edge_pct_tradeable'] = stats['edge_above_threshold'] / len(edge_distribution) * 100
    
    if candles:
        stats['start_date'] = str(candles[warmup]['time'])
        stats['end_date'] = str(candles[-1]['time'])
    
    return stats, trades


if __name__ == "__main__":
    import sys
    
    month = sys.argv[1] if len(sys.argv) > 1 else "202601"
    bankroll = float(sys.argv[2]) if len(sys.argv) > 2 else 1000
    
    filepath = f"/var/www/html/crpytotradingbot/data/monthly/BTCUSD_{month}.csv"
    
    print(f"\n{'='*60}")
    print(f"PolyEdge v2: Smart Model vs Crowd Model")
    print(f"Month: {month} | Bankroll: ${bankroll:,.2f}")
    print(f"{'='*60}")
    
    stats, trades = run_backtest_v2(filepath, bankroll)
    
    print(f"\n{'='*60}")
    print(f"RESULTS")
    print(f"{'='*60}")
    print(f"  ${bankroll:,.2f} → ${stats['bankroll']:,.2f} ({stats['return_pct']:+.2f}%)")
    print(f"  Trades: {stats['total_trades']} | WR: {stats['win_rate']:.1f}%")
    print(f"  Avg Win: ${stats['avg_win']:.2f} | Avg Loss: ${stats['avg_loss']:.2f}")
    print(f"  PF: {stats['profit_factor']:.2f} | Max DD: {stats['max_drawdown_pct']:.1f}%")
    print(f"  Fees: ${stats['total_fees']:.2f}")
    print(f"  Avg Edge: {stats.get('avg_abs_edge', 0)*100:.2f}%")
    print(f"  Tradeable bars: {stats.get('edge_pct_tradeable', 0):.1f}%")
    print(f"  Period: {stats.get('start_date')} to {stats.get('end_date')}")
