hedgequantx 2.4.6 → 2.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/package.json +2 -6
  2. package/src/lib/data.js +204 -473
  3. package/src/lib/m/s1.js +450 -301
  4. package/dist/lib/api.js +0 -1
  5. package/dist/lib/api.jsc +0 -0
  6. package/dist/lib/api2.js +0 -1
  7. package/dist/lib/api2.jsc +0 -0
  8. package/dist/lib/core.js +0 -1
  9. package/dist/lib/core.jsc +0 -0
  10. package/dist/lib/core2.js +0 -1
  11. package/dist/lib/core2.jsc +0 -0
  12. package/dist/lib/data.js +0 -1
  13. package/dist/lib/data.jsc +0 -0
  14. package/dist/lib/data2.js +0 -1
  15. package/dist/lib/data2.jsc +0 -0
  16. package/dist/lib/decoder.js +0 -1
  17. package/dist/lib/decoder.jsc +0 -0
  18. package/dist/lib/m/mod1.js +0 -1
  19. package/dist/lib/m/mod1.jsc +0 -0
  20. package/dist/lib/m/mod2.js +0 -1
  21. package/dist/lib/m/mod2.jsc +0 -0
  22. package/dist/lib/n/r1.js +0 -1
  23. package/dist/lib/n/r1.jsc +0 -0
  24. package/dist/lib/n/r2.js +0 -1
  25. package/dist/lib/n/r2.jsc +0 -0
  26. package/dist/lib/n/r3.js +0 -1
  27. package/dist/lib/n/r3.jsc +0 -0
  28. package/dist/lib/n/r4.js +0 -1
  29. package/dist/lib/n/r4.jsc +0 -0
  30. package/dist/lib/n/r5.js +0 -1
  31. package/dist/lib/n/r5.jsc +0 -0
  32. package/dist/lib/n/r6.js +0 -1
  33. package/dist/lib/n/r6.jsc +0 -0
  34. package/dist/lib/n/r7.js +0 -1
  35. package/dist/lib/n/r7.jsc +0 -0
  36. package/dist/lib/o/util1.js +0 -1
  37. package/dist/lib/o/util1.jsc +0 -0
  38. package/dist/lib/o/util2.js +0 -1
  39. package/dist/lib/o/util2.jsc +0 -0
  40. package/src/lib/api.js +0 -198
  41. package/src/lib/api2.js +0 -353
  42. package/src/lib/core.js +0 -539
  43. package/src/lib/core2.js +0 -341
  44. package/src/lib/data2.js +0 -492
  45. package/src/lib/decoder.js +0 -599
  46. package/src/lib/m/s2.js +0 -34
  47. package/src/lib/n/r1.js +0 -454
  48. package/src/lib/n/r2.js +0 -514
  49. package/src/lib/n/r3.js +0 -631
  50. package/src/lib/n/r4.js +0 -401
  51. package/src/lib/n/r5.js +0 -335
  52. package/src/lib/n/r6.js +0 -425
  53. package/src/lib/n/r7.js +0 -530
  54. package/src/lib/o/l1.js +0 -44
  55. package/src/lib/o/l2.js +0 -427
  56. package/src/lib/python-bridge.js +0 -206
package/src/lib/m/s1.js CHANGED
@@ -1,372 +1,521 @@
1
1
  /**
2
- * HQX Ultra Scalping Strategy
3
- * Tick-based real-time scalping
2
+ * =============================================================================
3
+ * HQX ULTRA SCALPING STRATEGY
4
+ * =============================================================================
5
+ * 6 Mathematical Models with 4-Layer Trailing Stop System
6
+ *
7
+ * BACKTEST RESULTS (162 tests, V4):
8
+ * - Net P&L: $195,272.52
9
+ * - Win Rate: 86.3%
10
+ * - Profit Factor: 34.44
11
+ * - Sharpe: 1.29
12
+ * - Tests Passed: 150/162 (92.6%)
13
+ *
14
+ * MATHEMATICAL MODELS:
15
+ * 1. Z-Score Mean Reversion (Entry: |Z| > threshold, Exit: |Z| < 0.5)
16
+ * 2. VPIN (Volume-Synchronized Probability of Informed Trading)
17
+ * 3. Kyle's Lambda (Price Impact / Liquidity Measurement)
18
+ * 4. Kalman Filter (Signal Extraction from Noise)
19
+ * 5. Volatility Regime Detection (Low/Normal/High adaptive)
20
+ * 6. Order Flow Imbalance (OFI) - Directional Bias Confirmation
21
+ *
22
+ * KEY PARAMETERS:
23
+ * - Stop: 8 ticks = $40
24
+ * - Target: 16 ticks = $80
25
+ * - R:R = 1:2
26
+ * - Trailing: 50% profit lock
27
+ *
28
+ * SOURCE: /root/HQX-Dev/hqx_tg/src/algo/strategy/hqx-ultra-scalping.strategy.ts
4
29
  */
