"""Swing Trading Agent — multi-day stock swing setups (shares, not options).

Strategy: trend-following pullback entries.
- LONG (BUY):  stacked uptrend (EMA9 > EMA21 > SMA50, price above SMA50),
  price pulled back near EMA21, RSI in the 38–62 "reset" zone.
- SHORT (SELL): the mirror image.

Rationale: entering an established trend on a pullback gives a defined stop
(under/over the pullback, 2×ATR) with trend tailwind toward 2R/3R targets.
Parameters are intentionally exposed as constants for tuning as live results
accumulate over the coming months.
"""
from .base import Agent, AgentResult
from .indicators import atr, ema, rsi, sma

# ── Tunable parameters (adjust as paper-trade evidence accumulates) ──
MIN_SWING_SCORE = 65.0        # orchestrator gate for creating a swing signal
MIN_TREND_STRENGTH = 0.25     # hard gate: EMA21 must be ≥ ~0.6% away from SMA50
PULLBACK_ATR_WINDOW = 2.0     # "near EMA21" = within this many ATRs
RSI_LOW, RSI_HIGH = 38.0, 62.0
STOP_ATR_MULT = 2.0           # wider stop than the options play (multi-day hold)


class SwingTradingAgent(Agent):
    name = "swing"

    def run(self, context: dict) -> AgentResult:
        bars = context["history"]
        closes = [b["close"] for b in bars]
        price = closes[-1]
        e9, e21 = ema(closes, 9), ema(closes, 21)
        s50 = sma(closes, 50)
        r = rsi(closes)
        a = atr(bars) or price * 0.02

        if not all(v is not None for v in (e9, e21, s50, r)):
            return AgentResult(agent=self.name, score=0.0,
                               notes=["Insufficient history for swing analysis"])

        uptrend = e21 > s50 and price > s50
        downtrend = e21 < s50 and price < s50
        if not (uptrend or downtrend):
            return AgentResult(agent=self.name, score=0.0, data={"atr": a},
                               notes=["No trend structure — swing setup requires EMA21 vs "
                                      "SMA50 alignment with price on the same side"])

        direction = "BUY" if uptrend else "SELL"
        sign = 1 if uptrend else -1

        # Trend strength: separation of EMA21 from SMA50, normalized
        trend = min(1.0, abs(e21 - s50) / s50 * 40)
        # HARD GATE: a pullback entry is only valid inside a real trend.
        # Without this, choppy/flat markets can score well on pullback+RSI
        # alone — the classic way swing traders get chopped up.
        if trend < MIN_TREND_STRENGTH:
            return AgentResult(agent=self.name, score=0.0, data={"atr": a, "trend": round(trend, 2)},
                               notes=[f"Trend strength {trend:.2f} below minimum "
                                      f"{MIN_TREND_STRENGTH} — market too flat/choppy for a "
                                      "trend-following entry"])
        # Fast-EMA confirmation (scoring factor, not a hard gate)
        fast_confirm = 1.0 if (e9 > e21) == uptrend else 0.4
        # Pullback quality: how close price sits to EMA21 (entry value)
        pullback = max(0.0, 1 - abs(price - e21) / (PULLBACK_ATR_WINDOW * a))
        # RSI reset: cooled-off momentum, not overbought/oversold chasing
        if RSI_LOW <= r <= RSI_HIGH:
            rsi_fit = 1.0
        else:
            rsi_fit = max(0.0, 1 - abs(r - 50) / 35)

        score = round(min(100.0, (trend * 0.30 + pullback * 0.30
                                  + rsi_fit * 0.20 + fast_confirm * 0.20) * 100), 1)

        notes = [
            f"{'Up' if uptrend else 'Down'}trend structure: EMA9 {e9:.2f} / EMA21 {e21:.2f} / SMA50 {s50:.2f}",
            f"Fast EMA {'confirms' if fast_confirm == 1.0 else 'lags'} the trend",
            f"Pullback quality {pullback:.0%} (price {abs(price - e21) / a:.1f} ATR from EMA21)",
            f"RSI {r} — {'in the entry zone' if RSI_LOW <= r <= RSI_HIGH else 'outside ideal 38–62 zone'}",
            f"Swing stop uses {STOP_ATR_MULT}×ATR for multi-day holding room",
        ]
        return AgentResult(
            agent=self.name, score=score, direction=direction,
            data={"atr": a, "trend": round(trend, 2), "pullback": round(pullback, 2),
                  "rsi_fit": round(rsi_fit, 2), "stop_atr_mult": STOP_ATR_MULT,
                  "sign": sign},
            notes=notes,
        )
