hedgequantx 2.7.15 → 2.7.17
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 +1 -1
- package/src/app.js +10 -10
- package/src/lib/data.js +245 -471
- package/src/lib/m/s1-models.js +173 -0
- package/src/lib/m/s1.js +354 -735
- package/src/menus/dashboard.js +8 -5
- 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,804 +1,423 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* =============================================================================
|
|
3
|
-
* HQX
|
|
3
|
+
* HQX ULTRA SCALPING STRATEGY
|
|
4
4
|
* =============================================================================
|
|
5
|
-
*
|
|
5
|
+
* 6 Mathematical Models with 4-Layer Trailing Stop System
|
|
6
6
|
*
|
|
7
|
-
* BACKTEST RESULTS (
|
|
8
|
-
* - Net P&L:
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
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
13
|
*
|
|
14
|
-
* MODELS
|
|
15
|
-
* 1.
|
|
16
|
-
* 2.
|
|
17
|
-
* 3.
|
|
18
|
-
* 4.
|
|
19
|
-
* 5.
|
|
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
|
|
20
21
|
*
|
|
21
|
-
* KEY PARAMETERS
|
|
22
|
-
* - Stop:
|
|
23
|
-
* - Target:
|
|
24
|
-
* -
|
|
25
|
-
* -
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* - Order flow confirmation: REQUIRED
|
|
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
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
31
33
|
const EventEmitter = require('events');
|
|
32
34
|
const { v4: uuidv4 } = require('uuid');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const {
|
|
36
|
+
computeZScore,
|
|
37
|
+
computeVPIN,
|
|
38
|
+
computeKyleLambda,
|
|
39
|
+
applyKalmanFilter,
|
|
40
|
+
calculateATR,
|
|
41
|
+
detectVolatilityRegime,
|
|
42
|
+
computeOrderFlowImbalance,
|
|
43
|
+
} = require('./s1-models');
|
|
36
44
|
|
|
37
45
|
// =============================================================================
|
|
38
|
-
//
|
|
46
|
+
// CONSTANTS
|
|
39
47
|
// =============================================================================
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.d = new Map();
|
|
44
|
-
this.psv = new Map();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
gk(c) {
|
|
48
|
-
return `${c}_${new Date().toISOString().split('T')[0]}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
init(c) {
|
|
52
|
-
const k = this.gk(c);
|
|
53
|
-
if (!this.d.has(k)) {
|
|
54
|
-
this.d.set(k, {
|
|
55
|
-
v: 0, cv: 0, ctpv: 0,
|
|
56
|
-
ub1: 0, ub2: 0, ub3: 0,
|
|
57
|
-
lb1: 0, lb2: 0, lb3: 0,
|
|
58
|
-
sd: 0, tc: 0,
|
|
59
|
-
hod: -Infinity, lod: Infinity,
|
|
60
|
-
lu: Date.now()
|
|
61
|
-
});
|
|
62
|
-
this.psv.set(k, 0);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
pt(c, h, l, cl, vol, ts = Date.now()) {
|
|
67
|
-
const k = this.gk(c);
|
|
68
|
-
let d = this.d.get(k);
|
|
69
|
-
if (!d) { this.init(c); d = this.d.get(k); }
|
|
70
|
-
|
|
71
|
-
const tp = (h + l + cl) / 3;
|
|
72
|
-
d.cv += vol;
|
|
73
|
-
d.ctpv += tp * vol;
|
|
74
|
-
if (d.cv > 0) d.v = d.ctpv / d.cv;
|
|
75
|
-
|
|
76
|
-
const p = this.psv.get(k) || 0;
|
|
77
|
-
this.psv.set(k, p + tp * tp * vol);
|
|
78
|
-
this.cs(k);
|
|
79
|
-
|
|
80
|
-
if (h > d.hod) d.hod = h;
|
|
81
|
-
if (l < d.lod) d.lod = l;
|
|
82
|
-
d.tc++;
|
|
83
|
-
d.lu = ts;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
cs(k) {
|
|
87
|
-
const d = this.d.get(k);
|
|
88
|
-
if (!d || d.cv === 0) return;
|
|
89
|
-
|
|
90
|
-
const p = this.psv.get(k) || 0;
|
|
91
|
-
const ms = p / d.cv;
|
|
92
|
-
const sm = d.v * d.v;
|
|
93
|
-
const va = ms - sm;
|
|
94
|
-
d.sd = Math.sqrt(Math.max(0, va));
|
|
95
|
-
|
|
96
|
-
d.ub1 = d.v + d.sd;
|
|
97
|
-
d.ub2 = d.v + 2 * d.sd;
|
|
98
|
-
d.ub3 = d.v + 3 * d.sd;
|
|
99
|
-
d.lb1 = d.v - d.sd;
|
|
100
|
-
d.lb2 = d.v - 2 * d.sd;
|
|
101
|
-
d.lb3 = d.v - 3 * d.sd;
|
|
102
|
-
}
|
|
49
|
+
const OrderSide = { BID: 'BID', ASK: 'ASK' };
|
|
50
|
+
const SignalStrength = { WEAK: 'WEAK', MODERATE: 'MODERATE', STRONG: 'STRONG', VERY_STRONG: 'VERY_STRONG' };
|
|
103
51
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
if (ad < 1) bl = 0;
|
|
121
|
-
else if (ad < 2) bl = 1;
|
|
122
|
-
else if (ad < 3) bl = 2;
|
|
123
|
-
else bl = 3;
|
|
124
|
-
|
|
125
|
-
let mrp;
|
|
126
|
-
if (ad >= 3) mrp = 0.99;
|
|
127
|
-
else if (ad >= 2) mrp = 0.95;
|
|
128
|
-
else if (ad >= 1.5) mrp = 0.85;
|
|
129
|
-
else if (ad >= 1) mrp = 0.68;
|
|
130
|
-
else mrp = 0.50;
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
currentPrice: cp,
|
|
134
|
-
vwap: d.v,
|
|
135
|
-
deviation: dv,
|
|
136
|
-
deviationTicks: dt,
|
|
137
|
-
position: pos,
|
|
138
|
-
bandLevel: bl,
|
|
139
|
-
target: d.v,
|
|
140
|
-
probability: mrp,
|
|
141
|
-
isFavorableForMeanReversion: ad >= 1.5
|
|
142
|
-
};
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// HELPER: Extract base symbol from contractId
|
|
54
|
+
// =============================================================================
|
|
55
|
+
function extractBaseSymbol(contractId) {
|
|
56
|
+
// CON.F.US.ENQ.H25 -> NQ, CON.F.US.EP.H25 -> ES
|
|
57
|
+
const mapping = {
|
|
58
|
+
'ENQ': 'NQ', 'EP': 'ES', 'EMD': 'EMD', 'RTY': 'RTY',
|
|
59
|
+
'MNQ': 'MNQ', 'MES': 'MES', 'M2K': 'M2K', 'MYM': 'MYM',
|
|
60
|
+
'NKD': 'NKD', 'GC': 'GC', 'SI': 'SI', 'CL': 'CL', 'YM': 'YM'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (!contractId) return 'UNKNOWN';
|
|
64
|
+
const parts = contractId.split('.');
|
|
65
|
+
if (parts.length >= 4) {
|
|
66
|
+
const symbol = parts[3];
|
|
67
|
+
return mapping[symbol] || symbol;
|
|
143
68
|
}
|
|
144
|
-
|
|
145
|
-
get(c) { return this.d.get(this.gk(c)) || null; }
|
|
146
|
-
reset(c) { const k = this.gk(c); this.d.delete(k); this.psv.delete(k); }
|
|
69
|
+
return contractId;
|
|
147
70
|
}
|
|
148
71
|
|
|
149
72
|
// =============================================================================
|
|
150
|
-
//
|
|
73
|
+
// HQX ULTRA SCALPING STRATEGY CLASS
|
|
151
74
|
// =============================================================================
|
|
152
75
|
|
|
153
|
-
class
|
|
154
|
-
constructor(
|
|
76
|
+
class HQXUltraScalpingStrategy extends EventEmitter {
|
|
77
|
+
constructor() {
|
|
155
78
|
super();
|
|
156
|
-
|
|
157
|
-
this.
|
|
158
|
-
this.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.
|
|
162
|
-
this.
|
|
163
|
-
this.
|
|
164
|
-
this.
|
|
165
|
-
this.
|
|
166
|
-
this.
|
|
167
|
-
this.
|
|
168
|
-
this.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
this.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return this.processBar(contractId, b);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
onTick(t) { return this.processTick(t); }
|
|
197
|
-
|
|
198
|
-
processBar(c, b) {
|
|
199
|
-
let bs = this.bh.get(c);
|
|
200
|
-
if (!bs) { bs = []; this.bh.set(c, bs); }
|
|
201
|
-
|
|
202
|
-
bs.push(b);
|
|
203
|
-
if (bs.length > 500) bs.shift();
|
|
204
|
-
|
|
205
|
-
if (bs.length < 50) return null;
|
|
206
|
-
|
|
207
|
-
// Check session date
|
|
208
|
-
const sd = new Date(b.timestamp).toISOString().split('T')[0];
|
|
209
|
-
if (this.lsd !== sd) this.lsd = sd;
|
|
210
|
-
|
|
211
|
-
// Update VWAP
|
|
212
|
-
this.vc.pt(c, b.high, b.low, b.close, b.volume, b.timestamp);
|
|
213
|
-
|
|
214
|
-
// Get VWAP analysis
|
|
215
|
-
const va = this.vc.an(c, b.close, this.ts);
|
|
216
|
-
if (!va) return null;
|
|
217
|
-
|
|
218
|
-
// Calculate scores
|
|
219
|
-
const vs = this.sv(va, b.close);
|
|
220
|
-
const of = this.aof(bs);
|
|
221
|
-
const os = this.sof(of);
|
|
222
|
-
const dd = this.adb(bs);
|
|
223
|
-
const ds = this.sdd(dd);
|
|
224
|
-
const md = this.am(bs);
|
|
225
|
-
const ms = this.sm(md);
|
|
226
|
-
const pd = this.avp(bs, c, b.close);
|
|
227
|
-
const ps = this.svp(pd, b.close);
|
|
228
|
-
|
|
229
|
-
// Determine direction
|
|
230
|
-
const { dir, st, ofc } = this.dd(va, of, dd, pd, b.close);
|
|
231
|
-
|
|
232
|
-
if (dir === 'none') return null;
|
|
233
|
-
if (st !== 'vr') return null; // VWAP reversion only
|
|
234
|
-
if (!ofc) return null; // Require order flow confirmation
|
|
235
|
-
|
|
236
|
-
// Calculate confidence
|
|
237
|
-
const bc = vs * 0.25 + os * 0.30 + ds * 0.20 + ms * 0.10 + ps * 0.15;
|
|
238
|
-
const cf = Math.min(1.0, bc + 0.05);
|
|
239
|
-
|
|
240
|
-
// Get adaptive params
|
|
241
|
-
const pm = this.gap(bs);
|
|
242
|
-
|
|
243
|
-
if (cf < pm.ct) return null;
|
|
244
|
-
|
|
245
|
-
// Cooldown check
|
|
246
|
-
if (Date.now() - this.lst < this.cd) return null;
|
|
247
|
-
|
|
248
|
-
// Entry price
|
|
249
|
-
const ep = b.close;
|
|
250
|
-
|
|
251
|
-
// Stops and targets
|
|
252
|
-
const spt = pm.spt;
|
|
253
|
-
const tgt = pm.tgt;
|
|
254
|
-
|
|
255
|
-
let sl, tp;
|
|
256
|
-
if (dir === 'long') {
|
|
257
|
-
sl = ep - spt * this.ts;
|
|
258
|
-
tp = ep + tgt * this.ts;
|
|
259
|
-
// Adjust to VWAP if close
|
|
260
|
-
const vt = va.vwap;
|
|
261
|
-
if (vt > ep && vt < tp) {
|
|
262
|
-
const vr = (vt - ep) / this.ts;
|
|
263
|
-
if (vr >= spt * 1.5) tp = vt;
|
|
264
|
-
}
|
|
265
|
-
} else {
|
|
266
|
-
sl = ep + spt * this.ts;
|
|
267
|
-
tp = ep - tgt * this.ts;
|
|
268
|
-
const vt = va.vwap;
|
|
269
|
-
if (vt < ep && vt > tp) {
|
|
270
|
-
const vr = (ep - vt) / this.ts;
|
|
271
|
-
if (vr >= spt * 1.5) tp = vt;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const rt = Math.abs(ep - sl) / this.ts;
|
|
276
|
-
const rwt = Math.abs(tp - ep) / this.ts;
|
|
277
|
-
|
|
278
|
-
if (rwt / rt < this.mrr) return null;
|
|
279
|
-
|
|
280
|
-
// Partial targets
|
|
281
|
-
let p1, p2;
|
|
282
|
-
if (dir === 'long') {
|
|
283
|
-
p1 = ep + pm.p1t * this.ts;
|
|
284
|
-
p2 = ep + pm.p2t * this.ts;
|
|
285
|
-
} else {
|
|
286
|
-
p1 = ep - pm.p1t * this.ts;
|
|
287
|
-
p2 = ep - pm.p2t * this.ts;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Signal strength
|
|
291
|
-
let str = SS.M;
|
|
292
|
-
if (cf >= 0.75) str = SS.S;
|
|
293
|
-
else if (cf >= 0.85) str = SS.VS;
|
|
294
|
-
else if (cf < 0.55) str = SS.W;
|
|
295
|
-
|
|
296
|
-
// Edge calculation
|
|
297
|
-
const wp = 0.5 + (cf - 0.5) * 0.3;
|
|
298
|
-
const aw = Math.abs(tp - ep);
|
|
299
|
-
const al = Math.abs(ep - sl);
|
|
300
|
-
const eg = wp * aw - (1 - wp) * al;
|
|
301
|
-
|
|
302
|
-
this.lst = Date.now();
|
|
303
|
-
this.st.s++;
|
|
304
|
-
|
|
305
|
-
const sig = {
|
|
306
|
-
id: uuidv4(),
|
|
307
|
-
timestamp: Date.now(),
|
|
308
|
-
symbol: c.split('.')[0] || c,
|
|
309
|
-
contractId: c,
|
|
310
|
-
side: dir === 'long' ? OS.B : OS.A,
|
|
311
|
-
direction: dir,
|
|
312
|
-
strategy: 'ULTRA_SCALPING_VWAP_REVERSION',
|
|
313
|
-
strength: str,
|
|
314
|
-
edge: eg,
|
|
315
|
-
confidence: cf,
|
|
316
|
-
entry: ep,
|
|
317
|
-
entryPrice: ep,
|
|
318
|
-
stopLoss: sl,
|
|
319
|
-
takeProfit: tp,
|
|
320
|
-
riskReward: rwt / rt,
|
|
321
|
-
stopTicks: spt,
|
|
322
|
-
targetTicks: tgt,
|
|
323
|
-
trailTriggerTicks: pm.ttt,
|
|
324
|
-
trailDistanceTicks: pm.tdt,
|
|
325
|
-
partial1Price: p1,
|
|
326
|
-
partial2Price: p2,
|
|
327
|
-
vwapScore: vs,
|
|
328
|
-
orderFlowScore: os,
|
|
329
|
-
domScore: ds,
|
|
330
|
-
microstructureScore: ms,
|
|
331
|
-
volumeProfileScore: ps,
|
|
332
|
-
orderFlowConfirmed: ofc,
|
|
333
|
-
zScore: va.deviation,
|
|
334
|
-
vwap: va.vwap,
|
|
335
|
-
regime: pm.rg,
|
|
336
|
-
expires: Date.now() + 60000
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
this.emit('signal', sig);
|
|
340
|
-
return sig;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
recordTradeResult(p) {
|
|
344
|
-
this.rt.push({ netPnl: p, timestamp: Date.now() });
|
|
345
|
-
if (this.rt.length > 100) this.rt.shift();
|
|
346
|
-
if (p > 0) { this.ws++; this.ls = 0; this.st.w++; }
|
|
347
|
-
else { this.ls++; this.ws = 0; this.st.l++; }
|
|
348
|
-
this.st.p += p;
|
|
349
|
-
this.st.t++;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Score VWAP
|
|
353
|
-
sv(v, cp) {
|
|
354
|
-
const z = Math.abs(v.deviation);
|
|
355
|
-
if (z < 0.5) return 0.3;
|
|
356
|
-
else if (z < 1.0) return 0.6;
|
|
357
|
-
else if (z < 2.0) return 0.9;
|
|
358
|
-
else if (z < 2.5) return 0.7;
|
|
359
|
-
else return 0.4;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Score Order Flow
|
|
363
|
-
sof(o) {
|
|
364
|
-
let s = 0.5;
|
|
365
|
-
s += o.ir * 0.3;
|
|
366
|
-
if (o.dd) s += 0.2;
|
|
367
|
-
if (o.ad) s += 0.15;
|
|
368
|
-
return Math.min(1.0, s);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Score DOM
|
|
372
|
-
sdd(d) {
|
|
373
|
-
let s = 0.5;
|
|
374
|
-
s += Math.abs(d.pi) * 0.25;
|
|
375
|
-
if (d.sb || d.sa) s += 0.15;
|
|
376
|
-
if (d.al !== null) s += 0.1;
|
|
377
|
-
return Math.min(1.0, s);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Score Microstructure
|
|
381
|
-
sm(m) {
|
|
382
|
-
const ns = 1.0 - m.nr;
|
|
383
|
-
const ls = 0.0001 < m.kl && m.kl < 0.001 ? 0.7 : 0.5;
|
|
384
|
-
return ns * 0.6 + ls * 0.4;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Score Volume Profile
|
|
388
|
-
svp(p, cp) {
|
|
389
|
-
let s = 0.5;
|
|
390
|
-
const pd = Math.abs(cp - p.poc) / this.ts;
|
|
391
|
-
if (pd < 10) s += 0.2;
|
|
392
|
-
if (p.val <= cp && cp <= p.vah) s += 0.15;
|
|
393
|
-
for (const h of p.hvn) {
|
|
394
|
-
if (Math.abs(cp - h) < 5 * this.ts) { s += 0.15; break; }
|
|
395
|
-
}
|
|
396
|
-
return Math.min(1.0, s);
|
|
79
|
+
|
|
80
|
+
this.tickSize = 0.25;
|
|
81
|
+
this.tickValue = 5.0;
|
|
82
|
+
|
|
83
|
+
// === Model Parameters (from V4 backtest) ===
|
|
84
|
+
this.zscoreEntryThreshold = 1.5; // Adaptive per regime
|
|
85
|
+
this.zscoreExitThreshold = 0.5;
|
|
86
|
+
this.vpinWindow = 50;
|
|
87
|
+
this.vpinToxicThreshold = 0.7;
|
|
88
|
+
this.kalmanProcessNoise = 0.01;
|
|
89
|
+
this.kalmanMeasurementNoise = 0.1;
|
|
90
|
+
this.volatilityLookback = 100;
|
|
91
|
+
this.ofiLookback = 20;
|
|
92
|
+
|
|
93
|
+
// === Trade Parameters (from V4 backtest) ===
|
|
94
|
+
this.baseStopTicks = 8; // $40
|
|
95
|
+
this.baseTargetTicks = 16; // $80
|
|
96
|
+
this.breakevenTicks = 4; // Move to BE at +4 ticks
|
|
97
|
+
this.profitLockPct = 0.5; // Lock 50% of profit
|
|
98
|
+
|
|
99
|
+
// === State Storage ===
|
|
100
|
+
this.barHistory = new Map();
|
|
101
|
+
this.kalmanStates = new Map();
|
|
102
|
+
this.priceBuffer = new Map();
|
|
103
|
+
this.volumeBuffer = new Map();
|
|
104
|
+
this.tradesBuffer = new Map();
|
|
105
|
+
this.atrHistory = new Map();
|
|
106
|
+
|
|
107
|
+
// === Tick aggregation ===
|
|
108
|
+
this.tickBuffer = new Map();
|
|
109
|
+
this.lastBarTime = new Map();
|
|
110
|
+
this.barIntervalMs = 5000; // 5-second bars
|
|
111
|
+
|
|
112
|
+
// === Performance Tracking ===
|
|
113
|
+
this.recentTrades = [];
|
|
114
|
+
this.winStreak = 0;
|
|
115
|
+
this.lossStreak = 0;
|
|
397
116
|
}
|
|
398
117
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
bv = cb.volume * 0.5;
|
|
414
|
-
sv = cb.volume * 0.5;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const d = bv - sv;
|
|
418
|
-
const cd = cb.delta || d;
|
|
419
|
-
|
|
420
|
-
// Detect divergence
|
|
421
|
-
let dv = false;
|
|
422
|
-
if (bs.length >= 5) {
|
|
423
|
-
const rb = bs.slice(-5);
|
|
424
|
-
const pc = rb[4].close - rb[0].close;
|
|
425
|
-
const ds = rb.reduce((s, b) => s + (b.delta || 0), 0);
|
|
426
|
-
if ((pc > 0 && ds < -cb.volume * 0.5) || (pc < 0 && ds > cb.volume * 0.5)) dv = true;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Detect absorption
|
|
430
|
-
const lb = Math.min(20, bs.length);
|
|
431
|
-
const rbs = bs.slice(-lb);
|
|
432
|
-
const ar = rbs.reduce((s, b) => s + (b.high - b.low), 0) / lb;
|
|
433
|
-
const av = rbs.reduce((s, b) => s + b.volume, 0) / lb;
|
|
434
|
-
const ab = cb.volume > av * 1.5 && br < ar * 0.6;
|
|
435
|
-
|
|
436
|
-
const tv = bv + sv;
|
|
437
|
-
const im = tv > 0 ? Math.abs(bv - sv) / tv : 0;
|
|
438
|
-
|
|
439
|
-
let ag;
|
|
440
|
-
if (bv > sv * 1.3) ag = 'buy';
|
|
441
|
-
else if (sv > bv * 1.3) ag = 'sell';
|
|
442
|
-
else ag = 'neutral';
|
|
443
|
-
|
|
444
|
-
return { d, cd, dd: dv, ad: ab, ir: im, as: ag };
|
|
118
|
+
/**
|
|
119
|
+
* Initialize strategy for a contract
|
|
120
|
+
*/
|
|
121
|
+
initialize(contractId, tickSize = 0.25, tickValue = 5.0) {
|
|
122
|
+
this.tickSize = tickSize;
|
|
123
|
+
this.tickValue = tickValue;
|
|
124
|
+
this.barHistory.set(contractId, []);
|
|
125
|
+
this.priceBuffer.set(contractId, []);
|
|
126
|
+
this.volumeBuffer.set(contractId, []);
|
|
127
|
+
this.tradesBuffer.set(contractId, []);
|
|
128
|
+
this.atrHistory.set(contractId, []);
|
|
129
|
+
this.tickBuffer.set(contractId, []);
|
|
130
|
+
this.lastBarTime.set(contractId, 0);
|
|
131
|
+
this.kalmanStates.set(contractId, { estimate: 0, errorCovariance: 1.0 });
|
|
445
132
|
}
|
|
446
133
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
for (const b of rbs) {
|
|
456
|
-
const br = b.high - b.low;
|
|
457
|
-
if (br > 0) {
|
|
458
|
-
const cp = (b.close - b.low) / br;
|
|
459
|
-
bss.push(cp * b.volume);
|
|
460
|
-
ass.push((1 - cp) * b.volume);
|
|
461
|
-
}
|
|
134
|
+
/**
|
|
135
|
+
* Process a tick - aggregates into bars then runs strategy
|
|
136
|
+
*/
|
|
137
|
+
processTick(tick) {
|
|
138
|
+
const contractId = tick.contractId;
|
|
139
|
+
|
|
140
|
+
if (!this.barHistory.has(contractId)) {
|
|
141
|
+
this.initialize(contractId);
|
|
462
142
|
}
|
|
463
143
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const pi = bp - ap;
|
|
468
|
-
|
|
469
|
-
const lf = rbs.slice(-5);
|
|
470
|
-
let hc = 0, lc = 0;
|
|
471
|
-
for (const b of lf) {
|
|
472
|
-
const br = b.high - b.low;
|
|
473
|
-
if (br > 0) {
|
|
474
|
-
const cp = (b.close - b.low) / br;
|
|
475
|
-
if (cp > 0.7) hc++;
|
|
476
|
-
if (cp < 0.3) lc++;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
144
|
+
// Add tick to buffer
|
|
145
|
+
let ticks = this.tickBuffer.get(contractId);
|
|
146
|
+
ticks.push(tick);
|
|
479
147
|
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
148
|
+
// Check if we should form a new bar
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const lastBar = this.lastBarTime.get(contractId);
|
|
151
|
+
|
|
152
|
+
if (now - lastBar >= this.barIntervalMs && ticks.length > 0) {
|
|
153
|
+
const bar = this._aggregateTicksToBar(ticks, now);
|
|
154
|
+
this.tickBuffer.set(contractId, []);
|
|
155
|
+
this.lastBarTime.set(contractId, now);
|
|
156
|
+
|
|
157
|
+
if (bar) {
|
|
158
|
+
const signal = this.processBar(contractId, bar);
|
|
159
|
+
if (signal) {
|
|
160
|
+
this.emit('signal', signal);
|
|
161
|
+
return signal;
|
|
162
|
+
}
|
|
488
163
|
}
|
|
489
164
|
}
|
|
490
|
-
|
|
491
|
-
return { bp, ap, pi, sb: hc >= 3, sa: lc >= 3, al };
|
|
165
|
+
return null;
|
|
492
166
|
}
|
|
493
167
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
if (pc.length > 5) {
|
|
510
|
-
const mp = pc.reduce((a, b) => a + b, 0) / pc.length;
|
|
511
|
-
const mv = vs.reduce((a, b) => a + b, 0) / vs.length;
|
|
512
|
-
let cv = 0, vv = 0;
|
|
513
|
-
for (let i = 0; i < pc.length; i++) {
|
|
514
|
-
cv += (pc[i] - mp) * (vs[i] - mv);
|
|
515
|
-
vv += Math.pow(vs[i] - mv, 2);
|
|
516
|
-
}
|
|
517
|
-
cv /= pc.length;
|
|
518
|
-
vv /= pc.length;
|
|
519
|
-
kl = vv > 0 ? Math.abs(cv / vv) : 0;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Roll's Spread
|
|
523
|
-
let rs = 0;
|
|
524
|
-
if (pc.length > 2) {
|
|
525
|
-
const c1 = pc.slice(1), c0 = pc.slice(0, -1);
|
|
526
|
-
const m1 = c1.reduce((a, b) => a + b, 0) / c1.length;
|
|
527
|
-
const m0 = c0.reduce((a, b) => a + b, 0) / c0.length;
|
|
528
|
-
let ac = 0;
|
|
529
|
-
for (let i = 0; i < c1.length; i++) ac += (c1[i] - m1) * (c0[i] - m0);
|
|
530
|
-
ac /= c1.length;
|
|
531
|
-
rs = Math.sqrt(Math.max(0, -ac)) * 2;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const es = rbs.reduce((s, b) => s + (b.high - b.low), 0) / lb;
|
|
535
|
-
|
|
536
|
-
// Realized volatility
|
|
537
|
-
const rt = [];
|
|
538
|
-
for (let i = 1; i < rbs.length; i++) {
|
|
539
|
-
if (rbs[i - 1].close > 0) rt.push(Math.log(rbs[i].close / rbs[i - 1].close));
|
|
540
|
-
}
|
|
541
|
-
let rv = 0;
|
|
542
|
-
if (rt.length > 0) {
|
|
543
|
-
const mr = rt.reduce((a, b) => a + b, 0) / rt.length;
|
|
544
|
-
const va = rt.reduce((s, r) => s + Math.pow(r - mr, 2), 0) / rt.length;
|
|
545
|
-
rv = Math.sqrt(va) * Math.sqrt(252 * 390);
|
|
168
|
+
/**
|
|
169
|
+
* Aggregate ticks into a bar
|
|
170
|
+
*/
|
|
171
|
+
_aggregateTicksToBar(ticks, timestamp) {
|
|
172
|
+
if (ticks.length === 0) return null;
|
|
173
|
+
|
|
174
|
+
const prices = ticks.map(t => t.price).filter(p => p != null);
|
|
175
|
+
if (prices.length === 0) return null;
|
|
176
|
+
|
|
177
|
+
let buyVol = 0, sellVol = 0;
|
|
178
|
+
for (let i = 1; i < ticks.length; i++) {
|
|
179
|
+
const vol = ticks[i].volume || 1;
|
|
180
|
+
if (ticks[i].price > ticks[i-1].price) buyVol += vol;
|
|
181
|
+
else if (ticks[i].price < ticks[i-1].price) sellVol += vol;
|
|
182
|
+
else { buyVol += vol / 2; sellVol += vol / 2; }
|
|
546
183
|
}
|
|
547
184
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
185
|
+
return {
|
|
186
|
+
timestamp,
|
|
187
|
+
open: prices[0],
|
|
188
|
+
high: Math.max(...prices),
|
|
189
|
+
low: Math.min(...prices),
|
|
190
|
+
close: prices[prices.length - 1],
|
|
191
|
+
volume: ticks.reduce((sum, t) => sum + (t.volume || 1), 0),
|
|
192
|
+
delta: buyVol - sellVol,
|
|
193
|
+
tickCount: ticks.length
|
|
194
|
+
};
|
|
554
195
|
}
|
|
555
196
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
for (const b of rbs) {
|
|
565
|
-
const br = b.high - b.low;
|
|
566
|
-
const nl = Math.max(1, Math.round(br / this.ts));
|
|
567
|
-
const vpl = b.volume / nl;
|
|
568
|
-
let p = b.low;
|
|
569
|
-
while (p <= b.high) {
|
|
570
|
-
const r = Math.round(p / this.ts) * this.ts;
|
|
571
|
-
vap.set(r, (vap.get(r) || 0) + vpl);
|
|
572
|
-
p += this.ts;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
if (vap.size === 0) return { poc: cp, vah: cp, val: cp, hvn: [], lvn: [] };
|
|
577
|
-
|
|
578
|
-
// POC
|
|
579
|
-
let poc = cp, mv = 0;
|
|
580
|
-
for (const [p, v] of vap) {
|
|
581
|
-
if (v > mv) { mv = v; poc = p; }
|
|
197
|
+
/**
|
|
198
|
+
* Process a new bar and potentially generate signal
|
|
199
|
+
*/
|
|
200
|
+
processBar(contractId, bar) {
|
|
201
|
+
let bars = this.barHistory.get(contractId);
|
|
202
|
+
if (!bars) {
|
|
203
|
+
this.initialize(contractId);
|
|
204
|
+
bars = this.barHistory.get(contractId);
|
|
582
205
|
}
|
|
583
206
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
207
|
+
bars.push(bar);
|
|
208
|
+
if (bars.length > 500) bars.shift();
|
|
209
|
+
|
|
210
|
+
// Update price buffer
|
|
211
|
+
const prices = this.priceBuffer.get(contractId);
|
|
212
|
+
prices.push(bar.close);
|
|
213
|
+
if (prices.length > 200) prices.shift();
|
|
214
|
+
|
|
215
|
+
// Update volume buffer
|
|
216
|
+
const volumes = this.volumeBuffer.get(contractId);
|
|
217
|
+
const barRange = bar.high - bar.low;
|
|
218
|
+
let buyVol = bar.volume * 0.5;
|
|
219
|
+
let sellVol = bar.volume * 0.5;
|
|
220
|
+
if (barRange > 0) {
|
|
221
|
+
const closePosition = (bar.close - bar.low) / barRange;
|
|
222
|
+
buyVol = bar.volume * closePosition;
|
|
223
|
+
sellVol = bar.volume * (1 - closePosition);
|
|
597
224
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
225
|
+
volumes.push({ buy: buyVol, sell: sellVol });
|
|
226
|
+
if (volumes.length > 100) volumes.shift();
|
|
227
|
+
|
|
228
|
+
// Need minimum data
|
|
229
|
+
if (bars.length < 50) return null;
|
|
230
|
+
|
|
231
|
+
// === 6 MODELS ===
|
|
232
|
+
const zscore = computeZScore(prices);
|
|
233
|
+
const vpin = computeVPIN(volumes, this.vpinWindow);
|
|
234
|
+
const kyleLambda = computeKyleLambda(bars);
|
|
235
|
+
const kalmanEstimate = this._applyKalmanFilter(contractId, bar.close);
|
|
236
|
+
const { regime, params } = this._detectVolatilityRegime(contractId, bars);
|
|
237
|
+
const ofi = computeOrderFlowImbalance(bars, this.ofiLookback);
|
|
238
|
+
|
|
239
|
+
// === SIGNAL GENERATION ===
|
|
240
|
+
return this._generateSignal(contractId, bar.close, zscore, vpin, kyleLambda, kalmanEstimate, regime, params, ofi, bars);
|
|
609
241
|
}
|
|
610
242
|
|
|
611
|
-
//
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
if (o.as === 'buy' || o.dd) ofc = true;
|
|
620
|
-
} else if (v.deviation > 0.8) {
|
|
621
|
-
ss += 2;
|
|
622
|
-
st = 'vr';
|
|
623
|
-
if (o.as === 'sell' || o.dd) ofc = true;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// Order flow strength
|
|
627
|
-
if (o.as === 'buy' && o.ir > 0.35) ls += 1;
|
|
628
|
-
else if (o.as === 'sell' && o.ir > 0.35) ss += 1;
|
|
629
|
-
|
|
630
|
-
// Delta Divergence
|
|
631
|
-
if (o.dd) {
|
|
632
|
-
if (o.cd < 0) { ls += 2; st = 'dv'; ofc = true; }
|
|
633
|
-
else { ss += 2; st = 'dv'; ofc = true; }
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// DOM Pressure
|
|
637
|
-
if (d.pi > 0.35) ls += 1;
|
|
638
|
-
else if (d.pi < -0.35) ss += 1;
|
|
639
|
-
|
|
640
|
-
// Stacked Orders
|
|
641
|
-
if (d.sb) ls += 1;
|
|
642
|
-
if (d.sa) ss += 1;
|
|
643
|
-
|
|
644
|
-
// Volume Profile (POC attraction)
|
|
645
|
-
if (cp < p.poc - 5 * this.ts) ls += 1;
|
|
646
|
-
else if (cp > p.poc + 5 * this.ts) ss += 1;
|
|
647
|
-
|
|
648
|
-
// Decision
|
|
649
|
-
if (ls >= 2 && ls > ss) return { dir: 'long', st, ofc };
|
|
650
|
-
else if (ss >= 2 && ss > ls) return { dir: 'short', st, ofc };
|
|
651
|
-
else return { dir: 'none', st: 'none', ofc: false };
|
|
243
|
+
// ===========================================================================
|
|
244
|
+
// MODEL 4: KALMAN FILTER (uses shared state)
|
|
245
|
+
// ===========================================================================
|
|
246
|
+
_applyKalmanFilter(contractId, measurement) {
|
|
247
|
+
let state = this.kalmanStates.get(contractId);
|
|
248
|
+
const result = applyKalmanFilter(state, measurement, this.kalmanProcessNoise, this.kalmanMeasurementNoise);
|
|
249
|
+
this.kalmanStates.set(contractId, result.state);
|
|
250
|
+
return result.estimate;
|
|
652
251
|
}
|
|
653
252
|
|
|
654
|
-
//
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
const a = tv.reduce((a, b) => a + b, 0) / tv.length;
|
|
667
|
-
this.ah.push(a);
|
|
668
|
-
if (this.ah.length > 500) this.ah.shift();
|
|
669
|
-
|
|
670
|
-
return a;
|
|
253
|
+
// ===========================================================================
|
|
254
|
+
// MODEL 5: VOLATILITY REGIME (uses shared state)
|
|
255
|
+
// ===========================================================================
|
|
256
|
+
_detectVolatilityRegime(contractId, bars) {
|
|
257
|
+
const atr = calculateATR(bars);
|
|
258
|
+
let atrHist = this.atrHistory.get(contractId);
|
|
259
|
+
if (!atrHist) { atrHist = []; this.atrHistory.set(contractId, atrHist); }
|
|
260
|
+
atrHist.push(atr);
|
|
261
|
+
if (atrHist.length > 500) atrHist.shift();
|
|
262
|
+
return detectVolatilityRegime(atrHist, atr);
|
|
671
263
|
}
|
|
672
264
|
|
|
673
|
-
//
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (
|
|
680
|
-
|
|
681
|
-
let
|
|
682
|
-
if (
|
|
683
|
-
else if (
|
|
684
|
-
else
|
|
685
|
-
|
|
265
|
+
// ===========================================================================
|
|
266
|
+
// SIGNAL GENERATION
|
|
267
|
+
// ===========================================================================
|
|
268
|
+
_generateSignal(contractId, currentPrice, zscore, vpin, kyleLambda, kalmanEstimate, regime, volParams, ofi, bars) {
|
|
269
|
+
const absZscore = Math.abs(zscore);
|
|
270
|
+
if (absZscore < volParams.zscoreThreshold) return null;
|
|
271
|
+
if (vpin > this.vpinToxicThreshold) return null;
|
|
272
|
+
|
|
273
|
+
let direction;
|
|
274
|
+
if (zscore < -volParams.zscoreThreshold) direction = 'long';
|
|
275
|
+
else if (zscore > volParams.zscoreThreshold) direction = 'short';
|
|
276
|
+
else return null;
|
|
277
|
+
|
|
278
|
+
const ofiConfirms = (direction === 'long' && ofi > 0.1) || (direction === 'short' && ofi < -0.1);
|
|
279
|
+
const kalmanDiff = currentPrice - kalmanEstimate;
|
|
280
|
+
const kalmanConfirms = (direction === 'long' && kalmanDiff < 0) || (direction === 'short' && kalmanDiff > 0);
|
|
281
|
+
|
|
282
|
+
const scores = {
|
|
283
|
+
zscore: Math.min(1.0, absZscore / 4.0),
|
|
284
|
+
vpin: 1.0 - vpin,
|
|
285
|
+
kyleLambda: kyleLambda > 0.001 ? 0.5 : 0.8,
|
|
286
|
+
kalman: kalmanConfirms ? 0.8 : 0.4,
|
|
287
|
+
volatility: regime === 'normal' ? 0.8 : regime === 'low' ? 0.7 : 0.6,
|
|
288
|
+
ofi: ofiConfirms ? 0.9 : 0.5,
|
|
289
|
+
composite: 0
|
|
290
|
+
};
|
|
686
291
|
|
|
687
|
-
|
|
292
|
+
scores.composite = scores.zscore * 0.30 + scores.vpin * 0.15 + scores.kyleLambda * 0.10 +
|
|
293
|
+
scores.kalman * 0.15 + scores.volatility * 0.10 + scores.ofi * 0.20;
|
|
688
294
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
let spt = Math.round(bs6 * vm);
|
|
692
|
-
spt = Math.max(5, Math.min(8, spt));
|
|
295
|
+
const confidence = Math.min(1.0, scores.composite + volParams.confidenceBonus);
|
|
296
|
+
if (confidence < 0.55) return null;
|
|
693
297
|
|
|
694
|
-
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
298
|
+
const stopTicks = Math.round(this.baseStopTicks * volParams.stopMultiplier);
|
|
299
|
+
const targetTicks = Math.round(this.baseTargetTicks * volParams.targetMultiplier);
|
|
300
|
+
const actualStopTicks = Math.max(6, Math.min(12, stopTicks));
|
|
301
|
+
const actualTargetTicks = Math.max(actualStopTicks * 1.5, Math.min(24, targetTicks));
|
|
698
302
|
|
|
699
|
-
|
|
303
|
+
let stopLoss, takeProfit, beBreakeven, profitLockLevel;
|
|
304
|
+
if (direction === 'long') {
|
|
305
|
+
stopLoss = currentPrice - actualStopTicks * this.tickSize;
|
|
306
|
+
takeProfit = currentPrice + actualTargetTicks * this.tickSize;
|
|
307
|
+
beBreakeven = currentPrice + this.breakevenTicks * this.tickSize;
|
|
308
|
+
profitLockLevel = currentPrice + (actualTargetTicks * this.profitLockPct) * this.tickSize;
|
|
309
|
+
} else {
|
|
310
|
+
stopLoss = currentPrice + actualStopTicks * this.tickSize;
|
|
311
|
+
takeProfit = currentPrice - actualTargetTicks * this.tickSize;
|
|
312
|
+
beBreakeven = currentPrice - this.breakevenTicks * this.tickSize;
|
|
313
|
+
profitLockLevel = currentPrice - (actualTargetTicks * this.profitLockPct) * this.tickSize;
|
|
314
|
+
}
|
|
700
315
|
|
|
701
|
-
|
|
702
|
-
const
|
|
703
|
-
const
|
|
316
|
+
const riskReward = actualTargetTicks / actualStopTicks;
|
|
317
|
+
const trailTriggerTicks = Math.round(actualTargetTicks * 0.5);
|
|
318
|
+
const trailDistanceTicks = Math.round(actualStopTicks * 0.4);
|
|
704
319
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
ct = Math.min(0.65, ct * pm);
|
|
320
|
+
let strength = SignalStrength.MODERATE;
|
|
321
|
+
if (confidence >= 0.85) strength = SignalStrength.VERY_STRONG;
|
|
322
|
+
else if (confidence >= 0.75) strength = SignalStrength.STRONG;
|
|
323
|
+
else if (confidence < 0.60) strength = SignalStrength.WEAK;
|
|
710
324
|
|
|
711
|
-
|
|
712
|
-
const
|
|
713
|
-
const p2t = Math.round(tgt * 0.75);
|
|
325
|
+
const winProb = 0.5 + (confidence - 0.5) * 0.4;
|
|
326
|
+
const edge = winProb * Math.abs(takeProfit - currentPrice) - (1 - winProb) * Math.abs(currentPrice - stopLoss);
|
|
714
327
|
|
|
715
|
-
return {
|
|
328
|
+
return {
|
|
329
|
+
id: uuidv4(),
|
|
330
|
+
timestamp: Date.now(),
|
|
331
|
+
symbol: extractBaseSymbol(contractId),
|
|
332
|
+
contractId,
|
|
333
|
+
side: direction === 'long' ? OrderSide.BID : OrderSide.ASK,
|
|
334
|
+
direction,
|
|
335
|
+
strategy: 'HQX_ULTRA_SCALPING',
|
|
336
|
+
strength,
|
|
337
|
+
edge,
|
|
338
|
+
confidence,
|
|
339
|
+
entry: currentPrice,
|
|
340
|
+
entryPrice: currentPrice,
|
|
341
|
+
stopLoss,
|
|
342
|
+
takeProfit,
|
|
343
|
+
riskReward,
|
|
344
|
+
stopTicks: actualStopTicks,
|
|
345
|
+
targetTicks: actualTargetTicks,
|
|
346
|
+
trailTriggerTicks,
|
|
347
|
+
trailDistanceTicks,
|
|
348
|
+
beBreakeven,
|
|
349
|
+
profitLockLevel,
|
|
350
|
+
zScore: zscore,
|
|
351
|
+
zScoreExit: this.zscoreExitThreshold,
|
|
352
|
+
vpinValue: vpin,
|
|
353
|
+
kyleLambda,
|
|
354
|
+
kalmanEstimate,
|
|
355
|
+
volatilityRegime: regime,
|
|
356
|
+
ofiValue: ofi,
|
|
357
|
+
models: scores
|
|
358
|
+
};
|
|
716
359
|
}
|
|
717
360
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
let m = 1.0;
|
|
727
|
-
|
|
728
|
-
if (this.ls >= 2) {
|
|
729
|
-
m += 0.05 * this.ls;
|
|
730
|
-
m = Math.min(m, 1.3);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
if (r.length >= 5 && wr < 0.40) m *= 1.1;
|
|
734
|
-
if (this.ws >= 4 && wr > 0.50) m *= 0.95;
|
|
735
|
-
|
|
736
|
-
return Math.max(0.9, Math.min(1.3, m));
|
|
361
|
+
/**
|
|
362
|
+
* Check if should exit by Z-Score
|
|
363
|
+
*/
|
|
364
|
+
shouldExitByZScore(contractId) {
|
|
365
|
+
const prices = this.priceBuffer.get(contractId);
|
|
366
|
+
if (!prices || prices.length < 50) return false;
|
|
367
|
+
const zscore = computeZScore(prices);
|
|
368
|
+
return Math.abs(zscore) < this.zscoreExitThreshold;
|
|
737
369
|
}
|
|
738
370
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
const va = this.vc.an(c, cp, this.ts);
|
|
748
|
-
const of = this.aof(bs);
|
|
749
|
-
const pm = this.gap(bs);
|
|
371
|
+
/**
|
|
372
|
+
* Get current model values
|
|
373
|
+
*/
|
|
374
|
+
getModelValues(contractId) {
|
|
375
|
+
const prices = this.priceBuffer.get(contractId);
|
|
376
|
+
const volumes = this.volumeBuffer.get(contractId);
|
|
377
|
+
const bars = this.barHistory.get(contractId);
|
|
378
|
+
if (!prices || !volumes || !bars || bars.length < 50) return null;
|
|
750
379
|
|
|
751
380
|
return {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
ratio: of.ir,
|
|
757
|
-
regime: pm.rg,
|
|
758
|
-
stopTicks: pm.spt,
|
|
759
|
-
targetTicks: pm.tgt,
|
|
760
|
-
barsProcessed: bs.length
|
|
381
|
+
zscore: computeZScore(prices).toFixed(2),
|
|
382
|
+
vpin: (computeVPIN(volumes, this.vpinWindow) * 100).toFixed(1) + '%',
|
|
383
|
+
ofi: (computeOrderFlowImbalance(bars, this.ofiLookback) * 100).toFixed(1) + '%',
|
|
384
|
+
bars: bars.length
|
|
761
385
|
};
|
|
762
386
|
}
|
|
763
|
-
}
|
|
764
387
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
388
|
+
/**
|
|
389
|
+
* Record trade result
|
|
390
|
+
*/
|
|
391
|
+
recordTradeResult(pnl) {
|
|
392
|
+
this.recentTrades.push({ pnl, timestamp: Date.now() });
|
|
393
|
+
if (this.recentTrades.length > 100) this.recentTrades.shift();
|
|
394
|
+
if (pnl > 0) { this.winStreak++; this.lossStreak = 0; }
|
|
395
|
+
else { this.lossStreak++; this.winStreak = 0; }
|
|
396
|
+
}
|
|
768
397
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
this.
|
|
774
|
-
this.s.on('signal', (sig) => {
|
|
775
|
-
this.emit('signal', {
|
|
776
|
-
side: sig.direction === 'long' ? 'buy' : 'sell',
|
|
777
|
-
action: 'open',
|
|
778
|
-
reason: `Z=${sig.zScore.toFixed(2)}, cf=${(sig.confidence * 100).toFixed(0)}%`,
|
|
779
|
-
...sig
|
|
780
|
-
});
|
|
781
|
-
});
|
|
398
|
+
/**
|
|
399
|
+
* Get bar history
|
|
400
|
+
*/
|
|
401
|
+
getBarHistory(contractId) {
|
|
402
|
+
return this.barHistory.get(contractId) || [];
|
|
782
403
|
}
|
|
783
404
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
this.
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
405
|
+
/**
|
|
406
|
+
* Reset strategy
|
|
407
|
+
*/
|
|
408
|
+
reset(contractId) {
|
|
409
|
+
this.barHistory.set(contractId, []);
|
|
410
|
+
this.priceBuffer.set(contractId, []);
|
|
411
|
+
this.volumeBuffer.set(contractId, []);
|
|
412
|
+
this.tradesBuffer.set(contractId, []);
|
|
413
|
+
this.atrHistory.set(contractId, []);
|
|
414
|
+
this.tickBuffer.set(contractId, []);
|
|
415
|
+
this.lastBarTime.set(contractId, 0);
|
|
416
|
+
this.kalmanStates.set(contractId, { estimate: 0, errorCovariance: 1.0 });
|
|
795
417
|
}
|
|
796
|
-
initialize(c, ts, tv) { this.s.initialize(c, ts, tv); }
|
|
797
|
-
getAnalysisState(c, p) { return this.s.getAnalysisState(c, p); }
|
|
798
|
-
recordTradeResult(p) { this.s.recordTradeResult(p); }
|
|
799
|
-
reset(c) { this.s.reset(c); this.emit('log', { type: 'info', message: 'Reset' }); }
|
|
800
|
-
getStats() { return this.s.getStats(); }
|
|
801
|
-
generateSignal(p) { return null; }
|
|
802
418
|
}
|
|
803
419
|
|
|
804
|
-
|
|
420
|
+
// Singleton instance
|
|
421
|
+
const M1 = new HQXUltraScalpingStrategy();
|
|
422
|
+
|
|
423
|
+
module.exports = { M1, HQXUltraScalpingStrategy, OrderSide, SignalStrength };
|