@fullstackcraftllc/floe 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/exposure/index.d.ts +15 -10
- package/dist/exposure/index.js +164 -106
- package/dist/impliedpdf/adjusted.d.ts +5 -2
- package/dist/impliedpdf/adjusted.js +5 -2
- package/dist/types/index.d.ts +66 -0
- package/package.json +1 -1
package/dist/exposure/index.d.ts
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { OptionChain,
|
|
1
|
+
import { OptionChain, ExposureVariantsPerExpiry, ExposureCalculationOptions, IVSurface } from '../types';
|
|
2
2
|
/**
|
|
3
|
-
* Calculate
|
|
3
|
+
* Calculate canonical, state-weighted, and flow-delta exposure variants.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* canonical:
|
|
6
|
+
* - GEX: dollars per 1% underlying move
|
|
7
|
+
* - VEX: dollars per 1 vol-point move
|
|
8
|
+
* - CEX: dollars per 1 day of time decay
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* stateWeighted:
|
|
11
|
+
* - Gamma: same as canonical (spot is already the state variable)
|
|
12
|
+
* - Vanna: canonical vanna weighted by strike IV level
|
|
13
|
+
* - Charm: canonical charm weighted by days-to-expiration
|
|
14
|
+
*
|
|
15
|
+
* flowDelta:
|
|
16
|
+
* - Canonical exposure formulas using OI deltas:
|
|
17
|
+
* (liveOpenInterest - openInterest)
|
|
13
18
|
*/
|
|
14
|
-
export declare function calculateGammaVannaCharmExposures(chain: OptionChain, ivSurfaces: IVSurface[]):
|
|
19
|
+
export declare function calculateGammaVannaCharmExposures(chain: OptionChain, ivSurfaces: IVSurface[], options?: ExposureCalculationOptions): ExposureVariantsPerExpiry[];
|
|
15
20
|
/**
|
|
16
21
|
* Calculate shares needed to cover net exposure
|
|
17
22
|
*
|
package/dist/exposure/index.js
CHANGED
|
@@ -6,70 +6,62 @@ const types_1 = require("../types");
|
|
|
6
6
|
const blackscholes_1 = require("../blackscholes");
|
|
7
7
|
const volatility_1 = require("../volatility");
|
|
8
8
|
/**
|
|
9
|
-
* Calculate
|
|
9
|
+
* Calculate canonical, state-weighted, and flow-delta exposure variants.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* canonical:
|
|
12
|
+
* - GEX: dollars per 1% underlying move
|
|
13
|
+
* - VEX: dollars per 1 vol-point move
|
|
14
|
+
* - CEX: dollars per 1 day of time decay
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* stateWeighted:
|
|
17
|
+
* - Gamma: same as canonical (spot is already the state variable)
|
|
18
|
+
* - Vanna: canonical vanna weighted by strike IV level
|
|
19
|
+
* - Charm: canonical charm weighted by days-to-expiration
|
|
20
|
+
*
|
|
21
|
+
* flowDelta:
|
|
22
|
+
* - Canonical exposure formulas using OI deltas:
|
|
23
|
+
* (liveOpenInterest - openInterest)
|
|
19
24
|
*/
|
|
20
|
-
function calculateGammaVannaCharmExposures(chain, ivSurfaces) {
|
|
21
|
-
const { spot, riskFreeRate, dividendYield, options } = chain;
|
|
25
|
+
function calculateGammaVannaCharmExposures(chain, ivSurfaces, options = {}) {
|
|
26
|
+
const { spot, riskFreeRate, dividendYield, options: chainOptions } = chain;
|
|
27
|
+
const asOfTimestamp = options.asOfTimestamp ?? Date.now();
|
|
22
28
|
const exposureRows = [];
|
|
23
|
-
// Get unique expirations from options
|
|
24
29
|
const expirationsSet = new Set();
|
|
25
|
-
for (const option of
|
|
30
|
+
for (const option of chainOptions) {
|
|
26
31
|
expirationsSet.add(option.expirationTimestamp);
|
|
27
32
|
}
|
|
28
33
|
const expirations = Array.from(expirationsSet).sort((a, b) => a - b);
|
|
29
|
-
|
|
34
|
+
const putOptionsByKey = new Map();
|
|
35
|
+
for (const option of chainOptions) {
|
|
36
|
+
if (option.optionType === 'put') {
|
|
37
|
+
putOptionsByKey.set(getOptionKey(option.expirationTimestamp, option.strike), option);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
30
40
|
for (const expiration of expirations) {
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
if (expiration < asOfTimestamp) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const timeToExpirationInYears = (expiration - asOfTimestamp) / types_1.MILLISECONDS_PER_YEAR;
|
|
45
|
+
if (timeToExpirationInYears <= 0) {
|
|
33
46
|
continue;
|
|
34
47
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let strikeOfMaxGamma = 0.0;
|
|
40
|
-
let strikeOfMinGamma = 0.0;
|
|
41
|
-
let strikeOfMaxVanna = 0.0;
|
|
42
|
-
let strikeOfMinVanna = 0.0;
|
|
43
|
-
let strikeOfMaxCharm = 0.0;
|
|
44
|
-
let strikeOfMinCharm = 0.0;
|
|
45
|
-
let strikeOfMaxNet = 0.0;
|
|
46
|
-
let strikeOfMinNet = 0.0;
|
|
47
|
-
const strikeExposures = [];
|
|
48
|
-
// Process all call options first
|
|
49
|
-
for (const callOption of options) {
|
|
50
|
-
// Check if this option is at the expiration we are looking at
|
|
51
|
-
if (callOption.expirationTimestamp !== expiration || callOption.optionType === 'put') {
|
|
48
|
+
const timeToExpirationInDays = Math.max(timeToExpirationInYears * types_1.DAYS_PER_YEAR, 0);
|
|
49
|
+
const strikeExposureVariants = [];
|
|
50
|
+
for (const callOption of chainOptions) {
|
|
51
|
+
if (callOption.expirationTimestamp !== expiration || callOption.optionType !== 'call') {
|
|
52
52
|
continue;
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
const putOption = options.find((opt) => opt.expirationTimestamp === expiration &&
|
|
56
|
-
opt.optionType === 'put' &&
|
|
57
|
-
opt.strike === callOption.strike);
|
|
54
|
+
const putOption = putOptionsByKey.get(getOptionKey(expiration, callOption.strike));
|
|
58
55
|
if (!putOption) {
|
|
59
|
-
continue;
|
|
56
|
+
continue;
|
|
60
57
|
}
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
const putIVAtStrike = (0, volatility_1.getIVForStrike)(ivSurfaces, expiration, 'put', putOption.strike);
|
|
64
|
-
// Get time to expiration in years
|
|
65
|
-
const timeToExpirationInYears = (0, blackscholes_1.getTimeToExpirationInYears)(expiration);
|
|
66
|
-
// Calculate Greeks for both call and put
|
|
67
|
-
// Rates are already decimals in OptionChain, IV from surface is percentage
|
|
58
|
+
const callIVAtStrike = resolveIVPercent((0, volatility_1.getIVForStrike)(ivSurfaces, expiration, 'call', callOption.strike), callOption.impliedVolatility);
|
|
59
|
+
const putIVAtStrike = resolveIVPercent((0, volatility_1.getIVForStrike)(ivSurfaces, expiration, 'put', putOption.strike), putOption.impliedVolatility);
|
|
68
60
|
const callGreeks = (0, blackscholes_1.calculateGreeks)({
|
|
69
61
|
spot,
|
|
70
62
|
strike: callOption.strike,
|
|
71
63
|
timeToExpiry: timeToExpirationInYears,
|
|
72
|
-
volatility: callIVAtStrike / 100.0,
|
|
64
|
+
volatility: callIVAtStrike / 100.0,
|
|
73
65
|
riskFreeRate,
|
|
74
66
|
dividendYield,
|
|
75
67
|
optionType: 'call',
|
|
@@ -83,74 +75,33 @@ function calculateGammaVannaCharmExposures(chain, ivSurfaces) {
|
|
|
83
75
|
dividendYield,
|
|
84
76
|
optionType: 'put',
|
|
85
77
|
});
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const vannaExposureForStrike = -callOption.openInterest * callGreeks.vanna * (spot * 100.0) * callIVAtStrike * 0.01 +
|
|
95
|
-
putOption.openInterest * putGreeks.vanna * (spot * 100.0) * putIVAtStrike * 0.01;
|
|
96
|
-
// Charm: second order with respect to price and time
|
|
97
|
-
// Already normalized per day in calculateGreeks
|
|
98
|
-
const charmExposureForStrike = -callOption.openInterest * callGreeks.charm * (spot * 100.0) * types_1.DAYS_PER_YEAR * timeToExpirationInYears +
|
|
99
|
-
putOption.openInterest * putGreeks.charm * (spot * 100.0) * types_1.DAYS_PER_YEAR * timeToExpirationInYears;
|
|
100
|
-
// NaN checks
|
|
101
|
-
const gammaExposure = isNaN(gammaExposureForStrike) ? 0.0 : gammaExposureForStrike;
|
|
102
|
-
const vannaExposure = isNaN(vannaExposureForStrike) ? 0.0 : vannaExposureForStrike;
|
|
103
|
-
const charmExposure = isNaN(charmExposureForStrike) ? 0.0 : charmExposureForStrike;
|
|
104
|
-
// Add to totals
|
|
105
|
-
totalGammaExposure += gammaExposure;
|
|
106
|
-
totalVannaExposure += vannaExposure;
|
|
107
|
-
totalCharmExposure += charmExposure;
|
|
108
|
-
// Add to strike exposures
|
|
109
|
-
strikeExposures.push({
|
|
78
|
+
const callOpenInterest = sanitizeFinite(callOption.openInterest);
|
|
79
|
+
const putOpenInterest = sanitizeFinite(putOption.openInterest);
|
|
80
|
+
const canonical = calculateCanonicalVector(spot, callOpenInterest, putOpenInterest, callGreeks.gamma, putGreeks.gamma, callGreeks.vanna, putGreeks.vanna, callGreeks.charm, putGreeks.charm);
|
|
81
|
+
const stateWeighted = calculateStateWeightedVector(spot, callOpenInterest, putOpenInterest, callGreeks.vanna, putGreeks.vanna, callGreeks.charm, putGreeks.charm, callIVAtStrike, putIVAtStrike, timeToExpirationInDays, canonical.gammaExposure);
|
|
82
|
+
const callFlowDelta = resolveFlowDeltaOpenInterest(callOption.openInterest, callOption.liveOpenInterest);
|
|
83
|
+
const putFlowDelta = resolveFlowDeltaOpenInterest(putOption.openInterest, putOption.liveOpenInterest);
|
|
84
|
+
const flowDelta = calculateCanonicalVector(spot, callFlowDelta, putFlowDelta, callGreeks.gamma, putGreeks.gamma, callGreeks.vanna, putGreeks.vanna, callGreeks.charm, putGreeks.charm);
|
|
85
|
+
strikeExposureVariants.push({
|
|
110
86
|
strikePrice: callOption.strike,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
netExposure: gammaExposure + vannaExposure + charmExposure,
|
|
87
|
+
canonical,
|
|
88
|
+
stateWeighted,
|
|
89
|
+
flowDelta,
|
|
115
90
|
});
|
|
116
91
|
}
|
|
117
|
-
if (
|
|
118
|
-
continue;
|
|
92
|
+
if (strikeExposureVariants.length === 0) {
|
|
93
|
+
continue;
|
|
119
94
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
strikeOfMinGamma = strikeExposures[strikeExposures.length - 1].strikePrice;
|
|
124
|
-
// Sort by vanna exposure and find extremes
|
|
125
|
-
strikeExposures.sort((a, b) => b.vannaExposure - a.vannaExposure);
|
|
126
|
-
strikeOfMaxVanna = strikeExposures[0].strikePrice;
|
|
127
|
-
strikeOfMinVanna = strikeExposures[strikeExposures.length - 1].strikePrice;
|
|
128
|
-
// Sort by charm exposure and find extremes
|
|
129
|
-
strikeExposures.sort((a, b) => b.charmExposure - a.charmExposure);
|
|
130
|
-
strikeOfMaxCharm = strikeExposures[0].strikePrice;
|
|
131
|
-
strikeOfMinCharm = strikeExposures[strikeExposures.length - 1].strikePrice;
|
|
132
|
-
// Sort by net exposure and find extremes
|
|
133
|
-
strikeExposures.sort((a, b) => b.netExposure - a.netExposure);
|
|
134
|
-
strikeOfMaxNet = strikeExposures[0].strikePrice;
|
|
135
|
-
strikeOfMinNet = strikeExposures[strikeExposures.length - 1].strikePrice;
|
|
136
|
-
const totalNetExposure = totalGammaExposure + totalVannaExposure + totalCharmExposure;
|
|
137
|
-
// Add exposure row
|
|
95
|
+
const canonical = buildModeBreakdown(strikeExposureVariants, 'canonical');
|
|
96
|
+
const stateWeighted = buildModeBreakdown(strikeExposureVariants, 'stateWeighted');
|
|
97
|
+
const flowDelta = buildModeBreakdown(strikeExposureVariants, 'flowDelta');
|
|
138
98
|
exposureRows.push({
|
|
139
99
|
spotPrice: spot,
|
|
140
100
|
expiration,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
strikeOfMaxGamma,
|
|
146
|
-
strikeOfMinGamma,
|
|
147
|
-
strikeOfMaxVanna,
|
|
148
|
-
strikeOfMinVanna,
|
|
149
|
-
strikeOfMaxCharm,
|
|
150
|
-
strikeOfMinCharm,
|
|
151
|
-
strikeOfMaxNet,
|
|
152
|
-
strikeOfMinNet,
|
|
153
|
-
strikeExposures
|
|
101
|
+
canonical,
|
|
102
|
+
stateWeighted,
|
|
103
|
+
flowDelta,
|
|
104
|
+
strikeExposureVariants,
|
|
154
105
|
});
|
|
155
106
|
}
|
|
156
107
|
return exposureRows;
|
|
@@ -210,3 +161,110 @@ function calculateSharesNeededToCover(sharesOutstanding, totalNetExposure, under
|
|
|
210
161
|
resultingSpotToCover: resultingPrice,
|
|
211
162
|
};
|
|
212
163
|
}
|
|
164
|
+
function getOptionKey(expiration, strike) {
|
|
165
|
+
return `${expiration}:${strike}`;
|
|
166
|
+
}
|
|
167
|
+
function calculateCanonicalVector(spot, callPosition, putPosition, callGamma, putGamma, callVanna, putVanna, callCharm, putCharm) {
|
|
168
|
+
const gammaExposure = -callPosition * callGamma * (spot * 100.0) * spot * 0.01 +
|
|
169
|
+
putPosition * putGamma * (spot * 100.0) * spot * 0.01;
|
|
170
|
+
const vannaExposure = -callPosition * callVanna * (spot * 100.0) * 0.01 +
|
|
171
|
+
putPosition * putVanna * (spot * 100.0) * 0.01;
|
|
172
|
+
const charmExposure = -callPosition * callCharm * (spot * 100.0) +
|
|
173
|
+
putPosition * putCharm * (spot * 100.0);
|
|
174
|
+
return sanitizeVector({
|
|
175
|
+
gammaExposure,
|
|
176
|
+
vannaExposure,
|
|
177
|
+
charmExposure,
|
|
178
|
+
netExposure: gammaExposure + vannaExposure + charmExposure,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function calculateStateWeightedVector(spot, callPosition, putPosition, callVanna, putVanna, callCharm, putCharm, callIVPercent, putIVPercent, timeToExpirationInDays, canonicalGammaExposure) {
|
|
182
|
+
const callIVLevel = Math.max(callIVPercent * 0.01, 0);
|
|
183
|
+
const putIVLevel = Math.max(putIVPercent * 0.01, 0);
|
|
184
|
+
// Gamma already uses instantaneous price scaling in canonical GEX.
|
|
185
|
+
const gammaExposure = canonicalGammaExposure;
|
|
186
|
+
const vannaExposure = -callPosition * callVanna * (spot * 100.0) * 0.01 * callIVLevel +
|
|
187
|
+
putPosition * putVanna * (spot * 100.0) * 0.01 * putIVLevel;
|
|
188
|
+
const canonicalCharmComponent = -callPosition * callCharm * (spot * 100.0) +
|
|
189
|
+
putPosition * putCharm * (spot * 100.0);
|
|
190
|
+
const charmExposure = canonicalCharmComponent * Math.max(timeToExpirationInDays, 0);
|
|
191
|
+
return sanitizeVector({
|
|
192
|
+
gammaExposure,
|
|
193
|
+
vannaExposure,
|
|
194
|
+
charmExposure,
|
|
195
|
+
netExposure: gammaExposure + vannaExposure + charmExposure,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function resolveFlowDeltaOpenInterest(openInterest, liveOpenInterest) {
|
|
199
|
+
if (typeof liveOpenInterest !== 'number' || !isFinite(liveOpenInterest)) {
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
return sanitizeFinite(liveOpenInterest - openInterest);
|
|
203
|
+
}
|
|
204
|
+
function resolveIVPercent(ivFromSurface, optionImpliedVolatilityDecimal) {
|
|
205
|
+
if (isFinite(ivFromSurface) && ivFromSurface > 0) {
|
|
206
|
+
return ivFromSurface;
|
|
207
|
+
}
|
|
208
|
+
const fallback = optionImpliedVolatilityDecimal * 100.0;
|
|
209
|
+
if (isFinite(fallback) && fallback > 0) {
|
|
210
|
+
return fallback;
|
|
211
|
+
}
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
function buildModeBreakdown(strikeExposureVariants, mode) {
|
|
215
|
+
const strikeExposures = strikeExposureVariants.map((strike) => ({
|
|
216
|
+
strikePrice: strike.strikePrice,
|
|
217
|
+
...strike[mode],
|
|
218
|
+
}));
|
|
219
|
+
if (strikeExposures.length === 0) {
|
|
220
|
+
return {
|
|
221
|
+
totalGammaExposure: 0,
|
|
222
|
+
totalVannaExposure: 0,
|
|
223
|
+
totalCharmExposure: 0,
|
|
224
|
+
totalNetExposure: 0,
|
|
225
|
+
strikeOfMaxGamma: 0,
|
|
226
|
+
strikeOfMinGamma: 0,
|
|
227
|
+
strikeOfMaxVanna: 0,
|
|
228
|
+
strikeOfMinVanna: 0,
|
|
229
|
+
strikeOfMaxCharm: 0,
|
|
230
|
+
strikeOfMinCharm: 0,
|
|
231
|
+
strikeOfMaxNet: 0,
|
|
232
|
+
strikeOfMinNet: 0,
|
|
233
|
+
strikeExposures: [],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const totalGammaExposure = strikeExposures.reduce((sum, s) => sum + s.gammaExposure, 0);
|
|
237
|
+
const totalVannaExposure = strikeExposures.reduce((sum, s) => sum + s.vannaExposure, 0);
|
|
238
|
+
const totalCharmExposure = strikeExposures.reduce((sum, s) => sum + s.charmExposure, 0);
|
|
239
|
+
const totalNetExposure = totalGammaExposure + totalVannaExposure + totalCharmExposure;
|
|
240
|
+
const byGamma = [...strikeExposures].sort((a, b) => b.gammaExposure - a.gammaExposure);
|
|
241
|
+
const byVanna = [...strikeExposures].sort((a, b) => b.vannaExposure - a.vannaExposure);
|
|
242
|
+
const byCharm = [...strikeExposures].sort((a, b) => b.charmExposure - a.charmExposure);
|
|
243
|
+
const byNet = [...strikeExposures].sort((a, b) => b.netExposure - a.netExposure);
|
|
244
|
+
return {
|
|
245
|
+
totalGammaExposure: sanitizeFinite(totalGammaExposure),
|
|
246
|
+
totalVannaExposure: sanitizeFinite(totalVannaExposure),
|
|
247
|
+
totalCharmExposure: sanitizeFinite(totalCharmExposure),
|
|
248
|
+
totalNetExposure: sanitizeFinite(totalNetExposure),
|
|
249
|
+
strikeOfMaxGamma: byGamma[0].strikePrice,
|
|
250
|
+
strikeOfMinGamma: byGamma[byGamma.length - 1].strikePrice,
|
|
251
|
+
strikeOfMaxVanna: byVanna[0].strikePrice,
|
|
252
|
+
strikeOfMinVanna: byVanna[byVanna.length - 1].strikePrice,
|
|
253
|
+
strikeOfMaxCharm: byCharm[0].strikePrice,
|
|
254
|
+
strikeOfMinCharm: byCharm[byCharm.length - 1].strikePrice,
|
|
255
|
+
strikeOfMaxNet: byNet[0].strikePrice,
|
|
256
|
+
strikeOfMinNet: byNet[byNet.length - 1].strikePrice,
|
|
257
|
+
strikeExposures: byNet,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function sanitizeVector(vector) {
|
|
261
|
+
return {
|
|
262
|
+
gammaExposure: sanitizeFinite(vector.gammaExposure),
|
|
263
|
+
vannaExposure: sanitizeFinite(vector.vannaExposure),
|
|
264
|
+
charmExposure: sanitizeFinite(vector.charmExposure),
|
|
265
|
+
netExposure: sanitizeFinite(vector.netExposure),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function sanitizeFinite(value) {
|
|
269
|
+
return isFinite(value) && !isNaN(value) ? value : 0;
|
|
270
|
+
}
|
|
@@ -114,7 +114,7 @@ export declare const OPEX_CONFIG: ExposureAdjustmentConfig;
|
|
|
114
114
|
* @param symbol - Underlying ticker symbol
|
|
115
115
|
* @param underlyingPrice - Current spot price
|
|
116
116
|
* @param callOptions - Call options for a single expiry
|
|
117
|
-
* @param exposures -
|
|
117
|
+
* @param exposures - Canonical exposure metrics from calculateGammaVannaCharmExposures()
|
|
118
118
|
* @param config - Adjustment configuration (uses defaults if not provided)
|
|
119
119
|
* @returns Baseline and adjusted PDFs with comparison metrics
|
|
120
120
|
*
|
|
@@ -122,7 +122,10 @@ export declare const OPEX_CONFIG: ExposureAdjustmentConfig;
|
|
|
122
122
|
* ```typescript
|
|
123
123
|
* // Get exposures first
|
|
124
124
|
* const allExposures = calculateGammaVannaCharmExposures(chain, ivSurfaces);
|
|
125
|
-
* const
|
|
125
|
+
* const expiry = allExposures.find(e => e.expiration === targetExpiry);
|
|
126
|
+
* const expiryExposures = expiry
|
|
127
|
+
* ? { spotPrice: expiry.spotPrice, expiration: expiry.expiration, ...expiry.canonical }
|
|
128
|
+
* : undefined;
|
|
126
129
|
*
|
|
127
130
|
* // Calculate adjusted PDF
|
|
128
131
|
* const result = estimateExposureAdjustedPDF(
|
|
@@ -122,7 +122,7 @@ exports.OPEX_CONFIG = {
|
|
|
122
122
|
* @param symbol - Underlying ticker symbol
|
|
123
123
|
* @param underlyingPrice - Current spot price
|
|
124
124
|
* @param callOptions - Call options for a single expiry
|
|
125
|
-
* @param exposures -
|
|
125
|
+
* @param exposures - Canonical exposure metrics from calculateGammaVannaCharmExposures()
|
|
126
126
|
* @param config - Adjustment configuration (uses defaults if not provided)
|
|
127
127
|
* @returns Baseline and adjusted PDFs with comparison metrics
|
|
128
128
|
*
|
|
@@ -130,7 +130,10 @@ exports.OPEX_CONFIG = {
|
|
|
130
130
|
* ```typescript
|
|
131
131
|
* // Get exposures first
|
|
132
132
|
* const allExposures = calculateGammaVannaCharmExposures(chain, ivSurfaces);
|
|
133
|
-
* const
|
|
133
|
+
* const expiry = allExposures.find(e => e.expiration === targetExpiry);
|
|
134
|
+
* const expiryExposures = expiry
|
|
135
|
+
* ? { spotPrice: expiry.spotPrice, expiration: expiry.expiration, ...expiry.canonical }
|
|
136
|
+
* : undefined;
|
|
134
137
|
*
|
|
135
138
|
* // Calculate adjusted PDF
|
|
136
139
|
* const result = estimateExposureAdjustedPDF(
|
package/dist/types/index.d.ts
CHANGED
|
@@ -172,6 +172,52 @@ export interface StrikeExposure {
|
|
|
172
172
|
/** Net exposure (gamma + vanna + charm) */
|
|
173
173
|
netExposure: number;
|
|
174
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Input options for exposure calculations.
|
|
177
|
+
*/
|
|
178
|
+
export interface ExposureCalculationOptions {
|
|
179
|
+
/**
|
|
180
|
+
* Reference timestamp in milliseconds.
|
|
181
|
+
* If omitted, Date.now() is used.
|
|
182
|
+
*/
|
|
183
|
+
asOfTimestamp?: number;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Exposure vector used by variant outputs.
|
|
187
|
+
*/
|
|
188
|
+
export interface ExposureVector {
|
|
189
|
+
gammaExposure: number;
|
|
190
|
+
vannaExposure: number;
|
|
191
|
+
charmExposure: number;
|
|
192
|
+
netExposure: number;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Per-strike exposure variants (canonical, state-weighted, and flow delta).
|
|
196
|
+
*/
|
|
197
|
+
export interface StrikeExposureVariants {
|
|
198
|
+
strikePrice: number;
|
|
199
|
+
canonical: ExposureVector;
|
|
200
|
+
stateWeighted: ExposureVector;
|
|
201
|
+
flowDelta: ExposureVector;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Full exposure breakdown for one mode.
|
|
205
|
+
*/
|
|
206
|
+
export interface ExposureModeBreakdown {
|
|
207
|
+
totalGammaExposure: number;
|
|
208
|
+
totalVannaExposure: number;
|
|
209
|
+
totalCharmExposure: number;
|
|
210
|
+
totalNetExposure: number;
|
|
211
|
+
strikeOfMaxGamma: number;
|
|
212
|
+
strikeOfMinGamma: number;
|
|
213
|
+
strikeOfMaxVanna: number;
|
|
214
|
+
strikeOfMinVanna: number;
|
|
215
|
+
strikeOfMaxCharm: number;
|
|
216
|
+
strikeOfMinCharm: number;
|
|
217
|
+
strikeOfMaxNet: number;
|
|
218
|
+
strikeOfMinNet: number;
|
|
219
|
+
strikeExposures: StrikeExposure[];
|
|
220
|
+
}
|
|
175
221
|
/**
|
|
176
222
|
* Exposure metrics per expiration
|
|
177
223
|
*/
|
|
@@ -207,6 +253,26 @@ export interface ExposurePerExpiry {
|
|
|
207
253
|
/** Per-strike exposures */
|
|
208
254
|
strikeExposures: StrikeExposure[];
|
|
209
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Exposure variants per expiration.
|
|
258
|
+
* canonical: classic per-1% / per-vol-point / per-day definitions
|
|
259
|
+
* stateWeighted: level-weighted vanna/charm (gamma remains canonical)
|
|
260
|
+
* flowDelta: exposure deltas from intraday OI changes (liveOpenInterest - openInterest)
|
|
261
|
+
*/
|
|
262
|
+
export interface ExposureVariantsPerExpiry {
|
|
263
|
+
/** Current spot price */
|
|
264
|
+
spotPrice: number;
|
|
265
|
+
/** Expiration timestamp in milliseconds */
|
|
266
|
+
expiration: number;
|
|
267
|
+
/** Canonical exposure mode */
|
|
268
|
+
canonical: ExposureModeBreakdown;
|
|
269
|
+
/** State-weighted exposure mode */
|
|
270
|
+
stateWeighted: ExposureModeBreakdown;
|
|
271
|
+
/** Flow-delta exposure mode */
|
|
272
|
+
flowDelta: ExposureModeBreakdown;
|
|
273
|
+
/** Per-strike variants */
|
|
274
|
+
strikeExposureVariants: StrikeExposureVariants[];
|
|
275
|
+
}
|
|
210
276
|
/**
|
|
211
277
|
* Dealer Gamma Exposure (GEX) metrics
|
|
212
278
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fullstackcraftllc/floe",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "Production-ready options analytics toolkit. Normalize broker data structures and calculate Black-Scholes, Greeks, and exposures with a clean, type-safe API. Built for trading platforms and fintech applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|