"""
ML 模型回测集成。

MLStrategy: 将 ML 预测值作为交易信号接入回测引擎。
MLBenchmark: 多模型基准对比。
"""

import numpy as np
import pandas as pd

from backtest.base import BaseStrategy
from backtest.vectorbt.engine import VectorBTEngine
from models.base import BaseModel
from models.features import FeatureEngine


class MLStrategy(BaseStrategy):
    """
    ML 预测 → 交易信号。

    用模型预测未来 N 日收益，按预测值分位数生成信号：
    - 预测值 > buy_quantile → 买入
    - 预测值 < sell_quantile → 平仓

    参数:
        model: 已训练的 BaseModel
        feature_engine: 已 fit 的 FeatureEngine
        buy_quantile: 买入分位阈值（0.7 = 预测值最高的30%买入）
        sell_quantile: 卖出分位阈值（0.3 = 预测值最低的30%平仓）
        rebalance_freq: 调仓间隔（交易日）
    """

    category = "ml"

    def __init__(
        self,
        model: BaseModel,
        feature_engine: FeatureEngine,
        buy_quantile: float = 0.7,
        sell_quantile: float = 0.3,
        rebalance_freq: int = 5,
    ):
        self.model = model
        self.feature_engine = feature_engine
        self.buy_quantile = buy_quantile
        self.sell_quantile = sell_quantile
        self.rebalance_freq = rebalance_freq
        self.name = f"ml_{model.name}"

    def generate_signals(self, factor_df: pd.DataFrame) -> pd.Series:
        X, _ = self.feature_engine.build(factor_df, factor_df, fit=False)
        if X.empty:
            return pd.Series(-1, index=factor_df.index)

        preds = self.model.predict(X)
        # 用预测值本身的分布作为阈值（相对排序，避免模型偏差影响）
        buy_threshold = preds.quantile(self.buy_quantile)
        sell_threshold = preds.quantile(self.sell_quantile)

        signals = pd.Series(-1, index=factor_df.index)
        common = signals.index.intersection(preds.index)
        buy_mask = preds.loc[common] > buy_threshold
        sell_mask = preds.loc[common] < sell_threshold
        signals.loc[buy_mask[buy_mask].index] = 1
        signals.loc[sell_mask[sell_mask].index] = 0

        signals = self._filter_rebalance(signals)
        return signals

    def _filter_rebalance(self, signals: pd.Series) -> pd.Series:
        """每隔 rebalance_freq 个交易日保留第一个非持有信号。"""
        result = signals.copy()
        last_active = -self.rebalance_freq - 1
        for i in range(len(result)):
            sig = result.iloc[i]
            if sig in (0, 1):
                if i - last_active >= self.rebalance_freq:
                    last_active = i
                else:
                    result.iloc[i] = -1
        return result


class MLBenchmark:
    """ML 模型基准对比测试。"""

    def __init__(
        self,
        models: list[BaseModel],
        feature_engine: FeatureEngine,
        price_df: pd.DataFrame,
        factor_df: pd.DataFrame,
        bt_engine: VectorBTEngine | None = None,
    ):
        self.models = models
        self.feature_engine = feature_engine
        self.price_df = price_df
        self.factor_df = factor_df
        self.bt_engine = bt_engine or VectorBTEngine()

    def run(self) -> pd.DataFrame:
        """对比各模型的预测质量和回测表现。"""
        rows = []
        for model in self.models:
            strategy = MLStrategy(model, self.feature_engine)
            report = self.bt_engine.run(strategy, self.price_df, self.factor_df)

            # OOS 预测 vs 真实值
            X, y_true = self.feature_engine.build(self.factor_df, self.price_df, fit=True)
            y_pred = model.predict(X)

            ic = y_pred.corr(y_true) if len(y_pred) > 0 else 0

            rows.append({
                "model": model.name,
                "ic": round(ic, 4),
                "total_return": report.total_return,
                "cagr": report.cagr,
                "max_dd": report.max_drawdown,
                "sharpe": report.sharpe_ratio,
                "win_rate": report.win_rate,
                "trades": report.total_trades,
            })
        return pd.DataFrame(rows).set_index("model")
