"""
多策略多级别分类回测报告
日内交易 (30m/1h) ｜ 中线交易 (2h/4h/6h) ｜ 长线交易 (1d/1w)

策略：牛熊自适应 / MACD / EMA双均线 / RSI / 布林突破
币种：BTC / ETH / BNB / SOL
数据：日内近两年，中线+长线全量

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

import asyncio
import sys
from collections import defaultdict
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, atr

# ═══════════════════════════════════════════
# 策略定义
# ═══════════════════════════════════════════

# --- 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._c: list[float] = []
    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._c.append(k.close)
        ml, sl, _ = macd(self._c, self.cfg.fast, self.cfg.slow, self.cfg.signal)
        if len(ml) < 3 or ml[-1] == 0: return None
        if ml[-2] <= sl[-2] and ml[-1] > sl[-1]:
            return Signal(symbol=self.cfg.symbol, side="BUY", reason="MACD金叉", timestamp=k.open_time)
        if ml[-2] >= sl[-2] and ml[-1] < sl[-1]:
            return Signal(symbol=self.cfg.symbol, side="SELL", reason="MACD死叉", timestamp=k.open_time)
        return None

# --- 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._c: list[float] = []
    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._c.append(k.close)
        f = ema(self._c, self.cfg.fast); s = ema(self._c, self.cfg.slow)
        if len(f) < 3 or f[-1] == 0 or s[-1] == 0: return None
        if f[-2] <= s[-2] and f[-1] > s[-1]:
            return Signal(symbol=self.cfg.symbol, side="BUY", reason="EMA金叉", timestamp=k.open_time)
        if f[-2] >= s[-2] and f[-1] < s[-1]:
            return Signal(symbol=self.cfg.symbol, side="SELL", reason="EMA死叉", timestamp=k.open_time)
        return None

# --- 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._c: list[float] = []; self._in = False
    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._c.append(k.close)
        v = rsi(self._c, self.cfg.period)[-1]
        if v == 0: return None
        if v < self.cfg.oversold and not self._in:
            self._in = 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:
            self._in = False
            return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"RSI超买({v:.1f})", timestamp=k.open_time)
        return None

# --- 布林突破 ---
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._c: list[float] = []
    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._c.append(k.close)
        upper, mid, lower = bollinger(self._c, self.cfg.period, self.cfg.std)
        if len(upper) < 3 or mid[-1] == 0: return None
        p, md = k.close, mid[-1]
        pp, pm = self._c[-2], mid[-2]
        if pp <= pm and p > md and upper[-1] > 0 and mid[-1] > mid[-2]:
            return Signal(symbol=self.cfg.symbol, side="BUY", reason=f"突破BB中轨", timestamp=k.open_time)
        if pp >= pm and p < md:
            return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"跌破BB中轨", timestamp=k.open_time)
        return None

# --- 牛熊自适应 (多空双向) ---
class RegimeDetector:
    def __init__(self):
        self._ath = 0.0
    def update_ath(self, price: float):
        if price > self._ath: self._ath = price
    def ema200_slope(self, closes, idx):
        if idx < 210: return "unknown"
        e200 = ema(closes, 200)
        if e200[idx - 20] == 0: return "unknown"
        slope = (e200[idx] - e200[idx - 20]) / e200[idx - 20]
        if slope > 0.002: return "bull"
        if slope < -0.002: return "bear"
        return "sideways"
    def price_vs_ema200(self, closes, idx):
        if idx < 210: return "unknown"
        e200 = ema(closes, 200)
        return "bull" if closes[idx] > e200[idx] else "bear"
    def ath_drawdown(self, closes, idx):
        if self._ath == 0: return "unknown"
        dd = (closes[idx] - self._ath) / self._ath
        if dd > -0.15: return "bull"
        if dd < -0.35: return "bear"
        return "sideways"
    def detect(self, closes, idx):
        r1 = self.ema200_slope(closes, idx); r2 = self.price_vs_ema200(closes, idx); r3 = self.ath_drawdown(closes, idx)
        b = sum(1 for r in [r1,r2,r3] if r=="bull"); br = sum(1 for r in [r1,r2,r3] if r=="bear")
        if b >= 2: return "bull"
        if br >= 2: return "bear"
        return "sideways"

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

class RegimeStrategy(BaseStrategy):
    strategy_type = "regime"
    def __init__(self, c: RegimeConfig):
        super().__init__(c); self.cfg = c
        self._c: list[float] = []; self._h: list[float] = []; self._l: list[float] = []
        self._detector = RegimeDetector(); self._side = ""; self._hp = 0.0; self._lp = float('inf')
    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._c.append(k.close); self._h.append(k.high); self._l.append(k.low)
        self._detector.update_ath(k.close)
        n = len(self._c)
        if n < 220: return None
        regime = self._detector.detect(self._c, n - 1)
        f = ema(self._c, self.cfg.fast); s = ema(self._c, self.cfg.slow)
        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
        golden = pf <= ps and cf > cs; death = pf >= ps and cf < cs
        if self._side == "long":
            self._hp = max(self._hp, k.high); stop = self._hp - self.cfg.atr_stop * ca
            if death or k.close < stop or regime == "bear":
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="平多", timestamp=k.open_time)
        elif self._side == "short":
            self._lp = min(self._lp, k.low); stop = self._lp + self.cfg.atr_stop * ca
            if golden or k.close > stop or regime == "bull":
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="平空", timestamp=k.open_time)
        else:
            if regime == "bull" and golden:
                self._side = "long"; self._hp = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="牛市金叉", timestamp=k.open_time)
            elif regime == "bear" and death:
                self._side = "short"; self._lp = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="熊市死叉", timestamp=k.open_time)
        return None

# ═══════════════════════════════════════════
# 注册
# ═══════════════════════════════════════════

STRATEGY_REGISTRY = {
    "牛熊自适应": (RegimeStrategy, RegimeConfig, "regime"),
    "MACD": (MacdStrategy, MacdConfig, "trend"),
    "EMA双均线": (EmaCrossStrategy, EmaCrossConfig, "trend"),
    "RSI超卖反弹": (RsiStrategy, RsiConfig, "reversal"),
    "布林突破": (BollStrategy, BollConfig, "breakout"),
}

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

REGIME_PARAMS = {
    "BTCUSDT": (10, 50), "ETHUSDT": (10, 75), "BNBUSDT": (20, 50), "SOLUSDT": (30, 50),
}

CATEGORIES = {
    "日内交易": {
        "intervals": ["30m", "1h"],
        "strategies": ["MACD", "EMA双均线", "RSI超卖反弹", "布林突破", "牛熊自适应"],
        "data": "recent",
    },
    "中线交易": {
        "intervals": ["2h", "4h", "6h"],
        "strategies": ["牛熊自适应", "MACD", "EMA双均线", "RSI超卖反弹", "布林突破"],
        "data": "full",
    },
    "长线交易": {
        "intervals": ["1d", "1w"],
        "strategies": ["牛熊自适应", "MACD", "EMA双均线"],
        "data": "full",
    },
}

RECENT_START = datetime(2024, 6, 1)
RECENT_END = datetime(2026, 6, 12)
FULL_DEFAULT = datetime(2017, 1, 1)


async def run_simple(symbol, interval, strategy_cls, strategy_cfg, start, end) -> BacktestResult | None:
    """使用 BacktestEngine（只做多）运行回测"""
    bt = BacktestConfig(
        symbol=symbol, interval=interval, start_time=start, end_time=end,
        initial_capital=10_000.0, warmup_bars=100,
    )
    strategy_cfg.symbol = symbol
    engine = BacktestEngine(bt, db_config=config.db)
    return await engine.run(strategy_cls, strategy_cfg)


async def run_regime(symbol, interval, start, end) -> BacktestResult | None:
    """使用 LongShortEngine（多空双向）运行牛熊自适应策略"""
    from engine.example.long_short import LongShortEngine
    fast, slow = REGIME_PARAMS.get(symbol, (10, 50))
    sc = RegimeConfig(symbol=symbol, fast=fast, slow=slow)
    bt = BacktestConfig(
        symbol=symbol, interval=interval, start_time=start, end_time=end,
        initial_capital=10_000.0, warmup_bars=250,
    )
    engine = LongShortEngine(bt, db_config=config.db)
    return await engine.run(RegimeStrategy, sc)


# ═══════════════════════════════════════════
# 主流程
# ═══════════════════════════════════════════

async def main():
    sem = asyncio.Semaphore(2)  # 并发控制

    async def with_sem(coro):
        async with sem:
            return await coro

    out: list[str] = []
    def w(line=""):
        out.append(line); print(line)

    # 收集所有结果: (category, interval, symbol, strategy_name, result)
    all_results: list[dict] = []

    w("# 多策略多级别分类回测报告")
    w()
    w(f"> 生成时间：{datetime.now().strftime('%Y-%m-%d %H:%M')}")
    w(f"> 初始资金：10,000 USDT | 手续费：0.1% | 滑点：0.05%")
    w(f"> 日内交易使用近两年数据 (2024.06-2026.06)，中线/长线使用全量历史数据")
    w()

    for cat_name, cat_cfg in CATEGORIES.items():
        intervals = cat_cfg["intervals"]
        strategy_names = cat_cfg["strategies"]
        use_full = cat_cfg["data"] == "full"

        w(f"## {cat_name} ({'/'.join(intervals)})")
        w()

        for interval in intervals:
            w(f"### {interval}")
            w()
            # 表头
            cols = "| 币种 |"
            sep  = "|------|"
            for sn in strategy_names:
                cols += f" {sn} 收益% | {sn} 夏普 | {sn} 回撤% | {sn} 交易 | {sn} 胜率% |"
                sep  += "--------|------|------|------|------|"
            w(cols)
            w(sep)

            for symbol in SYMBOLS:
                row = f"| {symbol:<10} |"

                tasks = []
                for sn in strategy_names:
                    if sn == "牛熊自适应":
                        # 牛熊自适应使用 LongShortEngine
                        if use_full:
                            start, end = FULL_DEFAULT, RECENT_END
                        else:
                            start, end = RECENT_START, RECENT_END
                        tasks.append((sn, with_sem(run_regime(symbol, interval, start, end))))
                    else:
                        cls, cfg_cls, _ = STRATEGY_REGISTRY[sn]
                        if use_full:
                            start, end = FULL_DEFAULT, RECENT_END
                        else:
                            start, end = RECENT_START, RECENT_END
                        cfg = cfg_cls()
                        tasks.append((sn, with_sem(run_simple(symbol, interval, cls, cfg, start, end))))

                # 并行执行当前币种的所有策略
                results_list = await asyncio.gather(*[t[1] for t in tasks], return_exceptions=True)

                for (sn, _), r in zip(tasks, results_list):
                    if isinstance(r, Exception):
                        row += f" ERR | — | — | — | — |"
                        print(f"  ✗ {symbol} {interval} {sn}: {r}")
                    elif r is None:
                        row += f" N/A | — | — | — | — |"
                    else:
                        m = r.metrics
                        row += f" {m.total_return_pct:>+6.1f}% | {m.sharpe_ratio:>4.2f} | {m.max_drawdown_pct:>5.1f}% | {m.total_trades:>4} | {m.win_rate*100:>4.1f}% |"
                        all_results.append({
                            "category": cat_name, "interval": interval,
                            "symbol": symbol, "strategy": sn,
                            "return": m.total_return_pct, "sharpe": m.sharpe_ratio,
                            "dd": m.max_drawdown_pct, "trades": m.total_trades,
                            "win": m.win_rate, "pf": m.profit_factor,
                        })
                w(row)
            w()

    # ═══════════════════════════════════════
    # 汇总分析
    # ═══════════════════════════════════════
    w("---")
    w()
    w("## 汇总分析")
    w()

    for cat_name in CATEGORIES:
        cat_results = [r for r in all_results if r["category"] == cat_name]
        if not cat_results:
            continue
        w(f"### {cat_name} — 各币种最优策略")
        w()
        w("| 币种 | 最佳周期 | 最佳策略 | 总收益% | 夏普 | 回撤% | 交易 | 胜率% |")
        w("|------|---------|---------|--------|------|------|------|------|")

        for symbol in SYMBOLS:
            candidates = [r for r in cat_results if r["symbol"] == symbol]
            if not candidates:
                continue
            best = max(candidates, key=lambda x: x["sharpe"])
            w(f"| {symbol} | {best['interval']} | {best['strategy']} | {best['return']:>+7.1f}% | {best['sharpe']:.2f} | {best['dd']:.1f}% | {best['trades']} | {best['win']*100:.1f}% |")
        w()

    # 全市场最优
    w("### 全市场 TOP 10（按夏普排序）")
    w()
    w("| 排名 | 分类 | 币种 | 周期 | 策略 | 总收益% | 夏普 | 回撤% | 交易 | 胜率% |")
    w("|------|------|------|------|------|--------|------|------|------|------|")
    ranked = sorted(all_results, key=lambda x: x["sharpe"], reverse=True)
    for i, r in enumerate(ranked[:10]):
        w(f"| {i+1} | {r['category']} | {r['symbol']} | {r['interval']} | {r['strategy']} | {r['return']:>+7.1f}% | {r['sharpe']:.2f} | {r['dd']:.1f}% | {r['trades']} | {r['win']*100:.1f}% |")
    w()

    # 按策略类型汇总
    w("### 各策略类型平均表现")
    w()
    w("| 策略 | 分类 | 平均收益% | 平均夏普 | 平均回撤% | 平均胜率% |")
    w("|------|------|---------|---------|---------|---------|")
    for sn in ["牛熊自适应", "MACD", "EMA双均线", "RSI超卖反弹", "布林突破"]:
        for cat_name in CATEGORIES:
            sr = [r for r in all_results if r["strategy"] == sn and r["category"] == cat_name]
            if not sr:
                continue
            avg_ret = sum(r["return"] for r in sr) / len(sr)
            avg_sh = sum(r["sharpe"] for r in sr) / len(sr)
            avg_dd = sum(r["dd"] for r in sr) / len(sr)
            avg_win = sum(r["win"] for r in sr) / len(sr)
            w(f"| {sn} | {cat_name} | {avg_ret:>+7.1f}% | {avg_sh:.2f} | {avg_dd:.1f}% | {avg_win*100:.1f}% |")
    w()

    # 写出文件
    out_path = Path(__file__).resolve().parent.parent / "backtest" / "MULTI_STRATEGY_REPORT.md"
    with open(out_path, "w", encoding="utf-8") as f:
        f.write("\n".join(out) + "\n")
    print(f"\n✓ 报告已保存到: {out_path}")


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