@fullstackcraftllc/floe 0.0.1
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/LICENSE-COMMERCIAL +74 -0
- package/LICENSE-MIT +27 -0
- package/LICENSE.md +65 -0
- package/README.md +272 -0
- package/dist/adapters/index.d.ts +52 -0
- package/dist/adapters/index.js +132 -0
- package/dist/blackscholes/index.d.ts +73 -0
- package/dist/blackscholes/index.js +266 -0
- package/dist/exposure/index.d.ts +35 -0
- package/dist/exposure/index.js +212 -0
- package/dist/greeks/index.d.ts +29 -0
- package/dist/greeks/index.js +107 -0
- package/dist/impliedpdf/index.d.ts +0 -0
- package/dist/impliedpdf/index.js +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +56 -0
- package/dist/types/index.d.ts +228 -0
- package/dist/types/index.js +14 -0
- package/dist/utils/statistics.d.ts +18 -0
- package/dist/utils/statistics.js +32 -0
- package/dist/volatility/index.d.ts +37 -0
- package/dist/volatility/index.js +163 -0
- package/dist/volatility/smoothing.d.ts +17 -0
- package/dist/volatility/smoothing.js +171 -0
- package/package.json +55 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.blackScholes = blackScholes;
|
|
4
|
+
exports.calculateGreeks = calculateGreeks;
|
|
5
|
+
exports.calculateImpliedVolatility = calculateImpliedVolatility;
|
|
6
|
+
exports.getMillisecondsToExpiration = getMillisecondsToExpiration;
|
|
7
|
+
exports.getTimeToExpirationInYears = getTimeToExpirationInYears;
|
|
8
|
+
const types_1 = require("../types");
|
|
9
|
+
const statistics_1 = require("../utils/statistics");
|
|
10
|
+
/**
|
|
11
|
+
* Calculate option price using Black-Scholes model
|
|
12
|
+
*
|
|
13
|
+
* @param params - Black-Scholes parameters
|
|
14
|
+
* @returns Option price
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const price = blackScholes({
|
|
19
|
+
* spot: 100,
|
|
20
|
+
* strike: 105,
|
|
21
|
+
* timeToExpiry: 0.25,
|
|
22
|
+
* volatility: 0.20,
|
|
23
|
+
* riskFreeRate: 0.05,
|
|
24
|
+
* optionType: 'call'
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function blackScholes(params) {
|
|
29
|
+
const greeks = calculateGreeks(params);
|
|
30
|
+
return greeks.price;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Calculate complete option Greeks using Black-Scholes-Merton model
|
|
34
|
+
* Includes all first, second, and third-order Greeks
|
|
35
|
+
*
|
|
36
|
+
* @param params - Black-Scholes parameters
|
|
37
|
+
* @returns Complete Greeks including price
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const greeks = calculateGreeks({
|
|
42
|
+
* spot: 100,
|
|
43
|
+
* strike: 105,
|
|
44
|
+
* timeToExpiry: 0.25,
|
|
45
|
+
* volatility: 0.20,
|
|
46
|
+
* riskFreeRate: 0.05,
|
|
47
|
+
* optionType: 'call'
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function calculateGreeks(params) {
|
|
52
|
+
const { spot: S, strike: K, timeToExpiry: t, volatility: vol, riskFreeRate: r, optionType, dividendYield: q = 0, } = params;
|
|
53
|
+
// Safety checks
|
|
54
|
+
if (t < 0) {
|
|
55
|
+
return createZeroGreeks();
|
|
56
|
+
}
|
|
57
|
+
if (vol <= 0 || S <= 0 || t <= 0) {
|
|
58
|
+
return createZeroGreeks();
|
|
59
|
+
}
|
|
60
|
+
// Calculate d1 and d2
|
|
61
|
+
const sqrtT = Math.sqrt(t);
|
|
62
|
+
const d1 = (Math.log(S / K) + (r - q + (vol * vol) / 2) * t) / (vol * sqrtT);
|
|
63
|
+
const d2 = d1 - vol * sqrtT;
|
|
64
|
+
// Calculate probability functions
|
|
65
|
+
const nd1 = (0, statistics_1.normalPDF)(d1);
|
|
66
|
+
const Nd1 = (0, statistics_1.cumulativeNormalDistribution)(d1);
|
|
67
|
+
const Nd2 = (0, statistics_1.cumulativeNormalDistribution)(d2);
|
|
68
|
+
const eqt = Math.exp(-q * t);
|
|
69
|
+
const ert = Math.exp(-r * t);
|
|
70
|
+
if (optionType === 'call') {
|
|
71
|
+
return calculateCallGreeks(S, K, r, q, t, vol, d1, d2, nd1, Nd1, Nd2, eqt, ert);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return calculatePutGreeks(S, K, r, q, t, vol, d1, d2, nd1, Nd1, Nd2, eqt, ert);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Calculate all Greeks for a call option
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
function calculateCallGreeks(S, K, r, q, t, vol, d1, d2, nd1, Nd1, Nd2, eqt, ert) {
|
|
82
|
+
const sqrtT = Math.sqrt(t);
|
|
83
|
+
// Price
|
|
84
|
+
const price = S * eqt * Nd1 - K * ert * Nd2;
|
|
85
|
+
// First-order Greeks
|
|
86
|
+
const delta = eqt * Nd1;
|
|
87
|
+
const gamma = (eqt * nd1) / (S * vol * sqrtT);
|
|
88
|
+
const theta = -(S * vol * eqt * nd1) / (2 * sqrtT) - r * K * ert * Nd2 + q * S * eqt * Nd1;
|
|
89
|
+
const vega = S * eqt * sqrtT * nd1;
|
|
90
|
+
const rho = K * t * ert * Nd2;
|
|
91
|
+
// Second-order Greeks
|
|
92
|
+
const vanna = -eqt * nd1 * (d2 / vol);
|
|
93
|
+
const charm = -q * eqt * Nd1 - (eqt * nd1 * (2 * (r - q) * t - d2 * vol * sqrtT)) / (2 * t * vol * sqrtT);
|
|
94
|
+
const volga = vega * ((d1 * d2) / (S * vol));
|
|
95
|
+
const speed = nd1 / (S * vol);
|
|
96
|
+
const zomma = (nd1 * d1) / (S * vol * vol);
|
|
97
|
+
// Third-order Greeks
|
|
98
|
+
const color = -(d1 * d2 * nd1) / (vol * vol);
|
|
99
|
+
const ultima = (d1 * d2 * d2 * nd1) / (vol * vol * vol);
|
|
100
|
+
return {
|
|
101
|
+
price: round(price, 2),
|
|
102
|
+
delta: round(delta, 5),
|
|
103
|
+
gamma: round(gamma, 5),
|
|
104
|
+
theta: round(theta / types_1.DAYS_PER_YEAR, 5), // Per day
|
|
105
|
+
vega: round(vega * 0.01, 5), // Per 1% change in volatility
|
|
106
|
+
rho: round(rho * 0.01, 5), // Per 1% change in interest rate
|
|
107
|
+
charm: round(charm / types_1.DAYS_PER_YEAR, 5), // Per day
|
|
108
|
+
vanna: round(vanna, 5),
|
|
109
|
+
volga: round(volga, 5),
|
|
110
|
+
speed: round(speed, 5),
|
|
111
|
+
zomma: round(zomma, 5),
|
|
112
|
+
color: round(color, 5),
|
|
113
|
+
ultima: round(ultima, 5),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Calculate all Greeks for a put option
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
120
|
+
function calculatePutGreeks(S, K, r, q, t, vol, d1, d2, nd1, Nd1, Nd2, eqt, ert) {
|
|
121
|
+
const sqrtT = Math.sqrt(t);
|
|
122
|
+
// Price
|
|
123
|
+
const price = K * ert * (0, statistics_1.cumulativeNormalDistribution)(-d2) - S * eqt * (0, statistics_1.cumulativeNormalDistribution)(-d1);
|
|
124
|
+
// First-order Greeks
|
|
125
|
+
const delta = -eqt * (0, statistics_1.cumulativeNormalDistribution)(-d1);
|
|
126
|
+
const gamma = (eqt * nd1) / (S * vol * sqrtT); // Same as call
|
|
127
|
+
const theta = -(S * vol * eqt * nd1) / (2 * sqrtT) + r * K * ert * (0, statistics_1.cumulativeNormalDistribution)(-d2) - q * S * eqt * (0, statistics_1.cumulativeNormalDistribution)(-d1);
|
|
128
|
+
const vega = S * eqt * sqrtT * nd1; // Same as call
|
|
129
|
+
const rho = -K * t * ert * (0, statistics_1.cumulativeNormalDistribution)(-d2);
|
|
130
|
+
// Second-order Greeks (same as call for gamma, vega, vanna)
|
|
131
|
+
const vanna = -eqt * nd1 * (d2 / vol); // Same as call
|
|
132
|
+
const charm = -q * eqt * (0, statistics_1.cumulativeNormalDistribution)(-d1) - (eqt * nd1 * (2 * (r - q) * t - d2 * vol * sqrtT)) / (2 * t * vol * sqrtT);
|
|
133
|
+
const volga = vega * ((d1 * d2) / (S * vol)); // Same as call
|
|
134
|
+
const speed = (nd1 * d1 * d1) / vol;
|
|
135
|
+
const zomma = ((1 + d1 * d2) * nd1) / (vol * vol * sqrtT);
|
|
136
|
+
// Third-order Greeks
|
|
137
|
+
const color = ((1 - d1 * d2) * nd1) / S;
|
|
138
|
+
const ultima = (t * S * nd1 * d1 * d1) / vol;
|
|
139
|
+
return {
|
|
140
|
+
price: round(price, 2),
|
|
141
|
+
delta: round(delta, 5),
|
|
142
|
+
gamma: round(gamma, 5),
|
|
143
|
+
theta: round(theta / types_1.DAYS_PER_YEAR, 5), // Per day
|
|
144
|
+
vega: round(vega * 0.01, 5), // Per 1% change in volatility
|
|
145
|
+
rho: round(rho * 0.01, 5), // Per 1% change in interest rate
|
|
146
|
+
charm: round(charm / types_1.DAYS_PER_YEAR, 5), // Per day
|
|
147
|
+
vanna: round(vanna, 5),
|
|
148
|
+
volga: round(volga, 5),
|
|
149
|
+
speed: round(speed, 5),
|
|
150
|
+
zomma: round(zomma, 5),
|
|
151
|
+
color: round(color, 5),
|
|
152
|
+
ultima: round(ultima, 5),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Calculate implied volatility using bisection method
|
|
157
|
+
*
|
|
158
|
+
* @param price - Observed option price
|
|
159
|
+
* @param spot - Current spot price
|
|
160
|
+
* @param strike - Strike price
|
|
161
|
+
* @param riskFreeRate - Risk-free interest rate (as decimal)
|
|
162
|
+
* @param dividendYield - Dividend yield (as decimal)
|
|
163
|
+
* @param timeToExpiry - Time to expiration in years
|
|
164
|
+
* @param optionType - 'call' or 'put'
|
|
165
|
+
* @returns Implied volatility as a percentage (e.g., 20.0 for 20%)
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* const iv = calculateImpliedVolatility(5.50, 100, 105, 0.05, 0, 0.25, 'call');
|
|
170
|
+
* console.log(`IV: ${iv}%`);
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
function calculateImpliedVolatility(price, spot, strike, riskFreeRate, dividendYield, timeToExpiry, optionType) {
|
|
174
|
+
// Sanity checks
|
|
175
|
+
if (price <= 0 || spot <= 0 || strike <= 0 || timeToExpiry <= 0) {
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
// Calculate intrinsic value
|
|
179
|
+
const intrinsic = optionType === 'call'
|
|
180
|
+
? Math.max(0, spot * Math.exp(-dividendYield * timeToExpiry) - strike * Math.exp(-riskFreeRate * timeToExpiry))
|
|
181
|
+
: Math.max(0, strike * Math.exp(-riskFreeRate * timeToExpiry) - spot * Math.exp(-dividendYield * timeToExpiry));
|
|
182
|
+
// If price is at or below intrinsic value, return minimum IV
|
|
183
|
+
const extrinsic = price - intrinsic;
|
|
184
|
+
if (extrinsic <= 0.01) {
|
|
185
|
+
return 1.0; // 1% IV as floor
|
|
186
|
+
}
|
|
187
|
+
// Bisection search bounds (in decimal form)
|
|
188
|
+
let low = 0.0001;
|
|
189
|
+
let high = 5.0; // 500% volatility
|
|
190
|
+
let mid = 0;
|
|
191
|
+
// Bisection method
|
|
192
|
+
for (let i = 0; i < 100; i++) {
|
|
193
|
+
mid = 0.5 * (low + high);
|
|
194
|
+
const modelPrice = blackScholes({
|
|
195
|
+
spot,
|
|
196
|
+
strike,
|
|
197
|
+
timeToExpiry,
|
|
198
|
+
volatility: mid,
|
|
199
|
+
riskFreeRate,
|
|
200
|
+
dividendYield,
|
|
201
|
+
optionType,
|
|
202
|
+
});
|
|
203
|
+
const diff = modelPrice - price;
|
|
204
|
+
if (Math.abs(diff) < 1e-6) {
|
|
205
|
+
return mid * 100.0; // Return as percentage
|
|
206
|
+
}
|
|
207
|
+
if (diff > 0) {
|
|
208
|
+
// Model price too high → volatility too high
|
|
209
|
+
high = mid;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// Model price too low → volatility too low
|
|
213
|
+
low = mid;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Return midpoint as percentage
|
|
217
|
+
return 0.5 * (low + high) * 100.0;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get milliseconds until expiration
|
|
221
|
+
*
|
|
222
|
+
* @param expirationTimestamp - Expiration timestamp in milliseconds
|
|
223
|
+
* @returns Milliseconds until expiration
|
|
224
|
+
*/
|
|
225
|
+
function getMillisecondsToExpiration(expirationTimestamp) {
|
|
226
|
+
return expirationTimestamp - Date.now();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get time to expiration in years
|
|
230
|
+
*
|
|
231
|
+
* @param expirationTimestamp - Expiration timestamp in milliseconds
|
|
232
|
+
* @returns Time to expiration in years
|
|
233
|
+
*/
|
|
234
|
+
function getTimeToExpirationInYears(expirationTimestamp) {
|
|
235
|
+
const milliseconds = getMillisecondsToExpiration(expirationTimestamp);
|
|
236
|
+
return milliseconds / types_1.MILLISECONDS_PER_YEAR;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Helper: Round number to specified decimal places
|
|
240
|
+
* @internal
|
|
241
|
+
*/
|
|
242
|
+
function round(value, decimals) {
|
|
243
|
+
const factor = Math.pow(10, decimals);
|
|
244
|
+
return Math.round(value * factor) / factor;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Helper: Create zero Greeks object
|
|
248
|
+
* @internal
|
|
249
|
+
*/
|
|
250
|
+
function createZeroGreeks() {
|
|
251
|
+
return {
|
|
252
|
+
price: 0,
|
|
253
|
+
delta: 0,
|
|
254
|
+
gamma: 0,
|
|
255
|
+
theta: 0,
|
|
256
|
+
vega: 0,
|
|
257
|
+
rho: 0,
|
|
258
|
+
charm: 0,
|
|
259
|
+
vanna: 0,
|
|
260
|
+
volga: 0,
|
|
261
|
+
speed: 0,
|
|
262
|
+
zomma: 0,
|
|
263
|
+
color: 0,
|
|
264
|
+
ultima: 0,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { OptionChain, ExposurePerExpiry, IVSurface } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Calculate Gamma, Vanna, and Charm exposures for an option chain
|
|
4
|
+
*
|
|
5
|
+
* @param chain - Option chain with market context (spot, rates, options)
|
|
6
|
+
* @param ivSurfaces - IV surfaces for all expirations
|
|
7
|
+
* @returns Array of exposure metrics per expiration
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const exposures = calculateGammaVannaCharmExposures(chain, ivSurfaces);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function calculateGammaVannaCharmExposures(chain: OptionChain, ivSurfaces: IVSurface[]): ExposurePerExpiry[];
|
|
15
|
+
/**
|
|
16
|
+
* Calculate shares needed to cover net exposure
|
|
17
|
+
*
|
|
18
|
+
* @param sharesOutstanding - Total shares outstanding
|
|
19
|
+
* @param totalNetExposure - Total net exposure (gamma + vanna + charm)
|
|
20
|
+
* @param underlyingMark - Current spot price
|
|
21
|
+
* @returns Action to cover, shares to cover, implied move %, and resulting price
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const coverage = calculateSharesNeededToCover(1000000000, -5000000, 450.50);
|
|
26
|
+
* console.log(`Action: ${coverage.actionToCover}`);
|
|
27
|
+
* console.log(`Shares: ${coverage.sharesToCover}`);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function calculateSharesNeededToCover(sharesOutstanding: number, totalNetExposure: number, underlyingMark: number): {
|
|
31
|
+
actionToCover: string;
|
|
32
|
+
sharesToCover: number;
|
|
33
|
+
impliedMoveToCover: number;
|
|
34
|
+
resultingSpotToCover: number;
|
|
35
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateGammaVannaCharmExposures = calculateGammaVannaCharmExposures;
|
|
4
|
+
exports.calculateSharesNeededToCover = calculateSharesNeededToCover;
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const blackscholes_1 = require("../blackscholes");
|
|
7
|
+
const volatility_1 = require("../volatility");
|
|
8
|
+
/**
|
|
9
|
+
* Calculate Gamma, Vanna, and Charm exposures for an option chain
|
|
10
|
+
*
|
|
11
|
+
* @param chain - Option chain with market context (spot, rates, options)
|
|
12
|
+
* @param ivSurfaces - IV surfaces for all expirations
|
|
13
|
+
* @returns Array of exposure metrics per expiration
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const exposures = calculateGammaVannaCharmExposures(chain, ivSurfaces);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function calculateGammaVannaCharmExposures(chain, ivSurfaces) {
|
|
21
|
+
const { spot, riskFreeRate, dividendYield, options } = chain;
|
|
22
|
+
const exposureRows = [];
|
|
23
|
+
// Get unique expirations from options
|
|
24
|
+
const expirationsSet = new Set();
|
|
25
|
+
for (const option of options) {
|
|
26
|
+
expirationsSet.add(option.expirationTimestamp);
|
|
27
|
+
}
|
|
28
|
+
const expirations = Array.from(expirationsSet).sort((a, b) => a - b);
|
|
29
|
+
// Loop through all expirations
|
|
30
|
+
for (const expiration of expirations) {
|
|
31
|
+
// Skip any expiration that is in the past
|
|
32
|
+
if (expiration < Date.now()) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
// Reset totals for this expiration
|
|
36
|
+
let totalGammaExposure = 0.0;
|
|
37
|
+
let totalVannaExposure = 0.0;
|
|
38
|
+
let totalCharmExposure = 0.0;
|
|
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') {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
// Get the corresponding put option
|
|
55
|
+
const putOption = options.find((opt) => opt.expirationTimestamp === expiration &&
|
|
56
|
+
opt.optionType === 'put' &&
|
|
57
|
+
opt.strike === callOption.strike);
|
|
58
|
+
if (!putOption) {
|
|
59
|
+
continue; // Skip if no matching put
|
|
60
|
+
}
|
|
61
|
+
// Get IV for this strike and expiry from the surface
|
|
62
|
+
const callIVAtStrike = (0, volatility_1.getIVForStrike)(ivSurfaces, expiration, 'call', callOption.strike);
|
|
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
|
|
68
|
+
const callGreeks = (0, blackscholes_1.calculateGreeks)({
|
|
69
|
+
spot,
|
|
70
|
+
strike: callOption.strike,
|
|
71
|
+
timeToExpiry: timeToExpirationInYears,
|
|
72
|
+
volatility: callIVAtStrike / 100.0, // Convert from percentage to decimal
|
|
73
|
+
riskFreeRate,
|
|
74
|
+
dividendYield,
|
|
75
|
+
optionType: 'call',
|
|
76
|
+
});
|
|
77
|
+
const putGreeks = (0, blackscholes_1.calculateGreeks)({
|
|
78
|
+
spot,
|
|
79
|
+
strike: putOption.strike,
|
|
80
|
+
timeToExpiry: timeToExpirationInYears,
|
|
81
|
+
volatility: putIVAtStrike / 100.0,
|
|
82
|
+
riskFreeRate,
|
|
83
|
+
dividendYield,
|
|
84
|
+
optionType: 'put',
|
|
85
|
+
});
|
|
86
|
+
// Calculate exposures from dealer perspective
|
|
87
|
+
// Dealer is short calls (negative gamma) and long puts (positive gamma)
|
|
88
|
+
// Multiply by 100 for contract size, multiply by spot for dollar exposure
|
|
89
|
+
// Multiply by 0.01 for 1% move sensitivity
|
|
90
|
+
// Gamma: second order with respect to price twice
|
|
91
|
+
const gammaExposureForStrike = -callOption.openInterest * callGreeks.gamma * (spot * 100.0) * spot * 0.01 +
|
|
92
|
+
putOption.openInterest * putGreeks.gamma * (spot * 100.0) * spot * 0.01;
|
|
93
|
+
// Vanna: second order with respect to price and volatility
|
|
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({
|
|
110
|
+
strikePrice: callOption.strike,
|
|
111
|
+
gammaExposure,
|
|
112
|
+
vannaExposure,
|
|
113
|
+
charmExposure,
|
|
114
|
+
netExposure: gammaExposure + vannaExposure + charmExposure,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (strikeExposures.length === 0) {
|
|
118
|
+
continue; // No options for this expiration
|
|
119
|
+
}
|
|
120
|
+
// Sort by gamma exposure and find extremes
|
|
121
|
+
strikeExposures.sort((a, b) => b.gammaExposure - a.gammaExposure);
|
|
122
|
+
strikeOfMaxGamma = strikeExposures[0].strikePrice;
|
|
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
|
|
138
|
+
exposureRows.push({
|
|
139
|
+
spotPrice: spot,
|
|
140
|
+
expiration,
|
|
141
|
+
totalGammaExposure,
|
|
142
|
+
totalVannaExposure,
|
|
143
|
+
totalCharmExposure,
|
|
144
|
+
totalNetExposure,
|
|
145
|
+
strikeOfMaxGamma,
|
|
146
|
+
strikeOfMinGamma,
|
|
147
|
+
strikeOfMaxVanna,
|
|
148
|
+
strikeOfMinVanna,
|
|
149
|
+
strikeOfMaxCharm,
|
|
150
|
+
strikeOfMinCharm,
|
|
151
|
+
strikeOfMaxNet,
|
|
152
|
+
strikeOfMinNet,
|
|
153
|
+
strikeExposures
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return exposureRows;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Calculate shares needed to cover net exposure
|
|
160
|
+
*
|
|
161
|
+
* @param sharesOutstanding - Total shares outstanding
|
|
162
|
+
* @param totalNetExposure - Total net exposure (gamma + vanna + charm)
|
|
163
|
+
* @param underlyingMark - Current spot price
|
|
164
|
+
* @returns Action to cover, shares to cover, implied move %, and resulting price
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* const coverage = calculateSharesNeededToCover(1000000000, -5000000, 450.50);
|
|
169
|
+
* console.log(`Action: ${coverage.actionToCover}`);
|
|
170
|
+
* console.log(`Shares: ${coverage.sharesToCover}`);
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
function calculateSharesNeededToCover(sharesOutstanding, totalNetExposure, underlyingMark) {
|
|
174
|
+
let actionToCover = 'BUY';
|
|
175
|
+
if (totalNetExposure > 0) {
|
|
176
|
+
actionToCover = 'SELL';
|
|
177
|
+
}
|
|
178
|
+
// Protect from inf or nan
|
|
179
|
+
if (sharesOutstanding === 0 ||
|
|
180
|
+
isNaN(sharesOutstanding) ||
|
|
181
|
+
!isFinite(sharesOutstanding)) {
|
|
182
|
+
return {
|
|
183
|
+
actionToCover: '',
|
|
184
|
+
sharesToCover: 0,
|
|
185
|
+
impliedMoveToCover: 0,
|
|
186
|
+
resultingSpotToCover: underlyingMark,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// Protect against division by zero for underlyingMark
|
|
190
|
+
if (underlyingMark === 0 ||
|
|
191
|
+
isNaN(underlyingMark) ||
|
|
192
|
+
!isFinite(underlyingMark)) {
|
|
193
|
+
return {
|
|
194
|
+
actionToCover: '',
|
|
195
|
+
sharesToCover: 0,
|
|
196
|
+
impliedMoveToCover: 0,
|
|
197
|
+
resultingSpotToCover: underlyingMark,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Since this is the action the dealer makes, we negate it to get the implied move
|
|
201
|
+
// i.e. negative exposure means the dealer has to buy to cover, which implies upward pressure
|
|
202
|
+
// likewise positive exposure means the dealer has to sell to cover, which implies downward pressure
|
|
203
|
+
const sharesNeededToCoverFloat = -totalNetExposure / underlyingMark;
|
|
204
|
+
const impliedChange = (sharesNeededToCoverFloat / sharesOutstanding) * 100.0;
|
|
205
|
+
const resultingPrice = underlyingMark * (1 + impliedChange / 100);
|
|
206
|
+
return {
|
|
207
|
+
actionToCover,
|
|
208
|
+
sharesToCover: Math.abs(sharesNeededToCoverFloat),
|
|
209
|
+
impliedMoveToCover: impliedChange,
|
|
210
|
+
resultingSpotToCover: resultingPrice,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BlackScholesParams, Greeks } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Calculate all Greeks for an option
|
|
4
|
+
*
|
|
5
|
+
* @param params - Black-Scholes parameters
|
|
6
|
+
* @returns All Greeks (delta, gamma, theta, vega, rho)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const greeks = calculateGreeks({
|
|
11
|
+
* spot: 100,
|
|
12
|
+
* strike: 105,
|
|
13
|
+
* timeToExpiry: 0.25,
|
|
14
|
+
* volatility: 0.20,
|
|
15
|
+
* riskFreeRate: 0.05,
|
|
16
|
+
* optionType: 'call'
|
|
17
|
+
* });
|
|
18
|
+
* console.log('Delta:', greeks.delta);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function calculateGreeks(params: BlackScholesParams): Greeks;
|
|
22
|
+
/**
|
|
23
|
+
* Calculate delta only (optimized)
|
|
24
|
+
*/
|
|
25
|
+
export declare function calculateDelta(params: BlackScholesParams): number;
|
|
26
|
+
/**
|
|
27
|
+
* Calculate gamma only (optimized)
|
|
28
|
+
*/
|
|
29
|
+
export declare function calculateGamma(params: BlackScholesParams): number;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateGreeks = calculateGreeks;
|
|
4
|
+
exports.calculateDelta = calculateDelta;
|
|
5
|
+
exports.calculateGamma = calculateGamma;
|
|
6
|
+
const statistics_1 = require("../utils/statistics");
|
|
7
|
+
/**
|
|
8
|
+
* Calculate all Greeks for an option
|
|
9
|
+
*
|
|
10
|
+
* @param params - Black-Scholes parameters
|
|
11
|
+
* @returns All Greeks (delta, gamma, theta, vega, rho)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const greeks = calculateGreeks({
|
|
16
|
+
* spot: 100,
|
|
17
|
+
* strike: 105,
|
|
18
|
+
* timeToExpiry: 0.25,
|
|
19
|
+
* volatility: 0.20,
|
|
20
|
+
* riskFreeRate: 0.05,
|
|
21
|
+
* optionType: 'call'
|
|
22
|
+
* });
|
|
23
|
+
* console.log('Delta:', greeks.delta);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function calculateGreeks(params) {
|
|
27
|
+
const { spot, strike, timeToExpiry, volatility, riskFreeRate, optionType, dividendYield = 0, } = params;
|
|
28
|
+
// Handle edge case
|
|
29
|
+
if (timeToExpiry <= 0) {
|
|
30
|
+
return {
|
|
31
|
+
delta: 0,
|
|
32
|
+
gamma: 0,
|
|
33
|
+
theta: 0,
|
|
34
|
+
vega: 0,
|
|
35
|
+
rho: 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const sqrtT = Math.sqrt(timeToExpiry);
|
|
39
|
+
const d1 = (Math.log(spot / strike) +
|
|
40
|
+
(riskFreeRate - dividendYield + (volatility ** 2) / 2) * timeToExpiry) /
|
|
41
|
+
(volatility * sqrtT);
|
|
42
|
+
const d2 = d1 - volatility * sqrtT;
|
|
43
|
+
const Nd1 = (0, statistics_1.cumulativeNormalDistribution)(d1);
|
|
44
|
+
const Nd2 = (0, statistics_1.cumulativeNormalDistribution)(d2);
|
|
45
|
+
const nd1 = (0, statistics_1.normalPDF)(d1);
|
|
46
|
+
// Delta
|
|
47
|
+
const delta = optionType === 'call'
|
|
48
|
+
? Math.exp(-dividendYield * timeToExpiry) * Nd1
|
|
49
|
+
: -Math.exp(-dividendYield * timeToExpiry) * (0, statistics_1.cumulativeNormalDistribution)(-d1);
|
|
50
|
+
// Gamma (same for calls and puts)
|
|
51
|
+
const gamma = (Math.exp(-dividendYield * timeToExpiry) * nd1) / (spot * volatility * sqrtT);
|
|
52
|
+
// Theta (per day, so divide by 365)
|
|
53
|
+
const theta = optionType === 'call'
|
|
54
|
+
? (-(spot * nd1 * volatility * Math.exp(-dividendYield * timeToExpiry)) /
|
|
55
|
+
(2 * sqrtT) -
|
|
56
|
+
riskFreeRate * strike * Math.exp(-riskFreeRate * timeToExpiry) * Nd2 +
|
|
57
|
+
dividendYield * spot * Math.exp(-dividendYield * timeToExpiry) * Nd1) /
|
|
58
|
+
365
|
|
59
|
+
: (-(spot * nd1 * volatility * Math.exp(-dividendYield * timeToExpiry)) /
|
|
60
|
+
(2 * sqrtT) +
|
|
61
|
+
riskFreeRate * strike * Math.exp(-riskFreeRate * timeToExpiry) * (0, statistics_1.cumulativeNormalDistribution)(-d2) -
|
|
62
|
+
dividendYield * spot * Math.exp(-dividendYield * timeToExpiry) * (0, statistics_1.cumulativeNormalDistribution)(-d1)) /
|
|
63
|
+
365;
|
|
64
|
+
// Vega (per 1% change in volatility, so divide by 100)
|
|
65
|
+
const vega = (spot * Math.exp(-dividendYield * timeToExpiry) * nd1 * sqrtT) / 100;
|
|
66
|
+
// Rho (per 1% change in interest rate, so divide by 100)
|
|
67
|
+
const rho = optionType === 'call'
|
|
68
|
+
? (strike * timeToExpiry * Math.exp(-riskFreeRate * timeToExpiry) * Nd2) / 100
|
|
69
|
+
: (-strike * timeToExpiry * Math.exp(-riskFreeRate * timeToExpiry) * (0, statistics_1.cumulativeNormalDistribution)(-d2)) /
|
|
70
|
+
100;
|
|
71
|
+
return {
|
|
72
|
+
delta,
|
|
73
|
+
gamma,
|
|
74
|
+
theta,
|
|
75
|
+
vega,
|
|
76
|
+
rho,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Calculate delta only (optimized)
|
|
81
|
+
*/
|
|
82
|
+
function calculateDelta(params) {
|
|
83
|
+
const { spot, strike, timeToExpiry, volatility, riskFreeRate, optionType, dividendYield = 0 } = params;
|
|
84
|
+
if (timeToExpiry <= 0)
|
|
85
|
+
return 0;
|
|
86
|
+
const sqrtT = Math.sqrt(timeToExpiry);
|
|
87
|
+
const d1 = (Math.log(spot / strike) +
|
|
88
|
+
(riskFreeRate - dividendYield + (volatility ** 2) / 2) * timeToExpiry) /
|
|
89
|
+
(volatility * sqrtT);
|
|
90
|
+
return optionType === 'call'
|
|
91
|
+
? Math.exp(-dividendYield * timeToExpiry) * (0, statistics_1.cumulativeNormalDistribution)(d1)
|
|
92
|
+
: -Math.exp(-dividendYield * timeToExpiry) * (0, statistics_1.cumulativeNormalDistribution)(-d1);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Calculate gamma only (optimized)
|
|
96
|
+
*/
|
|
97
|
+
function calculateGamma(params) {
|
|
98
|
+
const { spot, strike, timeToExpiry, volatility, riskFreeRate, dividendYield = 0 } = params;
|
|
99
|
+
if (timeToExpiry <= 0)
|
|
100
|
+
return 0;
|
|
101
|
+
const sqrtT = Math.sqrt(timeToExpiry);
|
|
102
|
+
const d1 = (Math.log(spot / strike) +
|
|
103
|
+
(riskFreeRate - dividendYield + (volatility ** 2) / 2) * timeToExpiry) /
|
|
104
|
+
(volatility * sqrtT);
|
|
105
|
+
const nd1 = (0, statistics_1.normalPDF)(d1);
|
|
106
|
+
return (Math.exp(-dividendYield * timeToExpiry) * nd1) / (spot * volatility * sqrtT);
|
|
107
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fullstackcraftllc/floe - Options Analytics Library
|
|
3
|
+
*
|
|
4
|
+
* A comprehensive TypeScript library for options pricing, Greeks calculation,
|
|
5
|
+
* and dealer exposure analysis across multiple brokers.
|
|
6
|
+
*/
|
|
7
|
+
export * from './types';
|
|
8
|
+
export { blackScholes, calculateGreeks, calculateImpliedVolatility, getMillisecondsToExpiration, getTimeToExpirationInYears, } from './blackscholes';
|
|
9
|
+
export { getIVSurfaces, getIVForStrike, } from './volatility';
|
|
10
|
+
export { smoothTotalVarianceSmile, } from './volatility/smoothing';
|
|
11
|
+
export { calculateGammaVannaCharmExposures, calculateSharesNeededToCover, } from './exposure';
|
|
12
|
+
export { cumulativeNormalDistribution, normalPDF, } from './utils/statistics';
|
|
13
|
+
export { genericAdapter, schwabAdapter, ibkrAdapter, tdaAdapter, brokerAdapters, getAdapter, createOptionChain, } from './adapters';
|