"""
Pairs Trading strategy (BTC-PERP / ETH-PERP).

Computes the Z-score of the BTC-ETH spread over a rolling window.
When the spread moves beyond a threshold, trades mean reversion.

- Z > +2: BTC expensive -> short BTC, long ETH
- Z < -2: BTC cheap -> long BTC, short ETH
"""
import numpy as np
from nautilus_trader.trading.strategy import Strategy
from nautilus_trader.config import StrategyConfig


class PairsTradingConfig(StrategyConfig, frozen=True):
    pair: tuple[str, str]
    z_entry: float = 2.0
    z_exit: float = 0.5
    lookback_hours: int = 24
    trade_size: float = 0.001
    hedge_ratio: float = 0.05


class PairsTrading(Strategy):
    """
    Statistical arbitrage on BTC/ETH spread.

    Assumes BTC and ETH are cointegrated — the spread between
    them tends to revert to a mean. Trades the deviations.
    """

    def __init__(self, config: PairsTradingConfig) -> None:
        super().__init__(config)
        self.config = config
        self.price_history: dict[str, list[float]] = {
            self.config.pair[0]: [],
            self.config.pair[1]: [],
        }
        self.position_open = False

    def on_start(self) -> None:
        for inst in self.config.pair:
            self.subscribe_bars(f"{inst}-1-MINUTE-LAST-INTERNAL")
        self.log.info(f"Pairs trading: {self.config.pair[0]} / {self.config.pair[1]}")

    def on_bar(self, bar) -> None:
        inst_id = str(bar.bar_type.instrument_id)
        if inst_id not in self.price_history:
            return

        self.price_history[inst_id].append(bar.close.as_double())

        a_hist = self.price_history[self.config.pair[0]]
        b_hist = self.price_history[self.config.pair[1]]
        if len(a_hist) < 100 or len(b_hist) < 100:
            return

        maxlen = self.config.lookback_hours * 60
        self.price_history[self.config.pair[0]] = a_hist[-maxlen:]
        self.price_history[self.config.pair[1]] = b_hist[-maxlen:]

        a = np.array(a_hist[-100:])
        b = np.array(b_hist[-100:])
        spread = a - self.config.hedge_ratio * b

        std = spread.std()
        z = (spread[-1] - spread.mean()) / std if std > 0 else 0

        self._signal(z)

    def _signal(self, z: float) -> None:
        btc_pos = self.portfolio.net_position(self.config.pair[0])

        if z > self.config.z_entry and btc_pos <= 0:
            self._trade("SELL", "BUY")
            self.position_open = True
        elif z < -self.config.z_entry and btc_pos >= 0:
            self._trade("BUY", "SELL")
            self.position_open = True
        elif abs(z) < self.config.z_exit and self.position_open:
            self.close_all_positions(self.config.pair[0])
            self.close_all_positions(self.config.pair[1])
            self.position_open = False

    def _trade(self, a_side: str, b_side: str) -> None:
        self.submit_order(self.order_factory.market(
            instrument_id=self.config.pair[0],
            order_side=a_side,
            quantity=self.config.trade_size,
        ))
        self.submit_order(self.order_factory.market(
            instrument_id=self.config.pair[1],
            order_side=b_side,
            quantity=self.config.trade_size / self.config.hedge_ratio,
        ))
