hedgequantx 2.6.160 → 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.
- package/package.json +1 -1
- package/src/menus/ai-agent-connect.js +181 -0
- package/src/menus/ai-agent-models.js +219 -0
- package/src/menus/ai-agent-oauth.js +292 -0
- package/src/menus/ai-agent-ui.js +141 -0
- package/src/menus/ai-agent.js +88 -1489
- package/src/pages/algo/copy-engine.js +449 -0
- package/src/pages/algo/copy-trading.js +11 -543
- package/src/pages/algo/smart-logs-data.js +218 -0
- package/src/pages/algo/smart-logs.js +9 -214
- package/src/pages/algo/ui-constants.js +144 -0
- package/src/pages/algo/ui-summary.js +184 -0
- package/src/pages/algo/ui.js +42 -526
- package/src/pages/stats-calculations.js +191 -0
- package/src/pages/stats-ui.js +381 -0
- package/src/pages/stats.js +14 -507
- package/src/services/ai/client-analysis.js +194 -0
- package/src/services/ai/client-models.js +333 -0
- package/src/services/ai/client.js +6 -489
- package/src/services/ai/index.js +2 -257
- package/src/services/ai/proxy-install.js +249 -0
- package/src/services/ai/proxy-manager.js +29 -411
- package/src/services/ai/proxy-remote.js +161 -0
- package/src/services/ai/strategy-supervisor.js +10 -765
- package/src/services/ai/supervisor-data.js +195 -0
- package/src/services/ai/supervisor-optimize.js +215 -0
- package/src/services/ai/supervisor-sync.js +178 -0
- package/src/services/ai/supervisor-utils.js +158 -0
- package/src/services/ai/supervisor.js +50 -515
- package/src/services/ai/validation.js +250 -0
- package/src/services/hqx-server-events.js +110 -0
- package/src/services/hqx-server-handlers.js +217 -0
- package/src/services/hqx-server-latency.js +136 -0
- package/src/services/hqx-server.js +51 -403
- package/src/services/position-constants.js +28 -0
- package/src/services/position-manager.js +105 -554
- package/src/services/position-momentum.js +206 -0
- package/src/services/projectx/accounts.js +142 -0
- package/src/services/projectx/index.js +40 -289
- package/src/services/projectx/trading.js +180 -0
- package/src/services/rithmic/handlers.js +2 -208
- package/src/services/rithmic/index.js +32 -542
- package/src/services/rithmic/latency-tracker.js +182 -0
- package/src/services/rithmic/specs.js +146 -0
- package/src/services/rithmic/trade-history.js +254 -0
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Professional Copy Trading
|
|
2
|
+
* @fileoverview Professional Copy Trading Menu
|
|
3
3
|
* @module pages/algo/copy-trading
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* - Fast polling (250ms adaptive)
|
|
7
|
-
* - Multi-follower support
|
|
8
|
-
* - Parallel order execution
|
|
9
|
-
* - Automatic retry with exponential backoff
|
|
10
|
-
* - Position reconciliation
|
|
11
|
-
* - Slippage protection
|
|
12
|
-
* - Cross-platform support (ProjectX <-> Rithmic)
|
|
5
|
+
* Copy trading configuration and session management
|
|
13
6
|
*/
|
|
14
7
|
|
|
15
8
|
const chalk = require('chalk');
|
|
@@ -21,6 +14,7 @@ const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
|
21
14
|
const { logger, prompts } = require('../../utils');
|
|
22
15
|
const { checkMarketHours } = require('../../services/projectx/market');
|
|
23
16
|
const { algoLogger } = require('./logger');
|
|
17
|
+
const { CopyEngine } = require('./copy-engine');
|
|
24
18
|
|
|
25
19
|
// AI Strategy Supervisor
|
|
26
20
|
const aiService = require('../../services/ai');
|
|
@@ -28,531 +22,6 @@ const StrategySupervisor = require('../../services/ai/strategy-supervisor');
|
|
|
28
22
|
|
|
29
23
|
const log = logger.scope('CopyTrading');
|
|
30
24
|
|
|
31
|
-
// ============================================================================
|
|
32
|
-
// COPY ENGINE - Professional Order Execution
|
|
33
|
-
// ============================================================================
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* CopyEngine - Handles all copy trading logic with professional execution
|
|
37
|
-
*/
|
|
38
|
-
class CopyEngine {
|
|
39
|
-
constructor(config) {
|
|
40
|
-
this.lead = config.lead;
|
|
41
|
-
this.followers = config.followers; // Array of followers
|
|
42
|
-
this.symbol = config.symbol;
|
|
43
|
-
this.dailyTarget = config.dailyTarget;
|
|
44
|
-
this.maxRisk = config.maxRisk;
|
|
45
|
-
this.ui = config.ui;
|
|
46
|
-
this.stats = config.stats;
|
|
47
|
-
|
|
48
|
-
// Engine state
|
|
49
|
-
this.running = false;
|
|
50
|
-
this.stopReason = null;
|
|
51
|
-
|
|
52
|
-
// Position tracking
|
|
53
|
-
this.leadPositions = new Map(); // key: positionKey, value: position
|
|
54
|
-
this.followerPositions = new Map(); // key: `${followerIdx}:${posKey}`, value: position
|
|
55
|
-
this.pendingOrders = new Map(); // key: orderId, value: orderInfo
|
|
56
|
-
|
|
57
|
-
// Order queue for sequential execution per follower
|
|
58
|
-
this.orderQueues = new Map(); // key: followerIdx, value: queue[]
|
|
59
|
-
this.processingQueue = new Map(); // key: followerIdx, value: boolean
|
|
60
|
-
|
|
61
|
-
// Timing
|
|
62
|
-
this.pollInterval = 250; // Start at 250ms, adaptive
|
|
63
|
-
this.lastPollTime = 0;
|
|
64
|
-
this.pollCount = 0;
|
|
65
|
-
this.orderCount = 0;
|
|
66
|
-
this.failedOrders = 0;
|
|
67
|
-
this.lastLogTime = 0;
|
|
68
|
-
this.positionEntryTime = null;
|
|
69
|
-
|
|
70
|
-
// Retry configuration
|
|
71
|
-
this.maxRetries = 3;
|
|
72
|
-
this.retryDelayBase = 100; // ms
|
|
73
|
-
|
|
74
|
-
// Slippage protection (ticks)
|
|
75
|
-
this.maxSlippageTicks = 4;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Get unique position key (cross-platform compatible)
|
|
80
|
-
*/
|
|
81
|
-
getPositionKey(position) {
|
|
82
|
-
return position.contractId || position.symbol || position.id;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Resolve symbol for target platform
|
|
87
|
-
*/
|
|
88
|
-
resolveSymbol(position, targetAccount) {
|
|
89
|
-
const targetType = targetAccount.type;
|
|
90
|
-
|
|
91
|
-
if (targetType === 'rithmic') {
|
|
92
|
-
return {
|
|
93
|
-
symbol: position.symbol || this.symbol.name,
|
|
94
|
-
exchange: position.exchange || this.symbol.exchange || 'CME',
|
|
95
|
-
contractId: null
|
|
96
|
-
};
|
|
97
|
-
} else {
|
|
98
|
-
return {
|
|
99
|
-
contractId: position.contractId || this.symbol.id || this.symbol.contractId,
|
|
100
|
-
symbol: null,
|
|
101
|
-
exchange: null
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Build order data for specific platform
|
|
108
|
-
*/
|
|
109
|
-
buildOrderData(params, platformType) {
|
|
110
|
-
const { accountId, contractId, symbol, exchange, side, size, type, price } = params;
|
|
111
|
-
|
|
112
|
-
if (platformType === 'rithmic') {
|
|
113
|
-
return {
|
|
114
|
-
accountId,
|
|
115
|
-
symbol,
|
|
116
|
-
exchange: exchange || 'CME',
|
|
117
|
-
size,
|
|
118
|
-
side,
|
|
119
|
-
type,
|
|
120
|
-
price: price || 0
|
|
121
|
-
};
|
|
122
|
-
} else {
|
|
123
|
-
return {
|
|
124
|
-
accountId,
|
|
125
|
-
contractId,
|
|
126
|
-
type,
|
|
127
|
-
side,
|
|
128
|
-
size
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Execute order with retry logic
|
|
135
|
-
*/
|
|
136
|
-
async executeOrderWithRetry(follower, orderData, retryCount = 0) {
|
|
137
|
-
try {
|
|
138
|
-
const startTime = Date.now();
|
|
139
|
-
const result = await follower.service.placeOrder(orderData);
|
|
140
|
-
const latency = Date.now() - startTime;
|
|
141
|
-
|
|
142
|
-
if (result.success) {
|
|
143
|
-
this.orderCount++;
|
|
144
|
-
this.stats.latency = Math.round((this.stats.latency + latency) / 2);
|
|
145
|
-
return { success: true, latency };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Retry on failure
|
|
149
|
-
if (retryCount < this.maxRetries) {
|
|
150
|
-
const delay = this.retryDelayBase * Math.pow(2, retryCount);
|
|
151
|
-
await this.sleep(delay);
|
|
152
|
-
return this.executeOrderWithRetry(follower, orderData, retryCount + 1);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
this.failedOrders++;
|
|
156
|
-
return { success: false, error: result.error || 'Max retries exceeded' };
|
|
157
|
-
} catch (err) {
|
|
158
|
-
if (retryCount < this.maxRetries) {
|
|
159
|
-
const delay = this.retryDelayBase * Math.pow(2, retryCount);
|
|
160
|
-
await this.sleep(delay);
|
|
161
|
-
return this.executeOrderWithRetry(follower, orderData, retryCount + 1);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
this.failedOrders++;
|
|
165
|
-
return { success: false, error: err.message };
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Queue order for a follower (ensures sequential execution per follower)
|
|
171
|
-
*/
|
|
172
|
-
async queueOrder(followerIdx, orderFn) {
|
|
173
|
-
if (!this.orderQueues.has(followerIdx)) {
|
|
174
|
-
this.orderQueues.set(followerIdx, []);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return new Promise((resolve) => {
|
|
178
|
-
this.orderQueues.get(followerIdx).push({ fn: orderFn, resolve });
|
|
179
|
-
this.processQueue(followerIdx);
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Process order queue for a follower
|
|
185
|
-
*/
|
|
186
|
-
async processQueue(followerIdx) {
|
|
187
|
-
if (this.processingQueue.get(followerIdx)) return;
|
|
188
|
-
|
|
189
|
-
const queue = this.orderQueues.get(followerIdx);
|
|
190
|
-
if (!queue || queue.length === 0) return;
|
|
191
|
-
|
|
192
|
-
this.processingQueue.set(followerIdx, true);
|
|
193
|
-
|
|
194
|
-
while (queue.length > 0 && this.running) {
|
|
195
|
-
const { fn, resolve } = queue.shift();
|
|
196
|
-
try {
|
|
197
|
-
const result = await fn();
|
|
198
|
-
resolve(result);
|
|
199
|
-
} catch (err) {
|
|
200
|
-
resolve({ success: false, error: err.message });
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
this.processingQueue.set(followerIdx, false);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Copy position open to all followers (parallel execution)
|
|
209
|
-
*/
|
|
210
|
-
async copyPositionOpen(position) {
|
|
211
|
-
const side = position.quantity > 0 ? 'LONG' : 'SHORT';
|
|
212
|
-
const orderSide = position.quantity > 0 ? 0 : 1;
|
|
213
|
-
const displaySymbol = position.symbol || this.symbol.name;
|
|
214
|
-
const size = Math.abs(position.quantity);
|
|
215
|
-
const entry = position.averagePrice || 0;
|
|
216
|
-
|
|
217
|
-
// Track entry time for smart logs
|
|
218
|
-
this.positionEntryTime = Date.now();
|
|
219
|
-
|
|
220
|
-
algoLogger.positionOpened(this.ui, displaySymbol, side, size, entry);
|
|
221
|
-
|
|
222
|
-
// Feed to AI supervisor
|
|
223
|
-
if (this.stats.aiSupervision) {
|
|
224
|
-
StrategySupervisor.feedSignal({
|
|
225
|
-
direction: side.toLowerCase(),
|
|
226
|
-
entry,
|
|
227
|
-
stopLoss: null,
|
|
228
|
-
takeProfit: null,
|
|
229
|
-
confidence: 0.5
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Execute on all followers in parallel
|
|
234
|
-
const promises = this.followers.map((follower, idx) => {
|
|
235
|
-
return this.queueOrder(idx, async () => {
|
|
236
|
-
const resolved = this.resolveSymbol(position, follower);
|
|
237
|
-
const orderData = this.buildOrderData({
|
|
238
|
-
accountId: follower.account.accountId,
|
|
239
|
-
contractId: resolved.contractId,
|
|
240
|
-
symbol: resolved.symbol,
|
|
241
|
-
exchange: resolved.exchange,
|
|
242
|
-
side: orderSide,
|
|
243
|
-
size: follower.contracts,
|
|
244
|
-
type: 2 // Market
|
|
245
|
-
}, follower.type);
|
|
246
|
-
|
|
247
|
-
algoLogger.info(this.ui, 'COPY ORDER', `${side} ${follower.contracts}x -> ${follower.propfirm}`);
|
|
248
|
-
|
|
249
|
-
const result = await this.executeOrderWithRetry(follower, orderData);
|
|
250
|
-
|
|
251
|
-
if (result.success) {
|
|
252
|
-
algoLogger.orderFilled(this.ui, displaySymbol, side, follower.contracts, entry);
|
|
253
|
-
|
|
254
|
-
// Track follower position
|
|
255
|
-
const posKey = this.getPositionKey(position);
|
|
256
|
-
this.followerPositions.set(`${idx}:${posKey}`, {
|
|
257
|
-
...position,
|
|
258
|
-
followerIdx: idx,
|
|
259
|
-
openTime: Date.now()
|
|
260
|
-
});
|
|
261
|
-
} else {
|
|
262
|
-
algoLogger.orderRejected(this.ui, displaySymbol, result.error);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return result;
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
const results = await Promise.all(promises);
|
|
270
|
-
const successCount = results.filter(r => r.success).length;
|
|
271
|
-
|
|
272
|
-
if (successCount === this.followers.length) {
|
|
273
|
-
algoLogger.info(this.ui, 'ALL COPIED', `${successCount}/${this.followers.length} followers`);
|
|
274
|
-
} else if (successCount > 0) {
|
|
275
|
-
algoLogger.info(this.ui, 'PARTIAL COPY', `${successCount}/${this.followers.length} followers`);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return results;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Copy position close to all followers (parallel execution)
|
|
283
|
-
*/
|
|
284
|
-
async copyPositionClose(position, exitPrice, pnl) {
|
|
285
|
-
const side = position.quantity > 0 ? 'LONG' : 'SHORT';
|
|
286
|
-
const closeSide = position.quantity > 0 ? 1 : 0;
|
|
287
|
-
const displaySymbol = position.symbol || this.symbol.name;
|
|
288
|
-
const size = Math.abs(position.quantity);
|
|
289
|
-
|
|
290
|
-
// Reset entry time
|
|
291
|
-
this.positionEntryTime = null;
|
|
292
|
-
|
|
293
|
-
algoLogger.positionClosed(this.ui, displaySymbol, side, size, exitPrice, pnl);
|
|
294
|
-
|
|
295
|
-
// Feed to AI supervisor
|
|
296
|
-
if (this.stats.aiSupervision) {
|
|
297
|
-
StrategySupervisor.feedTradeResult({
|
|
298
|
-
side,
|
|
299
|
-
qty: size,
|
|
300
|
-
price: exitPrice,
|
|
301
|
-
pnl,
|
|
302
|
-
symbol: displaySymbol,
|
|
303
|
-
direction: side
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
const aiStatus = StrategySupervisor.getStatus();
|
|
307
|
-
if (aiStatus.patternsLearned.winning + aiStatus.patternsLearned.losing > 0) {
|
|
308
|
-
algoLogger.info(this.ui, 'AI LEARNING',
|
|
309
|
-
`${aiStatus.patternsLearned.winning}W/${aiStatus.patternsLearned.losing}L patterns`);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Close on all followers in parallel
|
|
314
|
-
const posKey = this.getPositionKey(position);
|
|
315
|
-
|
|
316
|
-
const promises = this.followers.map((follower, idx) => {
|
|
317
|
-
return this.queueOrder(idx, async () => {
|
|
318
|
-
const resolved = this.resolveSymbol(position, follower);
|
|
319
|
-
const posIdentifier = follower.type === 'rithmic'
|
|
320
|
-
? (position.symbol || this.symbol.name)
|
|
321
|
-
: (position.contractId || this.symbol.id);
|
|
322
|
-
|
|
323
|
-
algoLogger.info(this.ui, 'CLOSE ORDER', `${displaySymbol} -> ${follower.propfirm}`);
|
|
324
|
-
|
|
325
|
-
// Try closePosition first
|
|
326
|
-
let result = await follower.service.closePosition(
|
|
327
|
-
follower.account.accountId,
|
|
328
|
-
posIdentifier
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
if (!result.success) {
|
|
332
|
-
// Fallback: market order
|
|
333
|
-
const orderData = this.buildOrderData({
|
|
334
|
-
accountId: follower.account.accountId,
|
|
335
|
-
contractId: resolved.contractId,
|
|
336
|
-
symbol: resolved.symbol,
|
|
337
|
-
exchange: resolved.exchange,
|
|
338
|
-
side: closeSide,
|
|
339
|
-
size: follower.contracts,
|
|
340
|
-
type: 2
|
|
341
|
-
}, follower.type);
|
|
342
|
-
|
|
343
|
-
result = await this.executeOrderWithRetry(follower, orderData);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (result.success) {
|
|
347
|
-
algoLogger.info(this.ui, 'CLOSED', `${displaySymbol} on ${follower.propfirm}`);
|
|
348
|
-
this.followerPositions.delete(`${idx}:${posKey}`);
|
|
349
|
-
} else {
|
|
350
|
-
algoLogger.error(this.ui, 'CLOSE FAILED', `${follower.propfirm}: ${result.error}`);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return result;
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
const results = await Promise.all(promises);
|
|
358
|
-
const successCount = results.filter(r => r.success).length;
|
|
359
|
-
|
|
360
|
-
if (successCount === this.followers.length) {
|
|
361
|
-
this.stats.trades++;
|
|
362
|
-
this.stats.sessionPnl += pnl; // Track session P&L
|
|
363
|
-
if (pnl >= 0) this.stats.wins++;
|
|
364
|
-
else this.stats.losses++;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return results;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Poll lead positions and detect changes
|
|
372
|
-
*/
|
|
373
|
-
async pollLeadPositions() {
|
|
374
|
-
if (!this.running) return;
|
|
375
|
-
|
|
376
|
-
const startTime = Date.now();
|
|
377
|
-
|
|
378
|
-
try {
|
|
379
|
-
const result = await this.lead.service.getPositions(this.lead.account.accountId);
|
|
380
|
-
if (!result.success) return;
|
|
381
|
-
|
|
382
|
-
const currentPositions = result.positions || [];
|
|
383
|
-
const currentMap = new Map();
|
|
384
|
-
|
|
385
|
-
// Build current positions map
|
|
386
|
-
for (const pos of currentPositions) {
|
|
387
|
-
if (pos.quantity === 0) continue;
|
|
388
|
-
const key = this.getPositionKey(pos);
|
|
389
|
-
currentMap.set(key, pos);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Detect new positions (opened)
|
|
393
|
-
for (const [key, pos] of currentMap) {
|
|
394
|
-
if (!this.leadPositions.has(key)) {
|
|
395
|
-
// New position - copy to followers
|
|
396
|
-
await this.copyPositionOpen(pos);
|
|
397
|
-
this.leadPositions.set(key, pos);
|
|
398
|
-
} else {
|
|
399
|
-
// Position exists - check for size change (scaling)
|
|
400
|
-
const oldPos = this.leadPositions.get(key);
|
|
401
|
-
if (Math.abs(pos.quantity) !== Math.abs(oldPos.quantity)) {
|
|
402
|
-
// Size changed - update tracked position (scaling in/out)
|
|
403
|
-
this.leadPositions.set(key, pos);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Detect closed positions
|
|
409
|
-
for (const [key, oldPos] of this.leadPositions) {
|
|
410
|
-
if (!currentMap.has(key)) {
|
|
411
|
-
// Position closed - close on followers
|
|
412
|
-
const exitPrice = oldPos.averagePrice || 0;
|
|
413
|
-
const pnl = oldPos.profitAndLoss || 0;
|
|
414
|
-
await this.copyPositionClose(oldPos, exitPrice, pnl);
|
|
415
|
-
this.leadPositions.delete(key);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Update P&L from current positions
|
|
420
|
-
const totalPnL = currentPositions.reduce((sum, p) => sum + (p.profitAndLoss || 0), 0);
|
|
421
|
-
this.stats.pnl = totalPnL;
|
|
422
|
-
|
|
423
|
-
// Check limits
|
|
424
|
-
if (totalPnL >= this.dailyTarget) {
|
|
425
|
-
this.stop('target');
|
|
426
|
-
algoLogger.info(this.ui, 'TARGET REACHED', `+$${totalPnL.toFixed(2)}`);
|
|
427
|
-
} else if (totalPnL <= -this.maxRisk) {
|
|
428
|
-
this.stop('risk');
|
|
429
|
-
algoLogger.error(this.ui, 'MAX RISK HIT', `-$${Math.abs(totalPnL).toFixed(2)}`);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Adaptive polling - faster when positions are open
|
|
433
|
-
const pollTime = Date.now() - startTime;
|
|
434
|
-
this.stats.latency = pollTime;
|
|
435
|
-
|
|
436
|
-
if (this.leadPositions.size > 0) {
|
|
437
|
-
this.pollInterval = Math.max(100, Math.min(250, pollTime * 2));
|
|
438
|
-
} else {
|
|
439
|
-
this.pollInterval = Math.max(250, Math.min(500, pollTime * 3));
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
this.pollCount++;
|
|
443
|
-
|
|
444
|
-
// Smart logs - only on STATE CHANGES (not every second when in position)
|
|
445
|
-
const now = Date.now();
|
|
446
|
-
if (now - this.lastLogTime > 1000) {
|
|
447
|
-
const smartLogs = require('./smart-logs');
|
|
448
|
-
|
|
449
|
-
if (this.leadPositions.size === 0) {
|
|
450
|
-
// Not in position - show market analysis (varied messages)
|
|
451
|
-
// Use scanning log since copy trading doesn't have strategy model values
|
|
452
|
-
const scanLog = smartLogs.getScanningLog(true);
|
|
453
|
-
this.ui.addLog('info', `${scanLog.message} poll #${this.pollCount} | ${pollTime}ms`);
|
|
454
|
-
}
|
|
455
|
-
// When IN POSITION: Don't spam logs every second
|
|
456
|
-
// Position updates come from order fills and exit events (copyPositionOpen/copyPositionClose)
|
|
457
|
-
this.lastLogTime = now;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
} catch (err) {
|
|
461
|
-
log.warn('Poll error', { error: err.message });
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Reconcile follower positions with lead
|
|
467
|
-
*/
|
|
468
|
-
async reconcilePositions() {
|
|
469
|
-
// Get all follower positions and compare with lead
|
|
470
|
-
for (let idx = 0; idx < this.followers.length; idx++) {
|
|
471
|
-
const follower = this.followers[idx];
|
|
472
|
-
|
|
473
|
-
try {
|
|
474
|
-
const result = await follower.service.getPositions(follower.account.accountId);
|
|
475
|
-
if (!result.success) continue;
|
|
476
|
-
|
|
477
|
-
const followerPositions = result.positions || [];
|
|
478
|
-
|
|
479
|
-
// Check each lead position has corresponding follower position
|
|
480
|
-
for (const [key, leadPos] of this.leadPositions) {
|
|
481
|
-
const hasFollowerPos = followerPositions.some(fp => {
|
|
482
|
-
const fpKey = this.getPositionKey(fp);
|
|
483
|
-
return fpKey === key && fp.quantity !== 0;
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
if (!hasFollowerPos) {
|
|
487
|
-
// Missing position on follower - need to open
|
|
488
|
-
algoLogger.info(this.ui, 'RECONCILE', `Missing ${key} on ${follower.propfirm}`);
|
|
489
|
-
await this.copyPositionOpen(leadPos);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Check for orphaned follower positions (position on follower but not on lead)
|
|
494
|
-
for (const fp of followerPositions) {
|
|
495
|
-
if (fp.quantity === 0) continue;
|
|
496
|
-
const fpKey = this.getPositionKey(fp);
|
|
497
|
-
|
|
498
|
-
if (!this.leadPositions.has(fpKey)) {
|
|
499
|
-
// Orphaned position - close it
|
|
500
|
-
algoLogger.info(this.ui, 'RECONCILE', `Orphaned ${fpKey} on ${follower.propfirm}`);
|
|
501
|
-
|
|
502
|
-
const posIdentifier = follower.type === 'rithmic' ? fp.symbol : fp.contractId;
|
|
503
|
-
await follower.service.closePosition(follower.account.accountId, posIdentifier);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
} catch (err) {
|
|
508
|
-
log.warn('Reconcile error', { follower: follower.propfirm, error: err.message });
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* Start the copy engine
|
|
515
|
-
*/
|
|
516
|
-
async start() {
|
|
517
|
-
this.running = true;
|
|
518
|
-
this.stats.connected = true;
|
|
519
|
-
|
|
520
|
-
algoLogger.info(this.ui, 'ENGINE STARTED', `Polling every ${this.pollInterval}ms`);
|
|
521
|
-
algoLogger.info(this.ui, 'FOLLOWERS', `${this.followers.length} account(s)`);
|
|
522
|
-
|
|
523
|
-
// Initial reconciliation
|
|
524
|
-
await this.reconcilePositions();
|
|
525
|
-
|
|
526
|
-
// Main polling loop
|
|
527
|
-
while (this.running) {
|
|
528
|
-
await this.pollLeadPositions();
|
|
529
|
-
await this.sleep(this.pollInterval);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
return this.stopReason;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Stop the copy engine
|
|
537
|
-
*/
|
|
538
|
-
stop(reason = 'manual') {
|
|
539
|
-
this.running = false;
|
|
540
|
-
this.stopReason = reason;
|
|
541
|
-
this.stats.connected = false;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Sleep utility
|
|
546
|
-
*/
|
|
547
|
-
sleep(ms) {
|
|
548
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// ============================================================================
|
|
553
|
-
// COPY TRADING MENU
|
|
554
|
-
// ============================================================================
|
|
555
|
-
|
|
556
25
|
/**
|
|
557
26
|
* Copy Trading Menu
|
|
558
27
|
*/
|
|
@@ -608,14 +77,14 @@ const copyTradingMenu = async () => {
|
|
|
608
77
|
followers.length === 0 ? 'FOLLOWER ACCOUNT:' : 'ADD ANOTHER FOLLOWER:',
|
|
609
78
|
allAccounts,
|
|
610
79
|
excludeIndices,
|
|
611
|
-
followers.length > 0
|
|
80
|
+
followers.length > 0
|
|
612
81
|
);
|
|
613
82
|
|
|
614
83
|
if (followerIdx === null || followerIdx === -1) {
|
|
615
|
-
if (followers.length === 0) return;
|
|
84
|
+
if (followers.length === 0) return;
|
|
616
85
|
selectingFollowers = false;
|
|
617
86
|
} else if (followerIdx === -2) {
|
|
618
|
-
selectingFollowers = false;
|
|
87
|
+
selectingFollowers = false;
|
|
619
88
|
} else {
|
|
620
89
|
followers.push(allAccounts[followerIdx]);
|
|
621
90
|
excludeIndices.push(followerIdx);
|
|
@@ -725,7 +194,6 @@ const copyTradingMenu = async () => {
|
|
|
725
194
|
const fetchAllAccounts = async (allConns) => {
|
|
726
195
|
const allAccounts = [];
|
|
727
196
|
|
|
728
|
-
// Fetch in parallel
|
|
729
197
|
const promises = allConns.map(async (conn) => {
|
|
730
198
|
try {
|
|
731
199
|
const result = await conn.service.getTradingAccounts();
|
|
@@ -813,7 +281,6 @@ const selectSymbol = async (service) => {
|
|
|
813
281
|
return (a.name || '').localeCompare(b.name || '');
|
|
814
282
|
});
|
|
815
283
|
|
|
816
|
-
// Display contracts (uniform format: NAME - DESCRIPTION)
|
|
817
284
|
const options = contracts.slice(0, 30).map(c => {
|
|
818
285
|
const name = c.name || c.symbol || c.baseSymbol;
|
|
819
286
|
const desc = c.description || '';
|
|
@@ -868,7 +335,7 @@ const launchCopyTrading = async (config) => {
|
|
|
868
335
|
target: dailyTarget,
|
|
869
336
|
risk: maxRisk,
|
|
870
337
|
pnl: 0,
|
|
871
|
-
sessionPnl: 0,
|
|
338
|
+
sessionPnl: 0,
|
|
872
339
|
trades: 0,
|
|
873
340
|
wins: 0,
|
|
874
341
|
losses: 0,
|
|
@@ -878,10 +345,10 @@ const launchCopyTrading = async (config) => {
|
|
|
878
345
|
startTime: Date.now(),
|
|
879
346
|
aiSupervision: false,
|
|
880
347
|
aiMode: null,
|
|
881
|
-
agentCount: 0
|
|
348
|
+
agentCount: 0
|
|
882
349
|
};
|
|
883
350
|
|
|
884
|
-
// Initialize AI Supervisor
|
|
351
|
+
// Initialize AI Supervisor
|
|
885
352
|
if (enableAI) {
|
|
886
353
|
const aiAgents = aiService.getAgents();
|
|
887
354
|
stats.agentCount = aiAgents.length;
|
|
@@ -905,6 +372,7 @@ const launchCopyTrading = async (config) => {
|
|
|
905
372
|
algoLogger.info(ui, 'COPY MODE', `Lead: ${lead.propfirm} -> ${followers.length} follower(s)`);
|
|
906
373
|
|
|
907
374
|
if (stats.aiSupervision) {
|
|
375
|
+
const aiAgents = aiService.getAgents();
|
|
908
376
|
algoLogger.info(ui, 'AI SUPERVISION', `${aiAgents.length} agent(s) - LEARNING ACTIVE`);
|
|
909
377
|
}
|
|
910
378
|
|
|
@@ -959,7 +427,7 @@ const launchCopyTrading = async (config) => {
|
|
|
959
427
|
? `${minutes}m ${seconds}s`
|
|
960
428
|
: `${seconds}s`;
|
|
961
429
|
|
|
962
|
-
// Close log file
|
|
430
|
+
// Close log file
|
|
963
431
|
try { ui.closeLog(stats); } catch {}
|
|
964
432
|
|
|
965
433
|
ui.cleanup();
|