#!/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 time
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


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

def _detect_regime_v2(bars, adx_val, atr_val):
    """
    1+3+2 状态判定机:解决 ADX 迟滞问题的智能漏斗模型。
    优先级:动能突变 > 价格突破 > 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

    # 2. 回退到基础 ADX 判定
    if adx_val >= 20:
        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 == 0:
        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 == 0:
            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 _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(np.sqrt(variance) if 'numpy' in dir() else (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)

        # 确保所有的参数都被强制转换为正确的类型
        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={}
            )

        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 智能状态判定机
        regime, vol_surge, breakout_dir = _detect_regime_v2(bars, adx_val, 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),
            "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,
            "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)

        # 处理 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 绝对数值 (-2.0) 拟合，进行能量阈值过滤
                macd_factor = 0.05 if macd_hist_v > 0 else (-0.05 if macd_hist_v < -2.0 else 0.0)
                confidence = min(0.95, 0.88 + conf_bonus + macd_factor)
                signal = {
                    "action": "OPEN", "side": "BUY", "confidence": confidence,
                    "reason": f"{regime} BUY: EMA9>{ema21v:.1f} MACD_hist={macd_hist_v:.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 绝对数值 (2.0) 拟合，进行能量阈值过滤
                macd_factor = 0.05 if macd_hist_v < 0 else (-0.05 if macd_hist_v > 2.0 else 0.0)
                confidence = min(0.95, 0.88 + conf_bonus + macd_factor)
                signal = {
                    "action": "OPEN", "side": "SELL", "confidence": confidence,
                    "reason": f"{regime} SELL: EMA9<{ema9v:.1f} MACD_hist={macd_hist_v:.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:
            # ── ATR 下限保护：XAUUSD M5 ATR < 1.0 视为无波动，不开仓 ──
            if atr_v < 1.0:
                return self._make_signal(
                    trace_id=trace_id,
                    action="HOLD",
                    confidence=0.0,
                    reason=f"no_signal: ATR={atr_v:.2f}<1.0 波动率不足",
                    indicators=indicators
                )

            # ── 强制重新计算 SL/TP（不管底层怎么返回，都用 entry_price 重新算）──
            # 修复：底层 _trend_signal 的 entry_ref 可能用 EMA 而非实际价格，导致 SL 偏离
            entry_price = closes[n]  # 强制用实际收盘价

            side = signal["side"]

            # XAUUSD 最小止损距离：至少 2ATR（低于此无意义）
            # 修复：从 1.5 提到 2.0，防止低波动期 ATR 收缩时止损过窄被扫出，绝对下限提升至 4.0 避免小额止损
            min_atr_mult = 2.0
            tp_atr_mult = 3.0
            sl_distance = max(min_atr_mult * atr_v, 4.0)   # 最小4点，给予价格足够的呼吸空间
            tp_distance = max(tp_atr_mult * 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=signal["confidence"],
                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 方向判断,降低基础置信度
            if ema9v > ema21v:
                base_conf = 0.55
                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 ema9v < ema21v:
                base_conf = 0.55
                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)──
        # 恢复 MACD 绝对数值 (-2.0, 2.0) 的能量阈值过滤
        # 在黄金的高波动中，微弱的 MACD 交叉（如 0.5）很可能是假动作，必须有能量配合
        if side == "BUY":
            macd_factor = 0.10 if macd_hist_v > 0 else (-0.10 if macd_hist_v < -2.0 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_hist_v < 0 else (-0.10 if macd_hist_v > 2.0 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.20, min(0.92, confidence))  # 封顶在 0.92

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

        # ── ATR 下限保护 ──
        if atr_v < 1.0:
            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.20, 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 下限保护 ──
        if atr_v < 1.0:
            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.25, 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.25, 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}")
