"""
================================================================================
Strategy **D004** — ``s004``
================================================================================

Theme
-----
**Bollinger bandwidth squeeze (close-only) → short implied vol.**

Economic idea
-------------
A **squeeze** (narrow Bollinger band width) means recent closes have had low dispersion vs
their 20-session mean. Many practitioners treat that as pre-trend compression; this
sleeve instead implements the **short premium** hypothesis: after prolonged low
bandwidth, SPY may remain range-bound long enough for a short OTM strangle to decay. The
entry uses an **absolute** width threshold (not a percentile) to keep logic transparent.

Entry
-----
1. ``bb_width_20`` exists and ``bb_width_20 < 0.045``.
2. ``VIX < 17`` (avoid selling strangles if fear is already elevated).
3. ``rv21 > 0`` and ``iv_atm(35) > rv21`` (still positive variance risk premium at ~35d).
4. ``n_contracts[i] >= 100`` (strangles need reliable OTM liquidity).

Exit
----
Short strangle ~40 DTE with put delta **-0.20** and call delta **0.14**, hold **6**.
================================================================================
"""

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": "D004", "theme": "range_mean_reversion", "title": "Tight Bollinger width + low VIX short strangle"}

HOLD_SESSIONS = 6
TRADE_KIND = "sg"
TRADE_PARAMS = (40, -0.20, 0.14)


def wants_entry(i: int, row: pd.Series, ch: OptionChain, spy: float, ctx: ResearchContext) -> bool:
    bw = row.get("bb_width_20")
    if bw is None or pd.isna(bw) or float(bw) >= 0.045:
        return False
    vx = ctx.vix(i)
    if not math.isfinite(vx) or vx >= 17.0:
        return False
    rv = float(row["rv21"]) if pd.notna(row.get("rv21")) else float("nan")
    if not math.isfinite(rv) or rv <= 0:
        return False
    iv = ctx.iv_atm_dte(i, 35)
    if iv is None or iv <= rv:
        return False
    if ctx.chain_contracts(i) < 100:
        return False
    if not ch.contracts:
        return False
    _ = spy
    return True