30
+
31
+ 'use strict';
32
+
5
33
  const EventEmitter = require('events');
6
34
  const { v4: uuidv4 } = require('uuid');
7
35
 
8
- const OS = { B: 0, A: 1 }; // Order Side: Buy, Ask(Sell)
9
- const SS = { W: 1, M: 2, S: 3, VS: 4, E: 5 }; // Signal Strength
36
+ // =============================================================================
37
+ // CONSTANTS
38
+ // =============================================================================
39
+
40
+ const OrderSide = { BID: 'BID', ASK: 'ASK' };
41
+ const SignalStrength = { WEAK: 'WEAK', MODERATE: 'MODERATE', STRONG: 'STRONG', VERY_STRONG: 'VERY_STRONG' };
42
+
43
+ // =============================================================================
44
+ // HELPER: Extract base symbol from contractId
45
+ // =============================================================================
46
+ function extractBaseSymbol(contractId) {
47
+ // CON.F.US.ENQ.H25 -> NQ, CON.F.US.EP.H25 -> ES
48
+ const mapping = {
49
+ 'ENQ': 'NQ', 'EP': 'ES', 'EMD': 'EMD', 'RTY': 'RTY',
50
+ 'MNQ': 'MNQ', 'MES': 'MES', 'M2K': 'M2K', 'MYM': 'MYM',
51
+ 'NKD': 'NKD', 'GC': 'GC', 'SI': 'SI', 'CL': 'CL', 'YM': 'YM'
52
+ };
53
+
54
+ if (!contractId) return 'UNKNOWN';
55
+ const parts = contractId.split('.');
56
+ if (parts.length >= 4) {
57
+ const symbol = parts[3];
58
+ return mapping[symbol] || symbol;
59
+ }
60
+ return contractId;
61
+ }
62
+
63
+ // =============================================================================
64
+ // HQX ULTRA SCALPING STRATEGY CLASS
65
+ // =============================================================================
10
66
 
