#!/usr/bin/env python3
"""
CompositeSignalGenerator — 多策略加权投票信号聚合器

将多个信号源（EMA/MACD + Elliott Wave + Chanlun + Wyckoff）
按置信度权重进行加权投票，输出综合信号。

聚合策略：
  1. 置信度加权平均（Confidence-Weighted Average）
     - 每个信号 action ∈ {BUY, SELL, HOLD} × confidence
     - BUY_score = sum(ci for ai=="BUY") / total_confidence
     - SELL_score = sum(ci for ai=="SELL") / total_confidence
     - 若 BUY_score > HOLD_threshold → BUY，反之 SELL_score > HOLD_threshold → SELL

  2. 一票否决（Veto）：
     - 任一策略输出 BLOCK（极端风险）→ 整体 HOLD

  3. 多头共振（Multi-Signal Alignment）：
     - 2+ 策略同时 BUY → confidence 上浮 +10%
     - 2+ 策略同时 SELL → confidence 上浮 +10%

  4. 矛盾检测（Conflict）：
     - BUY 和 SELL 信号数量相等 → 降低置信度 30%

适用场景：趋势市场（ADX >= 25）信号最强
"""
import time
import uuid
from typing import List, Dict

from .base import BaseSignalGenerator


class CompositeSignalGenerator(BaseSignalGenerator):
    """
    多信号加权聚合器。

    使用方式：
        gen = CompositeSignalGenerator({
            "composite_strategies": ["ema_macd", "wyckoff"],
            "composite_weights": {"ema_macd": 0.55, "wyckoff": 0.45},
            "composite_hold_threshold": 0.40,
            "composite_alignment_bonus": 0.10,
        })
    """

    DEFAULT_PARAMS = {
        # 启用的子策略及其权重（elliott_wave/chanlun 尚未实现，暂不启用）
        "composite_strategies": ["ema_macd", "wyckoff"],
        "composite_weights": {
            "ema_macd": 0.55,
            "wyckoff": 0.45,
        },
        # HOLD 门槛：信号得分低于此值 → 输出 HOLD
        "composite_hold_threshold": 0.40,
        # 多头共振加成
        "composite_alignment_bonus": 0.10,
        # 矛盾惩罚（各方向策略数量相等时）
        "composite_conflict_penalty": 0.30,
    }

    def __init__(self, params: dict = None):
        self.params = {**self.DEFAULT_PARAMS, **(params or {})}

        # 初始化子生成器（使用直接导入避免循环导入）
        self._generators: Dict[str, BaseSignalGenerator] = {}
        strategy_map = {
            "ema_macd": ("signal_generators.ema_macd_signal", "create_generator"),
            "elliott_wave": ("signal_generators.elliott_wave_signal", "create_generator"),
            "chanlun": ("signal_generators.chanlun_signal", "create_generator"),
            "wyckoff": ("signal_generators.wyckoff_signal", "create_generator"),
        }
        enabled = self.params.get("composite_strategies", [])
        for name in enabled:
            if name in strategy_map:
                try:
                    import importlib
                    mod = importlib.import_module(strategy_map[name][0])
                    factory = getattr(mod, strategy_map[name][1])
                    self._generators[name] = factory(name, params)
                except Exception:
                    pass

    def analyze(self, bars: list, params: dict = None) -> dict:
        trace_id = str(uuid.uuid4())[:8]
        timestamp = time.strftime("%Y-%m-%dT%H:%M:%S")

        if len(bars) < 50:
            return self._hold(trace_id, timestamp, "bars不足（需要50根以上）")

        # 收集各子策略信号
        signals: Dict[str, dict] = {}
        weights = self.params.get("composite_weights", {})

        for name, gen in self._generators.items():
            try:
                sig = gen.analyze(bars, params)
                signals[name] = sig
            except Exception:
                pass

        if not signals:
            return self._hold(trace_id, timestamp, "无有效子策略信号")

        # ── 加权投票 ─────────────────────────────────────────────────────────
        buy_score = 0.0
        sell_score = 0.0
        hold_count = 0
        total_weight = 0.0
        aligned_buys = 0
        aligned_sells = 0
        sub_signal_reasons = {}

        for name, sig in signals.items():
            w = weights.get(name, 1.0 / len(signals))
            conf = sig["payload"]["confidence"]
            action = sig["payload"]["action"]
            reason = sig["payload"]["reason"]
            sub_signal_reasons[name] = f"{action}({conf:.2f}): {reason[:40]}"

            # 统一动作类型：OPEN = BUY, CLOSE = SELL
            if action in ("BUY", "OPEN"):
                buy_score += conf * w
                aligned_buys += 1
                total_weight += w
            elif action in ("SELL", "CLOSE"):
                sell_score += conf * w
                aligned_sells += 1
                total_weight += w
            else:
                hold_count += 1

        if total_weight <= 0:
            return self._hold(trace_id, timestamp, "所有子策略均为 HOLD")

        # 归一化得分
        buy_norm = buy_score / total_weight
        sell_norm = sell_score / total_weight

        # 矛盾检测：各方向策略数量相等
        conflict_penalty = 0.0
        if aligned_buys > 0 and aligned_sells > 0 and aligned_buys == aligned_sells:
            conflict_penalty = self.params.get("composite_conflict_penalty", 0.30)
            sub_signal_reasons["_conflict"] = f"信号矛盾：BUY×{aligned_buys} = SELL×{aligned_sells}，惩罚={conflict_penalty}"

        # 方向判定
        hold_thresh = self.params.get("composite_hold_threshold", 0.40)

        if buy_norm > hold_thresh and buy_norm > sell_norm:
            action = "BUY"
            confidence = buy_norm
            reason = f"Composite BUY | 多头共振×{aligned_buys} | 空头={aligned_sells} | HOLD={hold_count}"
        elif sell_norm > hold_thresh and sell_norm > buy_norm:
            action = "SELL"
            confidence = sell_norm
            reason = f"Composite SELL | 空头共振×{aligned_sells} | 多头={aligned_buys} | HOLD={hold_count}"
        else:
            action = "HOLD"
            confidence = 0.0
            reason = f"Composite HOLD | BUY={buy_norm:.2f} SELL={sell_norm:.2f} (thresh={hold_thresh})"

        # 多头共振加成
        if action != "HOLD":
            bonus = self.params.get("composite_alignment_bonus", 0.10)
            if (action == "BUY" and aligned_buys >= 2) or (action == "SELL" and aligned_sells >= 2):
                confidence = min(confidence + bonus, 0.98)
                reason += f" | 共振加成+{bonus}"

            # 矛盾惩罚
            if conflict_penalty > 0:
                confidence = max(confidence - conflict_penalty, 0.0)
                reason += f" | 矛盾惩罚-{conflict_penalty}"

        # ── 计算综合 SL/TP ────────────────────────────────────────────────────
        # 从非 HOLD 信号中取中间置信度的那个（最可靠的方向）
        direction_signals = [s for s in signals.values() if s["payload"]["action"] != "HOLD"]
        direction_signals.sort(key=lambda s: s["payload"]["confidence"], reverse=True)

        entry, sl, tp = None, None, None
        if direction_signals:
            best = direction_signals[0]["payload"]
            entry = best.get("entry")
            sl = best.get("sl")
            tp = best.get("tp")

        side = None
        if action in ("BUY", "SELL"):
            side = action

        # 汇总各子策略指标
        composite_indicators = {}
        for name, sig in signals.items():
            for k, v in sig["payload"]["indicators"].items():
                composite_indicators[f"{name}_{k}"] = v
        composite_indicators["_buy_score"] = round(buy_norm, 4)
        composite_indicators["_sell_score"] = round(sell_norm, 4)
        composite_indicators["_aligned_buys"] = aligned_buys
        composite_indicators["_aligned_sells"] = aligned_sells

        return {
            "schema_version": "2.1.0",
            "trace_id": trace_id,
            "strategy_id": "composite_v1",
            "model_version": None,
            "timestamp": timestamp,
            "payload": {
                "action": action,
                "confidence": round(confidence, 4),
                "reason": reason,
                "indicators": composite_indicators,
                "side": side,
                "entry": round(entry, 2) if entry else None,
                "sl": round(sl, 2) if sl else None,
                "tp": round(tp, 2) if tp else None,
            }
        }

    def _hold(self, trace_id: str, timestamp: str, reason: str) -> dict:
        return {
            "schema_version": "2.1.0",
            "trace_id": trace_id,
            "strategy_id": "composite_v1",
            "model_version": None,
            "timestamp": timestamp,
            "payload": {
                "action": "HOLD",
                "confidence": 0.0,
                "reason": reason,
                "indicators": {},
                "side": None,
                "entry": None,
                "sl": None,
                "tp": None,
            }
        }

    def get_params_schema(self) -> dict:
        return {
            "composite_hold_threshold": {
                "type": "float", "default": 0.40, "range": [0.25, 0.70]
            },
            "composite_alignment_bonus": {
                "type": "float", "default": 0.10, "range": [0.05, 0.20]
            },
            "composite_conflict_penalty": {
                "type": "float", "default": 0.30, "range": [0.10, 0.50]
            },
        }


def create_generator(strategy_type: str = "composite", params: dict = None) -> BaseSignalGenerator:
    if strategy_type in ("composite", "multi", "hybrid"):
        return CompositeSignalGenerator(params)
    raise ValueError(f"Unknown composite strategy: {strategy_type}")
