hedgequantx 2.6.163 → 2.7.1
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/README.md +15 -88
- package/bin/cli.js +0 -11
- 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.js +1 -1
- 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 +8 -5
- package/src/app.js +40 -162
- package/src/config/constants.js +31 -33
- package/src/config/propfirms.js +13 -217
- package/src/config/settings.js +0 -43
- package/src/lib/api.js +198 -0
- package/src/lib/api2.js +353 -0
- package/src/lib/core.js +539 -0
- package/src/lib/core2.js +341 -0
- package/src/lib/data.js +555 -0
- package/src/lib/data2.js +492 -0
- package/src/lib/decoder.js +599 -0
- package/src/lib/m/s1.js +804 -0
- package/src/lib/m/s2.js +34 -0
- package/src/lib/n/r1.js +454 -0
- package/src/lib/n/r2.js +514 -0
- package/src/lib/n/r3.js +631 -0
- package/src/lib/n/r4.js +401 -0
- package/src/lib/n/r5.js +335 -0
- package/src/lib/n/r6.js +425 -0
- package/src/lib/n/r7.js +530 -0
- package/src/lib/o/l1.js +44 -0
- package/src/lib/o/l2.js +427 -0
- package/src/lib/python-bridge.js +206 -0
- package/src/menus/connect.js +14 -176
- package/src/menus/dashboard.js +65 -110
- package/src/pages/accounts.js +18 -18
- package/src/pages/algo/copy-trading.js +210 -240
- package/src/pages/algo/index.js +41 -104
- package/src/pages/algo/one-account.js +386 -33
- package/src/pages/algo/ui.js +312 -151
- package/src/pages/orders.js +3 -3
- package/src/pages/positions.js +3 -3
- package/src/pages/stats/chart.js +74 -0
- package/src/pages/stats/display.js +228 -0
- package/src/pages/stats/index.js +236 -0
- package/src/pages/stats/metrics.js +213 -0
- package/src/pages/user.js +6 -6
- package/src/services/hqx-server/constants.js +55 -0
- package/src/services/hqx-server/index.js +401 -0
- package/src/services/hqx-server/latency.js +81 -0
- package/src/services/index.js +12 -3
- package/src/services/rithmic/accounts.js +7 -32
- package/src/services/rithmic/connection.js +1 -204
- package/src/services/rithmic/contracts.js +116 -99
- package/src/services/rithmic/handlers.js +21 -196
- package/src/services/rithmic/index.js +63 -120
- package/src/services/rithmic/market.js +31 -0
- package/src/services/rithmic/orders.js +5 -111
- package/src/services/rithmic/protobuf.js +384 -138
- package/src/services/session.js +22 -173
- package/src/ui/box.js +10 -18
- package/src/ui/index.js +1 -3
- package/src/ui/menu.js +1 -1
- package/src/utils/prompts.js +2 -2
- package/dist/lib/m/s1.js +0 -1
- package/src/menus/ai-agent-connect.js +0 -181
- package/src/menus/ai-agent-models.js +0 -219
- package/src/menus/ai-agent-oauth.js +0 -292
- package/src/menus/ai-agent-ui.js +0 -141
- package/src/menus/ai-agent.js +0 -484
- package/src/pages/algo/algo-config.js +0 -195
- package/src/pages/algo/algo-multi.js +0 -801
- package/src/pages/algo/algo-utils.js +0 -58
- package/src/pages/algo/copy-engine.js +0 -449
- package/src/pages/algo/custom-strategy.js +0 -459
- package/src/pages/algo/logger.js +0 -245
- package/src/pages/algo/smart-logs-data.js +0 -218
- package/src/pages/algo/smart-logs.js +0 -387
- package/src/pages/algo/ui-constants.js +0 -144
- package/src/pages/algo/ui-summary.js +0 -184
- package/src/pages/stats-calculations.js +0 -191
- package/src/pages/stats-ui.js +0 -381
- package/src/pages/stats.js +0 -339
- package/src/services/ai/client-analysis.js +0 -194
- package/src/services/ai/client-models.js +0 -333
- package/src/services/ai/client.js +0 -343
- package/src/services/ai/index.js +0 -384
- package/src/services/ai/oauth-anthropic.js +0 -265
- package/src/services/ai/oauth-gemini.js +0 -223
- package/src/services/ai/oauth-iflow.js +0 -269
- package/src/services/ai/oauth-openai.js +0 -233
- package/src/services/ai/oauth-qwen.js +0 -279
- package/src/services/ai/providers/direct-providers.js +0 -323
- package/src/services/ai/providers/index.js +0 -62
- package/src/services/ai/providers/other-providers.js +0 -104
- package/src/services/ai/proxy-install.js +0 -249
- package/src/services/ai/proxy-manager.js +0 -494
- package/src/services/ai/proxy-remote.js +0 -161
- package/src/services/ai/strategy-supervisor.js +0 -1312
- package/src/services/ai/supervisor-data.js +0 -195
- package/src/services/ai/supervisor-optimize.js +0 -215
- package/src/services/ai/supervisor-sync.js +0 -178
- package/src/services/ai/supervisor-utils.js +0 -158
- package/src/services/ai/supervisor.js +0 -484
- package/src/services/ai/validation.js +0 -250
- package/src/services/hqx-server-events.js +0 -110
- package/src/services/hqx-server-handlers.js +0 -217
- package/src/services/hqx-server-latency.js +0 -136
- package/src/services/hqx-server.js +0 -403
- package/src/services/position-constants.js +0 -28
- package/src/services/position-exit-logic.js +0 -174
- package/src/services/position-manager.js +0 -438
- package/src/services/position-momentum.js +0 -206
- package/src/services/projectx/accounts.js +0 -142
- package/src/services/projectx/index.js +0 -443
- package/src/services/projectx/market.js +0 -172
- package/src/services/projectx/stats.js +0 -110
- package/src/services/projectx/trading.js +0 -180
- package/src/services/rithmic/latency-tracker.js +0 -182
- package/src/services/rithmic/market-data-decoders.js +0 -229
- package/src/services/rithmic/market-data.js +0 -272
- package/src/services/rithmic/orders-fast.js +0 -246
- package/src/services/rithmic/proto-decoders.js +0 -403
- package/src/services/rithmic/specs.js +0 -146
- package/src/services/rithmic/trade-history.js +0 -254
- package/src/services/session-history.js +0 -475
- package/src/services/strategy/hft-signal-calc.js +0 -147
- package/src/services/strategy/hft-tick.js +0 -407
- package/src/services/strategy/recovery-math.js +0 -402
- package/src/services/tradovate/constants.js +0 -109
- package/src/services/tradovate/index.js +0 -392
- package/src/services/tradovate/market.js +0 -47
- package/src/services/tradovate/orders.js +0 -145
- package/src/services/tradovate/websocket.js +0 -97
package/src/pages/algo/ui.js
CHANGED
|
@@ -4,12 +4,86 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const chalk = require('chalk');
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const os = require('os');
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
const
|
|
8
|
+
// Box drawing characters
|
|
9
|
+
const BOX = {
|
|
10
|
+
TOP: '\u2554', BOT: '\u255A', V: '\u2551', H: '\u2550',
|
|
11
|
+
TR: '\u2557', BR: '\u255D', ML: '\u2560', MR: '\u2563',
|
|
12
|
+
TM: '\u2564', BM: '\u2567', MM: '\u256A', VS: '\u2502'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Spinner characters
|
|
16
|
+
const SPINNER = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F'];
|
|
17
|
+
|
|
18
|
+
// Log type colors - HF grade
|
|
19
|
+
const LOG_COLORS = {
|
|
20
|
+
// Executions
|
|
21
|
+
fill_buy: chalk.green.bold,
|
|
22
|
+
fill_sell: chalk.red.bold,
|
|
23
|
+
fill_win: chalk.green.bold,
|
|
24
|
+
fill_loss: chalk.red.bold,
|
|
25
|
+
// Status
|
|
26
|
+
connected: chalk.green,
|
|
27
|
+
ready: chalk.cyan,
|
|
28
|
+
// Errors
|
|
29
|
+
error: chalk.red.bold,
|
|
30
|
+
reject: chalk.red,
|
|
31
|
+
// Info
|
|
32
|
+
info: chalk.gray,
|
|
33
|
+
system: chalk.blue
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Log type icons - compact HF style
|
|
37
|
+
const LOG_ICONS = {
|
|
38
|
+
fill_buy: 'BUY ',
|
|
39
|
+
fill_sell: 'SELL ',
|
|
40
|
+
fill_win: 'WIN ',
|
|
41
|
+
fill_loss: 'LOSS ',
|
|
42
|
+
connected: 'CONN ',
|
|
43
|
+
ready: 'READY',
|
|
44
|
+
error: 'ERR ',
|
|
45
|
+
reject: 'REJ ',
|
|
46
|
+
info: 'INFO ',
|
|
47
|
+
system: 'SYS '
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Strip ANSI codes from string
|
|
52
|
+
*/
|
|
53
|
+
const stripAnsi = (str) => str.replace(/\x1B\[[0-9;]*m/g, '');
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Center text in width
|
|
57
|
+
*/
|
|
58
|
+
const center = (text, width) => {
|
|
59
|
+
const pad = Math.floor((width - text.length) / 2);
|
|
60
|
+
return ' '.repeat(pad) + text + ' '.repeat(width - pad - text.length);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fit text to exact width (truncate or pad)
|
|
65
|
+
*/
|
|
66
|
+
const fitToWidth = (text, width) => {
|
|
67
|
+
const plain = stripAnsi(text);
|
|
68
|
+
if (plain.length > width) {
|
|
69
|
+
let count = 0, cut = 0;
|
|
70
|
+
for (let i = 0; i < text.length && count < width - 3; i++) {
|
|
71
|
+
if (text[i] === '\x1B') { while (i < text.length && text[i] !== 'm') i++; }
|
|
72
|
+
else { count++; cut = i + 1; }
|
|
73
|
+
}
|
|
74
|
+
return text.substring(0, cut) + '...';
|
|
75
|
+
}
|
|
76
|
+
return text + ' '.repeat(width - plain.length);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build a labeled cell for grid
|
|
81
|
+
*/
|
|
82
|
+
const buildCell = (label, value, color, width) => {
|
|
83
|
+
const text = ` ${label}: ${color(value)}`;
|
|
84
|
+
const plain = ` ${label}: ${value}`;
|
|
85
|
+
return { text, plain, padded: text + ' '.repeat(Math.max(0, width - plain.length)) };
|
|
86
|
+
};
|
|
13
87
|
|
|
14
88
|
/**
|
|
15
89
|
* Create AlgoUI renderer
|
|
@@ -17,198 +91,226 @@ const { renderSessionSummary, renderMultiSymbolSummary } = require('./ui-summary
|
|
|
17
91
|
class AlgoUI {
|
|
18
92
|
constructor(config) {
|
|
19
93
|
this.config = config;
|
|
20
|
-
this.W = 96;
|
|
94
|
+
this.W = 96; // Fixed width
|
|
21
95
|
this.logs = [];
|
|
22
|
-
this.maxLogs =
|
|
96
|
+
this.maxLogs = 45; // Max visible logs
|
|
23
97
|
this.spinnerFrame = 0;
|
|
24
98
|
this.firstDraw = true;
|
|
25
99
|
this.isDrawing = false;
|
|
26
|
-
this.
|
|
27
|
-
this.lastOutput = '';
|
|
28
|
-
this.lastStatsHash = '';
|
|
29
|
-
this.lastLogsHash = '';
|
|
30
|
-
this.lastSpinnerUpdate = 0;
|
|
31
|
-
this.logFile = null;
|
|
32
|
-
this.logStream = null;
|
|
33
|
-
this._initLogFile();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
_initLogFile() {
|
|
37
|
-
try {
|
|
38
|
-
const logsDir = path.join(os.homedir(), '.hqx', 'logs');
|
|
39
|
-
if (!fs.existsSync(logsDir)) {
|
|
40
|
-
fs.mkdirSync(logsDir, { recursive: true });
|
|
41
|
-
}
|
|
42
|
-
const now = new Date();
|
|
43
|
-
const dateStr = now.toISOString().split('T')[0];
|
|
44
|
-
const timeStr = now.toTimeString().split(' ')[0].replace(/:/g, '-');
|
|
45
|
-
const mode = this.config.mode || 'algo';
|
|
46
|
-
this.logFile = path.join(logsDir, `session_${mode}_${dateStr}_${timeStr}.log`);
|
|
47
|
-
this.logStream = fs.createWriteStream(this.logFile, { flags: 'a' });
|
|
48
|
-
this.logStream.write(`\n${'='.repeat(80)}\n`);
|
|
49
|
-
this.logStream.write(`HQX ALGO SESSION LOG\n`);
|
|
50
|
-
this.logStream.write(`Mode: ${this.config.mode || 'unknown'}\n`);
|
|
51
|
-
this.logStream.write(`Started: ${now.toISOString()}\n`);
|
|
52
|
-
this.logStream.write(`${'='.repeat(80)}\n\n`);
|
|
53
|
-
} catch (e) {
|
|
54
|
-
this.logStream = null;
|
|
55
|
-
}
|
|
100
|
+
this.buffer = '';
|
|
56
101
|
}
|
|
57
102
|
|
|
58
103
|
addLog(type, message) {
|
|
59
104
|
const timestamp = new Date().toLocaleTimeString();
|
|
60
105
|
this.logs.push({ timestamp, type, message });
|
|
61
106
|
if (this.logs.length > this.maxLogs) this.logs.shift();
|
|
62
|
-
if (this.logStream) {
|
|
63
|
-
const isoTime = new Date().toISOString();
|
|
64
|
-
const logLine = `[${isoTime}] [${type.toUpperCase().padEnd(8)}] ${message}\n`;
|
|
65
|
-
this.logStream.write(logLine);
|
|
66
|
-
}
|
|
67
107
|
}
|
|
68
108
|
|
|
69
|
-
_line(text) {
|
|
109
|
+
_line(text) {
|
|
110
|
+
this.buffer += text + '\x1B[K\n';
|
|
111
|
+
}
|
|
70
112
|
|
|
71
113
|
_drawHeader() {
|
|
72
114
|
const { W } = this;
|
|
73
115
|
const version = require('../../../package.json').version;
|
|
116
|
+
|
|
117
|
+
// Top border
|
|
74
118
|
this._line(chalk.cyan(BOX.TOP + BOX.H.repeat(W) + BOX.TR));
|
|
119
|
+
|
|
120
|
+
// Logo (compact)
|
|
75
121
|
this._line(chalk.cyan(BOX.V) + chalk.cyan(' ██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗') + chalk.yellow('██╗ ██╗') + ' ' + chalk.cyan(BOX.V));
|
|
76
122
|
this._line(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝') + chalk.yellow('╚██╗██╔╝') + ' ' + chalk.cyan(BOX.V));
|
|
77
123
|
this._line(chalk.cyan(BOX.V) + chalk.cyan(' ███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ') + chalk.yellow(' ╚███╔╝ ') + ' ' + chalk.cyan(BOX.V));
|
|
78
124
|
this._line(chalk.cyan(BOX.V) + chalk.cyan(' ██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ') + chalk.yellow(' ██╔██╗ ') + ' ' + chalk.cyan(BOX.V));
|
|
79
125
|
this._line(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ') + chalk.yellow('██╔╝ ██╗') + ' ' + chalk.cyan(BOX.V));
|
|
80
126
|
this._line(chalk.cyan(BOX.V) + chalk.cyan(' ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ') + chalk.yellow('╚═╝ ╚═╝') + ' ' + chalk.cyan(BOX.V));
|
|
127
|
+
|
|
128
|
+
// Separator + title
|
|
81
129
|
this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|
|
82
|
-
this._line(chalk.cyan(BOX.V) + chalk.white(center(`
|
|
130
|
+
this._line(chalk.cyan(BOX.V) + chalk.white(center(`Prop Futures Algo Trading v${version}`, W)) + chalk.cyan(BOX.V));
|
|
83
131
|
this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|
|
84
|
-
this._line(chalk.cyan(BOX.V) + chalk.yellow
|
|
132
|
+
this._line(chalk.cyan(BOX.V) + chalk.yellow(center(this.config.subtitle || 'HQX Algo Trading', W)) + chalk.cyan(BOX.V));
|
|
85
133
|
}
|
|
86
134
|
|
|
87
135
|
_drawStats(stats) {
|
|
136
|
+
const { W } = this;
|
|
88
137
|
const isCopyTrading = this.config.mode === 'copy-trading';
|
|
89
|
-
|
|
90
|
-
|
|
138
|
+
|
|
139
|
+
const pnl = stats.pnl !== null && stats.pnl !== undefined ? stats.pnl : null;
|
|
140
|
+
const pnlColor = pnl === null ? chalk.gray : (pnl >= 0 ? chalk.green : chalk.red);
|
|
141
|
+
const pnlStr = pnl === null ? '--' : ((pnl >= 0 ? '+$' : '-$') + Math.abs(pnl).toFixed(2));
|
|
142
|
+
const latencyColor = stats.latency < 100 ? chalk.green : (stats.latency < 300 ? chalk.yellow : chalk.red);
|
|
143
|
+
const serverColor = stats.connected ? chalk.green : chalk.red;
|
|
144
|
+
|
|
145
|
+
if (isCopyTrading) {
|
|
146
|
+
this._drawCopyTradingStats(stats, pnlColor, pnlStr, latencyColor, serverColor);
|
|
147
|
+
} else {
|
|
148
|
+
this._drawOneAccountStats(stats, pnlColor, pnlStr, latencyColor, serverColor);
|
|
149
|
+
}
|
|
91
150
|
}
|
|
92
151
|
|
|
93
|
-
_drawOneAccountStats(stats) {
|
|
152
|
+
_drawOneAccountStats(stats, pnlColor, pnlStr, latencyColor, serverColor) {
|
|
94
153
|
const { W } = this;
|
|
95
154
|
const colL = 48, colR = 47;
|
|
96
155
|
const pad = (len) => ' '.repeat(Math.max(0, len));
|
|
156
|
+
|
|
97
157
|
const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
|
|
98
158
|
const GM = BOX.ML + BOX.H.repeat(colL) + BOX.MM + BOX.H.repeat(colR) + BOX.MR;
|
|
99
159
|
const GB = BOX.ML + BOX.H.repeat(colL) + BOX.BM + BOX.H.repeat(colR) + BOX.MR;
|
|
100
|
-
|
|
101
|
-
|
|
160
|
+
|
|
161
|
+
const row = (c1, c2) => {
|
|
162
|
+
this._line(chalk.cyan(BOX.V) + c1 + chalk.cyan(BOX.VS) + c2 + chalk.cyan(BOX.V));
|
|
163
|
+
};
|
|
164
|
+
|
|
102
165
|
this._line(chalk.cyan(GT));
|
|
166
|
+
|
|
167
|
+
// Row 1: Account | Symbol
|
|
103
168
|
const accountName = String(stats.accountName || 'N/A').substring(0, 40);
|
|
104
169
|
const symbol = String(stats.symbol || 'N/A').substring(0, 35);
|
|
105
|
-
|
|
170
|
+
const r1c1 = buildCell('Account', accountName, chalk.cyan, colL);
|
|
171
|
+
const r1c2 = buildCell('Symbol', symbol, chalk.yellow, colR);
|
|
172
|
+
row(r1c1.padded, r1c2.padded);
|
|
106
173
|
|
|
107
174
|
this._line(chalk.cyan(GM));
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
const closedPnlColor = (closedPnl || 0) >= 0 ? chalk.green : chalk.red;
|
|
114
|
-
row(buildCell('OPEN P&L', openPnlStr, openPnlColor, colL).padded, buildCell('CLOSED P&L', closedPnlStr, closedPnlColor, colR).padded);
|
|
175
|
+
|
|
176
|
+
// Row 2: Qty | P&L
|
|
177
|
+
const r2c1 = buildCell('Qty', (stats.qty || '1').toString(), chalk.cyan, colL);
|
|
178
|
+
const r2c2 = buildCell('P&L', pnlStr, pnlColor, colR);
|
|
179
|
+
row(r2c1.padded, r2c2.padded);
|
|
115
180
|
|
|
116
181
|
this._line(chalk.cyan(GM));
|
|
182
|
+
|
|
183
|
+
// Row 3: Target | Risk
|
|
117
184
|
const targetStr = stats.target !== null && stats.target !== undefined ? '$' + stats.target.toFixed(2) : '--';
|
|
118
185
|
const riskStr = stats.risk !== null && stats.risk !== undefined ? '$' + stats.risk.toFixed(2) : '--';
|
|
119
|
-
|
|
186
|
+
const r3c1 = buildCell('Target', targetStr, chalk.green, colL);
|
|
187
|
+
const r3c2 = buildCell('Risk', riskStr, chalk.red, colR);
|
|
188
|
+
row(r3c1.padded, r3c2.padded);
|
|
120
189
|
|
|
121
190
|
this._line(chalk.cyan(GM));
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
191
|
+
|
|
192
|
+
// Row 4: Trades | Latency (API response time)
|
|
193
|
+
const r4c1t = ` Trades: ${chalk.cyan(stats.trades || 0)} W/L: ${chalk.green(stats.wins || 0)}/${chalk.red(stats.losses || 0)}`;
|
|
194
|
+
const r4c1p = ` Trades: ${stats.trades || 0} W/L: ${stats.wins || 0}/${stats.losses || 0}`;
|
|
195
|
+
const r4c2 = buildCell('Latency', `${stats.latency || 0}ms`, latencyColor, colR);
|
|
196
|
+
row(r4c1t + pad(colL - r4c1p.length), r4c2.padded);
|
|
125
197
|
|
|
126
198
|
this._line(chalk.cyan(GM));
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
row(
|
|
199
|
+
|
|
200
|
+
// Row 5: Connection | Propfirm
|
|
201
|
+
const connection = stats.platform || 'Rithmic';
|
|
202
|
+
const r5c1 = buildCell('Connection', connection, chalk.white, colL);
|
|
203
|
+
const r5c2 = buildCell('Propfirm', stats.propfirm || 'N/A', chalk.cyan, colR);
|
|
204
|
+
row(r5c1.padded, r5c2.padded);
|
|
133
205
|
|
|
134
206
|
this._line(chalk.cyan(GB));
|
|
135
207
|
}
|
|
136
208
|
|
|
137
|
-
_drawCopyTradingStats(stats) {
|
|
209
|
+
_drawCopyTradingStats(stats, pnlColor, pnlStr, latencyColor, serverColor) {
|
|
138
210
|
const { W } = this;
|
|
139
211
|
const colL = 48, colR = 47;
|
|
140
212
|
const pad = (len) => ' '.repeat(Math.max(0, len));
|
|
213
|
+
|
|
141
214
|
const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
|
|
142
215
|
const GM = BOX.ML + BOX.H.repeat(colL) + BOX.MM + BOX.H.repeat(colR) + BOX.MR;
|
|
143
216
|
const GB = BOX.ML + BOX.H.repeat(colL) + BOX.BM + BOX.H.repeat(colR) + BOX.MR;
|
|
144
|
-
|
|
145
|
-
const row = (c1, c2) => {
|
|
146
|
-
|
|
217
|
+
|
|
218
|
+
const row = (c1, c2) => {
|
|
219
|
+
this._line(chalk.cyan(BOX.V) + c1 + chalk.cyan(BOX.VS) + c2 + chalk.cyan(BOX.V));
|
|
220
|
+
};
|
|
221
|
+
|
|
147
222
|
this._line(chalk.cyan(GT));
|
|
223
|
+
|
|
224
|
+
// Row 1: Lead Account | Follower Account
|
|
148
225
|
const leadName = (stats.leadName || 'N/A').substring(0, 40);
|
|
149
226
|
const followerName = (stats.followerName || 'N/A').substring(0, 40);
|
|
150
|
-
|
|
227
|
+
const r1c1 = buildCell('Lead', leadName, chalk.cyan, colL);
|
|
228
|
+
const r1c2 = buildCell('Follower', followerName, chalk.magenta, colR);
|
|
229
|
+
row(r1c1.padded, r1c2.padded);
|
|
230
|
+
|
|
231
|
+
// Full width separator
|
|
232
|
+
const GF = BOX.ML + BOX.H.repeat(W) + BOX.MR;
|
|
151
233
|
|
|
152
234
|
this._line(chalk.cyan(GF));
|
|
235
|
+
|
|
236
|
+
// Row 2: Symbol (centered, single row)
|
|
153
237
|
const symbol = (stats.symbol || stats.leadSymbol || 'N/A').substring(0, 60);
|
|
154
|
-
const symbolText = `
|
|
155
|
-
|
|
238
|
+
const symbolText = `Symbol: ${symbol}`;
|
|
239
|
+
const symbolPadded = center(symbolText, W);
|
|
240
|
+
this._line(chalk.cyan(BOX.V) + chalk.yellow(symbolPadded) + chalk.cyan(BOX.V));
|
|
156
241
|
|
|
157
242
|
this._line(chalk.cyan(GT));
|
|
158
|
-
|
|
243
|
+
|
|
244
|
+
// Row 3: Lead Qty | Follower Qty
|
|
245
|
+
const r3c1 = buildCell('Qty', (stats.leadQty || '1').toString(), chalk.cyan, colL);
|
|
246
|
+
const r3c2 = buildCell('Qty', (stats.followerQty || '1').toString(), chalk.cyan, colR);
|
|
247
|
+
row(r3c1.padded, r3c2.padded);
|
|
159
248
|
|
|
160
249
|
this._line(chalk.cyan(GM));
|
|
161
|
-
|
|
250
|
+
|
|
251
|
+
// Row 4: Target | Risk
|
|
252
|
+
const r4c1 = buildCell('Target', '$' + (stats.target || 0).toFixed(2), chalk.green, colL);
|
|
253
|
+
const r4c2 = buildCell('Risk', '$' + (stats.risk || 0).toFixed(2), chalk.red, colR);
|
|
254
|
+
row(r4c1.padded, r4c2.padded);
|
|
162
255
|
|
|
163
256
|
this._line(chalk.cyan(GM));
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
const r5c2t = `
|
|
168
|
-
const r5c2p = `
|
|
169
|
-
row(
|
|
257
|
+
|
|
258
|
+
// Row 5: P&L | Trades
|
|
259
|
+
const r5c1 = buildCell('P&L', pnlStr, pnlColor, colL);
|
|
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));
|
|
170
263
|
|
|
171
264
|
this._line(chalk.cyan(GB));
|
|
172
265
|
}
|
|
173
266
|
|
|
174
267
|
_drawLogs() {
|
|
175
268
|
const { W, logs, maxLogs } = this;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
269
|
+
|
|
270
|
+
// Activity header - HF style
|
|
271
|
+
this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER.length;
|
|
272
|
+
const spinner = SPINNER[this.spinnerFrame];
|
|
273
|
+
const now = new Date();
|
|
274
|
+
const timeStr = now.toLocaleTimeString('en-US', { hour12: false });
|
|
275
|
+
const dateStr = now.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
182
276
|
|
|
183
277
|
const leftText = ` EXECUTION LOG ${spinner}`;
|
|
184
278
|
const rightText = `[X] STOP `;
|
|
279
|
+
|
|
185
280
|
const totalFixed = leftText.length + rightText.length;
|
|
186
281
|
const centerSpace = W - totalFixed;
|
|
187
282
|
const centerPadLeft = Math.floor((centerSpace - dateStr.length) / 2);
|
|
188
283
|
const centerPadRight = centerSpace - dateStr.length - centerPadLeft;
|
|
189
284
|
|
|
190
|
-
const left = `
|
|
191
|
-
const
|
|
192
|
-
const right = chalk.yellow
|
|
285
|
+
const left = ` EXECUTION LOG ${chalk.yellow(spinner)}`;
|
|
286
|
+
const center = ' '.repeat(Math.max(0, centerPadLeft)) + chalk.white(dateStr) + ' '.repeat(Math.max(0, centerPadRight));
|
|
287
|
+
const right = chalk.yellow('[X] STOP') + ' ';
|
|
193
288
|
|
|
194
|
-
this._line(chalk.cyan(BOX.V) + chalk.white(left) +
|
|
289
|
+
this._line(chalk.cyan(BOX.V) + chalk.white.bold(left) + center + right + chalk.cyan(BOX.V));
|
|
195
290
|
this._line(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|
|
196
291
|
|
|
292
|
+
// Logs: newest at top
|
|
197
293
|
const visible = logs.slice(-maxLogs).reverse();
|
|
294
|
+
|
|
198
295
|
if (visible.length === 0) {
|
|
199
296
|
this._line(chalk.cyan(BOX.V) + chalk.gray(fitToWidth(' Awaiting market signals...', W)) + chalk.cyan(BOX.V));
|
|
200
|
-
for (let i = 0; i < maxLogs - 1; i++)
|
|
297
|
+
for (let i = 0; i < maxLogs - 1; i++) {
|
|
298
|
+
this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
|
|
299
|
+
}
|
|
201
300
|
} else {
|
|
202
301
|
visible.forEach(log => {
|
|
203
302
|
const color = LOG_COLORS[log.type] || chalk.gray;
|
|
204
|
-
const icon = LOG_ICONS[log.type] || '
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
const line = ` ${chalk.gray(log.timestamp)} ${coloredIcon} ${coloredMessage}`;
|
|
303
|
+
const icon = LOG_ICONS[log.type] || '';
|
|
304
|
+
// HF style: TIME | TYPE | MESSAGE
|
|
305
|
+
const line = ` ${chalk.gray(log.timestamp)} ${color(icon)}${log.message}`;
|
|
208
306
|
this._line(chalk.cyan(BOX.V) + fitToWidth(line, W) + chalk.cyan(BOX.V));
|
|
209
307
|
});
|
|
210
|
-
for (let i = visible.length; i < maxLogs; i++)
|
|
308
|
+
for (let i = visible.length; i < maxLogs; i++) {
|
|
309
|
+
this._line(chalk.cyan(BOX.V) + ' '.repeat(W) + chalk.cyan(BOX.V));
|
|
310
|
+
}
|
|
211
311
|
}
|
|
312
|
+
|
|
313
|
+
// Bottom border
|
|
212
314
|
this._line(chalk.cyan(BOX.BOT + BOX.H.repeat(W) + BOX.BR));
|
|
213
315
|
}
|
|
214
316
|
|
|
@@ -216,64 +318,123 @@ class AlgoUI {
|
|
|
216
318
|
if (this.isDrawing) return;
|
|
217
319
|
this.isDrawing = true;
|
|
218
320
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
this.lastStatsHash = statsHash;
|
|
225
|
-
this.lastLogsHash = logsHash;
|
|
226
|
-
this.lines = [];
|
|
227
|
-
|
|
228
|
-
if (this.firstDraw) {
|
|
229
|
-
console.clear();
|
|
230
|
-
process.stdout.write('\x1B[?25l');
|
|
231
|
-
this.firstDraw = false;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
this._line('');
|
|
235
|
-
this._drawHeader();
|
|
236
|
-
this._drawStats(stats);
|
|
237
|
-
this._drawLogs();
|
|
238
|
-
|
|
239
|
-
const output = this.lines.join('\n');
|
|
240
|
-
if (output !== this.lastOutput) {
|
|
241
|
-
const lines = this.lines;
|
|
242
|
-
for (let i = 0; i < lines.length; i++) {
|
|
243
|
-
process.stdout.write(`\x1B[${i + 1};1H${lines[i]}\x1B[K`);
|
|
244
|
-
}
|
|
245
|
-
this.lastOutput = output;
|
|
246
|
-
}
|
|
321
|
+
this.buffer = '';
|
|
322
|
+
|
|
323
|
+
if (this.firstDraw) {
|
|
324
|
+
this.buffer += '\x1B[?1049h\x1B[?25l\x1B[2J';
|
|
325
|
+
this.firstDraw = false;
|
|
247
326
|
}
|
|
327
|
+
|
|
328
|
+
this.buffer += '\x1B[H';
|
|
329
|
+
this._line('');
|
|
330
|
+
this._drawHeader();
|
|
331
|
+
this._drawStats(stats);
|
|
332
|
+
this._drawLogs();
|
|
333
|
+
|
|
334
|
+
process.stdout.write(this.buffer);
|
|
248
335
|
this.isDrawing = false;
|
|
249
336
|
}
|
|
250
337
|
|
|
251
338
|
cleanup() {
|
|
252
|
-
process.stdout.write('\x1B[?25h');
|
|
253
|
-
console.clear();
|
|
339
|
+
process.stdout.write('\x1B[?1049l\x1B[?25h');
|
|
254
340
|
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Check market hours
|
|
345
|
+
*/
|
|
346
|
+
const checkMarketStatus = () => {
|
|
347
|
+
const now = new Date();
|
|
348
|
+
const utcDay = now.getUTCDay();
|
|
349
|
+
const utcHour = now.getUTCHours();
|
|
350
|
+
const isDST = now.getTimezoneOffset() < Math.max(
|
|
351
|
+
new Date(now.getFullYear(), 0, 1).getTimezoneOffset(),
|
|
352
|
+
new Date(now.getFullYear(), 6, 1).getTimezoneOffset()
|
|
353
|
+
);
|
|
354
|
+
const ctOffset = isDST ? 5 : 6;
|
|
355
|
+
const ctHour = (utcHour - ctOffset + 24) % 24;
|
|
356
|
+
const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
|
|
357
|
+
|
|
358
|
+
if (ctDay === 6) return { isOpen: false, message: 'Market closed (Saturday)' };
|
|
359
|
+
if (ctDay === 0 && ctHour < 17) return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
|
|
360
|
+
if (ctDay === 5 && ctHour >= 16) return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
|
|
361
|
+
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) return { isOpen: false, message: 'Daily maintenance' };
|
|
362
|
+
return { isOpen: true, message: 'Market OPEN' };
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Render Session Summary - Same style as dashboard
|
|
367
|
+
*/
|
|
368
|
+
const renderSessionSummary = (stats, stopReason) => {
|
|
369
|
+
const W = 96; // Same width as dashboard
|
|
370
|
+
const colL = Math.floor(W / 2) - 1;
|
|
371
|
+
const colR = W - colL - 1;
|
|
372
|
+
const version = require('../../../package.json').version;
|
|
255
373
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
const now = new Date();
|
|
260
|
-
this.logStream.write(`\n${'='.repeat(80)}\n`);
|
|
261
|
-
this.logStream.write(`SESSION ENDED: ${now.toISOString()}\n`);
|
|
262
|
-
if (stats) {
|
|
263
|
-
this.logStream.write(`SUMMARY:\n`);
|
|
264
|
-
this.logStream.write(` Trades: ${stats.trades || 0}\n`);
|
|
265
|
-
this.logStream.write(` Wins: ${stats.wins || 0}\n`);
|
|
266
|
-
this.logStream.write(` Losses: ${stats.losses || 0}\n`);
|
|
267
|
-
this.logStream.write(` Session P&L: $${(stats.sessionPnl || 0).toFixed(2)}\n`);
|
|
268
|
-
this.logStream.write(` Duration: ${stats.duration || 'N/A'}\n`);
|
|
269
|
-
}
|
|
270
|
-
this.logStream.write(`${'='.repeat(80)}\n`);
|
|
271
|
-
this.logStream.end();
|
|
272
|
-
} catch (e) {}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
374
|
+
console.clear();
|
|
375
|
+
console.log();
|
|
275
376
|
|
|
276
|
-
|
|
277
|
-
|
|
377
|
+
// Top border
|
|
378
|
+
console.log(chalk.cyan(BOX.TOP + BOX.H.repeat(W) + BOX.TR));
|
|
379
|
+
|
|
380
|
+
// Logo
|
|
381
|
+
console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██╗ ██╗███████╗██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗') + chalk.yellow('██╗ ██╗') + ' ' + chalk.cyan(BOX.V));
|
|
382
|
+
console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝') + chalk.yellow('╚██╗██╔╝') + ' ' + chalk.cyan(BOX.V));
|
|
383
|
+
console.log(chalk.cyan(BOX.V) + chalk.cyan(' ███████║█████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ') + chalk.yellow(' ╚███╔╝ ') + ' ' + chalk.cyan(BOX.V));
|
|
384
|
+
console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██╔══██║██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ') + chalk.yellow(' ██╔██╗ ') + ' ' + chalk.cyan(BOX.V));
|
|
385
|
+
console.log(chalk.cyan(BOX.V) + chalk.cyan(' ██║ ██║███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ') + chalk.yellow('██╔╝ ██╗') + ' ' + chalk.cyan(BOX.V));
|
|
386
|
+
console.log(chalk.cyan(BOX.V) + chalk.cyan(' ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ') + chalk.yellow('╚═╝ ╚═╝') + ' ' + chalk.cyan(BOX.V));
|
|
387
|
+
|
|
388
|
+
// Separator + title
|
|
389
|
+
console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|
|
390
|
+
console.log(chalk.cyan(BOX.V) + chalk.white(center(`Prop Futures Algo Trading v${version}`, W)) + chalk.cyan(BOX.V));
|
|
391
|
+
console.log(chalk.cyan(BOX.ML + BOX.H.repeat(W) + BOX.MR));
|
|
392
|
+
console.log(chalk.cyan(BOX.V) + chalk.yellow(center('Session Summary', W)) + chalk.cyan(BOX.V));
|
|
393
|
+
|
|
394
|
+
// Grid separators
|
|
395
|
+
const GT = BOX.ML + BOX.H.repeat(colL) + BOX.TM + BOX.H.repeat(colR) + BOX.MR;
|
|
396
|
+
const GM = BOX.ML + BOX.H.repeat(colL) + BOX.MM + BOX.H.repeat(colR) + BOX.MR;
|
|
397
|
+
|
|
398
|
+
const row = (label1, value1, color1, label2, value2, color2) => {
|
|
399
|
+
const c1 = ` ${label1}: ${color1(value1)}`;
|
|
400
|
+
const c2 = ` ${label2}: ${color2(value2)}`;
|
|
401
|
+
const p1 = ` ${label1}: ${value1}`;
|
|
402
|
+
const p2 = ` ${label2}: ${value2}`;
|
|
403
|
+
const padded1 = c1 + ' '.repeat(Math.max(0, colL - p1.length));
|
|
404
|
+
const padded2 = c2 + ' '.repeat(Math.max(0, colR - p2.length));
|
|
405
|
+
console.log(chalk.cyan(BOX.V) + padded1 + chalk.cyan(BOX.VS) + padded2 + chalk.cyan(BOX.V));
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
console.log(chalk.cyan(GT));
|
|
409
|
+
|
|
410
|
+
// Row 1: Stop Reason | Duration
|
|
411
|
+
const duration = stats.duration || '--';
|
|
412
|
+
const reasonColor = stopReason === 'target' ? chalk.green : stopReason === 'risk' ? chalk.red : chalk.yellow;
|
|
413
|
+
row('Stop Reason', (stopReason || 'manual').toUpperCase(), reasonColor, 'Duration', duration, chalk.white);
|
|
414
|
+
|
|
415
|
+
console.log(chalk.cyan(GM));
|
|
416
|
+
|
|
417
|
+
// Row 2: Trades | Win Rate
|
|
418
|
+
const winRate = stats.trades > 0 ? ((stats.wins / stats.trades) * 100).toFixed(1) + '%' : '0%';
|
|
419
|
+
row('Trades', String(stats.trades || 0), chalk.white, 'Win Rate', winRate, stats.wins >= stats.losses ? chalk.green : chalk.red);
|
|
420
|
+
|
|
421
|
+
console.log(chalk.cyan(GM));
|
|
422
|
+
|
|
423
|
+
// Row 3: Wins | Losses
|
|
424
|
+
row('Wins', String(stats.wins || 0), chalk.green, 'Losses', String(stats.losses || 0), chalk.red);
|
|
425
|
+
|
|
426
|
+
console.log(chalk.cyan(GM));
|
|
427
|
+
|
|
428
|
+
// Row 4: P&L | Target
|
|
429
|
+
const pnl = stats.pnl || 0;
|
|
430
|
+
const pnlStr = `${pnl >= 0 ? '+' : ''}$${Math.abs(pnl).toFixed(2)}`;
|
|
431
|
+
const pnlColor = pnl >= 0 ? chalk.green : chalk.red;
|
|
432
|
+
const targetStr = `$${(stats.target || 0).toFixed(2)}`;
|
|
433
|
+
row('P&L', pnlStr, pnlColor, 'Target', targetStr, chalk.cyan);
|
|
434
|
+
|
|
435
|
+
// Bottom border
|
|
436
|
+
console.log(chalk.cyan(BOX.BOT + BOX.H.repeat(W) + BOX.BR));
|
|
437
|
+
console.log();
|
|
438
|
+
};
|
|
278
439
|
|
|
279
|
-
module.exports = { AlgoUI, checkMarketStatus, renderSessionSummary,
|
|
440
|
+
module.exports = { AlgoUI, checkMarketStatus, renderSessionSummary, LOG_COLORS, LOG_ICONS, stripAnsi, center, fitToWidth };
|
package/src/pages/orders.js
CHANGED
|
@@ -19,14 +19,14 @@ const showOrders = async (service) => {
|
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
// Step 1: Get connections
|
|
22
|
-
spinner = ora({ text: '
|
|
22
|
+
spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
|
|
23
23
|
|
|
24
24
|
const allConns = connections.count() > 0
|
|
25
25
|
? connections.getAll()
|
|
26
26
|
: (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
27
27
|
|
|
28
28
|
if (allConns.length === 0) {
|
|
29
|
-
spinner.fail('
|
|
29
|
+
spinner.fail('No connections found');
|
|
30
30
|
await prompts.waitForEnter();
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
@@ -90,7 +90,7 @@ const showOrders = async (service) => {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
spinner = ora({ text: '
|
|
93
|
+
spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
|
|
94
94
|
spinner.succeed(`Total: ${allOrders.length} order(s)`);
|
|
95
95
|
console.log();
|
|
96
96
|
|
package/src/pages/positions.js
CHANGED
|
@@ -19,14 +19,14 @@ const showPositions = async (service) => {
|
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
// Step 1: Get connections
|
|
22
|
-
spinner = ora({ text: '
|
|
22
|
+
spinner = ora({ text: 'Loading connections...', color: 'yellow' }).start();
|
|
23
23
|
|
|
24
24
|
const allConns = connections.count() > 0
|
|
25
25
|
? connections.getAll()
|
|
26
26
|
: (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'single' }] : []);
|
|
27
27
|
|
|
28
28
|
if (allConns.length === 0) {
|
|
29
|
-
spinner.fail('
|
|
29
|
+
spinner.fail('No connections found');
|
|
30
30
|
await prompts.waitForEnter();
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
@@ -90,7 +90,7 @@ const showPositions = async (service) => {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
spinner = ora({ text: '
|
|
93
|
+
spinner = ora({ text: 'Preparing display...', color: 'yellow' }).start();
|
|
94
94
|
spinner.succeed(`Total: ${allPositions.length} position(s)`);
|
|
95
95
|
console.log();
|
|
96
96
|
|