@fullstackcraftllc/floe 0.0.1 → 0.0.3
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 -210
- package/dist/adapters/index.js +64 -16
- package/dist/client/FloeClient.d.ts +411 -0
- package/dist/client/FloeClient.js +550 -0
- package/dist/client/brokers/SchwabClient.d.ts +2 -0
- package/dist/client/brokers/SchwabClient.js +6 -0
- package/dist/client/brokers/TradierClient.d.ts +393 -0
- package/dist/client/brokers/TradierClient.js +869 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +14 -1
- package/dist/types/index.d.ts +35 -0
- package/dist/utils/occ.d.ts +164 -0
- package/dist/utils/occ.js +203 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# `floe`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
  
|
|
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
|
+
|
|
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).
|
|
8
|
+
|
|
9
|
+
## Quick Start / Documentation / Examples
|
|
10
|
+
|
|
11
|
+
[fullstackcraft.github.io/floe](https://fullstackcraft.github.io/floe)
|
|
6
12
|
|
|
7
13
|
## 📋 Dual License
|
|
8
14
|
|
|
@@ -30,171 +36,6 @@ The same library that is used in Full Stack Craft's various fintech products inc
|
|
|
30
36
|
npm install @fullstackcraftllc/floe
|
|
31
37
|
```
|
|
32
38
|
|
|
33
|
-
## Quick Start
|
|
34
|
-
|
|
35
|
-
Nearly everything in `floe` revolves around the `NormalizedOption` interface, which provides a consistent way to represent options data regardless of the broker source:
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
/**
|
|
39
|
-
* Normalized option data structure (broker-agnostic)
|
|
40
|
-
*/
|
|
41
|
-
export interface NormalizedOption {
|
|
42
|
-
/** Underlying symbol */
|
|
43
|
-
symbol: string;
|
|
44
|
-
/** Strike price */
|
|
45
|
-
strike: number;
|
|
46
|
-
/** Expiration date (ISO 8601) */
|
|
47
|
-
expiration: string;
|
|
48
|
-
/** Option type */
|
|
49
|
-
optionType: OptionType;
|
|
50
|
-
/** Current bid price */
|
|
51
|
-
bid: number;
|
|
52
|
-
/** Current ask price */
|
|
53
|
-
ask: number;
|
|
54
|
-
/** Last traded price */
|
|
55
|
-
last: number;
|
|
56
|
-
/** Trading volume */
|
|
57
|
-
volume: number;
|
|
58
|
-
/** Open interest */
|
|
59
|
-
openInterest: number;
|
|
60
|
-
/** Implied volatility (as decimal) */
|
|
61
|
-
impliedVolatility: number;
|
|
62
|
-
/** Pre-calculated Greeks (optional) */
|
|
63
|
-
greeks?: Greeks;
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
import { blackScholes, calculateGreeks, calculateGEX } from '@fullstackcraftllc/floe';
|
|
69
|
-
|
|
70
|
-
// Calculate option price
|
|
71
|
-
const price = blackScholes({
|
|
72
|
-
spot: 100,
|
|
73
|
-
strike: 105,
|
|
74
|
-
timeToExpiry: 0.25,
|
|
75
|
-
volatility: 0.20,
|
|
76
|
-
riskFreeRate: 0.05,
|
|
77
|
-
optionType: 'call'
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Calculate Greeks
|
|
81
|
-
const greeks = calculateGreeks({
|
|
82
|
-
spot: 100,
|
|
83
|
-
strike: 105,
|
|
84
|
-
timeToExpiry: 0.25,
|
|
85
|
-
volatility: 0.20,
|
|
86
|
-
riskFreeRate: 0.05,
|
|
87
|
-
optionType: 'call'
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const normalizedOptions = [
|
|
91
|
-
{
|
|
92
|
-
symbol: 'AAPL',
|
|
93
|
-
strike: 105,
|
|
94
|
-
expiration: '2025-12-20',
|
|
95
|
-
optionType: 'call',
|
|
96
|
-
bid: 2.50,
|
|
97
|
-
ask: 2.60,
|
|
98
|
-
last: 2.55,
|
|
99
|
-
volume: 150,
|
|
100
|
-
openInterest: 2000,
|
|
101
|
-
impliedVolatility: 0.22
|
|
102
|
-
},
|
|
103
|
-
// ...more options
|
|
104
|
-
];
|
|
105
|
-
|
|
106
|
-
const gexData = calculateGEX(normalizedOptions, 100, 100);
|
|
107
|
-
|
|
108
|
-
console.log('Option Price:', price);
|
|
109
|
-
console.log('Delta:', greeks.delta);
|
|
110
|
-
console.log('Gamma:', greeks.gamma);
|
|
111
|
-
console.log('Net Gamma:', gexData.netGamma);
|
|
112
|
-
console.log('Strike of Max Gamma:', gexData.maxPositiveStrike);
|
|
113
|
-
console.log('Strike of Min Gamma:', gexData.maxNegativeStrike);
|
|
114
|
-
console.log('Zero Gamma Level:', gexData.zeroGammaLevel);
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Broker Normalization
|
|
118
|
-
|
|
119
|
-
Floe provides adapters for normalizing options data from multiple brokers:
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
import { normalizeTastyworksData } from '@fullstackcraftllc/floe';
|
|
123
|
-
|
|
124
|
-
// Tastyworks data
|
|
125
|
-
const normalizedOptions = normalizeTastyworksData(rawTastyData);
|
|
126
|
-
|
|
127
|
-
// Now both use the same interface
|
|
128
|
-
normalizedOptions.forEach(option => {
|
|
129
|
-
const greeks = calculateGreeks(option);
|
|
130
|
-
console.log(greeks);
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## For Devs - Example / Documentation Site
|
|
135
|
-
|
|
136
|
-
The website at [fullstackcraft.github.io/floe](https://fullstackcraft.github.io/floe) contains live code examples and documentation and is here locally at `./site`. It is a static site build with Next.js.
|
|
137
|
-
|
|
138
|
-
## API Documentation
|
|
139
|
-
|
|
140
|
-
### Black-Scholes Pricing
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
blackScholes(params: BlackScholesParams): number
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Calculate option price using Black-Scholes model.
|
|
147
|
-
|
|
148
|
-
**Parameters:**
|
|
149
|
-
- `spot`: Current price of underlying asset
|
|
150
|
-
- `strike`: Strike price of option
|
|
151
|
-
- `timeToExpiry`: Time to expiration in years
|
|
152
|
-
- `volatility`: Implied volatility (annualized)
|
|
153
|
-
- `riskFreeRate`: Risk-free interest rate (annualized)
|
|
154
|
-
- `optionType`: 'call' | 'put'
|
|
155
|
-
- `dividendYield?`: Dividend yield (optional, default 0)
|
|
156
|
-
|
|
157
|
-
### Greeks Calculation
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
calculateGreeks(params: BlackScholesParams): Greeks
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Calculate all Greeks for an option.
|
|
164
|
-
|
|
165
|
-
**Returns:**
|
|
166
|
-
- `delta`: Rate of change of option price with respect to underlying price
|
|
167
|
-
- `gamma`: Rate of change of delta with respect to underlying price
|
|
168
|
-
- `theta`: Rate of change of option price with respect to time
|
|
169
|
-
- `vega`: Rate of change of option price with respect to volatility
|
|
170
|
-
- `rho`: Rate of change of option price with respect to interest rate
|
|
171
|
-
|
|
172
|
-
### Estimate Implied Volatility Surface
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
estimateIVSurface(options: NormalizedOption[], spot: number): IVSurface
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Dealer Exposure Metrics
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
calculateGEX(options: NormalizedOption[]): GEXMetrics
|
|
182
|
-
calculateVanna(options: NormalizedOption[]): VannaMetrics
|
|
183
|
-
calculateCharm(options: NormalizedOption[]): CharmMetrics
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Notional Intraday Dealer Exposure Metrics
|
|
187
|
-
|
|
188
|
-
This process assume that dealer inventory consists of the previously reported open interest at t=0 (09:30AM EST), and that all options volume is bought or sold at market price based on the NBBO at the time of the trade. This is a simplification and may not reflect actual dealer inventory changes throughout the day.
|
|
189
|
-
|
|
190
|
-
Note that the nature of this calculation requires continuous intraday data including time-stamped trades and quotes to accurately model dealer inventory changes.
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
calculateIntradayGEX(trades: Trade[], quotes: Quote[], initialOpenInterest: Map<string, number>, spotPrices: Map<string, number>): IntradayGEXMetrics
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Calculate dealer exposure metrics across an options chain.
|
|
197
|
-
|
|
198
39
|
## License
|
|
199
40
|
|
|
200
41
|
**Free for Individuals** - Use the MIT License for personal, educational, and non-commercial projects.
|
|
@@ -207,45 +48,7 @@ See [LICENSE.md](LICENSE.md) for full details.
|
|
|
207
48
|
|
|
208
49
|
## Pricing
|
|
209
50
|
|
|
210
|
-
|
|
211
|
-
**$0/month** - Free forever
|
|
212
|
-
- ✅ Personal projects
|
|
213
|
-
- ✅ Open-source projects
|
|
214
|
-
- ✅ Educational use
|
|
215
|
-
- ✅ Community support
|
|
216
|
-
|
|
217
|
-
### Developer (Commercial)
|
|
218
|
-
**$149/month** or **$1,490/year**
|
|
219
|
-
- ✅ Commercial use
|
|
220
|
-
- ✅ Up to 100K calculations/month
|
|
221
|
-
- ✅ Email support
|
|
222
|
-
- ✅ All broker integrations
|
|
223
|
-
|
|
224
|
-
### Professional (Commercial)
|
|
225
|
-
**$499/month** or **$4,990/year**
|
|
226
|
-
- ✅ Unlimited calculations
|
|
227
|
-
- ✅ Priority support
|
|
228
|
-
- ✅ SLA guarantees
|
|
229
|
-
- ✅ Custom integrations
|
|
230
|
-
|
|
231
|
-
### Enterprise (Commercial)
|
|
232
|
-
**Custom pricing**
|
|
233
|
-
- ✅ White-label options
|
|
234
|
-
- ✅ Dedicated support
|
|
235
|
-
- ✅ On-premise deployment
|
|
236
|
-
- ✅ Custom broker adapters
|
|
237
|
-
|
|
238
|
-
[Contact hi@fullstackcraft.com for Enterprise pricing](mailto:hi@fullstackcraft.com)
|
|
239
|
-
|
|
240
|
-
## Documentation
|
|
241
|
-
|
|
242
|
-
Full documentation coming soon at [fullstackcraft.github.io/floe](https://fullstackcraft.github.io/floe)
|
|
243
|
-
|
|
244
|
-
## Support
|
|
245
|
-
|
|
246
|
-
- **Email:** hi@fullstackcraft.com
|
|
247
|
-
- **Bug Reports:** [GitHub Issues](https://github.com/FullStackCraft/floe/issues)
|
|
248
|
-
- **Discussions:** [GitHub Discussions](https://github.com/FullStackCraft/floe/discussions)
|
|
51
|
+
[Contact hi@fullstackcraft.com for pricing](mailto:hi@fullstackcraft.com)
|
|
249
52
|
|
|
250
53
|
## Contributing
|
|
251
54
|
|
|
@@ -253,16 +56,13 @@ Contributions welcome! Please open an issue or PR.
|
|
|
253
56
|
|
|
254
57
|
By contributing, you agree that your contributions will be licensed under the same dual-license terms.
|
|
255
58
|
|
|
256
|
-
##
|
|
59
|
+
## TODOs
|
|
257
60
|
|
|
258
|
-
- [ ] Homepage / documentation site
|
|
259
|
-
- [ ] Volatility surface estimation with a variety of interpolation methods
|
|
260
61
|
- [ ] Implied PDF calculations
|
|
261
62
|
- [ ] Tradier integration, normalization, and docs
|
|
262
63
|
- [ ] TradeStation integration, normalization, and docs
|
|
263
64
|
- [ ] Interactive Brokers integration, normalization, and docs
|
|
264
65
|
|
|
265
|
-
|
|
266
66
|
## Credits
|
|
267
67
|
|
|
268
68
|
Built with ❤️ by [Full Stack Craft LLC](https://fullstackcraft.com)
|
package/dist/adapters/index.js
CHANGED
|
@@ -3,23 +3,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.brokerAdapters = exports.tdaAdapter = exports.ibkrAdapter = exports.schwabAdapter = exports.genericAdapter = void 0;
|
|
4
4
|
exports.getAdapter = getAdapter;
|
|
5
5
|
exports.createOptionChain = createOptionChain;
|
|
6
|
+
const occ_1 = require("../utils/occ");
|
|
7
|
+
/**
|
|
8
|
+
* Helper to build OCC symbol from raw data
|
|
9
|
+
*/
|
|
10
|
+
function buildOCCFromRaw(underlying, expiration, optionType, strike) {
|
|
11
|
+
try {
|
|
12
|
+
return (0, occ_1.buildOCCSymbol)({ symbol: underlying, expiration, optionType, strike });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
6
18
|
/**
|
|
7
19
|
* Generic adapter that maps common field names
|
|
8
20
|
* This is a fallback adapter when broker-specific adapters are not available
|
|
9
21
|
*/
|
|
10
22
|
const genericAdapter = (data) => {
|
|
23
|
+
const underlying = String(data.underlying || data.symbol || data.underlyingSymbol || '');
|
|
24
|
+
const expiration = data.expiration || data.expirationDate || '';
|
|
25
|
+
const optionType = (data.optionType || data.putCall || '').toLowerCase() === 'call' ? 'call' : 'put';
|
|
26
|
+
const strike = Number(data.strike || data.strikePrice || 0);
|
|
11
27
|
return {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
occSymbol: data.occSymbol || data.symbol || buildOCCFromRaw(underlying, expiration, optionType, strike),
|
|
29
|
+
underlying,
|
|
30
|
+
strike,
|
|
31
|
+
expiration,
|
|
32
|
+
expirationTimestamp: data.expirationTimestamp || new Date(expiration).getTime(),
|
|
33
|
+
optionType,
|
|
16
34
|
bid: Number(data.bid || 0),
|
|
35
|
+
bidSize: Number(data.bidSize || data.bidQty || 0),
|
|
17
36
|
ask: Number(data.ask || 0),
|
|
37
|
+
askSize: Number(data.askSize || data.askQty || 0),
|
|
18
38
|
mark: Number(data.mark || (data.bid + data.ask) / 2 || 0),
|
|
19
39
|
last: Number(data.last || data.lastPrice || 0),
|
|
20
40
|
volume: Number(data.volume || 0),
|
|
21
41
|
openInterest: Number(data.openInterest || 0),
|
|
22
42
|
impliedVolatility: Number(data.impliedVolatility || data.iv || 0),
|
|
43
|
+
timestamp: Number(data.timestamp || Date.now()),
|
|
23
44
|
};
|
|
24
45
|
};
|
|
25
46
|
exports.genericAdapter = genericAdapter;
|
|
@@ -27,18 +48,27 @@ exports.genericAdapter = genericAdapter;
|
|
|
27
48
|
* Schwab-specific adapter for Schwab API responses
|
|
28
49
|
*/
|
|
29
50
|
const schwabAdapter = (data) => {
|
|
51
|
+
const underlying = String(data.underlying || data.underlyingSymbol || '');
|
|
52
|
+
const expiration = data.expirationDate || '';
|
|
53
|
+
const optionType = (data.putCall || '').toLowerCase() === 'call' ? 'call' : 'put';
|
|
54
|
+
const strike = Number(data.strikePrice || 0);
|
|
30
55
|
return {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
occSymbol: data.symbol || buildOCCFromRaw(underlying, expiration, optionType, strike),
|
|
57
|
+
underlying,
|
|
58
|
+
strike,
|
|
59
|
+
expiration,
|
|
60
|
+
expirationTimestamp: new Date(expiration).getTime(),
|
|
61
|
+
optionType,
|
|
35
62
|
bid: Number(data.bid || 0),
|
|
63
|
+
bidSize: Number(data.bidSize || 0),
|
|
36
64
|
ask: Number(data.ask || 0),
|
|
65
|
+
askSize: Number(data.askSize || 0),
|
|
37
66
|
mark: Number(data.mark || 0),
|
|
38
67
|
last: Number(data.last || 0),
|
|
39
68
|
volume: Number(data.totalVolume || 0),
|
|
40
69
|
openInterest: Number(data.openInterest || 0),
|
|
41
70
|
impliedVolatility: Number(data.volatility || 0),
|
|
71
|
+
timestamp: Number(data.quoteTime || Date.now()),
|
|
42
72
|
};
|
|
43
73
|
};
|
|
44
74
|
exports.schwabAdapter = schwabAdapter;
|
|
@@ -46,18 +76,27 @@ exports.schwabAdapter = schwabAdapter;
|
|
|
46
76
|
* Interactive Brokers adapter
|
|
47
77
|
*/
|
|
48
78
|
const ibkrAdapter = (data) => {
|
|
79
|
+
const underlying = String(data.underlying || data.symbol || '');
|
|
80
|
+
const expiration = data.lastTradeDateOrContractMonth || '';
|
|
81
|
+
const optionType = (data.right || '').toLowerCase() === 'c' ? 'call' : 'put';
|
|
82
|
+
const strike = Number(data.strike || 0);
|
|
49
83
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
84
|
+
occSymbol: data.localSymbol || buildOCCFromRaw(underlying, expiration, optionType, strike),
|
|
85
|
+
underlying,
|
|
86
|
+
strike,
|
|
87
|
+
expiration,
|
|
88
|
+
expirationTimestamp: new Date(expiration).getTime(),
|
|
89
|
+
optionType,
|
|
54
90
|
bid: Number(data.bid || 0),
|
|
91
|
+
bidSize: Number(data.bidSize || 0),
|
|
55
92
|
ask: Number(data.ask || 0),
|
|
93
|
+
askSize: Number(data.askSize || 0),
|
|
56
94
|
mark: Number(data.mark || (data.bid + data.ask) / 2 || 0),
|
|
57
95
|
last: Number(data.lastTradedPrice || 0),
|
|
58
96
|
volume: Number(data.volume || 0),
|
|
59
97
|
openInterest: Number(data.openInterest || 0),
|
|
60
98
|
impliedVolatility: Number(data.impliedVolatility || 0),
|
|
99
|
+
timestamp: Number(data.time || Date.now()),
|
|
61
100
|
};
|
|
62
101
|
};
|
|
63
102
|
exports.ibkrAdapter = ibkrAdapter;
|
|
@@ -65,18 +104,27 @@ exports.ibkrAdapter = ibkrAdapter;
|
|
|
65
104
|
* TD Ameritrade / Schwab TDA API adapter
|
|
66
105
|
*/
|
|
67
106
|
const tdaAdapter = (data) => {
|
|
107
|
+
const underlying = String(data.underlying || data.underlyingSymbol || '');
|
|
108
|
+
const expiration = data.expirationDate || '';
|
|
109
|
+
const optionType = (data.putCall || '').toLowerCase() === 'call' ? 'call' : 'put';
|
|
110
|
+
const strike = Number(data.strikePrice || 0);
|
|
68
111
|
return {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
112
|
+
occSymbol: data.symbol || buildOCCFromRaw(underlying, expiration, optionType, strike),
|
|
113
|
+
underlying,
|
|
114
|
+
strike,
|
|
115
|
+
expiration,
|
|
116
|
+
expirationTimestamp: expiration ? new Date(expiration).getTime() : 0,
|
|
117
|
+
optionType,
|
|
73
118
|
bid: Number(data.bid || 0),
|
|
119
|
+
bidSize: Number(data.bidSize || 0),
|
|
74
120
|
ask: Number(data.ask || 0),
|
|
121
|
+
askSize: Number(data.askSize || 0),
|
|
75
122
|
mark: Number(data.mark || 0),
|
|
76
123
|
last: Number(data.last || 0),
|
|
77
124
|
volume: Number(data.totalVolume || 0),
|
|
78
125
|
openInterest: Number(data.openInterest || 0),
|
|
79
126
|
impliedVolatility: Number(data.volatility || 0),
|
|
127
|
+
timestamp: Number(data.quoteTimeInLong || Date.now()),
|
|
80
128
|
};
|
|
81
129
|
};
|
|
82
130
|
exports.tdaAdapter = tdaAdapter;
|