"""
网络验证策略探索 — 5 个业界知名策略 × 全币种 × 1h

1. 海龟交易 (Turtle)       — Donchian 20/10 通道突破 + 2N ATR 止损
2. 超级趋势 (SuperTrend)    — ATR(10)×3 动态跟踪止损
3. MACD金叉死叉             — MACD(12,26,9) 零轴交叉 + ATR 止损
4. 布林收缩爆发 (BBSqueeze) — BB 收缩至 KC 内部后扩张突破
5. 三均线排列 (TripleEMA)   — EMA(10,30,60) 多头/空头排列 + ATR 追踪

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

import asyncio
import json
import sys
import time
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.data import DataService
from engine.indicators.incremental import EmaInc, AtrInc, RsiInc, BbInc
from engine.example.long_short import LongShortEngine

SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]
INTERVAL = "1h"
INITIAL = 10_000.0


# ════════════════════════════════════════════════════════
# 策略 1：海龟交易 (Turtle Trading)
# Richard Dennis & William Eckhardt, 1983
# 20 日高点突破入场，10 日低点突破出场，2N ATR 止损
# ════════════════════════════════════════════════════════


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


class TurtleStrategy(BaseStrategy):
    """海龟交易 — Donchian 通道突破 + ATR 动态止损"""

    strategy_type = "turtle"

    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_entry: float = 0.0
        self._lowest_since_entry: 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

        # Donchian 通道应排除当前 bar（用前 N 根 bar 计算）
        donchian_high = max(self._highs[-(self.cfg.entry_period + 1):-1])
        donchian_low = min(self._lows[-(self.cfg.entry_period + 1):-1])
        donchian_exit_high = max(self._highs[-(self.cfg.exit_period + 1):-1])
        donchian_exit_low = min(self._lows[-(self.cfg.exit_period + 1):-1])

        # ── 持仓管理 ──
        if self._side == "long":
            self._highest_since_entry = max(self._highest_since_entry, k.high)
            stop = self._entry_price - self.cfg.atr_stop * ca
            trail_stop = self._highest_since_entry - self.cfg.atr_stop * ca * 0.5
            if k.close < donchian_exit_low or k.close < max(stop, trail_stop):
                self._side = ""
                reason = "跌破10日低点" if k.close < donchian_exit_low else "ATR止损"
                return Signal(symbol=self.cfg.symbol, side="SELL", reason=reason, timestamp=k.open_time, confidence=0.25)

        elif self._side == "short":
            self._lowest_since_entry = min(self._lowest_since_entry, k.low)
            stop = self._entry_price + self.cfg.atr_stop * ca
            trail_stop = self._lowest_since_entry + self.cfg.atr_stop * ca * 0.5
            if k.close > donchian_exit_high or k.close > min(stop, trail_stop):
                self._side = ""
                reason = "突破10日高点" if k.close > donchian_exit_high else "ATR止损"
                return Signal(symbol=self.cfg.symbol, side="BUY", reason=reason, timestamp=k.open_time, confidence=0.25)

        # ── 入场：仅在有明显突破幅度时入场 ──
        else:
            breakout_margin = 0.002  # 需突破通道 0.2% 以上
            if k.close > donchian_high * (1 + breakout_margin):
                self._side = "long"
                self._entry_price = k.close
                self._highest_since_entry = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="突破20日高点", timestamp=k.open_time, confidence=0.25)
            elif k.close < donchian_low * (1 - breakout_margin):
                self._side = "short"
                self._entry_price = k.close
                self._lowest_since_entry = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="跌破20日低点", timestamp=k.open_time, confidence=0.25)

        return None


# ════════════════════════════════════════════════════════
# 策略 2：超级趋势 (SuperTrend)
# Olivier Seban, 广泛用于加密货币和商品
# ATR 动态跟踪止损，趋势翻转即反转
# ════════════════════════════════════════════════════════


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


class SuperTrendStrategy(BaseStrategy):
    """超级趋势 — ATR 动态跟踪止损，趋势跟踪"""

    strategy_type = "supertrend"

    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  # 1=多, -1=空
        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 prev_trend == prev_trend:  # always true, just need placeholder
            pass
        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 金叉死叉
# Gerald Appel, 1970s
# MACD(12,26,9) 零轴附近金叉/死叉 + ATR 止损
# ════════════════════════════════════════════════════════


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


class MacdCrossStrategy(BaseStrategy):
    """MACD 金叉死叉 — 零轴以上只做多，零轴以下只做空"""

    strategy_type = "macd_cross"

    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] = []  # MACD 线值
        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

        # 信号线 = EMA of MACD，简化：用列表算
        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 = self._macd_vals[-1]
        cur_s = self._signal_vals[-1]
        prev_m = self._macd_vals[-2]
        prev_s = 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
                reason = "ATR止损" if k.close < stop else "MACD死叉"
                return Signal(symbol=self.cfg.symbol, side="SELL", reason=reason, 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
                reason = "ATR止损" if k.close > stop else "MACD金叉"
                return Signal(symbol=self.cfg.symbol, side="BUY", reason=reason, 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)
# John Bollinger, 2002
# BB 在 KC 内部收缩 → 扩张突破入场
# ════════════════════════════════════════════════════════


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):
    """布林收缩爆发 — BB 收缩到极限后扩张，顺势入场"""

    strategy_type = "bb_squeeze"

    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)  # Keltner 中轨
        self._atr_kc = AtrInc(c.kc_period)  # Keltner 宽度的 ATR
        self._atr_stop = AtrInc(14)  # 止损 ATR
        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)

        # BB 在 KC 内部 = 收缩
        is_squeezed = bb_u < kc_u and bb_l > kc_l
        # BB 宽度处于近期最低水平
        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

        # 收缩释放信号：之前收缩，现在 BB 扩张出 KC
        was_squeezed = self._was_squeezed
        fired = False
        if is_squeezed:
            self._was_squeezed = True
            self._squeeze_bars += 1
        elif self._was_squeezed:
            # BB 不再在 KC 内部 → 收缩释放
            self._was_squeezed = False
            self._squeeze_bars = 0
            fired = True

        # 方向判断：用价格与 BB 中轨关系 + EMA(5) 动量
        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="ATR止损或转弱", 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="ATR止损或转强", 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)
# 经典三线开花 — EMA(10,30,60) 多头/空头排列 + ATR 追踪止损
# ════════════════════════════════════════════════════════


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 = "triple_ema"

    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_entry: float = 0.0
        self._lowest_since_entry: 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 = self._ema_fast[-1]
        em = self._ema_mid[-1]
        es = self._ema_slow[-1]
        pf = self._ema_fast[-2]
        pm = 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_entry = max(self._highest_since_entry, k.high)
            trail = self._highest_since_entry - self.cfg.atr_stop * ca
            if fast_cross_mid_down or k.close < trail:
                self._side = ""
                reason = "快线下穿中线" if fast_cross_mid_down else "ATR追踪止损"
                return Signal(symbol=self.cfg.symbol, side="SELL", reason=reason, timestamp=k.open_time, confidence=0.25)

        # ── 空头持仓 ──
        elif self._side == "short":
            self._lowest_since_entry = min(self._lowest_since_entry, k.low)
            trail = self._lowest_since_entry + self.cfg.atr_stop * ca
            if fast_cross_mid_up or k.close > trail:
                self._side = ""
                reason = "快线上穿中线" if fast_cross_mid_up else "ATR追踪止损"
                return Signal(symbol=self.cfg.symbol, side="BUY", reason=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_entry = 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_entry = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="三线空头排列+快线死叉", timestamp=k.open_time, confidence=0.25)

        return None


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

STRATEGIES = {
    "1.海龟交易(Turtle)": (TurtleConfig, TurtleStrategy),
    "2.超级趋势(SuperTrend)": (SuperTrendConfig, SuperTrendStrategy),
    "3.MACD金叉死叉": (MacdCrossConfig, MacdCrossStrategy),
    "4.布林收缩爆发(BBSqueeze)": (BBSqueezeConfig, BBSqueezeStrategy),
    "5.三均线排列(TripleEMA)": (TripleEmaConfig, TripleEmaStrategy),
}


def make_config(config_cls, symbol):
    """根据策略类型创建默认参数配置"""
    if config_cls == TurtleConfig:
        return config_cls(symbol=symbol, entry_period=20, exit_period=10, atr_period=20, atr_stop=2.0)
    elif config_cls == SuperTrendConfig:
        return config_cls(symbol=symbol, atr_period=10, multiplier=3.0)
    elif config_cls == MacdCrossConfig:
        return config_cls(symbol=symbol, fast=12, slow=26, signal=9, atr_period=14, atr_stop=2.0)
    elif config_cls == BBSqueezeConfig:
        return config_cls(symbol=symbol, bb_period=20, bb_std=2.0, kc_period=20, kc_mult=1.5, squeeze_lookback=30, atr_stop=2.0)
    elif config_cls == TripleEmaConfig:
        return config_cls(symbol=symbol, fast=10, mid=30, slow=60, atr_period=14, atr_stop=2.0)
    else:
        raise ValueError(f"未知策略: {config_cls}")


async def run_one(config_cls, strategy_cls, symbol, start, end):
    sc = make_config(config_cls, symbol)
    bt = BacktestConfig(symbol=symbol, interval=INTERVAL, start_time=start, end_time=end, initial_capital=INITIAL)
    engine = LongShortEngine(bt, db_config=config.db)
    r = await engine.run(strategy_cls, sc)
    return r


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

    # 获取数据范围
    ranges: dict[str, tuple] = {}
    for symbol in SYMBOLS:
        try:
            s, e = await ds.fetch_symbol_date_range(symbol, INTERVAL)
            ranges[symbol] = (s, e)
            print(f"  {symbol}: {s.date()} ~ {e.date()}")
        except Exception as ex:
            print(f"  {symbol}: 获取范围失败 {ex}")

    await ds.close()

    # 汇总数据
    all_results: list[dict] = []
    detail_results: dict[str, dict[str, dict]] = {}  # 用于保存详细结果

    print()
    print("═" * 140)
    print("  5 策略 × 4 币种 × 1h — 网络验证策略扫描")
    print("═" * 140)
    print()

    for strategy_name, (config_cls, strategy_cls) in STRATEGIES.items():
        print(f"  ■ {strategy_name}")
        print(f"  {'币种':<10} {'本金':>7} {'终值':>9} {'总收益%':>8} {'年化%':>8} {'夏普':>7} {'回撤%':>8} {'胜率%':>7} {'盈亏比':>7} {'交易':>6} {'耗时s':>7}")
        print("  " + "─" * 120)

        detail_results[strategy_name] = {}

        for symbol in SYMBOLS:
            if symbol not in ranges:
                continue
            start, end = ranges[symbol]

            t0 = time.time()
            try:
                r = await run_one(config_cls, strategy_cls, symbol, start, end)
                elapsed = time.time() - t0
            except Exception as ex:
                print(f"  {symbol:<10} {'错误: ' + str(ex)[:50]}")
                continue

            m = r.metrics
            final = m.final_equity
            print(f"  {symbol:<10} {INITIAL:>7.0f} {final:>9.0f} {m.total_return_pct:>7.1f}% {m.annual_return_pct:>7.1f}% {m.sharpe_ratio:>7.2f} {m.max_drawdown_pct:>7.1f}% {m.win_rate*100:>6.1f}% {m.profit_factor:>7.2f} {m.total_trades:>6} {elapsed:>6.1f}s")

            all_results.append({
                "strategy": strategy_name,
                "symbol": symbol,
                "interval": INTERVAL,
                "initial": INITIAL,
                "final": final,
                "total_return": m.total_return_pct,
                "annual_return": m.annual_return_pct,
                "sharpe": m.sharpe_ratio,
                "drawdown": m.max_drawdown_pct,
                "win_rate": m.win_rate * 100,
                "profit_factor": m.profit_factor,
                "trades": m.total_trades,
                "best": m.best_trade_pnl,
                "worst": m.worst_trade_pnl,
                "avg": m.avg_trade_pnl,
                "calmar": m.calmar_ratio,
                "start_date": str(start.date()),
                "end_date": str(end.date()),
            })

            # 详细交易记录
            detail_results[strategy_name][symbol] = {
                "config": {
                    "symbol": symbol,
                    "interval": INTERVAL,
                    "start": str(start.date()),
                    "end": str(end.date()),
                    "initial_capital": INITIAL,
                },
                "metrics": {
                    "total_return_pct": m.total_return_pct,
                    "annual_return_pct": m.annual_return_pct,
                    "sharpe_ratio": m.sharpe_ratio,
                    "max_drawdown_pct": m.max_drawdown_pct,
                    "win_rate": m.win_rate * 100,
                    "profit_factor": m.profit_factor,
                    "total_trades": m.total_trades,
                    "avg_trade_pnl": m.avg_trade_pnl,
                    "best_trade_pnl": m.best_trade_pnl,
                    "worst_trade_pnl": m.worst_trade_pnl,
                    "final_equity": m.final_equity,
                    "calmar_ratio": m.calmar_ratio,
                },
                "trades": [
                    {
                        "side": t.side,
                        "price": t.price,
                        "quantity": t.quantity,
                        "pnl": t.pnl,
                        "reason": t.reason,
                        "timestamp": t.timestamp,
                        "timestamp_str": datetime.fromtimestamp(t.timestamp / 1000, tz=timezone.utc).strftime("%Y-%m-%d %H:%M"),
                    }
                    for t in r.trades
                ],
            }
        print()

    # ── 汇总：每种策略的最佳/最差 ──
    print("═" * 140)
    print("  ■ 各策略汇总 (按年化收益排序)")
    print(f"  {'策略':<28} {'币种':<10} {'本金':>7} {'终值':>9} {'总收益%':>8} {'年化%':>8} {'夏普':>7} {'回撤%':>8} {'胜率%':>7} {'盈亏比':>7} {'交易':>6}")
    print("  " + "─" * 120)

    for sn in STRATEGIES:
        candidates = [r for r in all_results if r["strategy"] == sn]
        if not candidates:
            continue
        # 按年化排序，显示所有币种
        candidates.sort(key=lambda x: x["annual_return"], reverse=True)
        for c in candidates:
            marker = " ★" if c == candidates[0] else "  "
            print(f"  {sn:<26}{marker} {c['symbol']:<10} {c['initial']:>7.0f} {c['final']:>9.0f} {c['total_return']:>7.1f}% {c['annual_return']:>7.1f}% {c['sharpe']:>7.2f} {c['drawdown']:>7.1f}% {c['win_rate']:>6.1f}% {c['profit_factor']:>7.2f} {c['trades']:>6}")

    print("\n═" * 140)

    # ── 保存结果到 JSON ──
    output_file = _project_root / "engine" / "example" / "web_strategies_result.json"
    with open(output_file, "w", encoding="utf-8") as f:
        json.dump({
            "summary": all_results,
            "detail": detail_results,
            "run_time": datetime.now(timezone.utc).isoformat(),
        }, f, ensure_ascii=False, indent=2, default=str)
    print(f"\n  详细结果已保存至: {output_file}")


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