Scanner: IV/HV column + composite EDGE signal
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="antialiased">
|
||||
@@ -72,12 +76,15 @@
|
||||
<div class="col">
|
||||
<h2 class="page-title">IV Spike Scanner</h2>
|
||||
<div class="text-secondary mt-1">
|
||||
A <strong>spike</strong> is flagged when current ATM IV is at least <span class="mono">+30%</span>
|
||||
above the symbol's recent baseline (avg ATM IV of the last 30 days, falling back to HV30).
|
||||
<strong>IV Rank</strong> (0-100) shows where current IV sits in its 1-year (min, max) range
|
||||
from saved snapshots — <span style="color:#ffd43b">≥60 = expensive (sell premium)</span>,
|
||||
<span style="color:#51cf66">≤30 = cheap (buy premium)</span>. The yellow
|
||||
<strong>BIG MOVE</strong> 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.
|
||||
<ul class="mt-2 mb-0 small" style="list-style:none;padding-left:0;">
|
||||
<li>· <strong>IV Δ%</strong> — current ATM IV vs the symbol's recent 30-day baseline. <span class="mono">≥+30%</span> = SPIKE (sudden jump).</li>
|
||||
<li>· <strong>IV Rank</strong> — where current IV sits in its 1-year (min, max) range. <span style="color:#ffd43b">≥60 = expensive in its own history</span>.</li>
|
||||
<li>· <strong>IV/HV</strong> — current IV divided by 30-day realized vol. <span style="color:#ffd43b">≥1.2</span> = options pricing more vol than the stock is actually moving. <strong>This is the real edge</strong> — high IV alone just means a volatile name; high IV <em>above</em> HV means the market is overpaying.</li>
|
||||
</ul>
|
||||
The green <strong>EDGE</strong> badge fires when IV Rank ≥ 60 <em>AND</em> IV/HV ≥ 1.2 —
|
||||
that's the combined "sell-premium-now" signal. Blue <strong>BIG MOVE</strong> = |today Δ| ≥ 3%.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,8 +124,8 @@
|
||||
<!-- Spike summary cards -->
|
||||
<div class="row g-2 mb-3" x-show="results.length > 0" x-cloak>
|
||||
<div class="col-6 col-md"><div class="card" style="background:#1e2030;border:1px solid #2d3045;padding:.75rem 1rem;"><div class="text-secondary small">Scanned</div><div class="fs-4 fw-bold" x-text="results.length"></div></div></div>
|
||||
<div class="col-6 col-md"><div class="card" style="background:#1e2030;border:1px solid #51cf6666;padding:.75rem 1rem;"><div class="small" style="color:#51cf66;">EDGE setups</div><div class="fs-4 fw-bold" style="color:#51cf66;" x-text="results.filter(r => hasEdge(r)).length"></div></div></div>
|
||||
<div class="col-6 col-md"><div class="card" style="background:#1e2030;border:1px solid #ffd43b66;padding:.75rem 1rem;"><div class="small" style="color:#ffd43b;">Spikes</div><div class="fs-4 fw-bold" style="color:#ffd43b;" x-text="results.filter(r => r.spike).length"></div></div></div>
|
||||
<div class="col-6 col-md"><div class="card" style="background:#1e2030;border:1px solid #2d3045;padding:.75rem 1rem;"><div class="text-secondary small">High IV Rank (≥60)</div><div class="fs-4 fw-bold" style="color:#ffd43b;" x-text="results.filter(r => r.ivRankN >= 5 && r.ivRank >= 0.60).length"></div></div></div>
|
||||
<div class="col-6 col-md"><div class="card" style="background:#1e2030;border:1px solid #2d3045;padding:.75rem 1rem;"><div class="text-secondary small">Biggest mover</div><div class="fs-5 fw-bold mono" x-text="biggestMover"></div></div></div>
|
||||
<div class="col-6 col-md"><div class="card" style="background:#1e2030;border:1px solid #2d3045;padding:.75rem 1rem;"><div class="text-secondary small">Highest IV Δ</div><div class="fs-5 fw-bold mono" x-text="highestRatio"></div></div></div>
|
||||
</div>
|
||||
@@ -140,6 +147,7 @@
|
||||
<th @click="setSort('baselineIv')" class="text-end">Baseline IV</th>
|
||||
<th @click="setSort('ivJumpPct')" class="text-end">IV Δ%</th>
|
||||
<th @click="setSort('ivRank')" class="text-end" title="IV Rank: where current ATM IV sits in its 1-year (min, max) range. ≥60 = expensive (sell premium); ≤30 = cheap (buy premium).">IV Rank</th>
|
||||
<th @click="setSort('ivHv')" class="text-end" title="IV/HV ratio: current ATM IV divided by 30-day realized vol. ≥1.5 = options pricing FAR more vol than stock is actually moving — premium-rich. <1.0 = options under-pricing realized risk — premium-cheap.">IV/HV</th>
|
||||
<th @click="setSort('hv30')" class="text-end">HV30</th>
|
||||
<th class="text-center">Flags</th>
|
||||
<th>Expiry</th>
|
||||
@@ -167,8 +175,14 @@
|
||||
<span x-text="r.ivRankN >= 5 ? Math.round(r.ivRank*100) : 'n/a'"></span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end mono" :class="ivHvClass(r.ivHv)"
|
||||
:title="r.ivHv ? ('ATM IV ' + (r.atmIv*100).toFixed(1) + '% vs HV30 ' + (r.hv30*100).toFixed(1) + '% — ' + ivHvLabel(r.ivHv)) : 'HV30 unavailable'"
|
||||
x-text="r.ivHv ? r.ivHv.toFixed(2) + 'x' : '—'"></td>
|
||||
<td class="text-end mono" :class="ivClass(r.hv30)" x-text="r.hv30 ? (r.hv30*100).toFixed(1) + '%' : '—'"></td>
|
||||
<td class="text-center">
|
||||
<span x-show="hasEdge(r)" class="badge me-1"
|
||||
style="background:linear-gradient(135deg,#51cf66,#00adb5);color:#0b1020;font-weight:800;letter-spacing:.04em;"
|
||||
:title="'IV Rank ' + Math.round(r.ivRank*100) + ' AND IV/HV ' + r.ivHv.toFixed(2) + 'x — premium is rich AND not justified by realized vol'">EDGE</span>
|
||||
<span x-show="r.spike" class="badge me-1" style="background:#ffd43b;color:#1a1c2e;font-weight:700;">SPIKE</span>
|
||||
<span x-show="r.bigMove" class="badge" style="background:#4d9ef7;color:#fff;font-weight:700;">BIG MOVE</span>
|
||||
</td>
|
||||
@@ -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'; },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user