@fullstackcraftllc/floe 0.0.2 → 0.0.4
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 +23 -1
- package/dist/client/FloeClient.d.ts +64 -5
- package/dist/client/FloeClient.js +134 -5
- package/dist/client/brokers/SchwabClient.d.ts +2 -0
- package/dist/client/brokers/SchwabClient.js +6 -0
- package/dist/client/brokers/TastyTradeClient.d.ts +384 -0
- package/dist/client/brokers/TastyTradeClient.js +1081 -0
- package/dist/client/brokers/TradierClient.d.ts +233 -1
- package/dist/client/brokers/TradierClient.js +435 -0
- package/dist/impliedpdf/index.d.ts +148 -0
- package/dist/impliedpdf/index.js +277 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -1
- package/dist/utils/occ.d.ts +1 -1
- package/dist/utils/occ.js +5 -4
- package/dist/volatility/index.d.ts +1 -1
- package/dist/volatility/index.js +1 -1
- package/package.json +2 -1
package/dist/impliedpdf/index.js
CHANGED
|
@@ -1 +1,278 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.estimateImpliedProbabilityDistribution = estimateImpliedProbabilityDistribution;
|
|
4
|
+
exports.estimateImpliedProbabilityDistributions = estimateImpliedProbabilityDistributions;
|
|
5
|
+
exports.getProbabilityInRange = getProbabilityInRange;
|
|
6
|
+
exports.getCumulativeProbability = getCumulativeProbability;
|
|
7
|
+
exports.getQuantile = getQuantile;
|
|
8
|
+
/**
|
|
9
|
+
* Estimate an implied probability density function (PDF) for a single expiry
|
|
10
|
+
* using Breeden-Litzenberger style numerical differentiation of call prices.
|
|
11
|
+
*
|
|
12
|
+
* This method computes the second derivative of call option prices with respect
|
|
13
|
+
* to strike price, which under risk-neutral pricing gives the probability density
|
|
14
|
+
* of the underlying ending at each strike.
|
|
15
|
+
*
|
|
16
|
+
* @param symbol - Underlying ticker symbol
|
|
17
|
+
* @param underlyingPrice - Current spot/mark price of the underlying
|
|
18
|
+
* @param callOptions - Array of call options for a single expiry (must have bid > 0 and ask > 0)
|
|
19
|
+
* @returns ImpliedProbabilityDistribution with strike-level probabilities and summary statistics
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const result = estimateImpliedProbabilityDistribution(
|
|
24
|
+
* 'QQQ',
|
|
25
|
+
* 500.00,
|
|
26
|
+
* callOptionsForExpiry
|
|
27
|
+
* );
|
|
28
|
+
*
|
|
29
|
+
* if (result.success) {
|
|
30
|
+
* console.log('Mode:', result.distribution.mostLikelyPrice);
|
|
31
|
+
* console.log('Expected move:', result.distribution.expectedMove);
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function estimateImpliedProbabilityDistribution(symbol, underlyingPrice, callOptions) {
|
|
36
|
+
// Sort call options by strike price ascending
|
|
37
|
+
const sortedOptions = [...callOptions].sort((a, b) => a.strike - b.strike);
|
|
38
|
+
const n = sortedOptions.length;
|
|
39
|
+
if (n < 3) {
|
|
40
|
+
return { success: false, error: 'Not enough data points (need at least 3 call options)' };
|
|
41
|
+
}
|
|
42
|
+
// Get expiration from first option (assuming all same expiry)
|
|
43
|
+
const expiryDate = sortedOptions[0].expirationTimestamp;
|
|
44
|
+
// Estimate second derivative numerically (central difference)
|
|
45
|
+
// f(K) ≈ d²C/dK² where C is the call price
|
|
46
|
+
const strikeProbabilities = new Array(n);
|
|
47
|
+
// Initialize edge cases with zero probability
|
|
48
|
+
strikeProbabilities[0] = { strike: sortedOptions[0].strike, probability: 0 };
|
|
49
|
+
strikeProbabilities[n - 1] = { strike: sortedOptions[n - 1].strike, probability: 0 };
|
|
50
|
+
for (let i = 1; i < n - 1; i++) {
|
|
51
|
+
const kPrev = sortedOptions[i - 1].strike;
|
|
52
|
+
const kCurr = sortedOptions[i].strike;
|
|
53
|
+
const kNext = sortedOptions[i + 1].strike;
|
|
54
|
+
// Use mid prices for stability
|
|
55
|
+
const midPrev = (sortedOptions[i - 1].bid + sortedOptions[i - 1].ask) / 2;
|
|
56
|
+
const midCurr = (sortedOptions[i].bid + sortedOptions[i].ask) / 2;
|
|
57
|
+
const midNext = (sortedOptions[i + 1].bid + sortedOptions[i + 1].ask) / 2;
|
|
58
|
+
const cPrev = midPrev;
|
|
59
|
+
const cNext = midNext;
|
|
60
|
+
// Protect against division by zero if strikes are too close
|
|
61
|
+
const strikeDiff = kNext - kPrev;
|
|
62
|
+
if (Math.abs(strikeDiff) < 1e-9) {
|
|
63
|
+
strikeProbabilities[i] = { strike: kCurr, probability: 0 };
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Second derivative: d²C/dK² ≈ (C(K+) - 2*C(K) + C(K-)) / (ΔK)²
|
|
67
|
+
const d2 = (cNext - 2 * midCurr + cPrev) / Math.pow(strikeDiff, 2);
|
|
68
|
+
// f(K) = e^{rT} * d²C/dK², ignoring discount for simplicity
|
|
69
|
+
// Ensure non-negative probability
|
|
70
|
+
strikeProbabilities[i] = { strike: kCurr, probability: Math.max(d2, 0) };
|
|
71
|
+
}
|
|
72
|
+
// Normalize densities to sum to 1
|
|
73
|
+
let sum = 0;
|
|
74
|
+
for (const sp of strikeProbabilities) {
|
|
75
|
+
sum += sp.probability;
|
|
76
|
+
}
|
|
77
|
+
// Protect against division by zero if all probabilities are 0
|
|
78
|
+
if (sum < 1e-9) {
|
|
79
|
+
return { success: false, error: `Insufficient probability mass to normalize (sum=${sum})` };
|
|
80
|
+
}
|
|
81
|
+
for (let i = 0; i < strikeProbabilities.length; i++) {
|
|
82
|
+
strikeProbabilities[i].probability /= sum;
|
|
83
|
+
}
|
|
84
|
+
// Compute summary statistics
|
|
85
|
+
// Most likely price (mode)
|
|
86
|
+
let mostLikelyPrice = strikeProbabilities[0].strike;
|
|
87
|
+
let maxProb = 0;
|
|
88
|
+
for (const sp of strikeProbabilities) {
|
|
89
|
+
if (sp.probability > maxProb) {
|
|
90
|
+
maxProb = sp.probability;
|
|
91
|
+
mostLikelyPrice = sp.strike;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Compute cumulative distribution for median
|
|
95
|
+
let cumulative = 0;
|
|
96
|
+
let medianPrice = strikeProbabilities[Math.floor(strikeProbabilities.length / 2)].strike;
|
|
97
|
+
for (const sp of strikeProbabilities) {
|
|
98
|
+
cumulative += sp.probability;
|
|
99
|
+
if (cumulative >= 0.5) {
|
|
100
|
+
medianPrice = sp.strike;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Expected value (mean)
|
|
105
|
+
let mean = 0;
|
|
106
|
+
for (const sp of strikeProbabilities) {
|
|
107
|
+
mean += sp.strike * sp.probability;
|
|
108
|
+
}
|
|
109
|
+
// Variance and expected move (standard deviation)
|
|
110
|
+
let variance = 0;
|
|
111
|
+
for (const sp of strikeProbabilities) {
|
|
112
|
+
const diff = sp.strike - mean;
|
|
113
|
+
variance += diff * diff * sp.probability;
|
|
114
|
+
}
|
|
115
|
+
const expectedMove = Math.sqrt(variance);
|
|
116
|
+
// Tail skew: rightTail / leftTail relative to mean
|
|
117
|
+
let leftTail = 0;
|
|
118
|
+
let rightTail = 0;
|
|
119
|
+
for (const sp of strikeProbabilities) {
|
|
120
|
+
if (sp.strike < mean) {
|
|
121
|
+
leftTail += sp.probability;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
rightTail += sp.probability;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const tailSkew = rightTail / Math.max(leftTail, 1e-9);
|
|
128
|
+
// Cumulative probabilities above and below spot price
|
|
129
|
+
let cumulativeBelowSpot = 0;
|
|
130
|
+
let cumulativeAboveSpot = 0;
|
|
131
|
+
for (const sp of strikeProbabilities) {
|
|
132
|
+
if (sp.strike < underlyingPrice) {
|
|
133
|
+
cumulativeBelowSpot += sp.probability;
|
|
134
|
+
}
|
|
135
|
+
else if (sp.strike > underlyingPrice) {
|
|
136
|
+
cumulativeAboveSpot += sp.probability;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
distribution: {
|
|
142
|
+
symbol,
|
|
143
|
+
expiryDate,
|
|
144
|
+
calculationTimestamp: Date.now(),
|
|
145
|
+
underlyingPrice,
|
|
146
|
+
strikeProbabilities,
|
|
147
|
+
mostLikelyPrice,
|
|
148
|
+
medianPrice,
|
|
149
|
+
expectedValue: mean,
|
|
150
|
+
expectedMove,
|
|
151
|
+
tailSkew,
|
|
152
|
+
cumulativeProbabilityAboveSpot: cumulativeAboveSpot,
|
|
153
|
+
cumulativeProbabilityBelowSpot: cumulativeBelowSpot,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Estimate implied probability distributions for all expirations in an option chain
|
|
159
|
+
*
|
|
160
|
+
* @param symbol - Underlying ticker symbol
|
|
161
|
+
* @param underlyingPrice - Current spot/mark price of the underlying
|
|
162
|
+
* @param options - Array of all options (calls and puts, all expirations)
|
|
163
|
+
* @returns Array of ImpliedProbabilityDistribution for each expiration
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* const distributions = estimateImpliedProbabilityDistributions(
|
|
168
|
+
* 'QQQ',
|
|
169
|
+
* 500.00,
|
|
170
|
+
* chain.options
|
|
171
|
+
* );
|
|
172
|
+
*
|
|
173
|
+
* for (const dist of distributions) {
|
|
174
|
+
* console.log(`Expiry: ${new Date(dist.expiryDate).toISOString()}`);
|
|
175
|
+
* console.log(`Mode: ${dist.mostLikelyPrice}`);
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
function estimateImpliedProbabilityDistributions(symbol, underlyingPrice, options) {
|
|
180
|
+
// Get unique expirations
|
|
181
|
+
const expirationsSet = new Set();
|
|
182
|
+
for (const option of options) {
|
|
183
|
+
expirationsSet.add(option.expirationTimestamp);
|
|
184
|
+
}
|
|
185
|
+
const expirations = Array.from(expirationsSet).sort((a, b) => a - b);
|
|
186
|
+
const distributions = [];
|
|
187
|
+
for (const expiry of expirations) {
|
|
188
|
+
// Filter to call options at this expiry with valid bid/ask
|
|
189
|
+
const callOptionsAtExpiry = options.filter((opt) => opt.expirationTimestamp === expiry &&
|
|
190
|
+
opt.optionType === 'call' &&
|
|
191
|
+
opt.bid > 0 &&
|
|
192
|
+
opt.ask > 0);
|
|
193
|
+
const result = estimateImpliedProbabilityDistribution(symbol, underlyingPrice, callOptionsAtExpiry);
|
|
194
|
+
if (result.success) {
|
|
195
|
+
distributions.push(result.distribution);
|
|
196
|
+
}
|
|
197
|
+
// Silently skip expirations that don't have enough data
|
|
198
|
+
}
|
|
199
|
+
return distributions;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get the probability of the underlying finishing between two price levels
|
|
203
|
+
*
|
|
204
|
+
* @param distribution - Implied probability distribution
|
|
205
|
+
* @param lowerBound - Lower price bound
|
|
206
|
+
* @param upperBound - Upper price bound
|
|
207
|
+
* @returns Probability of finishing between the bounds
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* // Probability of QQQ finishing between 490 and 510
|
|
212
|
+
* const prob = getProbabilityInRange(distribution, 490, 510);
|
|
213
|
+
* console.log(`${(prob * 100).toFixed(1)}% chance of finishing in range`);
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
function getProbabilityInRange(distribution, lowerBound, upperBound) {
|
|
217
|
+
let probability = 0;
|
|
218
|
+
for (const sp of distribution.strikeProbabilities) {
|
|
219
|
+
if (sp.strike >= lowerBound && sp.strike <= upperBound) {
|
|
220
|
+
probability += sp.probability;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return probability;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get the cumulative probability up to a given price level
|
|
227
|
+
*
|
|
228
|
+
* @param distribution - Implied probability distribution
|
|
229
|
+
* @param price - Price level
|
|
230
|
+
* @returns Cumulative probability of finishing at or below the price
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* // Probability of QQQ finishing at or below 495
|
|
235
|
+
* const prob = getCumulativeProbability(distribution, 495);
|
|
236
|
+
* console.log(`${(prob * 100).toFixed(1)}% chance of finishing <= 495`);
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
function getCumulativeProbability(distribution, price) {
|
|
240
|
+
let probability = 0;
|
|
241
|
+
for (const sp of distribution.strikeProbabilities) {
|
|
242
|
+
if (sp.strike <= price) {
|
|
243
|
+
probability += sp.probability;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return probability;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get the quantile (inverse CDF) for a given probability
|
|
250
|
+
*
|
|
251
|
+
* @param distribution - Implied probability distribution
|
|
252
|
+
* @param probability - Probability value between 0 and 1
|
|
253
|
+
* @returns Strike price at the given probability quantile
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```typescript
|
|
257
|
+
* // Find the 5th and 95th percentile strikes
|
|
258
|
+
* const p5 = getQuantile(distribution, 0.05);
|
|
259
|
+
* const p95 = getQuantile(distribution, 0.95);
|
|
260
|
+
* console.log(`90% confidence interval: [${p5}, ${p95}]`);
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
function getQuantile(distribution, probability) {
|
|
264
|
+
if (probability <= 0) {
|
|
265
|
+
return distribution.strikeProbabilities[0]?.strike ?? 0;
|
|
266
|
+
}
|
|
267
|
+
if (probability >= 1) {
|
|
268
|
+
return distribution.strikeProbabilities[distribution.strikeProbabilities.length - 1]?.strike ?? 0;
|
|
269
|
+
}
|
|
270
|
+
let cumulative = 0;
|
|
271
|
+
for (const sp of distribution.strikeProbabilities) {
|
|
272
|
+
cumulative += sp.probability;
|
|
273
|
+
if (cumulative >= probability) {
|
|
274
|
+
return sp.strike;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return distribution.strikeProbabilities[distribution.strikeProbabilities.length - 1]?.strike ?? 0;
|
|
278
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ 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
17
|
export { FloeClient, Broker } from './client/FloeClient';
|
|
16
18
|
export { TradierClient } from './client/brokers/TradierClient';
|
|
19
|
+
export type { AggressorSide, IntradayTrade } from './client/brokers/TradierClient';
|
|
17
20
|
export { genericAdapter, schwabAdapter, ibkrAdapter, tdaAdapter, brokerAdapters, getAdapter, createOptionChain, } from './adapters';
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ 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.createOptionChain = exports.getAdapter = exports.brokerAdapters = exports.tdaAdapter = exports.ibkrAdapter = exports.schwabAdapter = exports.genericAdapter = exports.TradierClient = exports.Broker = exports.FloeClient = 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;
|
|
23
|
+
exports.createOptionChain = exports.getAdapter = exports.brokerAdapters = exports.tdaAdapter = exports.ibkrAdapter = exports.schwabAdapter = exports.genericAdapter = exports.TradierClient = exports.Broker = exports.FloeClient = 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
24
|
// Core types
|
|
25
25
|
__exportStar(require("./types"), exports);
|
|
26
26
|
// Black-Scholes pricing and Greeks
|
|
@@ -52,6 +52,13 @@ Object.defineProperty(exports, "parseOCCSymbol", { enumerable: true, get: functi
|
|
|
52
52
|
Object.defineProperty(exports, "generateStrikesAroundSpot", { enumerable: true, get: function () { return occ_1.generateStrikesAroundSpot; } });
|
|
53
53
|
Object.defineProperty(exports, "generateOCCSymbolsForStrikes", { enumerable: true, get: function () { return occ_1.generateOCCSymbolsForStrikes; } });
|
|
54
54
|
Object.defineProperty(exports, "generateOCCSymbolsAroundSpot", { enumerable: true, get: function () { return occ_1.generateOCCSymbolsAroundSpot; } });
|
|
55
|
+
// Implied PDF (probability density function)
|
|
56
|
+
var impliedpdf_1 = require("./impliedpdf");
|
|
57
|
+
Object.defineProperty(exports, "estimateImpliedProbabilityDistribution", { enumerable: true, get: function () { return impliedpdf_1.estimateImpliedProbabilityDistribution; } });
|
|
58
|
+
Object.defineProperty(exports, "estimateImpliedProbabilityDistributions", { enumerable: true, get: function () { return impliedpdf_1.estimateImpliedProbabilityDistributions; } });
|
|
59
|
+
Object.defineProperty(exports, "getProbabilityInRange", { enumerable: true, get: function () { return impliedpdf_1.getProbabilityInRange; } });
|
|
60
|
+
Object.defineProperty(exports, "getCumulativeProbability", { enumerable: true, get: function () { return impliedpdf_1.getCumulativeProbability; } });
|
|
61
|
+
Object.defineProperty(exports, "getQuantile", { enumerable: true, get: function () { return impliedpdf_1.getQuantile; } });
|
|
55
62
|
// Client
|
|
56
63
|
var FloeClient_1 = require("./client/FloeClient");
|
|
57
64
|
Object.defineProperty(exports, "FloeClient", { enumerable: true, get: function () { return FloeClient_1.FloeClient; } });
|
package/dist/utils/occ.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface StrikeGenerationParams {
|
|
|
48
48
|
/** Number of strikes below spot to include */
|
|
49
49
|
strikesBelow?: number;
|
|
50
50
|
/** Strike increment (e.g., 1 for $1 increments, 5 for $5) */
|
|
51
|
-
|
|
51
|
+
strikeIncrementInDollars?: number;
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Builds an OCC-formatted option symbol.
|
package/dist/utils/occ.js
CHANGED
|
@@ -48,7 +48,8 @@ function buildOCCSymbol(params) {
|
|
|
48
48
|
? symbol.toUpperCase().padEnd(6, ' ')
|
|
49
49
|
: symbol.toUpperCase();
|
|
50
50
|
// Format expiration date as YYMMDD
|
|
51
|
-
|
|
51
|
+
// Use UTC methods to avoid timezone shifts when parsing ISO date strings
|
|
52
|
+
const expirationDate = typeof expiration === 'string' ? new Date(expiration + 'T12:00:00') : expiration;
|
|
52
53
|
const year = expirationDate.getFullYear().toString().slice(-2);
|
|
53
54
|
const month = (expirationDate.getMonth() + 1).toString().padStart(2, '0');
|
|
54
55
|
const day = expirationDate.getDate().toString().padStart(2, '0');
|
|
@@ -100,11 +101,11 @@ function parseOCCSymbol(occSymbol) {
|
|
|
100
101
|
if (symbol.length === 0) {
|
|
101
102
|
throw new Error(`Invalid OCC symbol: no ticker found in ${occSymbol}`);
|
|
102
103
|
}
|
|
103
|
-
// Parse date
|
|
104
|
+
// Parse date - use noon to avoid timezone edge cases
|
|
104
105
|
const year = 2000 + parseInt(dateString.slice(0, 2), 10);
|
|
105
106
|
const month = parseInt(dateString.slice(2, 4), 10) - 1; // 0-indexed
|
|
106
107
|
const day = parseInt(dateString.slice(4, 6), 10);
|
|
107
|
-
const expiration = new Date(year, month, day);
|
|
108
|
+
const expiration = new Date(year, month, day, 12, 0, 0);
|
|
108
109
|
// Validate date
|
|
109
110
|
if (isNaN(expiration.getTime())) {
|
|
110
111
|
throw new Error(`Invalid date in OCC symbol: ${dateString}`);
|
|
@@ -133,7 +134,7 @@ function parseOCCSymbol(occSymbol) {
|
|
|
133
134
|
* ```
|
|
134
135
|
*/
|
|
135
136
|
function generateStrikesAroundSpot(params) {
|
|
136
|
-
const { spot, strikesAbove = 10, strikesBelow = 10, strikeIncrement = 1 } = params;
|
|
137
|
+
const { spot, strikesAbove = 10, strikesBelow = 10, strikeIncrementInDollars: strikeIncrement = 1 } = params;
|
|
137
138
|
// Find the nearest strike at or below spot
|
|
138
139
|
const baseStrike = Math.floor(spot / strikeIncrement) * strikeIncrement;
|
|
139
140
|
const strikes = [];
|
|
@@ -20,7 +20,7 @@ import { OptionChain, IVSurface, VolatilityModel, SmoothingModel } from '../type
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function getIVSurfaces(volatilityModel: VolatilityModel, smoothingModel: SmoothingModel, chain: OptionChain): IVSurface[];
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Helper function to get the smoothed IV for a specific expiration, option type, and strike combination
|
|
24
24
|
*
|
|
25
25
|
* @param ivSurfaces - Array of IV surfaces
|
|
26
26
|
* @param expiration - Expiration timestamp in milliseconds
|
package/dist/volatility/index.js
CHANGED
|
@@ -134,7 +134,7 @@ function getIVSurfaces(volatilityModel, smoothingModel, chain) {
|
|
|
134
134
|
return ivSurfaces;
|
|
135
135
|
}
|
|
136
136
|
/**
|
|
137
|
-
*
|
|
137
|
+
* Helper function to get the smoothed IV for a specific expiration, option type, and strike combination
|
|
138
138
|
*
|
|
139
139
|
* @param ivSurfaces - Array of IV surfaces
|
|
140
140
|
* @param expiration - Expiration timestamp in milliseconds
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fullstackcraftllc/floe",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
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",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/jest": "^29.5.0",
|
|
43
43
|
"@types/node": "^20.0.0",
|
|
44
|
+
"dotenv": "^17.2.3",
|
|
44
45
|
"jest": "^29.5.0",
|
|
45
46
|
"ts-jest": "^29.1.0",
|
|
46
47
|
"typescript": "^5.9.3"
|