@fullstackcraftllc/floe 0.0.13 → 0.0.14

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.
@@ -0,0 +1,220 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculatePressureAt = calculatePressureAt;
4
+ exports.buildPressureGrid = buildPressureGrid;
5
+ const types_1 = require("../types");
6
+ const regime_1 = require("./regime");
7
+ /**
8
+ * Calculate hedging pressure at a specific (spot, time) point
9
+ */
10
+ function calculatePressureAt(spot, timeToExpiry, exposures, regimeParams) {
11
+ const { expectedDailySpotMove, expectedDailyVolMove, impliedSpotVolCorr, regime } = regimeParams;
12
+ let gammaFlow = 0;
13
+ let vannaFlow = 0;
14
+ let charmFlow = 0;
15
+ for (const strike of exposures.strikeExposures) {
16
+ const gammaContrib = strike.gammaExposure * Math.pow(expectedDailySpotMove, 2);
17
+ gammaFlow += gammaContrib;
18
+ const corrAmplifier = 1 + Math.abs(impliedSpotVolCorr);
19
+ const vannaContrib = strike.vannaExposure * expectedDailySpotMove * expectedDailyVolMove * corrAmplifier;
20
+ vannaFlow += vannaContrib;
21
+ const daysToExpiry = timeToExpiry * 252;
22
+ const charmContrib = strike.charmExposure * Math.min(daysToExpiry, 1);
23
+ charmFlow += charmContrib;
24
+ }
25
+ const netPressure = gammaFlow + vannaFlow + charmFlow;
26
+ const regimeWeights = {
27
+ calm: { gamma: 0.3, vanna: 0.1, charm: 0.6 },
28
+ normal: { gamma: 0.4, vanna: 0.2, charm: 0.4 },
29
+ stressed: { gamma: 0.35, vanna: 0.4, charm: 0.25 },
30
+ crisis: { gamma: 0.25, vanna: 0.6, charm: 0.15 },
31
+ };
32
+ const w = regimeWeights[regime];
33
+ const effectivePressure = gammaFlow * w.gamma + vannaFlow * w.vanna + charmFlow * w.charm;
34
+ let direction = 'neutral';
35
+ const threshold = Math.abs(effectivePressure) * 0.1;
36
+ if (effectivePressure > threshold) {
37
+ direction = 'supportive';
38
+ }
39
+ else if (effectivePressure < -threshold) {
40
+ direction = 'resistive';
41
+ }
42
+ const hoursRemaining = timeToExpiry * 252 * 6.5;
43
+ return {
44
+ spot,
45
+ timeToExpiry,
46
+ hoursRemaining,
47
+ gammaFlow,
48
+ vannaFlow,
49
+ charmFlow,
50
+ netPressure,
51
+ effectivePressure,
52
+ direction,
53
+ };
54
+ }
55
+ /**
56
+ * Build a complete pressure grid for visualization
57
+ */
58
+ function buildPressureGrid(symbol, chain, exposures, ivSurface, options = {}) {
59
+ const { spotRangePercent = 2, spotStepPercent = 0.1, timeStepMinutes = 15, } = options;
60
+ const spot = chain.spot;
61
+ const expiration = exposures.expiration;
62
+ const timeToExpiry = (expiration - Date.now()) / types_1.MILLISECONDS_PER_YEAR;
63
+ const regimeParams = (0, regime_1.deriveRegimeParams)(ivSurface, spot);
64
+ const spotMin = spot * (1 - spotRangePercent / 100);
65
+ const spotMax = spot * (1 + spotRangePercent / 100);
66
+ const spotStep = spot * (spotStepPercent / 100);
67
+ const hoursRemaining = timeToExpiry * 252 * 6.5;
68
+ const timeMin = 0;
69
+ const timeMax = Math.max(hoursRemaining, 0.25);
70
+ const timeStep = timeStepMinutes / 60;
71
+ const cells = [];
72
+ const spotLevels = [];
73
+ const timeLevels = [];
74
+ for (let s = spotMin; s <= spotMax; s += spotStep) {
75
+ spotLevels.push(s);
76
+ }
77
+ for (let t = timeMax; t >= timeMin; t -= timeStep) {
78
+ timeLevels.push(t);
79
+ }
80
+ for (let i = 0; i < spotLevels.length; i++) {
81
+ cells[i] = [];
82
+ const s = spotLevels[i];
83
+ for (let j = 0; j < timeLevels.length; j++) {
84
+ const hoursLeft = timeLevels[j];
85
+ const t = hoursLeft / (252 * 6.5);
86
+ const pressure = calculatePressureAt(s, t, exposures, regimeParams);
87
+ cells[i][j] = {
88
+ spot: s,
89
+ hoursRemaining: hoursLeft,
90
+ pressure,
91
+ spotGradient: 0,
92
+ timeGradient: 0,
93
+ };
94
+ }
95
+ }
96
+ computeGradients(cells, spotStep, timeStep);
97
+ const keyLevels = findKeyLevels(cells, spotLevels, exposures);
98
+ const pathOfLeastResistance = computePathOfLeastResistance(cells, spotLevels, spot);
99
+ return {
100
+ symbol,
101
+ expiration,
102
+ currentSpot: spot,
103
+ timestamp: Date.now(),
104
+ regime: regimeParams.regime,
105
+ spotRange: { min: spotMin, max: spotMax, step: spotStep },
106
+ timeRange: { min: timeMin, max: timeMax, step: timeStep },
107
+ cells,
108
+ keyLevels,
109
+ pathOfLeastResistance,
110
+ };
111
+ }
112
+ function computeGradients(cells, spotStep, timeStep) {
113
+ const numSpot = cells.length;
114
+ const numTime = cells[0]?.length ?? 0;
115
+ for (let i = 0; i < numSpot; i++) {
116
+ for (let j = 0; j < numTime; j++) {
117
+ if (i > 0 && i < numSpot - 1) {
118
+ cells[i][j].spotGradient =
119
+ (cells[i + 1][j].pressure.effectivePressure - cells[i - 1][j].pressure.effectivePressure) /
120
+ (2 * spotStep);
121
+ }
122
+ if (j > 0 && j < numTime - 1) {
123
+ cells[i][j].timeGradient =
124
+ (cells[i][j + 1].pressure.effectivePressure - cells[i][j - 1].pressure.effectivePressure) /
125
+ (2 * timeStep);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ function findKeyLevels(cells, spotLevels, exposures) {
131
+ const support = [];
132
+ const resistance = [];
133
+ const pinning = [];
134
+ let gammaFlip = null;
135
+ const currentSlice = cells.map(row => row[0]);
136
+ for (let i = 1; i < currentSlice.length; i++) {
137
+ const prev = currentSlice[i - 1].pressure.effectivePressure;
138
+ const curr = currentSlice[i].pressure.effectivePressure;
139
+ if (prev < 0 && curr >= 0) {
140
+ support.push((spotLevels[i - 1] + spotLevels[i]) / 2);
141
+ }
142
+ else if (prev >= 0 && curr < 0) {
143
+ resistance.push((spotLevels[i - 1] + spotLevels[i]) / 2);
144
+ }
145
+ }
146
+ for (const strike of exposures.strikeExposures) {
147
+ if (strike.gammaExposure > 0 && strike.gammaExposure > exposures.totalGammaExposure * 0.1) {
148
+ pinning.push(strike.strikePrice);
149
+ }
150
+ }
151
+ return { support, resistance, pinning, gammaFlip };
152
+ }
153
+ function computePathOfLeastResistance(cells, spotLevels, currentSpot) {
154
+ let currentSpotIdx = 0;
155
+ let minDist = Infinity;
156
+ for (let i = 0; i < spotLevels.length; i++) {
157
+ const dist = Math.abs(spotLevels[i] - currentSpot);
158
+ if (dist < minDist) {
159
+ minDist = dist;
160
+ currentSpotIdx = i;
161
+ }
162
+ }
163
+ const pressureAbove = [];
164
+ const pressureBelow = [];
165
+ for (let i = currentSpotIdx + 1; i < spotLevels.length; i++) {
166
+ pressureAbove.push(cells[i][0].pressure.effectivePressure);
167
+ }
168
+ for (let i = currentSpotIdx - 1; i >= 0; i--) {
169
+ pressureBelow.push(cells[i][0].pressure.effectivePressure);
170
+ }
171
+ const avgPressureAbove = pressureAbove.length > 0
172
+ ? pressureAbove.reduce((a, b) => a + b, 0) / pressureAbove.length
173
+ : 0;
174
+ const avgPressureBelow = pressureBelow.length > 0
175
+ ? pressureBelow.reduce((a, b) => a + b, 0) / pressureBelow.length
176
+ : 0;
177
+ const currentPressure = cells[currentSpotIdx][0].pressure.effectivePressure;
178
+ let direction = 'pin';
179
+ let targetSpot = currentSpot;
180
+ let confidence = 0;
181
+ let rationale = '';
182
+ if (currentPressure > 0 && Math.abs(currentPressure) > Math.abs(avgPressureAbove) &&
183
+ Math.abs(currentPressure) > Math.abs(avgPressureBelow)) {
184
+ direction = 'pin';
185
+ targetSpot = currentSpot;
186
+ confidence = Math.min(1, Math.abs(currentPressure) / (Math.abs(avgPressureAbove) + Math.abs(avgPressureBelow) + 1));
187
+ rationale = `Strong positive gamma at current level suggests pinning`;
188
+ }
189
+ else if (avgPressureAbove > 0 && avgPressureBelow < 0) {
190
+ direction = 'up';
191
+ for (let i = currentSpotIdx + 1; i < spotLevels.length; i++) {
192
+ if (cells[i][0].pressure.effectivePressure < 0) {
193
+ targetSpot = spotLevels[i];
194
+ break;
195
+ }
196
+ targetSpot = spotLevels[i];
197
+ }
198
+ confidence = Math.min(1, (avgPressureAbove - avgPressureBelow) / (Math.abs(avgPressureAbove) + Math.abs(avgPressureBelow) + 1));
199
+ rationale = `Supportive pressure above vs resistive below`;
200
+ }
201
+ else if (avgPressureAbove < 0 && avgPressureBelow > 0) {
202
+ direction = 'down';
203
+ for (let i = currentSpotIdx - 1; i >= 0; i--) {
204
+ if (cells[i][0].pressure.effectivePressure > 0) {
205
+ targetSpot = spotLevels[i];
206
+ break;
207
+ }
208
+ targetSpot = spotLevels[i];
209
+ }
210
+ confidence = Math.min(1, (avgPressureBelow - avgPressureAbove) / (Math.abs(avgPressureAbove) + Math.abs(avgPressureBelow) + 1));
211
+ rationale = `Resistive pressure above vs supportive below`;
212
+ }
213
+ else {
214
+ direction = 'pin';
215
+ targetSpot = currentSpot;
216
+ confidence = 0.3;
217
+ rationale = `Mixed pressure signals; defaulting to pin hypothesis`;
218
+ }
219
+ return { direction, targetSpot, confidence, rationale };
220
+ }
@@ -0,0 +1,5 @@
1
+ export type { MarketRegime, RegimeParams, NormalizedExposure, NormalizedExposureSummary, HedgingPressure, PressureGridCell, KeyLevels, PathOfLeastResistance, PressureGrid, StrikeIVPressure, IVSurfaceEvolution, CascadeStep, CascadeSimulation, IVPathConfig, } from './types';
2
+ export { deriveRegimeParams, interpolateIVAtStrike } from './regime';
3
+ export { normalizeExposures } from './normalize';
4
+ export { calculatePressureAt, buildPressureGrid } from './grid';
5
+ export { calculateIVPressures, predictIVSurfaceEvolution, runCascadeSimulation, analyzeIVPath, getIVPathOfLeastResistance, DEFAULT_IV_PATH_CONFIG, } from './ivpath';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_IV_PATH_CONFIG = exports.getIVPathOfLeastResistance = exports.analyzeIVPath = exports.runCascadeSimulation = exports.predictIVSurfaceEvolution = exports.calculateIVPressures = exports.buildPressureGrid = exports.calculatePressureAt = exports.normalizeExposures = exports.interpolateIVAtStrike = exports.deriveRegimeParams = void 0;
4
+ // Regime derivation
5
+ var regime_1 = require("./regime");
6
+ Object.defineProperty(exports, "deriveRegimeParams", { enumerable: true, get: function () { return regime_1.deriveRegimeParams; } });
7
+ Object.defineProperty(exports, "interpolateIVAtStrike", { enumerable: true, get: function () { return regime_1.interpolateIVAtStrike; } });
8
+ // Exposure normalization
9
+ var normalize_1 = require("./normalize");
10
+ Object.defineProperty(exports, "normalizeExposures", { enumerable: true, get: function () { return normalize_1.normalizeExposures; } });
11
+ // Pressure field
12
+ var grid_1 = require("./grid");
13
+ Object.defineProperty(exports, "calculatePressureAt", { enumerable: true, get: function () { return grid_1.calculatePressureAt; } });
14
+ Object.defineProperty(exports, "buildPressureGrid", { enumerable: true, get: function () { return grid_1.buildPressureGrid; } });
15
+ // IV path prediction
16
+ var ivpath_1 = require("./ivpath");
17
+ Object.defineProperty(exports, "calculateIVPressures", { enumerable: true, get: function () { return ivpath_1.calculateIVPressures; } });
18
+ Object.defineProperty(exports, "predictIVSurfaceEvolution", { enumerable: true, get: function () { return ivpath_1.predictIVSurfaceEvolution; } });
19
+ Object.defineProperty(exports, "runCascadeSimulation", { enumerable: true, get: function () { return ivpath_1.runCascadeSimulation; } });
20
+ Object.defineProperty(exports, "analyzeIVPath", { enumerable: true, get: function () { return ivpath_1.analyzeIVPath; } });
21
+ Object.defineProperty(exports, "getIVPathOfLeastResistance", { enumerable: true, get: function () { return ivpath_1.getIVPathOfLeastResistance; } });
22
+ Object.defineProperty(exports, "DEFAULT_IV_PATH_CONFIG", { enumerable: true, get: function () { return ivpath_1.DEFAULT_IV_PATH_CONFIG; } });
@@ -0,0 +1,31 @@
1
+ import { IVSurface, ExposurePerExpiry } from '../types';
2
+ import { RegimeParams, StrikeIVPressure, IVSurfaceEvolution, CascadeSimulation, IVPathConfig } from './types';
3
+ export declare const DEFAULT_IV_PATH_CONFIG: IVPathConfig;
4
+ /**
5
+ * Calculate IV pressure at each strike based on dealer positioning
6
+ */
7
+ export declare function calculateIVPressures(ivSurface: IVSurface, exposures: ExposurePerExpiry, regimeParams: RegimeParams, spotMove: number, config?: Partial<IVPathConfig>): StrikeIVPressure[];
8
+ /**
9
+ * Predict how the IV surface will evolve given dealer positioning
10
+ */
11
+ export declare function predictIVSurfaceEvolution(ivSurface: IVSurface, exposures: ExposurePerExpiry, spot: number, spotMoveScenario?: number, config?: Partial<IVPathConfig>): IVSurfaceEvolution;
12
+ /**
13
+ * Run a cascade simulation showing feedback loops
14
+ */
15
+ export declare function runCascadeSimulation(ivSurface: IVSurface, exposures: ExposurePerExpiry, spot: number, spotShock?: number, config?: Partial<IVPathConfig>): CascadeSimulation;
16
+ /**
17
+ * Analyze IV path for multiple spot scenarios
18
+ */
19
+ export declare function analyzeIVPath(ivSurface: IVSurface, exposures: ExposurePerExpiry, spot: number, options?: {
20
+ spotScenarios?: number[];
21
+ config?: Partial<IVPathConfig>;
22
+ }): Map<number, IVSurfaceEvolution>;
23
+ /**
24
+ * Get the IV path of least resistance
25
+ */
26
+ export declare function getIVPathOfLeastResistance(ivSurface: IVSurface, exposures: ExposurePerExpiry, spot: number): {
27
+ direction: 'up' | 'down' | 'stable';
28
+ magnitude: number;
29
+ confidence: number;
30
+ rationale: string;
31
+ };
@@ -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;