"""
Hurst Exponent + VPIN Directional Strategy for Hyperliquid BTC-USD-PERP.

Based on nautilustrader tutorial:
  https://nautilustrader.io/docs/latest/tutorials/hurst_vpin_kraken/

Components:
  1. HURST EXPONENT (dollar bars) — R/S analysis, >0.55 = trending
  2. VPIN (Volume-synchronized Probability of Informed Trading) — 
     buy/sell aggressor volume imbalance over dollar-bar buckets
  3. QUOTE-DRIVEN ENTRY — both signals agree → place order on next tick

Data: Real Hyperliquid API trade fills (aggressor side + size + price).
      Dollar bars: constant-notional $10,000 bars.
      Hurst window: 128 bars (~R/S needs ≥ 64).
      VPIN window: 50 buckets.
"""

import numpy as np
from collections import deque
import time, json, requests, os

# ═══════════════════════════════════════════════════════════
# 1. Dollar Bar Construction
# ═══════════════════════════════════════════════════════════
class DollarBarBuilder:
    """Accumulate trades until notional threshold reached → emit bar."""
    def __init__(self, threshold: float = 10_000.0):
        self.threshold = threshold
        self.reset()

    def reset(self):
        self.accum_vol = 0.0
        self.open = self.high = self.low = self.close = None
        self.buy_vol = 0.0
        self.sell_vol = 0.0

    def add(self, price: float, size: float, side: str):
        notional = price * size
        self.accum_vol += notional
        if side.upper() == "B":
            self.buy_vol += notional
        else:
            self.sell_vol += notional

        if self.open is None:
            self.open = self.high = self.low = price
        else:
            self.high = max(self.high, price)
            self.low = min(self.low, price)
        self.close = price

    def is_ready(self) -> bool:
        return self.accum_vol >= self.threshold

    def emit(self) -> dict:
        bar = {
            "open": self.open,
            "high": self.high,
            "low": self.low,
            "close": self.close,
            "buy_vol": self.buy_vol,
            "sell_vol": self.sell_vol,
            "total_vol": self.accum_vol,
        }
        self.reset()
        return bar


