#!/usr/bin/env python3
"""
EMAMacdSignalGenerator - EMA + MACD + RSI + BB 综合策略信号生成器

模式1(TRENDING):EMA9 > EMA21 > EMA50 且 MACD柱 > 0 且 RSI > 50 → 做多
                  EMA9 < EMA21 < EMA50 且 MACD柱 < 0 且 RSI < 50 → 做空

模式2(RANGING):价格触及布林下轨 + RSI < 35 → 买入
                 价格触及布林上轨 + RSI > 65 → 卖出

信号生成器职责:
  - 只读 K线数据(bars)
  - 不感知账户余额、持仓、配置
  - 输出带 schema_version 和 trace_id 的标准 Signal dict
"""
import json
import time
import urllib.request
from pathlib import Path
from typing import Optional

from config_manager import get_effective_param
from .base import BaseSignalGenerator
from signal_generators.trend_stage_filter import trend_stage_score
try:
    from src.market_context import is_range_regime, normalize_regime_label
except ModuleNotFoundError:
    from market_context import is_range_regime, normalize_regime_label


# #region debug-point shared:ema-dispatch
def _debug_ema_event(hypothesis_id: str, location: str, msg: str, data: Optional[dict] = None, run_id: str = "pre-fix") -> None:
    pass
# #endregion


# ============ 内部技术指标(纯数学,与外部无关)============

def _detect_regime_v2(bars, adx_val, atr_val, macd_line=None, signal_line=None):
    """
    1+3+2 状态判定机:解决 ADX 迟滞问题的智能漏斗模型。
    优先级:动能突变 > 价格突破 > MACD 交叉 > ADX 基础判定
    返回: (regime_label, volatility_surge, price_breakout)
    """
    if len(bars) < 50:
        return "UNCERTAIN", False, "NONE"

    # 最新 K 线与前几根 K 线
    latest_bar = bars[-1]
    prev_bar = bars[-2]

    # 1. 动能突变检测 (极端波动率)
    # 当前 K 线的实体长度 > 3 * ATR
    body_length = abs(latest_bar["close"] - latest_bar["open"])
    volatility_surge = False
    if atr_val and body_length > (3.0 * atr_val):
        volatility_surge = True
        return "EXTREME_TREND", volatility_surge, "NONE"

    # 3. 价格突破检测 (Price Action)
    # 检查是否突破了过去 48 根 K 线 (约 4 小时) 的极值
    recent_bars = bars[-49:-1] # 排除最新一根
    high_4h = max(b["high"] for b in recent_bars)
    low_4h = min(b["low"] for b in recent_bars)

    price_breakout = "NONE"
    if latest_bar["close"] > high_4h:
        price_breakout = "UPWARD"
        return "BREAKOUT", volatility_surge, price_breakout
    elif latest_bar["close"] < low_4h:
        price_breakout = "DOWNWARD"
        return "BREAKOUT", volatility_surge, price_breakout

    # ── MACD 金叉/死叉交叉判定（ADX 的先行信号）──
    # 金叉: MACD 上穿信号线 → 趋势启动提前判定
    # 死叉: MACD 下穿信号线 → 趋势衰竭提前判定
    # 条件: 前一根未交叉，当前已交叉，且 ADX >= 18（避免假信号）
    if macd_line is not None and signal_line is not None and adx_val >= 20:
        n = len(macd_line) - 1
        if n >= 2 and n < len(signal_line):
            cur_m, prev_m = macd_line[n], macd_line[n - 1]
            cur_s, prev_s = signal_line[n], signal_line[n - 1]
            if None not in (cur_m, prev_m, cur_s, prev_s):
                crossed_up = prev_m <= prev_s and cur_m > cur_s
                crossed_down = prev_m >= prev_s and cur_m < cur_s
                if crossed_up and latest_bar["close"] > bars[-3]["close"]:
                    return "EARLY_TREND", volatility_surge, "UPWARD"
                if crossed_down and latest_bar["close"] < bars[-3]["close"]:
                    return "EARLY_TREND", volatility_surge, "DOWNWARD"

    # 2. 回退到基础 ADX 判定
    if adx_val >= 24:
        return "TRENDING", volatility_surge, price_breakout
    else:
        return "RANGING", volatility_surge, price_breakout


def _ema(data, period):
    period = int(period)
    if len(data) < period:
        return [None] * len(data)
    k = 2.0 / (period + 1)
    ema = [None] * (period - 1)
    ema.append(sum(data[:period]) / period)
    for v in data[period:]:
        ema.append(v * k + ema[-1] * (1 - k))
    return ema


def _sma(data, period):
    period = int(period)
    result = []
    for i in range(len(data)):
        if i < period - 1:
            result.append(None)
        else:
            result.append(sum(data[i - period + 1:i + 1]) / period)
    return result


