From d79b98bacaa9c015281c1dd7e064e813c352c9f2 Mon Sep 17 00:00:00 2001 From: ojy Date: Wed, 13 May 2026 09:53:16 +0000 Subject: [PATCH] Scanner: IV/HV column + composite EDGE signal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User clarified the real entry signal: not just high IV, but IV higher than HV — that's what makes premium "rich" relative to realized vol and gives the mean-reversion edge for premium sellers. Changes: - New IV/HV column (was already computed server-side as r.ivHv but not displayed). Colored cheap/fair/rich/very-rich. Sortable. - New EDGE composite badge in the Flags cell: lights green when IV Rank ≥ 60 AND IV/HV ≥ 1.20 — premium expensive in own history AND not justified by what stock is actually doing. - Summary row gains an "EDGE setups" count card. - Page header copy reworked to explain the three richness signals (IV Δ%, IV Rank, IV/HV) and why the composite EDGE flag matters. Live test confirms the discrimination: INTC / AAPL / NVDA flag as EDGE; TSLA (high IV/HV but low Rank — its normal regime) and SPY / COIN (IV/HV high because realized vol is unusually quiet, not because IV is rich) are correctly excluded. Co-Authored-By: Claude Sonnet 4.6 --- frontend/scanner.html | 51 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/frontend/scanner.html b/frontend/scanner.html index f452483..78bdbb2 100644 --- a/frontend/scanner.html +++ b/frontend/scanner.html @@ -24,6 +24,10 @@ .ivrank-high { background: rgba(255, 212, 59, 0.18); color: #ffd43b; } .ivrank-vhigh{ background: rgba(255, 107, 107, 0.18); color: #ff6b6b; } .ivrank-na { color: #6c757d; } + .ivhv-cheap { color: #51cf66; font-weight:600; } + .ivhv-fair { color: #cbd3df; font-weight:600; } + .ivhv-rich { color: #ffd43b; font-weight:700; } + .ivhv-vrich { color: #ff6b6b; font-weight:800; } @@ -72,12 +76,15 @@

IV Spike Scanner

- A spike is flagged when current ATM IV is at least +30% - above the symbol's recent baseline (avg ATM IV of the last 30 days, falling back to HV30). - IV Rank (0-100) shows where current IV sits in its 1-year (min, max) range - from saved snapshots — ≥60 = expensive (sell premium), - ≤30 = cheap (buy premium). The yellow - BIG MOVE badge flags |today Δ| ≥ 3%. + Three richness signals — used together they tell you whether the market is paying you + to sell vol that isn't being realized. +
    +
  • · IV Δ% — current ATM IV vs the symbol's recent 30-day baseline. ≥+30% = SPIKE (sudden jump).
  • +
  • · IV Rank — where current IV sits in its 1-year (min, max) range. ≥60 = expensive in its own history.
  • +
  • · IV/HV — current IV divided by 30-day realized vol. ≥1.2 = options pricing more vol than the stock is actually moving. This is the real edge — high IV alone just means a volatile name; high IV above HV means the market is overpaying.
  • +
+ The green EDGE badge fires when IV Rank ≥ 60 AND IV/HV ≥ 1.2 — + that's the combined "sell-premium-now" signal. Blue BIG MOVE = |today Δ| ≥ 3%.
@@ -117,8 +124,8 @@
Scanned
+
EDGE setups
Spikes
-
High IV Rank (≥60)
Biggest mover
Highest IV Δ
@@ -140,6 +147,7 @@ Baseline IV IV Δ% IV Rank + IV/HV HV30 Flags Expiry @@ -167,8 +175,14 @@ + + EDGE SPIKE BIG MOVE @@ -294,6 +308,29 @@ return 'ivrank-low'; }, + ivHvClass(ratio) { + if (!ratio) return ''; + if (ratio >= 1.50) return 'ivhv-vrich'; + if (ratio >= 1.20) return 'ivhv-rich'; + if (ratio >= 0.95) return 'ivhv-fair'; + return 'ivhv-cheap'; + }, + + ivHvLabel(ratio) { + if (!ratio) return ''; + if (ratio >= 1.50) return 'VERY RICH (sell vol)'; + if (ratio >= 1.20) return 'RICH (sell-vol candidate)'; + if (ratio >= 0.95) return 'fair'; + return 'CHEAP (buy-vol candidate)'; + }, + + // EDGE: high IV Rank (premium expensive in own range) AND IV/HV >= 1.2 + // (premium is actually pricing more vol than the stock is realizing) — the + // combined signal for "options market is paying you for risk that isn't there". + hasEdge(r) { + return r && r.ivRankN >= 5 && r.ivRank >= 0.60 && r.ivHv >= 1.20; + }, + goChain(sym) { try { const v = ViewState.load('chain') || {}; v.symbol = sym; ViewState.save('chain', v); } catch {} window.location.href = '/chain.html'; }, goSurface(sym){ try { const v = ViewState.load('surface') || {}; v.symbol = sym; ViewState.save('surface', v); } catch {} window.location.href = '/surface.html'; }, };