"""
市场状态识别 — 牛市/熊市判定方法对比 + 自适应策略回测

方法：
  1. EMA200 斜率 — EMA200 向上=牛，向下=熊
  2. 价格 vs EMA200 — Price > EMA200 = 牛
  3. ATH 回撤 — 距历史高点 < 20% = 牛，> 20% = 熊
  4. 综合投票 — 三选二

根据识别结果自动偏多/偏空：
  牛市：只做多（金叉买入，死叉平仓）
  熊市：只做空（死叉做空，金叉平仓）
  震荡（票数2:1或无共识）：空仓等待

BTC 2017-2026 全周期测试

用法：
    source .venv/bin/activate && python example/regime_detect.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 BacktestConfig
from engine.indicators import ema, atr
from engine.example.long_short import LongShortEngine


# ════════════════════════════════════════════════════════
# 市场状态识别器
# ════════════════════════════════════════════════════════


class RegimeDetector:
    """市场状态识别：牛/熊/震荡"""

    def __init__(self, closes: list[float]):
        self._c = closes
        self._ath = 0.0
        self._ath_tracking = []  # 追踪历史高点序列

    def update_ath(self, price: float):
        if price > self._ath:
            self._ath = price
        self._ath_tracking.append(self._ath)

    def ema200_slope(self, idx: int) -> str:
        """EMA200 斜率判定"""
        if idx < 202:
            return "unknown"
        e200 = ema(self._c, 200)
        # 最近5根EMA200的斜率
        if e200[idx] == 0 or e200[max(0, idx - 5)] == 0:
            return "unknown"
        slope = (e200[idx] - e200[max(0, idx - 5)]) / e200[max(0, idx - 5)]
        if slope > 0.001:
            return "bull"
        elif slope < -0.001:
            return "bear"
        return "sideways"

    def price_vs_ema200(self, idx: int) -> str:
        """价格 vs EMA200"""
        if idx < 202:
            return "unknown"
        e200 = ema(self._c, 200)
        if e200[idx] == 0:
            return "unknown"
        return "bull" if self._c[idx] > e200[idx] else "bear"

    def ath_drawdown(self, idx: int) -> str:
        """ATH 回撤判定（经典加密牛熊指标）"""
        if not self._ath_tracking or idx >= len(self._ath_tracking):
            return "unknown"
        curr_ath = self._ath_tracking[idx]
        if curr_ath == 0:
            return "unknown"
        dd = (self._c[idx] - curr_ath) / curr_ath
        if dd > -0.20:
            return "bull"
        elif dd < -0.40:
            return "bear"
        return "sideways"

    def combined(self, idx: int) -> tuple[str, str, str, str]:
        """综合判定"""
        r1 = self.ema200_slope(idx)
        r2 = self.price_vs_ema200(idx)
        r3 = self.ath_drawdown(idx)

        votes = {"bull": 0, "bear": 0}
        for r in [r1, r2, r3]:
            if r in votes:
                votes[r] += 1

        if votes["bull"] >= 2:
            return "bull", r1, r2, r3
        elif votes["bear"] >= 2:
            return "bear", r1, r2, r3
        return "sideways", r1, r2, r3


# ════════════════════════════════════════════════════════
# 自适应策略（根据市场状态偏多/偏空）
# ════════════════════════════════════════════════════════


class AdaptiveEmaConfig(StrategyConfig):
    fast: int = 10
    slow: int = 50
    atr_stop: float = 2.5


class AdaptiveEmaStrategy(BaseStrategy):
    """牛市只做多、熊市只做空、震荡空仓"""

    strategy_type = "adaptive_ema"

    def __init__(self, c: AdaptiveEmaConfig):
        super().__init__(c)
        self.cfg = c
        self._closes: list[float] = []
        self._highs: list[float] = []
        self._lows: list[float] = []
        self._detector: Optional[RegimeDetector] = None
        self._position_side: str = ""  # "long" / "short"
        self._highest: float = 0.0
        self._lowest: float = float('inf')

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._closes.append(k.close)
        self._highs.append(k.high)
        self._lows.append(k.low)

        if self._detector is None:
            self._detector = RegimeDetector(self._closes)
        self._detector.update_ath(k.close)

        n = len(self._closes)
        if n < 210:
            return None

        regime, r1, r2, r3 = self._detector.combined(n - 1)

        fast = ema(self._closes, self.cfg.fast)
        slow = ema(self._closes, self.cfg.slow)
        atr_vals = atr(self._highs, self._lows, self._closes, 14)
        cur_f, cur_s, cur_atr = fast[-1], slow[-1], atr_vals[-1]
        prev_f, prev_s = fast[-2], slow[-2]
        if cur_f == 0 or cur_s == 0 or cur_atr == 0:
            return None

        golden = prev_f <= prev_s and cur_f > cur_s
        death = prev_f >= prev_s and cur_f < cur_s

        # ── 多头持仓 ──
        if self._position_side == "long":
            self._highest = max(self._highest, k.high)
            stop = self._highest - self.cfg.atr_stop * cur_atr
            if death or k.close < stop or regime == "bear":
                self._position_side = ""
                reason = "死叉" if death else ("ATR止损" if k.close < stop else "转熊市")
                return Signal(symbol=self.cfg.symbol, side="SELL", reason=reason, timestamp=k.open_time)

        # ── 空头持仓 ──
        elif self._position_side == "short":
            self._lowest = min(self._lowest, k.low)
            stop = self._lowest + self.cfg.atr_stop * cur_atr
            if golden or k.close > stop or regime == "bull":
                self._position_side = ""
                reason = "金叉" if golden else ("ATR止损" if k.close > stop else "转牛市")
                return Signal(symbol=self.cfg.symbol, side="BUY", reason=reason, timestamp=k.open_time)

        # ── 空仓等待 ──
        else:
            if regime == "bull" and golden:
                self._position_side = "long"
                self._highest = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY",
                              reason=f"牛市金叉 ({r1}/{r2}/{r3})", timestamp=k.open_time)
            elif regime == "bear" and death:
                self._position_side = "short"
                self._lowest = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL",
                              reason=f"熊市死叉 ({r1}/{r2}/{r3})", timestamp=k.open_time)
            # 震荡市：不开仓

        return None


# ════════════════════════════════════════════════════════
# 对比测试
# ════════════════════════════════════════════════════════

DATE_START = datetime(2017, 1, 1)
DATE_END = datetime(2026, 1, 1)


async def main():
    print()
    print("═" * 120)
    print("  市场状态自适应策略 — 牛市只做多 / 熊市只做空 / 震荡空仓")
    print("═" * 120)

    # ── BTC 自适应 vs 多空 vs 只做多 ──
    print("\n  ■ BTC 全周期 2017-2026 对比")
    print(f"  {'策略':<18} {'总收益%':>7} {'年化%':>7} {'夏普':>6} {'回撤%':>7} {'交易':>5} {'多头P&L':>10} {'空头P&L':>10}")
    print("  " + "─" * 105)

    # 自适应
    sc = AdaptiveEmaConfig(symbol="BTCUSDT")
    bt = BacktestConfig(symbol="BTCUSDT", interval="4h",
                        start_time=DATE_START, end_time=DATE_END, initial_capital=10_000.0)
    engine = LongShortEngine(bt, db_config=config.db)
    r = await engine.run(AdaptiveEmaStrategy, sc)
    m = r.metrics
    long_t = [t for t in r.trades if t.pnl is not None and t.side == "SELL"]
    short_t = [t for t in r.trades if t.pnl is not None and t.side == "BUY"]
    print(f"  {'自适应(牛多熊空)':<18} {m.total_return_pct:>6.1f}% {m.annual_return_pct:>6.1f}% {m.sharpe_ratio:>6.2f} {m.max_drawdown_pct:>6.1f}% {m.total_trades:>5} {sum(t.pnl for t in long_t) if long_t else 0:>+9.0f} {sum(t.pnl for t in short_t) if short_t else 0:>+9.0f}")

    # 始终多空（之前结果）
    from engine.example.long_short import LongShortEmaStrategy, LongShortEmaConfig as LSCfg
    sc2 = LSCfg(symbol="BTCUSDT", fast=10, slow=50)
    r2 = await engine.run(LongShortEmaStrategy, sc2)
    m2 = r2.metrics
    print(f"  {'始终多空':<18} {m2.total_return_pct:>6.1f}% {m2.annual_return_pct:>6.1f}% {m2.sharpe_ratio:>6.2f} {m2.max_drawdown_pct:>6.1f}% {m2.total_trades:>5}")

    # 只做多
    from engine.backtest import BacktestEngine as OrigEngine
    class LongOnlyS(BaseStrategy):
        strategy_type = "lo"; _in = False; _hp = 0.0
        def __init__(self, c): super().__init__(c); self._c = []; self._h = []; self._l = []
        async def on_start(self): await super().on_start()
        async def on_kline(self, k):
            self._c.append(k.close); self._h.append(k.high); self._l.append(k.low)
            n = len(self._c)
            if n < 55: return None
            f = ema(self._c, 10); s = ema(self._c, 50); a = atr(self._h, self._l, self._c, 14)
            cf, cs, ca, pf, ps = f[-1], s[-1], a[-1], f[-2], s[-2]
            if cf == 0 or cs == 0 or ca == 0: return None
            if self._in:
                self._hp = max(self._hp, k.high); stop = self._hp - 2.5 * ca
                if (pf >= ps and cf < cs) or k.close < stop:
                    self._in = False
                    return Signal(symbol="BTCUSDT", side="SELL",
                                  reason="死叉" if pf>=ps else "ATR止损", timestamp=k.open_time)
            else:
                if pf <= ps and cf > cs:
                    self._in = True; self._hp = k.close
                    return Signal(symbol="BTCUSDT", side="BUY", reason="金叉", timestamp=k.open_time)
            return None
    from engine.common.base import StrategyConfig as SC
    lo_bt = BacktestConfig(symbol="BTCUSDT", interval="4h", start_time=DATE_START, end_time=DATE_END, initial_capital=10_000.0)
    lo_e = OrigEngine(lo_bt, db_config=config.db)
    lo_r = await lo_e.run(LongOnlyS, SC(symbol="BTCUSDT"))
    lo_m = lo_r.metrics
    long_only_t = [t for t in lo_r.trades if t.pnl is not None]
    print(f"  {'只做多':<18} {lo_m.total_return_pct:>6.1f}% {lo_m.annual_return_pct:>6.1f}% {lo_m.sharpe_ratio:>6.2f} {lo_m.max_drawdown_pct:>6.1f}% {lo_m.total_trades:>5}")

    # ── 分段对比 ──
    PERIODS = [
        ("2017 牛市",   datetime(2017,1,1), datetime(2018,1,1)),
        ("2018 熊市",   datetime(2018,1,1), datetime(2019,1,1)),
        ("2019 反弹",   datetime(2019,1,1), datetime(2020,1,1)),
        ("2020 牛初",   datetime(2020,1,1), datetime(2021,1,1)),
        ("2021 牛市",   datetime(2021,1,1), datetime(2022,1,1)),
        ("2022 熊市",   datetime(2022,1,1), datetime(2023,1,1)),
        ("2023 震荡",   datetime(2023,1,1), datetime(2024,1,1)),
        ("2024-25 牛",  datetime(2024,1,1), datetime(2026,1,1)),
    ]
    print(f"\n  ■ BTC 分段：自适应 vs 始终多空")
    print(f"  {'阶段':<16} {'自适应':>8} {'始终多空':>8} {'只做多':>8}")
    print("  " + "─" * 50)
    for name, s, e in PERIODS:
        try:
            bt_p = BacktestConfig(symbol="BTCUSDT", interval="4h", start_time=s, end_time=e, initial_capital=10_000.0)
            # 自适应
            sc_p = AdaptiveEmaConfig(symbol="BTCUSDT")
            e_p = LongShortEngine(bt_p, db_config=config.db)
            r_p = await e_p.run(AdaptiveEmaStrategy, sc_p)
            # 始终多空
            sc_p2 = LSCfg(symbol="BTCUSDT", fast=10, slow=50)
            r_p2 = await e_p.run(LongShortEmaStrategy, sc_p2)
            # 只做多
            lo_e2 = OrigEngine(bt_p, db_config=config.db)
            lo_r2 = await lo_e2.run(LongOnlyS, SC(symbol="BTCUSDT"))
            print(f"  {name:<16} {r_p.metrics.total_return_pct:>+7.1f}% {r_p2.metrics.total_return_pct:>+7.1f}% {lo_r2.metrics.total_return_pct:>+7.1f}%")
        except Exception as ex:
            print(f"  {name:<16} 错误: {ex}")

    print("\n═" * 120)


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