@fullstackcraftllc/floe 0.0.5 → 0.0.7
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 +10 -7
- package/dist/client/FloeClient.d.ts +17 -2
- package/dist/client/FloeClient.js +44 -10
- package/dist/client/brokers/SchwabClient.d.ts +4 -0
- package/dist/client/brokers/SchwabClient.js +25 -0
- package/dist/client/brokers/TastyTradeClient.d.ts +4 -0
- package/dist/client/brokers/TastyTradeClient.js +11 -0
- package/dist/client/brokers/TradeStationClient.d.ts +4 -0
- package/dist/client/brokers/TradeStationClient.js +9 -0
- package/dist/client/brokers/TradierClient.d.ts +19 -7
- package/dist/client/brokers/TradierClient.js +41 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Zero-dependency TypeScript functions for options flow: Black-Scholes, Greeks, and dealer exposures, and more, with a clean, type-safe API. Built for use in trading platforms and fintech applications.
|
|
6
6
|
|
|
7
|
-
The same library that is used in Full Stack Craft's various fintech products including [The Wheel Screener](https://wheelscreener.com), [LEAPS Screener](https://leapsscreener.com), [Option Screener](https://option-screener.com), [AMT JOY](https://amtjoy.com), and [VannaCharm](https://vannacharm.com).
|
|
7
|
+
The same library that is used in [Full Stack Craft's](https://fullstackcraft.com) various fintech products including [The Wheel Screener](https://wheelscreener.com), [LEAPS Screener](https://leapsscreener.com), [Option Screener](https://option-screener.com), [AMT JOY](https://amtjoy.com), and [VannaCharm](https://vannacharm.com).
|
|
8
8
|
|
|
9
9
|
## Quick Start / Documentation / Examples
|
|
10
10
|
|
|
@@ -23,12 +23,15 @@ The same library that is used in Full Stack Craft's various fintech products inc
|
|
|
23
23
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
26
|
+
- **Black-Scholes Pricing** - Fast, accurate options pricing
|
|
27
|
+
- **Greeks Calculations** - Delta, gamma, theta, vega, rho
|
|
28
|
+
- **Dealer Exposure Metrics** - GEX, VEX, and CEX exposures
|
|
29
|
+
- **Implied Volatility & Surfaces** - Calculate IV from market prices and build volatility surfaces
|
|
30
|
+
- **Implied PDF** - Risk-neutral probability density functions
|
|
31
|
+
- **Real-Time Data** - Stream normalized options data from multiple brokers
|
|
32
|
+
- **Broker-Agnostic** - Normalize data from any broker
|
|
33
|
+
- **Type-Safe** - Full TypeScript support
|
|
34
|
+
- **Zero Dependencies** - Lightweight and fast
|
|
32
35
|
|
|
33
36
|
## Broker Support Roadmap
|
|
34
37
|
|
|
@@ -119,7 +119,7 @@ export declare class FloeClient {
|
|
|
119
119
|
* Establishes a connection to a broker's API.
|
|
120
120
|
*
|
|
121
121
|
* @param broker - The broker to connect to (e.g., Broker.TRADIER)
|
|
122
|
-
* @param
|
|
122
|
+
* @param authToken - The API authentication token for the broker
|
|
123
123
|
*
|
|
124
124
|
* @throws {Error} Throws if the specified broker is not supported
|
|
125
125
|
*
|
|
@@ -132,7 +132,7 @@ export declare class FloeClient {
|
|
|
132
132
|
* await client.connect(Broker.TRADIER, 'your-tradier-api-key');
|
|
133
133
|
* ```
|
|
134
134
|
*/
|
|
135
|
-
connect(broker: Broker,
|
|
135
|
+
connect(broker: Broker, authToken: string): Promise<void>;
|
|
136
136
|
/**
|
|
137
137
|
* Disconnects from the current broker.
|
|
138
138
|
*
|
|
@@ -221,6 +221,21 @@ export declare class FloeClient {
|
|
|
221
221
|
* ```
|
|
222
222
|
*/
|
|
223
223
|
unsubscribeFromOptions(symbols: Array<string>): void;
|
|
224
|
+
/**
|
|
225
|
+
* Unsubscribes from all currently subscribed tickers and options.
|
|
226
|
+
*
|
|
227
|
+
* @throws {Error} Throws if no broker connection has been established
|
|
228
|
+
*
|
|
229
|
+
* @remarks
|
|
230
|
+
* After calling this method, no further updates will be received for any
|
|
231
|
+
* previously subscribed tickers or options.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```typescript
|
|
235
|
+
* client.unsubscribeFromAll();
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
unsubscribeFromAll(): void;
|
|
224
239
|
/**
|
|
225
240
|
* Fetches open interest and initial option data via REST API.
|
|
226
241
|
*
|
|
@@ -104,7 +104,7 @@ class FloeClient {
|
|
|
104
104
|
* Establishes a connection to a broker's API.
|
|
105
105
|
*
|
|
106
106
|
* @param broker - The broker to connect to (e.g., Broker.TRADIER)
|
|
107
|
-
* @param
|
|
107
|
+
* @param authToken - The API authentication token for the broker
|
|
108
108
|
*
|
|
109
109
|
* @throws {Error} Throws if the specified broker is not supported
|
|
110
110
|
*
|
|
@@ -117,12 +117,12 @@ class FloeClient {
|
|
|
117
117
|
* await client.connect(Broker.TRADIER, 'your-tradier-api-key');
|
|
118
118
|
* ```
|
|
119
119
|
*/
|
|
120
|
-
async connect(broker,
|
|
120
|
+
async connect(broker, authToken) {
|
|
121
121
|
this.currentBroker = broker;
|
|
122
|
-
// Connection logic to the broker's API using the
|
|
122
|
+
// Connection logic to the broker's API using the authToken
|
|
123
123
|
switch (broker.toLowerCase()) {
|
|
124
124
|
case Broker.TRADIER:
|
|
125
|
-
this.tradierClient = new TradierClient_1.TradierClient(
|
|
125
|
+
this.tradierClient = new TradierClient_1.TradierClient(authToken, { verbose: this.verbose });
|
|
126
126
|
// Wire up TradierClient events to FloeClient events
|
|
127
127
|
this.tradierClient.on('tickerUpdate', (ticker) => {
|
|
128
128
|
this.emit('tickerUpdate', ticker);
|
|
@@ -140,9 +140,9 @@ class FloeClient {
|
|
|
140
140
|
await this.tradierClient.connect();
|
|
141
141
|
break;
|
|
142
142
|
case Broker.TASTYTRADE:
|
|
143
|
-
// For TastyTrade,
|
|
143
|
+
// For TastyTrade, authToken is the session token
|
|
144
144
|
this.tastyTradeClient = new TastyTradeClient_1.TastyTradeClient({
|
|
145
|
-
sessionToken:
|
|
145
|
+
sessionToken: authToken,
|
|
146
146
|
verbose: this.verbose,
|
|
147
147
|
});
|
|
148
148
|
// Wire up TastyTradeClient events to FloeClient events
|
|
@@ -162,9 +162,9 @@ class FloeClient {
|
|
|
162
162
|
await this.tastyTradeClient.connect();
|
|
163
163
|
break;
|
|
164
164
|
case Broker.TRADESTATION:
|
|
165
|
-
// For TradeStation,
|
|
165
|
+
// For TradeStation, authToken is the OAuth access token
|
|
166
166
|
this.tradeStationClient = new TradeStationClient_1.TradeStationClient({
|
|
167
|
-
accessToken:
|
|
167
|
+
accessToken: authToken,
|
|
168
168
|
verbose: this.verbose,
|
|
169
169
|
});
|
|
170
170
|
// Wire up TradeStationClient events to FloeClient events
|
|
@@ -184,9 +184,9 @@ class FloeClient {
|
|
|
184
184
|
await this.tradeStationClient.connect();
|
|
185
185
|
break;
|
|
186
186
|
case Broker.SCHWAB:
|
|
187
|
-
// For Schwab,
|
|
187
|
+
// For Schwab, authToken is the OAuth access token
|
|
188
188
|
this.schwabClient = new SchwabClient_1.SchwabClient({
|
|
189
|
-
accessToken:
|
|
189
|
+
accessToken: authToken,
|
|
190
190
|
verbose: this.verbose,
|
|
191
191
|
});
|
|
192
192
|
// Wire up SchwabClient events to FloeClient events
|
|
@@ -394,6 +394,40 @@ class FloeClient {
|
|
|
394
394
|
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Unsubscribes from all currently subscribed tickers and options.
|
|
399
|
+
*
|
|
400
|
+
* @throws {Error} Throws if no broker connection has been established
|
|
401
|
+
*
|
|
402
|
+
* @remarks
|
|
403
|
+
* After calling this method, no further updates will be received for any
|
|
404
|
+
* previously subscribed tickers or options.
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```typescript
|
|
408
|
+
* client.unsubscribeFromAll();
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
unsubscribeFromAll() {
|
|
412
|
+
this.currentSubscribedTickers = [];
|
|
413
|
+
this.currentSubscribedOptions = [];
|
|
414
|
+
switch (this.currentBroker) {
|
|
415
|
+
case Broker.TRADIER:
|
|
416
|
+
this.tradierClient?.unsubscribeFromAll();
|
|
417
|
+
break;
|
|
418
|
+
case Broker.TASTYTRADE:
|
|
419
|
+
this.tastyTradeClient?.unsubscribeFromAll();
|
|
420
|
+
break;
|
|
421
|
+
case Broker.TRADESTATION:
|
|
422
|
+
this.tradeStationClient?.unsubscribeFromAll();
|
|
423
|
+
break;
|
|
424
|
+
case Broker.SCHWAB:
|
|
425
|
+
this.schwabClient?.unsubscribeFromAll();
|
|
426
|
+
break;
|
|
427
|
+
default:
|
|
428
|
+
throw new Error(`Unsupported broker: ${this.currentBroker}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
397
431
|
/**
|
|
398
432
|
* Fetches open interest and initial option data via REST API.
|
|
399
433
|
*
|
|
@@ -251,6 +251,10 @@ export declare class SchwabClient {
|
|
|
251
251
|
* @param symbols - Array of symbols to unsubscribe from
|
|
252
252
|
*/
|
|
253
253
|
unsubscribe(symbols: string[]): void;
|
|
254
|
+
/**
|
|
255
|
+
* Unsubscribes from all real-time updates.
|
|
256
|
+
*/
|
|
257
|
+
unsubscribeFromAll(): void;
|
|
254
258
|
/**
|
|
255
259
|
* Returns whether the client is currently connected.
|
|
256
260
|
*/
|
|
@@ -276,6 +276,31 @@ class SchwabClient {
|
|
|
276
276
|
this.unsubscribeOptionsBook(options);
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Unsubscribes from all real-time updates.
|
|
281
|
+
*/
|
|
282
|
+
unsubscribeFromAll() {
|
|
283
|
+
const allSymbols = Array.from(this.subscribedSymbols);
|
|
284
|
+
const allOptionSymbols = allSymbols.filter(s => this.isOptionSymbol(s)).map(s => this.toSchwabOptionSymbol(s));
|
|
285
|
+
this.subscribedSymbols.clear();
|
|
286
|
+
// unsub from all equities
|
|
287
|
+
if (allSymbols.length > 0) {
|
|
288
|
+
const request = this.makeRequest('LEVELONE_EQUITIES', 'UNSUBS', {
|
|
289
|
+
keys: allSymbols.join(','),
|
|
290
|
+
});
|
|
291
|
+
this.sendMessage({ requests: [request] });
|
|
292
|
+
}
|
|
293
|
+
// unsub from all options (quotes and book)
|
|
294
|
+
if (allOptionSymbols.length > 0) {
|
|
295
|
+
const requestOptions = this.makeRequest('LEVELONE_OPTIONS', 'UNSUBS', {
|
|
296
|
+
keys: allOptionSymbols.join(','),
|
|
297
|
+
});
|
|
298
|
+
const requestBook = this.makeRequest('OPTIONS_BOOK', 'UNSUBS', {
|
|
299
|
+
keys: allOptionSymbols.join(','),
|
|
300
|
+
});
|
|
301
|
+
this.sendMessage({ requests: [requestOptions, requestBook] });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
279
304
|
/**
|
|
280
305
|
* Returns whether the client is currently connected.
|
|
281
306
|
*/
|
|
@@ -201,6 +201,10 @@ export declare class TastyTradeClient {
|
|
|
201
201
|
* @param symbols - Array of symbols to unsubscribe from
|
|
202
202
|
*/
|
|
203
203
|
unsubscribe(symbols: string[]): void;
|
|
204
|
+
/**
|
|
205
|
+
* Unsubscribes from all real-time updates.
|
|
206
|
+
*/
|
|
207
|
+
unsubscribeFromAll(): void;
|
|
204
208
|
/**
|
|
205
209
|
* Returns whether the client is currently connected.
|
|
206
210
|
*/
|
|
@@ -226,6 +226,17 @@ class TastyTradeClient {
|
|
|
226
226
|
}
|
|
227
227
|
this.sendFeedSubscription(symbols, 'remove');
|
|
228
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Unsubscribes from all real-time updates.
|
|
231
|
+
*/
|
|
232
|
+
unsubscribeFromAll() {
|
|
233
|
+
const symbols = Array.from(this.subscribedSymbols);
|
|
234
|
+
this.subscribedSymbols.clear();
|
|
235
|
+
if (!this.connected || !this.feedChannelOpened) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.sendFeedSubscription(symbols, 'remove');
|
|
239
|
+
}
|
|
229
240
|
/**
|
|
230
241
|
* Returns whether the client is currently connected.
|
|
231
242
|
*/
|
|
@@ -177,6 +177,10 @@ export declare class TradeStationClient {
|
|
|
177
177
|
* restarting with the remaining symbols.
|
|
178
178
|
*/
|
|
179
179
|
unsubscribe(symbols: string[]): void;
|
|
180
|
+
/**
|
|
181
|
+
* Unsubscribes from all real-time updates.
|
|
182
|
+
*/
|
|
183
|
+
unsubscribeFromAll(): void;
|
|
180
184
|
/**
|
|
181
185
|
* Returns whether the client is currently connected.
|
|
182
186
|
*/
|
|
@@ -217,6 +217,15 @@ class TradeStationClient {
|
|
|
217
217
|
this.stopStream('options');
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Unsubscribes from all real-time updates.
|
|
222
|
+
*/
|
|
223
|
+
unsubscribeFromAll() {
|
|
224
|
+
this.subscribedTickers.clear();
|
|
225
|
+
this.subscribedOptions.clear();
|
|
226
|
+
this.stopStream('quotes');
|
|
227
|
+
this.stopStream('options');
|
|
228
|
+
}
|
|
220
229
|
/**
|
|
221
230
|
* Returns whether the client is currently connected.
|
|
222
231
|
*/
|
|
@@ -107,7 +107,7 @@ type TradierEventListener<T> = (data: T) => void;
|
|
|
107
107
|
*/
|
|
108
108
|
export declare class TradierClient {
|
|
109
109
|
/** Tradier API authentication token */
|
|
110
|
-
private
|
|
110
|
+
private authToken;
|
|
111
111
|
/** Current streaming session */
|
|
112
112
|
private streamSession;
|
|
113
113
|
/** WebSocket connection */
|
|
@@ -152,11 +152,11 @@ export declare class TradierClient {
|
|
|
152
152
|
/**
|
|
153
153
|
* Creates a new TradierClient instance.
|
|
154
154
|
*
|
|
155
|
-
* @param
|
|
155
|
+
* @param authToken - Tradier API auth token
|
|
156
156
|
* @param options - Optional configuration options
|
|
157
157
|
* @param options.verbose - Whether to log verbose debug information (default: false)
|
|
158
158
|
*/
|
|
159
|
-
constructor(
|
|
159
|
+
constructor(authToken: string, options?: {
|
|
160
160
|
verbose?: boolean;
|
|
161
161
|
});
|
|
162
162
|
/**
|
|
@@ -182,11 +182,18 @@ export declare class TradierClient {
|
|
|
182
182
|
* @param symbols - Array of symbols to unsubscribe from
|
|
183
183
|
*
|
|
184
184
|
* @remarks
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* you would need to disconnect and reconnect with the new symbol list.
|
|
185
|
+
* Since Tradier's streaming API doesn't support selective unsubscription,
|
|
186
|
+
* this method disconnects and reconnects with the updated symbol list.
|
|
188
187
|
*/
|
|
189
|
-
unsubscribe(symbols: string[]): void
|
|
188
|
+
unsubscribe(symbols: string[]): Promise<void>;
|
|
189
|
+
/**
|
|
190
|
+
* Unsubscribes from all symbols.
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* Since Tradier's streaming API doesn't support selective unsubscription,
|
|
194
|
+
* this method disconnects and reconnects without any symbols.
|
|
195
|
+
*/
|
|
196
|
+
unsubscribeFromAll(): Promise<void>;
|
|
190
197
|
/**
|
|
191
198
|
* Returns whether the client is currently connected.
|
|
192
199
|
*/
|
|
@@ -251,6 +258,11 @@ export declare class TradierClient {
|
|
|
251
258
|
* Attempts to reconnect with exponential backoff.
|
|
252
259
|
*/
|
|
253
260
|
private attemptReconnect;
|
|
261
|
+
/**
|
|
262
|
+
* Reconnects with the current symbol list.
|
|
263
|
+
* Used for unsubscribe operations since Tradier doesn't support selective unsubscription.
|
|
264
|
+
*/
|
|
265
|
+
private reconnectWithSymbols;
|
|
254
266
|
/**
|
|
255
267
|
* Handles incoming WebSocket messages.
|
|
256
268
|
*/
|
|
@@ -33,11 +33,11 @@ class TradierClient {
|
|
|
33
33
|
/**
|
|
34
34
|
* Creates a new TradierClient instance.
|
|
35
35
|
*
|
|
36
|
-
* @param
|
|
36
|
+
* @param authToken - Tradier API auth token
|
|
37
37
|
* @param options - Optional configuration options
|
|
38
38
|
* @param options.verbose - Whether to log verbose debug information (default: false)
|
|
39
39
|
*/
|
|
40
|
-
constructor(
|
|
40
|
+
constructor(authToken, options) {
|
|
41
41
|
/** Current streaming session */
|
|
42
42
|
this.streamSession = null;
|
|
43
43
|
/** WebSocket connection */
|
|
@@ -77,7 +77,7 @@ class TradierClient {
|
|
|
77
77
|
this.apiBaseUrl = 'https://api.tradier.com/v1';
|
|
78
78
|
/** Tradier WebSocket URL */
|
|
79
79
|
this.wsUrl = 'wss://ws.tradier.com/v1/markets/events';
|
|
80
|
-
this.
|
|
80
|
+
this.authToken = authToken;
|
|
81
81
|
this.verbose = options?.verbose ?? false;
|
|
82
82
|
// Initialize event listener maps
|
|
83
83
|
this.eventListeners.set('tickerUpdate', new Set());
|
|
@@ -142,14 +142,29 @@ class TradierClient {
|
|
|
142
142
|
* @param symbols - Array of symbols to unsubscribe from
|
|
143
143
|
*
|
|
144
144
|
* @remarks
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* you would need to disconnect and reconnect with the new symbol list.
|
|
145
|
+
* Since Tradier's streaming API doesn't support selective unsubscription,
|
|
146
|
+
* this method disconnects and reconnects with the updated symbol list.
|
|
148
147
|
*/
|
|
149
|
-
unsubscribe(symbols) {
|
|
148
|
+
async unsubscribe(symbols) {
|
|
150
149
|
symbols.forEach(s => this.subscribedSymbols.delete(s));
|
|
151
|
-
//
|
|
152
|
-
|
|
150
|
+
// If connected, reconnect with the new symbol list
|
|
151
|
+
if (this.connected) {
|
|
152
|
+
await this.reconnectWithSymbols();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Unsubscribes from all symbols.
|
|
157
|
+
*
|
|
158
|
+
* @remarks
|
|
159
|
+
* Since Tradier's streaming API doesn't support selective unsubscription,
|
|
160
|
+
* this method disconnects and reconnects without any symbols.
|
|
161
|
+
*/
|
|
162
|
+
async unsubscribeFromAll() {
|
|
163
|
+
this.subscribedSymbols.clear();
|
|
164
|
+
// If connected, reconnect with empty symbol list
|
|
165
|
+
if (this.connected) {
|
|
166
|
+
await this.reconnectWithSymbols();
|
|
167
|
+
}
|
|
153
168
|
}
|
|
154
169
|
/**
|
|
155
170
|
* Returns whether the client is currently connected.
|
|
@@ -176,7 +191,7 @@ class TradierClient {
|
|
|
176
191
|
const response = await fetch(url, {
|
|
177
192
|
method: 'GET',
|
|
178
193
|
headers: {
|
|
179
|
-
'Authorization': `Bearer ${this.
|
|
194
|
+
'Authorization': `Bearer ${this.authToken}`,
|
|
180
195
|
'Accept': 'application/json',
|
|
181
196
|
},
|
|
182
197
|
});
|
|
@@ -354,7 +369,7 @@ class TradierClient {
|
|
|
354
369
|
const response = await fetch(`${this.apiBaseUrl}/markets/events/session`, {
|
|
355
370
|
method: 'POST',
|
|
356
371
|
headers: {
|
|
357
|
-
'Authorization': `Bearer ${this.
|
|
372
|
+
'Authorization': `Bearer ${this.authToken}`,
|
|
358
373
|
'Accept': 'application/json',
|
|
359
374
|
},
|
|
360
375
|
});
|
|
@@ -431,6 +446,21 @@ class TradierClient {
|
|
|
431
446
|
// Reconnect attempt failed, will try again via onclose
|
|
432
447
|
}
|
|
433
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Reconnects with the current symbol list.
|
|
451
|
+
* Used for unsubscribe operations since Tradier doesn't support selective unsubscription.
|
|
452
|
+
*/
|
|
453
|
+
async reconnectWithSymbols() {
|
|
454
|
+
if (this.verbose) {
|
|
455
|
+
console.log(`[Tradier:WS] Reconnecting with ${this.subscribedSymbols.size} symbols`);
|
|
456
|
+
}
|
|
457
|
+
// Disconnect cleanly
|
|
458
|
+
this.disconnect();
|
|
459
|
+
// Wait briefly to ensure clean disconnect
|
|
460
|
+
await this.sleep(100);
|
|
461
|
+
// Reconnect with current symbol list
|
|
462
|
+
await this.connect();
|
|
463
|
+
}
|
|
434
464
|
/**
|
|
435
465
|
* Handles incoming WebSocket messages.
|
|
436
466
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fullstackcraftllc/floe",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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",
|