def _rsi(closes, period=14):
    period = int(period)
    if len(closes) < period + 1:
        return [None] * len(closes)
    gains, losses = [], []
    for i in range(1, len(closes)):
        delta = closes[i] - closes[i - 1]
        gains.append(max(delta, 0))
        losses.append(max(-delta, 0))
    result = [None] * (period + 1)
    avg_gain = sum(gains[:period]) / period
    avg_loss = sum(losses[:period]) / period
    if avg_loss < 1e-9:
        result.append(100)
    else:
        rs = avg_gain / avg_loss
        result.append(100 - 100 / (1 + rs))
    for i in range(period, len(gains)):
        avg_gain = (avg_gain * (period - 1) + gains[i]) / period
        avg_loss = (avg_loss * (period - 1) + losses[i]) / period
        if avg_loss < 1e-9:
            result.append(100)
        else:
            rs = avg_gain / avg_loss
            result.append(100 - 100 / (1 + rs))
    return result


def _macd(closes, fast=12, slow=26, signal=9):
    fast = int(fast)
    slow = int(slow)
    signal = int(signal)
    ema_fast = _ema(closes, fast)
    ema_slow = _ema(closes, slow)
    macd_line = [None] * len(closes)
    for i in range(len(closes)):
        if ema_fast[i] is not None and ema_slow[i] is not None:
            macd_line[i] = ema_fast[i] - ema_slow[i]
    signal_line = _ema([v if v is not None else 0 for v in macd_line], signal)
    histogram = [None] * len(closes)
    for i in range(len(closes)):
        if macd_line[i] is not None and signal_line[i] is not None:
            histogram[i] = macd_line[i] - signal_line[i]
    return macd_line, signal_line, histogram


def _macd_norm(hist_value: float, atr_value: float) -> float:
    """将 MACD 柱状图绝对值归一化为 ATR 倍数。

    归一化后阈值与价格水平解耦：
      macd_norm > 0.3 ATR → 有意义的多头动能
      macd_norm < -0.3 ATR → 有意义的空头动能
      macd_norm > 0.6 ATR → 强多头动能
      macd_norm < -0.6 ATR → 强空头动能
    """
    if atr_value is None or atr_value <= 0:
        return 0.0
    return hist_value / atr_value


def _detect_macd_divergence(closes: list, macd_line: list, lookback: int = 24) -> dict:
    """检测 MACD 顶背离 / 底背离。

    顶背离: 价格创 N 期新高但 MACD 未创新高 → 趋势衰竭
    底背离: 价格创 N 期新低但 MACD 未创新低 → 趋势反转
    返回: {"divergence": "BULLISH"|"BEARISH"|"NONE", "strength": 0-1}
    """
    if len(closes) < lookback + 5 or len(macd_line) < lookback + 5:
        return {"divergence": "NONE", "strength": 0.0}

    n = len(closes) - 1
    recent_closes = closes[n - lookback:n + 1]
    recent_macd = macd_line[n - lookback:n + 1]

    valid = [(i, c, m) for i, (c, m) in enumerate(zip(recent_closes, recent_macd))
             if c is not None and m is not None]
    if len(valid) < 10:
        return {"divergence": "NONE", "strength": 0.0}

    # 找价格极值点和对应的 MACD 值
    prices = [v[1] for v in valid]
    macds = [v[2] for v in valid]

    max_idx = max(range(len(prices)), key=prices.__getitem__)
    min_idx = min(range(len(prices)), key=prices.__getitem__)
    last_idx = len(valid) - 1

    # 顶背离：价格最高点出现在最后 1/3 区域且 MACD 未跟
    if max_idx >= len(valid) * 2 // 3:
        price_peak = prices[max_idx]
        macd_at_peak = macds[max_idx]
        # 检查当前价格是否接近峰顶且 MACD 明显更低
        if prices[last_idx] >= price_peak * 0.995 and macds[last_idx] < macd_at_peak * 0.8:
            strength = min(1.0, (macd_at_peak - macds[last_idx]) / max(abs(macd_at_peak), 0.01))
            return {"divergence": "BEARISH", "strength": round(strength, 4)}

    # 底背离：价格最低点出现在最后 1/3 区域且 MACD 未跟
    if min_idx >= len(valid) * 2 // 3:
        price_trough = prices[min_idx]
        macd_at_trough = macds[min_idx]
        if prices[last_idx] <= price_trough * 1.005 and macds[last_idx] > macd_at_trough * 0.8:
            strength = min(1.0, (macds[last_idx] - macd_at_trough) / max(abs(macd_at_trough), 0.01))
            return {"divergence": "BULLISH", "strength": round(strength, 4)}

    return {"divergence": "NONE", "strength": 0.0}


def _atr(bars, period=14):
    period = int(period)
    trs = []
    for i, b in enumerate(bars):
        if i == 0:
            trs.append(b["high"] - b["low"])
        else:
            hl = b["high"] - b["low"]
            hc = abs(b["high"] - bars[i-1]["close"])
            lc = abs(b["low"]  - bars[i-1]["close"])
            trs.append(max(hl, hc, lc))
    return _ema(trs, period)


