hedgequantx 2.4.4 → 2.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib/api.jsc CHANGED
Binary file
package/dist/lib/api2.jsc CHANGED
Binary file
package/dist/lib/core.jsc CHANGED
Binary file
Binary file
package/dist/lib/data.jsc CHANGED
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/lib/n/r1.jsc CHANGED
Binary file
package/dist/lib/n/r2.jsc CHANGED
Binary file
package/dist/lib/n/r3.jsc CHANGED
Binary file
package/dist/lib/n/r4.jsc CHANGED
Binary file
package/dist/lib/n/r5.jsc CHANGED
Binary file
package/dist/lib/n/r6.jsc CHANGED
Binary file
package/dist/lib/n/r7.jsc CHANGED
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/lib/m/s1.js CHANGED
@@ -1,61 +1,372 @@
1
1
  /**
2
- * M1 Module
2
+ * HQX Ultra Scalping Strategy
3
+ * Tick-based real-time scalping
3
4
  */
4
5
  const EventEmitter = require('events');
5
6
  const { v4: uuidv4 } = require('uuid');
6
- const OS = { B: 0, A: 1 };
7
- const SS = { W: 1, M: 2, S: 3, VS: 4, E: 5 };
8
7
 
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
10
+
11
+ /**
12
+ * VWAP Calculator - Real-time tick-based
13
+ */
9
14
  class VC {
10
- constructor() { this.d = new Map(); this.psv = new Map(); }
11
- gk(c) { return `${c}_${new Date().toISOString().split('T')[0]}`; }
12
- init(c) { const k = this.gk(c); if (!this.d.has(k)) { this.d.set(k, { v: 0, cv: 0, ctpv: 0, ub1: 0, ub2: 0, ub3: 0, lb1: 0, lb2: 0, lb3: 0, sd: 0, tc: 0, hod: -Infinity, lod: Infinity, lu: Date.now() }); this.psv.set(k, 0); } }
13
- pt(c, h, l, cl, vol, ts = Date.now()) { const k = this.gk(c); let d = this.d.get(k); if (!d) { this.init(c); d = this.d.get(k); } const tp = (h + l + cl) / 3; d.cv += vol; d.ctpv += tp * vol; if (d.cv > 0) d.v = d.ctpv / d.cv; const p = this.psv.get(k) || 0; this.psv.set(k, p + tp * tp * vol); this.cs(k); if (h > d.hod) d.hod = h; if (l < d.lod) d.lod = l; d.tc++; d.lu = ts; }
14
- cs(k) { const d = this.d.get(k); if (!d || d.cv === 0) return; const p = this.psv.get(k) || 0; const ms = p / d.cv; const sm = d.v * d.v; const va = ms - sm; d.sd = Math.sqrt(Math.max(0, va)); d.ub1 = d.v + d.sd; d.ub2 = d.v + 2 * d.sd; d.ub3 = d.v + 3 * d.sd; d.lb1 = d.v - d.sd; d.lb2 = d.v - 2 * d.sd; d.lb3 = d.v - 3 * d.sd; }
15
- an(c, cp, ts = 0.25) { const k = this.gk(c); const d = this.d.get(k); if (!d || d.v === 0) return null; const pd = cp - d.v; const dt = pd / ts; const dv = d.sd > 0 ? pd / d.sd : 0; let pos; if (Math.abs(dv) < 0.1) pos = 'AT'; else if (dv > 0) pos = 'ABOVE'; else pos = 'BELOW'; const ad = Math.abs(dv); let bl; if (ad < 1) bl = 0; else if (ad < 2) bl = 1; else if (ad < 3) bl = 2; else bl = 3; let mrp; if (ad >= 3) mrp = 0.99; else if (ad >= 2) mrp = 0.95; else if (ad >= 1.5) mrp = 0.85; else if (ad >= 1) mrp = 0.68; else mrp = 0.50; return { currentPrice: cp, vwap: d.v, deviation: dv, deviationTicks: dt, position: pos, bandLevel: bl, target: d.v, probability: mrp, isFavorableForMeanReversion: ad >= 1.5 }; }
16
- get(c) { return this.d.get(this.gk(c)) || null; }
17
- reset(c) { const k = this.gk(c); this.d.delete(k); this.psv.delete(k); }
15
+ constructor() {
16
+ this.data = new Map();
17
+ }
18
+
19
+ getKey(c) {
20
+ return `${c}_${new Date().toISOString().split('T')[0]}`;
21
+ }
22
+
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
+ });
36
+ }
37
+ }
38
+
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
+ }
47
+
48
+ // Update VWAP
49
+ d.cumVolume += volume;
50
+ d.cumTPV += price * volume;
51
+ if (d.cumVolume > 0) {
52
+ d.vwap = d.cumTPV / d.cumVolume;
53
+ }
54
+
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);
64
+ }
65
+
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;
72
+ }
73
+
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;
82
+
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
+ };
93
+ }
94
+
95
+ get(c) {
96
+ return this.data.get(this.getKey(c)) || null;
97
+ }
98
+
99
+ reset(c) {
100
+ this.data.delete(this.getKey(c));
101
+ }
18
102
  }
