@fullstackcraftllc/floe 0.0.13 → 0.0.15

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.
Files changed (44) hide show
  1. package/README.md +8 -8
  2. package/dist/client/FloeClient.d.ts +5 -1
  3. package/dist/client/FloeClient.js +54 -0
  4. package/dist/client/brokers/IBKRClient.d.ts +324 -0
  5. package/dist/client/brokers/IBKRClient.js +797 -0
  6. package/dist/client/brokers/TradierClient.js +7 -6
  7. package/dist/hedgeflow/charm.d.ts +23 -0
  8. package/dist/hedgeflow/charm.js +113 -0
  9. package/dist/hedgeflow/curve.d.ts +27 -0
  10. package/dist/hedgeflow/curve.js +315 -0
  11. package/dist/hedgeflow/index.d.ts +33 -0
  12. package/dist/hedgeflow/index.js +52 -0
  13. package/dist/hedgeflow/regime.d.ts +7 -0
  14. package/dist/hedgeflow/regime.js +99 -0
  15. package/dist/hedgeflow/types.d.ts +185 -0
  16. package/dist/hedgeflow/types.js +2 -0
  17. package/dist/impliedpdf/adjusted.d.ts +173 -0
  18. package/dist/impliedpdf/adjusted.js +500 -0
  19. package/dist/impliedpdf/index.d.ts +1 -0
  20. package/dist/impliedpdf/index.js +10 -0
  21. package/dist/index.d.ts +8 -2
  22. package/dist/index.js +27 -1
  23. package/dist/iv/index.d.ts +52 -0
  24. package/dist/iv/index.js +287 -0
  25. package/dist/iv/types.d.ts +40 -0
  26. package/dist/iv/types.js +2 -0
  27. package/dist/pressure/grid.d.ts +14 -0
  28. package/dist/pressure/grid.js +220 -0
  29. package/dist/pressure/index.d.ts +5 -0
  30. package/dist/pressure/index.js +22 -0
  31. package/dist/pressure/ivpath.d.ts +31 -0
  32. package/dist/pressure/ivpath.js +304 -0
  33. package/dist/pressure/normalize.d.ts +6 -0
  34. package/dist/pressure/normalize.js +76 -0
  35. package/dist/pressure/regime.d.ts +7 -0
  36. package/dist/pressure/regime.js +99 -0
  37. package/dist/pressure/types.d.ts +182 -0
  38. package/dist/pressure/types.js +2 -0
  39. package/dist/rv/index.d.ts +26 -0
  40. package/dist/rv/index.js +81 -0
  41. package/dist/rv/types.d.ts +32 -0
  42. package/dist/rv/types.js +2 -0
  43. package/dist/utils/indexOptions.js +2 -1
  44. package/package.json +1 -1
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deriveRegimeParams = deriveRegimeParams;
4
+ exports.interpolateIVAtStrike = interpolateIVAtStrike;
5
+ /**
6
+ * Derive regime parameters from the IV surface
7
+ */
8
+ function deriveRegimeParams(ivSurface, spot) {
9
+ const { strikes, smoothedIVs } = ivSurface;
10
+ const atmIV = interpolateIVAtStrike(strikes, smoothedIVs, spot) / 100;
11
+ const skew = calculateSkewAtSpot(strikes, smoothedIVs, spot);
12
+ const impliedSpotVolCorr = skewToCorrelation(skew);
13
+ const curvature = calculateCurvatureAtSpot(strikes, smoothedIVs, spot);
14
+ const impliedVolOfVol = curvatureToVolOfVol(curvature, atmIV);
15
+ const regime = ivToRegime(atmIV);
16
+ const expectedDailySpotMove = atmIV / Math.sqrt(252);
17
+ const expectedDailyVolMove = impliedVolOfVol / Math.sqrt(252);
18
+ return {
19
+ atmIV,
20
+ impliedSpotVolCorr,
21
+ impliedVolOfVol,
22
+ regime,
23
+ expectedDailySpotMove,
24
+ expectedDailyVolMove,
25
+ };
26
+ }
27
+ function skewToCorrelation(skew) {
28
+ const SKEW_TO_CORR_SCALE = 0.15;
29
+ return Math.max(-0.95, Math.min(0.5, skew * SKEW_TO_CORR_SCALE));
30
+ }
31
+ function curvatureToVolOfVol(curvature, atmIV) {
32
+ const VOL_OF_VOL_SCALE = 2.0;
33
+ return Math.sqrt(Math.abs(curvature)) * VOL_OF_VOL_SCALE * atmIV;
34
+ }
35
+ function ivToRegime(atmIV) {
36
+ if (atmIV < 0.15)
37
+ return 'calm';
38
+ if (atmIV < 0.20)
39
+ return 'normal';
40
+ if (atmIV < 0.35)
41
+ return 'stressed';
42
+ return 'crisis';
43
+ }
44
+ function interpolateIVAtStrike(strikes, ivs, targetStrike) {
45
+ if (strikes.length === 0 || ivs.length === 0)
46
+ return 20;
47
+ if (strikes.length === 1)
48
+ return ivs[0];
49
+ let lower = 0;
50
+ let upper = strikes.length - 1;
51
+ for (let i = 0; i < strikes.length - 1; i++) {
52
+ if (strikes[i] <= targetStrike && strikes[i + 1] >= targetStrike) {
53
+ lower = i;
54
+ upper = i + 1;
55
+ break;
56
+ }
57
+ }
58
+ if (targetStrike <= strikes[0])
59
+ return ivs[0];
60
+ if (targetStrike >= strikes[strikes.length - 1])
61
+ return ivs[ivs.length - 1];
62
+ const t = (targetStrike - strikes[lower]) / (strikes[upper] - strikes[lower]);
63
+ return ivs[lower] + t * (ivs[upper] - ivs[lower]);
64
+ }
65
+ function calculateSkewAtSpot(strikes, ivs, spot) {
66
+ if (strikes.length < 2)
67
+ return 0;
68
+ let lowerIdx = 0;
69
+ let upperIdx = strikes.length - 1;
70
+ for (let i = 0; i < strikes.length - 1; i++) {
71
+ if (strikes[i] <= spot && strikes[i + 1] >= spot) {
72
+ lowerIdx = i;
73
+ upperIdx = i + 1;
74
+ break;
75
+ }
76
+ }
77
+ const dIV = ivs[upperIdx] - ivs[lowerIdx];
78
+ const dK = strikes[upperIdx] - strikes[lowerIdx];
79
+ return dK > 0 ? (dIV / dK) * spot : 0;
80
+ }
81
+ function calculateCurvatureAtSpot(strikes, ivs, spot) {
82
+ if (strikes.length < 3)
83
+ return 0;
84
+ let centerIdx = 0;
85
+ for (let i = 0; i < strikes.length; i++) {
86
+ if (Math.abs(strikes[i] - spot) < Math.abs(strikes[centerIdx] - spot)) {
87
+ centerIdx = i;
88
+ }
89
+ }
90
+ if (centerIdx === 0 || centerIdx === strikes.length - 1)
91
+ return 0;
92
+ const h = (strikes[centerIdx + 1] - strikes[centerIdx - 1]) / 2;
93
+ if (h <= 0)
94
+ return 0;
95
+ const ivMinus = ivs[centerIdx - 1];
96
+ const iv = ivs[centerIdx];
97
+ const ivPlus = ivs[centerIdx + 1];
98
+ return ((ivPlus - 2 * iv + ivMinus) / (h * h)) * spot * spot;
99
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Regime classification based on IV level and surface characteristics
3
+ */
4
+ export type MarketRegime = 'calm' | 'normal' | 'stressed' | 'crisis';
5
+ /**
6
+ * Parameters derived from the IV surface itself (no external data needed)
7
+ */
8
+ export interface RegimeParams {
9
+ /** ATM implied volatility (as decimal, e.g., 0.18 for 18%) */
10
+ atmIV: number;
11
+ /** Implied spot-vol correlation derived from skew */
12
+ impliedSpotVolCorr: number;
13
+ /** Implied vol-of-vol derived from smile curvature */
14
+ impliedVolOfVol: number;
15
+ /** Regime classification */
16
+ regime: MarketRegime;
17
+ /** Expected daily spot move (as decimal) */
18
+ expectedDailySpotMove: number;
19
+ /** Expected daily vol move (as decimal) */
20
+ expectedDailyVolMove: number;
21
+ }
22
+ /**
23
+ * Configuration for the hedge impulse curve computation
24
+ */
25
+ export interface HedgeImpulseConfig {
26
+ /** Price grid range as percentage of spot (e.g. 3 for ±3%). Default: 3 */
27
+ rangePercent?: number;
28
+ /** Price grid step as percentage of spot (e.g. 0.05 for 0.05%). Default: 0.05 */
29
+ stepPercent?: number;
30
+ /**
31
+ * Gaussian kernel width in number of strike spacings.
32
+ * Auto-detected from the option chain's strike spacing.
33
+ * Default: 2 (i.e. lambda = 2 * modal strike spacing)
34
+ */
35
+ kernelWidthStrikes?: number;
36
+ }
37
+ /**
38
+ * Hedge impulse value at a single price level
39
+ *
40
+ * The hedge impulse H(S) represents the net dealer delta hedge change
41
+ * per unit spot move if price were at S. It combines gamma and vanna
42
+ * via the empirical spot-vol coupling:
43
+ *
44
+ * H(S) = Gamma(S) - (k / S) * Vanna(S)
45
+ *
46
+ * where k is derived from the IV surface skew slope.
47
+ */
48
+ export interface HedgeImpulsePoint {
49
+ /** Price level */
50
+ price: number;
51
+ /** Smoothed gamma exposure at this price level (kernel-weighted from strikes) */
52
+ gamma: number;
53
+ /** Smoothed vanna exposure at this price level (kernel-weighted from strikes) */
54
+ vanna: number;
55
+ /** Combined hedge impulse: gamma - (k/S) * vanna */
56
+ impulse: number;
57
+ }
58
+ /**
59
+ * A zero crossing of the hedge impulse curve
60
+ */
61
+ export interface ZeroCrossing {
62
+ /** Price at which the crossing occurs (interpolated) */
63
+ price: number;
64
+ /** Direction: 'rising' means impulse goes from negative to positive */
65
+ direction: 'rising' | 'falling';
66
+ }
67
+ /**
68
+ * A local extremum (basin or peak) of the hedge impulse curve
69
+ */
70
+ export interface ImpulseExtremum {
71
+ /** Price at the extremum */
72
+ price: number;
73
+ /** Impulse value at the extremum */
74
+ impulse: number;
75
+ /** 'basin' = positive local max (attractor), 'peak' = negative local min (accelerator) */
76
+ type: 'basin' | 'peak';
77
+ }
78
+ /**
79
+ * Directional asymmetry analysis around current spot
80
+ */
81
+ export interface DirectionalAsymmetry {
82
+ /** Integrated impulse from spot to spot + integrationRange */
83
+ upside: number;
84
+ /** Integrated impulse from spot to spot - integrationRange */
85
+ downside: number;
86
+ /** Integration range used (percentage of spot) */
87
+ integrationRangePercent: number;
88
+ /** Which side has more negative impulse (= path of least resistance) */
89
+ bias: 'up' | 'down' | 'neutral';
90
+ /** Ratio of |upside| to |downside| impulse. >1 means upside is larger magnitude */
91
+ asymmetryRatio: number;
92
+ }
93
+ /**
94
+ * Regime classification based on the impulse curve shape
95
+ */
96
+ export type ImpulseRegime = 'pinned' | 'expansion' | 'squeeze-up' | 'squeeze-down' | 'neutral';
97
+ /**
98
+ * Complete hedge impulse curve result
99
+ */
100
+ export interface HedgeImpulseCurve {
101
+ /** Current spot price */
102
+ spot: number;
103
+ /** Expiration timestamp */
104
+ expiration: number;
105
+ /** Timestamp when this curve was computed */
106
+ computedAt: number;
107
+ /** Spot-vol coupling coefficient derived from IV surface */
108
+ spotVolCoupling: number;
109
+ /** Kernel width used (in price units) */
110
+ kernelWidth: number;
111
+ /** Strike spacing detected from the option chain */
112
+ strikeSpacing: number;
113
+ /** The full curve of impulse values across the price grid */
114
+ curve: HedgeImpulsePoint[];
115
+ /** Impulse value at current spot (interpolated) */
116
+ impulseAtSpot: number;
117
+ /** Slope of the impulse curve at current spot (dH/dS) */
118
+ slopeAtSpot: number;
119
+ /** Zero crossings of the impulse curve */
120
+ zeroCrossings: ZeroCrossing[];
121
+ /** Local extrema (basins = attractors, peaks = accelerators) */
122
+ extrema: ImpulseExtremum[];
123
+ /** Directional asymmetry analysis */
124
+ asymmetry: DirectionalAsymmetry;
125
+ /** Regime classification */
126
+ regime: ImpulseRegime;
127
+ /** Nearest attractor (positive impulse basin) above spot, if any */
128
+ nearestAttractorAbove: number | null;
129
+ /** Nearest attractor (positive impulse basin) below spot, if any */
130
+ nearestAttractorBelow: number | null;
131
+ }
132
+ /**
133
+ * Configuration for charm integral computation
134
+ */
135
+ export interface CharmIntegralConfig {
136
+ /** Time step for the integral in minutes. Default: 15 */
137
+ timeStepMinutes?: number;
138
+ }
139
+ /**
140
+ * Charm integral at a single time bucket
141
+ */
142
+ export interface CharmBucket {
143
+ /** Minutes remaining to expiry at start of this bucket */
144
+ minutesRemaining: number;
145
+ /** Instantaneous CEX at this time */
146
+ instantaneousCEX: number;
147
+ /** Cumulative CEX from now to this time */
148
+ cumulativeCEX: number;
149
+ }
150
+ /**
151
+ * Complete charm integral result
152
+ */
153
+ export interface CharmIntegral {
154
+ /** Current spot price */
155
+ spot: number;
156
+ /** Expiration timestamp */
157
+ expiration: number;
158
+ /** Timestamp when this was computed */
159
+ computedAt: number;
160
+ /** Minutes remaining from computation time to expiry */
161
+ minutesRemaining: number;
162
+ /** Total charm integral from now to close (cumulative expected delta change) */
163
+ totalCharmToClose: number;
164
+ /** Direction: positive = net buying pressure from charm, negative = net selling */
165
+ direction: 'buying' | 'selling' | 'neutral';
166
+ /** Bucketed charm integral curve for visualization */
167
+ buckets: CharmBucket[];
168
+ /** Per-strike charm breakdown (for understanding what drives the integral) */
169
+ strikeContributions: Array<{
170
+ strike: number;
171
+ charmExposure: number;
172
+ fractionOfTotal: number;
173
+ }>;
174
+ }
175
+ /**
176
+ * Complete hedge flow analysis combining impulse curve and charm integral
177
+ */
178
+ export interface HedgeFlowAnalysis {
179
+ /** The instantaneous hedge impulse curve (left panel) */
180
+ impulseCurve: HedgeImpulseCurve;
181
+ /** The charm integral to close (right panel) */
182
+ charmIntegral: CharmIntegral;
183
+ /** Regime params derived from IV surface */
184
+ regimeParams: RegimeParams;
185
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,173 @@
1
+ import { ImpliedProbabilityDistribution, getProbabilityInRange, getCumulativeProbability, getQuantile } from './index';
2
+ import { ExposurePerExpiry, NormalizedOption } from '../types';
3
+ /**
4
+ * Configuration for exposure-based PDF adjustments
5
+ */
6
+ export interface ExposureAdjustmentConfig {
7
+ /** Gamma adjustment settings */
8
+ gamma: {
9
+ /** Enable gamma-based kurtosis adjustment */
10
+ enabled: boolean;
11
+ /** Strength of attractor effect at +GEX strikes (0-1) */
12
+ attractorStrength: number;
13
+ /** Strength of repellent effect at -GEX strikes (0-1) */
14
+ repellentStrength: number;
15
+ /** Minimum absolute GEX to consider significant (in dollars) */
16
+ threshold: number;
17
+ /** Decay rate for influence distance (higher = more localized) */
18
+ decayRate: number;
19
+ };
20
+ /** Vanna adjustment settings */
21
+ vanna: {
22
+ /** Enable vanna-based tail adjustment */
23
+ enabled: boolean;
24
+ /** Spot-vol beta: IV change per 1% spot move (typically -2 to -4 for indices) */
25
+ spotVolBeta: number;
26
+ /** Maximum tail fattening multiplier */
27
+ maxTailMultiplier: number;
28
+ /** Number of feedback iterations to simulate */
29
+ feedbackIterations: number;
30
+ };
31
+ /** Charm adjustment settings */
32
+ charm: {
33
+ /** Enable charm-based mean shift */
34
+ enabled: boolean;
35
+ /** Time horizon multiplier ('intraday' = 0.25, 'daily' = 1.0, 'weekly' = 5.0) */
36
+ timeHorizon: 'intraday' | 'daily' | 'weekly';
37
+ /** Scaling factor for mean shift */
38
+ shiftScale: number;
39
+ };
40
+ }
41
+ /**
42
+ * Result of exposure-adjusted PDF calculation
43
+ */
44
+ export interface AdjustedPDFResult {
45
+ /** Original market-implied distribution */
46
+ baseline: ImpliedProbabilityDistribution;
47
+ /** Exposure-adjusted distribution */
48
+ adjusted: ImpliedProbabilityDistribution;
49
+ /** Gamma modifier applied at each strike (multiplicative) */
50
+ gammaModifiers: number[];
51
+ /** Vanna modifier applied at each strike (multiplicative) */
52
+ vannaModifiers: number[];
53
+ /** Charm-induced mean shift (in price units) */
54
+ charmShift: number;
55
+ /** Comparison metrics between baseline and adjusted */
56
+ comparison: PDFComparison;
57
+ }
58
+ /**
59
+ * Comparison metrics between baseline and adjusted PDFs
60
+ */
61
+ export interface PDFComparison {
62
+ /** Shift in expected value */
63
+ meanShift: number;
64
+ /** Shift as percentage of spot */
65
+ meanShiftPercent: number;
66
+ /** Change in standard deviation */
67
+ stdDevChange: number;
68
+ /** Change in tail skew ratio */
69
+ tailSkewChange: number;
70
+ /** 5th percentile: baseline vs adjusted */
71
+ leftTail: {
72
+ baseline: number;
73
+ adjusted: number;
74
+ ratio: number;
75
+ };
76
+ /** 95th percentile: baseline vs adjusted */
77
+ rightTail: {
78
+ baseline: number;
79
+ adjusted: number;
80
+ ratio: number;
81
+ };
82
+ /** Dominant adjustment factor */
83
+ dominantFactor: 'gamma' | 'vanna' | 'charm' | 'none';
84
+ }
85
+ /**
86
+ * Default configuration tuned for SPX-like indices
87
+ */
88
+ export declare const DEFAULT_ADJUSTMENT_CONFIG: ExposureAdjustmentConfig;
89
+ /**
90
+ * Configuration for low volatility / grinding markets
91
+ */
92
+ export declare const LOW_VOL_CONFIG: ExposureAdjustmentConfig;
93
+ /**
94
+ * Configuration for high volatility / crisis markets
95
+ */
96
+ export declare const CRISIS_CONFIG: ExposureAdjustmentConfig;
97
+ /**
98
+ * Configuration for OPEX week
99
+ */
100
+ export declare const OPEX_CONFIG: ExposureAdjustmentConfig;
101
+ /**
102
+ * Estimate an exposure-adjusted implied probability distribution.
103
+ *
104
+ * This function takes the standard market-implied PDF (Breeden-Litzenberger)
105
+ * and adjusts it based on dealer Greek exposures to produce a "mechanically-informed"
106
+ * probability distribution that accounts for:
107
+ *
108
+ * - **Gamma**: Creates "sticky" zones (+GEX) where price pins, and "slippery" zones
109
+ * (-GEX) where price accelerates through
110
+ * - **Vanna**: Fattens tails based on the IV-spot feedback loop (selloffs spike IV,
111
+ * which forces more selling via negative vanna)
112
+ * - **Charm**: Shifts the mean based on predictable delta decay over time
113
+ *
114
+ * @param symbol - Underlying ticker symbol
115
+ * @param underlyingPrice - Current spot price
116
+ * @param callOptions - Call options for a single expiry
117
+ * @param exposures - Exposure metrics from calculateGammaVannaCharmExposures()
118
+ * @param config - Adjustment configuration (uses defaults if not provided)
119
+ * @returns Baseline and adjusted PDFs with comparison metrics
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * // Get exposures first
124
+ * const allExposures = calculateGammaVannaCharmExposures(chain, ivSurfaces);
125
+ * const expiryExposures = allExposures.find(e => e.expiration === targetExpiry);
126
+ *
127
+ * // Calculate adjusted PDF
128
+ * const result = estimateExposureAdjustedPDF(
129
+ * 'SPX',
130
+ * 4520,
131
+ * callOptionsForExpiry,
132
+ * expiryExposures
133
+ * );
134
+ *
135
+ * // Compare probabilities
136
+ * const target = 4400;
137
+ * const baselineProb = getCumulativeProbability(result.baseline, target);
138
+ * const adjustedProb = getCumulativeProbability(result.adjusted, target);
139
+ * console.log(`Market says ${baselineProb}% chance of ${target}`);
140
+ * console.log(`Flow-adjusted: ${adjustedProb}% chance`);
141
+ * ```
142
+ */
143
+ export declare function estimateExposureAdjustedPDF(symbol: string, underlyingPrice: number, callOptions: NormalizedOption[], exposures: ExposurePerExpiry, config?: Partial<ExposureAdjustmentConfig>): AdjustedPDFResult | {
144
+ success: false;
145
+ error: string;
146
+ };
147
+ /**
148
+ * Get the "edge" - the difference between market-implied and flow-adjusted
149
+ * probability of reaching a price level.
150
+ *
151
+ * Positive edge means the market is underpricing the probability of reaching that level.
152
+ * Negative edge means the market is overpricing it.
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * const result = estimateExposureAdjustedPDF(...);
157
+ * const edge = getEdgeAtPrice(result, 4400);
158
+ * console.log(`Edge at 4400: ${(edge * 100).toFixed(2)}%`);
159
+ * // Output: "Edge at 4400: 2.35%"
160
+ * // Meaning: flow mechanics suggest 2.35% higher probability than market prices
161
+ * ```
162
+ */
163
+ export declare function getEdgeAtPrice(result: AdjustedPDFResult, price: number): number;
164
+ /**
165
+ * Get price levels where the adjustment is most significant
166
+ */
167
+ export declare function getSignificantAdjustmentLevels(result: AdjustedPDFResult, threshold?: number): Array<{
168
+ strike: number;
169
+ baselineProb: number;
170
+ adjustedProb: number;
171
+ edge: number;
172
+ }>;
173
+ export { getProbabilityInRange, getCumulativeProbability, getQuantile };