"""
PolyEdge — Fair Value Model for Binary Options

Prices a "Will BTC be above $X in T minutes?" binary option using:
1. Log-normal model (Black-Scholes-style) for base fair value
2. Realized volatility (EWMA) for vol estimate
3. Momentum adjustment for short-term drift
4. Mean-reversion overlay for extreme prices

Fair Value = Φ((ln(S/K) + μT) / (σ√T))
Where:
  S = current BTC price
  K = strike price
  σ = annualized realized volatility
  μ = drift (momentum-adjusted)
  T = time to expiry in years
  Φ = standard normal CDF
"""

import math
from typing import List, Optional, Tuple

# Standard normal CDF (no scipy needed)
def norm_cdf(x: float) -> float:
    """Approximate standard normal CDF using Abramowitz & Stegun."""
    a1 =  0.254829592
    a2 = -0.284496736
    a3 =  1.421413741
    a4 = -1.453152027
    a5 =  1.061405429
    p  =  0.3275911
    
    sign = 1
    if x < 0:
        sign = -1
        x = abs(x)
    
    t = 1.0 / (1.0 + p * x)
    y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * math.exp(-x * x / 2.0)
    
    return 0.5 * (1.0 + sign * y)


def realized_vol(returns: List[float], ewma_span: int = 20) -> float:
    """
    Calculate realized volatility using EWMA of squared returns.
    Returns annualized vol (assuming 5-min bars, 252 trading days).
    
    Args:
        returns: List of log returns
        ewma_span: EWMA span for smoothing
    
    Returns:
        Annualized volatility
    """
    if len(returns) < 2:
        return 0.50  # Default 50% vol if insufficient data
    
    # EWMA of squared returns
    alpha = 2.0 / (ewma_span + 1)
    ewma_var = returns[0] ** 2
    
    for r in returns[1:]:
        ewma_var = alpha * (r ** 2) + (1 - alpha) * ewma_var
    
    # Per-bar vol → annualized
    # 5-min bars: 288 bars/day * 365 days = 105,120 bars/year
    bars_per_year = 288 * 365
    annual_vol = math.sqrt(ewma_var * bars_per_year)
    
    return max(annual_vol, 0.05)  # Floor at 5%


def momentum_drift(prices: List[float], lookback: int = 6) -> float:
    """
    Estimate short-term drift from recent price momentum.
    
    Args:
        prices: Recent close prices
        lookback: Number of bars for momentum calc
    
    Returns:
        Annualized drift estimate
    """
    if len(prices) < lookback + 1:
        return 0.0
    
    recent = prices[-lookback:]
    # Log return over lookback period
    total_return = math.log(recent[-1] / recent[0])
    
    # Annualize (per-bar return * bars/year)
    bars_per_year = 288 * 365
    per_bar_return = total_return / lookback
    annual_drift = per_bar_return * bars_per_year
    
    return annual_drift


def calc_fair_value(
    current_price: float,
    strike: float,
    vol: float,
    time_to_expiry_min: float = 5.0,
    drift: float = 0.0,
    momentum_weight: float = 0.3,
) -> float:
    """
    Calculate fair value of a binary option "BTC above strike at expiry".
    
    Uses log-normal model:
    FV = Φ((ln(S/K) + (μ - σ²/2)T) / (σ√T))
    
    With momentum-adjusted drift:
    μ_adj = drift * momentum_weight
    
    Args:
        current_price: Current BTC price
        strike: Strike price of the binary
        vol: Annualized volatility
        time_to_expiry_min: Minutes until resolution
        drift: Annualized momentum drift
        momentum_weight: Weight for momentum (0 = ignore, 1 = full)
    
    Returns:
        Fair value between 0 and 1
    """
    if current_price <= 0 or strike <= 0 or vol <= 0:
        return 0.5
    
    # Time in years
    T = time_to_expiry_min / (365 * 24 * 60)
    
    # Momentum-adjusted drift
    mu = drift * momentum_weight
    
    # d2 from Black-Scholes (for binary = digital option)
    sigma_sqrt_t = vol * math.sqrt(T)
    
    if sigma_sqrt_t < 1e-10:
        # No vol = deterministic
        return 1.0 if current_price > strike else 0.0
    
    d2 = (math.log(current_price / strike) + (mu - 0.5 * vol**2) * T) / sigma_sqrt_t
    
    fair_value = norm_cdf(d2)
    
    # Clamp to [0.01, 0.99]
    return max(0.01, min(0.99, fair_value))


def calc_edge(fair_value: float, market_price: float) -> Tuple[str, float]:
    """
    Calculate trading edge: difference between fair value and market price.
    
    Returns:
        (direction, edge_magnitude)
        direction: "buy_yes" if FV > market (YES is cheap)
                   "buy_no"  if FV < market (NO is cheap / YES is expensive)
        edge: absolute mispricing (always positive if tradeable)
    """
    edge = fair_value - market_price
    
    if edge > 0:
        return "buy_yes", edge
    elif edge < 0:
        return "buy_no", abs(edge)
    else:
        return "none", 0.0


def mean_reversion_adjustment(market_price: float) -> float:
    """
    Adjust edge for mean-reversion at extremes.
    
    When market price is very high (>0.85) or very low (<0.15),
    there's a statistical tendency to revert. This adds a small
    edge bonus for fading extreme prices.
    
    Returns:
        Additional edge (positive = fade the extreme)
    """
    if market_price > 0.85:
        # YES is expensive → bonus for buying NO
        return (market_price - 0.85) * 0.5
    elif market_price < 0.15:
        # YES is cheap → bonus for buying YES
        return (0.15 - market_price) * 0.5
    return 0.0