# ═══════════════════════════════════════════════════════════
# 2. Hurst Exponent (R/S Rescaled Range)
# ═══════════════════════════════════════════════════════════
def hurst_rs(log_returns: list, max_lag: int = None) -> float:
    """R/S Hurst exponent on log returns.

    H > 0.55 → persistent (trending)
    H < 0.50 → anti-persistent (mean-reverting)
    H ≈ 0.50 → random walk
    """
    n = len(log_returns)
    if n < 32:
        return 0.50  # not enough data

    if max_lag is None:
        max_lag = min(n // 2, 64)

    lags = range(2, min(max_lag + 1, n // 2 + 1))
    rs_vals = []
    for lag in lags:
        if lag < 2: continue
        segments = n // lag
        if segments < 2: continue
        r_div_s = []
        for s in range(segments):
            seg = log_returns[s * lag:(s + 1) * lag]
            mean = np.mean(seg)
            deviations = np.cumsum(seg - mean)
            r = np.max(deviations) - np.min(deviations)
            sd = np.std(seg, ddof=1)
            if sd > 1e-12:
                r_div_s.append(r / sd)
        if r_div_s:
            rs_vals.append(np.mean(r_div_s))

    if len(rs_vals) < 4:
        return 0.50

    # H = slope of log(R/S) vs log(lag)
    log_lags = np.log([l for l in lags if l >= 2][:len(rs_vals)])
    log_rs = np.log(rs_vals)
    slope, _ = np.polyfit(log_lags, log_rs, 1)
    return min(max(slope, 0.20), 0.90)


# ═══════════════════════════════════════════════════════════
# 3. VPIN (Volume-synchronized Probability of Informed Trading)
# ═══════════════════════════════════════════════════════════
class VPINComputer:
    """VPIN on dollar-bar buckets.
    
    Each bucket = one dollar bar.
    VPIN = abs(buy_vol - sell_vol) / total_vol of bucket.
    Running average over `window` buckets.
    """
    def __init__(self, window: int = 50):
        self.window = window
        self.buckets = deque(maxlen=window)

    def add_bucket(self, buy_vol: float, sell_vol: float):
        total = buy_vol + sell_vol
        if total < 1.0:
            self.buckets.append((0.0, 0.0))
        else:
            vpin = abs(buy_vol - sell_vol) / total
            signed = (buy_vol - sell_vol) / total  # + = net buying
            self.buckets.append((vpin, signed))

    @property
    def vpin(self) -> float:
        if not self.buckets:
            return 0.0
        return np.mean([b[0] for b in self.buckets])

    @property
    def direction(self) -> float:
        """Signed net direction: +1 = strong buying, -1 = strong selling."""
        if not self.buckets:
            return 0.0
        return np.mean([b[1] for b in self.buckets])

    @property
    def ready(self) -> bool:
        return len(self.buckets) >= self.window


# ═══════════════════════════════════════════════════════════
# 4. Strategy Signal Generator
# ═══════════════════════════════════════════════════════════
class HurstVPINSignal:
    def __init__(self, notional_threshold: float = 10_000.0,
                 hurst_window: int = 128, vpin_window: int = 50,
                 hurst_entry: float = 0.55, hurst_exit: float = 0.52,
                 vpin_threshold: float = 0.25):
        self.builder = DollarBarBuilder(notional_threshold)
        self.vpin = VPINComputer(vpin_window)
        self.hurst_window = hurst_window
        self.hurst_entry = hurst_entry
        self.hurst_exit = hurst_exit
        self.vpin_threshold = vpin_threshold
        self.returns = deque(maxlen=hurst_window)

        # Current state
        self.hurst_val = 0.50
        self.vpin_val = 0.0
        self.vpin_dir = 0.0
        self.position = 0  # -1 short, 0 flat, +1 long
        self._hold_bars = 0
        self.last_bar_close = 0.0
        self.bar_count = 0

    def add_trade(self, price: float, size: float, side: str):
        """Process a single trade tick."""
        self.builder.add(price, size, side)
        if self.builder.is_ready():
            bar = self.builder.emit()
            return self._process_bar(bar)
        return None

    def _process_bar(self, bar: dict) -> dict | None:
        self.bar_count += 1

        # Update VPIN
        self.vpin.add_bucket(bar["buy_vol"], bar["sell_vol"])
        self.vpin_val = self.vpin.vpin if self.vpin.ready else 0.0
        self.vpin_dir = self.vpin.direction if self.vpin.ready else 0.0

        # Update Hurst returns
        if self.last_bar_close > 0:
            log_ret = np.log(bar["close"] / self.last_bar_close)
            self.returns.append(log_ret)

        self.last_bar_close = bar["close"]

        # Compute Hurst
        if len(self.returns) >= self.hurst_window:
            self.hurst_val = hurst_rs(list(self.returns))
        else:
            self.hurst_val = 0.50

        # Signal logic
        signal = self._compute_signal()
        return {
            "bar": bar,
            "hurst": round(self.hurst_val, 4),
            "vpin": round(self.vpin_val, 4),
            "vpin_dir": round(self.vpin_dir, 4),
            "signal": signal,
            "position": self.position,
            "bar_count": self.bar_count,
        }

    def _compute_signal(self) -> str:
        trending = self.hurst_val >= self.hurst_entry
        high_vpin = self.vpin_val >= self.vpin_threshold
        exiting = self.hurst_val <= self.hurst_exit

        # Time-based exit: close after 20 bars regardless
        if self.position != 0:
            self._hold_bars += 1
            if exiting or self._hold_bars >= 20:
                self.position = 0
                self._hold_bars = 0
                return "EXIT"

        # Entry: both agree
        if self.position == 0 and trending and high_vpin:
            if self.vpin_dir > 0.02:
                self.position = 1
                self._hold_bars = 0
                return "BUY"
            elif self.vpin_dir < -0.02:
                self.position = -1
                self._hold_bars = 0
                return "SELL"

        return "HOLD"


# ═══════════════════════════════════════════════════════════
# 5. Hyperliquid Data Fetcher
# ═══════════════════════════════════════════════════════════
def fetch_recent_trades(user: str = None, limit: int = 500) -> list:
    """Fetch recent BTC-USD-PERP fills from Hyperliquid mainnet."""
    url = "https://api.hyperliquid.xyz/info"
    payload = {"type": "userFills", "user": user} if user else {
        "type": "allMids"}
    if user:
        resp = requests.post(url, json=payload, timeout=10)
        fills = resp.json()
        return fills[:limit] if isinstance(fills, list) else []
    return []


# ═══════════════════════════════════════════════════════════
# 6. Backtest Runner
# ═══════════════════════════════════════════════════════════
def run_hurst_vpin(trades: list, starting_capital: float = 100.0,
                   size: float = 0.0002) -> dict:
    signal_gen = HurstVPINSignal()
    equity = [{"t": 0, "v": starting_capital}]
    capital = starting_capital
    position = 0
    entry_price = 0.0
    all_trades = []
    signals = []

    for i, trade in enumerate(trades):
        price = float(trade.get("px", 0))
        sz = float(trade.get("sz", 0))
        side = trade.get("side", "B")

        result = signal_gen.add_trade(price, sz, side)
        if result:
            signals.append(result)

            # Execute signal
            sig = result["signal"]
            if sig in ("BUY", "SELL") and position == 0:
                entry_price = price
                direction = 1 if sig == "BUY" else -1
                notional = price * size
                if capital >= notional:
                    all_trades.append({
                        "i": i, "side": sig, "price": price, "size": size,
                        "hurst": result["hurst"], "vpin": result["vpin"],
                        "bar_count": result["bar_count"],
                    })
                    position = direction
            elif sig == "EXIT" and position != 0:
                pnl_pct = (price / entry_price - 1) * position
                pnl = capital * pnl_pct * 0.01  # 1% of capital at risk
                capital += pnl
                all_trades[-1]["exit_price"] = price
                all_trades[-1]["pnl"] = round(pnl, 4)
                equity.append({"t": i, "v": round(capital, 4)})
                position = 0
                entry_price = 0.0

    return {
        "total_trades": len(all_trades),
        "signals": len(signals),
        "final_equity": round(capital, 4),
        "pnl_pct": round((capital / starting_capital - 1) * 100, 2),
        "trades": all_trades,
        "signals_history": signals[-20:],
    }


# ═══════════════════════════════════════════════════════════
# 7. Test
# ═══════════════════════════════════════════════════════════
if __name__ == "__main__":
    # Simulated backtest with synthetic trades
    print("Hurst/VPIN Strategy — Hyperliquid BTC-USD")
    np.random.seed(42)
    n = 50000
    prices = 64000 + np.cumsum(np.random.randn(n) * 50)
    sizes = np.abs(np.random.randn(n) * 0.01) + 0.001
    sides = ["B" if np.random.random() > 0.5 else "A" for _ in range(n)]
    sim_trades = [{"px": p, "sz": s, "side": sd} for p, s, sd in zip(prices, sizes, sides)]

    result = run_hurst_vpin(sim_trades)
    print(f"  Total trades: {result['total_trades']}")
    print(f"  Signals generated: {result['signals']}")
    print(f"  Final equity: ${result['final_equity']:.2f} ({result['pnl_pct']:+.2f}%)")
    print(f"  Last signals:")
    for s in result["signals_history"][-5:]:
        print(f"    H={s['hurst']:.3f} VPIN={s['vpin']:.3f} dir={s['vpin_dir']:+.3f} → {s['signal']}")