19
103
 
104
+ /**
105
+ * S1 - HQX Ultra Scalping Strategy
106
+ * Pure tick-based, no bar conversion
107
+ */
20
108
  class S1 extends EventEmitter {
21
- constructor(cfg = {}) { super(); this.ts = cfg.tickSize || 0.25; this.mrr = 1.2; this.ap = 14; this.ah = []; this.ba = 2.5; this.rt = []; this.ws = 0; this.ls = 0; this.lsd = null; this.bh = new Map(); this.vc = new VC(); this.lst = 0; this.cd = 30000; this.st = { s: 0, t: 0, w: 0, l: 0, p: 0 }; }
22
- // tickSize for tick calculations, P&L comes from API
23
- initialize(c, ts = 0.25) { this.ts = ts; this.bh.set(c, []); this.vc.init(c); }
24
- processBar(c, b) { let bs = this.bh.get(c); if (!bs) { bs = []; this.bh.set(c, bs); } bs.push(b); if (bs.length > 500) bs.shift(); if (bs.length < 50) return null; const sd = new Date(b.timestamp).toISOString().split('T')[0]; if (this.lsd !== sd) this.lsd = sd; this.vc.pt(c, b.high, b.low, b.close, b.volume, b.timestamp); const va = this.vc.an(c, b.close, this.ts); if (!va) return null; const vs = this.sv(va, b.close); const of = this.aof(bs); const os = this.sof(of); const dd = this.adb(bs); const ds = this.sdd(dd); const md = this.am(bs); const ms = this.sm(md); const pd = this.avp(bs, c, b.close); const ps = this.svp(pd, b.close); const { dir, st, ofc } = this.dd(va, of, dd, pd, b.close); if (dir === 'none') return null; if (st !== 'vr') return null; if (!ofc) return null; const bc = vs * 0.25 + os * 0.30 + ds * 0.20 + ms * 0.10 + ps * 0.15; const cf = Math.min(1.0, bc + 0.05); const pm = this.gap(bs); if (cf < pm.ct) return null; if (Date.now() - this.lst < this.cd) return null; const ep = b.close; const spt = pm.spt; const tgt = pm.tgt; let sl, tp; if (dir === 'long') { sl = ep - spt * this.ts; tp = ep + tgt * this.ts; const vt = va.vwap; if (vt > ep && vt < tp) { const vr = (vt - ep) / this.ts; if (vr >= spt * 1.5) tp = vt; } } else { sl = ep + spt * this.ts; tp = ep - tgt * this.ts; const vt = va.vwap; if (vt < ep && vt > tp) { const vr = (ep - vt) / this.ts; if (vr >= spt * 1.5) tp = vt; } } const rt = Math.abs(ep - sl) / this.ts; const rwt = Math.abs(tp - ep) / this.ts; if (rwt / rt < this.mrr) return null; let p1, p2; if (dir === 'long') { p1 = ep + pm.p1t * this.ts; p2 = ep + pm.p2t * this.ts; } else { p1 = ep - pm.p1t * this.ts; p2 = ep - pm.p2t * this.ts; } let str = SS.M; if (cf >= 0.75) str = SS.S; else if (cf >= 0.85) str = SS.VS; else if (cf < 0.55) str = SS.W; const wp = 0.5 + (cf - 0.5) * 0.3; const aw = Math.abs(tp - ep); const al = Math.abs(ep - sl); const eg = wp * aw - (1 - wp) * al; this.lst = Date.now(); this.st.s++; const sig = { id: uuidv4(), timestamp: Date.now(), symbol: c.split('.')[0] || c, contractId: c, side: dir === 'long' ? OS.B : OS.A, direction: dir, strategy: 'S1', strength: str, edge: eg, confidence: cf, entryPrice: ep, stopLoss: sl, takeProfit: tp, riskReward: rwt / rt, stopTicks: spt, targetTicks: tgt, trailTriggerTicks: pm.ttt, trailDistanceTicks: pm.tdt, partial1Price: p1, partial2Price: p2, score1: vs, score2: os, domScore: ds, microstructureScore: ms, volumeProfileScore: ps, confirmed: ofc, zScore: va.deviation, regime: pm.rg, expires: Date.now() + 60000 }; this.emit('signal', sig); return sig; }
25
- processTick(t) { const { contractId, price, bid, ask, volume, side, timestamp } = t; const b = { timestamp: timestamp || Date.now(), open: price, high: price, low: price, close: price, volume: volume || 1, trades: 1, delta: side === 'buy' ? (volume || 1) : -(volume || 1), vwap: price }; return this.processBar(contractId, b); }
26
- onTick(t) { return this.processTick(t); }
27
- recordTradeResult(p) { this.rt.push({ netPnl: p, timestamp: Date.now() }); if (this.rt.length > 100) this.rt.shift(); if (p > 0) { this.ws++; this.ls = 0; this.st.w++; } else { this.ls++; this.ws = 0; this.st.l++; } this.st.p += p; this.st.t++; }
28
- sv(v, cp) { const z = Math.abs(v.deviation); if (z < 0.5) return 0.3; else if (z < 1.0) return 0.6; else if (z < 2.0) return 0.9; else if (z < 2.5) return 0.7; else return 0.4; }
29
- sof(o) { let s = 0.5; s += o.ir * 0.3; if (o.dd) s += 0.2; if (o.ad) s += 0.15; return Math.min(1.0, s); }
30
- sdd(d) { let s = 0.5; s += Math.abs(d.pi) * 0.25; if (d.sb || d.sa) s += 0.15; if (d.al !== null) s += 0.1; return Math.min(1.0, s); }
31
- sm(m) { const ns = 1.0 - m.nr; const ls = 0.0001 < m.kl && m.kl < 0.001 ? 0.7 : 0.5; return ns * 0.6 + ls * 0.4; }
32
- svp(p, cp) { let s = 0.5; const pd = Math.abs(cp - p.poc) / this.ts; if (pd < 10) s += 0.2; if (p.val <= cp && cp <= p.vah) s += 0.15; for (const h of p.hvn) { if (Math.abs(cp - h) < 5 * this.ts) { s += 0.15; break; } } return Math.min(1.0, s); }
33
- aof(bs) { if (bs.length < 2) return { d: 0, cd: 0, dd: false, ad: false, ir: 0, as: 'neutral' }; const cb = bs[bs.length - 1]; const pb = bs[bs.length - 2]; const br = cb.high - cb.low; let bv, sv; if (br > 0) { const cp = (cb.close - cb.low) / br; bv = cb.volume * cp; sv = cb.volume * (1 - cp); } else { bv = cb.volume * 0.5; sv = cb.volume * 0.5; } const d = bv - sv; const cd = cb.delta || d; let dv = false; if (bs.length >= 5) { const rb = bs.slice(-5); const pc = rb[4].close - rb[0].close; const ds = rb.reduce((s, b) => s + (b.delta || 0), 0); if ((pc > 0 && ds < -cb.volume * 0.5) || (pc < 0 && ds > cb.volume * 0.5)) dv = true; } const lb = Math.min(20, bs.length); const rbs = bs.slice(-lb); const ar = rbs.reduce((s, b) => s + (b.high - b.low), 0) / lb; const av = rbs.reduce((s, b) => s + b.volume, 0) / lb; const ab = cb.volume > av * 1.5 && br < ar * 0.6; const tv = bv + sv; const im = tv > 0 ? Math.abs(bv - sv) / tv : 0; let ag; if (bv > sv * 1.3) ag = 'buy'; else if (sv > bv * 1.3) ag = 'sell'; else ag = 'neutral'; return { d, cd, dd: dv, ad: ab, ir: im, as: ag }; }
34
- adb(bs) { const lb = Math.min(10, bs.length); if (lb < 2) return { bp: 0.5, ap: 0.5, pi: 0, sb: false, sa: false, al: null }; const rbs = bs.slice(-lb); let bss = [], ass = []; for (const b of rbs) { const br = b.high - b.low; if (br > 0) { const cp = (b.close - b.low) / br; bss.push(cp * b.volume); ass.push((1 - cp) * b.volume); } } const t = bss.reduce((a, b) => a + b, 0) + ass.reduce((a, b) => a + b, 0); const bp = t > 0 ? bss.reduce((a, b) => a + b, 0) / t : 0.5; const ap = t > 0 ? ass.reduce((a, b) => a + b, 0) / t : 0.5; const pi = bp - ap; const lf = rbs.slice(-5); let hc = 0, lc = 0; for (const b of lf) { const br = b.high - b.low; if (br > 0) { const cp = (b.close - b.low) / br; if (cp > 0.7) hc++; if (cp < 0.3) lc++; } } let al = null; const ar = rbs.reduce((s, b) => s + (b.high - b.low), 0) / lb; const av = rbs.reduce((s, b) => s + b.volume, 0) / lb; for (const b of rbs) { const br = b.high - b.low; if (b.volume > av * 1.5 && br < ar * 0.5) { al = (b.high + b.low) / 2; break; } } return { bp, ap, pi, sb: hc >= 3, sa: lc >= 3, al }; }
35
- am(bs) { const lb = Math.min(50, bs.length); if (lb < 10) return { kl: 0, rs: 0, es: 0, rv: 0, nr: 0.5 }; const rbs = bs.slice(-lb); const pc = [], vs = []; for (let i = 1; i < rbs.length; i++) { pc.push(rbs[i].close - rbs[i - 1].close); vs.push(rbs[i].volume); } let kl = 0; if (pc.length > 5) { const mp = pc.reduce((a, b) => a + b, 0) / pc.length; const mv = vs.reduce((a, b) => a + b, 0) / vs.length; let cv = 0, vv = 0; for (let i = 0; i < pc.length; i++) { cv += (pc[i] - mp) * (vs[i] - mv); vv += Math.pow(vs[i] - mv, 2); } cv /= pc.length; vv /= pc.length; kl = vv > 0 ? Math.abs(cv / vv) : 0; } let rs = 0; if (pc.length > 2) { const c1 = pc.slice(1), c0 = pc.slice(0, -1); const m1 = c1.reduce((a, b) => a + b, 0) / c1.length; const m0 = c0.reduce((a, b) => a + b, 0) / c0.length; let ac = 0; for (let i = 0; i < c1.length; i++) ac += (c1[i] - m1) * (c0[i] - m0); ac /= c1.length; rs = Math.sqrt(Math.max(0, -ac)) * 2; } const es = rbs.reduce((s, b) => s + (b.high - b.low), 0) / lb; const rt = []; for (let i = 1; i < rbs.length; i++) { if (rbs[i - 1].close > 0) rt.push(Math.log(rbs[i].close / rbs[i - 1].close)); } let rv = 0; if (rt.length > 0) { const mr = rt.reduce((a, b) => a + b, 0) / rt.length; const va = rt.reduce((s, r) => s + Math.pow(r - mr, 2), 0) / rt.length; rv = Math.sqrt(va) * Math.sqrt(252 * 390); } const sv = pc.length >= 10 ? Math.sqrt(pc.slice(-10).reduce((s, p) => s + p * p, 0) / 10) : 0; const lv = pc.length > 0 ? Math.sqrt(pc.reduce((s, p) => s + p * p, 0) / pc.length) : 1; const nr = lv > 0 ? Math.min(1, sv / lv) : 0.5; return { kl, rs, es, rv, nr }; }
36
- avp(bs, c, cp) { const lb = Math.min(100, bs.length); if (lb < 10) return { poc: cp, vah: cp, val: cp, hvn: [], lvn: [] }; const rbs = bs.slice(-lb); const vap = new Map(); for (const b of rbs) { const br = b.high - b.low; const nl = Math.max(1, Math.round(br / this.ts)); const vpl = b.volume / nl; let p = b.low; while (p <= b.high) { const r = Math.round(p / this.ts) * this.ts; vap.set(r, (vap.get(r) || 0) + vpl); p += this.ts; } } if (vap.size === 0) return { poc: cp, vah: cp, val: cp, hvn: [], lvn: [] }; let poc = cp, mv = 0; for (const [p, v] of vap) { if (v > mv) { mv = v; poc = p; } } const tv = Array.from(vap.values()).reduce((a, b) => a + b, 0); const tgv = tv * 0.7; const sp = Array.from(vap.keys()).sort((a, b) => a - b); const pi = sp.indexOf(poc); let vv = vap.get(poc) || 0, li = pi, hi = pi; while (vv < tgv && (li > 0 || hi < sp.length - 1)) { const lv = li > 0 ? (vap.get(sp[li - 1]) || 0) : 0; const hv = hi < sp.length - 1 ? (vap.get(sp[hi + 1]) || 0) : 0; if (lv >= hv && li > 0) { li--; vv += vap.get(sp[li]) || 0; } else if (hi < sp.length - 1) { hi++; vv += vap.get(sp[hi]) || 0; } else break; } const val = sp[li], vah = sp[hi]; const av = tv / vap.size; const hvn = [], lvn = []; for (const [p, v] of vap) { if (v > av * 1.5) hvn.push(p); if (v < av * 0.5) lvn.push(p); } return { poc, vah, val, hvn, lvn }; }
37
- dd(v, o, d, p, cp) { let ls = 0, ss = 0, st = 'mixed', ofc = false; if (v.deviation < -0.8) { ls += 2; st = 'vr'; if (o.as === 'buy' || o.dd) ofc = true; } else if (v.deviation > 0.8) { ss += 2; st = 'vr'; if (o.as === 'sell' || o.dd) ofc = true; } if (o.as === 'buy' && o.ir > 0.35) ls += 1; else if (o.as === 'sell' && o.ir > 0.35) ss += 1; if (o.dd) { if (o.cd < 0) { ls += 2; st = 'dv'; ofc = true; } else { ss += 2; st = 'dv'; ofc = true; } } if (d.pi > 0.35) ls += 1; else if (d.pi < -0.35) ss += 1; if (d.sb) ls += 1; if (d.sa) ss += 1; if (cp < p.poc - 5 * this.ts) ls += 1; else if (cp > p.poc + 5 * this.ts) ss += 1; if (ls >= 2 && ls > ss) return { dir: 'long', st, ofc }; else if (ss >= 2 && ss > ls) return { dir: 'short', st, ofc }; else return { dir: 'none', st: 'none', ofc: false }; }
38
- catr(bs) { if (bs.length < this.ap + 1) return this.ba; const tv = []; for (let i = bs.length - this.ap; i < bs.length; i++) { const b = bs[i]; const pc = bs[i - 1].close; const tr = Math.max(b.high - b.low, Math.abs(b.high - pc), Math.abs(b.low - pc)); tv.push(tr); } const a = tv.reduce((a, b) => a + b, 0) / tv.length; this.ah.push(a); if (this.ah.length > 500) this.ah.shift(); return a; }
39
- gap(bs) { const a = this.catr(bs); const at = a / this.ts; let ap = 0.5; if (this.ah.length >= 20) ap = this.ah.filter(x => x <= a).length / this.ah.length; let rg, vm; if (ap < 0.25) { rg = 'low'; vm = 0.9; } else if (ap < 0.70) { rg = 'normal'; vm = 1.0; } else if (ap < 0.90) { rg = 'high'; vm = 1.15; } else { rg = 'extreme'; vm = 1.25; } const pm = this.gpm(); const bs6 = 6; let spt = Math.round(bs6 * vm); spt = Math.max(5, Math.min(8, spt)); const bt8 = 8; let tgt = Math.round(bt8 * vm); tgt = Math.max(6, Math.min(10, tgt)); if (tgt / spt < 1.2) tgt = Math.round(spt * 1.33); const ttt = Math.max(3, Math.min(5, Math.round(tgt * 0.5))); const tdt = Math.max(2, Math.min(3, Math.round(spt * 0.35))); const bc = 0.45; const ra = { low: 0.05, normal: 0.0, high: 0.03, extreme: 0.10 }; let ct = bc + ra[rg]; ct = Math.min(0.65, ct * pm); const p1t = Math.round(tgt * 0.5); const p2t = Math.round(tgt * 0.75); return { spt, tgt, ct, ttt, tdt, p1t, p2t, rg, at, vm }; }
40
- gpm() { if (this.rt.length === 0) return 1.0; const r = this.rt.slice(-20); const w = r.filter(t => t.netPnl > 0).length; const wr = w / r.length; let m = 1.0; if (this.ls >= 2) { m += 0.05 * this.ls; m = Math.min(m, 1.3); } if (r.length >= 5 && wr < 0.40) m *= 1.1; if (this.ws >= 4 && wr > 0.50) m *= 0.95; return Math.max(0.9, Math.min(1.3, m)); }
41
- getBarHistory(c) { return this.bh.get(c) || []; }
42
- reset(c) { this.bh.set(c, []); this.lsd = null; this.vc.reset(c); }
43
- getStats() { return this.st; }
44
- getAnalysisState(c, cp) { const bs = this.bh.get(c) || []; if (bs.length < 10) return { ready: false, message: 'Collecting...' }; const va = this.vc.an(c, cp, this.ts); const of = this.aof(bs); const pm = this.gap(bs); return { ready: true, vwap: va?.vwap || 0, zScore: va?.deviation || 0, orderFlow: of.as, ratio: of.ir, regime: pm.rg, stopTicks: pm.spt, targetTicks: pm.tgt, barsProcessed: bs.length }; }
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 };
131
+ }
132
+
133
+ initialize(contractId, tickSize = 0.25) {
134
+ this.tickSize = tickSize;
135
+ this.ticks.set(contractId, []);
136
+ this.vc.init(contractId);
137
+ }
138
+
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);
152
+ }
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;
172
+ } else {
173
+ this.sellVolume += volume;
174
+ }
175
+
176
+ this.lastPrice = price;
177
+
178
+ // Check for signal
179
+ return this.checkSignal(contractId, price, tickHistory);
180
+ }
181
+
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);
217
+ }
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);
222
+ }
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;
231
+ if (direction === 'long') {
232
+ stopLoss = price - stopTicks * this.tickSize;
233
+ takeProfit = price + targetTicks * this.tickSize;
234
+ } else {
235
+ stopLoss = price + stopTicks * this.tickSize;
236
+ takeProfit = price - targetTicks * this.tickSize;
237
+ }
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 = {
249
+ id: uuidv4(),
250
+ timestamp: Date.now(),
251
+ contractId,
252
+ direction,
253
+ side: direction === 'long' ? OS.B : OS.A,
254
+ entry: price,
255
+ entryPrice: price,
256
+ stopLoss,
257
+ takeProfit,
258
+ stopTicks,
259
+ targetTicks,
260
+ confidence,
261
+ zScore: vwap.zScore,
262
+ vwap: vwap.vwap,
263
+ imbalance,
264
+ momentum,
265
+ riskReward: reward / risk
266
+ };
267
+
268
+ this.emit('signal', signal);
269
+ return signal;
270
+ }
271
+
272
+ /**
273
+ * Called when entering a position
274
+ */
275
+ onPositionOpened() {
276
+ this.inPosition = true;
277
+ }
278
+
279
+ /**
280
+ * Called when position is closed
281
+ */
282
+ onPositionClosed(pnl) {
283
+ this.inPosition = false;
284
+ this.recordTradeResult(pnl);
285
+ }
286
+
287
+ /**
288
+ * Record trade result
289
+ */
290
+ 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;
302
+ }
303
+
304
+ getStats() {
305
+ return this.stats;
306
+ }
307
+
308
+ 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;
315
+ }
45
316
  }
