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.
- package/package.json +2 -6
- package/src/lib/data.js +204 -473
- package/src/lib/m/s1.js +450 -301
- package/dist/lib/api.js +0 -1
- package/dist/lib/api.jsc +0 -0
- package/dist/lib/api2.js +0 -1
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.js +0 -1
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.js +0 -1
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.js +0 -1
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.js +0 -1
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.js +0 -1
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.js +0 -1
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.js +0 -1
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.js +0 -1
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.js +0 -1
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.js +0 -1
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.js +0 -1
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.js +0 -1
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.js +0 -1
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.js +0 -1
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.js +0 -1
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.js +0 -1
- package/dist/lib/o/util2.jsc +0 -0
- package/src/lib/api.js +0 -198
- package/src/lib/api2.js +0 -353
- package/src/lib/core.js +0 -539
- package/src/lib/core2.js +0 -341
- package/src/lib/data2.js +0 -492
- package/src/lib/decoder.js +0 -599
- package/src/lib/m/s2.js +0 -34
- package/src/lib/n/r1.js +0 -454
- package/src/lib/n/r2.js +0 -514
- package/src/lib/n/r3.js +0 -631
- package/src/lib/n/r4.js +0 -401
- package/src/lib/n/r5.js +0 -335
- package/src/lib/n/r6.js +0 -425
- package/src/lib/n/r7.js +0 -530
- package/src/lib/o/l1.js +0 -44
- package/src/lib/o/l2.js +0 -427
- package/src/lib/python-bridge.js +0 -206
package/src/lib/m/s1.js
CHANGED
|
@@ -1,372 +1,521 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
if (!
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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 (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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 =
|
|
233
|
-
takeProfit =
|
|
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 =
|
|
236
|
-
takeProfit =
|
|
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
|
-
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
433
|
+
strategy: 'HQX_ULTRA_SCALPING',
|
|
434
|
+
strength,
|
|
435
|
+
edge,
|
|
436
|
+
confidence,
|
|
437
|
+
entry: currentPrice,
|
|
438
|
+
entryPrice: currentPrice,
|
|
256
439
|
stopLoss,
|
|
257
440
|
takeProfit,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
*
|
|
460
|
+
* Check if should exit by Z-Score
|
|
274
461
|
*/
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
*
|
|
470
|
+
* Get current model values
|
|
281
471
|
*/
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
this.
|
|
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.
|
|
292
|
-
this.
|
|
293
|
-
if (pnl > 0) {
|
|
294
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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.
|
|
310
|
-
this.
|
|
311
|
-
this.
|
|
312
|
-
this.
|
|
313
|
-
this.
|
|
314
|
-
this.
|
|
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
|
-
|
|
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 = {
|
|
521
|
+
module.exports = { M1, HQXUltraScalpingStrategy, OrderSide, SignalStrength };
|