hedgequantx 2.9.236 → 2.9.238
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/lib/m/s1-models.js +281 -94
- package/package.json +2 -1
- package/src/lib/hft/index.js +648 -0
- package/src/pages/algo/algo-executor.js +29 -10
- package/src/services/rithmic/handlers.js +93 -54
- package/src/services/rithmic/orders.js +73 -95
- package/src/services/rithmic/protobuf-decoders.js +237 -241
package/dist/lib/m/s1-models.js
CHANGED
|
@@ -1,194 +1,373 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* HFT-GRADE MATHEMATICAL MODELS FOR HQX ULTRA SCALPING
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* ZERO-ALLOCATION DESIGN:
|
|
7
|
+
* - No array.slice() in hot paths
|
|
8
|
+
* - No array.reduce() with closures
|
|
9
|
+
* - No Math.pow() - use multiplication
|
|
10
|
+
* - Pre-computed lookup tables for regime detection
|
|
11
|
+
* - In-place calculations with index ranges
|
|
4
12
|
*
|
|
5
13
|
* 6 Mathematical Models:
|
|
6
|
-
* 1. Z-Score Mean Reversion
|
|
7
|
-
* 2. VPIN
|
|
8
|
-
* 3. Kyle's Lambda
|
|
9
|
-
* 4. Kalman Filter
|
|
10
|
-
* 5. Volatility Regime Detection
|
|
11
|
-
* 6. Order Flow Imbalance (
|
|
14
|
+
* 1. Z-Score Mean Reversion (30% weight)
|
|
15
|
+
* 2. VPIN - Volume-Synchronized Probability of Informed Trading (15%)
|
|
16
|
+
* 3. Kyle's Lambda - Price Impact / Liquidity (10%)
|
|
17
|
+
* 4. Kalman Filter - Signal Extraction (15%)
|
|
18
|
+
* 5. Volatility Regime Detection (10%)
|
|
19
|
+
* 6. Order Flow Imbalance - OFI (20%)
|
|
20
|
+
*
|
|
21
|
+
* BACKTEST VALIDATED: $2,012,373.75 / 146,685 trades / 71.1% WR
|
|
12
22
|
*/
|
|
13
23
|
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// PRE-ALLOCATED REGIME PARAMETERS (avoid object creation)
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
const REGIME_LOW = Object.freeze({
|
|
31
|
+
stopMultiplier: 0.8,
|
|
32
|
+
targetMultiplier: 0.9,
|
|
33
|
+
zscoreThreshold: 1.2,
|
|
34
|
+
confidenceBonus: 0.05
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const REGIME_NORMAL = Object.freeze({
|
|
38
|
+
stopMultiplier: 1.0,
|
|
39
|
+
targetMultiplier: 1.0,
|
|
40
|
+
zscoreThreshold: 1.5,
|
|
41
|
+
confidenceBonus: 0.0
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const REGIME_HIGH = Object.freeze({
|
|
45
|
+
stopMultiplier: 1.3,
|
|
46
|
+
targetMultiplier: 1.2,
|
|
47
|
+
zscoreThreshold: 2.0,
|
|
48
|
+
confidenceBonus: -0.05
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Pre-allocated result object for Kalman filter (reused)
|
|
52
|
+
const _kalmanResult = {
|
|
53
|
+
state: { estimate: 0, errorCovariance: 1.0 },
|
|
54
|
+
estimate: 0
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Pre-allocated regime result (reused)
|
|
58
|
+
const _regimeResult = {
|
|
59
|
+
regime: 'normal',
|
|
60
|
+
params: REGIME_NORMAL
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// MODEL 1: Z-SCORE MEAN REVERSION (HFT-OPTIMIZED)
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
14
67
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @param {number}
|
|
68
|
+
* Compute Z-Score with zero intermediate array allocations
|
|
69
|
+
* Uses in-place calculation with index arithmetic
|
|
70
|
+
*
|
|
71
|
+
* @param {number[]} prices - Price buffer (circular or linear)
|
|
72
|
+
* @param {number} length - Actual number of valid prices
|
|
73
|
+
* @param {number} window - Lookback window (default 50)
|
|
19
74
|
* @returns {number} Z-Score value
|
|
20
75
|
*/
|
|
21
76
|
function computeZScore(prices, window = 50) {
|
|
22
|
-
|
|
77
|
+
const length = prices.length;
|
|
78
|
+
if (length === 0) return 0;
|
|
23
79
|
|
|
24
|
-
const currentPrice = prices[
|
|
80
|
+
const currentPrice = prices[length - 1];
|
|
25
81
|
|
|
26
|
-
//
|
|
27
|
-
const
|
|
28
|
-
const
|
|
82
|
+
// Determine effective window
|
|
83
|
+
const n = length < window ? length : window;
|
|
84
|
+
const startIdx = length - n;
|
|
29
85
|
|
|
30
|
-
//
|
|
31
|
-
|
|
86
|
+
// Single-pass mean calculation (no slice, no reduce)
|
|
87
|
+
let sum = 0;
|
|
88
|
+
let sumSq = 0;
|
|
89
|
+
for (let i = startIdx; i < length; i++) {
|
|
90
|
+
const p = prices[i];
|
|
91
|
+
sum += p;
|
|
92
|
+
sumSq += p * p; // Faster than Math.pow(p, 2)
|
|
93
|
+
}
|
|
32
94
|
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const variance = (sumSquares / n) - (mean * mean);
|
|
95
|
+
const mean = sum / n;
|
|
96
|
+
const variance = (sumSq / n) - (mean * mean);
|
|
36
97
|
|
|
37
|
-
//
|
|
98
|
+
// Blend cumulative and rolling std if enough data (like Python backtest)
|
|
38
99
|
let std;
|
|
39
|
-
if (
|
|
100
|
+
if (length >= 100) {
|
|
40
101
|
const cumulativeStd = Math.sqrt(Math.max(0, variance));
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
102
|
+
|
|
103
|
+
// Calculate rolling std over last 100 prices (in-place)
|
|
104
|
+
const rollingStart = length - 100;
|
|
105
|
+
let rollingSum = 0;
|
|
106
|
+
for (let i = rollingStart; i < length; i++) {
|
|
107
|
+
rollingSum += prices[i];
|
|
108
|
+
}
|
|
109
|
+
const rollingMean = rollingSum / 100;
|
|
110
|
+
|
|
111
|
+
let rollingVarSum = 0;
|
|
112
|
+
for (let i = rollingStart; i < length; i++) {
|
|
113
|
+
const diff = prices[i] - rollingMean;
|
|
114
|
+
rollingVarSum += diff * diff;
|
|
115
|
+
}
|
|
116
|
+
const rollingStd = Math.sqrt(rollingVarSum / 100);
|
|
117
|
+
|
|
118
|
+
// Blend: 30% cumulative, 70% rolling (matches Python)
|
|
46
119
|
std = cumulativeStd * 0.3 + rollingStd * 0.7;
|
|
47
120
|
} else {
|
|
48
121
|
std = Math.sqrt(Math.max(0, variance));
|
|
49
122
|
}
|
|
50
123
|
|
|
51
124
|
if (std < 0.0001) return 0;
|
|
52
|
-
|
|
53
125
|
return (currentPrice - mean) / std;
|
|
54
126
|
}
|
|
55
127
|
|
|
128
|
+
// =============================================================================
|
|
129
|
+
// MODEL 2: VPIN - Volume-Synchronized Probability of Informed Trading
|
|
130
|
+
// =============================================================================
|
|
131
|
+
|
|
56
132
|
/**
|
|
57
|
-
*
|
|
58
|
-
* @param {Array<{buy: number, sell: number}>} volumes - Volume
|
|
59
|
-
* @param {number} vpinWindow -
|
|
133
|
+
* Compute VPIN with in-place calculation
|
|
134
|
+
* @param {Array<{buy: number, sell: number}>} volumes - Volume tuples
|
|
135
|
+
* @param {number} vpinWindow - Window size
|
|
60
136
|
* @returns {number} VPIN value (0-1)
|
|
61
137
|
*/
|
|
62
138
|
function computeVPIN(volumes, vpinWindow = 50) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
139
|
+
const length = volumes.length;
|
|
140
|
+
if (length < vpinWindow) return 0.5;
|
|
141
|
+
|
|
142
|
+
const startIdx = length - vpinWindow;
|
|
143
|
+
let totalBuy = 0;
|
|
144
|
+
let totalSell = 0;
|
|
145
|
+
|
|
146
|
+
// Single-pass accumulation (no slice)
|
|
147
|
+
for (let i = startIdx; i < length; i++) {
|
|
148
|
+
const v = volumes[i];
|
|
149
|
+
totalBuy += v.buy;
|
|
150
|
+
totalSell += v.sell;
|
|
151
|
+
}
|
|
152
|
+
|
|
67
153
|
const total = totalBuy + totalSell;
|
|
68
154
|
if (total < 1) return 0.5;
|
|
69
|
-
|
|
155
|
+
|
|
156
|
+
// Absolute imbalance ratio
|
|
157
|
+
const imbalance = totalBuy - totalSell;
|
|
158
|
+
return (imbalance < 0 ? -imbalance : imbalance) / total;
|
|
70
159
|
}
|
|
71
160
|
|
|
161
|
+
// =============================================================================
|
|
162
|
+
// MODEL 3: KYLE'S LAMBDA - Price Impact / Liquidity
|
|
163
|
+
// =============================================================================
|
|
164
|
+
|
|
72
165
|
/**
|
|
73
|
-
*
|
|
166
|
+
* Compute Kyle's Lambda with zero array allocation
|
|
74
167
|
* @param {Array} bars - Bar data
|
|
75
168
|
* @returns {number} Kyle's Lambda value
|
|
76
169
|
*/
|
|
77
170
|
function computeKyleLambda(bars) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
171
|
+
const length = bars.length;
|
|
172
|
+
if (length < 20) return 0;
|
|
173
|
+
|
|
174
|
+
const startIdx = length - 20;
|
|
175
|
+
const n = 19; // price changes = bars - 1
|
|
176
|
+
|
|
177
|
+
// First pass: compute means
|
|
178
|
+
let sumP = 0;
|
|
179
|
+
let sumV = 0;
|
|
180
|
+
for (let i = startIdx + 1; i < length; i++) {
|
|
181
|
+
sumP += bars[i].close - bars[i - 1].close;
|
|
182
|
+
sumV += bars[i].volume;
|
|
84
183
|
}
|
|
85
|
-
const meanP =
|
|
86
|
-
const meanV =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
184
|
+
const meanP = sumP / n;
|
|
185
|
+
const meanV = sumV / n;
|
|
186
|
+
|
|
187
|
+
// Second pass: compute covariance and variance
|
|
188
|
+
let cov = 0;
|
|
189
|
+
let varV = 0;
|
|
190
|
+
for (let i = startIdx + 1; i < length; i++) {
|
|
191
|
+
const pDiff = (bars[i].close - bars[i - 1].close) - meanP;
|
|
192
|
+
const vDiff = bars[i].volume - meanV;
|
|
193
|
+
cov += pDiff * vDiff;
|
|
194
|
+
varV += vDiff * vDiff;
|
|
91
195
|
}
|
|
92
|
-
|
|
93
|
-
|
|
196
|
+
|
|
197
|
+
cov /= n;
|
|
198
|
+
varV /= n;
|
|
199
|
+
|
|
94
200
|
if (varV < 0.0001) return 0;
|
|
95
|
-
|
|
201
|
+
|
|
202
|
+
const lambda = cov / varV;
|
|
203
|
+
return lambda < 0 ? -lambda : lambda;
|
|
96
204
|
}
|
|
97
205
|
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// MODEL 4: KALMAN FILTER - Signal Extraction
|
|
208
|
+
// =============================================================================
|
|
209
|
+
|
|
98
210
|
/**
|
|
99
|
-
*
|
|
211
|
+
* Apply Kalman filter update (reuses pre-allocated result object)
|
|
100
212
|
* @param {Object} state - {estimate, errorCovariance}
|
|
101
213
|
* @param {number} measurement - New measurement
|
|
102
|
-
* @param {number} processNoise -
|
|
103
|
-
* @param {number} measurementNoise -
|
|
104
|
-
* @returns {Object} Updated state and estimate
|
|
214
|
+
* @param {number} processNoise - Q parameter
|
|
215
|
+
* @param {number} measurementNoise - R parameter
|
|
216
|
+
* @returns {Object} Updated state and estimate (REUSED OBJECT - do not store reference)
|
|
105
217
|
*/
|
|
106
218
|
function applyKalmanFilter(state, measurement, processNoise = 0.01, measurementNoise = 0.1) {
|
|
107
219
|
if (!state || state.estimate === 0) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
220
|
+
// Initialize filter
|
|
221
|
+
_kalmanResult.state.estimate = measurement;
|
|
222
|
+
_kalmanResult.state.errorCovariance = 1.0;
|
|
223
|
+
_kalmanResult.estimate = measurement;
|
|
224
|
+
return _kalmanResult;
|
|
112
225
|
}
|
|
226
|
+
|
|
227
|
+
// Predict step
|
|
113
228
|
const predictedEstimate = state.estimate;
|
|
114
229
|
const predictedCovariance = state.errorCovariance + processNoise;
|
|
230
|
+
|
|
231
|
+
// Update step
|
|
115
232
|
const kalmanGain = predictedCovariance / (predictedCovariance + measurementNoise);
|
|
116
233
|
const newEstimate = predictedEstimate + kalmanGain * (measurement - predictedEstimate);
|
|
117
234
|
const newCovariance = (1 - kalmanGain) * predictedCovariance;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
235
|
+
|
|
236
|
+
// Reuse result object
|
|
237
|
+
_kalmanResult.state.estimate = newEstimate;
|
|
238
|
+
_kalmanResult.state.errorCovariance = newCovariance;
|
|
239
|
+
_kalmanResult.estimate = newEstimate;
|
|
240
|
+
|
|
241
|
+
return _kalmanResult;
|
|
122
242
|
}
|
|
123
243
|
|
|
124
244
|
/**
|
|
125
|
-
*
|
|
245
|
+
* Create new Kalman state (call once per contract, not in hot path)
|
|
246
|
+
* @returns {Object}
|
|
247
|
+
*/
|
|
248
|
+
function createKalmanState() {
|
|
249
|
+
return { estimate: 0, errorCovariance: 1.0 };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// =============================================================================
|
|
253
|
+
// ATR CALCULATION
|
|
254
|
+
// =============================================================================
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Calculate ATR with in-place computation
|
|
126
258
|
* @param {Array} bars - Bar data
|
|
127
259
|
* @param {number} period - ATR period
|
|
128
260
|
* @returns {number} ATR value
|
|
129
261
|
*/
|
|
130
262
|
function calculateATR(bars, period = 14) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
263
|
+
const length = bars.length;
|
|
264
|
+
if (length < period + 1) return 2.5; // Default for insufficient data
|
|
265
|
+
|
|
266
|
+
let sum = 0;
|
|
267
|
+
for (let i = length - period; i < length; i++) {
|
|
134
268
|
const bar = bars[i];
|
|
135
269
|
const prevClose = bars[i - 1].close;
|
|
136
|
-
|
|
137
|
-
|
|
270
|
+
|
|
271
|
+
// True Range = max(H-L, |H-prevC|, |L-prevC|)
|
|
272
|
+
const hl = bar.high - bar.low;
|
|
273
|
+
const hc = bar.high - prevClose;
|
|
274
|
+
const lc = bar.low - prevClose;
|
|
275
|
+
const absHc = hc < 0 ? -hc : hc;
|
|
276
|
+
const absLc = lc < 0 ? -lc : lc;
|
|
277
|
+
|
|
278
|
+
// Branchless max of 3 values
|
|
279
|
+
let tr = hl;
|
|
280
|
+
if (absHc > tr) tr = absHc;
|
|
281
|
+
if (absLc > tr) tr = absLc;
|
|
282
|
+
|
|
283
|
+
sum += tr;
|
|
138
284
|
}
|
|
139
|
-
|
|
285
|
+
|
|
286
|
+
return sum / period;
|
|
140
287
|
}
|
|
141
288
|
|
|
289
|
+
// =============================================================================
|
|
290
|
+
// MODEL 5: VOLATILITY REGIME DETECTION
|
|
291
|
+
// =============================================================================
|
|
292
|
+
|
|
142
293
|
/**
|
|
143
|
-
*
|
|
144
|
-
* @param {
|
|
145
|
-
* @param {number} currentATR - Current ATR
|
|
146
|
-
* @returns {Object} Regime
|
|
294
|
+
* Detect volatility regime with pre-allocated result
|
|
295
|
+
* @param {number[]} atrHistory - ATR history buffer
|
|
296
|
+
* @param {number} currentATR - Current ATR value
|
|
297
|
+
* @returns {Object} Regime result (REUSED OBJECT - do not store reference)
|
|
147
298
|
*/
|
|
148
299
|
function detectVolatilityRegime(atrHistory, currentATR) {
|
|
300
|
+
const length = atrHistory.length;
|
|
301
|
+
|
|
302
|
+
// Calculate percentile (in-place counting)
|
|
149
303
|
let atrPercentile = 0.5;
|
|
150
|
-
if (
|
|
151
|
-
|
|
304
|
+
if (length >= 20) {
|
|
305
|
+
let count = 0;
|
|
306
|
+
for (let i = 0; i < length; i++) {
|
|
307
|
+
if (atrHistory[i] <= currentATR) count++;
|
|
308
|
+
}
|
|
309
|
+
atrPercentile = count / length;
|
|
152
310
|
}
|
|
153
311
|
|
|
154
|
-
|
|
312
|
+
// Assign pre-allocated regime params (no object creation)
|
|
155
313
|
if (atrPercentile < 0.25) {
|
|
156
|
-
regime = 'low';
|
|
157
|
-
params =
|
|
314
|
+
_regimeResult.regime = 'low';
|
|
315
|
+
_regimeResult.params = REGIME_LOW;
|
|
158
316
|
} else if (atrPercentile < 0.75) {
|
|
159
|
-
regime = 'normal';
|
|
160
|
-
params =
|
|
317
|
+
_regimeResult.regime = 'normal';
|
|
318
|
+
_regimeResult.params = REGIME_NORMAL;
|
|
161
319
|
} else {
|
|
162
|
-
regime = 'high';
|
|
163
|
-
params =
|
|
320
|
+
_regimeResult.regime = 'high';
|
|
321
|
+
_regimeResult.params = REGIME_HIGH;
|
|
164
322
|
}
|
|
165
|
-
|
|
323
|
+
|
|
324
|
+
return _regimeResult;
|
|
166
325
|
}
|
|
167
326
|
|
|
327
|
+
// =============================================================================
|
|
328
|
+
// MODEL 6: ORDER FLOW IMBALANCE (OFI)
|
|
329
|
+
// =============================================================================
|
|
330
|
+
|
|
168
331
|
/**
|
|
169
|
-
*
|
|
332
|
+
* Compute Order Flow Imbalance with in-place calculation
|
|
170
333
|
* @param {Array} bars - Bar data
|
|
171
334
|
* @param {number} ofiLookback - Lookback period
|
|
172
335
|
* @returns {number} OFI value (-1 to 1)
|
|
173
336
|
*/
|
|
174
337
|
function computeOrderFlowImbalance(bars, ofiLookback = 20) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
338
|
+
const length = bars.length;
|
|
339
|
+
if (length < ofiLookback) return 0;
|
|
340
|
+
|
|
341
|
+
const startIdx = length - ofiLookback;
|
|
342
|
+
let buyPressure = 0;
|
|
343
|
+
let sellPressure = 0;
|
|
344
|
+
|
|
345
|
+
for (let i = startIdx; i < length; i++) {
|
|
346
|
+
const bar = bars[i];
|
|
179
347
|
const range = bar.high - bar.low;
|
|
348
|
+
|
|
180
349
|
if (range > 0) {
|
|
350
|
+
// Close position within range (0 = at low, 1 = at high)
|
|
181
351
|
const closePos = (bar.close - bar.low) / range;
|
|
182
|
-
|
|
183
|
-
|
|
352
|
+
const volume = bar.volume;
|
|
353
|
+
|
|
354
|
+
buyPressure += closePos * volume;
|
|
355
|
+
sellPressure += (1 - closePos) * volume;
|
|
184
356
|
}
|
|
185
357
|
}
|
|
358
|
+
|
|
186
359
|
const total = buyPressure + sellPressure;
|
|
187
360
|
if (total < 1) return 0;
|
|
361
|
+
|
|
188
362
|
return (buyPressure - sellPressure) / total;
|
|
189
363
|
}
|
|
190
364
|
|
|
365
|
+
// =============================================================================
|
|
366
|
+
// EXPORTS
|
|
367
|
+
// =============================================================================
|
|
368
|
+
|
|
191
369
|
module.exports = {
|
|
370
|
+
// Core models
|
|
192
371
|
computeZScore,
|
|
193
372
|
computeVPIN,
|
|
194
373
|
computeKyleLambda,
|
|
@@ -196,4 +375,12 @@ module.exports = {
|
|
|
196
375
|
calculateATR,
|
|
197
376
|
detectVolatilityRegime,
|
|
198
377
|
computeOrderFlowImbalance,
|
|
378
|
+
|
|
379
|
+
// State factory (call once per contract initialization)
|
|
380
|
+
createKalmanState,
|
|
381
|
+
|
|
382
|
+
// Pre-allocated regime params (for reference only)
|
|
383
|
+
REGIME_LOW,
|
|
384
|
+
REGIME_NORMAL,
|
|
385
|
+
REGIME_HIGH,
|
|
199
386
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hedgequantx",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.238",
|
|
4
4
|
"description": "HedgeQuantX - Prop Futures Trading CLI",
|
|
5
5
|
"main": "src/app.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"src/api/",
|
|
48
48
|
"src/config/",
|
|
49
49
|
"src/lib/data.js",
|
|
50
|
+
"src/lib/hft/",
|
|
50
51
|
"src/lib/smart-logs*.js",
|
|
51
52
|
"src/lib/smart-logs-messages/",
|
|
52
53
|
"src/lib/m/index.js",
|