"""
================================================================================
Strategy **D002** — ``s002``
================================================================================

Theme
-----
**Close-only Donchian breakout + “IV not extreme vs RV” filter.**

Economic idea
-------------
Breakouts on indices sometimes coincide with **vol expansion**, which makes naive short
straddles dangerous. This sleeve fires only when price prints a **new 20-session high**
(in close space) *and* ATM implied volatility at ~30 DTE is **not** far above the
rolling 21-session realized vol (annualized). The intent is to harvest a **calm**
breakout where the surface has not yet priced a full vol regime shift.

Entry
-----
1. ``close[i] >= roll_high_20[i-1]`` (must have ``i>=20`` for warm-up sanity).
2. ``iv_atm(30)`` exists and ``iv_atm(30) <= 1.15 * rv21``.
3. ``n_contracts[i] >= 80``.

Exit
----
Short ATM straddle ~30 DTE, hold **5** sessions.

Notes
-----
``roll_high_20`` is the trailing max of **closes**, not true highs.
================================================================================
"""

from __future__ import annotations

import math

import pandas as pd

from RenTech.core.options_data_loader import OptionChain
from RenTech.strategy_stack.diverse_theta_strategies_v1.context import ResearchContext

META = {"sid": "D002", "theme": "breakout_trend", "title": "20d close breakout + IV<=1.15*RV short straddle"}

HOLD_SESSIONS = 5
TRADE_KIND = "ss"
TRADE_PARAMS = (30,)


def wants_entry(i: int, row: pd.Series, ch: OptionChain, spy: float, ctx: ResearchContext) -> bool:
    if i < 20:
        return False
    prev = ctx.row(i - 1).get("roll_high_20")
    if pd.isna(prev):
        return False
    c = float(row["close"])
    if c <= float(prev):
        return False
    iv = ctx.iv_atm_dte(i, 30)
    rv = float(row["rv21"]) if pd.notna(row.get("rv21")) else float("nan")
    if iv is None or not math.isfinite(rv) or rv <= 0:
        return False
    if iv > 1.15 * rv:
        return False
    if ctx.chain_contracts(i) < 80:
        return False
    if not ch.contracts:
        return False
    _ = spy
    return True
