"""
策略对比回测 — 4 个策略 × 4 个币种

策略：
  1. MACD 金叉死叉 — MACD(12,26,9) 金叉买入，死叉卖出
  2. EMA 双均线 — EMA20 上穿 EMA50 买入，下穿卖出
  3. RSI 超卖反弹 — RSI(14)<30 买入，RSI>70 卖出
  4. 布林带突破 — 价格突破上轨买入，跌破中轨卖出

币种：BTCUSDT / ETHUSDT / BNBUSDT / SOLUSDT
周期：4h，最近两年 (2024-2026)

用法：
    source .venv/bin/activate && python example/strategy_battle.py
"""

import asyncio
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

_project_root = Path(__file__).resolve().parent.parent.parent
if str(_project_root) not in sys.path:
    sys.path.insert(0, str(_project_root))

from engine.common.base import BaseStrategy, Signal, StrategyConfig
from engine.common.models import Kline
from engine.common.config import config
from engine.backtest import BacktestEngine, BacktestConfig, BacktestResult
from engine.indicators import macd, ema, rsi, bollinger


# ════════════════════════════════════════════════════════
# 策略 1：MACD 金叉死叉
# ════════════════════════════════════════════════════════


class MacdConfig(StrategyConfig):
    fast: int = 12
    slow: int = 26
    signal: int = 9


class MacdStrategy(BaseStrategy):
    strategy_type = "macd"

    def __init__(self, c: MacdConfig):
        super().__init__(c)
        self.cfg = c
        self._closes: list[float] = []

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._closes.append(k.close)
        macd_line, sig_line, _ = macd(self._closes, self.cfg.fast, self.cfg.slow, self.cfg.signal)
        if len(macd_line) < 3:
            return None
        cur_m, cur_s = macd_line[-1], sig_line[-1]
        prev_m, prev_s = macd_line[-2], sig_line[-2]
        if cur_m == 0:
            return None

        if prev_m <= prev_s and cur_m > cur_s:
            return Signal(symbol=self.cfg.symbol, side="BUY", reason="MACD金叉", timestamp=k.open_time)
        if prev_m >= prev_s and cur_m < cur_s:
            return Signal(symbol=self.cfg.symbol, side="SELL", reason="MACD死叉", timestamp=k.open_time)
        return None


# ════════════════════════════════════════════════════════
# 策略 2：EMA 双均线
# ════════════════════════════════════════════════════════


class EmaCrossConfig(StrategyConfig):
    fast: int = 20
    slow: int = 50


class EmaCrossStrategy(BaseStrategy):
    strategy_type = "ema_cross"

    def __init__(self, c: EmaCrossConfig):
        super().__init__(c)
        self.cfg = c
        self._closes: list[float] = []

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._closes.append(k.close)
        fast = ema(self._closes, self.cfg.fast)
        slow = ema(self._closes, self.cfg.slow)
        if len(fast) < 3 or fast[-1] == 0 or slow[-1] == 0:
            return None

        if fast[-2] <= slow[-2] and fast[-1] > slow[-1]:
            return Signal(symbol=self.cfg.symbol, side="BUY", reason="EMA金叉", timestamp=k.open_time)
        if fast[-2] >= slow[-2] and fast[-1] < slow[-1]:
            return Signal(symbol=self.cfg.symbol, side="SELL", reason="EMA死叉", timestamp=k.open_time)
        return None


# ════════════════════════════════════════════════════════
# 策略 3：RSI 超卖反弹
# ════════════════════════════════════════════════════════


class RsiConfig(StrategyConfig):
    period: int = 14
    oversold: float = 30.0
    overbought: float = 70.0


class RsiStrategy(BaseStrategy):
    strategy_type = "rsi"

    def __init__(self, c: RsiConfig):
        super().__init__(c)
        self.cfg = c
        self._closes: list[float] = []
        self._in_position = False

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._closes.append(k.close)
        vals = rsi(self._closes, self.cfg.period)
        v = vals[-1]
        if v == 0:
            return None

        if v < self.cfg.oversold and not self._in_position:
            self._in_position = True
            return Signal(symbol=self.cfg.symbol, side="BUY", reason=f"RSI超卖({v:.1f})", timestamp=k.open_time)
        if v > self.cfg.overbought and self._in_position:
            self._in_position = False
            return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"RSI超买({v:.1f})", timestamp=k.open_time)
        return None


# ════════════════════════════════════════════════════════
# 策略 4：布林带突破
# ════════════════════════════════════════════════════════


class BollConfig(StrategyConfig):
    period: int = 20
    std: float = 2.0


