hedgequantx 2.6.161 → 2.6.162

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/src/menus/ai-agent-connect.js +181 -0
  3. package/src/menus/ai-agent-models.js +219 -0
  4. package/src/menus/ai-agent-oauth.js +292 -0
  5. package/src/menus/ai-agent-ui.js +141 -0
  6. package/src/menus/ai-agent.js +88 -1489
  7. package/src/pages/algo/copy-engine.js +449 -0
  8. package/src/pages/algo/copy-trading.js +11 -543
  9. package/src/pages/algo/smart-logs-data.js +218 -0
  10. package/src/pages/algo/smart-logs.js +9 -214
  11. package/src/pages/algo/ui-constants.js +144 -0
  12. package/src/pages/algo/ui-summary.js +184 -0
  13. package/src/pages/algo/ui.js +42 -526
  14. package/src/pages/stats-calculations.js +191 -0
  15. package/src/pages/stats-ui.js +381 -0
  16. package/src/pages/stats.js +14 -507
  17. package/src/services/ai/client-analysis.js +194 -0
  18. package/src/services/ai/client-models.js +333 -0
  19. package/src/services/ai/client.js +6 -489
  20. package/src/services/ai/index.js +2 -257
  21. package/src/services/ai/proxy-install.js +249 -0
  22. package/src/services/ai/proxy-manager.js +29 -411
  23. package/src/services/ai/proxy-remote.js +161 -0
  24. package/src/services/ai/supervisor-optimize.js +215 -0
  25. package/src/services/ai/supervisor-sync.js +178 -0
  26. package/src/services/ai/supervisor.js +50 -515
  27. package/src/services/ai/validation.js +250 -0
  28. package/src/services/hqx-server-events.js +110 -0
  29. package/src/services/hqx-server-handlers.js +217 -0
  30. package/src/services/hqx-server-latency.js +136 -0
  31. package/src/services/hqx-server.js +51 -403
  32. package/src/services/position-constants.js +28 -0
  33. package/src/services/position-manager.js +105 -554
  34. package/src/services/position-momentum.js +206 -0
  35. package/src/services/projectx/accounts.js +142 -0
  36. package/src/services/projectx/index.js +40 -289
  37. package/src/services/projectx/trading.js +180 -0
  38. package/src/services/rithmic/handlers.js +2 -208
  39. package/src/services/rithmic/index.js +32 -542
  40. package/src/services/rithmic/latency-tracker.js +182 -0
  41. package/src/services/rithmic/specs.js +146 -0
  42. package/src/services/rithmic/trade-history.js +254 -0
