@fullstackcraftllc/floe 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/client/FloeClient.d.ts +5 -1
- package/dist/client/FloeClient.js +54 -0
- package/dist/client/brokers/IBKRClient.d.ts +324 -0
- package/dist/client/brokers/IBKRClient.js +797 -0
- package/dist/exposure/index.d.ts +15 -10
- package/dist/exposure/index.js +164 -106
- package/dist/hedgeflow/charm.d.ts +23 -0
- package/dist/hedgeflow/charm.js +113 -0
- package/dist/hedgeflow/curve.d.ts +27 -0
- package/dist/hedgeflow/curve.js +315 -0
- package/dist/hedgeflow/index.d.ts +33 -0
- package/dist/hedgeflow/index.js +52 -0
- package/dist/hedgeflow/regime.d.ts +7 -0
- package/dist/hedgeflow/regime.js +99 -0
- package/dist/hedgeflow/types.d.ts +185 -0
- package/dist/hedgeflow/types.js +2 -0
- package/dist/impliedpdf/adjusted.d.ts +5 -2
- package/dist/impliedpdf/adjusted.js +5 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +19 -19
- package/dist/iv/index.d.ts +52 -0
- package/dist/iv/index.js +287 -0
- package/dist/iv/types.d.ts +40 -0
- package/dist/iv/types.js +2 -0
- package/dist/rv/index.d.ts +26 -0
- package/dist/rv/index.js +81 -0
- package/dist/rv/types.d.ts +32 -0
- package/dist/rv/types.js +2 -0
- package/dist/types/index.d.ts +66 -0
- package/package.json +1 -1
|
@@ -114,7 +114,7 @@ export declare const OPEX_CONFIG: ExposureAdjustmentConfig;
|
|
|
114
114
|
* @param symbol - Underlying ticker symbol
|
|
115
115
|
* @param underlyingPrice - Current spot price
|
|
116
116
|
* @param callOptions - Call options for a single expiry
|
|
117
|
-
* @param exposures -
|
|
117
|
+
* @param exposures - Canonical exposure metrics from calculateGammaVannaCharmExposures()
|
|
118
118
|
* @param config - Adjustment configuration (uses defaults if not provided)
|
|
119
119
|
* @returns Baseline and adjusted PDFs with comparison metrics
|
|
120
120
|
*
|
|
@@ -122,7 +122,10 @@ export declare const OPEX_CONFIG: ExposureAdjustmentConfig;
|
|
|
122
122
|
* ```typescript
|
|
123
123
|
* // Get exposures first
|
|
124
124
|
* const allExposures = calculateGammaVannaCharmExposures(chain, ivSurfaces);
|
|
125
|
-
* const
|
|
125
|
+
* const expiry = allExposures.find(e => e.expiration === targetExpiry);
|
|
126
|
+
* const expiryExposures = expiry
|
|
127
|
+
* ? { spotPrice: expiry.spotPrice, expiration: expiry.expiration, ...expiry.canonical }
|
|
128
|
+
* : undefined;
|
|
126
129
|
*
|
|
127
130
|
* // Calculate adjusted PDF
|
|
128
131
|
* const result = estimateExposureAdjustedPDF(
|
|
@@ -122,7 +122,7 @@ exports.OPEX_CONFIG = {
|
|
|
122
122
|
* @param symbol - Underlying ticker symbol
|
|
123
123
|
* @param underlyingPrice - Current spot price
|
|
124
124
|
* @param callOptions - Call options for a single expiry
|
|
125
|
-
* @param exposures -
|
|
125
|
+
* @param exposures - Canonical exposure metrics from calculateGammaVannaCharmExposures()
|
|
126
126
|
* @param config - Adjustment configuration (uses defaults if not provided)
|
|
127
127
|
* @returns Baseline and adjusted PDFs with comparison metrics
|
|
128
128
|
*
|
|
@@ -130,7 +130,10 @@ exports.OPEX_CONFIG = {
|
|
|
130
130
|
* ```typescript
|
|
131
131
|
* // Get exposures first
|
|
132
132
|
* const allExposures = calculateGammaVannaCharmExposures(chain, ivSurfaces);
|
|
133
|
-
* const
|
|
133
|
+
* const expiry = allExposures.find(e => e.expiration === targetExpiry);
|
|
134
|
+
* const expiryExposures = expiry
|
|
135
|
+
* ? { spotPrice: expiry.spotPrice, expiration: expiry.expiration, ...expiry.canonical }
|
|
136
|
+
* : undefined;
|
|
134
137
|
*
|
|
135
138
|
* // Calculate adjusted PDF
|
|
136
139
|
* const result = estimateExposureAdjustedPDF(
|
package/dist/index.d.ts
CHANGED
|
@@ -14,11 +14,15 @@ export { buildOCCSymbol, parseOCCSymbol, generateStrikesAroundSpot, generateOCCS
|
|
|
14
14
|
export type { OCCSymbolParams, ParsedOCCSymbol, StrikeGenerationParams, } from './utils/occ';
|
|
15
15
|
export { estimateImpliedProbabilityDistribution, estimateImpliedProbabilityDistributions, getProbabilityInRange, getCumulativeProbability, getQuantile, estimateExposureAdjustedPDF, getEdgeAtPrice, getSignificantAdjustmentLevels, DEFAULT_ADJUSTMENT_CONFIG, LOW_VOL_CONFIG, CRISIS_CONFIG, OPEX_CONFIG, } from './impliedpdf';
|
|
16
16
|
export type { StrikeProbability, ImpliedProbabilityDistribution, ImpliedPDFResult, ExposureAdjustmentConfig, AdjustedPDFResult, PDFComparison, } from './impliedpdf';
|
|
17
|
-
export { deriveRegimeParams, interpolateIVAtStrike,
|
|
18
|
-
export type { MarketRegime, RegimeParams,
|
|
17
|
+
export { deriveRegimeParams, interpolateIVAtStrike, computeHedgeImpulseCurve, computeCharmIntegral, analyzeHedgeFlow, } from './hedgeflow';
|
|
18
|
+
export type { MarketRegime, RegimeParams, HedgeImpulseConfig, HedgeImpulsePoint, HedgeImpulseCurve, ZeroCrossing, ImpulseExtremum, DirectionalAsymmetry, ImpulseRegime, CharmIntegralConfig, CharmBucket, CharmIntegral, HedgeFlowAnalysis, } from './hedgeflow';
|
|
19
19
|
export { FloeClient, Broker } from './client/FloeClient';
|
|
20
20
|
export { TradierClient } from './client/brokers/TradierClient';
|
|
21
21
|
export { TastyTradeClient } from './client/brokers/TastyTradeClient';
|
|
22
22
|
export { TradeStationClient } from './client/brokers/TradeStationClient';
|
|
23
23
|
export type { AggressorSide, IntradayTrade } from './client/brokers/TradierClient';
|
|
24
24
|
export { genericAdapter, schwabAdapter, ibkrAdapter, tdaAdapter, brokerAdapters, getAdapter, createOptionChain, } from './adapters';
|
|
25
|
+
export { computeVarianceSwapIV, computeImpliedVolatility, } from './iv';
|
|
26
|
+
export type { VarianceSwapResult, ImpliedVolatilityResult, } from './iv';
|
|
27
|
+
export { computeRealizedVolatility, } from './rv';
|
|
28
|
+
export type { PriceObservation, RealizedVolatilityResult, } from './rv';
|
package/dist/index.js
CHANGED
|
@@ -20,8 +20,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
20
20
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
21
|
};
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.
|
|
24
|
-
exports.createOptionChain = exports.getAdapter = void 0;
|
|
23
|
+
exports.computeRealizedVolatility = exports.computeImpliedVolatility = exports.computeVarianceSwapIV = exports.createOptionChain = exports.getAdapter = exports.brokerAdapters = exports.tdaAdapter = exports.ibkrAdapter = exports.schwabAdapter = exports.genericAdapter = exports.TradeStationClient = exports.TastyTradeClient = exports.TradierClient = exports.Broker = exports.FloeClient = exports.analyzeHedgeFlow = exports.computeCharmIntegral = exports.computeHedgeImpulseCurve = exports.interpolateIVAtStrike = exports.deriveRegimeParams = exports.OPEX_CONFIG = exports.CRISIS_CONFIG = exports.LOW_VOL_CONFIG = exports.DEFAULT_ADJUSTMENT_CONFIG = exports.getSignificantAdjustmentLevels = exports.getEdgeAtPrice = exports.estimateExposureAdjustedPDF = exports.getQuantile = exports.getCumulativeProbability = exports.getProbabilityInRange = exports.estimateImpliedProbabilityDistributions = exports.estimateImpliedProbabilityDistribution = exports.generateOCCSymbolsAroundSpot = exports.generateOCCSymbolsForStrikes = exports.generateStrikesAroundSpot = exports.parseOCCSymbol = exports.buildOCCSymbol = exports.normalPDF = exports.cumulativeNormalDistribution = exports.calculateSharesNeededToCover = exports.calculateGammaVannaCharmExposures = exports.smoothTotalVarianceSmile = exports.getIVForStrike = exports.getIVSurfaces = exports.getTimeToExpirationInYears = exports.getMillisecondsToExpiration = exports.calculateImpliedVolatility = exports.calculateGreeks = exports.blackScholes = void 0;
|
|
25
24
|
// Core types
|
|
26
25
|
__exportStar(require("./types"), exports);
|
|
27
26
|
// Black-Scholes pricing and Greeks
|
|
@@ -68,23 +67,17 @@ Object.defineProperty(exports, "DEFAULT_ADJUSTMENT_CONFIG", { enumerable: true,
|
|
|
68
67
|
Object.defineProperty(exports, "LOW_VOL_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.LOW_VOL_CONFIG; } });
|
|
69
68
|
Object.defineProperty(exports, "CRISIS_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.CRISIS_CONFIG; } });
|
|
70
69
|
Object.defineProperty(exports, "OPEX_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.OPEX_CONFIG; } });
|
|
71
|
-
//
|
|
72
|
-
var
|
|
73
|
-
// Regime derivation
|
|
74
|
-
Object.defineProperty(exports, "deriveRegimeParams", { enumerable: true, get: function () { return
|
|
75
|
-
Object.defineProperty(exports, "interpolateIVAtStrike", { enumerable: true, get: function () { return
|
|
76
|
-
//
|
|
77
|
-
Object.defineProperty(exports, "
|
|
78
|
-
//
|
|
79
|
-
Object.defineProperty(exports, "
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
Object.defineProperty(exports, "calculateIVPressures", { enumerable: true, get: function () { return pressure_1.calculateIVPressures; } });
|
|
83
|
-
Object.defineProperty(exports, "predictIVSurfaceEvolution", { enumerable: true, get: function () { return pressure_1.predictIVSurfaceEvolution; } });
|
|
84
|
-
Object.defineProperty(exports, "runCascadeSimulation", { enumerable: true, get: function () { return pressure_1.runCascadeSimulation; } });
|
|
85
|
-
Object.defineProperty(exports, "analyzeIVPath", { enumerable: true, get: function () { return pressure_1.analyzeIVPath; } });
|
|
86
|
-
Object.defineProperty(exports, "getIVPathOfLeastResistance", { enumerable: true, get: function () { return pressure_1.getIVPathOfLeastResistance; } });
|
|
87
|
-
Object.defineProperty(exports, "DEFAULT_IV_PATH_CONFIG", { enumerable: true, get: function () { return pressure_1.DEFAULT_IV_PATH_CONFIG; } });
|
|
70
|
+
// Hedge flow analysis (impulse curve, charm integral, regime derivation)
|
|
71
|
+
var hedgeflow_1 = require("./hedgeflow");
|
|
72
|
+
// Regime derivation from IV surface
|
|
73
|
+
Object.defineProperty(exports, "deriveRegimeParams", { enumerable: true, get: function () { return hedgeflow_1.deriveRegimeParams; } });
|
|
74
|
+
Object.defineProperty(exports, "interpolateIVAtStrike", { enumerable: true, get: function () { return hedgeflow_1.interpolateIVAtStrike; } });
|
|
75
|
+
// Hedge impulse curve
|
|
76
|
+
Object.defineProperty(exports, "computeHedgeImpulseCurve", { enumerable: true, get: function () { return hedgeflow_1.computeHedgeImpulseCurve; } });
|
|
77
|
+
// Charm integral
|
|
78
|
+
Object.defineProperty(exports, "computeCharmIntegral", { enumerable: true, get: function () { return hedgeflow_1.computeCharmIntegral; } });
|
|
79
|
+
// Combined analysis
|
|
80
|
+
Object.defineProperty(exports, "analyzeHedgeFlow", { enumerable: true, get: function () { return hedgeflow_1.analyzeHedgeFlow; } });
|
|
88
81
|
// Client
|
|
89
82
|
var FloeClient_1 = require("./client/FloeClient");
|
|
90
83
|
Object.defineProperty(exports, "FloeClient", { enumerable: true, get: function () { return FloeClient_1.FloeClient; } });
|
|
@@ -104,3 +97,10 @@ Object.defineProperty(exports, "tdaAdapter", { enumerable: true, get: function (
|
|
|
104
97
|
Object.defineProperty(exports, "brokerAdapters", { enumerable: true, get: function () { return adapters_1.brokerAdapters; } });
|
|
105
98
|
Object.defineProperty(exports, "getAdapter", { enumerable: true, get: function () { return adapters_1.getAdapter; } });
|
|
106
99
|
Object.defineProperty(exports, "createOptionChain", { enumerable: true, get: function () { return adapters_1.createOptionChain; } });
|
|
100
|
+
// Model-free implied volatility (variance swap / VIX methodology)
|
|
101
|
+
var iv_1 = require("./iv");
|
|
102
|
+
Object.defineProperty(exports, "computeVarianceSwapIV", { enumerable: true, get: function () { return iv_1.computeVarianceSwapIV; } });
|
|
103
|
+
Object.defineProperty(exports, "computeImpliedVolatility", { enumerable: true, get: function () { return iv_1.computeImpliedVolatility; } });
|
|
104
|
+
// Realized volatility (tick-based quadratic variation)
|
|
105
|
+
var rv_1 = require("./rv");
|
|
106
|
+
Object.defineProperty(exports, "computeRealizedVolatility", { enumerable: true, get: function () { return rv_1.computeRealizedVolatility; } });
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { NormalizedOption } from '../types';
|
|
2
|
+
import { VarianceSwapResult, ImpliedVolatilityResult } from './types';
|
|
3
|
+
export type { VarianceSwapResult, ImpliedVolatilityResult } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Compute the model-free implied variance for a single expiration
|
|
6
|
+
* using the CBOE variance swap methodology.
|
|
7
|
+
*
|
|
8
|
+
* This implements the formula:
|
|
9
|
+
*
|
|
10
|
+
* σ² = (2/T) × Σᵢ (ΔKᵢ/Kᵢ²) × e^(rT) × Q(Kᵢ) - (1/T) × (F/K₀ - 1)²
|
|
11
|
+
*
|
|
12
|
+
* Where:
|
|
13
|
+
* - K₀ is the strike where |call_mid - put_mid| is minimized
|
|
14
|
+
* - F = K₀ + e^(rT) × (call(K₀) - put(K₀)) is the forward price
|
|
15
|
+
* - Q(Kᵢ) = put mid for Kᵢ < K₀, call mid for Kᵢ > K₀,
|
|
16
|
+
* average of call and put mids at K₀
|
|
17
|
+
* - ΔKᵢ = (Kᵢ₊₁ - Kᵢ₋₁) / 2 (actual strike spacing, not hardcoded)
|
|
18
|
+
*
|
|
19
|
+
* Two consecutive zero-bid options terminate the summation in each
|
|
20
|
+
* direction (puts walking down, calls walking up), per CBOE rules.
|
|
21
|
+
*
|
|
22
|
+
* @param options - All options for one expiration (calls and puts)
|
|
23
|
+
* @param spot - Current underlying price
|
|
24
|
+
* @param riskFreeRate - Annual risk-free rate as decimal (e.g. 0.05)
|
|
25
|
+
* @returns Variance swap result with annualized IV
|
|
26
|
+
*/
|
|
27
|
+
export declare function computeVarianceSwapIV(options: NormalizedOption[], spot: number, riskFreeRate: number): VarianceSwapResult;
|
|
28
|
+
/**
|
|
29
|
+
* Compute implied volatility from option prices using the CBOE
|
|
30
|
+
* variance swap methodology.
|
|
31
|
+
*
|
|
32
|
+
* If only nearTermOptions are provided, computes the single-expiration
|
|
33
|
+
* model-free implied volatility directly.
|
|
34
|
+
*
|
|
35
|
+
* If farTermOptions are also provided, performs CBOE VIX-style
|
|
36
|
+
* interpolation between the two terms to produce a constant-maturity
|
|
37
|
+
* measure at `targetDays` (defaults to the far term's DTE).
|
|
38
|
+
*
|
|
39
|
+
* The interpolation formula (in variance space):
|
|
40
|
+
*
|
|
41
|
+
* VIX = 100 × √{ [T₁σ₁² × (N₂ - N_target)/(N₂ - N₁)
|
|
42
|
+
* + T₂σ₂² × (N_target - N₁)/(N₂ - N₁)]
|
|
43
|
+
* × N_365 / N_target }
|
|
44
|
+
*
|
|
45
|
+
* @param nearTermOptions - Options for the near-term expiration
|
|
46
|
+
* @param spot - Current underlying price
|
|
47
|
+
* @param riskFreeRate - Annual risk-free rate as decimal
|
|
48
|
+
* @param farTermOptions - Options for the far-term expiration (optional)
|
|
49
|
+
* @param targetDays - Target constant maturity in days for interpolation
|
|
50
|
+
* (defaults to far term DTE if far term provided)
|
|
51
|
+
*/
|
|
52
|
+
export declare function computeImpliedVolatility(nearTermOptions: NormalizedOption[], spot: number, riskFreeRate: number, farTermOptions?: NormalizedOption[] | null, targetDays?: number): ImpliedVolatilityResult;
|
package/dist/iv/index.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeVarianceSwapIV = computeVarianceSwapIV;
|
|
4
|
+
exports.computeImpliedVolatility = computeImpliedVolatility;
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const blackscholes_1 = require("../blackscholes");
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Internal helpers
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Compute ΔK for each strike: half the distance between adjacent strikes.
|
|
12
|
+
* For endpoints, use the distance to the single neighbor.
|
|
13
|
+
*/
|
|
14
|
+
function computeDeltaK(strikes) {
|
|
15
|
+
const deltaK = new Map();
|
|
16
|
+
if (strikes.length === 0)
|
|
17
|
+
return deltaK;
|
|
18
|
+
if (strikes.length === 1) {
|
|
19
|
+
deltaK.set(strikes[0], 1); // fallback
|
|
20
|
+
return deltaK;
|
|
21
|
+
}
|
|
22
|
+
for (let i = 0; i < strikes.length; i++) {
|
|
23
|
+
if (i === 0) {
|
|
24
|
+
deltaK.set(strikes[i], strikes[i + 1] - strikes[i]);
|
|
25
|
+
}
|
|
26
|
+
else if (i === strikes.length - 1) {
|
|
27
|
+
deltaK.set(strikes[i], strikes[i] - strikes[i - 1]);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
deltaK.set(strikes[i], (strikes[i + 1] - strikes[i - 1]) / 2);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return deltaK;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Group options by strike, pairing calls and puts.
|
|
37
|
+
*/
|
|
38
|
+
function pairOptionsByStrike(options) {
|
|
39
|
+
const pairs = new Map();
|
|
40
|
+
for (const opt of options) {
|
|
41
|
+
const existing = pairs.get(opt.strike) || {};
|
|
42
|
+
if (opt.optionType === 'call') {
|
|
43
|
+
existing.call = opt;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
existing.put = opt;
|
|
47
|
+
}
|
|
48
|
+
pairs.set(opt.strike, existing);
|
|
49
|
+
}
|
|
50
|
+
return pairs;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get mid price for an option. Returns 0 if no valid quote.
|
|
54
|
+
*/
|
|
55
|
+
function midPrice(opt) {
|
|
56
|
+
if (!opt)
|
|
57
|
+
return 0;
|
|
58
|
+
if (opt.bid > 0 && opt.ask > 0)
|
|
59
|
+
return (opt.bid + opt.ask) / 2;
|
|
60
|
+
if (opt.mark > 0)
|
|
61
|
+
return opt.mark;
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Compute the model-free implied variance for a single expiration
|
|
66
|
+
* using the CBOE variance swap methodology.
|
|
67
|
+
*
|
|
68
|
+
* This implements the formula:
|
|
69
|
+
*
|
|
70
|
+
* σ² = (2/T) × Σᵢ (ΔKᵢ/Kᵢ²) × e^(rT) × Q(Kᵢ) - (1/T) × (F/K₀ - 1)²
|
|
71
|
+
*
|
|
72
|
+
* Where:
|
|
73
|
+
* - K₀ is the strike where |call_mid - put_mid| is minimized
|
|
74
|
+
* - F = K₀ + e^(rT) × (call(K₀) - put(K₀)) is the forward price
|
|
75
|
+
* - Q(Kᵢ) = put mid for Kᵢ < K₀, call mid for Kᵢ > K₀,
|
|
76
|
+
* average of call and put mids at K₀
|
|
77
|
+
* - ΔKᵢ = (Kᵢ₊₁ - Kᵢ₋₁) / 2 (actual strike spacing, not hardcoded)
|
|
78
|
+
*
|
|
79
|
+
* Two consecutive zero-bid options terminate the summation in each
|
|
80
|
+
* direction (puts walking down, calls walking up), per CBOE rules.
|
|
81
|
+
*
|
|
82
|
+
* @param options - All options for one expiration (calls and puts)
|
|
83
|
+
* @param spot - Current underlying price
|
|
84
|
+
* @param riskFreeRate - Annual risk-free rate as decimal (e.g. 0.05)
|
|
85
|
+
* @returns Variance swap result with annualized IV
|
|
86
|
+
*/
|
|
87
|
+
function computeVarianceSwapIV(options, spot, riskFreeRate) {
|
|
88
|
+
// Pair calls and puts by strike
|
|
89
|
+
const pairs = pairOptionsByStrike(options);
|
|
90
|
+
const strikes = Array.from(pairs.keys()).sort((a, b) => a - b);
|
|
91
|
+
if (strikes.length === 0) {
|
|
92
|
+
return emptyResult(spot);
|
|
93
|
+
}
|
|
94
|
+
// Use first option's expiration (all should be same expiry)
|
|
95
|
+
const expiration = options[0].expirationTimestamp;
|
|
96
|
+
const T = (0, blackscholes_1.getTimeToExpirationInYears)(expiration);
|
|
97
|
+
if (T <= 0) {
|
|
98
|
+
return emptyResult(spot);
|
|
99
|
+
}
|
|
100
|
+
const r = riskFreeRate;
|
|
101
|
+
const eRT = Math.exp(r * T);
|
|
102
|
+
// Step 1: Find K₀ — strike where |call_mid - put_mid| is minimized
|
|
103
|
+
// Only consider strikes within reasonable range of spot
|
|
104
|
+
let k0 = strikes[0];
|
|
105
|
+
let minDiff = Infinity;
|
|
106
|
+
let callAtK0 = 0;
|
|
107
|
+
let putAtK0 = 0;
|
|
108
|
+
for (const strike of strikes) {
|
|
109
|
+
const pair = pairs.get(strike);
|
|
110
|
+
const callMid = midPrice(pair.call);
|
|
111
|
+
const putMid = midPrice(pair.put);
|
|
112
|
+
if (callMid > 0 && putMid > 0) {
|
|
113
|
+
const diff = Math.abs(callMid - putMid);
|
|
114
|
+
if (diff < minDiff) {
|
|
115
|
+
minDiff = diff;
|
|
116
|
+
k0 = strike;
|
|
117
|
+
callAtK0 = callMid;
|
|
118
|
+
putAtK0 = putMid;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Step 2: Forward price via put-call parity
|
|
123
|
+
const F = k0 + eRT * (callAtK0 - putAtK0);
|
|
124
|
+
// Step 3: Compute ΔK for each strike
|
|
125
|
+
const deltaKMap = computeDeltaK(strikes);
|
|
126
|
+
// Step 4: Sum OTM contributions
|
|
127
|
+
// Puts below K₀ (walk down, stop after two consecutive zero bids)
|
|
128
|
+
let putContribution = 0;
|
|
129
|
+
let callContribution = 0;
|
|
130
|
+
let numStrikes = 0;
|
|
131
|
+
// Put side: strikes ≤ K₀, walking downward from K₀
|
|
132
|
+
const putStrikes = strikes.filter(k => k <= k0).reverse(); // descending
|
|
133
|
+
let consecutiveZeroBids = 0;
|
|
134
|
+
for (const strike of putStrikes) {
|
|
135
|
+
const pair = pairs.get(strike);
|
|
136
|
+
const dk = deltaKMap.get(strike) || 1;
|
|
137
|
+
let Q = 0;
|
|
138
|
+
if (strike === k0) {
|
|
139
|
+
// At K₀: average of call and put
|
|
140
|
+
Q = (midPrice(pair.call) + midPrice(pair.put)) / 2;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Below K₀: use put
|
|
144
|
+
if (!pair.put || pair.put.bid === 0) {
|
|
145
|
+
consecutiveZeroBids++;
|
|
146
|
+
if (consecutiveZeroBids >= 2)
|
|
147
|
+
break;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
consecutiveZeroBids = 0;
|
|
151
|
+
Q = midPrice(pair.put);
|
|
152
|
+
}
|
|
153
|
+
if (Q > 0) {
|
|
154
|
+
putContribution += (dk / (strike * strike)) * eRT * Q;
|
|
155
|
+
numStrikes++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Call side: strikes ≥ K₀, walking upward from K₀
|
|
159
|
+
const callStrikes = strikes.filter(k => k >= k0); // ascending
|
|
160
|
+
consecutiveZeroBids = 0;
|
|
161
|
+
for (const strike of callStrikes) {
|
|
162
|
+
const pair = pairs.get(strike);
|
|
163
|
+
const dk = deltaKMap.get(strike) || 1;
|
|
164
|
+
let Q = 0;
|
|
165
|
+
if (strike === k0) {
|
|
166
|
+
// Already counted in put side, skip to avoid double-counting
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Above K₀: use call
|
|
171
|
+
if (!pair.call || pair.call.bid === 0) {
|
|
172
|
+
consecutiveZeroBids++;
|
|
173
|
+
if (consecutiveZeroBids >= 2)
|
|
174
|
+
break;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
consecutiveZeroBids = 0;
|
|
178
|
+
Q = midPrice(pair.call);
|
|
179
|
+
}
|
|
180
|
+
if (Q > 0) {
|
|
181
|
+
callContribution += (dk / (strike * strike)) * eRT * Q;
|
|
182
|
+
numStrikes++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Step 5: Compute σ²
|
|
186
|
+
const totalContribution = putContribution + callContribution;
|
|
187
|
+
const variance = (2 / T) * totalContribution - (1 / T) * Math.pow(F / k0 - 1, 2);
|
|
188
|
+
// Guard against negative variance (can happen with bad data)
|
|
189
|
+
const clampedVariance = Math.max(0, variance);
|
|
190
|
+
const iv = Math.sqrt(clampedVariance);
|
|
191
|
+
return {
|
|
192
|
+
impliedVolatility: iv,
|
|
193
|
+
annualizedVariance: clampedVariance,
|
|
194
|
+
forward: F,
|
|
195
|
+
k0,
|
|
196
|
+
timeToExpiry: T,
|
|
197
|
+
expiration,
|
|
198
|
+
numStrikes,
|
|
199
|
+
putContribution,
|
|
200
|
+
callContribution,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Compute implied volatility from option prices using the CBOE
|
|
205
|
+
* variance swap methodology.
|
|
206
|
+
*
|
|
207
|
+
* If only nearTermOptions are provided, computes the single-expiration
|
|
208
|
+
* model-free implied volatility directly.
|
|
209
|
+
*
|
|
210
|
+
* If farTermOptions are also provided, performs CBOE VIX-style
|
|
211
|
+
* interpolation between the two terms to produce a constant-maturity
|
|
212
|
+
* measure at `targetDays` (defaults to the far term's DTE).
|
|
213
|
+
*
|
|
214
|
+
* The interpolation formula (in variance space):
|
|
215
|
+
*
|
|
216
|
+
* VIX = 100 × √{ [T₁σ₁² × (N₂ - N_target)/(N₂ - N₁)
|
|
217
|
+
* + T₂σ₂² × (N_target - N₁)/(N₂ - N₁)]
|
|
218
|
+
* × N_365 / N_target }
|
|
219
|
+
*
|
|
220
|
+
* @param nearTermOptions - Options for the near-term expiration
|
|
221
|
+
* @param spot - Current underlying price
|
|
222
|
+
* @param riskFreeRate - Annual risk-free rate as decimal
|
|
223
|
+
* @param farTermOptions - Options for the far-term expiration (optional)
|
|
224
|
+
* @param targetDays - Target constant maturity in days for interpolation
|
|
225
|
+
* (defaults to far term DTE if far term provided)
|
|
226
|
+
*/
|
|
227
|
+
function computeImpliedVolatility(nearTermOptions, spot, riskFreeRate, farTermOptions, targetDays) {
|
|
228
|
+
const nearResult = computeVarianceSwapIV(nearTermOptions, spot, riskFreeRate);
|
|
229
|
+
// Single-term mode
|
|
230
|
+
if (!farTermOptions || farTermOptions.length === 0) {
|
|
231
|
+
return {
|
|
232
|
+
impliedVolatility: nearResult.impliedVolatility,
|
|
233
|
+
nearTerm: nearResult,
|
|
234
|
+
farTerm: null,
|
|
235
|
+
targetDays: null,
|
|
236
|
+
isInterpolated: false,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// Two-term interpolation mode
|
|
240
|
+
const farResult = computeVarianceSwapIV(farTermOptions, spot, riskFreeRate);
|
|
241
|
+
const N1 = nearResult.timeToExpiry * types_1.MILLISECONDS_PER_YEAR; // near term ms
|
|
242
|
+
const N2 = farResult.timeToExpiry * types_1.MILLISECONDS_PER_YEAR; // far term ms
|
|
243
|
+
const T1 = nearResult.timeToExpiry;
|
|
244
|
+
const T2 = farResult.timeToExpiry;
|
|
245
|
+
const sigma1sq = nearResult.annualizedVariance;
|
|
246
|
+
const sigma2sq = farResult.annualizedVariance;
|
|
247
|
+
// Target in milliseconds
|
|
248
|
+
const N365 = types_1.MILLISECONDS_PER_YEAR;
|
|
249
|
+
const effectiveTargetDays = targetDays ?? Math.round(farResult.timeToExpiry * 365);
|
|
250
|
+
const Ntarget = effectiveTargetDays * (types_1.MILLISECONDS_PER_YEAR / 365);
|
|
251
|
+
// Guard: if near and far are the same expiration, just return near
|
|
252
|
+
if (Math.abs(N2 - N1) < 1) {
|
|
253
|
+
return {
|
|
254
|
+
impliedVolatility: nearResult.impliedVolatility,
|
|
255
|
+
nearTerm: nearResult,
|
|
256
|
+
farTerm: farResult,
|
|
257
|
+
targetDays: effectiveTargetDays,
|
|
258
|
+
isInterpolated: false,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
// CBOE interpolation in variance space
|
|
262
|
+
const weight1 = (N2 - Ntarget) / (N2 - N1);
|
|
263
|
+
const weight2 = (Ntarget - N1) / (N2 - N1);
|
|
264
|
+
const interpolatedVariance = (T1 * sigma1sq * weight1 + T2 * sigma2sq * weight2) * (N365 / Ntarget);
|
|
265
|
+
const clampedVariance = Math.max(0, interpolatedVariance);
|
|
266
|
+
const iv = Math.sqrt(clampedVariance);
|
|
267
|
+
return {
|
|
268
|
+
impliedVolatility: iv,
|
|
269
|
+
nearTerm: nearResult,
|
|
270
|
+
farTerm: farResult,
|
|
271
|
+
targetDays: effectiveTargetDays,
|
|
272
|
+
isInterpolated: true,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function emptyResult(spot) {
|
|
276
|
+
return {
|
|
277
|
+
impliedVolatility: 0,
|
|
278
|
+
annualizedVariance: 0,
|
|
279
|
+
forward: spot,
|
|
280
|
+
k0: spot,
|
|
281
|
+
timeToExpiry: 0,
|
|
282
|
+
expiration: 0,
|
|
283
|
+
numStrikes: 0,
|
|
284
|
+
putContribution: 0,
|
|
285
|
+
callContribution: 0,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of computing model-free implied variance for a single expiration
|
|
3
|
+
* using the CBOE variance swap methodology.
|
|
4
|
+
*/
|
|
5
|
+
export interface VarianceSwapResult {
|
|
6
|
+
/** Annualized implied volatility as decimal (e.g. 0.20 = 20%) */
|
|
7
|
+
impliedVolatility: number;
|
|
8
|
+
/** Annualized variance σ² */
|
|
9
|
+
annualizedVariance: number;
|
|
10
|
+
/** Forward price F derived from put-call parity at K₀ */
|
|
11
|
+
forward: number;
|
|
12
|
+
/** At-the-money strike K₀ (strike where |call - put| is minimized) */
|
|
13
|
+
k0: number;
|
|
14
|
+
/** Time to expiry in years */
|
|
15
|
+
timeToExpiry: number;
|
|
16
|
+
/** Expiration timestamp (ms) */
|
|
17
|
+
expiration: number;
|
|
18
|
+
/** Number of strikes that contributed to the variance sum */
|
|
19
|
+
numStrikes: number;
|
|
20
|
+
/** Total put-side contribution to variance */
|
|
21
|
+
putContribution: number;
|
|
22
|
+
/** Total call-side contribution to variance */
|
|
23
|
+
callContribution: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Result of computing implied volatility, either single-term or
|
|
27
|
+
* interpolated between two terms (CBOE VIX-style).
|
|
28
|
+
*/
|
|
29
|
+
export interface ImpliedVolatilityResult {
|
|
30
|
+
/** Final annualized implied volatility as decimal */
|
|
31
|
+
impliedVolatility: number;
|
|
32
|
+
/** Near-term (or only-term) variance swap result */
|
|
33
|
+
nearTerm: VarianceSwapResult;
|
|
34
|
+
/** Far-term variance swap result, if two terms provided */
|
|
35
|
+
farTerm: VarianceSwapResult | null;
|
|
36
|
+
/** Target days for interpolation (null if single-term) */
|
|
37
|
+
targetDays: number | null;
|
|
38
|
+
/** Whether the result is interpolated between two terms */
|
|
39
|
+
isInterpolated: boolean;
|
|
40
|
+
}
|
package/dist/iv/types.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PriceObservation, RealizedVolatilityResult } from './types';
|
|
2
|
+
export type { PriceObservation, RealizedVolatilityResult } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Compute annualized realized volatility from price observations.
|
|
5
|
+
*
|
|
6
|
+
* Uses the standard quadratic variation estimator:
|
|
7
|
+
*
|
|
8
|
+
* QV = Σᵢ (ln(Pᵢ / Pᵢ₋₁))²
|
|
9
|
+
*
|
|
10
|
+
* Annualized as:
|
|
11
|
+
*
|
|
12
|
+
* σ_realized = √(QV × (N_year / elapsed_time))
|
|
13
|
+
*
|
|
14
|
+
* This function is stateless and tick-based: pass all observed prices
|
|
15
|
+
* and it computes from the full set. No windowing, no sampling — the
|
|
16
|
+
* consumer decides what observations to include. As the session
|
|
17
|
+
* progresses and more ticks arrive, the estimate naturally converges.
|
|
18
|
+
*
|
|
19
|
+
* Observations are sorted by timestamp internally, so order does not
|
|
20
|
+
* matter. Duplicate timestamps are preserved (both contribute).
|
|
21
|
+
* Zero or negative prices are filtered out.
|
|
22
|
+
*
|
|
23
|
+
* @param observations - Array of { price, timestamp } observations
|
|
24
|
+
* @returns Realized volatility result with annualized RV
|
|
25
|
+
*/
|
|
26
|
+
export declare function computeRealizedVolatility(observations: PriceObservation[]): RealizedVolatilityResult;
|
package/dist/rv/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeRealizedVolatility = computeRealizedVolatility;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
/**
|
|
6
|
+
* Compute annualized realized volatility from price observations.
|
|
7
|
+
*
|
|
8
|
+
* Uses the standard quadratic variation estimator:
|
|
9
|
+
*
|
|
10
|
+
* QV = Σᵢ (ln(Pᵢ / Pᵢ₋₁))²
|
|
11
|
+
*
|
|
12
|
+
* Annualized as:
|
|
13
|
+
*
|
|
14
|
+
* σ_realized = √(QV × (N_year / elapsed_time))
|
|
15
|
+
*
|
|
16
|
+
* This function is stateless and tick-based: pass all observed prices
|
|
17
|
+
* and it computes from the full set. No windowing, no sampling — the
|
|
18
|
+
* consumer decides what observations to include. As the session
|
|
19
|
+
* progresses and more ticks arrive, the estimate naturally converges.
|
|
20
|
+
*
|
|
21
|
+
* Observations are sorted by timestamp internally, so order does not
|
|
22
|
+
* matter. Duplicate timestamps are preserved (both contribute).
|
|
23
|
+
* Zero or negative prices are filtered out.
|
|
24
|
+
*
|
|
25
|
+
* @param observations - Array of { price, timestamp } observations
|
|
26
|
+
* @returns Realized volatility result with annualized RV
|
|
27
|
+
*/
|
|
28
|
+
function computeRealizedVolatility(observations) {
|
|
29
|
+
// Filter invalid observations and sort by timestamp
|
|
30
|
+
const valid = observations
|
|
31
|
+
.filter(o => o.price > 0 && isFinite(o.price) && isFinite(o.timestamp))
|
|
32
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
33
|
+
if (valid.length < 2) {
|
|
34
|
+
return emptyResult(valid);
|
|
35
|
+
}
|
|
36
|
+
// Compute sum of squared log returns
|
|
37
|
+
let quadraticVariation = 0;
|
|
38
|
+
let numReturns = 0;
|
|
39
|
+
for (let i = 1; i < valid.length; i++) {
|
|
40
|
+
const logReturn = Math.log(valid[i].price / valid[i - 1].price);
|
|
41
|
+
quadraticVariation += logReturn * logReturn;
|
|
42
|
+
numReturns++;
|
|
43
|
+
}
|
|
44
|
+
// Elapsed time
|
|
45
|
+
const firstTs = valid[0].timestamp;
|
|
46
|
+
const lastTs = valid[valid.length - 1].timestamp;
|
|
47
|
+
const elapsedMs = lastTs - firstTs;
|
|
48
|
+
const elapsedMinutes = elapsedMs / 60000;
|
|
49
|
+
const elapsedYears = elapsedMs / types_1.MILLISECONDS_PER_YEAR;
|
|
50
|
+
// Guard against zero elapsed time
|
|
51
|
+
if (elapsedYears <= 0) {
|
|
52
|
+
return emptyResult(valid);
|
|
53
|
+
}
|
|
54
|
+
// Annualize: σ² = QV × (year / elapsed)
|
|
55
|
+
const annualizedVariance = quadraticVariation / elapsedYears;
|
|
56
|
+
const realizedVolatility = Math.sqrt(annualizedVariance);
|
|
57
|
+
return {
|
|
58
|
+
realizedVolatility,
|
|
59
|
+
annualizedVariance,
|
|
60
|
+
quadraticVariation,
|
|
61
|
+
numObservations: valid.length,
|
|
62
|
+
numReturns,
|
|
63
|
+
elapsedMinutes,
|
|
64
|
+
elapsedYears,
|
|
65
|
+
firstObservation: firstTs,
|
|
66
|
+
lastObservation: lastTs,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function emptyResult(valid) {
|
|
70
|
+
return {
|
|
71
|
+
realizedVolatility: 0,
|
|
72
|
+
annualizedVariance: 0,
|
|
73
|
+
quadraticVariation: 0,
|
|
74
|
+
numObservations: valid.length,
|
|
75
|
+
numReturns: 0,
|
|
76
|
+
elapsedMinutes: 0,
|
|
77
|
+
elapsedYears: 0,
|
|
78
|
+
firstObservation: valid.length > 0 ? valid[0].timestamp : 0,
|
|
79
|
+
lastObservation: valid.length > 0 ? valid[valid.length - 1].timestamp : 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single price observation for realized volatility computation.
|
|
3
|
+
*/
|
|
4
|
+
export interface PriceObservation {
|
|
5
|
+
/** Price of the underlying (mid, last, or whatever the consumer provides) */
|
|
6
|
+
price: number;
|
|
7
|
+
/** Timestamp in milliseconds */
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Result of computing realized volatility from price observations.
|
|
12
|
+
*/
|
|
13
|
+
export interface RealizedVolatilityResult {
|
|
14
|
+
/** Annualized realized volatility as decimal (e.g. 0.18 = 18%) */
|
|
15
|
+
realizedVolatility: number;
|
|
16
|
+
/** Annualized realized variance */
|
|
17
|
+
annualizedVariance: number;
|
|
18
|
+
/** Raw quadratic variation: Σ (ln(Pᵢ/Pᵢ₋₁))² */
|
|
19
|
+
quadraticVariation: number;
|
|
20
|
+
/** Number of price observations used */
|
|
21
|
+
numObservations: number;
|
|
22
|
+
/** Number of returns computed (observations - 1) */
|
|
23
|
+
numReturns: number;
|
|
24
|
+
/** Elapsed time in minutes from first to last observation */
|
|
25
|
+
elapsedMinutes: number;
|
|
26
|
+
/** Elapsed time in years (for annualization reference) */
|
|
27
|
+
elapsedYears: number;
|
|
28
|
+
/** Timestamp of first observation */
|
|
29
|
+
firstObservation: number;
|
|
30
|
+
/** Timestamp of last observation */
|
|
31
|
+
lastObservation: number;
|
|
32
|
+
}
|
package/dist/rv/types.js
ADDED