hedgequantx 2.6.163 → 2.7.0

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 (146) hide show
  1. package/README.md +15 -88
  2. package/bin/cli.js +0 -11
  3. package/dist/lib/api.jsc +0 -0
  4. package/dist/lib/api2.jsc +0 -0
  5. package/dist/lib/core.jsc +0 -0
  6. package/dist/lib/core2.jsc +0 -0
  7. package/dist/lib/data.js +1 -1
  8. package/dist/lib/data.jsc +0 -0
  9. package/dist/lib/data2.jsc +0 -0
  10. package/dist/lib/decoder.jsc +0 -0
  11. package/dist/lib/m/mod1.jsc +0 -0
  12. package/dist/lib/m/mod2.jsc +0 -0
  13. package/dist/lib/n/r1.jsc +0 -0
  14. package/dist/lib/n/r2.jsc +0 -0
  15. package/dist/lib/n/r3.jsc +0 -0
  16. package/dist/lib/n/r4.jsc +0 -0
  17. package/dist/lib/n/r5.jsc +0 -0
  18. package/dist/lib/n/r6.jsc +0 -0
  19. package/dist/lib/n/r7.jsc +0 -0
  20. package/dist/lib/o/util1.jsc +0 -0
  21. package/dist/lib/o/util2.jsc +0 -0
  22. package/package.json +6 -3
  23. package/src/app.js +40 -162
  24. package/src/config/constants.js +31 -33
  25. package/src/config/propfirms.js +13 -217
  26. package/src/config/settings.js +0 -43
  27. package/src/lib/api.js +198 -0
  28. package/src/lib/api2.js +353 -0
  29. package/src/lib/core.js +539 -0
  30. package/src/lib/core2.js +341 -0
  31. package/src/lib/data.js +555 -0
  32. package/src/lib/data2.js +492 -0
  33. package/src/lib/decoder.js +599 -0
  34. package/src/lib/m/s1.js +804 -0
  35. package/src/lib/m/s2.js +34 -0
  36. package/src/lib/n/r1.js +454 -0
  37. package/src/lib/n/r2.js +514 -0
  38. package/src/lib/n/r3.js +631 -0
  39. package/src/lib/n/r4.js +401 -0
  40. package/src/lib/n/r5.js +335 -0
  41. package/src/lib/n/r6.js +425 -0
  42. package/src/lib/n/r7.js +530 -0
  43. package/src/lib/o/l1.js +44 -0
  44. package/src/lib/o/l2.js +427 -0
  45. package/src/lib/python-bridge.js +206 -0
  46. package/src/menus/connect.js +14 -176
  47. package/src/menus/dashboard.js +65 -110
  48. package/src/pages/accounts.js +18 -18
  49. package/src/pages/algo/copy-trading.js +210 -240
  50. package/src/pages/algo/index.js +41 -104
  51. package/src/pages/algo/one-account.js +386 -33
  52. package/src/pages/algo/ui.js +312 -151
  53. package/src/pages/orders.js +3 -3
  54. package/src/pages/positions.js +3 -3
  55. package/src/pages/stats/chart.js +74 -0
  56. package/src/pages/stats/display.js +228 -0
  57. package/src/pages/stats/index.js +236 -0
  58. package/src/pages/stats/metrics.js +213 -0
  59. package/src/pages/user.js +6 -6
  60. package/src/services/hqx-server/constants.js +55 -0
  61. package/src/services/hqx-server/index.js +401 -0
  62. package/src/services/hqx-server/latency.js +81 -0
  63. package/src/services/index.js +12 -3
  64. package/src/services/rithmic/accounts.js +7 -32
  65. package/src/services/rithmic/connection.js +1 -204
  66. package/src/services/rithmic/contracts.js +116 -99
  67. package/src/services/rithmic/handlers.js +21 -196
  68. package/src/services/rithmic/index.js +63 -120
  69. package/src/services/rithmic/market.js +31 -0
  70. package/src/services/rithmic/orders.js +5 -111
  71. package/src/services/rithmic/protobuf.js +384 -138
  72. package/src/services/session.js +22 -173
  73. package/src/ui/box.js +10 -18
  74. package/src/ui/index.js +1 -3
  75. package/src/ui/menu.js +1 -1
  76. package/src/utils/prompts.js +2 -2
  77. package/dist/lib/m/s1.js +0 -1
  78. package/src/menus/ai-agent-connect.js +0 -181
  79. package/src/menus/ai-agent-models.js +0 -219
  80. package/src/menus/ai-agent-oauth.js +0 -292
  81. package/src/menus/ai-agent-ui.js +0 -141
  82. package/src/menus/ai-agent.js +0 -484
  83. package/src/pages/algo/algo-config.js +0 -195
  84. package/src/pages/algo/algo-multi.js +0 -801
  85. package/src/pages/algo/algo-utils.js +0 -58
  86. package/src/pages/algo/copy-engine.js +0 -449
  87. package/src/pages/algo/custom-strategy.js +0 -459
  88. package/src/pages/algo/logger.js +0 -245
  89. package/src/pages/algo/smart-logs-data.js +0 -218
  90. package/src/pages/algo/smart-logs.js +0 -387
  91. package/src/pages/algo/ui-constants.js +0 -144
  92. package/src/pages/algo/ui-summary.js +0 -184
  93. package/src/pages/stats-calculations.js +0 -191
  94. package/src/pages/stats-ui.js +0 -381
  95. package/src/pages/stats.js +0 -339
  96. package/src/services/ai/client-analysis.js +0 -194
  97. package/src/services/ai/client-models.js +0 -333
  98. package/src/services/ai/client.js +0 -343
  99. package/src/services/ai/index.js +0 -384
  100. package/src/services/ai/oauth-anthropic.js +0 -265
  101. package/src/services/ai/oauth-gemini.js +0 -223
  102. package/src/services/ai/oauth-iflow.js +0 -269
  103. package/src/services/ai/oauth-openai.js +0 -233
  104. package/src/services/ai/oauth-qwen.js +0 -279
  105. package/src/services/ai/providers/direct-providers.js +0 -323
  106. package/src/services/ai/providers/index.js +0 -62
  107. package/src/services/ai/providers/other-providers.js +0 -104
  108. package/src/services/ai/proxy-install.js +0 -249
  109. package/src/services/ai/proxy-manager.js +0 -494
  110. package/src/services/ai/proxy-remote.js +0 -161
  111. package/src/services/ai/strategy-supervisor.js +0 -1312
  112. package/src/services/ai/supervisor-data.js +0 -195
  113. package/src/services/ai/supervisor-optimize.js +0 -215
  114. package/src/services/ai/supervisor-sync.js +0 -178
  115. package/src/services/ai/supervisor-utils.js +0 -158
  116. package/src/services/ai/supervisor.js +0 -484
  117. package/src/services/ai/validation.js +0 -250
  118. package/src/services/hqx-server-events.js +0 -110
  119. package/src/services/hqx-server-handlers.js +0 -217
  120. package/src/services/hqx-server-latency.js +0 -136
  121. package/src/services/hqx-server.js +0 -403
  122. package/src/services/position-constants.js +0 -28
  123. package/src/services/position-exit-logic.js +0 -174
  124. package/src/services/position-manager.js +0 -438
  125. package/src/services/position-momentum.js +0 -206
  126. package/src/services/projectx/accounts.js +0 -142
  127. package/src/services/projectx/index.js +0 -443
  128. package/src/services/projectx/market.js +0 -172
  129. package/src/services/projectx/stats.js +0 -110
  130. package/src/services/projectx/trading.js +0 -180
  131. package/src/services/rithmic/latency-tracker.js +0 -182
  132. package/src/services/rithmic/market-data-decoders.js +0 -229
  133. package/src/services/rithmic/market-data.js +0 -272
  134. package/src/services/rithmic/orders-fast.js +0 -246
  135. package/src/services/rithmic/proto-decoders.js +0 -403
  136. package/src/services/rithmic/specs.js +0 -146
  137. package/src/services/rithmic/trade-history.js +0 -254
  138. package/src/services/session-history.js +0 -475
  139. package/src/services/strategy/hft-signal-calc.js +0 -147
  140. package/src/services/strategy/hft-tick.js +0 -407
  141. package/src/services/strategy/recovery-math.js +0 -402
  142. package/src/services/tradovate/constants.js +0 -109
  143. package/src/services/tradovate/index.js +0 -392
  144. package/src/services/tradovate/market.js +0 -47
  145. package/src/services/tradovate/orders.js +0 -145
  146. package/src/services/tradovate/websocket.js +0 -97