@@ -0,0 +1,449 @@
1
+ /**
2
+ * @fileoverview Copy Trading Engine
3
+ * @module pages/algo/copy-engine
4
+ *
5
+ * Ultra-low latency copy trading with:
6
+ * - Fast polling (250ms adaptive)
7
+ * - Multi-follower support
8
+ * - Parallel order execution
9
+ * - Position reconciliation
10
+ */
11
+
12
+ const { logger } = require('../../utils');
13
+ const { algoLogger } = require('./logger');
14
+ const StrategySupervisor = require('../../services/ai/strategy-supervisor');
15
+
16
+ const log = logger.scope('CopyEngine');
17
+
18
+ /**
19
+ * CopyEngine - Handles all copy trading logic with professional execution
20
+ */
21
+ class CopyEngine {
22
+ constructor(config) {
23
+ this.lead = config.lead;
24
+ this.followers = config.followers;
25
+ this.symbol = config.symbol;
26
+ this.dailyTarget = config.dailyTarget;
27
+ this.maxRisk = config.maxRisk;
28
+ this.ui = config.ui;
29
+ this.stats = config.stats;
30
+
31
+ // Engine state
32
+ this.running = false;
33
+ this.stopReason = null;
34
+
35
+ // Position tracking
36
+ this.leadPositions = new Map();
37
+ this.followerPositions = new Map();
38
+ this.pendingOrders = new Map();
39
+
40
+ // Order queue
41
+ this.orderQueues = new Map();
42
+ this.processingQueue = new Map();
43
+
44
+ // Timing
45
+ this.pollInterval = 250;
46
+ this.lastPollTime = 0;
47
+ this.pollCount = 0;
48
+ this.orderCount = 0;
49
+ this.failedOrders = 0;
50
+ this.lastLogTime = 0;
51
+ this.positionEntryTime = null;
52
+
53
+ // Retry configuration
54
+ this.maxRetries = 3;
55
+ this.retryDelayBase = 100;
56
+ this.maxSlippageTicks = 4;
57
+ }
58
+
59
+ getPositionKey(position) {
60
+ return position.contractId || position.symbol || position.id;
61
+ }
62
+
63
+ resolveSymbol(position, targetAccount) {
64
+ const targetType = targetAccount.type;
65
+
66
+ if (targetType === 'rithmic') {
67
+ return {
68
+ symbol: position.symbol || this.symbol.name,
69
+ exchange: position.exchange || this.symbol.exchange || 'CME',
70
+ contractId: null
71
+ };
72
+ } else {
73
+ return {
74
+ contractId: position.contractId || this.symbol.id || this.symbol.contractId,
75
+ symbol: null,
76
+ exchange: null
77
+ };
78
+ }
79
+ }
80
+
81
+ buildOrderData(params, platformType) {
82
+ const { accountId, contractId, symbol, exchange, side, size, type, price } = params;
83
+
84
+ if (platformType === 'rithmic') {
85
+ return { accountId, symbol, exchange: exchange || 'CME', size, side, type, price: price || 0 };
86
+ } else {
87
+ return { accountId, contractId, type, side, size };
88
+ }
89
+ }
90
+
91
+ async executeOrderWithRetry(follower, orderData, retryCount = 0) {
92
+ try {
93
+ const startTime = Date.now();
94
+ const result = await follower.service.placeOrder(orderData);
95
+ const latency = Date.now() - startTime;
96
+
97
+ if (result.success) {
98
+ this.orderCount++;
99
+ this.stats.latency = Math.round((this.stats.latency + latency) / 2);
100
+ return { success: true, latency };
101
+ }
102
+
103
+ if (retryCount < this.maxRetries) {
104
+ const delay = this.retryDelayBase * Math.pow(2, retryCount);
105
+ await this.sleep(delay);
106
+ return this.executeOrderWithRetry(follower, orderData, retryCount + 1);
107
+ }
108
+
109
+ this.failedOrders++;
110
+ return { success: false, error: result.error || 'Max retries exceeded' };
111
+ } catch (err) {
112
+ if (retryCount < this.maxRetries) {
113
+ const delay = this.retryDelayBase * Math.pow(2, retryCount);
114
+ await this.sleep(delay);
115
+ return this.executeOrderWithRetry(follower, orderData, retryCount + 1);
116
+ }
117
+
118
+ this.failedOrders++;
119
+ return { success: false, error: err.message };
120
+ }
121
+ }
122
+
123
+ async queueOrder(followerIdx, orderFn) {
124
+ if (!this.orderQueues.has(followerIdx)) {
125
+ this.orderQueues.set(followerIdx, []);
126
+ }
127
+
128
+ return new Promise((resolve) => {
129
+ this.orderQueues.get(followerIdx).push({ fn: orderFn, resolve });
130
+ this.processQueue(followerIdx);
131
+ });
132
+ }
133
+
134
+ async processQueue(followerIdx) {
135
+ if (this.processingQueue.get(followerIdx)) return;
136
+
137
+ const queue = this.orderQueues.get(followerIdx);
138
+ if (!queue || queue.length === 0) return;
139
+
140
+ this.processingQueue.set(followerIdx, true);
141
+
142
+ while (queue.length > 0 && this.running) {
143
+ const { fn, resolve } = queue.shift();
144
+ try {
145
+ const result = await fn();
146
+ resolve(result);
147
+ } catch (err) {
148
+ resolve({ success: false, error: err.message });
149
+ }
150
+ }
151
+
152
+ this.processingQueue.set(followerIdx, false);
153
+ }
154
+
155
+ async copyPositionOpen(position) {
156
+ const side = position.quantity > 0 ? 'LONG' : 'SHORT';
157
+ const orderSide = position.quantity > 0 ? 0 : 1;
158
+ const displaySymbol = position.symbol || this.symbol.name;
159
+ const size = Math.abs(position.quantity);
160
+ const entry = position.averagePrice || 0;
161
+
162
+ this.positionEntryTime = Date.now();
163
+ algoLogger.positionOpened(this.ui, displaySymbol, side, size, entry);
164
+
165
+ if (this.stats.aiSupervision) {
166
+ StrategySupervisor.feedSignal({
167
+ direction: side.toLowerCase(),
168
+ entry,
169
+ stopLoss: null,
170
+ takeProfit: null,
171
+ confidence: 0.5
172
+ });
173
+ }
174
+
175
+ const promises = this.followers.map((follower, idx) => {
176
+ return this.queueOrder(idx, async () => {
177
+ const resolved = this.resolveSymbol(position, follower);
178
+ const orderData = this.buildOrderData({
179
+ accountId: follower.account.accountId,
180
+ contractId: resolved.contractId,
181
+ symbol: resolved.symbol,
182
+ exchange: resolved.exchange,
183
+ side: orderSide,
184
+ size: follower.contracts,
185
+ type: 2
186
+ }, follower.type);
187
+
188
+ algoLogger.info(this.ui, 'COPY ORDER', `${side} ${follower.contracts}x -> ${follower.propfirm}`);
189
+
190
+ const result = await this.executeOrderWithRetry(follower, orderData);
191
+
192
+ if (result.success) {
193
+ algoLogger.orderFilled(this.ui, displaySymbol, side, follower.contracts, entry);
194
+ const posKey = this.getPositionKey(position);
195
+ this.followerPositions.set(`${idx}:${posKey}`, {
196
+ ...position,
197
+ followerIdx: idx,
198
+ openTime: Date.now()
199
+ });
200
+ } else {
201
+ algoLogger.orderRejected(this.ui, displaySymbol, result.error);
202
+ }
203
+
204
+ return result;
205
+ });
206
+ });
207
+
208
+ const results = await Promise.all(promises);
209
+ const successCount = results.filter(r => r.success).length;
210
+
211
+ if (successCount === this.followers.length) {
212
+ algoLogger.info(this.ui, 'ALL COPIED', `${successCount}/${this.followers.length} followers`);
213
+ } else if (successCount > 0) {
214
+ algoLogger.info(this.ui, 'PARTIAL COPY', `${successCount}/${this.followers.length} followers`);
215
+ }
216
+
217
+ return results;
218
+ }
219
+
220
+ async copyPositionClose(position, exitPrice, pnl) {
221
+ const side = position.quantity > 0 ? 'LONG' : 'SHORT';
222
+ const closeSide = position.quantity > 0 ? 1 : 0;
223
+ const displaySymbol = position.symbol || this.symbol.name;
224
+ const size = Math.abs(position.quantity);
225
+
226
+ this.positionEntryTime = null;
227
+ algoLogger.positionClosed(this.ui, displaySymbol, side, size, exitPrice, pnl);
228
+
229
+ if (this.stats.aiSupervision) {
230
+ StrategySupervisor.feedTradeResult({
231
+ side,
232
+ qty: size,
233
+ price: exitPrice,
234
+ pnl,
235
+ symbol: displaySymbol,
236
+ direction: side
237
+ });
238
+
239
+ const aiStatus = StrategySupervisor.getStatus();
240
+ if (aiStatus.patternsLearned.winning + aiStatus.patternsLearned.losing > 0) {
241
+ algoLogger.info(this.ui, 'AI LEARNING',
242
+ `${aiStatus.patternsLearned.winning}W/${aiStatus.patternsLearned.losing}L patterns`);
243
+ }
244
+ }
245
+
246
+ const posKey = this.getPositionKey(position);
247
+
248
+ const promises = this.followers.map((follower, idx) => {
249
+ return this.queueOrder(idx, async () => {
250
+ const resolved = this.resolveSymbol(position, follower);
251
+ const posIdentifier = follower.type === 'rithmic'
252
+ ? (position.symbol || this.symbol.name)
253
+ : (position.contractId || this.symbol.id);
254
+
255
+ algoLogger.info(this.ui, 'CLOSE ORDER', `${displaySymbol} -> ${follower.propfirm}`);
256
+
257
+ let result = await follower.service.closePosition(follower.account.accountId, posIdentifier);
258
+
259
+ if (!result.success) {
260
+ const orderData = this.buildOrderData({
261
+ accountId: follower.account.accountId,
262
+ contractId: resolved.contractId,
263
+ symbol: resolved.symbol,
264
+ exchange: resolved.exchange,
265
+ side: closeSide,
266
+ size: follower.contracts,
267
+ type: 2
268
+ }, follower.type);
269
+
270
+ result = await this.executeOrderWithRetry(follower, orderData);
271
+ }
272
+
273
+ if (result.success) {
274
+ algoLogger.info(this.ui, 'CLOSED', `${displaySymbol} on ${follower.propfirm}`);
275
+ this.followerPositions.delete(`${idx}:${posKey}`);
276
+ } else {
277
+ algoLogger.error(this.ui, 'CLOSE FAILED', `${follower.propfirm}: ${result.error}`);
278
+ }
279
+
280
+ return result;
281
+ });
282
+ });
283
+
284
+ const results = await Promise.all(promises);
285
+ const successCount = results.filter(r => r.success).length;
286
+
287
+ if (successCount === this.followers.length) {
288
+ this.stats.trades++;
289
+ this.stats.sessionPnl += pnl;
290
+ if (pnl >= 0) this.stats.wins++;
291
+ else this.stats.losses++;
292
+ }
293
+
294
+ return results;
295
+ }
296
+
297
+ async pollLeadPositions() {
298
+ if (!this.running) return;
299
+
300
+ const startTime = Date.now();
301
+
302
+ try {
303
+ const result = await this.lead.service.getPositions(this.lead.account.accountId);
304
+ if (!result.success) return;
305
+
306
+ const currentPositions = result.positions || [];
307
+ const currentMap = new Map();
308
+
309
+ for (const pos of currentPositions) {
310
+ if (pos.quantity === 0) continue;
311
+ const key = this.getPositionKey(pos);
312
+ currentMap.set(key, pos);
313
+ }
314
+
315
+ // Detect new positions
316
+ for (const [key, pos] of currentMap) {
317
+ if (!this.leadPositions.has(key)) {
318
+ await this.copyPositionOpen(pos);
319
+ this.leadPositions.set(key, pos);
320
+ } else {
321
+ const oldPos = this.leadPositions.get(key);
322
+ if (Math.abs(pos.quantity) !== Math.abs(oldPos.quantity)) {
323
+ this.leadPositions.set(key, pos);
324
+ }
325
+ }
326
+ }
327
+
328
+ // Detect closed positions
329
+ for (const [key, oldPos] of this.leadPositions) {
330
+ if (!currentMap.has(key)) {
331
+ const exitPrice = oldPos.averagePrice || 0;
332
+ const pnl = oldPos.profitAndLoss || 0;
333
+ await this.copyPositionClose(oldPos, exitPrice, pnl);
334
+ this.leadPositions.delete(key);
335
+ }
336
+ }
337
+
338
+ // Update P&L
339
+ const totalPnL = currentPositions.reduce((sum, p) => sum + (p.profitAndLoss || 0), 0);
340
+ this.stats.pnl = totalPnL;
341
+
342
+ // Check limits
343
+ if (totalPnL >= this.dailyTarget) {
344
+ this.stop('target');
345
+ algoLogger.info(this.ui, 'TARGET REACHED', `+$${totalPnL.toFixed(2)}`);
346
+ } else if (totalPnL <= -this.maxRisk) {
347
+ this.stop('risk');
348
+ algoLogger.error(this.ui, 'MAX RISK HIT', `-$${Math.abs(totalPnL).toFixed(2)}`);
349
+ }
350
+
351
+ // Adaptive polling
352
+ const pollTime = Date.now() - startTime;
353
+ this.stats.latency = pollTime;
354
+
355
+ if (this.leadPositions.size > 0) {
356
+ this.pollInterval = Math.max(100, Math.min(250, pollTime * 2));
357
+ } else {
358
+ this.pollInterval = Math.max(250, Math.min(500, pollTime * 3));
359
+ }
360
+
361
+ this.pollCount++;
362
+
363
+ // Smart logs
364
+ const now = Date.now();
365
+ if (now - this.lastLogTime > 1000) {
366
+ const smartLogs = require('./smart-logs');
367
+
368
+ if (this.leadPositions.size === 0) {
369
+ const scanLog = smartLogs.getScanningLog(true);
370
+ this.ui.addLog('info', `${scanLog.message} poll #${this.pollCount} | ${pollTime}ms`);
371
+ }
372
+ this.lastLogTime = now;
373
+ }
374
+
375
+ } catch (err) {
376
+ log.warn('Poll error', { error: err.message });
377
+ }
378
+ }
379
+
380
+ async reconcilePositions() {
381
+ for (let idx = 0; idx < this.followers.length; idx++) {
382
+ const follower = this.followers[idx];
383
+
384
+ try {
385
+ const result = await follower.service.getPositions(follower.account.accountId);
386
+ if (!result.success) continue;
387
+
388
+ const followerPositions = result.positions || [];
389
+
390
+ // Check missing positions
391
+ for (const [key, leadPos] of this.leadPositions) {
392
+ const hasFollowerPos = followerPositions.some(fp => {
393
+ const fpKey = this.getPositionKey(fp);
394
+ return fpKey === key && fp.quantity !== 0;
395
+ });
396
+
397
+ if (!hasFollowerPos) {
398
+ algoLogger.info(this.ui, 'RECONCILE', `Missing ${key} on ${follower.propfirm}`);
399
+ await this.copyPositionOpen(leadPos);
400
+ }
401
+ }
402
+
403
+ // Check orphaned positions
404
+ for (const fp of followerPositions) {
405
+ if (fp.quantity === 0) continue;
406
+ const fpKey = this.getPositionKey(fp);
407
+
408
+ if (!this.leadPositions.has(fpKey)) {
409
+ algoLogger.info(this.ui, 'RECONCILE', `Orphaned ${fpKey} on ${follower.propfirm}`);
410
+ const posIdentifier = follower.type === 'rithmic' ? fp.symbol : fp.contractId;
411
+ await follower.service.closePosition(follower.account.accountId, posIdentifier);
412
+ }
413
+ }
414
+
415
+ } catch (err) {
416
+ log.warn('Reconcile error', { follower: follower.propfirm, error: err.message });
417
+ }
418
+ }
419
+ }
420
+
421
+ async start() {
422
+ this.running = true;
423
+ this.stats.connected = true;
424
+
425
+ algoLogger.info(this.ui, 'ENGINE STARTED', `Polling every ${this.pollInterval}ms`);
426
+ algoLogger.info(this.ui, 'FOLLOWERS', `${this.followers.length} account(s)`);
427
+
428
+ await this.reconcilePositions();
429
+
430
+ while (this.running) {
431
+ await this.pollLeadPositions();
432
+ await this.sleep(this.pollInterval);
433
+ }
434
+
435
+ return this.stopReason;
436
+ }
437
+
438
+ stop(reason = 'manual') {
439
+ this.running = false;
440
+ this.stopReason = reason;
441
+ this.stats.connected = false;
442
+ }
443
+
444
+ sleep(ms) {
445
+ return new Promise(resolve => setTimeout(resolve, ms));
446
+ }
447
+ }
448
+
449
+ module.exports = { CopyEngine };