def _bollinger_bands(closes, period=20, std_dev=2):
    period = int(period)
    std_dev = float(std_dev)
    mid = _sma(closes, period)
    std = []
    for i in range(len(closes)):
        if i < period - 1:
            std.append(None)
        else:
            vals = closes[i - period + 1:i + 1]
            m = sum(vals) / period
            variance = sum((v - m) ** 2 for v in vals) / period
            std.append(variance ** 0.5)
    upper, lower = [], []
    for i in range(len(closes)):
        if mid[i] is None or std[i] is None:
            upper.append(None); lower.append(None)
        else:
            upper.append(mid[i] + std_dev * std[i])
            lower.append(mid[i] - std_dev * std[i])
    return mid, upper, lower


def _adx(bars, period=14):
    """返回 (adx_value, regime_label)"""
    period = int(period)
    if len(bars) < period + 15:
        return 25.0, "UNCERTAIN", []

    plus_dm, minus_dm, true_range = [], [], []
    for i in range(1, len(bars)):
        b = bars[i]; pb = bars[i-1]
        hl = b["high"] - pb["high"]
        lh = pb["low"] - b["low"]
        plus_dm.append(max(hl, 0) if hl > lh else 0)
        minus_dm.append(max(lh, 0) if lh > hl else 0)
        tr = max(b["high"] - b["low"], abs(b["high"] - pb["close"]), abs(b["low"] - pb["close"]))
        true_range.append(tr)

    if len(true_range) < period:
        return 25.0, "UNCERTAIN", []

    atr_vals = _ema(true_range, period)
    ema_plus_dm = _ema(plus_dm, period)
    ema_minus_dm = _ema(minus_dm, period)

    plus_di, minus_di = [], []
    for i in range(len(plus_dm)):
        atr_v = atr_vals[i+1] if (i+1) < len(atr_vals) and atr_vals[i+1] is not None else None
        if atr_v and atr_v != 0 and (i+1) < len(ema_plus_dm) and ema_plus_dm[i+1] is not None:
            plus_di.append(100 * ema_plus_dm[i+1] / atr_v)
            minus_di.append(100 * ema_minus_dm[i+1] / atr_v if (i+1) < len(ema_minus_dm) and ema_minus_dm[i+1] is not None else 0)
        else:
            plus_di.append(0); minus_di.append(0)

    dx = []
    for i in range(len(plus_di)):
        di_sum = plus_di[i] + minus_di[i]
        dx.append(100 * abs(plus_di[i] - minus_di[i]) / di_sum if di_sum > 0 else 0)

    adx_vals = _ema(dx, period)
    adx = adx_vals[-1] if adx_vals and adx_vals[-1] is not None else 25.0

    if adx >= 25:
        label = "TRENDING"
    elif adx >= 18:
        label = "RANGING"
    else:
        label = "UNCERTAIN"

    return adx, label, adx_vals


# ============ 信号生成器实现 =============

