"""볼린저 밴드 역추세 매매."""

from __future__ import annotations

import pandas as pd

from deepcoin.techniques.base import BaseTechnique, TechniqueParams, TechniqueSignal
from deepcoin.techniques.indicators import bollinger_bands, ema


class BbReversalTechnique(BaseTechnique):
    """BB 하단 터치 후 반등 매수, 상단 터치 후 하락 매도."""

    technique_id = "bb_reversal"
    technique_name = "볼린저 역추세"
    category = "indicator"
    causal = True
    description = "BB(20,2) 하단 매수·상단 매도 + EMA 추세 필터"

    def default_extra_params(self) -> dict:
        return {"window": 20, "num_std": 2.0, "ema_span": 60}

    def generate_signals(self, df: pd.DataFrame, params: TechniqueParams) -> list[TechniqueSignal]:
        window = int(params.extra.get("window", 20))
        num_std = float(params.extra.get("num_std", 2.0))
        ema_span = int(params.extra.get("ema_span", 60))

        close = df["close"].astype(float)
        low = df["low"].astype(float)
        high = df["high"].astype(float)
        _, upper, lower = bollinger_bands(close, window=window, num_std=num_std)
        trend = ema(close, ema_span)

        signals: list[TechniqueSignal] = []
        in_oversold = False
        in_overbought = False

        for i in range(window, len(df)):
            if pd.isna(lower.iloc[i]) or pd.isna(upper.iloc[i]):
                continue

            low_i = float(low.iloc[i])
            high_i = float(high.iloc[i])
            close_i = float(close.iloc[i])
            lower_i = float(lower.iloc[i])
            upper_i = float(upper.iloc[i])
            trend_i = float(trend.iloc[i])
            dt = pd.Timestamp(df.iloc[i]["datetime"]).strftime("%Y-%m-%d %H:%M:%S")

            if low_i <= lower_i:
                in_oversold = True
            if in_oversold and close_i > lower_i and close_i >= trend_i * 0.98:
                signals.append(
                    TechniqueSignal(
                        side="buy",
                        bar_index=i,
                        price=round(close_i, 2),
                        datetime=dt,
                        confidence=0.7,
                        reason="bb_lower_bounce",
                    )
                )
                in_oversold = False

            if high_i >= upper_i:
                in_overbought = True
            if in_overbought and close_i < upper_i:
                signals.append(
                    TechniqueSignal(
                        side="sell",
                        bar_index=i,
                        price=round(close_i, 2),
                        datetime=dt,
                        confidence=0.7,
                        reason="bb_upper_reject",
                    )
                )
                in_overbought = False

        return signals
