IV vs RV Analysis
Compare model-free implied volatility from options with tick-based realized volatility in real time.
What is IV vs RV Analysis?
This workflow compares:
- Implied Volatility (IV) from the option chain using model-free variance-swap methodology.
- Realized Volatility (RV) from actual price observations using quadratic variation.
Monitoring both together gives you a live view of whether options are pricing more or less movement than the underlying is actually realizing.
1. Compute Model-Free 0DTE IV
Use all calls and puts for one expiration:
import { computeVarianceSwapIV, NormalizedOption } from "@fullstackcraftllc/floe";
const todayOptions: NormalizedOption[] = getTodayOptions(); // your chain slice
const spot = 600.0;
const riskFreeRate = 0.05;
const iv = computeVarianceSwapIV(todayOptions, spot, riskFreeRate);
console.log("0DTE IV:", (iv.impliedVolatility * 100).toFixed(2) + "%");
console.log("Forward:", iv.forward.toFixed(2));
console.log("K0:", iv.k0);
console.log("Contributing strikes:", iv.numStrikes);
2. Optional: Interpolate Across Two Terms
You can compute a constant-maturity estimate by blending near/far expirations:
import { computeImpliedVolatility, NormalizedOption } from "@fullstackcraftllc/floe";
const nearTermOptions: NormalizedOption[] = getNearTermOptions();
const farTermOptions: NormalizedOption[] = getFarTermOptions();
const result = computeImpliedVolatility(
nearTermOptions,
600.0,
0.05,
farTermOptions,
1 // targetDays
);
console.log("Interpolated IV:", (result.impliedVolatility * 100).toFixed(2) + "%");
console.log("Near-term IV:", (result.nearTerm.impliedVolatility * 100).toFixed(2) + "%");
console.log("Far-term IV:", ((result.farTerm?.impliedVolatility ?? 0) * 100).toFixed(2) + "%");
3. Compute Tick-Based RV
Pass all observed prices and timestamps:
import { computeRealizedVolatility, PriceObservation } from "@fullstackcraftllc/floe";
const observations: PriceObservation[] = [
{ price: 600.10, timestamp: 1708099800000 },
{ price: 600.25, timestamp: 1708099860000 },
{ price: 599.80, timestamp: 1708099920000 },
// ...streaming ticks
];
const rv = computeRealizedVolatility(observations);
console.log("RV:", (rv.realizedVolatility * 100).toFixed(2) + "%");
console.log("Observations:", rv.numObservations);
console.log("Elapsed (min):", rv.elapsedMinutes.toFixed(0));
console.log("Quadratic Variation:", rv.quadraticVariation.toFixed(8));
4. Track the IV-RV Spread
Compute the live spread and monitor flips:
const spread = iv.impliedVolatility - rv.realizedVolatility;
console.log("IV - RV:", (spread * 100).toFixed(2) + " pts");
if (spread > 0) {
console.log("Options imply more movement than has been realized.");
} else if (spread < 0) {
console.log("Realized movement is exceeding implied expectations.");
} else {
console.log("Implied and realized are currently aligned.");
}
5. Vol Response Z-Score (Is Vol Bid or Offered?)
The raw IV-RV spread tells you whether IV exceeds RV, but not whether this is abnormal given the price path. The vol response model answers: "Is IV moving more than expected given what the underlying is doing?"
import {
buildVolResponseObservation,
computeVolResponseZScore,
VolResponseObservation,
} from "@fullstackcraftllc/floe";
// Accumulate observations as the session progresses
const observations: VolResponseObservation[] = [];
let previous: { iv: number; spot: number } | null = null;
function onTick(iv: number, rv: number, spot: number) {
if (previous) {
const obs = buildVolResponseObservation(
{ iv, rv, spot, timestamp: Date.now() },
previous
);
observations.push(obs);
const result = computeVolResponseZScore(observations);
if (result.isValid) {
console.log(`Z-Score: ${result.zScore.toFixed(2)} (${result.signal})`);
console.log(`R²: ${result.rSquared.toFixed(3)}`);
console.log(`Expected ΔIV: ${(result.expectedDeltaIV * 10000).toFixed(1)} bps`);
console.log(`Observed ΔIV: ${(result.observedDeltaIV * 10000).toFixed(1)} bps`);
} else {
console.log(`Warming up: ${result.numObservations}/${result.minObservations}`);
}
}
previous = { iv, spot };
}
Signal interpretation:
- z > 1.5 (vol_bid): IV is rising faster than the price action justifies — stress or demand for protection.
- z < -1.5 (vol_offered): IV is falling faster than expected — supply or vol crush.
- Near zero (neutral): Normal vol response given the return path.
The model uses an expanding window from session open, so it requires ~30 ticks to warm up. After that, the z-score becomes increasingly reliable as the regression coefficients stabilize.
Practical Notes
- For 0DTE monitoring, recompute both IV and RV continuously as new quotes/ticks arrive.
- RV naturally stabilizes as more observations accumulate throughout the session.
- IV-RV is most useful as a time series, not a single point estimate.
- The vol response z-score adds a third dimension: whether the IV movement is expected or anomalous given price action.