"""
日内策略探索 — 4 种思路 (15m / 30m / 1h 全币种)

1. 均值回归：RSI超买超卖 + 布林带触碰，震荡市中做回归
2. 多时间框架：4h 牛熊判定方向过滤 + 1h EMA交叉入场
3. 波动率突破：ATR 收缩后扩张，顺势突破
4. 成交量：OBV 背离 + VWAP 回归

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

import asyncio
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
from engine.example.regime_all import RegimeDetector3, RegimeEmaConfig, RegimeEmaStrategy


# ════════════════════════════════════════════════════════
# 策略 1：均值回归 — RSI + 布林带
# ════════════════════════════════════════════════════════


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):
    """RSI 极端 + 布林带确认 → 均值回归，ATR 止损"""

    strategy_type = "mean_rev"

    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 = ""  # "long" / "short"
        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


# ════════════════════════════════════════════════════════
# 策略 2：多时间框架 — 4h 方向 + 1h 入场
# ════════════════════════════════════════════════════════


class MultiTFConfig(StrategyConfig):
    fast: int = 20
    slow: int = 100
    atr_stop: float = 2.0
    # 4h 数据由策略内部自动加载


class MultiTFStrategy(BaseStrategy):
    """4h 牛熊判定方向过滤，1h EMA 交叉入场，只顺大势"""

    strategy_type = "multi_tf"

    def __init__(self, c: MultiTFConfig):
        super().__init__(c)
        self.cfg = c
        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")
        # 4h 牛熊判定 — 在 on_start 中加载
        self._regime_map: dict[int, str] = {}  # timestamp_hour -> regime
        self._4h_loaded = False

    async def on_start(self) -> None:
        """加载 4h 数据并预计算牛熊判定"""
        await super().on_start()
        if self._4h_loaded:
            return
        try:
            ds = DataService(config.db)
            await ds.connect()
            try:
                klines_4h = await ds.fetch_klines(
                    symbol=self.cfg.symbol, interval="4h",
                    start_time=datetime(2017, 1, 1),
                    end_time=datetime(2026, 12, 31),
                    limit=1_000_000,
                )
                detector = RegimeDetector3()
                for k in klines_4h:
                    detector.update(k.close)
                    idx = len(detector._e200) - 1
                    if idx >= 220:
                        regime = detector.detect(k.close, idx)
                        # 4h bar 覆盖的时间窗口（按小时取整）
                        hour_key = int(k.open_time / 3_600_000)
                        for h in range(4):
                            self._regime_map[hour_key + h] = regime
                self._4h_loaded = True
            finally:
                await ds.close()
        except Exception:
            pass  # 加载失败则不做过滤

    def _get_regime(self, ts_ms: float) -> str:
        """根据 1h bar 时间戳查找对应 4h 牛熊状态"""
        hour_key = int(ts_ms / 3_600_000)
        return self._regime_map.get(hour_key, "sideways")

    async def on_kline(self, k: Kline) -> Optional[Signal]:
        self._ema_fast.update(k.close)
        self._ema_slow.update(k.close)
        self._atr.update(k.high, k.low, k.close)

        n = len(self._ema_fast)
        if n < self.cfg.slow + 5:
            return None

        cf, cs = self._ema_fast[-1], self._ema_slow[-1]
        pf, ps = self._ema_fast[-2], self._ema_slow[-2]
        ca = self._atr[-1]
        if cf == 0 or cs == 0 or ca == 0:
            return None

        golden = pf <= ps and cf > cs
        death = pf >= ps and cf < cs
        regime = self._get_regime(k.open_time)

        # ── 多头持仓 ──
        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:
                self._side = ""
                reason = "死叉" if death else "ATR止损"
                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:
                self._side = ""
                reason = "金叉" if golden else "ATR止损"
                return Signal(symbol=self.cfg.symbol, side="BUY", reason=reason, timestamp=k.open_time)

        # ── 入场：必须顺4h方向 ──
        else:
            if golden and regime == "bull":
                self._side = "long"
                self._hp = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="4h牛+金叉", timestamp=k.open_time)
            elif death and regime == "bear":
                self._side = "short"
                self._lp = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="4h熊+死叉", timestamp=k.open_time)

        return None


# ════════════════════════════════════════════════════════
# 策略 3：波动率突破 — ATR 收缩扩张
# ════════════════════════════════════════════════════════


class VolBreakConfig(StrategyConfig):
    atr_period: int = 14
    squeeze_period: int = 20  # ATR 收缩回看窗口
    squeeze_ratio: float = 0.7  # 当前 ATR < 最低 ATR * ratio 时视为收缩
    atr_stop: float = 2.0


class VolBreakStrategy(BaseStrategy):
    """ATR 收缩到极致后扩张 → 顺势突破，ATR 止损"""

    strategy_type = "vol_break"

    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 收缩检测：当前 ATR 是否处于 squeeze_period 内的最低水平
        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 扩张信号
        atr_expanding = atr_now > atr_prev * 1.05 if atr_prev > 0 else False

        # 趋势方向
        cf = self._ema_fast[-1]
        cs = 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


# ════════════════════════════════════════════════════════
# 策略 4：成交量 — OBV 背离 + VWAP 回归
# ════════════════════════════════════════════════════════


class VolumeConfig(StrategyConfig):
    obv_lookback: int = 20  # OBV 背离检测窗口
    vwap_std: float = 2.0  # VWAP 偏离标准差倍数
    atr_stop: float = 2.0


class VolumeStrategy(BaseStrategy):
    """OBV 背离（价格新低但 OBV 未新低→看涨）+ VWAP 偏离回归"""

    strategy_type = "volume"

    def __init__(self, c: VolumeConfig):
        super().__init__(c)
        self.cfg = c
        self._closes: list[float] = []
        self._highs: list[float] = []
        self._lows: list[float] = []
        self._volumes: list[float] = []
        self._obv: list[float] = []  # 增量 OBV
        self._obv_val: float = 0.0
        self._atr = AtrInc(14)
        self._cum_pv: float = 0.0  # 累积 price*volume
        self._cum_vol: float = 0.0  # 累积 volume
        self._side: str = ""
        self._entry_price: float = 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)
        self._volumes.append(k.volume)
        self._atr.update(k.high, k.low, k.close)

        # 增量 OBV
        n = len(self._closes)
        if n == 1:
            self._obv_val = k.volume
        else:
            if k.close > self._closes[-2]:
                self._obv_val += k.volume
            elif k.close < self._closes[-2]:
                self._obv_val -= k.volume
        self._obv.append(self._obv_val)

        # 增量 VWAP
        typical = (k.high + k.low + k.close) / 3.0
        self._cum_pv += typical * k.volume
        self._cum_vol += k.volume
        vwap = self._cum_pv / self._cum_vol if self._cum_vol > 0 else k.close

        if n < self.cfg.obv_lookback + 5:
            return None

        ca = self._atr[-1]
        if ca == 0:
            return None

        # OBV 背离检测：价格新低但 OBV 未新低 → 潜在反转
        lookback = self.cfg.obv_lookback
        price_window = self._closes[-lookback:]
        obv_window = self._obv[-lookback:]
        price_made_new_low = min(price_window) == price_window[-1]
        obv_not_new_low = min(obv_window) < obv_window[-1]
        obv_bull_div = price_made_new_low and obv_not_new_low

        # OBV 负背离：价格新高但 OBV 未新高
        price_made_new_high = max(price_window) == price_window[-1]
        obv_not_new_high = max(obv_window) > obv_window[-1]
        obv_bear_div = price_made_new_high and obv_not_new_high

        # VWAP 偏离度
        vwap_dev = (k.close - vwap) / vwap if vwap > 0 else 0

        # ── 持仓管理 ──
        if self._side == "long":
            stop = self._entry_price - self.cfg.atr_stop * ca
            if k.close < stop or vwap_dev < -0.01:  # 回到 VWAP 下方
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="SELL", reason="止损或回VWAP", timestamp=k.open_time)

        elif self._side == "short":
            stop = self._entry_price + self.cfg.atr_stop * ca
            if k.close > stop or vwap_dev > 0.01:
                self._side = ""
                return Signal(symbol=self.cfg.symbol, side="BUY", reason="止损或回VWAP", timestamp=k.open_time)

        # ── 入场 ──
        else:
            if obv_bull_div and vwap_dev < -self.cfg.vwap_std * 0.02:  # 价格显著低于 VWAP
                self._side = "long"
                self._entry_price = k.close
                return Signal(symbol=self.cfg.symbol, side="BUY", reason=f"OBV底背离+VWAP下方", timestamp=k.open_time)
            elif obv_bear_div and vwap_dev > self.cfg.vwap_std * 0.02:
                self._side = "short"
                self._entry_price = k.close
                return Signal(symbol=self.cfg.symbol, side="SELL", reason=f"OBV顶背离+VWAP上方", timestamp=k.open_time)

        return None


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

SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT"]
INTERVALS = ["15m", "30m", "1h"]

STRATEGIES = {
    "1.均值回归": (MeanRevConfig, MeanRevStrategy),
    "2.多TF(4h+1h)": (MultiTFConfig, MultiTFStrategy),
    "3.波动突破": (VolBreakConfig, VolBreakStrategy),
    "4.成交量": (VolumeConfig, VolumeStrategy),
}

# 均值回归参数
MEAN_REV_PARAMS = {
    "BTCUSDT": {"rsi_period": 14, "rsi_oversold": 25, "rsi_overbought": 75, "bb_period": 20, "bb_std": 2.0, "atr_stop": 1.5},
    "ETHUSDT": {"rsi_period": 14, "rsi_oversold": 25, "rsi_overbought": 75, "bb_period": 20, "bb_std": 2.0, "atr_stop": 1.5},
    "BNBUSDT": {"rsi_period": 14, "rsi_oversold": 25, "rsi_overbought": 75, "bb_period": 20, "bb_std": 2.0, "atr_stop": 1.5},
    "SOLUSDT": {"rsi_period": 14, "rsi_oversold": 25, "rsi_overbought": 75, "bb_period": 20, "bb_std": 2.0, "atr_stop": 1.5},
}

# 多TF参数
MULTI_TF_PARAMS = {
    "BTCUSDT": {"fast": 20, "slow": 100, "atr_stop": 2.0},
    "ETHUSDT": {"fast": 20, "slow": 100, "atr_stop": 2.0},
    "BNBUSDT": {"fast": 20, "slow": 100, "atr_stop": 2.0},
    "SOLUSDT": {"fast": 20, "slow": 100, "atr_stop": 2.0},
}

# 波动突破参数
VOL_BREAK_PARAMS = {
    "BTCUSDT": {"atr_period": 14, "squeeze_period": 20, "squeeze_ratio": 0.7, "atr_stop": 2.0},
    "ETHUSDT": {"atr_period": 14, "squeeze_period": 20, "squeeze_ratio": 0.7, "atr_stop": 2.0},
    "BNBUSDT": {"atr_period": 14, "squeeze_period": 20, "squeeze_ratio": 0.7, "atr_stop": 2.0},
    "SOLUSDT": {"atr_period": 14, "squeeze_period": 20, "squeeze_ratio": 0.7, "atr_stop": 2.0},
}

# 成交量参数
VOLUME_PARAMS = {
    "BTCUSDT": {"obv_lookback": 20, "vwap_std": 2.0, "atr_stop": 2.0},
    "ETHUSDT": {"obv_lookback": 20, "vwap_std": 2.0, "atr_stop": 2.0},
    "BNBUSDT": {"obv_lookback": 20, "vwap_std": 2.0, "atr_stop": 2.0},
    "SOLUSDT": {"obv_lookback": 20, "vwap_std": 2.0, "atr_stop": 2.0},
}


async def run_one(engine_factory, strategy_cls, config_cls, params, symbol, interval, start, end):
    """运行单组回测"""
    INITIAL = 10_000.0
    sc = config_cls(symbol=symbol, **params)
    bt = BacktestConfig(symbol=symbol, interval=interval, start_time=start, end_time=end, initial_capital=INITIAL)
    engine = engine_factory(bt)
    r = await engine.run(strategy_cls, sc)
    m = r.metrics
    return m, INITIAL, m.final_equity


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

    # 预加载所有数据范围
    ranges: dict[str, dict[str, tuple[datetime, datetime]]] = {}
    for interval in INTERVALS:
        ranges[interval] = {}
        for symbol in SYMBOLS:
            try:
                s, e = await ds.fetch_symbol_date_range(symbol, interval)
                ranges[interval][symbol] = (s, e)
            except Exception:
                pass
    await ds.close()

    all_results: list[dict] = []

    print()
    print("═" * 135)
    print("  日内策略探索 — 4思路 × 4币种 × 3周期")
    print("═" * 135)

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

        for interval in INTERVALS:
            for symbol in SYMBOLS:
                if symbol not in ranges.get(interval, {}):
                    continue
                start, end = ranges[interval][symbol]

                # 选择参数
                if strategy_name == "1.均值回归":
                    params = MEAN_REV_PARAMS[symbol]
                elif strategy_name == "2.多TF(4h+1h)":
                    params = MULTI_TF_PARAMS[symbol]
                elif strategy_name == "3.波动突破":
                    params = VOL_BREAK_PARAMS[symbol]
                else:
                    params = VOLUME_PARAMS[symbol]

                t0 = time.time()
                try:
                    m, initial, final_equity = await run_one(
                        lambda bt: LongShortEngine(bt, db_config=config.db),
                        strategy_cls, config_cls, params,
                        symbol, interval, start, end,
                    )
                    elapsed = time.time() - t0
                except Exception as ex:
                    print(f"  {symbol:<10} {interval:<6} {'错误: ' + str(ex)[:40]}")
                    continue

                print(f"  {symbol:<10} {interval:<6} {initial:>7.0f} {final_equity:>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.total_trades:>6} {elapsed:>6.1f}s")

                all_results.append({
                    "strategy": strategy_name, "interval": interval, "symbol": symbol,
                    "return": m.total_return_pct, "annual": m.annual_return_pct,
                    "sharpe": m.sharpe_ratio, "dd": m.max_drawdown_pct,
                    "trades": m.total_trades, "initial": initial, "final": final_equity,
                })

    # ── 汇总：每种策略的最佳组合 ──
    print(f"\n\n  ■ 各策略最佳组合 (按夏普排名)")
    print(f"  {'策略':<18} {'级别':<6} {'币种':<10} {'本金':>7} {'终值':>9} {'总收益%':>8} {'年化%':>8} {'夏普':>7} {'回撤%':>8} {'交易':>6}")
    print("  " + "─" * 115)

    # 每种策略取最佳
    for sn in STRATEGIES:
        candidates = [r for r in all_results if r["strategy"] == sn]
        if not candidates:
            continue
        best = max(candidates, key=lambda x: x["sharpe"])
        print(f"  {sn:<18} {best['interval']:<6} {best['symbol']:<10} {best['initial']:>7.0f} {best['final']:>9.0f} {best['return']:>7.1f}% {best['annual']:>7.1f}% {best['sharpe']:>7.2f} {best['dd']:>7.1f}% {best['trades']:>6}")

    print("\n═" * 135)


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