"""
1h-1d 策略全维度对比回测 — 9策略 × 4币种 × 6时间级别 × 2交易类型 × 4数据量

8个网络知名策略 + 牛熊自适应策略（RegimeDetector3投票）
在 1h/2h/4h/6h/8h/1d 六个时间级别上的表现对比。

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

import asyncio
import json
import statistics
import sys
import time
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Optional, Type

_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.models import BacktestConfig, BacktestResult
from engine.data import DataService
from engine.indicators.incremental import EmaInc, AtrInc, RsiInc, BbInc
from engine.example.long_short import LongShortEngine

# ── 全局常量 ──
SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]
TIMEFRAMES = ["1h", "2h", "4h", "6h", "8h", "1d"]
PAIR_TYPES = ["spot", "um"]
INITIAL = 10_000.0
WARMUP = 150
MAX_CONCURRENCY = 6

NOW = datetime.now(timezone.utc)
PERIODS = {
    "全量": (None, None),
    "近两年": (NOW - timedelta(days=730), NOW),
    "近一年": (NOW - timedelta(days=365), NOW),
    "近半年": (NOW - timedelta(days=182), NOW),
}

MIN_BARS_FOR_PERIOD = {
    "全量": 500,
    "近两年": 200,
    "近一年": 100,
    "近半年": 50,
}


# ════════════════════════════════════════════════════════
# 策略 1：海龟交易 (Turtle Trading)
# ════════════════════════════════════════════════════════

class TurtleConfig(StrategyConfig):
    entry_period: int = 20
    exit_period: int = 10
    atr_period: int = 20
    atr_stop: float = 2.0


class TurtleStrategy(BaseStrategy):
    strategy_type = "趋势跟踪"
    strategy_desc = "Donchian 20/10通道突破 + 2N ATR止损，多空双向"

    def __init__(self, c: TurtleConfig):
        super().__init__(c)
        self.cfg = c
        self._highs: list[float] = []
        self._lows: list[float] = []
        self._closes: list[float] = []
        self._atr = AtrInc(c.atr_period)
        self._side: str = ""
        self._entry_price: float = 0.0
        self._highest_since: float = 0.0
        self._lowest_since: float = float("inf")

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._highs.append(k.high)
        self._lows.append(k.low)
        self._closes.append(k.close)
        self._atr.update(k.high, k.low, k.close)
        n = len(self._closes)
        min_bars = max(self.cfg.entry_period, self.cfg.atr_period) + 5
        if n < min_bars:
            return None
        ca = self._atr[-1]
        if ca == 0:
            return None
        d_high = max(self._highs[-(self.cfg.entry_period + 1):-1])
        d_low = min(self._lows[-(self.cfg.entry_period + 1):-1])
        d_exit_high = max(self._highs[-(self.cfg.exit_period + 1):-1])
        d_exit_low = min(self._lows[-(self.cfg.exit_period + 1):-1])

        if self._side == "long":
            self._highest_since = max(self._highest_since, k.high)
            stop = self._entry_price - self.cfg.atr_stop * ca
            trail = self._highest_since - self.cfg.atr_stop * ca * 0.5
            if k.close < d_exit_low or k.close < max(stop, trail):
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="海龟退出", timestamp=k.open_time, confidence=0.25)
        elif self._side == "short":
            self._lowest_since = min(self._lowest_since, k.low)
            stop = self._entry_price + self.cfg.atr_stop * ca
            trail = self._lowest_since + self.cfg.atr_stop * ca * 0.5
            if k.close > d_exit_high or k.close > min(stop, trail):
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="海龟退出", timestamp=k.open_time, confidence=0.25)
        else:
            margin = 0.002
            if k.close > d_high * (1 + margin):
                self._side = "long"
                self._entry_price = k.close
                self._highest_since = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="突破20日高", timestamp=k.open_time, confidence=0.25)
            elif k.close < d_low * (1 - margin):
                self._side = "short"
                self._entry_price = k.close
                self._lowest_since = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="跌破20日低", timestamp=k.open_time, confidence=0.25)
        return None


# ════════════════════════════════════════════════════════
# 策略 2：超级趋势 (SuperTrend)
# ════════════════════════════════════════════════════════

class SuperTrendConfig(StrategyConfig):
    atr_period: int = 10
    multiplier: float = 3.0


class SuperTrendStrategy(BaseStrategy):
    strategy_type = "趋势跟踪"
    strategy_desc = "ATR(10)×3倍动态跟踪止损带，趋势翻转即反转"

    def __init__(self, c: SuperTrendConfig):
        super().__init__(c)
        self.cfg = c
        self._atr = AtrInc(c.atr_period)
        self._highs: list[float] = []
        self._lows: list[float] = []
        self._closes: list[float] = []
        self._trend: int = 0
        self._final_upper: float = 0.0
        self._final_lower: float = 0.0

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._highs.append(k.high)
        self._lows.append(k.low)
        self._closes.append(k.close)
        self._atr.update(k.high, k.low, k.close)
        n = len(self._closes)
        if n < self.cfg.atr_period + 5:
            return None
        ca = self._atr[-1]
        if ca == 0:
            return None
        hl2 = (k.high + k.low) / 2.0
        upper = hl2 + self.cfg.multiplier * ca
        lower = hl2 - self.cfg.multiplier * ca
        prev_upper = self._final_upper
        prev_lower = self._final_lower
        prev_trend = self._trend

        if k.close > prev_upper and prev_upper > 0:
            self._trend = 1
        elif k.close < prev_lower and prev_lower > 0:
            self._trend = -1

        if self._trend == 1:
            self._final_lower = max(lower, prev_lower) if prev_lower > 0 else lower
            self._final_upper = float("inf")
        elif self._trend == -1:
            self._final_upper = min(upper, prev_upper) if prev_upper > 0 else upper
            self._final_lower = float("-inf")
        else:
            self._final_upper = upper
            self._final_lower = lower

        if prev_trend == self._trend:
            return None
        if self._trend == 1:
            return Signal(symbol=self.cfg.symbol, side="BUY", reason="SuperTrend转多", timestamp=k.open_time, confidence=0.25)
        elif self._trend == -1:
            return Signal(symbol=self.cfg.symbol, side="SELL", reason="SuperTrend转空", timestamp=k.open_time, confidence=0.25)
        return None


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

class MacdCrossConfig(StrategyConfig):
    fast: int = 12
    slow: int = 26
    signal: int = 9
    atr_period: int = 14
    atr_stop: float = 2.0


class MacdCrossStrategy(BaseStrategy):
    strategy_type = "动量"
    strategy_desc = "MACD(12,26,9)零轴上金叉做多/零轴下死叉做空+ATR止损"

    def __init__(self, c: MacdCrossConfig):
        super().__init__(c)
        self.cfg = c
        self._ema_fast = EmaInc(c.fast)
        self._ema_slow = EmaInc(c.slow)
        self._atr = AtrInc(c.atr_period)
        self._macd_vals: list[float] = []
        self._signal_vals: list[float] = []
        self._side: str = ""
        self._entry_price: float = 0.0
        self._bars_held: int = 0

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        fe = self._ema_fast.update(k.close)
        se = self._ema_slow.update(k.close)
        self._atr.update(k.high, k.low, k.close)
        n = len(self._ema_fast)
        min_bars = max(self.cfg.slow, self.cfg.signal) + 10
        if n < min_bars:
            return None
        macd = fe - se
        self._macd_vals.append(macd)
        if len(self._macd_vals) < self.cfg.signal + 2:
            self._signal_vals.append(0.0)
            return None
        if len(self._signal_vals) < self.cfg.signal:
            self._signal_vals.append(0.0)
            if len(self._signal_vals) == self.cfg.signal:
                self._signal_vals[-1] = sum(self._macd_vals[-self.cfg.signal:]) / self.cfg.signal
            return None
        k_sig = 2.0 / (self.cfg.signal + 1)
        sig_val = macd * k_sig + self._signal_vals[-1] * (1 - k_sig)
        self._signal_vals.append(sig_val)
        if len(self._signal_vals) < 3:
            return None
        cur_m, cur_s = self._macd_vals[-1], self._signal_vals[-1]
        prev_m, prev_s = self._macd_vals[-2], self._signal_vals[-2]
        ca = self._atr[-1]
        if ca == 0:
            return None
        golden = prev_m <= prev_s and cur_m > cur_s
        death = prev_m >= prev_s and cur_m < cur_s

        if self._side == "long":
            self._bars_held += 1
            stop = self._entry_price - self.cfg.atr_stop * ca
            if k.close < stop or (death and self._bars_held > 3):
                self._side = ""; self._bars_held = 0
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="MACD退出", timestamp=k.open_time, confidence=0.25)
        elif self._side == "short":
            self._bars_held += 1
            stop = self._entry_price + self.cfg.atr_stop * ca
            if k.close > stop or (golden and self._bars_held > 3):
                self._side = ""; self._bars_held = 0
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="MACD退出", timestamp=k.open_time, confidence=0.25)
        else:
            if golden and cur_m > 0:
                self._side = "long"; self._entry_price = k.close; self._bars_held = 0
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="MACD零轴上金叉", timestamp=k.open_time, confidence=0.25)
            elif death and cur_m < 0:
                self._side = "short"; self._entry_price = k.close; self._bars_held = 0
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="MACD零轴下死叉", timestamp=k.open_time, confidence=0.25)
        return None


# ════════════════════════════════════════════════════════
# 策略 4：布林收缩爆发 (Bollinger Squeeze)
# ════════════════════════════════════════════════════════

class BBSqueezeConfig(StrategyConfig):
    bb_period: int = 20
    bb_std: float = 2.0
    kc_period: int = 20
    kc_mult: float = 1.5
    squeeze_lookback: int = 30
    atr_stop: float = 2.0


class BBSqueezeStrategy(BaseStrategy):
    strategy_type = "波动率突破"
    strategy_desc = "BB收缩至KC内部后扩张爆发，顺势入场 + ATR止损"

    def __init__(self, c: BBSqueezeConfig):
        super().__init__(c)
        self.cfg = c
        self._bb = BbInc(c.bb_period, c.bb_std)
        self._ema = EmaInc(c.kc_period)
        self._atr_kc = AtrInc(c.kc_period)
        self._atr_stop = AtrInc(14)
        self._closes: list[float] = []
        self._side: str = ""
        self._entry_price: float = 0.0
        self._bb_widths: list[float] = []
        self._kc_widths: list[float] = []
        self._was_squeezed: bool = False
        self._squeeze_bars: int = 0

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._closes.append(k.close)
        bb_u, bb_m, bb_l = self._bb.update(k.close)
        typical = (k.high + k.low + k.close) / 3.0
        kc_mid = self._ema.update(typical)
        self._atr_kc.update(k.high, k.low, k.close)
        self._atr_stop.update(k.high, k.low, k.close)
        n = len(self._closes)
        min_bars = max(self.cfg.bb_period, self.cfg.kc_period, self.cfg.squeeze_lookback) + 5
        if n < min_bars:
            return None
        atr_kc = self._atr_kc[-1]
        ca = self._atr_stop[-1]
        if atr_kc == 0 or ca == 0 or bb_u == 0:
            return None
        kc_u = kc_mid + self.cfg.kc_mult * atr_kc
        kc_l = kc_mid - self.cfg.kc_mult * atr_kc
        bb_width = bb_u - bb_l
        kc_width = kc_u - kc_l
        self._bb_widths.append(bb_width)
        self._kc_widths.append(kc_width)
        is_squeezed = bb_u < kc_u and bb_l > kc_l
        lookback = min(self.cfg.squeeze_lookback, len(self._bb_widths))
        recent_bb_w = self._bb_widths[-lookback:]
        min_bb_w = min(recent_bb_w)
        width_squeeze = bb_width < min_bb_w * 1.2
        was_squeezed = self._was_squeezed
        fired = False
        if is_squeezed:
            self._was_squeezed = True
            self._squeeze_bars += 1
        elif self._was_squeezed:
            self._was_squeezed = False
            self._squeeze_bars = 0
            fired = True
        ema5 = sum(self._closes[-5:]) / 5.0 if n >= 5 else k.close
        up_momentum = k.close > bb_m and k.close > ema5
        down_momentum = k.close < bb_m and k.close < ema5

        if self._side == "long":
            stop = self._entry_price - self.cfg.atr_stop * ca
            if k.close < stop or (down_momentum and not is_squeezed):
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="BB退出", timestamp=k.open_time, confidence=0.25)
        elif self._side == "short":
            stop = self._entry_price + self.cfg.atr_stop * ca
            if k.close > stop or (up_momentum and not is_squeezed):
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="BB退出", timestamp=k.open_time, confidence=0.25)
        else:
            if was_squeezed and fired and width_squeeze:
                if up_momentum:
                    self._side = "long"; self._entry_price = k.close
                    return Signal(symbol=self.cfg.symbol, side="BUY", reason="BB收缩爆发做多", timestamp=k.open_time, confidence=0.25)
                elif down_momentum:
                    self._side = "short"; self._entry_price = k.close
                    return Signal(symbol=self.cfg.symbol, side="SELL", reason="BB收缩爆发做空", timestamp=k.open_time, confidence=0.25)
        return None


# ════════════════════════════════════════════════════════
# 策略 5：三均线排列 (Triple EMA)
# ════════════════════════════════════════════════════════

class TripleEmaConfig(StrategyConfig):
    fast: int = 10
    mid: int = 30
    slow: int = 60
    atr_period: int = 14
    atr_stop: float = 2.0


class TripleEmaStrategy(BaseStrategy):
    strategy_type = "趋势跟踪"
    strategy_desc = "EMA(10,30,60)多头/空头排列，快线金叉入场+ATR追踪止损"

    def __init__(self, c: TripleEmaConfig):
        super().__init__(c)
        self.cfg = c
        self._ema_fast = EmaInc(c.fast)
        self._ema_mid = EmaInc(c.mid)
        self._ema_slow = EmaInc(c.slow)
        self._atr = AtrInc(c.atr_period)
        self._side: str = ""
        self._entry_price: float = 0.0
        self._highest_since: float = 0.0
        self._lowest_since: float = float("inf")

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._ema_fast.update(k.close)
        self._ema_mid.update(k.close)
        self._ema_slow.update(k.close)
        self._atr.update(k.high, k.low, k.close)
        n = len(self._ema_slow)
        if n < self.cfg.slow + 10:
            return None
        ef, em, es = self._ema_fast[-1], self._ema_mid[-1], self._ema_slow[-1]
        pf, pm = self._ema_fast[-2], self._ema_mid[-2]
        ca = self._atr[-1]
        if ef == 0 or em == 0 or es == 0 or ca == 0:
            return None
        bull_align = ef > em > es
        bear_align = ef < em < es
        fast_cross_mid_up = pf <= pm and ef > em
        fast_cross_mid_down = pf >= pm and ef < em

        if self._side == "long":
            self._highest_since = max(self._highest_since, k.high)
            trail = self._highest_since - self.cfg.atr_stop * ca
            if fast_cross_mid_down or k.close < trail:
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="三均线退出", timestamp=k.open_time, confidence=0.25)
        elif self._side == "short":
            self._lowest_since = min(self._lowest_since, k.low)
            trail = self._lowest_since + self.cfg.atr_stop * ca
            if fast_cross_mid_up or k.close > trail:
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="三均线退出", timestamp=k.open_time, confidence=0.25)
        else:
            if fast_cross_mid_up and bull_align:
                self._side = "long"; self._entry_price = k.close; self._highest_since = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="三线多头排列", timestamp=k.open_time, confidence=0.25)
            elif fast_cross_mid_down and bear_align:
                self._side = "short"; self._entry_price = k.close; self._lowest_since = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="三线空头排列", timestamp=k.open_time, confidence=0.25)
        return None


# ════════════════════════════════════════════════════════
# 策略 6：RSI均值回归 (RSI Mean Reversion)
# ════════════════════════════════════════════════════════

class MeanRevConfig(StrategyConfig):
    rsi_period: int = 14
    rsi_oversold: float = 25.0
    rsi_overbought: float = 75.0
    bb_period: int = 20
    bb_std: float = 2.0
    atr_stop: float = 1.5
    require_bb_touch: bool = True


class MeanRevStrategy(BaseStrategy):
    strategy_type = "均值回归"
    strategy_desc = "RSI(14)超卖25/超买75 + 布林带触碰确认 → 逆向回归"

    def __init__(self, c: MeanRevConfig):
        super().__init__(c)
        self.cfg = c
        self._rsi = RsiInc(c.rsi_period)
        self._bb = BbInc(c.bb_period, c.bb_std)
        self._atr = AtrInc(14)
        self._side: str = ""
        self._entry_price: float = 0.0

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        r = self._rsi.update(k.close)
        up, mid, lo = self._bb.update(k.close)
        atr_v = self._atr.update(k.high, k.low, k.close)
        if r == 0 or up == 0 or atr_v == 0:
            return None
        below_bb = k.close < lo if self.cfg.require_bb_touch else True
        above_bb = k.close > up if self.cfg.require_bb_touch else True

        if self._side == "long":
            stop = self._entry_price - self.cfg.atr_stop * atr_v
            take = self._entry_price + self.cfg.atr_stop * atr_v * 1.5
            if k.close <= stop or k.close >= take or r > 55:
                self._side = ""
                reason = "止损" if k.close <= stop else ("止盈" if k.close >= take else "RSI回归中轨")
                return Signal(symbol=self.cfg.symbol, side="SELL", reason=reason, timestamp=k.open_time)
        elif self._side == "short":
            stop = self._entry_price + self.cfg.atr_stop * atr_v
            take = self._entry_price - self.cfg.atr_stop * atr_v * 1.5
            if k.close >= stop or k.close <= take or r < 45:
                self._side = ""
                reason = "止损" if k.close >= stop else ("止盈" if k.close <= take else "RSI回归中轨")
                return Signal(symbol=self.cfg.symbol, side="BUY", reason=reason, timestamp=k.open_time)
        else:
            if r < self.cfg.rsi_oversold and below_bb:
                self._side = "long"; self._entry_price = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY", reason=f"RSI超卖{r:.0f}", timestamp=k.open_time)
            elif r > self.cfg.rsi_overbought and above_bb:
                self._side = "short"; self._entry_price = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"RSI超买{r:.0f}", timestamp=k.open_time)
        return None


# ════════════════════════════════════════════════════════
# 策略 7：ATR波动率突破 (Volatility Breakout)
# ════════════════════════════════════════════════════════

class VolBreakConfig(StrategyConfig):
    atr_period: int = 14
    squeeze_period: int = 20
    squeeze_ratio: float = 0.7
    atr_stop: float = 2.0


class VolBreakStrategy(BaseStrategy):
    strategy_type = "波动率突破"
    strategy_desc = "ATR(14)收缩至极低后扩张突破 + EMA(10/30)方向确认"

    def __init__(self, c: VolBreakConfig):
        super().__init__(c)
        self.cfg = c
        self._atr = AtrInc(c.atr_period)
        self._ema_fast = EmaInc(10)
        self._ema_slow = EmaInc(30)
        self._closes: list[float] = []
        self._highs: list[float] = []
        self._lows: list[float] = []
        self._side: str = ""
        self._entry_price: float = 0.0
        self._was_squeezed = False

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._closes.append(k.close)
        self._highs.append(k.high)
        self._lows.append(k.low)
        self._atr.update(k.high, k.low, k.close)
        self._ema_fast.update(k.close)
        self._ema_slow.update(k.close)
        n = len(self._closes)
        if n < self.cfg.atr_period + self.cfg.squeeze_period:
            return None
        atr_now = self._atr[-1]
        atr_prev = self._atr[-2] if n >= 2 else 0
        ca = atr_now
        if ca == 0:
            return None
        atr_window = [self._atr[i] for i in range(max(0, n - self.cfg.squeeze_period), n) if self._atr[i] > 0]
        if not atr_window:
            return None
        min_atr = min(atr_window)
        is_squeezed = atr_now < min_atr * (1 + (1 - self.cfg.squeeze_ratio))
        atr_expanding = atr_now > atr_prev * 1.05 if atr_prev > 0 else False
        cf, cs = self._ema_fast[-1], self._ema_slow[-1]
        trend_up = cf > cs

        if self._side == "long":
            self._was_squeezed = False
            stop = self._entry_price - self.cfg.atr_stop * ca
            if k.close < stop or (cf < cs and not is_squeezed):
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="ATR退出", timestamp=k.open_time)
        elif self._side == "short":
            self._was_squeezed = False
            stop = self._entry_price + self.cfg.atr_stop * ca
            if k.close > stop or (cf > cs and not is_squeezed):
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="ATR退出", timestamp=k.open_time)
        else:
            if is_squeezed:
                self._was_squeezed = True
            elif self._was_squeezed and atr_expanding:
                self._was_squeezed = False
                if trend_up:
                    self._side = "long"; self._entry_price = k.close
                    return Signal(symbol=self.cfg.symbol, side="BUY", reason="ATR扩张突破做多", timestamp=k.open_time)
                else:
                    self._side = "short"; self._entry_price = k.close
                    return Signal(symbol=self.cfg.symbol, side="SELL", reason="ATR扩张突破做空", timestamp=k.open_time)
        return None


# ════════════════════════════════════════════════════════
# 策略 8：EMA双均线多空 (EMA Crossover)
# ════════════════════════════════════════════════════════

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


class EmaCrossStrategy(BaseStrategy):
    strategy_type = "趋势跟踪"
    strategy_desc = "EMA(10,50)金叉做多死叉做空 + ATR追踪止损，始终在场"

    def __init__(self, c: EmaCrossConfig):
        super().__init__(c)
        self.cfg = c
        self._ema_fast = EmaInc(c.fast)
        self._ema_slow = EmaInc(c.slow)
        self._atr = AtrInc(14)
        self._closes: list[float] = []
        self._highs: list[float] = []
        self._lows: list[float] = []
        self._highest: float = 0.0
        self._lowest: float = float('inf')
        self._position_side: str = ""

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._closes.append(k.close)
        self._highs.append(k.high)
        self._lows.append(k.low)
        self._ema_fast.update(k.close)
        self._ema_slow.update(k.close)
        self._atr.update(k.high, k.low, k.close)
        n = len(self._closes)
        if n < self.cfg.slow + 5:
            return None
        cur_f, cur_s = self._ema_fast[-1], self._ema_slow[-1]
        cur_atr = self._atr[-1]
        prev_f, prev_s = self._ema_fast[-2], self._ema_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:
                self._position_side = "short"
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="EMA死叉→做空", timestamp=k.open_time)
            if k.close < stop:
                self._position_side = ""
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="ATR止损→空仓", 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:
                self._position_side = "long"
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="EMA金叉→做多", timestamp=k.open_time)
            if k.close > stop:
                self._position_side = ""
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="ATR止损→空仓", timestamp=k.open_time)
        else:
            if golden:
                self._position_side = "long"; self._highest = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="金叉→做多", timestamp=k.open_time)
            elif death:
                self._position_side = "short"; self._lowest = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="死叉→做空", timestamp=k.open_time)
        return None


# ════════════════════════════════════════════════════════
# 策略 9：牛熊自适应 (Regime Adaptive) — 3法投票
# EMA200斜率 + 价格vs EMA200 + ATH回撤 → 牛市只多/熊市只空/震荡空仓
# ════════════════════════════════════════════════════════

class RegimeDetector3:
    """牛熊判定器，内部维护增量 EMA(200)，避免每次从头重算"""

    def __init__(self):
        self._ath = 0.0
        self._e200 = EmaInc(200)

    def update(self, price: float):
        if price > self._ath:
            self._ath = price
        self._e200.update(price)

    def _ema200_slope(self, idx: int) -> str:
        if idx < 220: return "unknown"
        e200 = self._e200
        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, price: float, idx: int) -> str:
        if idx < 210: return "unknown"
        e = self._e200[idx]
        if e == 0: return "unknown"
        return "bull" if price > e else "bear"

    def _ath_drawdown(self, price: float) -> str:
        if self._ath == 0: return "unknown"
        dd = (price - self._ath) / self._ath
        if dd > -0.15: return "bull"
        if dd < -0.35: return "bear"
        return "sideways"

    def detect(self, price: float, idx: int) -> str:
        r1 = self._ema200_slope(idx)
        r2 = self._price_vs_ema200(price, idx)
        r3 = self._ath_drawdown(price)
        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 RegimeEmaConfig(StrategyConfig):
    fast: int = 10
    slow: int = 50
    atr_stop: float = 2.5


class RegimeEmaStrategy(BaseStrategy):
    """按市场状态自适应做多/做空 — 全部指标增量计算，O(1) per bar"""
    strategy_type = "牛熊自适应"
    strategy_desc = "EMA200斜率+价格vsEMA200+ATH回撤3选2投票，牛市只多/熊市只空"

    def __init__(self, c: RegimeEmaConfig):
        super().__init__(c)
        self.cfg = c
        self._c: list[float] = []; self._h: list[float] = []; self._l: list[float] = []
        self._detector = RegimeDetector3()
        self._ema_fast = EmaInc(c.fast)
        self._ema_slow = EmaInc(c.slow)
        self._atr = AtrInc(14)
        self._side: str = ""; self._hp: float = 0.0; self._lp: float = 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(k.close)
        self._ema_fast.update(k.close)
        self._ema_slow.update(k.close)
        self._atr.update(k.high, k.low, k.close)
        n = len(self._c)
        if n < 220: return None
        regime = self._detector.detect(k.close, n - 1)
        cf, cs = self._ema_fast[-1], self._ema_slow[-1]
        ca = self._atr[-1]
        pf, ps = self._ema_fast[-2], self._ema_slow[-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 = ""
                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._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 = ""
                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._side = "long"; self._hp = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY", reason=f"牛市金叉", 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=f"熊市死叉", timestamp=k.open_time)
        return None


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

STRATEGY_REGISTRY = {
    "1.海龟交易": {
        "config_cls": TurtleConfig,
        "strategy_cls": TurtleStrategy,
        "make_config": lambda s: TurtleConfig(symbol=s, entry_period=20, exit_period=10, atr_period=20, atr_stop=2.0),
    },
    "2.超级趋势": {
        "config_cls": SuperTrendConfig,
        "strategy_cls": SuperTrendStrategy,
        "make_config": lambda s: SuperTrendConfig(symbol=s, atr_period=10, multiplier=3.0),
    },
    "3.MACD金叉死叉": {
        "config_cls": MacdCrossConfig,
        "strategy_cls": MacdCrossStrategy,
        "make_config": lambda s: MacdCrossConfig(symbol=s, fast=12, slow=26, signal=9, atr_period=14, atr_stop=2.0),
    },
    "4.布林收缩爆发": {
        "config_cls": BBSqueezeConfig,
        "strategy_cls": BBSqueezeStrategy,
        "make_config": lambda s: BBSqueezeConfig(symbol=s, bb_period=20, bb_std=2.0, kc_period=20, kc_mult=1.5, squeeze_lookback=30, atr_stop=2.0),
    },
    "5.三均线排列": {
        "config_cls": TripleEmaConfig,
        "strategy_cls": TripleEmaStrategy,
        "make_config": lambda s: TripleEmaConfig(symbol=s, fast=10, mid=30, slow=60, atr_period=14, atr_stop=2.0),
    },
    "6.RSI均值回归": {
        "config_cls": MeanRevConfig,
        "strategy_cls": MeanRevStrategy,
        "make_config": lambda s: MeanRevConfig(symbol=s, rsi_period=14, rsi_oversold=25, rsi_overbought=75, bb_period=20, bb_std=2.0, atr_stop=1.5),
    },
    "7.ATR波动率突破": {
        "config_cls": VolBreakConfig,
        "strategy_cls": VolBreakStrategy,
        "make_config": lambda s: VolBreakConfig(symbol=s, atr_period=14, squeeze_period=20, squeeze_ratio=0.7, atr_stop=2.0),
    },
    "8.EMA双均线多空": {
        "config_cls": EmaCrossConfig,
        "strategy_cls": EmaCrossStrategy,
        "make_config": lambda s: EmaCrossConfig(symbol=s, fast=10, slow=50, atr_stop=2.5),
    },
    "9.牛熊自适应": {
        "config_cls": RegimeEmaConfig,
        "strategy_cls": RegimeEmaStrategy,
        "make_config": lambda s: RegimeEmaConfig(symbol=s, fast=10, slow=50, atr_stop=2.5),
    },
}

STRATEGY_PARAMS_STR = {
    "1.海龟交易": "entry=20/exit=10/ATR(20)x2.0",
    "2.超级趋势": "ATR(10)x3.0",
    "3.MACD金叉死叉": "MACD(12,26,9)/ATR(14)x2.0",
    "4.布林收缩爆发": "BB(20,2.0)/KC(20,1.5)/squeeze=30",
    "5.三均线排列": "EMA(10,30,60)/ATR(14)x2.0",
    "6.RSI均值回归": "RSI(14)25/75+BB(20,2.0)/ATR(14)x1.5",
    "7.ATR波动率突破": "ATR(14)/squeeze=20x0.7/EMA(10,30)",
    "8.EMA双均线多空": "EMA(10,50)/ATR(14)x2.5",
    "9.牛熊自适应": "EMA200投票(斜率+价格+ATH)牛多熊空",
}


# ════════════════════════════════════════════════════════
# 执行
# ════════════════════════════════════════════════════════

async def run_one(entry, symbol, interval, pair_type, period_label, start, end):
    make_config = entry["make_config"]
    strategy_cls = entry["strategy_cls"]
    sc = make_config(symbol)
    bt = BacktestConfig(
        symbol=symbol, interval=interval,
        type=pair_type,
        start_time=start, end_time=end,
        initial_capital=INITIAL, warmup_bars=WARMUP,
    )
    engine = LongShortEngine(bt, db_config=config.db)
    t0 = time.time()
    try:
        r = await engine.run(strategy_cls, sc)
        elapsed = time.time() - t0
        return r, elapsed, None
    except Exception as ex:
        elapsed = time.time() - t0
        return None, elapsed, str(ex)


def safe(val, default=0):
    return default if val is None else val


async def main():
    ds = DataService(config.db)
    await ds.connect()

    print("正在获取数据范围...")
    date_ranges: dict[tuple[str, str], tuple] = {}
    for symbol in SYMBOLS:
        for tf in TIMEFRAMES:
            for pair_type in PAIR_TYPES:
                try:
                    s, e = await ds.fetch_symbol_date_range(symbol, tf, type=pair_type)
                    bar_ms = {"1h": 3_600_000, "2h": 7_200_000, "4h": 14_400_000, "6h": 21_600_000, "8h": 28_800_000, "1d": 86_400_000}
                    estimated_bars = int((e - s).total_seconds() * 1000 / bar_ms[tf])
                    date_ranges[(symbol, tf, pair_type)] = (s, e, estimated_bars)
                    print(f"  {symbol} {tf:<4} {pair_type:<5}: {s.date()} ~ {e.date()} (约{estimated_bars:,}根)")
                except Exception as ex:
                    print(f"  {symbol} {tf:<4} {pair_type:<5}: 获取失败 — {ex}")

    await ds.close()

    sem = asyncio.Semaphore(MAX_CONCURRENCY)
    tasks_info: list[dict] = []

    for strat_name, entry in STRATEGY_REGISTRY.items():
        for symbol in SYMBOLS:
            for tf in TIMEFRAMES:
                for pair_type in PAIR_TYPES:
                    key = (symbol, tf, pair_type)
                    if key not in date_ranges:
                        continue
                    fs, fe, est_bars = date_ranges[key]

                    for period_label, (period_start, period_end) in PERIODS.items():
                        actual_start = period_start or fs
                        actual_end = period_end or fe
                        if actual_start >= actual_end:
                            continue

                        min_bars = MIN_BARS_FOR_PERIOD.get(period_label, 50)
                        actual_bars = est_bars
                        if period_label != "全量":
                            actual_bars = int((actual_end - actual_start).total_seconds() * 1000 / {
                                "1h": 3_600_000, "2h": 7_200_000, "4h": 14_400_000, "6h": 21_600_000, "8h": 28_800_000, "1d": 86_400_000
                            }[tf])

                        if actual_bars < min_bars:
                            continue

                        tasks_info.append({
                            "strat_name": strat_name,
                            "entry": entry,
                            "symbol": symbol,
                            "tf": tf,
                            "pair_type": pair_type,
                            "period_label": period_label,
                            "start": actual_start,
                            "end": actual_end,
                        })

    total = len(tasks_info)
    print(f"\n共 {total} 组回测任务 (9策略×4币种×6时间×2类型×4数据量 - 跳过数据不足)")

    results: list[dict] = []
    completed = 0
    errors = 0

    async def run_one_safe(info):
        nonlocal completed, errors
        async with sem:
            r, elapsed, err = await run_one(
                info["entry"], info["symbol"], info["tf"], info["pair_type"],
                info["period_label"], info["start"], info["end"],
            )
            completed += 1
            if err:
                errors += 1
                status = f"✗ {err[:40]}"
            elif r is None:
                errors += 1
                status = "✗ 无结果"
            else:
                m = r.metrics
                status = f"✓ {m.annual_return_pct:+.1f}%/yr"
            print(f"  [{completed}/{total}] {info['strat_name']} {info['symbol']} {info['tf']} {info['pair_type']} {info['period_label']} ({elapsed:.1f}s) {status}", flush=True)

            row = {
                "策略名": info["strat_name"],
                "币种": info["symbol"],
                "时间级别": info["tf"],
                "类型": info["pair_type"],
                "数据量": info["period_label"],
                "策略类型": info["entry"]["strategy_cls"].strategy_type if r else "",
                "策略参数": STRATEGY_PARAMS_STR.get(info["strat_name"], ""),
                "策略描述": info["entry"]["strategy_cls"].strategy_desc if r else "",
                "日期范围": f"{info['start'].date()}~{info['end'].date()}",
            }

            if r is not None:
                m = r.metrics
                row.update({
                    "初始资金": INITIAL,
                    "最终权益": round(m.final_equity, 2),
                    "总收益%": round(m.total_return_pct, 2),
                    "年化收益%": round(m.annual_return_pct, 2),
                    "夏普比率": round(m.sharpe_ratio, 2),
                    "最大回撤%": round(m.max_drawdown_pct, 2),
                    "胜率%": round(m.win_rate * 100, 2),
                    "盈亏比": round(m.profit_factor, 2),
                    "交易次数": m.total_trades,
                    "平均盈亏": round(m.avg_trade_pnl, 2),
                    "最佳盈亏": round(m.best_trade_pnl, 2),
                    "最差盈亏": round(m.worst_trade_pnl, 2),
                    "卡尔玛比率": round(m.calmar_ratio, 2),
                    "耗时s": round(elapsed, 1),
                })
            else:
                row.update({
                    "初始资金": INITIAL,
                    "最终权益": 0,
                    "总收益%": 0,
                    "年化收益%": 0,
                    "夏普比率": 0,
                    "最大回撤%": 0,
                    "胜率%": 0,
                    "盈亏比": 0,
                    "交易次数": 0,
                    "平均盈亏": 0,
                    "最佳盈亏": 0,
                    "最差盈亏": 0,
                    "卡尔玛比率": 0,
                    "耗时s": round(elapsed, 1),
                    "错误": err or "未知错误",
                })

            results.append(row)
            return row

    t_total = time.time()
    await asyncio.gather(*[run_one_safe(info) for info in tasks_info])
    total_elapsed = time.time() - t_total

    print(f"\n全部完成！成功 {total - errors}/{total}，错误 {errors}，总耗时 {total_elapsed:.0f}s")

    # ── 打印完整表格 ──
    print()
    print("═" * 195)
    print("  1h-1d 全维度策略对比回测结果（9策略 × 4币种 × 6时间 × 2类型 × 4数据量）")
    print("═" * 195)
    print()

    for strat_name in STRATEGY_REGISTRY:
        strat_results = [r for r in results if r["策略名"] == strat_name]
        if not strat_results:
            continue
        first = strat_results[0]
        print(f"■ {strat_name} | 类型: {first['策略类型']} | {first['策略描述']}")
        print(f"  参数: {first['策略参数']}")
        print(f"  {'币种':<10} {'时间':<5} {'类型':<5} {'数据量':<6} {'总收益%':>8} {'年化%':>8} {'夏普':>7} {'回撤%':>7} {'胜率%':>7} {'盈亏比':>7} {'交易':>6} {'日期范围':<24}")
        print("  " + "─" * 195)

        strat_results.sort(key=lambda x: (SYMBOLS.index(x["币种"]), TIMEFRAMES.index(x["时间级别"]), PAIR_TYPES.index(x["类型"]), list(PERIODS.keys()).index(x["数据量"])))

        for r in strat_results:
            print(f"  {r['币种']:<10} {r['时间级别']:<5} {r['类型']:<5} {r['数据量']:<6} {r['总收益%']:>7.1f}% {r['年化收益%']:>7.1f}% {r['夏普比率']:>7.2f} {r['最大回撤%']:>7.1f}% {r['胜率%']:>6.1f}% {r['盈亏比']:>7.2f} {r['交易次数']:>6} {r['日期范围']:<24}")
        print()

    # ── 终极汇总 ──
    print("═" * 195)
    print("  ■ 终极汇总：每组(时间级别+类型+数据量)下各币种最佳策略（按年化收益）\n（1h结果可能因数据量过大导致内存/耗时问题，已设MAX_CONCURRENCY=6）")
    print("═" * 195)
    print()

    for tf in TIMEFRAMES:
        for pair_type in PAIR_TYPES:
            for period_label in PERIODS:
                subset = [r for r in results if r["时间级别"] == tf and r["类型"] == pair_type and r["数据量"] == period_label and r.get("总收益%", 0) != 0]
                if not subset:
                    continue
                subset.sort(key=lambda x: x.get("年化收益%", -9999), reverse=True)

                print(f"  ▲ {tf} | {pair_type} | {period_label}")
                print(f"  {'排名':<5} {'策略名':<22} {'币种':<10} {'总收益%':>8} {'年化%':>8} {'夏普':>7} {'回撤%':>7} {'胜率%':>7} {'盈亏比':>7} {'交易':>6}")
                print("  " + "─" * 135)
                for i, r in enumerate(subset[:5]):
                    marker = ["🥇", "🥈", "🥉", " 4", " 5"][i]
                    print(f"  {marker:<5} {r['策略名']:<22} {r['币种']:<10} {r['总收益%']:>7.1f}% {r['年化收益%']:>7.1f}% {r['夏普比率']:>7.2f} {r['最大回撤%']:>7.1f}% {r['胜率%']:>6.1f}% {r['盈亏比']:>7.2f} {r['交易次数']:>6}")
                print()

    # ── 保存 JSON ──
    output_file = _project_root / "engine" / "example" / "comparison_2h_6h_result.json"
    with open(output_file, "w", encoding="utf-8") as f:
        json.dump({
            "config": {
                "symbols": SYMBOLS,
                "timeframes": TIMEFRAMES,
                "pair_types": PAIR_TYPES,
                "periods": list(PERIODS.keys()),
                "initial_capital": INITIAL,
                "warmup_bars": WARMUP,
                "total_tasks": total,
                "total_errors": errors,
                "elapsed_seconds": total_elapsed,
                "run_time": datetime.now(timezone.utc).isoformat(),
            },
            "results": results,
        }, f, ensure_ascii=False, indent=2, default=str)
    print(f"  详细结果已保存至: {output_file}")
    print()
    print("═" * 195)


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