class EMAMacdSignalGenerator(BaseSignalGenerator):
    """
    EMA + MACD + RSI + BB 综合策略信号生成器

    参数:
        ema_short: 短期EMA周期(默认9)
        ema_long: 长期EMA周期(默认21)
        ema50: 趋势确认EMA周期(默认50)
        rsi_period: RSI周期(默认14)
        macd_fast: MACD快线周期(默认12)
        macd_slow: MACD慢线周期(默认26)
        atr_period: ATR周期(默认14)
        bb_period: 布林带周期(默认20)
        bb_std: 布林带标准差倍数(默认2)
        sl_atr_mult: 止损ATR倍数(趋势模式默认1.5)
        tp_atr_mult: 止盈ATR倍数(趋势模式默认2.5)
    """

    STRATEGY_ID = "ema_macd_v2"  # Strategy ID

    # 默认参数
    DEFAULT_PARAMS = {
        "ema_short": 9,
        "ema_long": 21,
        "ema50": 50,
        "rsi_period": 14,
        "macd_fast": 12,
        "macd_slow": 26,
        "atr_period": 14,
        "bb_period": 20,
        "bb_std": 2.0,
        "sl_atr_mult_trend": 1.5,
        "tp_atr_mult_trend": 2.5,
        "sl_atr_mult_range": 0.5,
    }

    def __init__(self, params: dict = None):
        """
        初始化信号生成器

        输入:
            params: dict, 策略参数(覆盖默认参数)
        """
        self.params = {**self.DEFAULT_PARAMS}
        if params:
            self.params.update(params)

    def get_strategy_id(self) -> str:
        return self.STRATEGY_ID

    def get_params_schema(self) -> dict:
        return {
            "ema_short": {"type": "int", "default": 9, "range": [5, 20]},
            "ema_long": {"type": "int", "default": 21, "range": [15, 50]},
            "ema50": {"type": "int", "default": 50, "range": [30, 100]},
            "rsi_period": {"type": "int", "default": 14, "range": [7, 21]},
            "macd_fast": {"type": "int", "default": 12, "range": [5, 20]},
            "macd_slow": {"type": "int", "default": 26, "range": [15, 50]},
            "atr_period": {"type": "int", "default": 14, "range": [7, 30]},
            "sl_atr_mult_trend": {"type": "float", "default": 1.5, "range": [0.5, 3.0]},
            "tp_atr_mult_trend": {"type": "float", "default": 2.5, "range": [1.0, 5.0]},
        }

    def analyze(self, bars: list, params: dict = None, vol_ratio: float = 1.0) -> dict:
        """
        分析市场并生成信号

        输入:
            bars: K线数据 list[dict]
            params: 可选,覆盖初始化时的参数
            vol_ratio: GARCH 波动率比率（current_vol / long_run_vol）
                      用于动态缩放 SL/TP 的 ATR 倍数
                      > 1.0 → 高波动 → 宽松 SL/TP（防止被洗出）
                      < 1.0 → 低波动 → 紧凑 SL/TP（快速锁定利润）
                      范围钳制: [0.5, 2.0]

        输出:标准 Signal dict
        """
        p = {**self.params}
        if params:
            p.update(params)
            # Merge per-symbol calibration (atr_floor, etc.) into self.params
            # so sub-functions (_trend_signal, _range_signal) can access them
            for _key in ("atr_floor", "_symbol", "_pip_size"):
                if _key in params:
                    self.params[_key] = params[_key]

        # 确保所有的参数都被强制转换为正确的类型
        for key, value in p.items():
            if 'period' in key or 'ema' in key or 'macd' in key or 'rsi' in key or 'adx' in key or 'bb' in key:
                p[key] = int(value)
            elif 'mult' in key or 'level' in key or 'dist' in key or 'vol' in key:
                if isinstance(value, str) and not value.replace('.','',1).isdigit():
                    pass # Keep as string for things like 'risk_level'
                else:
                    p[key] = float(value)

        trace_id = f"sig_{int(time.time()*1000)}"

        # 数据校验
        if not bars or len(bars) < 50:
            return self._make_signal(
                trace_id=trace_id,
                action="HOLD",
                confidence=0.0,
                reason="insufficient_bars",
                indicators={}
            )

        system_regime = normalize_regime_label(p.get("_system_regime", ""))
        system_route_family = str(p.get("_system_route_family", "") or "").strip().lower()

        # Quick direction hint for suppressed signals (feeds loosening state machine)
        intended_dir = ""
        if bars and len(bars) >= 10:
            recent_closes = [float(b.get("close", 0) or 0) for b in bars[-10:] if b.get("close")]
            if len(recent_closes) >= 8:
                ups = sum(1 for i in range(1, len(recent_closes)) if recent_closes[i] > recent_closes[i-1])
                downs = len(recent_closes) - 1 - ups
                if ups >= int(len(recent_closes) * 0.65):
                    intended_dir = "BUY"
                elif downs >= int(len(recent_closes) * 0.65):
                    intended_dir = "SELL"

        if system_route_family == "blocked":
            return self._make_signal(
                trace_id=trace_id,
                action="HOLD",
                confidence=0.0,
                reason=f"system_route_blocked:{system_regime or 'UNKNOWN'}",
                indicators={
                    "system_regime": system_regime or "UNKNOWN",
                    "system_route_family": system_route_family,
                },
            )
        if system_route_family == "range":
            return self._make_signal(
                trace_id=trace_id,
                action="HOLD",
                confidence=0.0,
                reason=f"system_regime_override:{system_regime}",
                indicators={
                    "system_regime": system_regime,
                    "system_route_family": system_route_family,
                    "suppressed": True,
                    "intended_side": intended_dir,
                },
            )
        route_modifier = 1.0
        if system_route_family == "range_loosening":
            route_modifier = 0.6

        # u2500 LLM direction bias: if LLM has strong direction, prefer it
        _llm_dir = str(p.get("_llm_direction", "") or "").upper()
        _llm_conf = float(p.get("_llm_confidence", 0.0) or 0.0)
        _llm_active = _llm_dir in {"BUY", "SELL"} and _llm_conf >= 0.4

        closes = [b["close"] for b in bars]
        highs  = [b["high"]  for b in bars]
        lows   = [b["low"]   for b in bars]

        # 计算指标
        ema_short = _ema(closes, p["ema_short"])
        ema_long  = _ema(closes, p["ema_long"])
        ema50     = _ema(closes, p["ema50"])
        rsi_vals  = _rsi(closes, p["rsi_period"])
        macd_l, macd_s, macd_h = _macd(closes, p["macd_fast"], p["macd_slow"])
        atr_vals  = _atr(bars, p["atr_period"])
        bb_mid, bb_upper, bb_lower = _bollinger_bands(closes, p["bb_period"], p["bb_std"])
        adx_val, _, adx_series = _adx(bars, p["atr_period"])

        # 最新值
        n = len(closes) - 1
        def v(arr): return arr[n] if n < len(arr) and arr[n] is not None else 0

        c_atr = v(atr_vals)

        # 引入 1+3+2 智能状态判定机（加入 MACD 交叉先行信号）
        regime, vol_surge, breakout_dir = _detect_regime_v2(bars, adx_val, c_atr, macd_line=macd_l, signal_line=macd_s)

        # MACD 背离检测
        divergence = _detect_macd_divergence(closes, macd_l)

        # MACD 柱状图归一化（ATR 倍数，与价格水平解耦）
        macd_hist_norm = _macd_norm(v(macd_h), c_atr)

        # 构建 indicators 快照(供 Risk Layer 参考)
        indicators = {
            "ema9": round(v(ema_short), 5),
            "ema21": round(v(ema_long), 5),
            "ema50": round(v(ema50), 5),
            "rsi14": round(v(rsi_vals), 1),
            "macd_hist": round(v(macd_h), 5),
            "macd_norm": round(macd_hist_norm, 5),
            "macd_divergence": divergence["divergence"],
            "macd_divergence_strength": divergence["strength"],
            "adx": round(adx_val, 1),
            "atr14": round(c_atr, 5),
            "bb_upper": round(v(bb_upper), 3),
            "bb_mid": round(v(bb_mid), 3),
            "bb_lower": round(v(bb_lower), 3),
            "regime": regime,
            "system_regime": system_regime or "UNKNOWN",
            "system_route_family": system_route_family or "",
            "route_modifier": "range_loosening" if route_modifier < 1.0 else "",
            "volatility_surge": vol_surge,
            "price_breakout": breakout_dir,
        }

        # 信号决策
        signal = None
        ema9v, ema21v, ema50v = v(ema_short), v(ema_long), v(ema50)
        rsiv = v(rsi_vals)
        atr_v = v(atr_vals)
        macd_hist_v = v(macd_h)
        bbu = v(bb_upper); bbl = v(bb_lower); bbm = v(bb_mid)

        # #region debug-point A:ema-regime
        _debug_ema_event(
            "A",
            "signal_generators/ema_macd_signal.py:analyze",
            "[DEBUG] ema internal regime evaluated",
            {
                "strategy_id_raw": self.STRATEGY_ID,
                "regime": regime,
                "system_regime": system_regime or "UNKNOWN",
                "system_route_family": system_route_family,
                "volatility_surge": bool(vol_surge),
                "price_breakout": breakout_dir,
                "ema9": round(ema9v, 5),
                "ema21": round(ema21v, 5),
                "ema50": round(ema50v, 5),
                "adx": round(adx_val, 4),
                "atr": round(atr_v, 5),
                "rsi": round(rsiv, 4),
                "macd_hist": round(macd_hist_v, 6),
                "bb_upper": round(bbu, 3),
                "bb_mid": round(bbm, 3),
                "bb_lower": round(bbl, 3),
            },
        )
        # #endregion

        # 处理 1+3+2 新状态机返回的极端状态
        if regime in ("EXTREME_TREND", "BREAKOUT"):
            # 极端行情下方向明确，EMA 排列决定方向，MACD/ RSI 仅调置信度
            risk_level = get_effective_param("risk_level", "balanced")
            conf_bonus = 0.05 if risk_level == "aggressive" else 0.0
            sl_bonus = 1.5 if risk_level == "aggressive" else 1.0

            # 动态读取最新的 SL/TP 乘数配置，并按 GARCH vol_ratio 缩放
            # 高波动(vol_ratio>1) → ATR距离加宽，防止被洗出
            # 低波动(vol_ratio<1) → ATR距离收窄，快速锁定利润
            vr = max(0.5, min(2.0, vol_ratio))
            sl_mult = float(get_effective_param("sl_atr_mult_trend", 1.5)) * vr
            tp_mult = float(get_effective_param("tp_atr_mult_trend", 2.5)) * vr

            if ema9v > ema21v:
                # MACD 归一化能量阈值: macd_norm > 0   → 同向动能 +0.05
                #                        macd_norm < -0.3 → 反向强动能 -0.05
                macd_factor = 0.05 if macd_hist_norm > 0 else (-0.05 if macd_hist_norm < -0.3 else 0.0)
                confidence = min(0.90, 0.75 + conf_bonus + macd_factor)
                if _llm_active and _llm_dir == "SELL":
                    confidence = max(0.30, confidence * 0.5)
                signal = {
                    "action": "OPEN", "side": "BUY", "confidence": confidence,
                    "reason": f"{regime} BUY: EMA9>{ema21v:.1f} MACD_norm={macd_hist_norm:.3f}",
                    "entry": closes[n],
                    "sl": round(closes[n] - (sl_mult * sl_bonus) * atr_v, 3),
                    "tp": round(closes[n] + tp_mult * atr_v, 3)
                }
            elif ema9v < ema21v:
                # MACD 归一化: macd_norm < 0   → 同向动能 +0.05
                #                macd_norm > 0.3 → 反向强动能 -0.05
                macd_factor = 0.05 if macd_hist_norm < 0 else (-0.05 if macd_hist_norm > 0.3 else 0.0)
                confidence = min(0.90, 0.75 + conf_bonus + macd_factor)
                if _llm_active and _llm_dir == "BUY":
                    confidence = max(0.30, confidence * 0.5)
                signal = {
                    "action": "OPEN", "side": "SELL", "confidence": confidence,
                    "reason": f"{regime} SELL: EMA9<{ema9v:.1f} MACD_norm={macd_hist_norm:.3f}",
                    "entry": closes[n],
                    "sl": round(closes[n] + (sl_mult * sl_bonus) * atr_v, 3),
                    "tp": round(closes[n] - tp_mult * atr_v, 3)
                }

        elif regime == "TRENDING":
            signal = self._trend_signal(
                ema9v, ema21v, ema50v, rsiv, macd_hist_v, atr_v, adx_val, indicators, vol_ratio, bars=bars, rsi_vals=rsi_vals, adx_series=adx_series
            )
        elif regime == "RANGING":
            signal = self._range_signal(
                bbu, bbl, bbm, rsiv, atr_v, closes, indicators, vol_ratio
            )
        else:
            # UNCERTAIN:不产生信号
            pass

        if signal:
            # #region debug-point B:ema-signal
            _debug_ema_event(
                "B",
                "signal_generators/ema_macd_signal.py:analyze",
                "[DEBUG] ema signal produced",
                {
                    "strategy_id_raw": self.STRATEGY_ID,
                    "regime": regime,
                    "system_regime": system_regime or "UNKNOWN",
                    "system_route_family": system_route_family,
                    "signal_side": str(signal.get("side", "") or ""),
                    "signal_confidence": float(signal.get("confidence", 0.0) or 0.0),
                    "signal_reason": str(signal.get("reason", "") or ""),
                    "atr_floor": float(p.get("atr_floor", 1.0) or 1.0),
                    "atr": round(atr_v, 5),
                },
            )
            # #endregion
            # ── ATR 下限保护（按品种校准）──
            _atr_floor = float(p.get("atr_floor", 1.0) or 1.0)
            if atr_v < _atr_floor:
                return self._make_signal(
                    trace_id=trace_id,
                    action="HOLD",
                    confidence=0.0,
                    reason=f"no_signal: ATR={atr_v:.2f}<{_atr_floor} 波动率不足",
                    indicators=indicators
                )

            # ── 强制重新计算 SL/TP — 使用策略配置值而非硬编码 ──
            entry_price = closes[n]

            side = signal["side"]

            # 从配置读取 SL/TP 倍数（不再硬编码覆盖）
            vr = max(0.5, min(2.0, vol_ratio))
            sl_atr_mult = float(get_effective_param("sl_atr_mult_trend", 1.5)) * vr
            tp_atr_mult_cfg = float(get_effective_param("tp_atr_mult_trend", 2.5)) * vr
            # 保底：SL 最小 1.2 ATR，TP 最小 1.8 ATR（盈亏比 >= 1.5:1）
            min_sl_atr = max(sl_atr_mult, 1.2)
            min_tp_atr = max(tp_atr_mult_cfg, min_sl_atr * 1.5)
            sl_distance = max(min_sl_atr * atr_v, 4.0)   # 绝对最小 4 点
            tp_distance = max(min_tp_atr * atr_v, 6.0)   # 绝对最小 6 点

            if side == "BUY":
                sl_val = round(entry_price - sl_distance, 3)
                tp_val = round(entry_price + tp_distance, 3)
            else:  # SELL
                sl_val = round(entry_price + sl_distance, 3)
                tp_val = round(entry_price - tp_distance, 3)

            return self._make_signal(
                trace_id=trace_id,
                action="OPEN",
                side=side,
                confidence=min(signal["confidence"] * route_modifier, 1.0),
                reason=signal["reason"],
                indicators=indicators,
                entry=entry_price,
                sl=sl_val,
                tp=tp_val,
            )
        else:
            return self._make_signal(
                trace_id=trace_id,
                action="HOLD",
                confidence=0.0,
                reason=f"no_signal: {regime} regime, ADX={adx_val:.1f}, price={closes[n]:.1f}",
                indicators=indicators
            )

    def _trend_signal(self, ema9v, ema21v, ema50v, rsiv, macd_hist_v, atr_v, adx_val, indicators, vol_ratio=1.0, bars=None, rsi_vals=None, adx_series=None) -> Optional[dict]:
        """
        趋势模式信号生成。

        改动原则(2026-03-24):
        - EMA 排列是趋势的基础,保留为信号产生条件
        - MACD / RSI / ADX 不再是硬否决条件,而是置信度调节因子
        - 信号永远产生,置信度随指标共振程度浮动
        - 最终主决定权交给 Agent 层
        """
        bull_trend = ema9v > ema21v > ema50v
        bear_trend = ema9v < ema21v < ema50v

        p = self.params
        risk_level = get_effective_param("risk_level", "balanced")
        conf_bonus = 0.05 if risk_level == "aggressive" else 0.0

        # ── 基础置信度:基于 EMA 排列质量 ──
        if bull_trend:
            # EMA 完美多头排列:基础置信度 0.70
            base_conf = 0.70
            reason = (f"trend BUY: EMA9={ema9v:.1f}>EMA21={ema21v:.1f}>EMA50={ema50v:.1f} "
                      f"ADX={adx_val:.1f}")
            side = "BUY"
            entry_ref = ema9v
        elif bear_trend:
            base_conf = 0.70
            reason = (f"trend SELL: EMA9={ema9v:.1f}<EMA21={ema21v:.1f}<EMA50={ema50v:.1f} "
                      f"ADX={adx_val:.1f}")
            side = "SELL"
            entry_ref = ema9v
        else:
            # EMA 排列不完美,但趋势方向仍可能存在
            # 例如 EMA9>EMA21 但 EMA21<EMA50(当前行情)
            # 改为基于 EMA 方向判断,降低基础置信度
            partial_bull = ema9v > ema21v and adx_val >= 24 and rsiv >= 52 and macd_hist_v > 0
            partial_bear = ema9v < ema21v and adx_val >= 24 and rsiv <= 48 and macd_hist_v < 0
            if partial_bull:
                base_conf = 0.50
                reason = (f"trend BUY (partial): EMA9={ema9v:.1f}>EMA21={ema21v:.1f} "
                          f"EMA21<EMA50={ema50v:.1f} ADX={adx_val:.1f}")
                side = "BUY"
                entry_ref = ema9v
            elif partial_bear:
                base_conf = 0.50
                reason = (f"trend SELL (partial): EMA9={ema9v:.1f}<EMA21={ema21v:.1f} "
                          f"EMA21>EMA50={ema50v:.1f} ADX={adx_val:.1f}")
                side = "SELL"
                entry_ref = ema9v
            else:
                return None  # EMA 完全缠绕,无方向

        # ── 置信度调节:MACD 共振因子(±0~0.10)──
        # 使用 ATR 归一化柱状图，与价格水平解耦:
        #   macd_norm > 0     → 同向动能 +0.10
        #   macd_norm < -0.3  → 反向强动能 -0.10
        #   中间区域 → 0（无显著影响）
        macd_norm = _macd_norm(macd_hist_v, atr_v)
        if side == "BUY":
            macd_factor = 0.10 if macd_norm > 0 else (-0.10 if macd_norm < -0.3 else 0.0)
            rsi_factor = 0.05 if rsiv > 55 else (-0.05 if rsiv < 45 else 0.0)
        else:
            macd_factor = 0.10 if macd_norm < 0 else (-0.10 if macd_norm > 0.3 else 0.0)
            rsi_factor = 0.05 if rsiv < 45 else (-0.05 if rsiv > 55 else 0.0)

        # ── ADX 加成(趋势强度确认)──
        adx_factor = min(0.10, (adx_val - 20) / 50) if adx_val >= 20 else 0.0

        confidence = base_conf + macd_factor + rsi_factor + adx_factor + conf_bonus
        confidence = max(0.50, min(0.92, confidence))

        reason += f" RSI={rsiv:.1f} MACD_hist={macd_hist_v:.3f}"

        # ── ATR 下限保护（按品种校准）──
        _atr_floor_2 = float(p.get("atr_floor", 1.0) or 1.0)
        if atr_v < _atr_floor_2:
            return None  # 波动率不足，不产生信号

        # 动态读取最新的 SL/TP 乘数配置，按 GARCH vol_ratio 缩放
        vr = max(0.5, min(2.0, vol_ratio))
        sl_mult = float(get_effective_param("sl_atr_mult_trend", 1.5)) * vr
        tp_mult = float(get_effective_param("tp_atr_mult_trend", 2.5)) * vr

        signal = {
            "side": side,
            "entry": round(entry_ref, 3),
            "sl": round(entry_ref - sl_mult * atr_v, 3) if side == "BUY"
                    else round(entry_ref + sl_mult * atr_v, 3),
            "tp": round(entry_ref + tp_mult * atr_v, 3) if side == "BUY"
                    else round(entry_ref - tp_mult * atr_v, 3),
            "confidence": round(confidence, 2),
            "reason": reason,
        }

        # 引入趋势阶段过滤 (Trend Stage Filter)
        if bars is not None and rsi_vals is not None and adx_series is not None:
            stage, score = trend_stage_score(bars, adx_series, rsi_vals, side=side, ema21_v=ema21v, atr_v=atr_v)
            
            if stage == "LATE":
                # LATE 阶段：强制拦截，不产生入场信号
                return None
            elif stage == "MIDDLE":
                # MIDDLE 阶段：置信度降低，增加入场难度
                signal["confidence"] = max(0.45, signal["confidence"] - 0.05)
                signal["reason"] += f" [MIDDLE_STAGE_DISCOUNT_score={score}]"
            else:
                # EARLY 阶段：不再虚标置信度
                signal["reason"] += f" [EARLY_STAGE_score={score}]"

        return signal

    def _range_signal(self, bb_upper, bb_lower, bb_mid, rsiv, atr_v, closes, indicators, vol_ratio=1.0) -> Optional[dict]:
        """
        震荡模式信号生成。

        改动原则(2026-03-24):
        - 不再用 RSI 极端值硬否决信号
        - 布林带位置是核心条件(是否超买超卖)
        - RSI 和 MACD 转为置信度调节因子
        - 信号永远产生,Agent 拿完整信息做主决定
        - vol_ratio 动态缩放 SL/TP 的 ATR 距离
        """
        p = self.params
        n = len(closes) - 1
        close_px = closes[n]

        # ── ATR 下限保护（按品种校准）──
        _atr_floor_3 = float(self.params.get("atr_floor", 1.0) or 1.0)
        if atr_v < _atr_floor_3:
            return None  # 波动率不足，不产生信号

        # GARCH vol_ratio 缩放：高波动 → 宽松距离；低波动 → 紧凑距离
        vr = max(0.5, min(2.0, vol_ratio))
        sl_range = p.get("sl_atr_mult_range", 0.5) * vr
        tp_range_mult = p.get("tp_atr_mult_range", 0.75) * vr
        risk_level = get_effective_param("risk_level", "balanced")
        conf_bonus = 0.05 if risk_level == "aggressive" else 0.0

        signal = None

        # ── BUY 信号:价格触及或接近布林下轨 ──
        if bb_lower:
            proximity = (bb_lower - close_px) / atr_v if atr_v else 0  # 距离下轨多少 ATR
            if proximity >= -0.5:  # 在下轨附近0.5ATR内都算
                # RSI 调节因子
                rsi_factor = 0.10 if rsiv < 40 else (-0.05 if rsiv > 55 else 0.0)
                # 贴近下轨越多,置信度越高
                proximity_factor = min(0.10, max(0.0, proximity * 0.05))
                base_conf = 0.55 + proximity_factor + rsi_factor + conf_bonus
                confidence = max(0.45, min(0.88, base_conf))
                signal = {
                    "side": "BUY",
                    "entry": round(close_px, 3),
                    "sl": round(bb_lower - sl_range * atr_v, 3),
                    "tp": round(bb_mid + tp_range_mult * atr_v, 3),
                    "confidence": round(confidence, 2),
                    "reason": f"range BUY: BB_lower={bb_lower:.1f} close={close_px:.1f} "
                               f"proximity={proximity:.2f}ATR RSI={rsiv:.1f}",
                }

        # ── SELL 信号:价格触及或接近布林上轨 ──
        if bb_upper:
            proximity = (close_px - bb_upper) / atr_v if atr_v else 0
            if proximity >= -0.5:
                rsi_factor = 0.10 if rsiv > 60 else (-0.05 if rsiv < 45 else 0.0)
                proximity_factor = min(0.10, max(0.0, proximity * 0.05))
                base_conf = 0.55 + proximity_factor + rsi_factor + conf_bonus
                confidence = max(0.45, min(0.88, base_conf))
                signal = {
                    "side": "SELL",
                    "entry": round(close_px, 3),
                    "sl": round(bb_upper + sl_range * atr_v, 3),
                    "tp": round(bb_mid - tp_range_mult * atr_v, 3),
                    "confidence": round(confidence, 2),
                    "reason": f"range SELL: BB_upper={bb_upper:.1f} close={close_px:.1f} "
                               f"proximity={proximity:.2f}ATR RSI={rsiv:.1f}",
                }

        return signal

    def _make_signal(self, trace_id: str, action: str, confidence: float,
                     reason: str, indicators: dict,
                     side: str = None, entry: float = None,
                     sl: float = None, tp: float = None) -> dict:
        """构造标准 Signal dict"""
        payload = {
            "action": action,
            "confidence": confidence,
            "reason": reason,
            "indicators": indicators,
        }
        if side:
            payload.update({
                "side": side,
                "entry": entry,
                "sl": sl,
                "tp": tp,
            })
        return {
            "schema_version": self.SCHEMA_VERSION,
            "trace_id": trace_id,
            "strategy_id": self.STRATEGY_ID,
            "model_version": None,
            "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S+08:00"),
            "payload": payload,
        }


# ============ 便捷工厂函数 ============

def create_generator(strategy_type: str = "ema_macd", params: dict = None) -> BaseSignalGenerator:
    """
    工厂函数:根据策略类型创建对应的信号生成器

    输入:
        strategy_type: "ema_macd" | "breakout" | "ml_fusion"
        params: 策略参数
    """
    if strategy_type in ("ema_macd", "ema", "default"):
        return EMAMacdSignalGenerator(params)
    else:
        raise ValueError(f"Unknown strategy type: {strategy_type}")