11
- /**
12
- * VWAP Calculator - Real-time tick-based
13
- */
14
- class VC {
67
+ class HQXUltraScalpingStrategy extends EventEmitter {
15
68
  constructor() {
16
- this.data = new Map();
69
+ super();
70
+
71
+ this.tickSize = 0.25;
72
+ this.tickValue = 5.0;
73
+
74
+ // === Model Parameters (from V4 backtest) ===
75
+ this.zscoreEntryThreshold = 1.5; // Adaptive per regime
76
+ this.zscoreExitThreshold = 0.5;
77
+ this.vpinWindow = 50;
78
+ this.vpinToxicThreshold = 0.7;
79
+ this.kalmanProcessNoise = 0.01;
80
+ this.kalmanMeasurementNoise = 0.1;
81
+ this.volatilityLookback = 100;
82
+ this.ofiLookback = 20;
83
+
84
+ // === Trade Parameters (from V4 backtest) ===
85
+ this.baseStopTicks = 8; // $40
86
+ this.baseTargetTicks = 16; // $80
87
+ this.breakevenTicks = 4; // Move to BE at +4 ticks
88
+ this.profitLockPct = 0.5; // Lock 50% of profit
89
+
90
+ // === State Storage ===
91
+ this.barHistory = new Map();
92
+ this.kalmanStates = new Map();
93
+ this.priceBuffer = new Map();
94
+ this.volumeBuffer = new Map();
95
+ this.tradesBuffer = new Map();
96
+ this.atrHistory = new Map();
97
+
98
+ // === Tick aggregation ===
99
+ this.tickBuffer = new Map();
100
+ this.lastBarTime = new Map();
101
+ this.barIntervalMs = 5000; // 5-second bars
102
+
103
+ // === Performance Tracking ===
104
+ this.recentTrades = [];
105
+ this.winStreak = 0;
106
+ this.lossStreak = 0;
17
107
  }
18
108
 
19
- getKey(c) {
20
- return `${c}_${new Date().toISOString().split('T')[0]}`;
109
+ /**
110
+ * Initialize strategy for a contract
111
+ */
112
+ initialize(contractId, tickSize = 0.25, tickValue = 5.0) {
113
+ this.tickSize = tickSize;
114
+ this.tickValue = tickValue;
115
+ this.barHistory.set(contractId, []);
116
+ this.priceBuffer.set(contractId, []);
117
+ this.volumeBuffer.set(contractId, []);
118
+ this.tradesBuffer.set(contractId, []);
119
+ this.atrHistory.set(contractId, []);
120
+ this.tickBuffer.set(contractId, []);
121
+ this.lastBarTime.set(contractId, 0);
122
+ this.kalmanStates.set(contractId, { estimate: 0, errorCovariance: 1.0 });
21
123
  }
22
124
 
23
- init(c) {
24
- const k = this.getKey(c);
25
- if (!this.data.has(k)) {
26
- this.data.set(k, {
27
- vwap: 0,
28
- cumVolume: 0,
29
- cumTPV: 0, // cumulative typical price * volume
30
- stdDev: 0,
31
- prices: [],
32
- high: -Infinity,
33
- low: Infinity,
34
- tickCount: 0
35
- });
125
+ /**
126
+ * Process a tick - aggregates into bars then runs strategy
127
+ */
128
+ processTick(tick) {
129
+ const contractId = tick.contractId;
130
+
131
+ if (!this.barHistory.has(contractId)) {
132
+ this.initialize(contractId);
36
133
  }
37
- }
38
134
 
39
- // Process each tick
40
- addTick(c, price, volume = 1) {
41
- const k = this.getKey(c);
42
- let d = this.data.get(k);
43
- if (!d) {
44
- this.init(c);
45
- d = this.data.get(k);
46
- }
135
+ // Add tick to buffer
136
+ let ticks = this.tickBuffer.get(contractId);
137
+ ticks.push(tick);
47
138
 
48
- // Update VWAP
49
- d.cumVolume += volume;
50
- d.cumTPV += price * volume;
51
- if (d.cumVolume > 0) {
52
- d.vwap = d.cumTPV / d.cumVolume;
139
+ // Check if we should form a new bar
140
+ const now = Date.now();
141
+ const lastBar = this.lastBarTime.get(contractId);
142
+
143
+ if (now - lastBar >= this.barIntervalMs && ticks.length > 0) {
144
+ const bar = this._aggregateTicksToBar(ticks, now);
145
+ this.tickBuffer.set(contractId, []);
146
+ this.lastBarTime.set(contractId, now);
147
+
148
+ if (bar) {
149
+ const signal = this.processBar(contractId, bar);
150
+ if (signal) {
151
+ this.emit('signal', signal);
152
+ return signal;
153
+ }
154
+ }
53
155
  }
156
+ return null;
157
+ }
54
158
 
55
- // Track prices for std dev (last 100)
56
- d.prices.push(price);
57
- if (d.prices.length > 100) d.prices.shift();
58
-
59
- // Calculate standard deviation
60
- if (d.prices.length >= 10) {
61
- const mean = d.prices.reduce((a, b) => a + b, 0) / d.prices.length;
62
- const variance = d.prices.reduce((s, p) => s + Math.pow(p - mean, 2), 0) / d.prices.length;
63
- d.stdDev = Math.sqrt(variance);
159
+ /**
160
+ * Aggregate ticks into a bar
161
+ */
162
+ _aggregateTicksToBar(ticks, timestamp) {
163
+ if (ticks.length === 0) return null;
164
+
165
+ const prices = ticks.map(t => t.price).filter(p => p != null);
166
+ if (prices.length === 0) return null;
167
+
168
+ let buyVol = 0, sellVol = 0;
169
+ for (let i = 1; i < ticks.length; i++) {
170
+ const vol = ticks[i].volume || 1;
171
+ if (ticks[i].price > ticks[i-1].price) buyVol += vol;
172
+ else if (ticks[i].price < ticks[i-1].price) sellVol += vol;
173
+ else { buyVol += vol / 2; sellVol += vol / 2; }
64
174
  }
65
175
 
66
- // Update high/low
67
- if (price > d.high) d.high = price;
68
- if (price < d.low) d.low = price;
69
- d.tickCount++;
70
-
71
- return d;
176
+ return {
177
+ timestamp,
178
+ open: prices[0],
179
+ high: Math.max(...prices),
180
+ low: Math.min(...prices),
181
+ close: prices[prices.length - 1],
182
+ volume: ticks.reduce((sum, t) => sum + (t.volume || 1), 0),
183
+ delta: buyVol - sellVol,
184
+ tickCount: ticks.length
185
+ };
72
186
  }
73
187
 
74
- // Analyze current price vs VWAP
75
- analyze(c, currentPrice, tickSize = 0.25) {
76
- const k = this.getKey(c);
77
- const d = this.data.get(k);
78
- if (!d || d.vwap === 0) return null;
79
-
80
- const deviation = currentPrice - d.vwap;
81
- const zScore = d.stdDev > 0 ? deviation / d.stdDev : 0;
188
+ /**
189
+ * Process a new bar and potentially generate signal
190
+ */
191
+ processBar(contractId, bar) {
192
+ let bars = this.barHistory.get(contractId);
193
+ if (!bars) {
194
+ this.initialize(contractId);
195
+ bars = this.barHistory.get(contractId);
196
+ }
82
197
 
83
- return {
84
- vwap: d.vwap,
85
- price: currentPrice,
86
- deviation,
87
- zScore,
88
- stdDev: d.stdDev,
89
- high: d.high,
90
- low: d.low,
91
- tickCount: d.tickCount
92
- };
198
+ bars.push(bar);
199
+ if (bars.length > 500) bars.shift();
200
+
201
+ // Update price buffer
202
+ const prices = this.priceBuffer.get(contractId);
203
+ prices.push(bar.close);
204
+ if (prices.length > 200) prices.shift();
205
+
206
+ // Update volume buffer
207
+ const volumes = this.volumeBuffer.get(contractId);
208
+ const barRange = bar.high - bar.low;
209
+ let buyVol = bar.volume * 0.5;
210
+ let sellVol = bar.volume * 0.5;
211
+ if (barRange > 0) {
212
+ const closePosition = (bar.close - bar.low) / barRange;
213
+ buyVol = bar.volume * closePosition;
214
+ sellVol = bar.volume * (1 - closePosition);
215
+ }
216
+ volumes.push({ buy: buyVol, sell: sellVol });
217
+ if (volumes.length > 100) volumes.shift();
218
+
219
+ // Need minimum data
220
+ if (bars.length < 50) return null;
221
+
222
+ // === 6 MODELS ===
223
+ const zscore = this._computeZScore(prices);
224
+ const vpin = this._computeVPIN(volumes);
225
+ const kyleLambda = this._computeKyleLambda(bars);
226
+ const kalmanEstimate = this._applyKalmanFilter(contractId, bar.close);
227
+ const { regime, params } = this._detectVolatilityRegime(contractId, bars);
228
+ const ofi = this._computeOrderFlowImbalance(bars);
229
+
230
+ // === SIGNAL GENERATION ===
231
+ return this._generateSignal(contractId, bar.close, zscore, vpin, kyleLambda, kalmanEstimate, regime, params, ofi, bars);
93
232
  }
94
233
 
95
- get(c) {
96
- return this.data.get(this.getKey(c)) || null;
234
+ // ===========================================================================
235
+ // MODEL 1: Z-SCORE MEAN REVERSION
236
+ // ===========================================================================
237
+ _computeZScore(prices, window = 50) {
238
+ if (prices.length < window) return 0;
239
+ const recentPrices = prices.slice(-window);
240
+ const mean = recentPrices.reduce((a, b) => a + b, 0) / window;
241
+ const variance = recentPrices.reduce((sum, p) => sum + Math.pow(p - mean, 2), 0) / window;
242
+ const std = Math.sqrt(variance);
243
+ if (std < 0.0001) return 0;
244
+ return (prices[prices.length - 1] - mean) / std;
97
245
  }
98
246
 
99
- reset(c) {
100
- this.data.delete(this.getKey(c));
247
+ // ===========================================================================
248
+ // MODEL 2: VPIN
249
+ // ===========================================================================
250
+ _computeVPIN(volumes) {
251
+ if (volumes.length < this.vpinWindow) return 0.5;
252
+ const recent = volumes.slice(-this.vpinWindow);
253
+ let totalBuy = 0, totalSell = 0;
254
+ for (const v of recent) { totalBuy += v.buy; totalSell += v.sell; }
255
+ const total = totalBuy + totalSell;
256
+ if (total < 1) return 0.5;
257
+ return Math.abs(totalBuy - totalSell) / total;
101
258
  }
102
- }
103
259
 
104
- /**
105
- * S1 - HQX Ultra Scalping Strategy
106
- * Pure tick-based, no bar conversion
107
- */
108
- class S1 extends EventEmitter {
109
- constructor(cfg = {}) {
110
- super();
111
-
112
- // Config
113
- this.tickSize = cfg.tickSize || 0.25;
114
- this.minRR = cfg.minRR || 1.2; // Minimum risk/reward
115
- this.cooldown = cfg.cooldown || 15000; // 15s between signals
116
- this.minTicks = cfg.minTicks || 20; // Min ticks before first signal
117
-
118
- // State
119
- this.vc = new VC();
120
- this.ticks = new Map(); // contractId -> tick history
121
- this.lastSignalTime = 0;
122
- this.inPosition = false;
123
-
124
- // Order flow tracking
125
- this.buyVolume = 0;
126
- this.sellVolume = 0;
127
- this.lastPrice = 0;
128
-
129
- // Stats
130
- this.stats = { signals: 0, trades: 0, wins: 0, losses: 0, pnl: 0 };
260
+ // ===========================================================================
261
+ // MODEL 3: KYLE'S LAMBDA
262
+ // ===========================================================================
263
+ _computeKyleLambda(bars) {
264
+ if (bars.length < 20) return 0;
265
+ const recent = bars.slice(-20);
266
+ const priceChanges = [], vols = [];
267
+ for (let i = 1; i < recent.length; i++) {
268
+ priceChanges.push(recent[i].close - recent[i - 1].close);
269
+ vols.push(recent[i].volume);
270
+ }
271
+ const meanP = priceChanges.reduce((a, b) => a + b, 0) / priceChanges.length;
272
+ const meanV = vols.reduce((a, b) => a + b, 0) / vols.length;
273
+ let cov = 0, varV = 0;
274
+ for (let i = 0; i < priceChanges.length; i++) {
275
+ cov += (priceChanges[i] - meanP) * (vols[i] - meanV);
276
+ varV += Math.pow(vols[i] - meanV, 2);
277
+ }
278
+ cov /= priceChanges.length;
279
+ varV /= priceChanges.length;
280
+ if (varV < 0.0001) return 0;
281
+ return Math.abs(cov / varV);
131
282
  }
132
283
 
133
- initialize(contractId, tickSize = 0.25) {
134
- this.tickSize = tickSize;
135
- this.ticks.set(contractId, []);
136
- this.vc.init(contractId);
284
+ // ===========================================================================
285
+ // MODEL 4: KALMAN FILTER
286
+ // ===========================================================================
287
+ _applyKalmanFilter(contractId, measurement) {
288
+ let state = this.kalmanStates.get(contractId);
289
+ if (!state) {
290
+ state = { estimate: measurement, errorCovariance: 1.0 };
291
+ this.kalmanStates.set(contractId, state);
292
+ return measurement;
293
+ }
294
+ const predictedEstimate = state.estimate;
295
+ const predictedCovariance = state.errorCovariance + this.kalmanProcessNoise;
296
+ const kalmanGain = predictedCovariance / (predictedCovariance + this.kalmanMeasurementNoise);
297
+ state.estimate = predictedEstimate + kalmanGain * (measurement - predictedEstimate);
298
+ state.errorCovariance = (1 - kalmanGain) * predictedCovariance;
299
+ return state.estimate;
137
300
  }
138
301
 
139
- /**
140
- * Process incoming tick - MAIN ENTRY POINT
141
- */
142
- processTick(tick) {
143
- const { contractId, price, bid, ask, volume = 1, side, timestamp } = tick;
144
-
145
- if (!price || price <= 0) return null;
146
-
147
- // Store tick
148
- let tickHistory = this.ticks.get(contractId);
149
- if (!tickHistory) {
150
- tickHistory = [];
151
- this.ticks.set(contractId, tickHistory);
302
+ // ===========================================================================
303
+ // MODEL 5: VOLATILITY REGIME
304
+ // ===========================================================================
305
+ _detectVolatilityRegime(contractId, bars) {
306
+ const atr = this._calculateATR(bars);
307
+ let atrHist = this.atrHistory.get(contractId);
308
+ if (!atrHist) { atrHist = []; this.atrHistory.set(contractId, atrHist); }
309
+ atrHist.push(atr);
310
+ if (atrHist.length > 500) atrHist.shift();
311
+
312
+ let atrPercentile = 0.5;
313
+ if (atrHist.length >= 20) {
314
+ atrPercentile = atrHist.filter(a => a <= atr).length / atrHist.length;
152
315
  }
153
-
154
- const tickData = {
155
- price,
156
- bid: bid || price,
157
- ask: ask || price,
158
- volume,
159
- side: side || (price >= this.lastPrice ? 'buy' : 'sell'),
160
- timestamp: timestamp || Date.now()
161
- };
162
-
163
- tickHistory.push(tickData);
164
- if (tickHistory.length > 500) tickHistory.shift();
165
-
166
- // Update VWAP
167
- this.vc.addTick(contractId, price, volume);
168
-
169
- // Update order flow
170
- if (tickData.side === 'buy') {
171
- this.buyVolume += volume;
316
+
317
+ let regime, params;
318
+ if (atrPercentile < 0.25) {
319
+ regime = 'low';
320
+ params = { stopMultiplier: 0.8, targetMultiplier: 0.9, zscoreThreshold: 1.2, confidenceBonus: 0.05 };
321
+ } else if (atrPercentile < 0.75) {
322
+ regime = 'normal';
323
+ params = { stopMultiplier: 1.0, targetMultiplier: 1.0, zscoreThreshold: 1.5, confidenceBonus: 0.0 };
172
324
  } else {
173
- this.sellVolume += volume;
325
+ regime = 'high';
326
+ params = { stopMultiplier: 1.3, targetMultiplier: 1.2, zscoreThreshold: 2.0, confidenceBonus: -0.05 };
174
327
  }
175
-
176
- this.lastPrice = price;
177
-
178
- // Check for signal
179
- return this.checkSignal(contractId, price, tickHistory);
328
+ return { regime, params };
180
329
  }
181
330
 
182
- /**
183
- * Check if conditions are met for a signal
184
- */
185
- checkSignal(contractId, price, ticks) {
186
- // Need minimum ticks
187
- if (ticks.length < this.minTicks) return null;
188
-
189
- // Cooldown check
190
- if (Date.now() - this.lastSignalTime < this.cooldown) return null;
191
-
192
- // Don't signal if already in position
193
- if (this.inPosition) return null;
194
-
195
- // Get VWAP analysis
196
- const vwap = this.vc.analyze(contractId, price, this.tickSize);
197
- if (!vwap || vwap.tickCount < this.minTicks) return null;
198
-
199
- // Calculate order flow imbalance
200
- const totalVolume = this.buyVolume + this.sellVolume;
201
- const imbalance = totalVolume > 0 ? (this.buyVolume - this.sellVolume) / totalVolume : 0;
202
-
203
- // Calculate momentum (last 10 ticks)
204
- const recentTicks = ticks.slice(-10);
205
- const momentum = recentTicks.length >= 2
206
- ? recentTicks[recentTicks.length - 1].price - recentTicks[0].price
207
- : 0;
208
-
209
- // Signal logic
210
- let direction = null;
211
- let confidence = 0;
212
-
213
- // LONG conditions: Price below VWAP + buying pressure
214
- if (vwap.zScore < -1.0 && imbalance > 0.1) {
215
- direction = 'long';
216
- confidence = Math.min(0.9, 0.5 + Math.abs(vwap.zScore) * 0.15 + imbalance * 0.3);
331
+ _calculateATR(bars, period = 14) {
332
+ if (bars.length < period + 1) return 2.5;
333
+ const trValues = [];
334
+ for (let i = bars.length - period; i < bars.length; i++) {
335
+ const bar = bars[i];
336
+ const prevClose = bars[i - 1].close;
337
+ const tr = Math.max(bar.high - bar.low, Math.abs(bar.high - prevClose), Math.abs(bar.low - prevClose));
338
+ trValues.push(tr);
217
339
  }
218
- // SHORT conditions: Price above VWAP + selling pressure
219
- else if (vwap.zScore > 1.0 && imbalance < -0.1) {
220
- direction = 'short';
221
- confidence = Math.min(0.9, 0.5 + Math.abs(vwap.zScore) * 0.15 + Math.abs(imbalance) * 0.3);
340
+ return trValues.reduce((a, b) => a + b, 0) / trValues.length;
341
+ }
342
+
343
+ // ===========================================================================
344
+ // MODEL 6: ORDER FLOW IMBALANCE
345
+ // ===========================================================================
346
+ _computeOrderFlowImbalance(bars) {
347
+ if (bars.length < this.ofiLookback) return 0;
348
+ const recent = bars.slice(-this.ofiLookback);
349
+ let buyPressure = 0, sellPressure = 0;
350
+ for (const bar of recent) {
351
+ const range = bar.high - bar.low;
352
+ if (range > 0) {
353
+ const closePos = (bar.close - bar.low) / range;
354
+ buyPressure += closePos * bar.volume;
355
+ sellPressure += (1 - closePos) * bar.volume;
356
+ }
222
357
  }
223
-
224
- if (!direction || confidence < 0.55) return null;
225
-
226
- // Calculate SL/TP
227
- const stopTicks = 6;
228
- const targetTicks = 8;
229
-
230
- let stopLoss, takeProfit;
358
+ const total = buyPressure + sellPressure;
359
+ if (total < 1) return 0;
360
+ return (buyPressure - sellPressure) / total;
361
+ }
362
+
363
+ // ===========================================================================
364
+ // SIGNAL GENERATION
365
+ // ===========================================================================
366
+ _generateSignal(contractId, currentPrice, zscore, vpin, kyleLambda, kalmanEstimate, regime, volParams, ofi, bars) {
367
+ const absZscore = Math.abs(zscore);
368
+ if (absZscore < volParams.zscoreThreshold) return null;
369
+ if (vpin > this.vpinToxicThreshold) return null;
370
+
371
+ let direction;
372
+ if (zscore < -volParams.zscoreThreshold) direction = 'long';
373
+ else if (zscore > volParams.zscoreThreshold) direction = 'short';
374
+ else return null;
375
+
376
+ const ofiConfirms = (direction === 'long' && ofi > 0.1) || (direction === 'short' && ofi < -0.1);
377
+ const kalmanDiff = currentPrice - kalmanEstimate;
378
+ const kalmanConfirms = (direction === 'long' && kalmanDiff < 0) || (direction === 'short' && kalmanDiff > 0);
379
+
380
+ const scores = {
381
+ zscore: Math.min(1.0, absZscore / 4.0),
382
+ vpin: 1.0 - vpin,
383
+ kyleLambda: kyleLambda > 0.001 ? 0.5 : 0.8,
384
+ kalman: kalmanConfirms ? 0.8 : 0.4,
385
+ volatility: regime === 'normal' ? 0.8 : regime === 'low' ? 0.7 : 0.6,
386
+ ofi: ofiConfirms ? 0.9 : 0.5,
387
+ composite: 0
388
+ };
389
+
390
+ scores.composite = scores.zscore * 0.30 + scores.vpin * 0.15 + scores.kyleLambda * 0.10 +
391
+ scores.kalman * 0.15 + scores.volatility * 0.10 + scores.ofi * 0.20;
392
+
393
+ const confidence = Math.min(1.0, scores.composite + volParams.confidenceBonus);
394
+ if (confidence < 0.55) return null;
395
+
396
+ const stopTicks = Math.round(this.baseStopTicks * volParams.stopMultiplier);
397
+ const targetTicks = Math.round(this.baseTargetTicks * volParams.targetMultiplier);
398
+ const actualStopTicks = Math.max(6, Math.min(12, stopTicks));
399
+ const actualTargetTicks = Math.max(actualStopTicks * 1.5, Math.min(24, targetTicks));
400
+
401
+ let stopLoss, takeProfit, beBreakeven, profitLockLevel;
231
402
  if (direction === 'long') {
232
- stopLoss = price - stopTicks * this.tickSize;
233
- takeProfit = price + targetTicks * this.tickSize;
403
+ stopLoss = currentPrice - actualStopTicks * this.tickSize;
404
+ takeProfit = currentPrice + actualTargetTicks * this.tickSize;
405
+ beBreakeven = currentPrice + this.breakevenTicks * this.tickSize;
406
+ profitLockLevel = currentPrice + (actualTargetTicks * this.profitLockPct) * this.tickSize;
234
407
  } else {
235
- stopLoss = price + stopTicks * this.tickSize;
236
- takeProfit = price - targetTicks * this.tickSize;
408
+ stopLoss = currentPrice + actualStopTicks * this.tickSize;
409
+ takeProfit = currentPrice - actualTargetTicks * this.tickSize;
410
+ beBreakeven = currentPrice - this.breakevenTicks * this.tickSize;
411
+ profitLockLevel = currentPrice - (actualTargetTicks * this.profitLockPct) * this.tickSize;
237
412
  }
238
-
239
- // Check R:R
240
- const risk = Math.abs(price - stopLoss);
241
- const reward = Math.abs(takeProfit - price);
242
- if (reward / risk < this.minRR) return null;
243
-
244
- // Generate signal
245
- this.lastSignalTime = Date.now();
246
- this.stats.signals++;
247
-
248
- const signal = {
413
+
414
+ const riskReward = actualTargetTicks / actualStopTicks;
415
+ const trailTriggerTicks = Math.round(actualTargetTicks * 0.5);
416
+ const trailDistanceTicks = Math.round(actualStopTicks * 0.4);
417
+
418
+ let strength = SignalStrength.MODERATE;
419
+ if (confidence >= 0.85) strength = SignalStrength.VERY_STRONG;
420
+ else if (confidence >= 0.75) strength = SignalStrength.STRONG;
421
+ else if (confidence < 0.60) strength = SignalStrength.WEAK;
422
+
423
+ const winProb = 0.5 + (confidence - 0.5) * 0.4;
424
+ const edge = winProb * Math.abs(takeProfit - currentPrice) - (1 - winProb) * Math.abs(currentPrice - stopLoss);
425
+
426
+ return {
249
427
  id: uuidv4(),
250
428
  timestamp: Date.now(),
429
+ symbol: extractBaseSymbol(contractId),
251
430
  contractId,
431
+ side: direction === 'long' ? OrderSide.BID : OrderSide.ASK,
252
432
  direction,
253
- side: direction === 'long' ? OS.B : OS.A,
254
- entry: price,
255
- entryPrice: price,
433
+ strategy: 'HQX_ULTRA_SCALPING',
434
+ strength,
435
+ edge,
436
+ confidence,
437
+ entry: currentPrice,
438
+ entryPrice: currentPrice,
256
439
  stopLoss,
257
440
  takeProfit,
258
- stopTicks,
259
- targetTicks,
260
- confidence,
261
- zScore: vwap.zScore,
262
- vwap: vwap.vwap,
263
- imbalance,
264
- momentum,
265
- riskReward: reward / risk
441
+ riskReward,
442
+ stopTicks: actualStopTicks,
443
+ targetTicks: actualTargetTicks,
444
+ trailTriggerTicks,
445
+ trailDistanceTicks,
446
+ beBreakeven,
447
+ profitLockLevel,
448
+ zScore: zscore,
449
+ zScoreExit: this.zscoreExitThreshold,
450
+ vpinValue: vpin,
451
+ kyleLambda,
452
+ kalmanEstimate,
453
+ volatilityRegime: regime,
454
+ ofiValue: ofi,
455
+ models: scores
266
456
  };
267
-
268
- this.emit('signal', signal);
269
- return signal;
270
457
  }
271
458
 
272
459
  /**
273
- * Called when entering a position
460
+ * Check if should exit by Z-Score
274
461
  */
275
- onPositionOpened() {
276
- this.inPosition = true;
462
+ shouldExitByZScore(contractId) {
463
+ const prices = this.priceBuffer.get(contractId);
464
+ if (!prices || prices.length < 50) return false;
465
+ const zscore = this._computeZScore(prices);
466
+ return Math.abs(zscore) < this.zscoreExitThreshold;
277
467
  }
278
468
 
279
469
  /**
280
- * Called when position is closed
470
+ * Get current model values
281
471
  */
282
- onPositionClosed(pnl) {
283
- this.inPosition = false;
284
- this.recordTradeResult(pnl);
472
+ getModelValues(contractId) {
473
+ const prices = this.priceBuffer.get(contractId);
474
+ const volumes = this.volumeBuffer.get(contractId);
475
+ const bars = this.barHistory.get(contractId);
476
+ if (!prices || !volumes || !bars || bars.length < 50) return null;
477
+
478
+ return {
479
+ zscore: this._computeZScore(prices).toFixed(2),
480
+ vpin: (this._computeVPIN(volumes) * 100).toFixed(1) + '%',
481
+ ofi: (this._computeOrderFlowImbalance(bars) * 100).toFixed(1) + '%',
482
+ bars: bars.length
483
+ };
285
484
  }
286
485
 
287
486
  /**
288
487
  * Record trade result
289
488
  */
290
489
  recordTradeResult(pnl) {
291
- this.stats.trades++;
292
- this.stats.pnl += pnl;
293
- if (pnl > 0) {
294
- this.stats.wins++;
295
- } else {
296
- this.stats.losses++;
297
- }
298
-
299
- // Reset order flow after trade
300
- this.buyVolume = 0;
301
- this.sellVolume = 0;
490
+ this.recentTrades.push({ pnl, timestamp: Date.now() });
491
+ if (this.recentTrades.length > 100) this.recentTrades.shift();
492
+ if (pnl > 0) { this.winStreak++; this.lossStreak = 0; }
493
+ else { this.lossStreak++; this.winStreak = 0; }
302
494
  }
303
495
 
304
- getStats() {
305
- return this.stats;
496
+ /**
497
+ * Get bar history
498
+ */
499
+ getBarHistory(contractId) {
500
+ return this.barHistory.get(contractId) || [];
306
501
  }
307
502
 
503
+ /**
504
+ * Reset strategy
505
+ */
308
506
  reset(contractId) {
309
- this.ticks.set(contractId, []);
310
- this.vc.reset(contractId);
311
- this.buyVolume = 0;
312
- this.sellVolume = 0;
313
- this.lastSignalTime = 0;
314
- this.inPosition = false;
507
+ this.barHistory.set(contractId, []);
508
+ this.priceBuffer.set(contractId, []);
509
+ this.volumeBuffer.set(contractId, []);
510
+ this.tradesBuffer.set(contractId, []);
511
+ this.atrHistory.set(contractId, []);
512
+ this.tickBuffer.set(contractId, []);
513
+ this.lastBarTime.set(contractId, 0);
514
+ this.kalmanStates.set(contractId, { estimate: 0, errorCovariance: 1.0 });
315
515
  }
316
516
  }
317
517
 
318
- /**
319
- * M1 - Main Strategy Interface
320
- */
321
- class M1 extends EventEmitter {
322
- constructor(cfg = {}) {
323
- super();
324
- this.cfg = cfg;
325
- this.s = new S1(cfg);
326
-
327
- // Forward signals
328
- this.s.on('signal', (sig) => {
329
- this.emit('signal', {
330
- ...sig,
331
- side: sig.direction === 'long' ? 'buy' : 'sell',
332
- action: 'open',
333
- reason: `Z=${sig.zScore.toFixed(2)}, conf=${(sig.confidence * 100).toFixed(0)}%`
334
- });
335
- });
336
- }
337
-
338
- processTick(t) {
339
- return this.s.processTick(t);
340
- }
341
-
342
- onTick(t) {
343
- return this.processTick(t);
344
- }
345
-
346
- initialize(contractId, tickSize) {
347
- this.s.initialize(contractId, tickSize);
348
- }
349
-
350
- onPositionOpened() {
351
- this.s.onPositionOpened();
352
- }
353
-
354
- onPositionClosed(pnl) {
355
- this.s.onPositionClosed(pnl);
356
- }
357
-
358
- recordTradeResult(pnl) {
359
- this.s.recordTradeResult(pnl);
360
- }
361
-
362
- getStats() {
363
- return this.s.getStats();
364
- }
365
-
366
- reset(contractId) {
367
- this.s.reset(contractId);
368
- this.emit('log', { type: 'info', message: 'Strategy reset' });
369
- }
370
- }
518
+ // Singleton instance
519
+ const M1 = new HQXUltraScalpingStrategy();
371
520
 
372
- module.exports = { S1, M1, VC, OS, SS };
521
+ module.exports = { M1, HQXUltraScalpingStrategy, OrderSide, SignalStrength };