"""Compute IV rank and percentile from your own consistent daily IV CSV.
Python 3.10+, standard library. Columns: date,symbol,iv_pct.
The companion synthetic-iv-history.csv demonstrates arithmetic, not markets.
"""
import argparse
import csv
import math
from collections import defaultdict
from datetime import date


def scan(path, min_rank=50, min_percentile=70, lookback=252):
    observations = defaultdict(dict)
    with open(path, newline="", encoding="utf-8-sig") as stream:
        for row in csv.DictReader(stream):
            day = date.fromisoformat(row["date"])
            symbol = row["symbol"].strip().upper()
            value = float(row["iv_pct"])
            if not symbol or not math.isfinite(value) or value <= 0:
                raise ValueError("Each observation needs a symbol and positive finite IV percentage.")
            if day in observations[symbol]:
                raise ValueError(f"Duplicate date for {symbol}: {day}")
            observations[symbol][day] = value
    if not observations:
        raise ValueError("No IV observations supplied.")
    as_of = max(max(values) for values in observations.values())
    results = []
    for symbol, values in observations.items():
        days = sorted(values)
        history = [values[d] for d in days][-lookback:]
        reason = ""
        rank = percentile = None
        if days[-1] != as_of:
            reason = "stale observation"
        elif len(history) < lookback:
            reason = "insufficient history"
        elif max(history) == min(history):
            reason = "zero IV range"
        else:
            current = history[-1]
            rank = 100 * (current - min(history)) / (max(history) - min(history))
            # Compare with strictly lower prior observations; ties are not lower.
            prior = history[:-1]
            percentile = 100 * sum(iv < current for iv in prior) / len(prior)
        passed = rank is not None and rank >= min_rank and percentile >= min_percentile
        results.append({"symbol": symbol, "as_of": str(days[-1]), "observations": len(history),
                        "iv_pct": history[-1], "iv_rank": rank, "iv_percentile": percentile,
                        "passes": passed, "excluded_reason": reason})
    return sorted(results, key=lambda r: (r["passes"], r["iv_rank"] if r["iv_rank"] is not None else -1), reverse=True)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("csv")
    parser.add_argument("--min-rank", type=float, default=50)
    parser.add_argument("--min-percentile", type=float, default=70)
    args = parser.parse_args()
    for row in scan(args.csv, args.min_rank, args.min_percentile):
        print(row)


if __name__ == "__main__":
    main()
