@fullstackcraftllc/floe 0.0.13 → 0.0.15

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.
Files changed (44) hide show
  1. package/README.md +8 -8
  2. package/dist/client/FloeClient.d.ts +5 -1
  3. package/dist/client/FloeClient.js +54 -0
  4. package/dist/client/brokers/IBKRClient.d.ts +324 -0
  5. package/dist/client/brokers/IBKRClient.js +797 -0
  6. package/dist/client/brokers/TradierClient.js +7 -6
  7. package/dist/hedgeflow/charm.d.ts +23 -0
  8. package/dist/hedgeflow/charm.js +113 -0
  9. package/dist/hedgeflow/curve.d.ts +27 -0
  10. package/dist/hedgeflow/curve.js +315 -0
  11. package/dist/hedgeflow/index.d.ts +33 -0
  12. package/dist/hedgeflow/index.js +52 -0
  13. package/dist/hedgeflow/regime.d.ts +7 -0
  14. package/dist/hedgeflow/regime.js +99 -0
  15. package/dist/hedgeflow/types.d.ts +185 -0
  16. package/dist/hedgeflow/types.js +2 -0
  17. package/dist/impliedpdf/adjusted.d.ts +173 -0
  18. package/dist/impliedpdf/adjusted.js +500 -0
  19. package/dist/impliedpdf/index.d.ts +1 -0
  20. package/dist/impliedpdf/index.js +10 -0
  21. package/dist/index.d.ts +8 -2
  22. package/dist/index.js +27 -1
  23. package/dist/iv/index.d.ts +52 -0
  24. package/dist/iv/index.js +287 -0
  25. package/dist/iv/types.d.ts +40 -0
  26. package/dist/iv/types.js +2 -0
  27. package/dist/pressure/grid.d.ts +14 -0
  28. package/dist/pressure/grid.js +220 -0
  29. package/dist/pressure/index.d.ts +5 -0
  30. package/dist/pressure/index.js +22 -0
  31. package/dist/pressure/ivpath.d.ts +31 -0
  32. package/dist/pressure/ivpath.js +304 -0
  33. package/dist/pressure/normalize.d.ts +6 -0
  34. package/dist/pressure/normalize.js +76 -0
  35. package/dist/pressure/regime.d.ts +7 -0
  36. package/dist/pressure/regime.js +99 -0
  37. package/dist/pressure/types.d.ts +182 -0
  38. package/dist/pressure/types.js +2 -0
  39. package/dist/rv/index.d.ts +26 -0
  40. package/dist/rv/index.js +81 -0
  41. package/dist/rv/types.d.ts +32 -0
  42. package/dist/rv/types.js +2 -0
  43. package/dist/utils/indexOptions.js +2 -1
  44. package/package.json +1 -1
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_IV_PATH_CONFIG = void 0;
4
+ exports.calculateIVPressures = calculateIVPressures;
5
+ exports.predictIVSurfaceEvolution = predictIVSurfaceEvolution;
6
+ exports.runCascadeSimulation = runCascadeSimulation;
7
+ exports.analyzeIVPath = analyzeIVPath;
8
+ exports.getIVPathOfLeastResistance = getIVPathOfLeastResistance;
9
+ const regime_1 = require("./regime");
10
+ exports.DEFAULT_IV_PATH_CONFIG = {
11
+ spotMoveScenario: 0,
12
+ timeStepHours: 1,
13
+ cascadeIterations: 5,
14
+ cascadeDampening: 0.5,
15
+ ivImpactPerMillion: 0.0005,
16
+ ivFloor: 0.05,
17
+ ivCeiling: 2.0,
18
+ };
19
+ /**
20
+ * Calculate IV pressure at each strike based on dealer positioning
21
+ */
22
+ function calculateIVPressures(ivSurface, exposures, regimeParams, spotMove, config = {}) {
23
+ const cfg = { ...exports.DEFAULT_IV_PATH_CONFIG, ...config };
24
+ const { strikes, smoothedIVs } = ivSurface;
25
+ const { impliedSpotVolCorr, impliedVolOfVol, expectedDailyVolMove } = regimeParams;
26
+ const pressures = [];
27
+ const exposureByStrike = new Map();
28
+ for (const exp of exposures.strikeExposures) {
29
+ exposureByStrike.set(exp.strikePrice, exp);
30
+ }
31
+ for (let i = 0; i < strikes.length; i++) {
32
+ const strike = strikes[i];
33
+ const currentIV = smoothedIVs[i] / 100;
34
+ const exposure = exposureByStrike.get(strike) ?? findNearestExposure(strike, exposures);
35
+ const vannaIVPressure = calculateVannaIVPressure(exposure?.vannaExposure ?? 0, spotMove, impliedSpotVolCorr, cfg.ivImpactPerMillion);
36
+ const vommaIVPressure = calculateVommaIVPressure(exposure?.vannaExposure ?? 0, expectedDailyVolMove, impliedVolOfVol, cfg.ivImpactPerMillion);
37
+ const vetaIVPressure = calculateVetaIVPressure(exposure?.charmExposure ?? 0, cfg.timeStepHours, cfg.ivImpactPerMillion);
38
+ const liquidityScore = estimateLiquidity(strike, exposures.spotPrice, currentIV);
39
+ const netIVPressure = vannaIVPressure + vommaIVPressure + vetaIVPressure;
40
+ const liquidityMultiplier = 1 / (liquidityScore + 0.1);
41
+ const expectedIVMove = netIVPressure * liquidityMultiplier;
42
+ pressures.push({
43
+ strike,
44
+ currentIV,
45
+ vannaIVPressure,
46
+ vommaIVPressure,
47
+ vetaIVPressure,
48
+ netIVPressure,
49
+ liquidityScore,
50
+ expectedIVMove,
51
+ });
52
+ }
53
+ return pressures;
54
+ }
55
+ function calculateVannaIVPressure(vannaExposure, spotMove, spotVolCorr, ivImpactPerMillion) {
56
+ const vannaHedgingFlow = -vannaExposure * spotMove;
57
+ const ivImpact = (vannaHedgingFlow / 1000000) * ivImpactPerMillion;
58
+ const corrMultiplier = 1 + Math.abs(spotVolCorr) * 0.5;
59
+ return ivImpact * corrMultiplier;
60
+ }
61
+ function calculateVommaIVPressure(vannaExposure, expectedVolMove, volOfVol, ivImpactPerMillion) {
62
+ const vommaProxy = vannaExposure * 0.1;
63
+ const vommaHedgingFlow = vommaProxy * Math.pow(expectedVolMove, 2) * volOfVol * 10;
64
+ return (vommaHedgingFlow / 1000000) * ivImpactPerMillion;
65
+ }
66
+ function calculateVetaIVPressure(charmExposure, timeStepHours, ivImpactPerMillion) {
67
+ const timeDecayFlow = charmExposure * (timeStepHours / 6.5);
68
+ return (timeDecayFlow / 1000000) * ivImpactPerMillion * 0.1;
69
+ }
70
+ function estimateLiquidity(strike, spot, iv) {
71
+ const moneyness = Math.abs(Math.log(strike / spot)) / iv;
72
+ return Math.max(0.1, 1 - moneyness * 0.4);
73
+ }
74
+ function findNearestExposure(strike, exposures) {
75
+ let nearest;
76
+ let minDist = Infinity;
77
+ for (const exp of exposures.strikeExposures) {
78
+ const dist = Math.abs(exp.strikePrice - strike);
79
+ if (dist < minDist) {
80
+ minDist = dist;
81
+ nearest = exp;
82
+ }
83
+ }
84
+ return nearest;
85
+ }
86
+ /**
87
+ * Predict how the IV surface will evolve given dealer positioning
88
+ */
89
+ function predictIVSurfaceEvolution(ivSurface, exposures, spot, spotMoveScenario = 0, config = {}) {
90
+ const cfg = { ...exports.DEFAULT_IV_PATH_CONFIG, ...config, spotMoveScenario };
91
+ const regimeParams = (0, regime_1.deriveRegimeParams)(ivSurface, spot);
92
+ const strikePressures = calculateIVPressures(ivSurface, exposures, regimeParams, spotMoveScenario, cfg);
93
+ const predictedIVs = ivSurface.smoothedIVs.map((iv, i) => {
94
+ const pressure = strikePressures[i];
95
+ const newIV = iv / 100 + pressure.expectedIVMove;
96
+ return Math.max(cfg.ivFloor, Math.min(cfg.ivCeiling, newIV)) * 100;
97
+ });
98
+ const predictedSurface = {
99
+ ...ivSurface,
100
+ smoothedIVs: predictedIVs,
101
+ rawIVs: predictedIVs,
102
+ };
103
+ const atmIdx = findATMIndex(ivSurface.strikes, spot);
104
+ const atmIVMove = (predictedIVs[atmIdx] - ivSurface.smoothedIVs[atmIdx]) / 100;
105
+ const skewChange = calculateSkewChange(ivSurface, predictedSurface, spot);
106
+ const curvatureChange = calculateCurvatureChange(ivSurface, predictedSurface, spot);
107
+ const netSurfacePressure = strikePressures.reduce((sum, p) => sum + p.netIVPressure * (p.liquidityScore + 0.1), 0);
108
+ const confidence = Math.min(1, calculatePositioningClarity(exposures) * 0.8);
109
+ return {
110
+ currentSurface: ivSurface,
111
+ predictedSurface,
112
+ strikePressures,
113
+ netSurfacePressure,
114
+ atmIVMove,
115
+ skewChange,
116
+ curvatureChange,
117
+ confidence,
118
+ };
119
+ }
120
+ function findATMIndex(strikes, spot) {
121
+ let minDist = Infinity;
122
+ let atmIdx = 0;
123
+ for (let i = 0; i < strikes.length; i++) {
124
+ const dist = Math.abs(strikes[i] - spot);
125
+ if (dist < minDist) {
126
+ minDist = dist;
127
+ atmIdx = i;
128
+ }
129
+ }
130
+ return atmIdx;
131
+ }
132
+ function calculateSkewChange(current, predicted, spot) {
133
+ const { strikes } = current;
134
+ const atmIdx = findATMIndex(strikes, spot);
135
+ const atmIV = current.smoothedIVs[atmIdx] / 100;
136
+ const stdDev = spot * atmIV * Math.sqrt(1 / 252);
137
+ const putIdx = findATMIndex(strikes, spot - stdDev);
138
+ const callIdx = findATMIndex(strikes, spot + stdDev);
139
+ const currentSkew = (current.smoothedIVs[putIdx] - current.smoothedIVs[callIdx]) / 100;
140
+ const predictedSkew = (predicted.smoothedIVs[putIdx] - predicted.smoothedIVs[callIdx]) / 100;
141
+ return predictedSkew - currentSkew;
142
+ }
143
+ function calculateCurvatureChange(current, predicted, spot) {
144
+ const { strikes } = current;
145
+ const atmIdx = findATMIndex(strikes, spot);
146
+ if (atmIdx === 0 || atmIdx === strikes.length - 1)
147
+ return 0;
148
+ const deltaK = (strikes[atmIdx + 1] - strikes[atmIdx - 1]) / 2;
149
+ if (deltaK === 0)
150
+ return 0;
151
+ const currentCurv = (current.smoothedIVs[atmIdx - 1] + current.smoothedIVs[atmIdx + 1] - 2 * current.smoothedIVs[atmIdx]) / (deltaK * deltaK);
152
+ const predictedCurv = (predicted.smoothedIVs[atmIdx - 1] + predicted.smoothedIVs[atmIdx + 1] - 2 * predicted.smoothedIVs[atmIdx]) / (deltaK * deltaK);
153
+ return predictedCurv - currentCurv;
154
+ }
155
+ function calculatePositioningClarity(exposures) {
156
+ const totalAbs = Math.abs(exposures.totalGammaExposure) +
157
+ Math.abs(exposures.totalVannaExposure) +
158
+ Math.abs(exposures.totalCharmExposure);
159
+ if (totalAbs === 0)
160
+ return 0;
161
+ const netExposure = exposures.totalNetExposure;
162
+ const directionalClarity = Math.abs(netExposure) / totalAbs;
163
+ const maxStrikeExposure = Math.max(...exposures.strikeExposures.map(e => Math.abs(e.netExposure)));
164
+ const concentration = maxStrikeExposure / (totalAbs + 1);
165
+ return (directionalClarity + concentration) / 2;
166
+ }
167
+ /**
168
+ * Run a cascade simulation showing feedback loops
169
+ */
170
+ function runCascadeSimulation(ivSurface, exposures, spot, spotShock = 0, config = {}) {
171
+ const cfg = { ...exports.DEFAULT_IV_PATH_CONFIG, ...config };
172
+ const steps = [];
173
+ let currentSpot = spot * (1 + spotShock);
174
+ let currentSurface = ivSurface;
175
+ let currentExposures = exposures;
176
+ let cumulativeIVMove = 0;
177
+ let cumulativeSpotMove = spotShock;
178
+ const initialRegime = (0, regime_1.deriveRegimeParams)(ivSurface, spot);
179
+ const initial = {
180
+ step: 0,
181
+ spot,
182
+ ivSurface,
183
+ exposures,
184
+ regime: initialRegime.regime,
185
+ ivPressure: 0,
186
+ spotPressure: 0,
187
+ cumulativeIVMove: 0,
188
+ cumulativeSpotMove: 0,
189
+ };
190
+ for (let i = 0; i < cfg.cascadeIterations; i++) {
191
+ const regime = (0, regime_1.deriveRegimeParams)(currentSurface, currentSpot);
192
+ const evolution = predictIVSurfaceEvolution(currentSurface, currentExposures, currentSpot, i === 0 ? spotShock : 0, cfg);
193
+ const dampenedIVMove = evolution.atmIVMove * Math.pow(cfg.cascadeDampening, i);
194
+ cumulativeIVMove += dampenedIVMove;
195
+ currentSurface = evolution.predictedSurface;
196
+ const spotPressure = estimateSpotPressureFromIVChange(dampenedIVMove, currentExposures, currentSpot);
197
+ const dampenedSpotMove = spotPressure * Math.pow(cfg.cascadeDampening, i);
198
+ cumulativeSpotMove += dampenedSpotMove;
199
+ currentSpot = currentSpot * (1 + dampenedSpotMove);
200
+ steps.push({
201
+ step: i + 1,
202
+ spot: currentSpot,
203
+ ivSurface: currentSurface,
204
+ exposures: currentExposures,
205
+ regime: regime.regime,
206
+ ivPressure: dampenedIVMove,
207
+ spotPressure: dampenedSpotMove,
208
+ cumulativeIVMove,
209
+ cumulativeSpotMove,
210
+ });
211
+ }
212
+ const outcome = determineOutcome(steps);
213
+ const insight = generateInsight(initial, steps, outcome, spotShock);
214
+ return {
215
+ initial,
216
+ steps,
217
+ terminal: steps[steps.length - 1],
218
+ outcome,
219
+ insight,
220
+ };
221
+ }
222
+ function estimateSpotPressureFromIVChange(ivChange, exposures, spot) {
223
+ const deltaChange = exposures.totalVannaExposure * ivChange;
224
+ return (deltaChange / 1000000000) * 0.001;
225
+ }
226
+ function determineOutcome(steps) {
227
+ if (steps.length < 3)
228
+ return 'converged';
229
+ const lastSteps = steps.slice(-3);
230
+ const ivMoves = lastSteps.map(s => Math.abs(s.ivPressure));
231
+ const isDiverging = ivMoves[2] > ivMoves[1] && ivMoves[1] > ivMoves[0];
232
+ if (isDiverging)
233
+ return 'diverged';
234
+ const signs = lastSteps.map(s => Math.sign(s.ivPressure));
235
+ const isOscillating = signs[0] !== signs[1] && signs[1] !== signs[2];
236
+ if (isOscillating)
237
+ return 'oscillating';
238
+ return 'converged';
239
+ }
240
+ function generateInsight(initial, steps, outcome, spotShock) {
241
+ const terminal = steps[steps.length - 1];
242
+ const totalIVMove = terminal.cumulativeIVMove * 100;
243
+ const totalSpotMove = terminal.cumulativeSpotMove * 100;
244
+ const direction = spotShock < 0 ? 'down' : spotShock > 0 ? 'up' : 'flat';
245
+ if (outcome === 'diverged') {
246
+ return `Warning: Cascade diverged after ${spotShock * 100}% spot ${direction} shock. ` +
247
+ `IV moved ${totalIVMove.toFixed(2)} vol points cumulatively.`;
248
+ }
249
+ if (outcome === 'oscillating') {
250
+ return `Cascade oscillated after ${spotShock * 100}% spot ${direction} shock. ` +
251
+ `Net IV move: ${totalIVMove.toFixed(2)} vol points.`;
252
+ }
253
+ if (Math.abs(totalIVMove) > 2) {
254
+ return `Significant IV reaction: ${spotShock * 100}% spot ${direction} shock triggered ` +
255
+ `${totalIVMove.toFixed(2)} vol point IV move through feedback loop.`;
256
+ }
257
+ return `Minimal cascade effect: ${spotShock * 100}% spot ${direction} shock absorbed ` +
258
+ `with only ${totalIVMove.toFixed(2)} vol point IV move.`;
259
+ }
260
+ /**
261
+ * Analyze IV path for multiple spot scenarios
262
+ */
263
+ function analyzeIVPath(ivSurface, exposures, spot, options = {}) {
264
+ const scenarios = options.spotScenarios ?? [-0.03, -0.02, -0.01, 0, 0.01, 0.02, 0.03];
265
+ const results = new Map();
266
+ for (const scenario of scenarios) {
267
+ const evolution = predictIVSurfaceEvolution(ivSurface, exposures, spot, scenario, options.config);
268
+ results.set(scenario, evolution);
269
+ }
270
+ return results;
271
+ }
272
+ /**
273
+ * Get the IV path of least resistance
274
+ */
275
+ function getIVPathOfLeastResistance(ivSurface, exposures, spot) {
276
+ const regimeParams = (0, regime_1.deriveRegimeParams)(ivSurface, spot);
277
+ const upPressures = calculateIVPressures(ivSurface, exposures, regimeParams, 0.01);
278
+ const downPressures = calculateIVPressures(ivSurface, exposures, regimeParams, -0.01);
279
+ const neutralPressures = calculateIVPressures(ivSurface, exposures, regimeParams, 0);
280
+ const upNetPressure = upPressures.reduce((sum, p) => sum + p.netIVPressure, 0);
281
+ const downNetPressure = downPressures.reduce((sum, p) => sum + p.netIVPressure, 0);
282
+ const neutralNetPressure = neutralPressures.reduce((sum, p) => sum + p.netIVPressure, 0);
283
+ let direction = 'stable';
284
+ let magnitude = 0;
285
+ let rationale = '';
286
+ const netBias = (downNetPressure + neutralNetPressure) - upNetPressure;
287
+ if (netBias > 0.001) {
288
+ direction = 'up';
289
+ magnitude = Math.abs(netBias);
290
+ rationale = `Positioning suggests IV upside bias. Vanna hedging would add ${(magnitude * 100).toFixed(2)} vol points on down moves.`;
291
+ }
292
+ else if (netBias < -0.001) {
293
+ direction = 'down';
294
+ magnitude = Math.abs(netBias);
295
+ rationale = `Positioning suggests IV downside bias. Vol selling pressure would reduce IV by ${(magnitude * 100).toFixed(2)} vol points.`;
296
+ }
297
+ else {
298
+ direction = 'stable';
299
+ magnitude = 0;
300
+ rationale = `Balanced positioning suggests IV stability.`;
301
+ }
302
+ const confidence = calculatePositioningClarity(exposures);
303
+ return { direction, magnitude, confidence, rationale };
304
+ }
@@ -0,0 +1,6 @@
1
+ import { ExposurePerExpiry } from '../types';
2
+ import { RegimeParams, NormalizedExposureSummary } from './types';
3
+ /**
4
+ * Normalize raw exposures to comparable "expected daily flow" units
5
+ */
6
+ export declare function normalizeExposures(exposures: ExposurePerExpiry, regimeParams: RegimeParams): NormalizedExposureSummary;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeExposures = normalizeExposures;
4
+ /**
5
+ * Normalize raw exposures to comparable "expected daily flow" units
6
+ */
7
+ function normalizeExposures(exposures, regimeParams) {
8
+ const { expectedDailySpotMove, expectedDailyVolMove, impliedSpotVolCorr, regime } = regimeParams;
9
+ const corrAmplifier = 1 + Math.abs(impliedSpotVolCorr);
10
+ const volOfVolMultiplier = {
11
+ calm: 0.5,
12
+ normal: 1.0,
13
+ stressed: 2.5,
14
+ crisis: 5.0,
15
+ };
16
+ const normalizedStrikes = [];
17
+ let totalEffectiveGamma = 0;
18
+ let totalEffectiveVanna = 0;
19
+ let totalEffectiveCharm = 0;
20
+ for (const strike of exposures.strikeExposures) {
21
+ const effectiveGamma = Math.abs(strike.gammaExposure) * expectedDailySpotMove;
22
+ const effectiveVanna = Math.abs(strike.vannaExposure) *
23
+ expectedDailySpotMove *
24
+ expectedDailyVolMove *
25
+ volOfVolMultiplier[regime] *
26
+ corrAmplifier;
27
+ const effectiveCharm = Math.abs(strike.charmExposure);
28
+ const totalEffective = effectiveGamma + effectiveVanna + effectiveCharm;
29
+ let dominant = 'gamma';
30
+ if (effectiveVanna > effectiveGamma && effectiveVanna > effectiveCharm) {
31
+ dominant = 'vanna';
32
+ }
33
+ else if (effectiveCharm > effectiveGamma && effectiveCharm > effectiveVanna) {
34
+ dominant = 'charm';
35
+ }
36
+ normalizedStrikes.push({
37
+ strike: strike.strikePrice,
38
+ rawGamma: strike.gammaExposure,
39
+ rawVanna: strike.vannaExposure,
40
+ rawCharm: strike.charmExposure,
41
+ effectiveGamma,
42
+ effectiveVanna,
43
+ effectiveCharm,
44
+ totalEffective,
45
+ dominant,
46
+ });
47
+ totalEffectiveGamma += effectiveGamma;
48
+ totalEffectiveVanna += effectiveVanna;
49
+ totalEffectiveCharm += effectiveCharm;
50
+ }
51
+ const totalEffective = totalEffectiveGamma + totalEffectiveVanna + totalEffectiveCharm;
52
+ const weights = {
53
+ gamma: totalEffective > 0 ? totalEffectiveGamma / totalEffective : 0.33,
54
+ vanna: totalEffective > 0 ? totalEffectiveVanna / totalEffective : 0.33,
55
+ charm: totalEffective > 0 ? totalEffectiveCharm / totalEffective : 0.34,
56
+ };
57
+ let dominantGreek = 'gamma';
58
+ if (weights.vanna > weights.gamma && weights.vanna > weights.charm) {
59
+ dominantGreek = 'vanna';
60
+ }
61
+ else if (weights.charm > weights.gamma && weights.charm > weights.vanna) {
62
+ dominantGreek = 'charm';
63
+ }
64
+ return {
65
+ spot: exposures.spotPrice,
66
+ expiration: exposures.expiration,
67
+ regimeParams,
68
+ strikeExposures: normalizedStrikes,
69
+ totalEffectiveGamma,
70
+ totalEffectiveVanna,
71
+ totalEffectiveCharm,
72
+ totalEffective,
73
+ dominantGreek,
74
+ weights,
75
+ };
76
+ }
@@ -0,0 +1,7 @@
1
+ import { IVSurface } from '../types';
2
+ import { RegimeParams } from './types';
3
+ /**
4
+ * Derive regime parameters from the IV surface
5
+ */
6
+ export declare function deriveRegimeParams(ivSurface: IVSurface, spot: number): RegimeParams;
7
+ export declare function interpolateIVAtStrike(strikes: number[], ivs: number[], targetStrike: number): number;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deriveRegimeParams = deriveRegimeParams;
4
+ exports.interpolateIVAtStrike = interpolateIVAtStrike;
5
+ /**
6
+ * Derive regime parameters from the IV surface
7
+ */
8
+ function deriveRegimeParams(ivSurface, spot) {
9
+ const { strikes, smoothedIVs } = ivSurface;
10
+ const atmIV = interpolateIVAtStrike(strikes, smoothedIVs, spot) / 100;
11
+ const skew = calculateSkewAtSpot(strikes, smoothedIVs, spot);
12
+ const impliedSpotVolCorr = skewToCorrelation(skew);
13
+ const curvature = calculateCurvatureAtSpot(strikes, smoothedIVs, spot);
14
+ const impliedVolOfVol = curvatureToVolOfVol(curvature, atmIV);
15
+ const regime = ivToRegime(atmIV);
16
+ const expectedDailySpotMove = atmIV / Math.sqrt(252);
17
+ const expectedDailyVolMove = impliedVolOfVol / Math.sqrt(252);
18
+ return {
19
+ atmIV,
20
+ impliedSpotVolCorr,
21
+ impliedVolOfVol,
22
+ regime,
23
+ expectedDailySpotMove,
24
+ expectedDailyVolMove,
25
+ };
26
+ }
27
+ function skewToCorrelation(skew) {
28
+ const SKEW_TO_CORR_SCALE = 0.15;
29
+ return Math.max(-0.95, Math.min(0.5, skew * SKEW_TO_CORR_SCALE));
30
+ }
31
+ function curvatureToVolOfVol(curvature, atmIV) {
32
+ const VOL_OF_VOL_SCALE = 2.0;
33
+ return Math.sqrt(Math.abs(curvature)) * VOL_OF_VOL_SCALE * atmIV;
34
+ }
35
+ function ivToRegime(atmIV) {
36
+ if (atmIV < 0.15)
37
+ return 'calm';
38
+ if (atmIV < 0.20)
39
+ return 'normal';
40
+ if (atmIV < 0.35)
41
+ return 'stressed';
42
+ return 'crisis';
43
+ }
44
+ function interpolateIVAtStrike(strikes, ivs, targetStrike) {
45
+ if (strikes.length === 0 || ivs.length === 0)
46
+ return 20;
47
+ if (strikes.length === 1)
48
+ return ivs[0];
49
+ let lower = 0;
50
+ let upper = strikes.length - 1;
51
+ for (let i = 0; i < strikes.length - 1; i++) {
52
+ if (strikes[i] <= targetStrike && strikes[i + 1] >= targetStrike) {
53
+ lower = i;
54
+ upper = i + 1;
55
+ break;
56
+ }
57
+ }
58
+ if (targetStrike <= strikes[0])
59
+ return ivs[0];
60
+ if (targetStrike >= strikes[strikes.length - 1])
61
+ return ivs[ivs.length - 1];
62
+ const t = (targetStrike - strikes[lower]) / (strikes[upper] - strikes[lower]);
63
+ return ivs[lower] + t * (ivs[upper] - ivs[lower]);
64
+ }
65
+ function calculateSkewAtSpot(strikes, ivs, spot) {
66
+ if (strikes.length < 2)
67
+ return 0;
68
+ let lowerIdx = 0;
69
+ let upperIdx = strikes.length - 1;
70
+ for (let i = 0; i < strikes.length - 1; i++) {
71
+ if (strikes[i] <= spot && strikes[i + 1] >= spot) {
72
+ lowerIdx = i;
73
+ upperIdx = i + 1;
74
+ break;
75
+ }
76
+ }
77
+ const dIV = ivs[upperIdx] - ivs[lowerIdx];
78
+ const dK = strikes[upperIdx] - strikes[lowerIdx];
79
+ return dK > 0 ? (dIV / dK) * spot : 0;
80
+ }
81
+ function calculateCurvatureAtSpot(strikes, ivs, spot) {
82
+ if (strikes.length < 3)
83
+ return 0;
84
+ let centerIdx = 0;
85
+ for (let i = 0; i < strikes.length; i++) {
86
+ if (Math.abs(strikes[i] - spot) < Math.abs(strikes[centerIdx] - spot)) {
87
+ centerIdx = i;
88
+ }
89
+ }
90
+ if (centerIdx === 0 || centerIdx === strikes.length - 1)
91
+ return 0;
92
+ const h = (strikes[centerIdx + 1] - strikes[centerIdx - 1]) / 2;
93
+ if (h <= 0)
94
+ return 0;
95
+ const ivMinus = ivs[centerIdx - 1];
96
+ const iv = ivs[centerIdx];
97
+ const ivPlus = ivs[centerIdx + 1];
98
+ return ((ivPlus - 2 * iv + ivMinus) / (h * h)) * spot * spot;
99
+ }
@@ -0,0 +1,182 @@
1
+ import { IVSurface, ExposurePerExpiry } from '../types';
2
+ /**
3
+ * Regime classification based on IV level and surface characteristics
4
+ */
5
+ export type MarketRegime = 'calm' | 'normal' | 'stressed' | 'crisis';
6
+ /**
7
+ * Parameters derived from the IV surface itself (no external data needed)
8
+ */
9
+ export interface RegimeParams {
10
+ /** ATM implied volatility (as decimal, e.g., 0.18 for 18%) */
11
+ atmIV: number;
12
+ /** Implied spot-vol correlation derived from skew */
13
+ impliedSpotVolCorr: number;
14
+ /** Implied vol-of-vol derived from smile curvature */
15
+ impliedVolOfVol: number;
16
+ /** Regime classification */
17
+ regime: MarketRegime;
18
+ /** Expected daily spot move (as decimal) */
19
+ expectedDailySpotMove: number;
20
+ /** Expected daily vol move (as decimal) */
21
+ expectedDailyVolMove: number;
22
+ }
23
+ /**
24
+ * Normalized exposure at a single strike
25
+ */
26
+ export interface NormalizedExposure {
27
+ strike: number;
28
+ rawGamma: number;
29
+ rawVanna: number;
30
+ rawCharm: number;
31
+ effectiveGamma: number;
32
+ effectiveVanna: number;
33
+ effectiveCharm: number;
34
+ totalEffective: number;
35
+ dominant: 'gamma' | 'vanna' | 'charm';
36
+ }
37
+ /**
38
+ * Aggregate normalized exposure metrics
39
+ */
40
+ export interface NormalizedExposureSummary {
41
+ spot: number;
42
+ expiration: number;
43
+ regimeParams: RegimeParams;
44
+ strikeExposures: NormalizedExposure[];
45
+ totalEffectiveGamma: number;
46
+ totalEffectiveVanna: number;
47
+ totalEffectiveCharm: number;
48
+ totalEffective: number;
49
+ dominantGreek: 'gamma' | 'vanna' | 'charm';
50
+ weights: {
51
+ gamma: number;
52
+ vanna: number;
53
+ charm: number;
54
+ };
55
+ }
56
+ /**
57
+ * Hedging pressure at a (spot, time) coordinate
58
+ */
59
+ export interface HedgingPressure {
60
+ spot: number;
61
+ timeToExpiry: number;
62
+ hoursRemaining: number;
63
+ gammaFlow: number;
64
+ vannaFlow: number;
65
+ charmFlow: number;
66
+ netPressure: number;
67
+ effectivePressure: number;
68
+ direction: 'supportive' | 'resistive' | 'neutral';
69
+ }
70
+ /**
71
+ * Grid cell for pressure visualization
72
+ */
73
+ export interface PressureGridCell {
74
+ spot: number;
75
+ hoursRemaining: number;
76
+ pressure: HedgingPressure;
77
+ spotGradient: number;
78
+ timeGradient: number;
79
+ }
80
+ /**
81
+ * Key levels derived from pressure field
82
+ */
83
+ export interface KeyLevels {
84
+ support: number[];
85
+ resistance: number[];
86
+ pinning: number[];
87
+ gammaFlip: number | null;
88
+ }
89
+ /**
90
+ * Path of least resistance analysis
91
+ */
92
+ export interface PathOfLeastResistance {
93
+ direction: 'up' | 'down' | 'pin';
94
+ targetSpot: number;
95
+ confidence: number;
96
+ rationale: string;
97
+ }
98
+ /**
99
+ * Complete pressure grid
100
+ */
101
+ export interface PressureGrid {
102
+ symbol: string;
103
+ expiration: number;
104
+ currentSpot: number;
105
+ timestamp: number;
106
+ regime: MarketRegime;
107
+ spotRange: {
108
+ min: number;
109
+ max: number;
110
+ step: number;
111
+ };
112
+ timeRange: {
113
+ min: number;
114
+ max: number;
115
+ step: number;
116
+ };
117
+ cells: PressureGridCell[][];
118
+ keyLevels: KeyLevels;
119
+ pathOfLeastResistance: PathOfLeastResistance;
120
+ }
121
+ /**
122
+ * IV pressure at a specific strike
123
+ */
124
+ export interface StrikeIVPressure {
125
+ strike: number;
126
+ currentIV: number;
127
+ vannaIVPressure: number;
128
+ vommaIVPressure: number;
129
+ vetaIVPressure: number;
130
+ netIVPressure: number;
131
+ liquidityScore: number;
132
+ expectedIVMove: number;
133
+ }
134
+ /**
135
+ * IV surface evolution prediction
136
+ */
137
+ export interface IVSurfaceEvolution {
138
+ currentSurface: IVSurface;
139
+ predictedSurface: IVSurface;
140
+ strikePressures: StrikeIVPressure[];
141
+ netSurfacePressure: number;
142
+ atmIVMove: number;
143
+ skewChange: number;
144
+ curvatureChange: number;
145
+ confidence: number;
146
+ }
147
+ /**
148
+ * Cascade simulation step
149
+ */
150
+ export interface CascadeStep {
151
+ step: number;
152
+ spot: number;
153
+ ivSurface: IVSurface;
154
+ exposures: ExposurePerExpiry;
155
+ regime: MarketRegime;
156
+ ivPressure: number;
157
+ spotPressure: number;
158
+ cumulativeIVMove: number;
159
+ cumulativeSpotMove: number;
160
+ }
161
+ /**
162
+ * Full cascade simulation result
163
+ */
164
+ export interface CascadeSimulation {
165
+ initial: CascadeStep;
166
+ steps: CascadeStep[];
167
+ terminal: CascadeStep;
168
+ outcome: 'converged' | 'diverged' | 'oscillating';
169
+ insight: string;
170
+ }
171
+ /**
172
+ * Configuration for IV path prediction
173
+ */
174
+ export interface IVPathConfig {
175
+ spotMoveScenario: number;
176
+ timeStepHours: number;
177
+ cascadeIterations: number;
178
+ cascadeDampening: number;
179
+ ivImpactPerMillion: number;
180
+ ivFloor: number;
181
+ ivCeiling: number;
182
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });