@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.
- package/README.md +7 -7
- package/dist/client/brokers/TradierClient.js +7 -6
- package/dist/impliedpdf/adjusted.d.ts +173 -0
- package/dist/impliedpdf/adjusted.js +500 -0
- package/dist/impliedpdf/index.d.ts +1 -0
- package/dist/impliedpdf/index.js +10 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +27 -1
- package/dist/pressure/grid.d.ts +14 -0
- package/dist/pressure/grid.js +220 -0
- package/dist/pressure/index.d.ts +5 -0
- package/dist/pressure/index.js +22 -0
- package/dist/pressure/ivpath.d.ts +31 -0
- package/dist/pressure/ivpath.js +304 -0
- package/dist/pressure/normalize.d.ts +6 -0
- package/dist/pressure/normalize.js +76 -0
- package/dist/pressure/regime.d.ts +7 -0
- package/dist/pressure/regime.js +99 -0
- package/dist/pressure/types.d.ts +182 -0
- package/dist/pressure/types.js +2 -0
- package/dist/utils/indexOptions.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getQuantile = exports.getCumulativeProbability = exports.getProbabilityInRange = exports.OPEX_CONFIG = exports.CRISIS_CONFIG = exports.LOW_VOL_CONFIG = exports.DEFAULT_ADJUSTMENT_CONFIG = void 0;
|
|
4
|
+
exports.estimateExposureAdjustedPDF = estimateExposureAdjustedPDF;
|
|
5
|
+
exports.getEdgeAtPrice = getEdgeAtPrice;
|
|
6
|
+
exports.getSignificantAdjustmentLevels = getSignificantAdjustmentLevels;
|
|
7
|
+
const index_1 = require("./index");
|
|
8
|
+
Object.defineProperty(exports, "getProbabilityInRange", { enumerable: true, get: function () { return index_1.getProbabilityInRange; } });
|
|
9
|
+
Object.defineProperty(exports, "getCumulativeProbability", { enumerable: true, get: function () { return index_1.getCumulativeProbability; } });
|
|
10
|
+
Object.defineProperty(exports, "getQuantile", { enumerable: true, get: function () { return index_1.getQuantile; } });
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Default Configuration
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Default configuration tuned for SPX-like indices
|
|
16
|
+
*/
|
|
17
|
+
exports.DEFAULT_ADJUSTMENT_CONFIG = {
|
|
18
|
+
gamma: {
|
|
19
|
+
enabled: true,
|
|
20
|
+
attractorStrength: 0.3,
|
|
21
|
+
repellentStrength: 0.3,
|
|
22
|
+
threshold: 1000000, // $1M GEX
|
|
23
|
+
decayRate: 2.0,
|
|
24
|
+
},
|
|
25
|
+
vanna: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
spotVolBeta: -3.0,
|
|
28
|
+
maxTailMultiplier: 2.5,
|
|
29
|
+
feedbackIterations: 3,
|
|
30
|
+
},
|
|
31
|
+
charm: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
timeHorizon: 'daily',
|
|
34
|
+
shiftScale: 1.0,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Configuration for low volatility / grinding markets
|
|
39
|
+
*/
|
|
40
|
+
exports.LOW_VOL_CONFIG = {
|
|
41
|
+
gamma: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
attractorStrength: 0.4, // Stronger pinning
|
|
44
|
+
repellentStrength: 0.2,
|
|
45
|
+
threshold: 500000,
|
|
46
|
+
decayRate: 1.5,
|
|
47
|
+
},
|
|
48
|
+
vanna: {
|
|
49
|
+
enabled: true,
|
|
50
|
+
spotVolBeta: -2.0, // Less reactive
|
|
51
|
+
maxTailMultiplier: 1.5,
|
|
52
|
+
feedbackIterations: 2,
|
|
53
|
+
},
|
|
54
|
+
charm: {
|
|
55
|
+
enabled: true,
|
|
56
|
+
timeHorizon: 'daily',
|
|
57
|
+
shiftScale: 1.5, // Charm matters more in calm markets
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Configuration for high volatility / crisis markets
|
|
62
|
+
*/
|
|
63
|
+
exports.CRISIS_CONFIG = {
|
|
64
|
+
gamma: {
|
|
65
|
+
enabled: true,
|
|
66
|
+
attractorStrength: 0.1, // Pins don't hold
|
|
67
|
+
repellentStrength: 0.5, // -GEX acceleration dominates
|
|
68
|
+
threshold: 2000000,
|
|
69
|
+
decayRate: 3.0,
|
|
70
|
+
},
|
|
71
|
+
vanna: {
|
|
72
|
+
enabled: true,
|
|
73
|
+
spotVolBeta: -5.0, // Highly reactive
|
|
74
|
+
maxTailMultiplier: 3.0,
|
|
75
|
+
feedbackIterations: 5,
|
|
76
|
+
},
|
|
77
|
+
charm: {
|
|
78
|
+
enabled: true,
|
|
79
|
+
timeHorizon: 'intraday',
|
|
80
|
+
shiftScale: 0.5, // Charm less important vs gamma/vanna
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Configuration for OPEX week
|
|
85
|
+
*/
|
|
86
|
+
exports.OPEX_CONFIG = {
|
|
87
|
+
gamma: {
|
|
88
|
+
enabled: true,
|
|
89
|
+
attractorStrength: 0.5, // Strong pinning into expiry
|
|
90
|
+
repellentStrength: 0.4,
|
|
91
|
+
threshold: 1000000,
|
|
92
|
+
decayRate: 2.5,
|
|
93
|
+
},
|
|
94
|
+
vanna: {
|
|
95
|
+
enabled: true,
|
|
96
|
+
spotVolBeta: -3.0,
|
|
97
|
+
maxTailMultiplier: 2.0,
|
|
98
|
+
feedbackIterations: 3,
|
|
99
|
+
},
|
|
100
|
+
charm: {
|
|
101
|
+
enabled: true,
|
|
102
|
+
timeHorizon: 'intraday', // Charm accelerates into expiry
|
|
103
|
+
shiftScale: 2.0,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Core Functions
|
|
108
|
+
// ============================================================================
|
|
109
|
+
/**
|
|
110
|
+
* Estimate an exposure-adjusted implied probability distribution.
|
|
111
|
+
*
|
|
112
|
+
* This function takes the standard market-implied PDF (Breeden-Litzenberger)
|
|
113
|
+
* and adjusts it based on dealer Greek exposures to produce a "mechanically-informed"
|
|
114
|
+
* probability distribution that accounts for:
|
|
115
|
+
*
|
|
116
|
+
* - **Gamma**: Creates "sticky" zones (+GEX) where price pins, and "slippery" zones
|
|
117
|
+
* (-GEX) where price accelerates through
|
|
118
|
+
* - **Vanna**: Fattens tails based on the IV-spot feedback loop (selloffs spike IV,
|
|
119
|
+
* which forces more selling via negative vanna)
|
|
120
|
+
* - **Charm**: Shifts the mean based on predictable delta decay over time
|
|
121
|
+
*
|
|
122
|
+
* @param symbol - Underlying ticker symbol
|
|
123
|
+
* @param underlyingPrice - Current spot price
|
|
124
|
+
* @param callOptions - Call options for a single expiry
|
|
125
|
+
* @param exposures - Exposure metrics from calculateGammaVannaCharmExposures()
|
|
126
|
+
* @param config - Adjustment configuration (uses defaults if not provided)
|
|
127
|
+
* @returns Baseline and adjusted PDFs with comparison metrics
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* // Get exposures first
|
|
132
|
+
* const allExposures = calculateGammaVannaCharmExposures(chain, ivSurfaces);
|
|
133
|
+
* const expiryExposures = allExposures.find(e => e.expiration === targetExpiry);
|
|
134
|
+
*
|
|
135
|
+
* // Calculate adjusted PDF
|
|
136
|
+
* const result = estimateExposureAdjustedPDF(
|
|
137
|
+
* 'SPX',
|
|
138
|
+
* 4520,
|
|
139
|
+
* callOptionsForExpiry,
|
|
140
|
+
* expiryExposures
|
|
141
|
+
* );
|
|
142
|
+
*
|
|
143
|
+
* // Compare probabilities
|
|
144
|
+
* const target = 4400;
|
|
145
|
+
* const baselineProb = getCumulativeProbability(result.baseline, target);
|
|
146
|
+
* const adjustedProb = getCumulativeProbability(result.adjusted, target);
|
|
147
|
+
* console.log(`Market says ${baselineProb}% chance of ${target}`);
|
|
148
|
+
* console.log(`Flow-adjusted: ${adjustedProb}% chance`);
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
function estimateExposureAdjustedPDF(symbol, underlyingPrice, callOptions, exposures, config = {}) {
|
|
152
|
+
// Merge config with defaults
|
|
153
|
+
const cfg = mergeConfig(config);
|
|
154
|
+
// Step 1: Get baseline PDF using existing Breeden-Litzenberger implementation
|
|
155
|
+
const baselineResult = (0, index_1.estimateImpliedProbabilityDistribution)(symbol, underlyingPrice, callOptions);
|
|
156
|
+
if (!baselineResult.success) {
|
|
157
|
+
return { success: false, error: baselineResult.error };
|
|
158
|
+
}
|
|
159
|
+
const baseline = baselineResult.distribution;
|
|
160
|
+
// Step 2: Calculate gamma modifiers
|
|
161
|
+
const gammaModifiers = cfg.gamma.enabled
|
|
162
|
+
? calculateGammaModifiers(baseline.strikeProbabilities, exposures, underlyingPrice, cfg.gamma)
|
|
163
|
+
: baseline.strikeProbabilities.map(() => 1.0);
|
|
164
|
+
// Step 3: Calculate vanna modifiers
|
|
165
|
+
const vannaModifiers = cfg.vanna.enabled
|
|
166
|
+
? calculateVannaModifiers(baseline.strikeProbabilities, exposures, underlyingPrice, cfg.vanna)
|
|
167
|
+
: baseline.strikeProbabilities.map(() => 1.0);
|
|
168
|
+
// Step 4: Calculate charm-induced mean shift
|
|
169
|
+
const charmShift = cfg.charm.enabled
|
|
170
|
+
? calculateCharmShift(exposures, underlyingPrice, cfg.charm)
|
|
171
|
+
: 0;
|
|
172
|
+
// Step 5: Apply modifiers to create adjusted probabilities
|
|
173
|
+
const adjustedProbabilities = applyModifiers(baseline.strikeProbabilities, gammaModifiers, vannaModifiers, charmShift);
|
|
174
|
+
// Step 6: Normalize to ensure probabilities sum to 1
|
|
175
|
+
const normalizedProbabilities = normalizeProbabilities(adjustedProbabilities);
|
|
176
|
+
// Step 7: Recalculate distribution statistics
|
|
177
|
+
const adjusted = recalculateDistributionStats(baseline, normalizedProbabilities, underlyingPrice);
|
|
178
|
+
// Step 8: Calculate comparison metrics
|
|
179
|
+
const comparison = calculateComparison(baseline, adjusted, gammaModifiers, vannaModifiers, charmShift);
|
|
180
|
+
return {
|
|
181
|
+
baseline,
|
|
182
|
+
adjusted,
|
|
183
|
+
gammaModifiers,
|
|
184
|
+
vannaModifiers,
|
|
185
|
+
charmShift,
|
|
186
|
+
comparison,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// Modifier Calculations
|
|
191
|
+
// ============================================================================
|
|
192
|
+
/**
|
|
193
|
+
* Calculate gamma-based probability modifiers.
|
|
194
|
+
*
|
|
195
|
+
* Positive GEX creates "attractors" - price tends to pin at these levels.
|
|
196
|
+
* Negative GEX creates "repellents" - price accelerates through these levels.
|
|
197
|
+
*
|
|
198
|
+
* Uses 1/d² decay similar to the "local hedge pressure field" concept.
|
|
199
|
+
*/
|
|
200
|
+
function calculateGammaModifiers(strikeProbabilities, exposures, spot, config) {
|
|
201
|
+
const modifiers = [];
|
|
202
|
+
// Find max absolute GEX for normalization
|
|
203
|
+
const maxGex = Math.max(...exposures.strikeExposures.map(e => Math.abs(e.gammaExposure)), 1 // Prevent division by zero
|
|
204
|
+
);
|
|
205
|
+
for (const prob of strikeProbabilities) {
|
|
206
|
+
let modifier = 1.0;
|
|
207
|
+
for (const exposure of exposures.strikeExposures) {
|
|
208
|
+
// Skip insignificant exposures
|
|
209
|
+
if (Math.abs(exposure.gammaExposure) < config.threshold) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
// Distance normalized by spot price
|
|
213
|
+
const distance = Math.abs(prob.strike - exposure.strikePrice) / spot;
|
|
214
|
+
// Influence decays with distance squared (electrostatic/gravitational)
|
|
215
|
+
const influence = 1 / (1 + config.decayRate * distance * distance);
|
|
216
|
+
// Normalized GEX (-1 to 1)
|
|
217
|
+
const normalizedGex = exposure.gammaExposure / maxGex;
|
|
218
|
+
if (exposure.gammaExposure > 0) {
|
|
219
|
+
// Positive GEX = attractor = increase probability density
|
|
220
|
+
modifier *= 1 + config.attractorStrength * normalizedGex * influence;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
// Negative GEX = repellent = decrease probability density
|
|
224
|
+
modifier *= 1 - config.repellentStrength * Math.abs(normalizedGex) * influence;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Clamp to reasonable bounds
|
|
228
|
+
modifiers.push(Math.max(0.1, Math.min(3.0, modifier)));
|
|
229
|
+
}
|
|
230
|
+
return modifiers;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Calculate vanna-based probability modifiers.
|
|
234
|
+
*
|
|
235
|
+
* Vanna creates feedback loops:
|
|
236
|
+
* - Spot drops → IV spikes → negative vanna forces selling → spot drops more
|
|
237
|
+
*
|
|
238
|
+
* This fattens the left tail beyond what the market-implied PDF shows.
|
|
239
|
+
*/
|
|
240
|
+
function calculateVannaModifiers(strikeProbabilities, exposures, spot, config) {
|
|
241
|
+
const modifiers = [];
|
|
242
|
+
// Sum vanna below and above spot
|
|
243
|
+
const vannaBelow = exposures.strikeExposures
|
|
244
|
+
.filter(e => e.strikePrice < spot)
|
|
245
|
+
.reduce((sum, e) => sum + e.vannaExposure, 0);
|
|
246
|
+
const vannaAbove = exposures.strikeExposures
|
|
247
|
+
.filter(e => e.strikePrice > spot)
|
|
248
|
+
.reduce((sum, e) => sum + e.vannaExposure, 0);
|
|
249
|
+
for (const prob of strikeProbabilities) {
|
|
250
|
+
let modifier = 1.0;
|
|
251
|
+
// Percentage move to this strike
|
|
252
|
+
const movePercent = (prob.strike - spot) / spot;
|
|
253
|
+
if (movePercent < 0) {
|
|
254
|
+
// Downside move: estimate IV spike
|
|
255
|
+
const ivSpike = -movePercent * Math.abs(config.spotVolBeta);
|
|
256
|
+
// Vanna flow from IV change
|
|
257
|
+
// Negative vanna * positive IV change = selling pressure
|
|
258
|
+
const vannaFlow = vannaBelow * ivSpike;
|
|
259
|
+
if (vannaFlow < 0) {
|
|
260
|
+
// Selling pressure increases left tail probability
|
|
261
|
+
let cumulativeEffect = 0;
|
|
262
|
+
let currentFlow = Math.abs(vannaFlow);
|
|
263
|
+
// Iterate feedback loop (each iteration dampens ~50%)
|
|
264
|
+
for (let i = 0; i < config.feedbackIterations; i++) {
|
|
265
|
+
cumulativeEffect += currentFlow;
|
|
266
|
+
currentFlow *= 0.5;
|
|
267
|
+
}
|
|
268
|
+
// Scale to modifier (normalize by spot * $1M for comparability)
|
|
269
|
+
const effectScale = cumulativeEffect / (spot * 1000000);
|
|
270
|
+
modifier = 1 + Math.min(config.maxTailMultiplier - 1, effectScale);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else if (movePercent > 0) {
|
|
274
|
+
// Upside move: IV typically compresses (asymmetrically less than down moves)
|
|
275
|
+
const ivCompress = movePercent * Math.abs(config.spotVolBeta) * 0.5;
|
|
276
|
+
// Positive vanna + IV compression = less buying pressure on upside
|
|
277
|
+
const vannaFlow = vannaAbove * (-ivCompress);
|
|
278
|
+
if (vannaFlow > 0) {
|
|
279
|
+
const effectScale = vannaFlow / (spot * 1000000);
|
|
280
|
+
modifier = Math.max(0.5, 1 - effectScale * 0.5);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
modifiers.push(modifier);
|
|
284
|
+
}
|
|
285
|
+
return modifiers;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Calculate charm-induced mean shift.
|
|
289
|
+
*
|
|
290
|
+
* Net charm represents guaranteed delta decay. Negative net charm means
|
|
291
|
+
* dealers will be net sellers over time (downward pressure).
|
|
292
|
+
*/
|
|
293
|
+
function calculateCharmShift(exposures, spot, config) {
|
|
294
|
+
const timeMultiplier = {
|
|
295
|
+
'intraday': 0.25,
|
|
296
|
+
'daily': 1.0,
|
|
297
|
+
'weekly': 5.0,
|
|
298
|
+
}[config.timeHorizon];
|
|
299
|
+
// Estimate price impact of charm flow
|
|
300
|
+
// Heuristic: $1B of flow ≈ 0.1% price impact for large indices
|
|
301
|
+
const flowImpactPerBillion = 0.001 * spot;
|
|
302
|
+
const priceShift = (exposures.totalCharmExposure / 1000000000) * flowImpactPerBillion * timeMultiplier;
|
|
303
|
+
return priceShift * config.shiftScale;
|
|
304
|
+
}
|
|
305
|
+
// ============================================================================
|
|
306
|
+
// PDF Operations
|
|
307
|
+
// ============================================================================
|
|
308
|
+
/**
|
|
309
|
+
* Apply gamma, vanna, and charm modifiers to probability distribution
|
|
310
|
+
*/
|
|
311
|
+
function applyModifiers(strikeProbabilities, gammaModifiers, vannaModifiers, charmShift) {
|
|
312
|
+
return strikeProbabilities.map((sp, i) => ({
|
|
313
|
+
strike: sp.strike + charmShift,
|
|
314
|
+
probability: sp.probability * gammaModifiers[i] * vannaModifiers[i],
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Normalize probabilities to sum to 1
|
|
319
|
+
*/
|
|
320
|
+
function normalizeProbabilities(strikeProbabilities) {
|
|
321
|
+
const sum = strikeProbabilities.reduce((acc, sp) => acc + sp.probability, 0);
|
|
322
|
+
if (sum < 1e-9) {
|
|
323
|
+
// If all probabilities are essentially zero, return uniform
|
|
324
|
+
const uniform = 1 / strikeProbabilities.length;
|
|
325
|
+
return strikeProbabilities.map(sp => ({ ...sp, probability: uniform }));
|
|
326
|
+
}
|
|
327
|
+
return strikeProbabilities.map(sp => ({
|
|
328
|
+
strike: sp.strike,
|
|
329
|
+
probability: sp.probability / sum,
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Recalculate distribution statistics from adjusted probabilities
|
|
334
|
+
*/
|
|
335
|
+
function recalculateDistributionStats(baseline, adjustedProbabilities, underlyingPrice) {
|
|
336
|
+
// Most likely price (mode)
|
|
337
|
+
let mostLikelyPrice = adjustedProbabilities[0].strike;
|
|
338
|
+
let maxProb = 0;
|
|
339
|
+
for (const sp of adjustedProbabilities) {
|
|
340
|
+
if (sp.probability > maxProb) {
|
|
341
|
+
maxProb = sp.probability;
|
|
342
|
+
mostLikelyPrice = sp.strike;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Median (50th percentile)
|
|
346
|
+
let cumulative = 0;
|
|
347
|
+
let medianPrice = adjustedProbabilities[Math.floor(adjustedProbabilities.length / 2)].strike;
|
|
348
|
+
for (const sp of adjustedProbabilities) {
|
|
349
|
+
cumulative += sp.probability;
|
|
350
|
+
if (cumulative >= 0.5) {
|
|
351
|
+
medianPrice = sp.strike;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Expected value (mean)
|
|
356
|
+
let mean = 0;
|
|
357
|
+
for (const sp of adjustedProbabilities) {
|
|
358
|
+
mean += sp.strike * sp.probability;
|
|
359
|
+
}
|
|
360
|
+
// Variance and expected move (std dev)
|
|
361
|
+
let variance = 0;
|
|
362
|
+
for (const sp of adjustedProbabilities) {
|
|
363
|
+
const diff = sp.strike - mean;
|
|
364
|
+
variance += diff * diff * sp.probability;
|
|
365
|
+
}
|
|
366
|
+
const expectedMove = Math.sqrt(variance);
|
|
367
|
+
// Tail skew
|
|
368
|
+
let leftTail = 0;
|
|
369
|
+
let rightTail = 0;
|
|
370
|
+
for (const sp of adjustedProbabilities) {
|
|
371
|
+
if (sp.strike < mean) {
|
|
372
|
+
leftTail += sp.probability;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
rightTail += sp.probability;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const tailSkew = rightTail / Math.max(leftTail, 1e-9);
|
|
379
|
+
// Cumulative above/below spot
|
|
380
|
+
let cumulativeBelowSpot = 0;
|
|
381
|
+
let cumulativeAboveSpot = 0;
|
|
382
|
+
for (const sp of adjustedProbabilities) {
|
|
383
|
+
if (sp.strike < underlyingPrice) {
|
|
384
|
+
cumulativeBelowSpot += sp.probability;
|
|
385
|
+
}
|
|
386
|
+
else if (sp.strike > underlyingPrice) {
|
|
387
|
+
cumulativeAboveSpot += sp.probability;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
symbol: baseline.symbol,
|
|
392
|
+
expiryDate: baseline.expiryDate,
|
|
393
|
+
calculationTimestamp: Date.now(),
|
|
394
|
+
underlyingPrice,
|
|
395
|
+
strikeProbabilities: adjustedProbabilities,
|
|
396
|
+
mostLikelyPrice,
|
|
397
|
+
medianPrice,
|
|
398
|
+
expectedValue: mean,
|
|
399
|
+
expectedMove,
|
|
400
|
+
tailSkew,
|
|
401
|
+
cumulativeProbabilityAboveSpot: cumulativeAboveSpot,
|
|
402
|
+
cumulativeProbabilityBelowSpot: cumulativeBelowSpot,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Calculate comparison metrics between baseline and adjusted distributions
|
|
407
|
+
*/
|
|
408
|
+
function calculateComparison(baseline, adjusted, gammaModifiers, vannaModifiers, charmShift) {
|
|
409
|
+
// Tail percentiles
|
|
410
|
+
const baseline5 = (0, index_1.getQuantile)(baseline, 0.05);
|
|
411
|
+
const baseline95 = (0, index_1.getQuantile)(baseline, 0.95);
|
|
412
|
+
const adjusted5 = (0, index_1.getQuantile)(adjusted, 0.05);
|
|
413
|
+
const adjusted95 = (0, index_1.getQuantile)(adjusted, 0.95);
|
|
414
|
+
// Determine dominant factor
|
|
415
|
+
const gammaEffect = Math.max(...gammaModifiers) - Math.min(...gammaModifiers);
|
|
416
|
+
const vannaEffect = Math.max(...vannaModifiers) - Math.min(...vannaModifiers);
|
|
417
|
+
const charmEffect = Math.abs(charmShift) / baseline.underlyingPrice;
|
|
418
|
+
let dominantFactor = 'none';
|
|
419
|
+
const maxEffect = Math.max(gammaEffect, vannaEffect, charmEffect);
|
|
420
|
+
if (maxEffect > 0.01) {
|
|
421
|
+
if (gammaEffect === maxEffect)
|
|
422
|
+
dominantFactor = 'gamma';
|
|
423
|
+
else if (vannaEffect === maxEffect)
|
|
424
|
+
dominantFactor = 'vanna';
|
|
425
|
+
else
|
|
426
|
+
dominantFactor = 'charm';
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
meanShift: adjusted.expectedValue - baseline.expectedValue,
|
|
430
|
+
meanShiftPercent: ((adjusted.expectedValue - baseline.expectedValue) / baseline.underlyingPrice) * 100,
|
|
431
|
+
stdDevChange: adjusted.expectedMove - baseline.expectedMove,
|
|
432
|
+
tailSkewChange: adjusted.tailSkew - baseline.tailSkew,
|
|
433
|
+
leftTail: {
|
|
434
|
+
baseline: baseline5,
|
|
435
|
+
adjusted: adjusted5,
|
|
436
|
+
ratio: adjusted5 / baseline5,
|
|
437
|
+
},
|
|
438
|
+
rightTail: {
|
|
439
|
+
baseline: baseline95,
|
|
440
|
+
adjusted: adjusted95,
|
|
441
|
+
ratio: adjusted95 / baseline95,
|
|
442
|
+
},
|
|
443
|
+
dominantFactor,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
// ============================================================================
|
|
447
|
+
// Utility Functions
|
|
448
|
+
// ============================================================================
|
|
449
|
+
/**
|
|
450
|
+
* Merge partial config with defaults
|
|
451
|
+
*/
|
|
452
|
+
function mergeConfig(partial) {
|
|
453
|
+
return {
|
|
454
|
+
gamma: { ...exports.DEFAULT_ADJUSTMENT_CONFIG.gamma, ...partial.gamma },
|
|
455
|
+
vanna: { ...exports.DEFAULT_ADJUSTMENT_CONFIG.vanna, ...partial.vanna },
|
|
456
|
+
charm: { ...exports.DEFAULT_ADJUSTMENT_CONFIG.charm, ...partial.charm },
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get the "edge" - the difference between market-implied and flow-adjusted
|
|
461
|
+
* probability of reaching a price level.
|
|
462
|
+
*
|
|
463
|
+
* Positive edge means the market is underpricing the probability of reaching that level.
|
|
464
|
+
* Negative edge means the market is overpricing it.
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```typescript
|
|
468
|
+
* const result = estimateExposureAdjustedPDF(...);
|
|
469
|
+
* const edge = getEdgeAtPrice(result, 4400);
|
|
470
|
+
* console.log(`Edge at 4400: ${(edge * 100).toFixed(2)}%`);
|
|
471
|
+
* // Output: "Edge at 4400: 2.35%"
|
|
472
|
+
* // Meaning: flow mechanics suggest 2.35% higher probability than market prices
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
function getEdgeAtPrice(result, price) {
|
|
476
|
+
const baselineProb = (0, index_1.getCumulativeProbability)(result.baseline, price);
|
|
477
|
+
const adjustedProb = (0, index_1.getCumulativeProbability)(result.adjusted, price);
|
|
478
|
+
return adjustedProb - baselineProb;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Get price levels where the adjustment is most significant
|
|
482
|
+
*/
|
|
483
|
+
function getSignificantAdjustmentLevels(result, threshold = 0.01) {
|
|
484
|
+
const levels = [];
|
|
485
|
+
for (let i = 0; i < result.baseline.strikeProbabilities.length; i++) {
|
|
486
|
+
const baselineProb = result.baseline.strikeProbabilities[i].probability;
|
|
487
|
+
const adjustedProb = result.adjusted.strikeProbabilities[i]?.probability ?? 0;
|
|
488
|
+
const edge = adjustedProb - baselineProb;
|
|
489
|
+
if (Math.abs(edge) >= threshold) {
|
|
490
|
+
levels.push({
|
|
491
|
+
strike: result.baseline.strikeProbabilities[i].strike,
|
|
492
|
+
baselineProb,
|
|
493
|
+
adjustedProb,
|
|
494
|
+
edge,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// Sort by absolute edge descending
|
|
499
|
+
return levels.sort((a, b) => Math.abs(b.edge) - Math.abs(a.edge));
|
|
500
|
+
}
|
|
@@ -146,3 +146,4 @@ export declare function getCumulativeProbability(distribution: ImpliedProbabilit
|
|
|
146
146
|
* ```
|
|
147
147
|
*/
|
|
148
148
|
export declare function getQuantile(distribution: ImpliedProbabilityDistribution, probability: number): number;
|
|
149
|
+
export { estimateExposureAdjustedPDF, getEdgeAtPrice, getSignificantAdjustmentLevels, DEFAULT_ADJUSTMENT_CONFIG, LOW_VOL_CONFIG, CRISIS_CONFIG, OPEX_CONFIG, type ExposureAdjustmentConfig, type AdjustedPDFResult, type PDFComparison, } from './adjusted';
|
package/dist/impliedpdf/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OPEX_CONFIG = exports.CRISIS_CONFIG = exports.LOW_VOL_CONFIG = exports.DEFAULT_ADJUSTMENT_CONFIG = exports.getSignificantAdjustmentLevels = exports.getEdgeAtPrice = exports.estimateExposureAdjustedPDF = void 0;
|
|
3
4
|
exports.estimateImpliedProbabilityDistribution = estimateImpliedProbabilityDistribution;
|
|
4
5
|
exports.estimateImpliedProbabilityDistributions = estimateImpliedProbabilityDistributions;
|
|
5
6
|
exports.getProbabilityInRange = getProbabilityInRange;
|
|
@@ -276,3 +277,12 @@ function getQuantile(distribution, probability) {
|
|
|
276
277
|
}
|
|
277
278
|
return distribution.strikeProbabilities[distribution.strikeProbabilities.length - 1]?.strike ?? 0;
|
|
278
279
|
}
|
|
280
|
+
// Re-export exposure-adjusted PDF functionality
|
|
281
|
+
var adjusted_1 = require("./adjusted");
|
|
282
|
+
Object.defineProperty(exports, "estimateExposureAdjustedPDF", { enumerable: true, get: function () { return adjusted_1.estimateExposureAdjustedPDF; } });
|
|
283
|
+
Object.defineProperty(exports, "getEdgeAtPrice", { enumerable: true, get: function () { return adjusted_1.getEdgeAtPrice; } });
|
|
284
|
+
Object.defineProperty(exports, "getSignificantAdjustmentLevels", { enumerable: true, get: function () { return adjusted_1.getSignificantAdjustmentLevels; } });
|
|
285
|
+
Object.defineProperty(exports, "DEFAULT_ADJUSTMENT_CONFIG", { enumerable: true, get: function () { return adjusted_1.DEFAULT_ADJUSTMENT_CONFIG; } });
|
|
286
|
+
Object.defineProperty(exports, "LOW_VOL_CONFIG", { enumerable: true, get: function () { return adjusted_1.LOW_VOL_CONFIG; } });
|
|
287
|
+
Object.defineProperty(exports, "CRISIS_CONFIG", { enumerable: true, get: function () { return adjusted_1.CRISIS_CONFIG; } });
|
|
288
|
+
Object.defineProperty(exports, "OPEX_CONFIG", { enumerable: true, get: function () { return adjusted_1.OPEX_CONFIG; } });
|
package/dist/index.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ export { calculateGammaVannaCharmExposures, calculateSharesNeededToCover, } from
|
|
|
12
12
|
export { cumulativeNormalDistribution, normalPDF, } from './utils/statistics';
|
|
13
13
|
export { buildOCCSymbol, parseOCCSymbol, generateStrikesAroundSpot, generateOCCSymbolsForStrikes, generateOCCSymbolsAroundSpot, } from './utils/occ';
|
|
14
14
|
export type { OCCSymbolParams, ParsedOCCSymbol, StrikeGenerationParams, } from './utils/occ';
|
|
15
|
-
export { estimateImpliedProbabilityDistribution, estimateImpliedProbabilityDistributions, getProbabilityInRange, getCumulativeProbability, getQuantile, } from './impliedpdf';
|
|
16
|
-
export type { StrikeProbability, ImpliedProbabilityDistribution, ImpliedPDFResult, } from './impliedpdf';
|
|
15
|
+
export { estimateImpliedProbabilityDistribution, estimateImpliedProbabilityDistributions, getProbabilityInRange, getCumulativeProbability, getQuantile, estimateExposureAdjustedPDF, getEdgeAtPrice, getSignificantAdjustmentLevels, DEFAULT_ADJUSTMENT_CONFIG, LOW_VOL_CONFIG, CRISIS_CONFIG, OPEX_CONFIG, } from './impliedpdf';
|
|
16
|
+
export type { StrikeProbability, ImpliedProbabilityDistribution, ImpliedPDFResult, ExposureAdjustmentConfig, AdjustedPDFResult, PDFComparison, } from './impliedpdf';
|
|
17
|
+
export { deriveRegimeParams, interpolateIVAtStrike, normalizeExposures, calculatePressureAt, buildPressureGrid, calculateIVPressures, predictIVSurfaceEvolution, runCascadeSimulation, analyzeIVPath, getIVPathOfLeastResistance, DEFAULT_IV_PATH_CONFIG, } from './pressure';
|
|
18
|
+
export type { MarketRegime, RegimeParams, NormalizedExposure, NormalizedExposureSummary, HedgingPressure, PressureGridCell, KeyLevels, PathOfLeastResistance, PressureGrid, StrikeIVPressure, IVSurfaceEvolution, CascadeStep, CascadeSimulation, IVPathConfig, } from './pressure';
|
|
17
19
|
export { FloeClient, Broker } from './client/FloeClient';
|
|
18
20
|
export { TradierClient } from './client/brokers/TradierClient';
|
|
19
21
|
export { TastyTradeClient } from './client/brokers/TastyTradeClient';
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,8 @@ 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.
|
|
23
|
+
exports.brokerAdapters = exports.tdaAdapter = exports.ibkrAdapter = exports.schwabAdapter = exports.genericAdapter = exports.TradeStationClient = exports.TastyTradeClient = exports.TradierClient = exports.Broker = exports.FloeClient = 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 = 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;
|
|
24
|
+
exports.createOptionChain = exports.getAdapter = void 0;
|
|
24
25
|
// Core types
|
|
25
26
|
__exportStar(require("./types"), exports);
|
|
26
27
|
// Black-Scholes pricing and Greeks
|
|
@@ -59,6 +60,31 @@ Object.defineProperty(exports, "estimateImpliedProbabilityDistributions", { enum
|
|
|
59
60
|
Object.defineProperty(exports, "getProbabilityInRange", { enumerable: true, get: function () { return impliedpdf_1.getProbabilityInRange; } });
|
|
60
61
|
Object.defineProperty(exports, "getCumulativeProbability", { enumerable: true, get: function () { return impliedpdf_1.getCumulativeProbability; } });
|
|
61
62
|
Object.defineProperty(exports, "getQuantile", { enumerable: true, get: function () { return impliedpdf_1.getQuantile; } });
|
|
63
|
+
// Exposure-adjusted PDF
|
|
64
|
+
Object.defineProperty(exports, "estimateExposureAdjustedPDF", { enumerable: true, get: function () { return impliedpdf_1.estimateExposureAdjustedPDF; } });
|
|
65
|
+
Object.defineProperty(exports, "getEdgeAtPrice", { enumerable: true, get: function () { return impliedpdf_1.getEdgeAtPrice; } });
|
|
66
|
+
Object.defineProperty(exports, "getSignificantAdjustmentLevels", { enumerable: true, get: function () { return impliedpdf_1.getSignificantAdjustmentLevels; } });
|
|
67
|
+
Object.defineProperty(exports, "DEFAULT_ADJUSTMENT_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.DEFAULT_ADJUSTMENT_CONFIG; } });
|
|
68
|
+
Object.defineProperty(exports, "LOW_VOL_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.LOW_VOL_CONFIG; } });
|
|
69
|
+
Object.defineProperty(exports, "CRISIS_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.CRISIS_CONFIG; } });
|
|
70
|
+
Object.defineProperty(exports, "OPEX_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.OPEX_CONFIG; } });
|
|
71
|
+
// Pressure field and IV path prediction
|
|
72
|
+
var pressure_1 = require("./pressure");
|
|
73
|
+
// Regime derivation
|
|
74
|
+
Object.defineProperty(exports, "deriveRegimeParams", { enumerable: true, get: function () { return pressure_1.deriveRegimeParams; } });
|
|
75
|
+
Object.defineProperty(exports, "interpolateIVAtStrike", { enumerable: true, get: function () { return pressure_1.interpolateIVAtStrike; } });
|
|
76
|
+
// Exposure normalization
|
|
77
|
+
Object.defineProperty(exports, "normalizeExposures", { enumerable: true, get: function () { return pressure_1.normalizeExposures; } });
|
|
78
|
+
// Pressure field
|
|
79
|
+
Object.defineProperty(exports, "calculatePressureAt", { enumerable: true, get: function () { return pressure_1.calculatePressureAt; } });
|
|
80
|
+
Object.defineProperty(exports, "buildPressureGrid", { enumerable: true, get: function () { return pressure_1.buildPressureGrid; } });
|
|
81
|
+
// IV path prediction
|
|
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; } });
|
|
62
88
|
// Client
|
|
63
89
|
var FloeClient_1 = require("./client/FloeClient");
|
|
64
90
|
Object.defineProperty(exports, "FloeClient", { enumerable: true, get: function () { return FloeClient_1.FloeClient; } });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { OptionChain, IVSurface, ExposurePerExpiry } from '../types';
|
|
2
|
+
import { RegimeParams, HedgingPressure, PressureGrid } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Calculate hedging pressure at a specific (spot, time) point
|
|
5
|
+
*/
|
|
6
|
+
export declare function calculatePressureAt(spot: number, timeToExpiry: number, exposures: ExposurePerExpiry, regimeParams: RegimeParams): HedgingPressure;
|
|
7
|
+
/**
|
|
8
|
+
* Build a complete pressure grid for visualization
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildPressureGrid(symbol: string, chain: OptionChain, exposures: ExposurePerExpiry, ivSurface: IVSurface, options?: {
|
|
11
|
+
spotRangePercent?: number;
|
|
12
|
+
spotStepPercent?: number;
|
|
13
|
+
timeStepMinutes?: number;
|
|
14
|
+
}): PressureGrid;
|