@@ -1,58 +0,0 @@
1
- /**
2
- * Algo Trading Utilities
3
- * Shared functions and constants for algo trading
4
- */
5
-
6
- 'use strict';
7
-
8
- /**
9
- * Format price to avoid floating point errors
10
- * @param {number} price - Raw price
11
- * @param {number} tickSize - Tick size (default 0.25)
12
- * @returns {string} - Formatted price string
13
- */
14
- const formatPrice = (price, tickSize = 0.25) => {
15
- if (price === null || price === undefined || isNaN(price)) return '--';
16
- const rounded = Math.round(price / tickSize) * tickSize;
17
- const decimals = tickSize < 1 ? Math.max(0, -Math.floor(Math.log10(tickSize))) : 0;
18
- return rounded.toFixed(decimals);
19
- };
20
-
21
- /**
22
- * Check if service supports fast path (Rithmic direct)
23
- * @param {Object} service - Trading service
24
- * @returns {boolean}
25
- */
26
- const isRithmicFastPath = (service) => {
27
- return typeof service.fastEntry === 'function' &&
28
- typeof service.fastExit === 'function' &&
29
- service.orderConn?.isConnected;
30
- };
31
-
32
- // Maximum symbols for multi-symbol trading
33
- const MAX_MULTI_SYMBOLS = 5;
34
-
35
- // Use HFT tick-based strategy for Rithmic
36
- const USE_HFT_STRATEGY = true;
37
-
38
- // Timeout for async operations
39
- const TIMEOUT_MS = 5000;
40
-
41
- /**
42
- * Wrap promise with timeout
43
- */
44
- const withTimeout = (promise, ms) => {
45
- return Promise.race([
46
- promise,
47
- new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms))
48
- ]);
49
- };
50
-
51
- module.exports = {
52
- formatPrice,
53
- isRithmicFastPath,
54
- MAX_MULTI_SYMBOLS,
55
- USE_HFT_STRATEGY,
56
- TIMEOUT_MS,
57
- withTimeout
58
- };
@@ -1,449 +0,0 @@
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 };