class BollStrategy(BaseStrategy):
    strategy_type = "boll"

    def __init__(self, c: BollConfig):
        super().__init__(c)
        self.cfg = c
        self._closes: list[float] = []

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._closes.append(k.close)
        upper, mid, lower = bollinger(self._closes, self.cfg.period, self.cfg.std)
        if mid[-1] == 0 or len(upper) < 3:
            return None
        p, up, md = k.close, upper[-1], mid[-1]
        pp, prev_md = self._closes[-2], mid[-2]

        # 突破上轨+中轨向上 → 买入
        if pp <= prev_md and p > md and up > 0 and mid[-1] > mid[-2]:
            return Signal(symbol=self.cfg.symbol, side="BUY", reason=f"突破BB中轨 P={p:.0f}>M={md:.0f}", timestamp=k.open_time)
        # 跌破中轨 → 卖出
        if pp >= prev_md and p < md:
            return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"跌破BB中轨 P={p:.0f}<M={md:.0f}", timestamp=k.open_time)
        return None


# ════════════════════════════════════════════════════════
# 注册策略
# ════════════════════════════════════════════════════════

STRATEGIES = [
    ("MACD金叉死叉", MacdStrategy, MacdConfig()),
    ("EMA双均线", EmaCrossStrategy, EmaCrossConfig()),
    ("RSI超卖反弹", RsiStrategy, RsiConfig()),
    ("布林突破", BollStrategy, BollConfig()),
]

SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]


async def run_one(
    symbol: str,
    strategy_name: str,
    strategy_cls,
    strategy_config: StrategyConfig,
) -> BacktestResult:
    bt = BacktestConfig(
        symbol=symbol,
        interval="4h",
        start_time=datetime(2024, 1, 1),
        end_time=datetime(2026, 1, 1),
        initial_capital=10_000.0,
        commission_pct=0.001,
        slippage_pct=0.0005,
        warmup_bars=100,
    )
    strategy_config.symbol = symbol
    strategy_config.exchange = bt.exchange
    strategy_config.type = bt.type
    strategy_config.name = f"{strategy_name}_{symbol}"

    engine = BacktestEngine(bt, db_config=config.db)
    return await engine.run(strategy_cls, strategy_config)


async def main():
    print()
    print("═" * 98)
    print("  策略对比回测 — 4 策略 × 4 币种 | 4h 周期 | 2024-2026")
    print("═" * 98)
    print(f"  {'策略':<16} {'币种':<10} {'总收益%':>8} {'夏普':>6} {'最大回撤%':>8} {'交易数':>6} {'胜率%':>6}")
    print("─" * 98)

    results: list[tuple[str, str, BacktestResult]] = []

    # 创建引擎（每个币种一个，复用连接）
    for symbol in SYMBOLS:
        for s_name, s_cls, s_cfg in STRATEGIES:
            cfg = s_cfg.model_copy() if hasattr(s_cfg, 'model_copy') else s_cfg.__class__(**s_cfg.model_dump())
            r = await run_one(symbol, s_name, s_cls, cfg)
            results.append((s_name, symbol, r))

            m = r.metrics
            print(
                f"  {s_name:<16} {symbol:<10} "
                f"{m.total_return_pct:>7.1f}% "
                f"{m.sharpe_ratio:>6.2f} "
                f"{m.max_drawdown_pct:>7.1f}% "
                f"{m.total_trades:>6} "
                f"{m.win_rate*100:>5.1f}%"
            )

    # ── 汇总排名 ──
    print("─" * 98)
    print("\n  ■ 按总收益排名 TOP 5:")
    ranked = sorted(results, key=lambda x: x[2].metrics.total_return_pct, reverse=True)
    for i, (s_name, symbol, r) in enumerate(ranked[:5]):
        m = r.metrics
        print(f"  {i+1}. {symbol} {s_name:<16} 收益={m.total_return_pct:+.1f}%  夏普={m.sharpe_ratio:.2f}  回撤={m.max_drawdown_pct:.1f}%  胜率={m.win_rate*100:.0f}%")

    print("\n  ■ 按夏普排名 TOP 5:")
    by_sharpe = sorted(results, key=lambda x: x[2].metrics.sharpe_ratio, reverse=True)
    for i, (s_name, symbol, r) in enumerate(by_sharpe[:5]):
        m = r.metrics
        print(f"  {i+1}. {symbol} {s_name:<16} 夏普={m.sharpe_ratio:.2f}  收益={m.total_return_pct:+.1f}%  回撤={m.max_drawdown_pct:.1f}%")

    print("\n  ■ 各币种最佳策略:")
    for symbol in SYMBOLS:
        sym_results = [(s, r) for s, sym, r in results if sym == symbol]
        best = max(sym_results, key=lambda x: x[1].metrics.sharpe_ratio)
        m = best[1].metrics
        print(f"  {symbol}: {best[0]:<16} 夏普={m.sharpe_ratio:.2f}  收益={m.total_return_pct:+.1f}%  交易={m.total_trades}")

    print("\n═" * 98)
    print("  全部回测完成。")
    print("═" * 98)


if __name__ == "__main__":
    asyncio.run(main())
