@fullstackcraftllc/floe 0.0.16 → 0.0.18
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/dist/hedgeflow/index.d.ts +2 -0
- package/dist/hedgeflow/index.js +4 -1
- package/dist/hedgeflow/pressurecloud.d.ts +166 -0
- package/dist/hedgeflow/pressurecloud.js +243 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +9 -2
- package/dist/volresponse/index.d.ts +42 -0
- package/dist/volresponse/index.js +251 -0
- package/dist/volresponse/types.d.ts +75 -0
- package/dist/volresponse/types.js +2 -0
- package/package.json +5 -2
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ExposurePerExpiry, IVSurface } from '../types';
|
|
2
2
|
import { HedgeImpulseConfig, CharmIntegralConfig, HedgeFlowAnalysis } from './types';
|
|
3
3
|
export type { MarketRegime, RegimeParams, HedgeImpulseConfig, HedgeImpulsePoint, HedgeImpulseCurve, ZeroCrossing, ImpulseExtremum, DirectionalAsymmetry, ImpulseRegime, CharmIntegralConfig, CharmBucket, CharmIntegral, HedgeFlowAnalysis, } from './types';
|
|
4
|
+
export type { HedgeContractEstimates, PressureZone, RegimeEdge, PressureLevel, PressureCloudConfig, PressureCloud, } from './pressurecloud';
|
|
4
5
|
export { deriveRegimeParams, interpolateIVAtStrike } from './regime';
|
|
5
6
|
export { computeHedgeImpulseCurve } from './curve';
|
|
6
7
|
export { computeCharmIntegral } from './charm';
|
|
8
|
+
export { computePressureCloud } from './pressurecloud';
|
|
7
9
|
/**
|
|
8
10
|
* Compute a complete hedge flow analysis for a single expiration.
|
|
9
11
|
*
|
package/dist/hedgeflow/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.computeCharmIntegral = exports.computeHedgeImpulseCurve = exports.interpolateIVAtStrike = exports.deriveRegimeParams = void 0;
|
|
3
|
+
exports.computePressureCloud = exports.computeCharmIntegral = exports.computeHedgeImpulseCurve = exports.interpolateIVAtStrike = exports.deriveRegimeParams = void 0;
|
|
4
4
|
exports.analyzeHedgeFlow = analyzeHedgeFlow;
|
|
5
5
|
const regime_1 = require("./regime");
|
|
6
6
|
const curve_1 = require("./curve");
|
|
@@ -14,6 +14,9 @@ var curve_2 = require("./curve");
|
|
|
14
14
|
Object.defineProperty(exports, "computeHedgeImpulseCurve", { enumerable: true, get: function () { return curve_2.computeHedgeImpulseCurve; } });
|
|
15
15
|
var charm_2 = require("./charm");
|
|
16
16
|
Object.defineProperty(exports, "computeCharmIntegral", { enumerable: true, get: function () { return charm_2.computeCharmIntegral; } });
|
|
17
|
+
// Re-export pressure cloud
|
|
18
|
+
var pressurecloud_1 = require("./pressurecloud");
|
|
19
|
+
Object.defineProperty(exports, "computePressureCloud", { enumerable: true, get: function () { return pressurecloud_1.computePressureCloud; } });
|
|
17
20
|
/**
|
|
18
21
|
* Compute a complete hedge flow analysis for a single expiration.
|
|
19
22
|
*
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { HedgeImpulseCurve } from './types';
|
|
2
|
+
import { RegimeParams } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Expected dealer hedge volume in futures contracts for a 1-point spot move.
|
|
5
|
+
* Positive = dealers buying, negative = dealers selling.
|
|
6
|
+
*/
|
|
7
|
+
export interface HedgeContractEstimates {
|
|
8
|
+
/** E-mini Nasdaq 100 (multiplier: 20) */
|
|
9
|
+
nq: number;
|
|
10
|
+
/** Micro E-mini Nasdaq 100 (multiplier: 2) */
|
|
11
|
+
mnq: number;
|
|
12
|
+
/** E-mini S&P 500 (multiplier: 50) */
|
|
13
|
+
es: number;
|
|
14
|
+
/** Micro E-mini S&P 500 (multiplier: 5) */
|
|
15
|
+
mes: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A zone where price is likely to stabilize (positive impulse)
|
|
19
|
+
* or accelerate (negative impulse).
|
|
20
|
+
*/
|
|
21
|
+
export interface PressureZone {
|
|
22
|
+
/** Center price of the zone (peak of the impulse) */
|
|
23
|
+
center: number;
|
|
24
|
+
/** Lower bound of the zone (25th percentile of the peak width) */
|
|
25
|
+
lower: number;
|
|
26
|
+
/** Upper bound of the zone (75th percentile of the peak width) */
|
|
27
|
+
upper: number;
|
|
28
|
+
/** Normalized strength 0-1 (relative to the strongest zone found) */
|
|
29
|
+
strength: number;
|
|
30
|
+
/** Whether this zone is above or below current spot */
|
|
31
|
+
side: 'above-spot' | 'below-spot';
|
|
32
|
+
/**
|
|
33
|
+
* Trade type this zone favors:
|
|
34
|
+
* - Stability zones below spot → long (buy the bounce)
|
|
35
|
+
* - Stability zones above spot → short (sell the rejection)
|
|
36
|
+
* - Acceleration zones below spot → short (momentum downside)
|
|
37
|
+
* - Acceleration zones above spot → long (momentum upside / squeeze)
|
|
38
|
+
*/
|
|
39
|
+
tradeType: 'long' | 'short';
|
|
40
|
+
/**
|
|
41
|
+
* Hedge execution type:
|
|
42
|
+
* - passive: dealers post resting limit orders (positive gamma → absorption)
|
|
43
|
+
* - aggressive: dealers send market orders (negative gamma → sweeping)
|
|
44
|
+
*/
|
|
45
|
+
hedgeType: 'passive' | 'aggressive';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A regime boundary where behavior flips between mean-reverting and trend-amplifying.
|
|
49
|
+
*/
|
|
50
|
+
export interface RegimeEdge {
|
|
51
|
+
/** Price at which the impulse curve crosses zero */
|
|
52
|
+
price: number;
|
|
53
|
+
/** Direction of the transition relative to a downward price move */
|
|
54
|
+
transitionType: 'stable-to-unstable' | 'unstable-to-stable';
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Per-price-level detail for the full pressure overlay.
|
|
58
|
+
*/
|
|
59
|
+
export interface PressureLevel {
|
|
60
|
+
/** Price level */
|
|
61
|
+
price: number;
|
|
62
|
+
/**
|
|
63
|
+
* Stability score: positive means mean-reverting pressure at this level.
|
|
64
|
+
* Higher = stronger buffering. Weighted by proximity to spot.
|
|
65
|
+
*/
|
|
66
|
+
stabilityScore: number;
|
|
67
|
+
/**
|
|
68
|
+
* Acceleration score: positive means trend-amplifying pressure at this level.
|
|
69
|
+
* Higher = stronger momentum fuel. Weighted by proximity to spot.
|
|
70
|
+
*/
|
|
71
|
+
accelerationScore: number;
|
|
72
|
+
/**
|
|
73
|
+
* Expected signed hedge contracts (positive = dealers buy, negative = dealers sell)
|
|
74
|
+
* for a 1-point spot move toward this level. Units depend on product.
|
|
75
|
+
* @deprecated Use hedgeContracts instead for multi-product estimates.
|
|
76
|
+
*/
|
|
77
|
+
expectedHedgeContracts: number;
|
|
78
|
+
/** Multi-product hedge contract estimates */
|
|
79
|
+
hedgeContracts: HedgeContractEstimates;
|
|
80
|
+
/**
|
|
81
|
+
* Whether dealers would hedge passively (limit orders / absorption)
|
|
82
|
+
* or aggressively (market orders / sweeping) at this level.
|
|
83
|
+
*/
|
|
84
|
+
hedgeType: 'passive' | 'aggressive';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Configuration for pressure cloud computation.
|
|
88
|
+
*/
|
|
89
|
+
export interface PressureCloudConfig {
|
|
90
|
+
/**
|
|
91
|
+
* Product multiplier for contract conversion.
|
|
92
|
+
* NQ = 20, MNQ = 2, ES = 50, MES = 5, SPY = 100 (shares).
|
|
93
|
+
* Default: 20 (NQ)
|
|
94
|
+
*/
|
|
95
|
+
contractMultiplier?: number;
|
|
96
|
+
/** Product type hint for primary contract display. Default: 'NQ' */
|
|
97
|
+
product?: 'NQ' | 'MNQ' | 'ES' | 'MES' | 'SPY';
|
|
98
|
+
/**
|
|
99
|
+
* How many expected-daily-moves to consider "reachable".
|
|
100
|
+
* Levels beyond this get heavily penalized. Default: 2.0
|
|
101
|
+
*/
|
|
102
|
+
reachabilityMultiple?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Minimum impulse magnitude (as fraction of mean abs impulse) to
|
|
105
|
+
* qualify as a zone. Default: 0.15
|
|
106
|
+
*/
|
|
107
|
+
zoneThreshold?: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Complete pressure cloud analysis combining stability and acceleration zones.
|
|
111
|
+
*/
|
|
112
|
+
export interface PressureCloud {
|
|
113
|
+
/** Current spot price */
|
|
114
|
+
spot: number;
|
|
115
|
+
/** Expiration timestamp */
|
|
116
|
+
expiration: number;
|
|
117
|
+
/** Timestamp when this cloud was computed */
|
|
118
|
+
computedAt: number;
|
|
119
|
+
/**
|
|
120
|
+
* Stability zones: levels where positive dealer impulse creates
|
|
121
|
+
* mean-reverting pressure. Price decelerates into these zones.
|
|
122
|
+
* Trade approach: reversal / bounce entries.
|
|
123
|
+
*/
|
|
124
|
+
stabilityZones: PressureZone[];
|
|
125
|
+
/**
|
|
126
|
+
* Acceleration zones: levels where negative dealer impulse creates
|
|
127
|
+
* trend-amplifying pressure. Price accelerates through these zones.
|
|
128
|
+
* Trade approach: momentum / breakout continuation.
|
|
129
|
+
*/
|
|
130
|
+
accelerationZones: PressureZone[];
|
|
131
|
+
/**
|
|
132
|
+
* Regime edges: prices where the impulse curve crosses zero,
|
|
133
|
+
* marking transitions between mean-reverting and trend-amplifying behavior.
|
|
134
|
+
*/
|
|
135
|
+
regimeEdges: RegimeEdge[];
|
|
136
|
+
/**
|
|
137
|
+
* Per-price-level detail for the full chart overlay.
|
|
138
|
+
* Every point on the impulse curve grid, enriched with scores and contract estimates.
|
|
139
|
+
*/
|
|
140
|
+
priceLevels: PressureLevel[];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Compute a pressure cloud from an existing hedge impulse curve.
|
|
144
|
+
*
|
|
145
|
+
* The pressure cloud translates the raw impulse curve into actionable
|
|
146
|
+
* trading zones:
|
|
147
|
+
*
|
|
148
|
+
* - **Stability zones** (positive impulse peaks, weighted by proximity):
|
|
149
|
+
* Where dealer hedging creates counter-trend flow. These are bounce/rejection
|
|
150
|
+
* targets — price decelerates into them and often stalls or reverses.
|
|
151
|
+
*
|
|
152
|
+
* - **Acceleration zones** (negative impulse troughs, weighted by proximity):
|
|
153
|
+
* Where dealer hedging creates with-trend flow. If price reaches these levels,
|
|
154
|
+
* dealers amplify the move. These are momentum/breakout zones — price
|
|
155
|
+
* accelerates through them.
|
|
156
|
+
*
|
|
157
|
+
* - **Regime edges** (zero crossings):
|
|
158
|
+
* Where behavior flips. Critical for understanding "if price breaks past X,
|
|
159
|
+
* the character of the tape changes."
|
|
160
|
+
*
|
|
161
|
+
* @param impulseCurve - A previously computed hedge impulse curve
|
|
162
|
+
* @param regimeParams - Regime parameters (for expected daily move / reachability)
|
|
163
|
+
* @param config - Optional tuning parameters
|
|
164
|
+
* @returns Complete pressure cloud analysis
|
|
165
|
+
*/
|
|
166
|
+
export declare function computePressureCloud(impulseCurve: HedgeImpulseCurve, regimeParams: RegimeParams, config?: PressureCloudConfig): PressureCloud;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computePressureCloud = computePressureCloud;
|
|
4
|
+
/** Product multipliers for contract conversion */
|
|
5
|
+
const PRODUCT_MULTIPLIERS = {
|
|
6
|
+
NQ: 20,
|
|
7
|
+
MNQ: 2,
|
|
8
|
+
ES: 50,
|
|
9
|
+
MES: 5,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Convert a dollar impulse value to hedge contracts for a given product multiplier.
|
|
13
|
+
* contracts = impulse / (multiplier * spot * 0.01)
|
|
14
|
+
*/
|
|
15
|
+
function impulseToContracts(impulse, multiplier, spot) {
|
|
16
|
+
const denominator = multiplier * spot * 0.01;
|
|
17
|
+
return denominator > 0 ? sanitize(impulse / denominator) : 0;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Compute multi-product hedge contract estimates from a dollar impulse value.
|
|
21
|
+
*/
|
|
22
|
+
function computeHedgeContractEstimates(impulse, spot) {
|
|
23
|
+
return {
|
|
24
|
+
nq: impulseToContracts(impulse, PRODUCT_MULTIPLIERS.NQ, spot),
|
|
25
|
+
mnq: impulseToContracts(impulse, PRODUCT_MULTIPLIERS.MNQ, spot),
|
|
26
|
+
es: impulseToContracts(impulse, PRODUCT_MULTIPLIERS.ES, spot),
|
|
27
|
+
mes: impulseToContracts(impulse, PRODUCT_MULTIPLIERS.MES, spot),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Implementation
|
|
32
|
+
// ============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Compute a pressure cloud from an existing hedge impulse curve.
|
|
35
|
+
*
|
|
36
|
+
* The pressure cloud translates the raw impulse curve into actionable
|
|
37
|
+
* trading zones:
|
|
38
|
+
*
|
|
39
|
+
* - **Stability zones** (positive impulse peaks, weighted by proximity):
|
|
40
|
+
* Where dealer hedging creates counter-trend flow. These are bounce/rejection
|
|
41
|
+
* targets — price decelerates into them and often stalls or reverses.
|
|
42
|
+
*
|
|
43
|
+
* - **Acceleration zones** (negative impulse troughs, weighted by proximity):
|
|
44
|
+
* Where dealer hedging creates with-trend flow. If price reaches these levels,
|
|
45
|
+
* dealers amplify the move. These are momentum/breakout zones — price
|
|
46
|
+
* accelerates through them.
|
|
47
|
+
*
|
|
48
|
+
* - **Regime edges** (zero crossings):
|
|
49
|
+
* Where behavior flips. Critical for understanding "if price breaks past X,
|
|
50
|
+
* the character of the tape changes."
|
|
51
|
+
*
|
|
52
|
+
* @param impulseCurve - A previously computed hedge impulse curve
|
|
53
|
+
* @param regimeParams - Regime parameters (for expected daily move / reachability)
|
|
54
|
+
* @param config - Optional tuning parameters
|
|
55
|
+
* @returns Complete pressure cloud analysis
|
|
56
|
+
*/
|
|
57
|
+
function computePressureCloud(impulseCurve, regimeParams, config = {}) {
|
|
58
|
+
const { contractMultiplier = 20, // NQ default
|
|
59
|
+
reachabilityMultiple = 2.0, zoneThreshold = 0.15, } = config;
|
|
60
|
+
const { spot, curve, extrema, zeroCrossings } = impulseCurve;
|
|
61
|
+
const expectedMove = regimeParams.expectedDailySpotMove * spot;
|
|
62
|
+
// Compute reachability-weighted scores for each price level
|
|
63
|
+
const priceLevels = computePriceLevels(curve, spot, expectedMove, reachabilityMultiple, contractMultiplier);
|
|
64
|
+
// Extract stability zones from positive impulse peaks
|
|
65
|
+
const stabilityZones = extractStabilityZones(extrema, curve, spot, expectedMove, reachabilityMultiple, zoneThreshold);
|
|
66
|
+
// Extract acceleration zones from negative impulse troughs
|
|
67
|
+
const accelerationZones = extractAccelerationZones(extrema, curve, spot, expectedMove, reachabilityMultiple, zoneThreshold);
|
|
68
|
+
// Convert zero crossings to regime edges
|
|
69
|
+
const regimeEdges = convertZeroCrossingsToEdges(zeroCrossings, spot);
|
|
70
|
+
return {
|
|
71
|
+
spot,
|
|
72
|
+
expiration: impulseCurve.expiration,
|
|
73
|
+
computedAt: Date.now(),
|
|
74
|
+
stabilityZones,
|
|
75
|
+
accelerationZones,
|
|
76
|
+
regimeEdges,
|
|
77
|
+
priceLevels,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Compute per-price-level detail from the impulse curve.
|
|
82
|
+
*/
|
|
83
|
+
function computePriceLevels(curve, spot, expectedMove, reachabilityMultiple, contractMultiplier) {
|
|
84
|
+
const reachRange = expectedMove * reachabilityMultiple;
|
|
85
|
+
return curve.map((point) => {
|
|
86
|
+
const distance = Math.abs(point.price - spot);
|
|
87
|
+
const proximity = Math.exp(-((distance / reachRange) ** 2));
|
|
88
|
+
// Stability: positive impulse weighted by proximity
|
|
89
|
+
const stabilityScore = point.impulse > 0
|
|
90
|
+
? point.impulse * proximity
|
|
91
|
+
: 0;
|
|
92
|
+
// Acceleration: negative impulse magnitude weighted by proximity
|
|
93
|
+
const accelerationScore = point.impulse < 0
|
|
94
|
+
? Math.abs(point.impulse) * proximity
|
|
95
|
+
: 0;
|
|
96
|
+
// Convert dollar impulse to signed contract count.
|
|
97
|
+
// impulse is $ of hedging per 1% move. For contracts per 1 point:
|
|
98
|
+
// contracts = impulse / (contractMultiplier * spot * 0.01)
|
|
99
|
+
const contractDenominator = contractMultiplier * spot * 0.01;
|
|
100
|
+
const expectedHedgeContracts = contractDenominator > 0
|
|
101
|
+
? point.impulse / contractDenominator
|
|
102
|
+
: 0;
|
|
103
|
+
return {
|
|
104
|
+
price: point.price,
|
|
105
|
+
stabilityScore,
|
|
106
|
+
accelerationScore,
|
|
107
|
+
expectedHedgeContracts: sanitize(expectedHedgeContracts),
|
|
108
|
+
hedgeContracts: computeHedgeContractEstimates(point.impulse, spot),
|
|
109
|
+
hedgeType: point.impulse >= 0 ? 'passive' : 'aggressive',
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Extract stability zones from positive impulse peaks (basins).
|
|
115
|
+
*/
|
|
116
|
+
function extractStabilityZones(extrema, curve, spot, expectedMove, reachabilityMultiple, zoneThreshold) {
|
|
117
|
+
const basins = extrema.filter((e) => e.type === 'basin');
|
|
118
|
+
if (basins.length === 0)
|
|
119
|
+
return [];
|
|
120
|
+
const reachRange = expectedMove * reachabilityMultiple;
|
|
121
|
+
const maxImpulse = Math.max(...basins.map((b) => Math.abs(b.impulse)), 1e-10);
|
|
122
|
+
// Filter by threshold
|
|
123
|
+
const significant = basins.filter((b) => Math.abs(b.impulse) / maxImpulse >= zoneThreshold);
|
|
124
|
+
// Build zones
|
|
125
|
+
const zones = significant.map((basin) => {
|
|
126
|
+
const proximity = Math.exp(-((Math.abs(basin.price - spot) / reachRange) ** 2));
|
|
127
|
+
const rawStrength = (Math.abs(basin.impulse) / maxImpulse) * proximity;
|
|
128
|
+
// Find zone width: where impulse drops to 50% of peak
|
|
129
|
+
const halfPeak = basin.impulse * 0.5;
|
|
130
|
+
const { lower, upper } = findZoneBounds(curve, basin.price, halfPeak);
|
|
131
|
+
const side = basin.price >= spot ? 'above-spot' : 'below-spot';
|
|
132
|
+
const tradeType = side === 'below-spot' ? 'long' : 'short';
|
|
133
|
+
return {
|
|
134
|
+
center: basin.price,
|
|
135
|
+
lower,
|
|
136
|
+
upper,
|
|
137
|
+
strength: Math.min(1, rawStrength),
|
|
138
|
+
side,
|
|
139
|
+
tradeType,
|
|
140
|
+
hedgeType: 'passive',
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
// Sort by strength descending
|
|
144
|
+
return zones.sort((a, b) => b.strength - a.strength);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Extract acceleration zones from negative impulse troughs (peaks in the type system).
|
|
148
|
+
*/
|
|
149
|
+
function extractAccelerationZones(extrema, curve, spot, expectedMove, reachabilityMultiple, zoneThreshold) {
|
|
150
|
+
const peaks = extrema.filter((e) => e.type === 'peak');
|
|
151
|
+
if (peaks.length === 0)
|
|
152
|
+
return [];
|
|
153
|
+
const reachRange = expectedMove * reachabilityMultiple;
|
|
154
|
+
const maxImpulse = Math.max(...peaks.map((p) => Math.abs(p.impulse)), 1e-10);
|
|
155
|
+
const significant = peaks.filter((p) => Math.abs(p.impulse) / maxImpulse >= zoneThreshold);
|
|
156
|
+
const zones = significant.map((peak) => {
|
|
157
|
+
const proximity = Math.exp(-((Math.abs(peak.price - spot) / reachRange) ** 2));
|
|
158
|
+
const rawStrength = (Math.abs(peak.impulse) / maxImpulse) * proximity;
|
|
159
|
+
const halfTrough = peak.impulse * 0.5;
|
|
160
|
+
const { lower, upper } = findZoneBounds(curve, peak.price, halfTrough);
|
|
161
|
+
const side = peak.price >= spot ? 'above-spot' : 'below-spot';
|
|
162
|
+
// Acceleration below spot → momentum short (waterfall)
|
|
163
|
+
// Acceleration above spot → momentum long (squeeze)
|
|
164
|
+
const tradeType = side === 'below-spot' ? 'short' : 'long';
|
|
165
|
+
return {
|
|
166
|
+
center: peak.price,
|
|
167
|
+
lower,
|
|
168
|
+
upper,
|
|
169
|
+
strength: Math.min(1, rawStrength),
|
|
170
|
+
side,
|
|
171
|
+
tradeType,
|
|
172
|
+
hedgeType: 'aggressive',
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
return zones.sort((a, b) => b.strength - a.strength);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Find the price bounds where impulse drops to the given threshold
|
|
179
|
+
* around a peak/trough center price.
|
|
180
|
+
*/
|
|
181
|
+
function findZoneBounds(curve, centerPrice, thresholdImpulse) {
|
|
182
|
+
// Find the center index
|
|
183
|
+
let centerIdx = 0;
|
|
184
|
+
let minDist = Infinity;
|
|
185
|
+
for (let i = 0; i < curve.length; i++) {
|
|
186
|
+
const d = Math.abs(curve[i].price - centerPrice);
|
|
187
|
+
if (d < minDist) {
|
|
188
|
+
minDist = d;
|
|
189
|
+
centerIdx = i;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const isPositive = thresholdImpulse > 0;
|
|
193
|
+
// Scan left for lower bound
|
|
194
|
+
let lowerIdx = centerIdx;
|
|
195
|
+
for (let i = centerIdx - 1; i >= 0; i--) {
|
|
196
|
+
if (isPositive ? curve[i].impulse < thresholdImpulse : curve[i].impulse > thresholdImpulse) {
|
|
197
|
+
lowerIdx = i;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
lowerIdx = i;
|
|
201
|
+
}
|
|
202
|
+
// Scan right for upper bound
|
|
203
|
+
let upperIdx = centerIdx;
|
|
204
|
+
for (let i = centerIdx + 1; i < curve.length; i++) {
|
|
205
|
+
if (isPositive ? curve[i].impulse < thresholdImpulse : curve[i].impulse > thresholdImpulse) {
|
|
206
|
+
upperIdx = i;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
upperIdx = i;
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
lower: curve[lowerIdx].price,
|
|
213
|
+
upper: curve[upperIdx].price,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Convert impulse zero crossings into regime edge descriptors.
|
|
218
|
+
*/
|
|
219
|
+
function convertZeroCrossingsToEdges(crossings, spot) {
|
|
220
|
+
return crossings.map((crossing) => {
|
|
221
|
+
// A "falling" crossing means impulse goes from positive to negative.
|
|
222
|
+
// If this is below spot, moving down into it means going from
|
|
223
|
+
// stable (positive impulse) to unstable (negative impulse).
|
|
224
|
+
// If above spot, it's the reverse perspective.
|
|
225
|
+
const isBelow = crossing.price < spot;
|
|
226
|
+
let transitionType;
|
|
227
|
+
if (crossing.direction === 'falling') {
|
|
228
|
+
// Impulse goes + → -
|
|
229
|
+
transitionType = isBelow ? 'stable-to-unstable' : 'unstable-to-stable';
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// Impulse goes - → +
|
|
233
|
+
transitionType = isBelow ? 'unstable-to-stable' : 'stable-to-unstable';
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
price: crossing.price,
|
|
237
|
+
transitionType,
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function sanitize(value) {
|
|
242
|
+
return isFinite(value) && !isNaN(value) ? value : 0;
|
|
243
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ export { buildOCCSymbol, parseOCCSymbol, generateStrikesAroundSpot, generateOCCS
|
|
|
14
14
|
export type { OCCSymbolParams, ParsedOCCSymbol, StrikeGenerationParams, } from './utils/occ';
|
|
15
15
|
export { estimateImpliedProbabilityDistribution, estimateImpliedProbabilityDistributions, getProbabilityInRange, getCumulativeProbability, getQuantile, estimateExposureAdjustedPDF, getEdgeAtPrice, getSignificantAdjustmentLevels, DEFAULT_ADJUSTMENT_CONFIG, LOW_VOL_CONFIG, CRISIS_CONFIG, OPEX_CONFIG, } from './impliedpdf';
|
|
16
16
|
export type { StrikeProbability, ImpliedProbabilityDistribution, ImpliedPDFResult, ExposureAdjustmentConfig, AdjustedPDFResult, PDFComparison, } from './impliedpdf';
|
|
17
|
-
export { deriveRegimeParams, interpolateIVAtStrike, computeHedgeImpulseCurve, computeCharmIntegral, analyzeHedgeFlow, } from './hedgeflow';
|
|
18
|
-
export type { MarketRegime, RegimeParams, HedgeImpulseConfig, HedgeImpulsePoint, HedgeImpulseCurve, ZeroCrossing, ImpulseExtremum, DirectionalAsymmetry, ImpulseRegime, CharmIntegralConfig, CharmBucket, CharmIntegral, HedgeFlowAnalysis, } from './hedgeflow';
|
|
17
|
+
export { deriveRegimeParams, interpolateIVAtStrike, computeHedgeImpulseCurve, computeCharmIntegral, computePressureCloud, analyzeHedgeFlow, } from './hedgeflow';
|
|
18
|
+
export type { MarketRegime, RegimeParams, HedgeImpulseConfig, HedgeImpulsePoint, HedgeImpulseCurve, ZeroCrossing, ImpulseExtremum, DirectionalAsymmetry, ImpulseRegime, CharmIntegralConfig, CharmBucket, CharmIntegral, HedgeFlowAnalysis, HedgeContractEstimates, PressureZone, RegimeEdge, PressureLevel, PressureCloudConfig, PressureCloud, } from './hedgeflow';
|
|
19
19
|
export { FloeClient, Broker } from './client/FloeClient';
|
|
20
20
|
export { TradierClient } from './client/brokers/TradierClient';
|
|
21
21
|
export { TastyTradeClient } from './client/brokers/TastyTradeClient';
|
|
@@ -26,3 +26,5 @@ export { computeVarianceSwapIV, computeImpliedVolatility, } from './iv';
|
|
|
26
26
|
export type { VarianceSwapResult, ImpliedVolatilityResult, } from './iv';
|
|
27
27
|
export { computeRealizedVolatility, } from './rv';
|
|
28
28
|
export type { PriceObservation, RealizedVolatilityResult, } from './rv';
|
|
29
|
+
export { computeVolResponseZScore, buildVolResponseObservation, } from './volresponse';
|
|
30
|
+
export type { VolResponseObservation, VolResponseCoefficients, VolResponseConfig, VolResponseResult, } from './volresponse';
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,8 @@ 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.computeRealizedVolatility = exports.computeImpliedVolatility = exports.computeVarianceSwapIV = exports.createOptionChain = exports.getAdapter = exports.brokerAdapters = exports.tdaAdapter = exports.ibkrAdapter = exports.schwabAdapter = exports.genericAdapter = exports.TradeStationClient = exports.TastyTradeClient = exports.TradierClient = exports.Broker = exports.FloeClient = exports.analyzeHedgeFlow = exports.computeCharmIntegral = exports.computeHedgeImpulseCurve = exports.interpolateIVAtStrike = exports.deriveRegimeParams = exports.OPEX_CONFIG = exports.CRISIS_CONFIG = exports.LOW_VOL_CONFIG = exports.DEFAULT_ADJUSTMENT_CONFIG = exports.getSignificantAdjustmentLevels = exports.getEdgeAtPrice = exports.estimateExposureAdjustedPDF = 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;
|
|
23
|
+
exports.computeRealizedVolatility = exports.computeImpliedVolatility = exports.computeVarianceSwapIV = exports.createOptionChain = exports.getAdapter = exports.brokerAdapters = exports.tdaAdapter = exports.ibkrAdapter = exports.schwabAdapter = exports.genericAdapter = exports.TradeStationClient = exports.TastyTradeClient = exports.TradierClient = exports.Broker = exports.FloeClient = exports.analyzeHedgeFlow = exports.computePressureCloud = exports.computeCharmIntegral = exports.computeHedgeImpulseCurve = exports.interpolateIVAtStrike = exports.deriveRegimeParams = exports.OPEX_CONFIG = exports.CRISIS_CONFIG = exports.LOW_VOL_CONFIG = exports.DEFAULT_ADJUSTMENT_CONFIG = exports.getSignificantAdjustmentLevels = exports.getEdgeAtPrice = exports.estimateExposureAdjustedPDF = 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
|
+
exports.buildVolResponseObservation = exports.computeVolResponseZScore = void 0;
|
|
24
25
|
// Core types
|
|
25
26
|
__exportStar(require("./types"), exports);
|
|
26
27
|
// Black-Scholes pricing and Greeks
|
|
@@ -67,7 +68,7 @@ Object.defineProperty(exports, "DEFAULT_ADJUSTMENT_CONFIG", { enumerable: true,
|
|
|
67
68
|
Object.defineProperty(exports, "LOW_VOL_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.LOW_VOL_CONFIG; } });
|
|
68
69
|
Object.defineProperty(exports, "CRISIS_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.CRISIS_CONFIG; } });
|
|
69
70
|
Object.defineProperty(exports, "OPEX_CONFIG", { enumerable: true, get: function () { return impliedpdf_1.OPEX_CONFIG; } });
|
|
70
|
-
// Hedge flow analysis (impulse curve, charm integral, regime derivation)
|
|
71
|
+
// Hedge flow analysis (impulse curve, charm integral, regime derivation, pressure cloud)
|
|
71
72
|
var hedgeflow_1 = require("./hedgeflow");
|
|
72
73
|
// Regime derivation from IV surface
|
|
73
74
|
Object.defineProperty(exports, "deriveRegimeParams", { enumerable: true, get: function () { return hedgeflow_1.deriveRegimeParams; } });
|
|
@@ -76,6 +77,8 @@ Object.defineProperty(exports, "interpolateIVAtStrike", { enumerable: true, get:
|
|
|
76
77
|
Object.defineProperty(exports, "computeHedgeImpulseCurve", { enumerable: true, get: function () { return hedgeflow_1.computeHedgeImpulseCurve; } });
|
|
77
78
|
// Charm integral
|
|
78
79
|
Object.defineProperty(exports, "computeCharmIntegral", { enumerable: true, get: function () { return hedgeflow_1.computeCharmIntegral; } });
|
|
80
|
+
// Pressure cloud (stability/acceleration zones)
|
|
81
|
+
Object.defineProperty(exports, "computePressureCloud", { enumerable: true, get: function () { return hedgeflow_1.computePressureCloud; } });
|
|
79
82
|
// Combined analysis
|
|
80
83
|
Object.defineProperty(exports, "analyzeHedgeFlow", { enumerable: true, get: function () { return hedgeflow_1.analyzeHedgeFlow; } });
|
|
81
84
|
// Client
|
|
@@ -104,3 +107,7 @@ Object.defineProperty(exports, "computeImpliedVolatility", { enumerable: true, g
|
|
|
104
107
|
// Realized volatility (tick-based quadratic variation)
|
|
105
108
|
var rv_1 = require("./rv");
|
|
106
109
|
Object.defineProperty(exports, "computeRealizedVolatility", { enumerable: true, get: function () { return rv_1.computeRealizedVolatility; } });
|
|
110
|
+
// Vol response model (IV response residual / vol bid-offered z-score)
|
|
111
|
+
var volresponse_1 = require("./volresponse");
|
|
112
|
+
Object.defineProperty(exports, "computeVolResponseZScore", { enumerable: true, get: function () { return volresponse_1.computeVolResponseZScore; } });
|
|
113
|
+
Object.defineProperty(exports, "buildVolResponseObservation", { enumerable: true, get: function () { return volresponse_1.buildVolResponseObservation; } });
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { VolResponseObservation, VolResponseConfig, VolResponseResult } from './types';
|
|
2
|
+
export type { VolResponseObservation, VolResponseCoefficients, VolResponseConfig, VolResponseResult, } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Build a VolResponseObservation from consecutive IV/RV/spot readings.
|
|
5
|
+
*
|
|
6
|
+
* The consumer calls this on each tick after the first, passing the
|
|
7
|
+
* current and previous IV/spot values. The resulting observation can
|
|
8
|
+
* then be accumulated and passed to computeVolResponseZScore.
|
|
9
|
+
*
|
|
10
|
+
* @param current - Current tick values
|
|
11
|
+
* @param previous - Previous tick values (IV and spot only)
|
|
12
|
+
* @returns A VolResponseObservation ready for the regression
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildVolResponseObservation(current: {
|
|
15
|
+
iv: number;
|
|
16
|
+
rv: number;
|
|
17
|
+
spot: number;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
}, previous: {
|
|
20
|
+
iv: number;
|
|
21
|
+
spot: number;
|
|
22
|
+
}): VolResponseObservation;
|
|
23
|
+
/**
|
|
24
|
+
* Compute the vol response z-score from accumulated observations.
|
|
25
|
+
*
|
|
26
|
+
* Fits an expanding-window OLS regression:
|
|
27
|
+
*
|
|
28
|
+
* deltaIV(t) ~ a + b1*return + b2*|return| + b3*RV + b4*IV_level
|
|
29
|
+
*
|
|
30
|
+
* Then computes the residual of the most recent observation and
|
|
31
|
+
* normalizes it by the residual standard deviation to produce a z-score.
|
|
32
|
+
*
|
|
33
|
+
* Interpretation:
|
|
34
|
+
* - z >> 0: vol is bid relative to baseline (stress / demand)
|
|
35
|
+
* - z << 0: vol is offered relative to baseline (supply / crush)
|
|
36
|
+
* - z ~ 0: normal vol response given the price path
|
|
37
|
+
*
|
|
38
|
+
* @param observations - All accumulated VolResponseObservation for the session
|
|
39
|
+
* @param config - Optional configuration overrides
|
|
40
|
+
* @returns VolResponseResult with z-score and signal classification
|
|
41
|
+
*/
|
|
42
|
+
export declare function computeVolResponseZScore(observations: VolResponseObservation[], config?: VolResponseConfig): VolResponseResult;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildVolResponseObservation = buildVolResponseObservation;
|
|
4
|
+
exports.computeVolResponseZScore = computeVolResponseZScore;
|
|
5
|
+
// Number of features in the regression (intercept + 4 regressors)
|
|
6
|
+
const NUM_FEATURES = 5;
|
|
7
|
+
// Small ridge penalty for numerical stability of the normal equations
|
|
8
|
+
const RIDGE_LAMBDA = 1e-8;
|
|
9
|
+
/**
|
|
10
|
+
* Build a VolResponseObservation from consecutive IV/RV/spot readings.
|
|
11
|
+
*
|
|
12
|
+
* The consumer calls this on each tick after the first, passing the
|
|
13
|
+
* current and previous IV/spot values. The resulting observation can
|
|
14
|
+
* then be accumulated and passed to computeVolResponseZScore.
|
|
15
|
+
*
|
|
16
|
+
* @param current - Current tick values
|
|
17
|
+
* @param previous - Previous tick values (IV and spot only)
|
|
18
|
+
* @returns A VolResponseObservation ready for the regression
|
|
19
|
+
*/
|
|
20
|
+
function buildVolResponseObservation(current, previous) {
|
|
21
|
+
const deltaIV = current.iv - previous.iv;
|
|
22
|
+
const spotReturn = Math.log(current.spot / previous.spot);
|
|
23
|
+
return {
|
|
24
|
+
timestamp: current.timestamp,
|
|
25
|
+
deltaIV,
|
|
26
|
+
spotReturn,
|
|
27
|
+
absSpotReturn: Math.abs(spotReturn),
|
|
28
|
+
rvLevel: current.rv,
|
|
29
|
+
ivLevel: current.iv,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Compute the vol response z-score from accumulated observations.
|
|
34
|
+
*
|
|
35
|
+
* Fits an expanding-window OLS regression:
|
|
36
|
+
*
|
|
37
|
+
* deltaIV(t) ~ a + b1*return + b2*|return| + b3*RV + b4*IV_level
|
|
38
|
+
*
|
|
39
|
+
* Then computes the residual of the most recent observation and
|
|
40
|
+
* normalizes it by the residual standard deviation to produce a z-score.
|
|
41
|
+
*
|
|
42
|
+
* Interpretation:
|
|
43
|
+
* - z >> 0: vol is bid relative to baseline (stress / demand)
|
|
44
|
+
* - z << 0: vol is offered relative to baseline (supply / crush)
|
|
45
|
+
* - z ~ 0: normal vol response given the price path
|
|
46
|
+
*
|
|
47
|
+
* @param observations - All accumulated VolResponseObservation for the session
|
|
48
|
+
* @param config - Optional configuration overrides
|
|
49
|
+
* @returns VolResponseResult with z-score and signal classification
|
|
50
|
+
*/
|
|
51
|
+
function computeVolResponseZScore(observations, config = {}) {
|
|
52
|
+
const { minObservations = 30, volBidThreshold = 1.5, volOfferedThreshold = -1.5, } = config;
|
|
53
|
+
const emptyCoefficients = {
|
|
54
|
+
intercept: 0,
|
|
55
|
+
betaReturn: 0,
|
|
56
|
+
betaAbsReturn: 0,
|
|
57
|
+
betaRV: 0,
|
|
58
|
+
betaIVLevel: 0,
|
|
59
|
+
};
|
|
60
|
+
if (observations.length < minObservations) {
|
|
61
|
+
return {
|
|
62
|
+
isValid: false,
|
|
63
|
+
minObservations,
|
|
64
|
+
numObservations: observations.length,
|
|
65
|
+
coefficients: emptyCoefficients,
|
|
66
|
+
rSquared: 0,
|
|
67
|
+
residualStdDev: 0,
|
|
68
|
+
expectedDeltaIV: 0,
|
|
69
|
+
observedDeltaIV: observations.length > 0 ? observations[observations.length - 1].deltaIV : 0,
|
|
70
|
+
residual: 0,
|
|
71
|
+
zScore: 0,
|
|
72
|
+
signal: 'insufficient_data',
|
|
73
|
+
timestamp: observations.length > 0 ? observations[observations.length - 1].timestamp : 0,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Build design matrix X and response vector y
|
|
77
|
+
const n = observations.length;
|
|
78
|
+
const X = new Array(n);
|
|
79
|
+
const y = new Array(n);
|
|
80
|
+
for (let i = 0; i < n; i++) {
|
|
81
|
+
const obs = observations[i];
|
|
82
|
+
X[i] = [1, obs.spotReturn, obs.absSpotReturn, obs.rvLevel, obs.ivLevel];
|
|
83
|
+
y[i] = obs.deltaIV;
|
|
84
|
+
}
|
|
85
|
+
// Solve OLS via normal equations with ridge regularization
|
|
86
|
+
const ols = solveOLS(X, y);
|
|
87
|
+
if (!ols) {
|
|
88
|
+
return {
|
|
89
|
+
isValid: false,
|
|
90
|
+
minObservations,
|
|
91
|
+
numObservations: n,
|
|
92
|
+
coefficients: emptyCoefficients,
|
|
93
|
+
rSquared: 0,
|
|
94
|
+
residualStdDev: 0,
|
|
95
|
+
expectedDeltaIV: 0,
|
|
96
|
+
observedDeltaIV: observations[n - 1].deltaIV,
|
|
97
|
+
residual: 0,
|
|
98
|
+
zScore: 0,
|
|
99
|
+
signal: 'insufficient_data',
|
|
100
|
+
timestamp: observations[n - 1].timestamp,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const { beta, residuals, rSquared, residualStdDev } = ols;
|
|
104
|
+
const lastObs = observations[n - 1];
|
|
105
|
+
const lastX = X[n - 1];
|
|
106
|
+
// Predicted deltaIV for the most recent observation
|
|
107
|
+
let expectedDeltaIV = 0;
|
|
108
|
+
for (let j = 0; j < NUM_FEATURES; j++) {
|
|
109
|
+
expectedDeltaIV += beta[j] * lastX[j];
|
|
110
|
+
}
|
|
111
|
+
const residual = lastObs.deltaIV - expectedDeltaIV;
|
|
112
|
+
const zScore = residualStdDev > 0 ? residual / residualStdDev : 0;
|
|
113
|
+
let signal = 'neutral';
|
|
114
|
+
if (zScore > volBidThreshold) {
|
|
115
|
+
signal = 'vol_bid';
|
|
116
|
+
}
|
|
117
|
+
else if (zScore < volOfferedThreshold) {
|
|
118
|
+
signal = 'vol_offered';
|
|
119
|
+
}
|
|
120
|
+
const coefficients = {
|
|
121
|
+
intercept: beta[0],
|
|
122
|
+
betaReturn: beta[1],
|
|
123
|
+
betaAbsReturn: beta[2],
|
|
124
|
+
betaRV: beta[3],
|
|
125
|
+
betaIVLevel: beta[4],
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
isValid: true,
|
|
129
|
+
minObservations,
|
|
130
|
+
numObservations: n,
|
|
131
|
+
coefficients,
|
|
132
|
+
rSquared,
|
|
133
|
+
residualStdDev,
|
|
134
|
+
expectedDeltaIV,
|
|
135
|
+
observedDeltaIV: lastObs.deltaIV,
|
|
136
|
+
residual,
|
|
137
|
+
zScore,
|
|
138
|
+
signal,
|
|
139
|
+
timestamp: lastObs.timestamp,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Solve ordinary least squares: beta = (X'X + lambda*I)^{-1} X'y
|
|
144
|
+
*
|
|
145
|
+
* For a small system (5 features), direct inversion via Gauss-Jordan
|
|
146
|
+
* elimination is efficient and avoids external dependencies.
|
|
147
|
+
*/
|
|
148
|
+
function solveOLS(X, y) {
|
|
149
|
+
const n = X.length;
|
|
150
|
+
const p = NUM_FEATURES;
|
|
151
|
+
// Compute X'X (p x p)
|
|
152
|
+
const XtX = new Array(p);
|
|
153
|
+
for (let i = 0; i < p; i++) {
|
|
154
|
+
XtX[i] = new Array(p).fill(0);
|
|
155
|
+
for (let j = 0; j < p; j++) {
|
|
156
|
+
let sum = 0;
|
|
157
|
+
for (let k = 0; k < n; k++) {
|
|
158
|
+
sum += X[k][i] * X[k][j];
|
|
159
|
+
}
|
|
160
|
+
XtX[i][j] = sum;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Add ridge penalty to diagonal (skip intercept at index 0)
|
|
164
|
+
for (let i = 1; i < p; i++) {
|
|
165
|
+
XtX[i][i] += RIDGE_LAMBDA;
|
|
166
|
+
}
|
|
167
|
+
// Compute X'y (p x 1)
|
|
168
|
+
const Xty = new Array(p).fill(0);
|
|
169
|
+
for (let i = 0; i < p; i++) {
|
|
170
|
+
let sum = 0;
|
|
171
|
+
for (let k = 0; k < n; k++) {
|
|
172
|
+
sum += X[k][i] * y[k];
|
|
173
|
+
}
|
|
174
|
+
Xty[i] = sum;
|
|
175
|
+
}
|
|
176
|
+
// Solve (X'X + lambda*I) * beta = X'y via Gauss-Jordan elimination
|
|
177
|
+
// Augment [XtX | Xty] into a (p x p+1) matrix
|
|
178
|
+
const aug = new Array(p);
|
|
179
|
+
for (let i = 0; i < p; i++) {
|
|
180
|
+
aug[i] = new Array(p + 1);
|
|
181
|
+
for (let j = 0; j < p; j++) {
|
|
182
|
+
aug[i][j] = XtX[i][j];
|
|
183
|
+
}
|
|
184
|
+
aug[i][p] = Xty[i];
|
|
185
|
+
}
|
|
186
|
+
// Forward elimination with partial pivoting
|
|
187
|
+
for (let col = 0; col < p; col++) {
|
|
188
|
+
// Find pivot
|
|
189
|
+
let maxVal = Math.abs(aug[col][col]);
|
|
190
|
+
let maxRow = col;
|
|
191
|
+
for (let row = col + 1; row < p; row++) {
|
|
192
|
+
const val = Math.abs(aug[row][col]);
|
|
193
|
+
if (val > maxVal) {
|
|
194
|
+
maxVal = val;
|
|
195
|
+
maxRow = row;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (maxVal < 1e-14) {
|
|
199
|
+
return null; // Singular matrix
|
|
200
|
+
}
|
|
201
|
+
// Swap rows
|
|
202
|
+
if (maxRow !== col) {
|
|
203
|
+
const temp = aug[col];
|
|
204
|
+
aug[col] = aug[maxRow];
|
|
205
|
+
aug[maxRow] = temp;
|
|
206
|
+
}
|
|
207
|
+
// Eliminate below and above
|
|
208
|
+
const pivot = aug[col][col];
|
|
209
|
+
for (let j = col; j <= p; j++) {
|
|
210
|
+
aug[col][j] /= pivot;
|
|
211
|
+
}
|
|
212
|
+
for (let row = 0; row < p; row++) {
|
|
213
|
+
if (row === col)
|
|
214
|
+
continue;
|
|
215
|
+
const factor = aug[row][col];
|
|
216
|
+
for (let j = col; j <= p; j++) {
|
|
217
|
+
aug[row][j] -= factor * aug[col][j];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Extract beta
|
|
222
|
+
const beta = new Array(p);
|
|
223
|
+
for (let i = 0; i < p; i++) {
|
|
224
|
+
beta[i] = aug[i][p];
|
|
225
|
+
if (!isFinite(beta[i]))
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
// Compute residuals and statistics
|
|
229
|
+
const residuals = new Array(n);
|
|
230
|
+
let ssRes = 0;
|
|
231
|
+
let yMean = 0;
|
|
232
|
+
for (let i = 0; i < n; i++) {
|
|
233
|
+
yMean += y[i];
|
|
234
|
+
}
|
|
235
|
+
yMean /= n;
|
|
236
|
+
let ssTot = 0;
|
|
237
|
+
for (let i = 0; i < n; i++) {
|
|
238
|
+
let predicted = 0;
|
|
239
|
+
for (let j = 0; j < p; j++) {
|
|
240
|
+
predicted += beta[j] * X[i][j];
|
|
241
|
+
}
|
|
242
|
+
residuals[i] = y[i] - predicted;
|
|
243
|
+
ssRes += residuals[i] * residuals[i];
|
|
244
|
+
ssTot += (y[i] - yMean) * (y[i] - yMean);
|
|
245
|
+
}
|
|
246
|
+
const rSquared = ssTot > 0 ? Math.max(0, 1 - ssRes / ssTot) : 0;
|
|
247
|
+
// Residual standard deviation (using n - p degrees of freedom)
|
|
248
|
+
const dof = Math.max(n - p, 1);
|
|
249
|
+
const residualStdDev = Math.sqrt(ssRes / dof);
|
|
250
|
+
return { beta, residuals, rSquared, residualStdDev };
|
|
251
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single observation for the vol response regression model.
|
|
3
|
+
* The consumer accumulates these as the 0DTE session progresses.
|
|
4
|
+
*/
|
|
5
|
+
export interface VolResponseObservation {
|
|
6
|
+
/** Timestamp in milliseconds */
|
|
7
|
+
timestamp: number;
|
|
8
|
+
/** Change in IV from previous observation: IV(t) - IV(t-1), as decimal */
|
|
9
|
+
deltaIV: number;
|
|
10
|
+
/** Log return of spot: ln(S(t) / S(t-1)) */
|
|
11
|
+
spotReturn: number;
|
|
12
|
+
/** Absolute value of the log return */
|
|
13
|
+
absSpotReturn: number;
|
|
14
|
+
/** Current realized volatility level (annualized, decimal) */
|
|
15
|
+
rvLevel: number;
|
|
16
|
+
/** Current IV level (annualized, decimal) */
|
|
17
|
+
ivLevel: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Regression coefficients from the IV response model.
|
|
21
|
+
*
|
|
22
|
+
* deltaIV(t) ~ intercept + b1*return + b2*|return| + b3*RV + b4*IV_level
|
|
23
|
+
*/
|
|
24
|
+
export interface VolResponseCoefficients {
|
|
25
|
+
/** Intercept */
|
|
26
|
+
intercept: number;
|
|
27
|
+
/** Coefficient on signed spot return (captures spot-vol correlation) */
|
|
28
|
+
betaReturn: number;
|
|
29
|
+
/** Coefficient on |return| (captures vol-of-vol / convexity response) */
|
|
30
|
+
betaAbsReturn: number;
|
|
31
|
+
/** Coefficient on RV level (captures RV mean-reversion effect) */
|
|
32
|
+
betaRV: number;
|
|
33
|
+
/** Coefficient on IV level (captures IV mean-reversion effect) */
|
|
34
|
+
betaIVLevel: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Configuration for the vol response model.
|
|
38
|
+
*/
|
|
39
|
+
export interface VolResponseConfig {
|
|
40
|
+
/** Minimum observations before the model is considered valid. Default: 30 */
|
|
41
|
+
minObservations?: number;
|
|
42
|
+
/** Z-score threshold for vol_bid signal. Default: 1.5 */
|
|
43
|
+
volBidThreshold?: number;
|
|
44
|
+
/** Z-score threshold for vol_offered signal. Default: -1.5 */
|
|
45
|
+
volOfferedThreshold?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Full result of the vol response residual model.
|
|
49
|
+
*/
|
|
50
|
+
export interface VolResponseResult {
|
|
51
|
+
/** Whether the model has enough data to be meaningful */
|
|
52
|
+
isValid: boolean;
|
|
53
|
+
/** Minimum number of observations required (for reference) */
|
|
54
|
+
minObservations: number;
|
|
55
|
+
/** Number of observations used in the regression */
|
|
56
|
+
numObservations: number;
|
|
57
|
+
/** Fitted regression coefficients */
|
|
58
|
+
coefficients: VolResponseCoefficients;
|
|
59
|
+
/** R-squared of the regression */
|
|
60
|
+
rSquared: number;
|
|
61
|
+
/** Standard deviation of the residuals */
|
|
62
|
+
residualStdDev: number;
|
|
63
|
+
/** The most recent predicted (expected) deltaIV */
|
|
64
|
+
expectedDeltaIV: number;
|
|
65
|
+
/** The most recent observed deltaIV */
|
|
66
|
+
observedDeltaIV: number;
|
|
67
|
+
/** The residual: observed - expected */
|
|
68
|
+
residual: number;
|
|
69
|
+
/** The z-score: residual / residualStdDev */
|
|
70
|
+
zScore: number;
|
|
71
|
+
/** Discrete signal classification */
|
|
72
|
+
signal: 'vol_bid' | 'vol_offered' | 'neutral' | 'insufficient_data';
|
|
73
|
+
/** Timestamp of the most recent observation */
|
|
74
|
+
timestamp: number;
|
|
75
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fullstackcraftllc/floe",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
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",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"test": "jest",
|
|
10
10
|
"prepublishOnly": "npm run build",
|
|
11
|
-
"type-check": "tsc --noEmit"
|
|
11
|
+
"type-check": "tsc --noEmit",
|
|
12
|
+
"prepare": "husky",
|
|
13
|
+
"copy-whitepaper": "cp whitepaper/whitepaper.pdf site/public/whitepaper.pdf"
|
|
12
14
|
},
|
|
13
15
|
"keywords": [
|
|
14
16
|
"options",
|
|
@@ -42,6 +44,7 @@
|
|
|
42
44
|
"@types/jest": "^29.5.0",
|
|
43
45
|
"@types/node": "^20.0.0",
|
|
44
46
|
"dotenv": "^17.2.3",
|
|
47
|
+
"husky": "^9.1.7",
|
|
45
48
|
"jest": "^29.5.0",
|
|
46
49
|
"ts-jest": "^29.1.0",
|
|
47
50
|
"typescript": "^5.9.3"
|