hedgequantx 2.9.236 → 2.9.237
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/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.237",
|
|
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",
|
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* HFT-GRADE INFRASTRUCTURE
|
|
4
|
+
* =============================================================================
|
|
5
|
+
* Ultra-low latency primitives for high-frequency trading
|
|
6
|
+
*
|
|
7
|
+
* Components:
|
|
8
|
+
* - ObjectPool: Zero-allocation object reuse
|
|
9
|
+
* - CircularBuffer: O(1) fixed-size buffer operations
|
|
10
|
+
* - FastMath: Optimized math operations
|
|
11
|
+
* - TimestampCache: Cached time operations
|
|
12
|
+
* - FieldDecoder: Pre-indexed protobuf decoding
|
|
13
|
+
*
|
|
14
|
+
* Design Principles:
|
|
15
|
+
* - ZERO allocations in hot paths
|
|
16
|
+
* - Pre-computed lookup tables
|
|
17
|
+
* - Branch prediction optimization
|
|
18
|
+
* - Cache-friendly data structures
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// OBJECT POOL - Zero-allocation object reuse
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* High-performance object pool for hot path allocation avoidance
|
|
29
|
+
* @template T
|
|
30
|
+
*/
|
|
31
|
+
class ObjectPool {
|
|
32
|
+
/**
|
|
33
|
+
* @param {() => T} factory - Function to create new objects
|
|
34
|
+
* @param {(obj: T) => void} reset - Function to reset object state
|
|
35
|
+
* @param {number} initialSize - Initial pool size
|
|
36
|
+
*/
|
|
37
|
+
constructor(factory, reset, initialSize = 128) {
|
|
38
|
+
this.factory = factory;
|
|
39
|
+
this.reset = reset;
|
|
40
|
+
this.pool = new Array(initialSize);
|
|
41
|
+
this.index = initialSize;
|
|
42
|
+
|
|
43
|
+
// Pre-allocate all objects
|
|
44
|
+
for (let i = 0; i < initialSize; i++) {
|
|
45
|
+
this.pool[i] = factory();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Acquire object from pool (O(1), zero allocation if pool not empty)
|
|
51
|
+
* @returns {T}
|
|
52
|
+
*/
|
|
53
|
+
acquire() {
|
|
54
|
+
if (this.index > 0) {
|
|
55
|
+
return this.pool[--this.index];
|
|
56
|
+
}
|
|
57
|
+
// Pool exhausted - create new (should be rare in well-tuned system)
|
|
58
|
+
return this.factory();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Release object back to pool (O(1))
|
|
63
|
+
* @param {T} obj
|
|
64
|
+
*/
|
|
65
|
+
release(obj) {
|
|
66
|
+
this.reset(obj);
|
|
67
|
+
if (this.index < this.pool.length) {
|
|
68
|
+
this.pool[this.index++] = obj;
|
|
69
|
+
}
|
|
70
|
+
// If pool full, object is discarded (GC will collect)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get current pool size
|
|
75
|
+
* @returns {number}
|
|
76
|
+
*/
|
|
77
|
+
get available() {
|
|
78
|
+
return this.index;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Pre-warm pool to full capacity
|
|
83
|
+
*/
|
|
84
|
+
prewarm() {
|
|
85
|
+
while (this.index < this.pool.length) {
|
|
86
|
+
this.pool[this.index++] = this.factory();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// CIRCULAR BUFFER - O(1) fixed-size buffer
|
|
93
|
+
// =============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Lock-free circular buffer for time-series data
|
|
97
|
+
* Eliminates O(n) shift() operations
|
|
98
|
+
* @template T
|
|
99
|
+
*/
|
|
100
|
+
class CircularBuffer {
|
|
101
|
+
/**
|
|
102
|
+
* @param {number} capacity - Fixed buffer capacity
|
|
103
|
+
*/
|
|
104
|
+
constructor(capacity) {
|
|
105
|
+
this.buffer = new Array(capacity);
|
|
106
|
+
this.capacity = capacity;
|
|
107
|
+
this.head = 0; // Read position
|
|
108
|
+
this.tail = 0; // Write position
|
|
109
|
+
this.length = 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Add item to buffer (O(1), overwrites oldest if full)
|
|
114
|
+
* @param {T} item
|
|
115
|
+
*/
|
|
116
|
+
push(item) {
|
|
117
|
+
this.buffer[this.tail] = item;
|
|
118
|
+
this.tail = (this.tail + 1) % this.capacity;
|
|
119
|
+
|
|
120
|
+
if (this.length < this.capacity) {
|
|
121
|
+
this.length++;
|
|
122
|
+
} else {
|
|
123
|
+
// Buffer full - advance head (overwrite oldest)
|
|
124
|
+
this.head = (this.head + 1) % this.capacity;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get item at logical index (O(1))
|
|
130
|
+
* @param {number} index - 0 = oldest, length-1 = newest
|
|
131
|
+
* @returns {T}
|
|
132
|
+
*/
|
|
133
|
+
get(index) {
|
|
134
|
+
if (index < 0 || index >= this.length) return undefined;
|
|
135
|
+
return this.buffer[(this.head + index) % this.capacity];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get most recent item (O(1))
|
|
140
|
+
* @returns {T}
|
|
141
|
+
*/
|
|
142
|
+
last() {
|
|
143
|
+
if (this.length === 0) return undefined;
|
|
144
|
+
return this.buffer[(this.tail - 1 + this.capacity) % this.capacity];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get oldest item (O(1))
|
|
149
|
+
* @returns {T}
|
|
150
|
+
*/
|
|
151
|
+
first() {
|
|
152
|
+
if (this.length === 0) return undefined;
|
|
153
|
+
return this.buffer[this.head];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Iterate over all items (oldest to newest)
|
|
158
|
+
* @param {(item: T, index: number) => void} fn
|
|
159
|
+
*/
|
|
160
|
+
forEach(fn) {
|
|
161
|
+
for (let i = 0; i < this.length; i++) {
|
|
162
|
+
fn(this.buffer[(this.head + i) % this.capacity], i);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Reduce over all items
|
|
168
|
+
* @template U
|
|
169
|
+
* @param {(acc: U, item: T, index: number) => U} fn
|
|
170
|
+
* @param {U} initial
|
|
171
|
+
* @returns {U}
|
|
172
|
+
*/
|
|
173
|
+
reduce(fn, initial) {
|
|
174
|
+
let acc = initial;
|
|
175
|
+
for (let i = 0; i < this.length; i++) {
|
|
176
|
+
acc = fn(acc, this.buffer[(this.head + i) % this.capacity], i);
|
|
177
|
+
}
|
|
178
|
+
return acc;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get slice of recent items (newest N items)
|
|
183
|
+
* @param {number} count
|
|
184
|
+
* @returns {T[]}
|
|
185
|
+
*/
|
|
186
|
+
recent(count) {
|
|
187
|
+
const n = Math.min(count, this.length);
|
|
188
|
+
const result = new Array(n);
|
|
189
|
+
const start = (this.tail - n + this.capacity) % this.capacity;
|
|
190
|
+
for (let i = 0; i < n; i++) {
|
|
191
|
+
result[i] = this.buffer[(start + i) % this.capacity];
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Clear buffer (O(1))
|
|
198
|
+
*/
|
|
199
|
+
clear() {
|
|
200
|
+
this.head = 0;
|
|
201
|
+
this.tail = 0;
|
|
202
|
+
this.length = 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check if buffer is full
|
|
207
|
+
* @returns {boolean}
|
|
208
|
+
*/
|
|
209
|
+
isFull() {
|
|
210
|
+
return this.length === this.capacity;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// =============================================================================
|
|
215
|
+
// TYPED CIRCULAR BUFFER - For numeric data (Float64)
|
|
216
|
+
// =============================================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Typed circular buffer using Float64Array for numeric time-series
|
|
220
|
+
* More cache-friendly than generic CircularBuffer for numbers
|
|
221
|
+
*/
|
|
222
|
+
class Float64CircularBuffer {
|
|
223
|
+
/**
|
|
224
|
+
* @param {number} capacity
|
|
225
|
+
*/
|
|
226
|
+
constructor(capacity) {
|
|
227
|
+
this.buffer = new Float64Array(capacity);
|
|
228
|
+
this.capacity = capacity;
|
|
229
|
+
this.head = 0;
|
|
230
|
+
this.tail = 0;
|
|
231
|
+
this.length = 0;
|
|
232
|
+
|
|
233
|
+
// Pre-computed values for statistics
|
|
234
|
+
this._sum = 0;
|
|
235
|
+
this._sumSq = 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Add value with running statistics (O(1))
|
|
240
|
+
* @param {number} value
|
|
241
|
+
*/
|
|
242
|
+
push(value) {
|
|
243
|
+
if (this.length === this.capacity) {
|
|
244
|
+
// Remove oldest value from running stats
|
|
245
|
+
const oldest = this.buffer[this.head];
|
|
246
|
+
this._sum -= oldest;
|
|
247
|
+
this._sumSq -= oldest * oldest;
|
|
248
|
+
this.head = (this.head + 1) % this.capacity;
|
|
249
|
+
} else {
|
|
250
|
+
this.length++;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Add new value
|
|
254
|
+
this.buffer[this.tail] = value;
|
|
255
|
+
this._sum += value;
|
|
256
|
+
this._sumSq += value * value;
|
|
257
|
+
this.tail = (this.tail + 1) % this.capacity;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get mean (O(1) - pre-computed)
|
|
262
|
+
* @returns {number}
|
|
263
|
+
*/
|
|
264
|
+
mean() {
|
|
265
|
+
return this.length > 0 ? this._sum / this.length : 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get variance (O(1) - pre-computed)
|
|
270
|
+
* @returns {number}
|
|
271
|
+
*/
|
|
272
|
+
variance() {
|
|
273
|
+
if (this.length < 2) return 0;
|
|
274
|
+
const mean = this._sum / this.length;
|
|
275
|
+
return (this._sumSq / this.length) - (mean * mean);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get standard deviation (O(1))
|
|
280
|
+
* @returns {number}
|
|
281
|
+
*/
|
|
282
|
+
std() {
|
|
283
|
+
return Math.sqrt(Math.max(0, this.variance()));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get value at index
|
|
288
|
+
* @param {number} index
|
|
289
|
+
* @returns {number}
|
|
290
|
+
*/
|
|
291
|
+
get(index) {
|
|
292
|
+
if (index < 0 || index >= this.length) return NaN;
|
|
293
|
+
return this.buffer[(this.head + index) % this.capacity];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get most recent value
|
|
298
|
+
* @returns {number}
|
|
299
|
+
*/
|
|
300
|
+
last() {
|
|
301
|
+
if (this.length === 0) return NaN;
|
|
302
|
+
return this.buffer[(this.tail - 1 + this.capacity) % this.capacity];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get min value (O(n) but rarely needed in hot path)
|
|
307
|
+
* @returns {number}
|
|
308
|
+
*/
|
|
309
|
+
min() {
|
|
310
|
+
if (this.length === 0) return NaN;
|
|
311
|
+
let min = this.buffer[this.head];
|
|
312
|
+
for (let i = 1; i < this.length; i++) {
|
|
313
|
+
const val = this.buffer[(this.head + i) % this.capacity];
|
|
314
|
+
if (val < min) min = val;
|
|
315
|
+
}
|
|
316
|
+
return min;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get max value
|
|
321
|
+
* @returns {number}
|
|
322
|
+
*/
|
|
323
|
+
max() {
|
|
324
|
+
if (this.length === 0) return NaN;
|
|
325
|
+
let max = this.buffer[this.head];
|
|
326
|
+
for (let i = 1; i < this.length; i++) {
|
|
327
|
+
const val = this.buffer[(this.head + i) % this.capacity];
|
|
328
|
+
if (val > max) max = val;
|
|
329
|
+
}
|
|
330
|
+
return max;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
clear() {
|
|
334
|
+
this.head = 0;
|
|
335
|
+
this.tail = 0;
|
|
336
|
+
this.length = 0;
|
|
337
|
+
this._sum = 0;
|
|
338
|
+
this._sumSq = 0;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// =============================================================================
|
|
343
|
+
// FAST MATH - Optimized math operations
|
|
344
|
+
// =============================================================================
|
|
345
|
+
|
|
346
|
+
const FastMath = {
|
|
347
|
+
/**
|
|
348
|
+
* Fast square (faster than Math.pow(x, 2))
|
|
349
|
+
* @param {number} x
|
|
350
|
+
* @returns {number}
|
|
351
|
+
*/
|
|
352
|
+
square: (x) => x * x,
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Clamp value between min and max (branchless)
|
|
356
|
+
* @param {number} value
|
|
357
|
+
* @param {number} min
|
|
358
|
+
* @param {number} max
|
|
359
|
+
* @returns {number}
|
|
360
|
+
*/
|
|
361
|
+
clamp: (value, min, max) => Math.max(min, Math.min(max, value)),
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Linear interpolation
|
|
365
|
+
* @param {number} a
|
|
366
|
+
* @param {number} b
|
|
367
|
+
* @param {number} t - 0 to 1
|
|
368
|
+
* @returns {number}
|
|
369
|
+
*/
|
|
370
|
+
lerp: (a, b, t) => a + (b - a) * t,
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Sign function (-1, 0, 1)
|
|
374
|
+
* @param {number} x
|
|
375
|
+
* @returns {number}
|
|
376
|
+
*/
|
|
377
|
+
sign: (x) => (x > 0) - (x < 0),
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Fast approximate inverse square root (for non-critical paths)
|
|
381
|
+
* @param {number} x
|
|
382
|
+
* @returns {number}
|
|
383
|
+
*/
|
|
384
|
+
invSqrt: (x) => 1 / Math.sqrt(x),
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Round to tick size
|
|
388
|
+
* @param {number} price
|
|
389
|
+
* @param {number} tickSize
|
|
390
|
+
* @returns {number}
|
|
391
|
+
*/
|
|
392
|
+
roundToTick: (price, tickSize) => Math.round(price / tickSize) * tickSize,
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Calculate ticks between prices
|
|
396
|
+
* @param {number} price1
|
|
397
|
+
* @param {number} price2
|
|
398
|
+
* @param {number} tickSize
|
|
399
|
+
* @returns {number}
|
|
400
|
+
*/
|
|
401
|
+
ticksBetween: (price1, price2, tickSize) => Math.round((price1 - price2) / tickSize),
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// =============================================================================
|
|
405
|
+
// TIMESTAMP CACHE - Reduce Date.now() calls
|
|
406
|
+
// =============================================================================
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Cached timestamp provider to reduce Date.now() calls in hot paths
|
|
410
|
+
* Updates at configurable interval
|
|
411
|
+
*/
|
|
412
|
+
class TimestampCache {
|
|
413
|
+
constructor(updateIntervalMs = 1) {
|
|
414
|
+
this._now = Date.now();
|
|
415
|
+
this._hrTime = process.hrtime.bigint();
|
|
416
|
+
this._interval = null;
|
|
417
|
+
this._updateInterval = updateIntervalMs;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Start automatic updates
|
|
422
|
+
*/
|
|
423
|
+
start() {
|
|
424
|
+
if (this._interval) return;
|
|
425
|
+
this._interval = setInterval(() => {
|
|
426
|
+
this._now = Date.now();
|
|
427
|
+
this._hrTime = process.hrtime.bigint();
|
|
428
|
+
}, this._updateInterval);
|
|
429
|
+
this._interval.unref(); // Don't prevent process exit
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Stop automatic updates
|
|
434
|
+
*/
|
|
435
|
+
stop() {
|
|
436
|
+
if (this._interval) {
|
|
437
|
+
clearInterval(this._interval);
|
|
438
|
+
this._interval = null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Get cached timestamp (ms)
|
|
444
|
+
* @returns {number}
|
|
445
|
+
*/
|
|
446
|
+
now() {
|
|
447
|
+
return this._now;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get high-resolution timestamp (ns)
|
|
452
|
+
* @returns {bigint}
|
|
453
|
+
*/
|
|
454
|
+
hrNow() {
|
|
455
|
+
return this._hrTime;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Force update (use sparingly)
|
|
460
|
+
*/
|
|
461
|
+
update() {
|
|
462
|
+
this._now = Date.now();
|
|
463
|
+
this._hrTime = process.hrtime.bigint();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// =============================================================================
|
|
468
|
+
// PRE-ALLOCATED STRUCTURES
|
|
469
|
+
// =============================================================================
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Pre-allocated bar structure for tick aggregation
|
|
473
|
+
*/
|
|
474
|
+
function createBarStruct() {
|
|
475
|
+
return {
|
|
476
|
+
timestamp: 0,
|
|
477
|
+
open: 0,
|
|
478
|
+
high: 0,
|
|
479
|
+
low: 0,
|
|
480
|
+
close: 0,
|
|
481
|
+
volume: 0,
|
|
482
|
+
delta: 0,
|
|
483
|
+
tickCount: 0,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Reset bar struct for reuse
|
|
489
|
+
* @param {Object} bar
|
|
490
|
+
*/
|
|
491
|
+
function resetBarStruct(bar) {
|
|
492
|
+
bar.timestamp = 0;
|
|
493
|
+
bar.open = 0;
|
|
494
|
+
bar.high = -Infinity;
|
|
495
|
+
bar.low = Infinity;
|
|
496
|
+
bar.close = 0;
|
|
497
|
+
bar.volume = 0;
|
|
498
|
+
bar.delta = 0;
|
|
499
|
+
bar.tickCount = 0;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Pre-allocated signal structure
|
|
504
|
+
*/
|
|
505
|
+
function createSignalStruct() {
|
|
506
|
+
return {
|
|
507
|
+
id: '',
|
|
508
|
+
timestamp: 0,
|
|
509
|
+
symbol: '',
|
|
510
|
+
contractId: '',
|
|
511
|
+
side: 0,
|
|
512
|
+
direction: '',
|
|
513
|
+
strategy: '',
|
|
514
|
+
strength: 0,
|
|
515
|
+
edge: 0,
|
|
516
|
+
confidence: 0,
|
|
517
|
+
entry: 0,
|
|
518
|
+
entryPrice: 0,
|
|
519
|
+
stopLoss: 0,
|
|
520
|
+
takeProfit: 0,
|
|
521
|
+
riskReward: 0,
|
|
522
|
+
stopTicks: 0,
|
|
523
|
+
targetTicks: 0,
|
|
524
|
+
breakevenTicks: 0,
|
|
525
|
+
trailTriggerTicks: 0,
|
|
526
|
+
trailDistanceTicks: 0,
|
|
527
|
+
regime: '',
|
|
528
|
+
zscore: 0,
|
|
529
|
+
vpin: 0,
|
|
530
|
+
ofi: 0,
|
|
531
|
+
kyleLambda: 0,
|
|
532
|
+
kalmanEstimate: 0,
|
|
533
|
+
expires: 0,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Reset signal struct for reuse
|
|
539
|
+
* @param {Object} signal
|
|
540
|
+
*/
|
|
541
|
+
function resetSignalStruct(signal) {
|
|
542
|
+
signal.id = '';
|
|
543
|
+
signal.timestamp = 0;
|
|
544
|
+
signal.symbol = '';
|
|
545
|
+
signal.contractId = '';
|
|
546
|
+
signal.side = 0;
|
|
547
|
+
signal.direction = '';
|
|
548
|
+
signal.strategy = '';
|
|
549
|
+
signal.strength = 0;
|
|
550
|
+
signal.edge = 0;
|
|
551
|
+
signal.confidence = 0;
|
|
552
|
+
signal.entry = 0;
|
|
553
|
+
signal.entryPrice = 0;
|
|
554
|
+
signal.stopLoss = 0;
|
|
555
|
+
signal.takeProfit = 0;
|
|
556
|
+
signal.riskReward = 0;
|
|
557
|
+
signal.stopTicks = 0;
|
|
558
|
+
signal.targetTicks = 0;
|
|
559
|
+
signal.breakevenTicks = 0;
|
|
560
|
+
signal.trailTriggerTicks = 0;
|
|
561
|
+
signal.trailDistanceTicks = 0;
|
|
562
|
+
signal.regime = '';
|
|
563
|
+
signal.zscore = 0;
|
|
564
|
+
signal.vpin = 0;
|
|
565
|
+
signal.ofi = 0;
|
|
566
|
+
signal.kyleLambda = 0;
|
|
567
|
+
signal.kalmanEstimate = 0;
|
|
568
|
+
signal.expires = 0;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Pre-allocated trade result structure
|
|
573
|
+
*/
|
|
574
|
+
function createTradeStruct() {
|
|
575
|
+
return {
|
|
576
|
+
id: '',
|
|
577
|
+
entryTime: 0,
|
|
578
|
+
exitTime: 0,
|
|
579
|
+
direction: 0,
|
|
580
|
+
entryPrice: 0,
|
|
581
|
+
exitPrice: 0,
|
|
582
|
+
quantity: 0,
|
|
583
|
+
grossPnl: 0,
|
|
584
|
+
netPnl: 0,
|
|
585
|
+
commission: 0,
|
|
586
|
+
slippage: 0,
|
|
587
|
+
exitReason: '',
|
|
588
|
+
duration: 0,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function resetTradeStruct(trade) {
|
|
593
|
+
trade.id = '';
|
|
594
|
+
trade.entryTime = 0;
|
|
595
|
+
trade.exitTime = 0;
|
|
596
|
+
trade.direction = 0;
|
|
597
|
+
trade.entryPrice = 0;
|
|
598
|
+
trade.exitPrice = 0;
|
|
599
|
+
trade.quantity = 0;
|
|
600
|
+
trade.grossPnl = 0;
|
|
601
|
+
trade.netPnl = 0;
|
|
602
|
+
trade.commission = 0;
|
|
603
|
+
trade.slippage = 0;
|
|
604
|
+
trade.exitReason = '';
|
|
605
|
+
trade.duration = 0;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// =============================================================================
|
|
609
|
+
// SINGLETON INSTANCES
|
|
610
|
+
// =============================================================================
|
|
611
|
+
|
|
612
|
+
// Global timestamp cache (start on import)
|
|
613
|
+
const timestampCache = new TimestampCache(1);
|
|
614
|
+
timestampCache.start();
|
|
615
|
+
|
|
616
|
+
// Pre-created object pools
|
|
617
|
+
const barPool = new ObjectPool(createBarStruct, resetBarStruct, 256);
|
|
618
|
+
const signalPool = new ObjectPool(createSignalStruct, resetSignalStruct, 64);
|
|
619
|
+
const tradePool = new ObjectPool(createTradeStruct, resetTradeStruct, 128);
|
|
620
|
+
|
|
621
|
+
// =============================================================================
|
|
622
|
+
// EXPORTS
|
|
623
|
+
// =============================================================================
|
|
624
|
+
|
|
625
|
+
module.exports = {
|
|
626
|
+
// Classes
|
|
627
|
+
ObjectPool,
|
|
628
|
+
CircularBuffer,
|
|
629
|
+
Float64CircularBuffer,
|
|
630
|
+
TimestampCache,
|
|
631
|
+
|
|
632
|
+
// Fast math utilities
|
|
633
|
+
FastMath,
|
|
634
|
+
|
|
635
|
+
// Struct factories
|
|
636
|
+
createBarStruct,
|
|
637
|
+
resetBarStruct,
|
|
638
|
+
createSignalStruct,
|
|
639
|
+
resetSignalStruct,
|
|
640
|
+
createTradeStruct,
|
|
641
|
+
resetTradeStruct,
|
|
642
|
+
|
|
643
|
+
// Singleton pools
|
|
644
|
+
barPool,
|
|
645
|
+
signalPool,
|
|
646
|
+
tradePool,
|
|
647
|
+
timestampCache,
|
|
648
|
+
};
|