hedgequantx 2.3.11 → 2.3.13
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 +69 -58
- package/src/pages/algo/ui.js +14 -18
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');
|
|
@@ -148,74 +147,85 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
148
147
|
accountName, symbol: symbolName, contracts,
|
|
149
148
|
target: dailyTarget, risk: maxRisk,
|
|
150
149
|
propfirm: account.propfirm || 'Unknown',
|
|
150
|
+
platform: account.platform || 'ProjectX',
|
|
151
151
|
pnl: 0, trades: 0, wins: 0, losses: 0,
|
|
152
152
|
latency: 0, connected: false,
|
|
153
153
|
startTime: Date.now() // Track start time for duration
|
|
154
154
|
};
|
|
155
155
|
|
|
156
|
+
// Measure API latency (CLI <-> API)
|
|
157
|
+
const measureLatency = async () => {
|
|
158
|
+
try {
|
|
159
|
+
const start = Date.now();
|
|
160
|
+
await service.getPositions(account.accountId);
|
|
161
|
+
stats.latency = Date.now() - start;
|
|
162
|
+
} catch (e) {
|
|
163
|
+
stats.latency = 0;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
156
167
|
let running = true;
|
|
157
168
|
let stopReason = null;
|
|
158
169
|
|
|
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
|
-
|
|
170
|
+
// Local algo - no external server needed
|
|
171
|
+
ui.addLog('info', `Starting algo on ${stats.platform}...`);
|
|
172
|
+
ui.addLog('info', `Symbol: ${symbolName} | Qty: ${contracts}`);
|
|
173
|
+
ui.addLog('info', `Target: $${dailyTarget} | Risk: $${maxRisk}`);
|
|
174
|
+
stats.connected = true;
|
|
175
|
+
|
|
176
|
+
// Poll P&L from API every 2 seconds
|
|
177
|
+
const pollPnL = async () => {
|
|
178
|
+
try {
|
|
179
|
+
// Get positions to check P&L
|
|
180
|
+
const posResult = await service.getPositions(account.accountId);
|
|
181
|
+
if (posResult.success && posResult.positions) {
|
|
182
|
+
// Find position for our symbol
|
|
183
|
+
const pos = posResult.positions.find(p =>
|
|
184
|
+
(p.contractId || p.symbol || '').includes(contract.name || contract.symbol)
|
|
185
|
+
);
|
|
186
|
+
if (pos && pos.profitAndLoss !== undefined) {
|
|
187
|
+
const prevPnL = stats.pnl;
|
|
188
|
+
stats.pnl = pos.profitAndLoss;
|
|
189
|
+
|
|
190
|
+
// Detect trade completion
|
|
191
|
+
if (Math.abs(stats.pnl - prevPnL) > 0.01 && prevPnL !== 0) {
|
|
192
|
+
const tradePnL = stats.pnl - prevPnL;
|
|
193
|
+
stats.trades++;
|
|
194
|
+
if (tradePnL >= 0) {
|
|
195
|
+
stats.wins++;
|
|
196
|
+
ui.addLog('trade', `Trade closed: +$${tradePnL.toFixed(2)}`);
|
|
197
|
+
} else {
|
|
198
|
+
stats.losses++;
|
|
199
|
+
ui.addLog('loss', `Trade closed: -$${Math.abs(tradePnL).toFixed(2)}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check target/risk limits
|
|
206
|
+
if (stats.pnl >= dailyTarget) {
|
|
207
|
+
stopReason = 'target';
|
|
208
|
+
running = false;
|
|
209
|
+
ui.addLog('success', `TARGET REACHED! +$${stats.pnl.toFixed(2)}`);
|
|
210
|
+
} else if (stats.pnl <= -maxRisk) {
|
|
211
|
+
stopReason = 'risk';
|
|
212
|
+
running = false;
|
|
213
|
+
ui.addLog('error', `MAX RISK HIT! -$${Math.abs(stats.pnl).toFixed(2)}`);
|
|
214
|
+
}
|
|
215
|
+
} catch (e) {
|
|
216
|
+
// Silent fail - will retry
|
|
188
217
|
}
|
|
189
|
-
|
|
190
|
-
stats.wins = d.wins;
|
|
191
|
-
stats.losses = d.losses;
|
|
192
|
-
});
|
|
218
|
+
};
|
|
193
219
|
|
|
194
|
-
|
|
195
|
-
hqx.on('disconnected', () => { stats.connected = false; ui.addLog('warning', 'Disconnected'); });
|
|
220
|
+
const refreshInterval = setInterval(() => { if (running) ui.render(stats); }, 250);
|
|
196
221
|
|
|
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
|
-
}
|
|
222
|
+
// Measure API latency every 5 seconds
|
|
223
|
+
measureLatency(); // Initial measurement
|
|
224
|
+
const latencyInterval = setInterval(() => { if (running) measureLatency(); }, 5000);
|
|
217
225
|
|
|
218
|
-
|
|
226
|
+
// Poll P&L from API every 2 seconds
|
|
227
|
+
pollPnL(); // Initial poll
|
|
228
|
+
const pnlInterval = setInterval(() => { if (running) pollPnL(); }, 2000);
|
|
219
229
|
|
|
220
230
|
// Keyboard
|
|
221
231
|
const setupKeyHandler = () => {
|
|
@@ -243,8 +253,9 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
243
253
|
});
|
|
244
254
|
|
|
245
255
|
clearInterval(refreshInterval);
|
|
256
|
+
clearInterval(latencyInterval);
|
|
257
|
+
clearInterval(pnlInterval);
|
|
246
258
|
if (cleanupKeys) cleanupKeys();
|
|
247
|
-
if (stats.connected) { hqx.stopAlgo(); hqx.disconnect(); }
|
|
248
259
|
ui.cleanup();
|
|
249
260
|
|
|
250
261
|
// Calculate duration
|
package/src/pages/algo/ui.js
CHANGED
|
@@ -189,16 +189,17 @@ 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
198
|
this._line(chalk.cyan(GM));
|
|
199
199
|
|
|
200
|
-
// Row 5:
|
|
201
|
-
const
|
|
200
|
+
// Row 5: Platform | Propfirm
|
|
201
|
+
const platform = stats.platform || 'ProjectX';
|
|
202
|
+
const r5c1 = buildCell('Platform', platform, chalk.white, colL);
|
|
202
203
|
const r5c2 = buildCell('Propfirm', stats.propfirm || 'N/A', chalk.cyan, colR);
|
|
203
204
|
row(r5c1.padded, r5c2.padded);
|
|
204
205
|
|
|
@@ -254,18 +255,11 @@ class AlgoUI {
|
|
|
254
255
|
|
|
255
256
|
this._line(chalk.cyan(GM));
|
|
256
257
|
|
|
257
|
-
// Row 5: P&L |
|
|
258
|
+
// Row 5: P&L | Trades
|
|
258
259
|
const r5c1 = buildCell('P&L', pnlStr, pnlColor, colL);
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
this._line(chalk.cyan(GM));
|
|
263
|
-
|
|
264
|
-
// Row 6: Trades | Latency
|
|
265
|
-
const r6c1t = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
|
|
266
|
-
const r6c1p = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
|
|
267
|
-
const r6c2 = buildCell('Latency', `${stats.latency || 0}ms`, latencyColor, colR);
|
|
268
|
-
row(r6c1t + pad(colL - r6c1p.length), r6c2.padded);
|
|
260
|
+
const r5c2t = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
|
|
261
|
+
const r5c2p = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
|
|
262
|
+
row(r5c1.padded, r5c2t + pad(colR - r5c2p.length));
|
|
269
263
|
|
|
270
264
|
this._line(chalk.cyan(GB));
|
|
271
265
|
}
|
|
@@ -280,11 +274,13 @@ class AlgoUI {
|
|
|
280
274
|
const timeStr = now.toLocaleTimeString('en-US', { hour12: false });
|
|
281
275
|
const dateStr = now.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
282
276
|
|
|
277
|
+
const leftText = ` EXECUTION LOG ${spinner}`;
|
|
278
|
+
const rightText = `${dateStr} ${timeStr} [X] STOP `;
|
|
279
|
+
const totalLen = leftText.length + rightText.length;
|
|
280
|
+
const space = W - totalLen;
|
|
281
|
+
|
|
283
282
|
const left = ` EXECUTION LOG ${chalk.yellow(spinner)}`;
|
|
284
283
|
const right = `${chalk.gray(dateStr)} ${chalk.white(timeStr)} ${chalk.yellow('[X] STOP')} `;
|
|
285
|
-
const leftPlain = stripAnsi(left);
|
|
286
|
-
const rightPlain = ` ${dateStr} ${timeStr} [X] STOP `;
|
|
287
|
-
const space = W - leftPlain.length - rightPlain.length;
|
|
288
284
|
|
|
289
285
|
this._line(chalk.cyan(BOX.V) + chalk.white.bold(left) + ' '.repeat(Math.max(0, space)) + right + chalk.cyan(BOX.V));
|
|
290
286
|
this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|