hedgequantx 2.3.12 → 2.3.14
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 +0 -0
- package/dist/lib/api2.jsc +0 -0
- package/dist/lib/core.jsc +0 -0
- package/dist/lib/core2.jsc +0 -0
- package/dist/lib/data.jsc +0 -0
- package/dist/lib/data2.jsc +0 -0
- package/dist/lib/decoder.jsc +0 -0
- package/dist/lib/m/mod1.jsc +0 -0
- package/dist/lib/m/mod2.jsc +0 -0
- package/dist/lib/n/r1.jsc +0 -0
- package/dist/lib/n/r2.jsc +0 -0
- package/dist/lib/n/r3.jsc +0 -0
- package/dist/lib/n/r4.jsc +0 -0
- package/dist/lib/n/r5.jsc +0 -0
- package/dist/lib/n/r6.jsc +0 -0
- package/dist/lib/n/r7.jsc +0 -0
- package/dist/lib/o/util1.jsc +0 -0
- package/dist/lib/o/util2.jsc +0 -0
- package/package.json +1 -1
- package/src/pages/algo/copy-trading.js +86 -119
- package/src/pages/algo/one-account.js +94 -61
- package/src/pages/algo/ui.js +10 -2
package/dist/lib/api.jsc
CHANGED
|
Binary file
|
package/dist/lib/api2.jsc
CHANGED
|
Binary file
|
package/dist/lib/core.jsc
CHANGED
|
Binary file
|
package/dist/lib/core2.jsc
CHANGED
|
Binary file
|
package/dist/lib/data.jsc
CHANGED
|
Binary file
|
package/dist/lib/data2.jsc
CHANGED
|
Binary file
|
package/dist/lib/decoder.jsc
CHANGED
|
Binary file
|
package/dist/lib/m/mod1.jsc
CHANGED
|
Binary file
|
package/dist/lib/m/mod2.jsc
CHANGED
|
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
|
package/dist/lib/o/util1.jsc
CHANGED
|
Binary file
|
package/dist/lib/o/util2.jsc
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -8,7 +8,6 @@ const ora = require('ora');
|
|
|
8
8
|
const readline = require('readline');
|
|
9
9
|
|
|
10
10
|
const { connections } = require('../../services');
|
|
11
|
-
const { HQXServerService } = require('../../services/hqx-server');
|
|
12
11
|
const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
13
12
|
const { logger, prompts } = require('../../utils');
|
|
14
13
|
const { checkMarketHours } = require('../../services/projectx/market');
|
|
@@ -283,48 +282,100 @@ const launchCopyTrading = async (config) => {
|
|
|
283
282
|
losses: 0,
|
|
284
283
|
latency: 0,
|
|
285
284
|
connected: false,
|
|
285
|
+
platform: lead.account.platform || 'ProjectX',
|
|
286
286
|
};
|
|
287
287
|
|
|
288
288
|
let running = true;
|
|
289
289
|
let stopReason = null;
|
|
290
|
+
|
|
291
|
+
// Measure API latency (CLI <-> API)
|
|
292
|
+
const measureLatency = async () => {
|
|
293
|
+
try {
|
|
294
|
+
const start = Date.now();
|
|
295
|
+
await lead.service.getPositions(lead.account.accountId);
|
|
296
|
+
stats.latency = Date.now() - start;
|
|
297
|
+
} catch (e) {
|
|
298
|
+
stats.latency = 0;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
290
301
|
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
302
|
+
// Local copy trading - no external server needed
|
|
303
|
+
ui.addLog('info', `Starting copy trading on ${stats.platform}...`);
|
|
304
|
+
ui.addLog('info', `Lead: ${stats.leadName} -> Follower: ${stats.followerName}`);
|
|
305
|
+
ui.addLog('info', `Symbol: ${stats.symbol} | Target: $${dailyTarget} | Risk: $${maxRisk}`);
|
|
306
|
+
stats.connected = true;
|
|
307
|
+
|
|
308
|
+
// Track lead positions and copy to follower
|
|
309
|
+
let lastLeadPositions = [];
|
|
310
|
+
|
|
311
|
+
const pollAndCopy = async () => {
|
|
312
|
+
try {
|
|
313
|
+
// Get lead positions
|
|
314
|
+
const leadResult = await lead.service.getPositions(lead.account.accountId);
|
|
315
|
+
if (!leadResult.success) return;
|
|
316
|
+
|
|
317
|
+
const currentPositions = leadResult.positions || [];
|
|
318
|
+
|
|
319
|
+
// Detect new positions on lead
|
|
320
|
+
for (const pos of currentPositions) {
|
|
321
|
+
const existing = lastLeadPositions.find(p => p.contractId === pos.contractId);
|
|
322
|
+
if (!existing && pos.quantity !== 0) {
|
|
323
|
+
// New position opened - copy to follower
|
|
324
|
+
ui.addLog('trade', `Lead opened: ${pos.quantity > 0 ? 'LONG' : 'SHORT'} ${Math.abs(pos.quantity)}x ${pos.symbol || pos.contractId}`);
|
|
325
|
+
// TODO: Place order on follower account
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Detect closed positions
|
|
330
|
+
for (const oldPos of lastLeadPositions) {
|
|
331
|
+
const stillOpen = currentPositions.find(p => p.contractId === oldPos.contractId);
|
|
332
|
+
if (!stillOpen || stillOpen.quantity === 0) {
|
|
333
|
+
ui.addLog('info', `Lead closed: ${oldPos.symbol || oldPos.contractId}`);
|
|
334
|
+
// TODO: Close position on follower account
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
lastLeadPositions = currentPositions;
|
|
339
|
+
|
|
340
|
+
// Update P&L from lead
|
|
341
|
+
const leadPnL = currentPositions.reduce((sum, p) => sum + (p.profitAndLoss || 0), 0);
|
|
342
|
+
if (leadPnL !== stats.pnl) {
|
|
343
|
+
const diff = leadPnL - stats.pnl;
|
|
344
|
+
if (Math.abs(diff) > 0.01 && stats.pnl !== 0) {
|
|
345
|
+
stats.trades++;
|
|
346
|
+
if (diff >= 0) stats.wins++;
|
|
347
|
+
else stats.losses++;
|
|
348
|
+
}
|
|
349
|
+
stats.pnl = leadPnL;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Check target/risk limits
|
|
353
|
+
if (stats.pnl >= dailyTarget) {
|
|
354
|
+
stopReason = 'target';
|
|
355
|
+
running = false;
|
|
356
|
+
ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
|
|
357
|
+
} else if (stats.pnl <= -maxRisk) {
|
|
358
|
+
stopReason = 'risk';
|
|
359
|
+
running = false;
|
|
360
|
+
ui.addLog('error', `MAX RISK HIT! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
361
|
+
}
|
|
362
|
+
} catch (e) {
|
|
363
|
+
// Silent fail - will retry
|
|
364
|
+
}
|
|
365
|
+
};
|
|
323
366
|
|
|
324
367
|
// UI refresh loop
|
|
325
368
|
const refreshInterval = setInterval(() => {
|
|
326
369
|
if (running) ui.render(stats);
|
|
327
370
|
}, 250);
|
|
371
|
+
|
|
372
|
+
// Measure API latency every 5 seconds
|
|
373
|
+
measureLatency(); // Initial measurement
|
|
374
|
+
const latencyInterval = setInterval(() => { if (running) measureLatency(); }, 5000);
|
|
375
|
+
|
|
376
|
+
// Poll and copy every 2 seconds
|
|
377
|
+
pollAndCopy(); // Initial poll
|
|
378
|
+
const copyInterval = setInterval(() => { if (running) pollAndCopy(); }, 2000);
|
|
328
379
|
|
|
329
380
|
// Keyboard handling
|
|
330
381
|
const cleanupKeys = setupKeyboardHandler(() => {
|
|
@@ -344,11 +395,9 @@ const launchCopyTrading = async (config) => {
|
|
|
344
395
|
|
|
345
396
|
// Cleanup
|
|
346
397
|
clearInterval(refreshInterval);
|
|
398
|
+
clearInterval(latencyInterval);
|
|
399
|
+
clearInterval(copyInterval);
|
|
347
400
|
if (cleanupKeys) cleanupKeys();
|
|
348
|
-
if (stats.connected) {
|
|
349
|
-
hqx.stopAlgo();
|
|
350
|
-
hqx.disconnect();
|
|
351
|
-
}
|
|
352
401
|
ui.cleanup();
|
|
353
402
|
|
|
354
403
|
// Show summary
|
|
@@ -356,88 +405,6 @@ const launchCopyTrading = async (config) => {
|
|
|
356
405
|
await prompts.waitForEnter();
|
|
357
406
|
};
|
|
358
407
|
|
|
359
|
-
/**
|
|
360
|
-
* Setup HQX event handlers
|
|
361
|
-
*/
|
|
362
|
-
const setupEventHandlers = (hqx, ui, stats, lead, follower, showNames, stop, setReason, dailyTarget, maxRisk) => {
|
|
363
|
-
hqx.on('latency', (d) => {
|
|
364
|
-
stats.latency = d.latency || 0;
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
hqx.on('log', (d) => {
|
|
368
|
-
let msg = d.message;
|
|
369
|
-
if (!showNames) {
|
|
370
|
-
if (lead.account.accountName) {
|
|
371
|
-
msg = msg.replace(new RegExp(lead.account.accountName, 'gi'), 'Lead *****');
|
|
372
|
-
}
|
|
373
|
-
if (follower.account.accountName) {
|
|
374
|
-
msg = msg.replace(new RegExp(follower.account.accountName, 'gi'), 'Follower *****');
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
ui.addLog(d.type || 'info', msg);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
hqx.on('trade', (d) => {
|
|
381
|
-
stats.trades++;
|
|
382
|
-
stats.pnl += d.pnl || 0;
|
|
383
|
-
d.pnl >= 0 ? stats.wins++ : stats.losses++;
|
|
384
|
-
ui.addLog(d.pnl >= 0 ? 'trade' : 'loss', `${d.pnl >= 0 ? '+' : ''}$${d.pnl.toFixed(2)}`);
|
|
385
|
-
|
|
386
|
-
if (stats.pnl >= dailyTarget) {
|
|
387
|
-
setReason('target');
|
|
388
|
-
stop();
|
|
389
|
-
ui.addLog('success', `TARGET! +$${stats.pnl.toFixed(2)}`);
|
|
390
|
-
hqx.stopAlgo();
|
|
391
|
-
} else if (stats.pnl <= -maxRisk) {
|
|
392
|
-
setReason('risk');
|
|
393
|
-
stop();
|
|
394
|
-
ui.addLog('error', `MAX RISK! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
395
|
-
hqx.stopAlgo();
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
hqx.on('copy', (d) => {
|
|
400
|
-
ui.addLog('trade', `COPIED: ${d.side} ${d.quantity}x`);
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
hqx.on('error', (d) => {
|
|
404
|
-
ui.addLog('error', d.message);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
hqx.on('disconnected', () => {
|
|
408
|
-
stats.connected = false;
|
|
409
|
-
});
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Start copy trading on HQX server
|
|
414
|
-
*/
|
|
415
|
-
const startCopyTradingOnServer = (hqx, lead, follower, dailyTarget, maxRisk, ui) => {
|
|
416
|
-
ui.addLog('info', 'Starting Copy Trading...');
|
|
417
|
-
|
|
418
|
-
const leadCreds = lead.service.getRithmicCredentials?.() || null;
|
|
419
|
-
const followerCreds = follower.service.getRithmicCredentials?.() || null;
|
|
420
|
-
|
|
421
|
-
hqx.startCopyTrading({
|
|
422
|
-
leadAccountId: lead.account.accountId,
|
|
423
|
-
leadContractId: lead.symbol.id || lead.symbol.contractId,
|
|
424
|
-
leadSymbol: lead.symbol.symbol || lead.symbol.name,
|
|
425
|
-
leadContracts: lead.contracts,
|
|
426
|
-
leadPropfirm: lead.propfirm,
|
|
427
|
-
leadToken: lead.service.getToken?.() || null,
|
|
428
|
-
leadRithmicCredentials: leadCreds,
|
|
429
|
-
followerAccountId: follower.account.accountId,
|
|
430
|
-
followerContractId: follower.symbol.id || follower.symbol.contractId,
|
|
431
|
-
followerSymbol: follower.symbol.symbol || follower.symbol.name,
|
|
432
|
-
followerContracts: follower.contracts,
|
|
433
|
-
followerPropfirm: follower.propfirm,
|
|
434
|
-
followerToken: follower.service.getToken?.() || null,
|
|
435
|
-
followerRithmicCredentials: followerCreds,
|
|
436
|
-
dailyTarget,
|
|
437
|
-
maxRisk,
|
|
438
|
-
});
|
|
439
|
-
};
|
|
440
|
-
|
|
441
408
|
/**
|
|
442
409
|
* Setup keyboard handler
|
|
443
410
|
* @param {Function} onStop - Stop callback
|
|
@@ -7,7 +7,6 @@ const ora = require('ora');
|
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
|
|
9
9
|
const { connections } = require('../../services');
|
|
10
|
-
const { HQXServerService } = require('../../services/hqx-server');
|
|
11
10
|
const { AlgoUI, renderSessionSummary } = require('./ui');
|
|
12
11
|
const { prompts } = require('../../utils');
|
|
13
12
|
const { checkMarketHours } = require('../../services/projectx/market');
|
|
@@ -79,7 +78,7 @@ const oneAccountMenu = async (service) => {
|
|
|
79
78
|
};
|
|
80
79
|
|
|
81
80
|
/**
|
|
82
|
-
* Symbol selection -
|
|
81
|
+
* Symbol selection - sorted with popular indices first
|
|
83
82
|
*/
|
|
84
83
|
const selectSymbol = async (service, account) => {
|
|
85
84
|
const spinner = ora({ text: 'Loading symbols...', color: 'yellow' }).start();
|
|
@@ -90,10 +89,32 @@ const selectSymbol = async (service, account) => {
|
|
|
90
89
|
return null;
|
|
91
90
|
}
|
|
92
91
|
|
|
93
|
-
|
|
92
|
+
let contracts = contractsResult.contracts;
|
|
93
|
+
|
|
94
|
+
// Sort: Popular indices first (ES, NQ, MES, MNQ, RTY, YM, etc.)
|
|
95
|
+
const popularPrefixes = ['ES', 'NQ', 'MES', 'MNQ', 'M2K', 'RTY', 'YM', 'MYM', 'NKD', 'GC', 'SI', 'CL'];
|
|
96
|
+
|
|
97
|
+
contracts.sort((a, b) => {
|
|
98
|
+
const nameA = a.name || '';
|
|
99
|
+
const nameB = b.name || '';
|
|
100
|
+
|
|
101
|
+
// Check if names start with popular prefixes
|
|
102
|
+
const idxA = popularPrefixes.findIndex(p => nameA.startsWith(p));
|
|
103
|
+
const idxB = popularPrefixes.findIndex(p => nameB.startsWith(p));
|
|
104
|
+
|
|
105
|
+
// Both are popular - sort by popularity order
|
|
106
|
+
if (idxA !== -1 && idxB !== -1) return idxA - idxB;
|
|
107
|
+
// Only A is popular - A first
|
|
108
|
+
if (idxA !== -1) return -1;
|
|
109
|
+
// Only B is popular - B first
|
|
110
|
+
if (idxB !== -1) return 1;
|
|
111
|
+
// Neither - alphabetical
|
|
112
|
+
return nameA.localeCompare(nameB);
|
|
113
|
+
});
|
|
114
|
+
|
|
94
115
|
spinner.succeed(`Found ${contracts.length} contracts`);
|
|
95
116
|
|
|
96
|
-
// Display
|
|
117
|
+
// Display sorted contracts from API
|
|
97
118
|
const options = contracts.map(c => ({
|
|
98
119
|
label: `${c.name} - ${c.description}`,
|
|
99
120
|
value: c
|
|
@@ -148,74 +169,85 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
148
169
|
accountName, symbol: symbolName, contracts,
|
|
149
170
|
target: dailyTarget, risk: maxRisk,
|
|
150
171
|
propfirm: account.propfirm || 'Unknown',
|
|
172
|
+
platform: account.platform || 'ProjectX',
|
|
151
173
|
pnl: 0, trades: 0, wins: 0, losses: 0,
|
|
152
174
|
latency: 0, connected: false,
|
|
153
175
|
startTime: Date.now() // Track start time for duration
|
|
154
176
|
};
|
|
155
177
|
|
|
178
|
+
// Measure API latency (CLI <-> API)
|
|
179
|
+
const measureLatency = async () => {
|
|
180
|
+
try {
|
|
181
|
+
const start = Date.now();
|
|
182
|
+
await service.getPositions(account.accountId);
|
|
183
|
+
stats.latency = Date.now() - start;
|
|
184
|
+
} catch (e) {
|
|
185
|
+
stats.latency = 0;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
156
189
|
let running = true;
|
|
157
190
|
let stopReason = null;
|
|
158
191
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
192
|
+
// Local algo - no external server needed
|
|
193
|
+
ui.addLog('info', `Starting algo on ${stats.platform}...`);
|
|
194
|
+
ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
|
|
195
|
+
ui.addLog('info', `Target: $${dailyTarget} | Risk: $${maxRisk}`);
|
|
196
|
+
stats.connected = true;
|
|
197
|
+
|
|
198
|
+
// Poll P&L from API every 2 seconds
|
|
199
|
+
const pollPnL = async () => {
|
|
200
|
+
try {
|
|
201
|
+
// Get positions to check P&L
|
|
202
|
+
const posResult = await service.getPositions(account.accountId);
|
|
203
|
+
if (posResult.success && posResult.positions) {
|
|
204
|
+
// Find position for our symbol
|
|
205
|
+
const pos = posResult.positions.find(p =>
|
|
206
|
+
(p.contractId || p.symbol || '').includes(contract.name || contract.symbol)
|
|
207
|
+
);
|
|
208
|
+
if (pos && pos.profitAndLoss !== undefined) {
|
|
209
|
+
const prevPnL = stats.pnl;
|
|
210
|
+
stats.pnl = pos.profitAndLoss;
|
|
211
|
+
|
|
212
|
+
// Detect trade completion
|
|
213
|
+
if (Math.abs(stats.pnl - prevPnL) > 0.01 && prevPnL !== 0) {
|
|
214
|
+
const tradePnL = stats.pnl - prevPnL;
|
|
215
|
+
stats.trades++;
|
|
216
|
+
if (tradePnL >= 0) {
|
|
217
|
+
stats.wins++;
|
|
218
|
+
ui.addLog('trade', `Trade closed: +$${tradePnL.toFixed(2)}`);
|
|
219
|
+
} else {
|
|
220
|
+
stats.losses++;
|
|
221
|
+
ui.addLog('loss', `Trade closed: -$${Math.abs(tradePnL).toFixed(2)}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check target/risk limits
|
|
228
|
+
if (stats.pnl >= dailyTarget) {
|
|
229
|
+
stopReason = 'target';
|
|
230
|
+
running = false;
|
|
231
|
+
ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
|
|
232
|
+
} else if (stats.pnl <= -maxRisk) {
|
|
233
|
+
stopReason = 'risk';
|
|
234
|
+
running = false;
|
|
235
|
+
ui.addLog('error', `MAX RISK HIT! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
// Silent fail - will retry
|
|
188
239
|
}
|
|
189
|
-
|
|
190
|
-
stats.wins = d.wins;
|
|
191
|
-
stats.losses = d.losses;
|
|
192
|
-
});
|
|
240
|
+
};
|
|
193
241
|
|
|
194
|
-
|
|
195
|
-
hqx.on('disconnected', () => { stats.connected = false; ui.addLog('warning', 'Disconnected'); });
|
|
242
|
+
const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
|
|
196
243
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// Get Rithmic credentials from the account's service
|
|
202
|
-
let rithmicCreds = null;
|
|
203
|
-
if (service && service.getRithmicCredentials) {
|
|
204
|
-
rithmicCreds = service.getRithmicCredentials();
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
hqx.startAlgo({
|
|
208
|
-
accountId: account.accountId,
|
|
209
|
-
contractId: contract.id || contract.contractId,
|
|
210
|
-
symbol: contract.symbol || contract.name,
|
|
211
|
-
contracts, dailyTarget, maxRisk,
|
|
212
|
-
propfirm: account.propfirm || 'topstep',
|
|
213
|
-
propfirmToken: service.getToken?.() || null,
|
|
214
|
-
rithmicCredentials: rithmicCreds
|
|
215
|
-
});
|
|
216
|
-
}
|
|
244
|
+
// Measure API latency every 5 seconds
|
|
245
|
+
measureLatency(); // Initial measurement
|
|
246
|
+
const latencyInterval = setInterval(() => { if (running) measureLatency(); }, 5000);
|
|
217
247
|
|
|
218
|
-
|
|
248
|
+
// Poll P&L from API every 2 seconds
|
|
249
|
+
pollPnL(); // Initial poll
|
|
250
|
+
const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
|
|
219
251
|
|
|
220
252
|
// Keyboard
|
|
221
253
|
const setupKeyHandler = () => {
|
|
@@ -243,8 +275,9 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
243
275
|
});
|
|
244
276
|
|
|
245
277
|
clearInterval(refreshInterval);
|
|
278
|
+
clearInterval(latencyInterval);
|
|
279
|
+
clearInterval(pnlInterval);
|
|
246
280
|
if (cleanupKeys) cleanupKeys();
|
|
247
|
-
if (stats.connected) { hqx.stopAlgo(); hqx.disconnect(); }
|
|
248
281
|
ui.cleanup();
|
|
249
282
|
|
|
250
283
|
// Calculate duration
|
package/src/pages/algo/ui.js
CHANGED
|
@@ -189,12 +189,20 @@ class AlgoUI {
|
|
|
189
189
|
|
|
190
190
|
this._line(chalk.cyan(GM));
|
|
191
191
|
|
|
192
|
-
// Row 4: Trades |
|
|
192
|
+
// Row 4: Trades | Latency (API response time)
|
|
193
193
|
const r4c1t = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
|
|
194
194
|
const r4c1p = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
|
|
195
|
-
const r4c2 = buildCell('
|
|
195
|
+
const r4c2 = buildCell('Latency', `${stats.latency || 0}ms`, latencyColor, colR);
|
|
196
196
|
row(r4c1t + pad(colL - r4c1p.length), r4c2.padded);
|
|
197
197
|
|
|
198
|
+
this._line(chalk.cyan(GM));
|
|
199
|
+
|
|
200
|
+
// Row 5: Platform | Propfirm
|
|
201
|
+
const platform = stats.platform || 'ProjectX';
|
|
202
|
+
const r5c1 = buildCell('Platform', platform, chalk.white, colL);
|
|
203
|
+
const r5c2 = buildCell('Propfirm', stats.propfirm || 'N/A', chalk.cyan, colR);
|
|
204
|
+
row(r5c1.padded, r5c2.padded);
|
|
205
|
+
|
|
198
206
|
this._line(chalk.cyan(GB));
|
|
199
207
|
}
|
|
200
208
|
|