46
317
 
318
+ /**
319
+ * M1 - Main Strategy Interface
320
+ */
47
321
  class M1 extends EventEmitter {
48
- constructor(cfg = {}) { super(); this.cfg = cfg; this.s = new S1(cfg); this.s.on('signal', (sig) => { this.emit('signal', { side: sig.direction === 'long' ? 'buy' : 'sell', action: 'open', reason: `Z=${sig.zScore.toFixed(2)}, cf=${(sig.confidence * 100).toFixed(0)}%`, ...sig }); }); }
49
- processTick(t) { return this.s.processTick(t); }
50
- onTick(t) { return this.processTick(t); }
51
- processBar(c, b) { return this.s.processBar(c, b); }
52
- onTrade(t) { this.s.processTick({ contractId: t.contractId || t.symbol, price: t.price, volume: t.size || t.volume || 1, side: t.side, timestamp: t.timestamp || Date.now() }); }
53
- initialize(c, ts, tv) { this.s.initialize(c, ts, tv); }
54
- getAnalysisState(c, p) { return this.s.getAnalysisState(c, p); }
55
- recordTradeResult(p) { this.s.recordTradeResult(p); }
56
- reset(c) { this.s.reset(c); this.emit('log', { type: 'info', message: 'Reset' }); }
57
- getStats() { return this.s.getStats(); }
58
- generateSignal(p) { return null; }
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
+ }
59
370
  }
60
371
 
61
372
  module.exports = { S1, M1, VC, OS, SS };
@@ -313,7 +313,8 @@ const launchAlgo = async (service, account, contract, config) => {
313
313
  // Connect to market data
314
314
  try {
315
315
  const token = service.token || service.getToken?.();
316
- await marketFeed.connect(token, account.propfirm, contractId);
316
+ const propfirmKey = (account.propfirm || 'topstep').toLowerCase().replace(/\s+/g, '_');
317
+ await marketFeed.connect(token, propfirmKey, contractId);
317
318
  await marketFeed.subscribe(symbolName, contractId);
318
319
  } catch (e) {
319
320
  ui.addLog('error', `Failed to connect: ${e.message}`);