"""
多时间框架 v3 — 1d 定趋势 / 4h 找买点

策略：
  1d EMA(20,50) → 日线金叉=多头趋势，死叉=空头
  4h 回调入场 → 日线多头中，4h 价格回调到 EMA20 附近 + 收阳 → 买入
  4h EMA死叉 或 日线转空 → 卖出
  ATR 动态止损

币种：BTC/ETH/BNB/SOL | 2024-2026

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

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

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

from engine.common.base import BaseStrategy, Signal, StrategyConfig
from engine.common.models import Kline
from engine.common.config import config
from engine.backtest import BacktestEngine, BacktestConfig, BacktestResult
from engine.data import DataService
from engine.indicators import ema, atr


class DailyTrendConfig(StrategyConfig):
    """1d趋势+4h交易 策略配置"""

    # 1d 趋势参数
    trend_fast: int = 20
    trend_slow: int = 50

    # 4h 交易参数
    entry_ema: int = 20     # 4h 回调到该均线附近买入
    atr_period: int = 14
    atr_stop_mult: float = 2.5

    # 数据范围
    data_start: Optional[datetime] = None
    data_end: Optional[datetime] = None


class DailyTrendStrategy(BaseStrategy):
    """日线定大势，4h 抓回调

    ┌──────────────────────────────────────────────┐
    │  1d EMA(20,50)                                │
    │  ├─ 金叉 → 多头区间                           │
    │  └─ 死叉 → 空仓等待                           │
    │                                               │
    │  4h K线（引擎推送）                             │
    │  ├─ 多头区间 + 回调到 EMA20 + 收阳 → 买入       │
    │  ├─ EMA死叉 → 卖出                            │
    │  └─ ATR 动态止损                              │
    └──────────────────────────────────────────────┘
    """

    strategy_type = "daily_trend"

    def __init__(self, c: DailyTrendConfig):
        super().__init__(c)
        self.cfg = c

        # 1d 预加载
        self._klines_1d: list[Kline] = []
        self._ema_fast_1d: list[float] = []
        self._ema_slow_1d: list[float] = []

        # 4h 积累
        self._closes: list[float] = []
        self._highs: list[float] = []
        self._lows: list[float] = []

        self._highest: float = 0.0
        self._in_position = False

    async def on_start(self):
        from engine.common.config import config as app_config
        ds = DataService(app_config.db)
        await ds.connect()
        try:
            self._klines_1d = await ds.fetch_klines(
                symbol=self.cfg.symbol, interval="1d",
                start_time=self.cfg.data_start, end_time=self.cfg.data_end, limit=1_000_000,
            )
            closes_1d = [k.close for k in self._klines_1d]
            self._ema_fast_1d = ema(closes_1d, self.cfg.trend_fast)
            self._ema_slow_1d = ema(closes_1d, self.cfg.trend_slow)
        finally:
            await ds.close()
        await super().on_start()

    def _get_1d_state(self, ts: float) -> tuple[bool, bool, float, float]:
        """获取日线趋势状态（仅已完成K线）
        Returns: (is_bull, is_golden_cross, price, ema_fast)
        """
        if not self._klines_1d:
            return False, False, 0.0, 0.0
        for i in range(len(self._klines_1d) - 1, -1, -1):
            if self._klines_1d[i].close_time <= ts:
                ef = self._ema_fast_1d[i]
                es = self._ema_slow_1d[i]
                if ef == 0 or es == 0:
                    return False, False, self._klines_1d[i].close, ef
                # 金叉 = EMA20 > EMA50
                golden = ef > es
                # 趋势向上 = 价格在EMA20上方（额外确认）
                price_above = self._klines_1d[i].close > ef
                return golden and price_above, golden, self._klines_1d[i].close, ef
        return False, False, 0.0, 0.0

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._closes.append(k.close)
        self._highs.append(k.high)
        self._lows.append(k.low)
        n = len(self._closes)
        if n < self.cfg.entry_ema + 20:
            return None

        ema_4h = ema(self._closes, self.cfg.entry_ema)
        atr_vals = atr(self._highs, self._lows, self._closes, self.cfg.atr_period)
        cur_ema, cur_atr = ema_4h[-1], atr_vals[-1]
        if cur_ema == 0 or cur_atr == 0:
            return None

        is_bull, is_golden, price_1d, ema_1d = self._get_1d_state(k.open_time)

        # ── 出场 ──
        if self._in_position:
            self._highest = max(self._highest, k.high)
            stop = self._highest - self.cfg.atr_stop_mult * cur_atr

            # 4h EMA死叉（用EMA9代替，更快反应）
            ema9_vals = ema(self._closes, 9)
            ema20_vals = ema(self._closes, 20)
            if len(ema9_vals) > 2 and ema9_vals[-2] >= ema20_vals[-2] and ema9_vals[-1] < ema20_vals[-1]:
                self._in_position = False
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="4h EMA死叉", timestamp=k.open_time)

            if k.close < stop:
                self._in_position = False
                return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"ATR止损", timestamp=k.open_time)

        # ── 入场 ──
        if not self._in_position and is_bull:
            # 4h 价格回调到 EMA20 附近（偏离不超过 1.5 倍 ATR）
            distance = abs(k.close - cur_ema) / cur_atr
            near_ema = distance < 1.5
            # K线收阳
            bullish_bar = k.close > k.open
            # EMA20 走平或向上
            ema_rising = n >= 4 and ema_4h[-1] >= ema_4h[-3]

            if near_ema and bullish_bar and ema_rising:
                self._in_position = True
                self._highest = k.close
                return Signal(
                    symbol=self.cfg.symbol, side="BUY",
                    confidence=0.8,
                    reason=f"1d多头+4h回调 P={k.close:.0f}≈EMA={cur_ema:.0f} dist={distance:.1f}σ",
                    timestamp=k.open_time,
                )

        return None


# ════════════════════════════════════════════════════════
# 运行
# ════════════════════════════════════════════════════════

SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]
DATE_START = datetime(2024, 1, 1)
DATE_END = datetime(2026, 1, 1)


# v3 EMA最优结果（用于对比）
EMA_V3 = {
    "BTCUSDT":  (39.9, 1.03, -11.5, 20, 55.0),
    "ETHUSDT":  (53.6, 1.04, -15.3, 18, 38.9),
    "BNBUSDT":  (26.0, 0.64, -23.4, 23, 34.8),
    "SOLUSDT":  (73.6, 1.18, -25.7, 13, 46.2),
}


async def main():
    print()
    print("═" * 110)
    print("  多时间框架 v3 — 1d 定趋势 / 4h 找回调买点 | 2024-2026")
    print("═" * 110)
    print(f"  {'币种':<10} {'版本':<22} {'收益%':>7} {'夏普':>6} {'回撤%':>7} {'交易':>5} {'胜率%':>6} {'盈亏比':>6}")
    print("─" * 110)

    for symbol in SYMBOLS:
        sc = DailyTrendConfig(
            symbol=symbol,
            data_start=DATE_START, data_end=DATE_END,
        )
        bt = BacktestConfig(symbol=symbol, interval="4h",
                            start_time=DATE_START, end_time=DATE_END, initial_capital=10_000.0)
        engine = BacktestEngine(bt, db_config=config.db)
        r = await engine.run(DailyTrendStrategy, sc)
        m = r.metrics

        # v3 对比
        v3 = EMA_V3[symbol]
        print(f"  {symbol:<10} {'1d趋势+4h回调':<22} {m.total_return_pct:>6.1f}% {m.sharpe_ratio:>6.2f} {m.max_drawdown_pct:>6.1f}% {m.total_trades:>5} {m.win_rate*100:>5.1f}% {m.profit_factor:>6.2f}")
        print(f"  {'':<10} {'(对比) EMA v3 最优':<22} {v3[0]:>6.1f}% {v3[1]:>6.2f} {v3[2]:>6.1f}% {v3[3]:>5} {v3[4]:>5.1f}%")

        # 打印最近几笔出场
        sells = [t for t in r.trades if t.side == "SELL" and t.pnl is not None]
        if sells:
            recent = sells[-3:]
            for t in recent:
                dt = datetime.fromtimestamp(t.timestamp / 1000, tz=timezone.utc).strftime("%m-%d %H:%M")
                print(f"  {'':<10}   └ {dt} {t.pnl:>+8.2f} USDT  {t.reason}")
        print()

    print("═" * 110)


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