Scanner: spike = current ATM IV >= +30% above recent baseline
Replaces the IV/HV >= 1.5 heuristic with a baseline-relative jump: a symbol is flagged SPIKE when current ATM IV is at least 30% above its recent baseline (e.g., 10% -> 13%+). Baseline prefers the 30-day average of saved snapshots; falls back to HV30 when no scan history exists. Each scan persists a snapshot so the baseline self-improves over time. BIG MOVE (|chg%| >= 3%) is now shown as a separate badge instead of a spike requirement. - snapshots.ts: add getAverageAtmIv(symbol, days) - datafetch.ts: save snapshot per scan; compute baselineIv, baselineSrc, baselineN, ivJumpPct; spike from jump threshold - options.ts: /api/scan returns new baseline + jump fields - scanner.html: header copy, table columns (Baseline IV, IV Δ%), default sort by ivJumpPct desc, separate SPIKE/BIG MOVE badges Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@ import YahooFinance from "yahoo-finance2";
|
||||
import type { OptionsResult, CallOrPut } from "yahoo-finance2/modules/options";
|
||||
import { bsPrice, bsGreeks, impliedVol } from "./blackscholes.js";
|
||||
import type { OptionQuote, ChainSnapshot } from "./analytics.js";
|
||||
import { computeSkewMetrics } from "./analytics.js";
|
||||
import { saveSnapshot, getAverageAtmIv } from "../db/snapshots.js";
|
||||
import {
|
||||
fmpEnabled,
|
||||
fmpExpirations,
|
||||
@@ -9,6 +11,10 @@ import {
|
||||
fmpQuote,
|
||||
} from "./fmp.js";
|
||||
|
||||
/** Spike threshold — current ATM IV must be at least this much above the baseline. */
|
||||
const SPIKE_JUMP_PCT = 0.30; // +30%
|
||||
const SPIKE_BIG_MOVE_PCT = 3; // |today Δ| ≥ 3%
|
||||
|
||||
const yf = new YahooFinance({ suppressNotices: ["yahooSurvey"] });
|
||||
|
||||
const RISK_FREE_RATE = 0.05;
|
||||
@@ -294,8 +300,13 @@ export type ScanResult = {
|
||||
changePct: number;
|
||||
atmIv: number;
|
||||
hv30: number;
|
||||
ivHv: number; // atmIv / hv30
|
||||
spike: boolean; // true if atmIv/hv30 > 1.5 or |changePct| >= 3
|
||||
baselineIv: number; // recent-30d avg ATM IV from snapshots, else HV30
|
||||
baselineSrc: "history" | "hv30" | "none";
|
||||
baselineN: number; // # of snapshot rows feeding the baseline (0 = HV30 fallback)
|
||||
ivJumpPct: number; // (atmIv - baselineIv) / baselineIv
|
||||
ivHv: number; // atmIv / hv30 (informational; pre-baseline metric)
|
||||
spike: boolean; // ivJumpPct ≥ 30%
|
||||
bigMove: boolean; // |today Δ| ≥ 3%
|
||||
expiry: string;
|
||||
error?: string;
|
||||
};
|
||||
@@ -361,7 +372,33 @@ export async function scanSymbol(symbol: string): Promise<ScanResult> {
|
||||
|
||||
const hv30 = await fetchHistoricalVol(symbol);
|
||||
const ivHv = hv30 > 0 ? atmIv / hv30 : 0;
|
||||
const spike = ivHv >= 1.5 || Math.abs(changePct) >= 3;
|
||||
|
||||
return { symbol, spot, change, changePct, atmIv, hv30, ivHv, spike, expiry };
|
||||
// Save this scan as a snapshot so the baseline grows over time.
|
||||
// Use the snapshot's chain so per-strike IV is recorded too.
|
||||
try {
|
||||
if (atmIv > 0 && spot > 0) {
|
||||
const snap = {
|
||||
symbol, expiry, spot, spotIv: hv30,
|
||||
timestamp: new Date().toISOString(),
|
||||
chain,
|
||||
};
|
||||
saveSnapshot(symbol, expiry, spot, computeSkewMetrics(snap), chain);
|
||||
}
|
||||
} catch { /* non-fatal */ }
|
||||
|
||||
// Baseline IV: prefer 30-day avg from snapshots; fall back to HV30.
|
||||
const hist = getAverageAtmIv(symbol, 30);
|
||||
let baselineIv = 0, baselineSrc: ScanResult["baselineSrc"] = "none", baselineN = 0;
|
||||
if (hist && hist.avg > 0) { baselineIv = hist.avg; baselineSrc = "history"; baselineN = hist.n; }
|
||||
else if (hv30 > 0) { baselineIv = hv30; baselineSrc = "hv30"; baselineN = 0; }
|
||||
|
||||
const ivJumpPct = baselineIv > 0 ? (atmIv - baselineIv) / baselineIv : 0;
|
||||
const spike = ivJumpPct >= SPIKE_JUMP_PCT;
|
||||
const bigMove = Math.abs(changePct) >= SPIKE_BIG_MOVE_PCT;
|
||||
|
||||
return {
|
||||
symbol, spot, change, changePct, atmIv, hv30,
|
||||
baselineIv, baselineSrc, baselineN,
|
||||
ivJumpPct, ivHv, spike, bigMove, expiry,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user