hedgequantx 1.1.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 +247 -0
- package/bin/cli.js +2096 -0
- package/package.json +52 -0
- package/src/api/projectx_gatewayapi.json +1766 -0
- package/src/api/projectx_userapi.json +641 -0
- package/src/config/constants.js +75 -0
- package/src/config/index.js +24 -0
- package/src/config/propfirms.js +56 -0
- package/src/pages/index.js +9 -0
- package/src/pages/stats.js +289 -0
- package/src/services/hqx-server.js +351 -0
- package/src/services/index.js +12 -0
- package/src/services/local-storage.js +309 -0
- package/src/services/projectx.js +369 -0
- package/src/services/session.js +143 -0
- package/src/ui/box.js +105 -0
- package/src/ui/device.js +85 -0
- package/src/ui/index.js +48 -0
- package/src/ui/table.js +81 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { PROPFIRMS, PROPFIRM_CHOICES } = require('./propfirms');
|
|
6
|
+
const {
|
|
7
|
+
ACCOUNT_STATUS,
|
|
8
|
+
ACCOUNT_TYPE,
|
|
9
|
+
ORDER_STATUS,
|
|
10
|
+
ORDER_TYPE,
|
|
11
|
+
ORDER_SIDE,
|
|
12
|
+
FUTURES_SYMBOLS
|
|
13
|
+
} = require('./constants');
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
PROPFIRMS,
|
|
17
|
+
PROPFIRM_CHOICES,
|
|
18
|
+
ACCOUNT_STATUS,
|
|
19
|
+
ACCOUNT_TYPE,
|
|
20
|
+
ORDER_STATUS,
|
|
21
|
+
ORDER_TYPE,
|
|
22
|
+
ORDER_SIDE,
|
|
23
|
+
FUTURES_SYMBOLS
|
|
24
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PropFirm API Configurations
|
|
3
|
+
* UserAPI: Authentication, accounts, user info
|
|
4
|
+
* GatewayAPI: Trading (orders, positions, trades)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const PROPFIRMS = {
|
|
8
|
+
topstep: {
|
|
9
|
+
name: 'Topstep',
|
|
10
|
+
userApi: 'userapi.topstepx.com',
|
|
11
|
+
gatewayApi: 'api.topstepx.com'
|
|
12
|
+
},
|
|
13
|
+
alpha_futures: {
|
|
14
|
+
name: 'Alpha Futures',
|
|
15
|
+
userApi: 'userapi.alphafutures.projectx.com',
|
|
16
|
+
gatewayApi: 'api.alphafutures.projectx.com'
|
|
17
|
+
},
|
|
18
|
+
tickticktrader: {
|
|
19
|
+
name: 'TickTickTrader',
|
|
20
|
+
userApi: 'userapi.tickticktrader.projectx.com',
|
|
21
|
+
gatewayApi: 'api.tickticktrader.projectx.com'
|
|
22
|
+
},
|
|
23
|
+
bulenox: {
|
|
24
|
+
name: 'Bulenox',
|
|
25
|
+
userApi: 'userapi.bulenox.projectx.com',
|
|
26
|
+
gatewayApi: 'api.bulenox.projectx.com'
|
|
27
|
+
},
|
|
28
|
+
tradeday: {
|
|
29
|
+
name: 'TradeDay',
|
|
30
|
+
userApi: 'userapi.tradeday.projectx.com',
|
|
31
|
+
gatewayApi: 'api.tradeday.projectx.com'
|
|
32
|
+
},
|
|
33
|
+
blusky: {
|
|
34
|
+
name: 'Blusky',
|
|
35
|
+
userApi: 'userapi.blusky.projectx.com',
|
|
36
|
+
gatewayApi: 'api.blusky.projectx.com'
|
|
37
|
+
},
|
|
38
|
+
goat_futures: {
|
|
39
|
+
name: 'Goat Futures',
|
|
40
|
+
userApi: 'userapi.goatfunded.projectx.com',
|
|
41
|
+
gatewayApi: 'api.goatfunded.projectx.com'
|
|
42
|
+
},
|
|
43
|
+
futures_desk: {
|
|
44
|
+
name: 'The Futures Desk',
|
|
45
|
+
userApi: 'userapi.thefuturesdesk.projectx.com',
|
|
46
|
+
gatewayApi: 'api.thefuturesdesk.projectx.com'
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// PropFirm display list for menus
|
|
51
|
+
const PROPFIRM_CHOICES = Object.entries(PROPFIRMS).map(([key, val]) => ({
|
|
52
|
+
name: val.name,
|
|
53
|
+
value: key
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
module.exports = { PROPFIRMS, PROPFIRM_CHOICES };
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats Page
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const ora = require('ora');
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const asciichart = require('asciichart');
|
|
9
|
+
|
|
10
|
+
const { connections } = require('../services');
|
|
11
|
+
const { ACCOUNT_STATUS, ACCOUNT_TYPE } = require('../config');
|
|
12
|
+
const {
|
|
13
|
+
getLogoWidth,
|
|
14
|
+
visibleLength,
|
|
15
|
+
drawBoxHeader,
|
|
16
|
+
drawBoxFooter,
|
|
17
|
+
getColWidths,
|
|
18
|
+
draw2ColHeader,
|
|
19
|
+
draw2ColSeparator,
|
|
20
|
+
fmtRow
|
|
21
|
+
} = require('../ui');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Show Stats Page
|
|
25
|
+
*/
|
|
26
|
+
const showStats = async (service) => {
|
|
27
|
+
const spinner = ora('Fetching stats for all accounts...').start();
|
|
28
|
+
|
|
29
|
+
let allAccountsData = [];
|
|
30
|
+
|
|
31
|
+
// Get accounts from all connections
|
|
32
|
+
if (connections.count() > 0) {
|
|
33
|
+
for (const conn of connections.getAll()) {
|
|
34
|
+
try {
|
|
35
|
+
const result = await conn.service.getTradingAccounts();
|
|
36
|
+
if (result.success && result.accounts) {
|
|
37
|
+
result.accounts.forEach(account => {
|
|
38
|
+
allAccountsData.push({
|
|
39
|
+
...account,
|
|
40
|
+
propfirm: conn.propfirm || conn.type,
|
|
41
|
+
service: conn.service
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
} catch (e) { /* ignore */ }
|
|
46
|
+
}
|
|
47
|
+
} else if (service) {
|
|
48
|
+
const result = await service.getTradingAccounts();
|
|
49
|
+
if (result.success && result.accounts) {
|
|
50
|
+
allAccountsData = result.accounts.map(a => ({ ...a, service }));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (allAccountsData.length === 0) {
|
|
55
|
+
spinner.fail('No accounts found');
|
|
56
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Collect stats
|
|
61
|
+
let totalBalance = 0;
|
|
62
|
+
let totalStartingBalance = 0;
|
|
63
|
+
let totalPnL = 0;
|
|
64
|
+
let allTrades = [];
|
|
65
|
+
let totalOpenPositions = 0;
|
|
66
|
+
let totalOpenOrders = 0;
|
|
67
|
+
let allDailyStats = [];
|
|
68
|
+
|
|
69
|
+
spinner.text = 'Fetching detailed stats...';
|
|
70
|
+
|
|
71
|
+
for (const account of allAccountsData) {
|
|
72
|
+
const svc = account.service;
|
|
73
|
+
const currentBalance = account.balance || 0;
|
|
74
|
+
totalBalance += currentBalance;
|
|
75
|
+
|
|
76
|
+
// Estimate starting balance
|
|
77
|
+
let startingBalance = account.startingBalance || account.initialBalance || 0;
|
|
78
|
+
if (!startingBalance) {
|
|
79
|
+
const name = (account.accountName || '').toUpperCase();
|
|
80
|
+
if (name.includes('150K') || name.includes('150')) startingBalance = 150000;
|
|
81
|
+
else if (name.includes('100K') || name.includes('100')) startingBalance = 100000;
|
|
82
|
+
else if (name.includes('50K') || name.includes('50')) startingBalance = 50000;
|
|
83
|
+
else if (currentBalance >= 140000) startingBalance = 150000;
|
|
84
|
+
else if (currentBalance >= 90000) startingBalance = 100000;
|
|
85
|
+
else if (currentBalance >= 45000) startingBalance = 50000;
|
|
86
|
+
else startingBalance = currentBalance;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
totalStartingBalance += startingBalance;
|
|
90
|
+
account.startingBalance = startingBalance;
|
|
91
|
+
|
|
92
|
+
if (account.profitAndLoss !== undefined) {
|
|
93
|
+
totalPnL += account.profitAndLoss;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Positions & Orders
|
|
97
|
+
const posResult = await svc.getPositions(account.accountId);
|
|
98
|
+
if (posResult.success) totalOpenPositions += posResult.positions.length;
|
|
99
|
+
|
|
100
|
+
const ordResult = await svc.getOrders(account.accountId);
|
|
101
|
+
if (ordResult.success) totalOpenOrders += ordResult.orders.filter(o => o.status === 1).length;
|
|
102
|
+
|
|
103
|
+
// Lifetime stats
|
|
104
|
+
const lifetimeResult = await svc.getLifetimeStats(account.accountId);
|
|
105
|
+
if (lifetimeResult.success && lifetimeResult.stats) {
|
|
106
|
+
account.lifetimeStats = lifetimeResult.stats;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Daily stats
|
|
110
|
+
const dailyResult = await svc.getDailyStats(account.accountId);
|
|
111
|
+
if (dailyResult.success && dailyResult.stats) {
|
|
112
|
+
account.dailyStats = dailyResult.stats;
|
|
113
|
+
allDailyStats = allDailyStats.concat(dailyResult.stats);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Trade history
|
|
117
|
+
const tradesResult = await svc.getTradeHistory(account.accountId, 30);
|
|
118
|
+
if (tradesResult.success && tradesResult.trades.length > 0) {
|
|
119
|
+
allTrades = allTrades.concat(tradesResult.trades.map(t => ({
|
|
120
|
+
...t,
|
|
121
|
+
accountName: account.accountName,
|
|
122
|
+
propfirm: account.propfirm
|
|
123
|
+
})));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (totalPnL === 0 && totalStartingBalance > 0) {
|
|
128
|
+
totalPnL = totalBalance - totalStartingBalance;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Aggregate stats
|
|
132
|
+
let stats = {
|
|
133
|
+
totalTrades: 0, winningTrades: 0, losingTrades: 0,
|
|
134
|
+
totalWinAmount: 0, totalLossAmount: 0,
|
|
135
|
+
bestTrade: 0, worstTrade: 0, totalVolume: 0,
|
|
136
|
+
maxConsecutiveWins: 0, maxConsecutiveLosses: 0,
|
|
137
|
+
longTrades: 0, shortTrades: 0, longWins: 0, shortWins: 0
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
for (const account of allAccountsData) {
|
|
141
|
+
if (account.lifetimeStats) {
|
|
142
|
+
const s = account.lifetimeStats;
|
|
143
|
+
stats.totalTrades += s.totalTrades || 0;
|
|
144
|
+
stats.winningTrades += s.winningTrades || 0;
|
|
145
|
+
stats.losingTrades += s.losingTrades || 0;
|
|
146
|
+
stats.totalWinAmount += s.totalWinAmount || 0;
|
|
147
|
+
stats.totalLossAmount += s.totalLossAmount || 0;
|
|
148
|
+
stats.bestTrade = Math.max(stats.bestTrade, s.bestTrade || 0);
|
|
149
|
+
stats.worstTrade = Math.min(stats.worstTrade, s.worstTrade || 0);
|
|
150
|
+
stats.totalVolume += s.totalVolume || 0;
|
|
151
|
+
stats.maxConsecutiveWins = Math.max(stats.maxConsecutiveWins, s.maxConsecutiveWins || 0);
|
|
152
|
+
stats.maxConsecutiveLosses = Math.max(stats.maxConsecutiveLosses, s.maxConsecutiveLosses || 0);
|
|
153
|
+
stats.longTrades += s.longTrades || 0;
|
|
154
|
+
stats.shortTrades += s.shortTrades || 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// If no stats from API, calculate from trades
|
|
159
|
+
if (stats.totalTrades === 0 && allTrades.length > 0) {
|
|
160
|
+
stats.totalTrades = allTrades.length;
|
|
161
|
+
let consecutiveWins = 0, consecutiveLosses = 0;
|
|
162
|
+
|
|
163
|
+
for (const trade of allTrades) {
|
|
164
|
+
const pnl = trade.profitAndLoss || trade.pnl || 0;
|
|
165
|
+
const size = trade.size || trade.quantity || 1;
|
|
166
|
+
const side = trade.side;
|
|
167
|
+
|
|
168
|
+
stats.totalVolume += Math.abs(size);
|
|
169
|
+
|
|
170
|
+
if (side === 0) {
|
|
171
|
+
stats.longTrades++;
|
|
172
|
+
if (pnl > 0) stats.longWins++;
|
|
173
|
+
} else if (side === 1) {
|
|
174
|
+
stats.shortTrades++;
|
|
175
|
+
if (pnl > 0) stats.shortWins++;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (pnl > 0) {
|
|
179
|
+
stats.winningTrades++;
|
|
180
|
+
stats.totalWinAmount += pnl;
|
|
181
|
+
consecutiveWins++;
|
|
182
|
+
consecutiveLosses = 0;
|
|
183
|
+
if (consecutiveWins > stats.maxConsecutiveWins) stats.maxConsecutiveWins = consecutiveWins;
|
|
184
|
+
if (pnl > stats.bestTrade) stats.bestTrade = pnl;
|
|
185
|
+
} else if (pnl < 0) {
|
|
186
|
+
stats.losingTrades++;
|
|
187
|
+
stats.totalLossAmount += Math.abs(pnl);
|
|
188
|
+
consecutiveLosses++;
|
|
189
|
+
consecutiveWins = 0;
|
|
190
|
+
if (consecutiveLosses > stats.maxConsecutiveLosses) stats.maxConsecutiveLosses = consecutiveLosses;
|
|
191
|
+
if (pnl < stats.worstTrade) stats.worstTrade = pnl;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
spinner.succeed('Stats loaded');
|
|
197
|
+
console.log();
|
|
198
|
+
|
|
199
|
+
// Display
|
|
200
|
+
const boxWidth = getLogoWidth();
|
|
201
|
+
const { col1, col2 } = getColWidths(boxWidth);
|
|
202
|
+
|
|
203
|
+
// Calculated metrics
|
|
204
|
+
const winRate = stats.totalTrades > 0 ? ((stats.winningTrades / stats.totalTrades) * 100).toFixed(1) : '0.0';
|
|
205
|
+
const avgWin = stats.winningTrades > 0 ? (stats.totalWinAmount / stats.winningTrades).toFixed(2) : '0.00';
|
|
206
|
+
const avgLoss = stats.losingTrades > 0 ? (stats.totalLossAmount / stats.losingTrades).toFixed(2) : '0.00';
|
|
207
|
+
const profitFactor = stats.totalLossAmount > 0 ? (stats.totalWinAmount / stats.totalLossAmount).toFixed(2) : '0.00';
|
|
208
|
+
const netPnL = stats.totalWinAmount - stats.totalLossAmount;
|
|
209
|
+
const returnPercent = totalStartingBalance > 0 ? ((totalPnL / totalStartingBalance) * 100).toFixed(2) : '0.00';
|
|
210
|
+
const longWinRate = stats.longTrades > 0 ? ((stats.longWins / stats.longTrades) * 100).toFixed(1) : '0.0';
|
|
211
|
+
const shortWinRate = stats.shortTrades > 0 ? ((stats.shortWins / stats.shortTrades) * 100).toFixed(1) : '0.0';
|
|
212
|
+
|
|
213
|
+
const totalBalanceColor = totalBalance >= 0 ? chalk.green : chalk.red;
|
|
214
|
+
const pnlColor = totalPnL >= 0 ? chalk.green : chalk.red;
|
|
215
|
+
|
|
216
|
+
// Main Summary
|
|
217
|
+
drawBoxHeader('HQX STATS', boxWidth);
|
|
218
|
+
draw2ColHeader('ACCOUNT OVERVIEW', 'TRADING PERFORMANCE', boxWidth);
|
|
219
|
+
|
|
220
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Connections:', chalk.cyan(connections.count().toString() || '1'), col1) + chalk.cyan('\u2502') + fmtRow('Total Trades:', chalk.white(stats.totalTrades.toString()), col2) + chalk.cyan('\u2551'));
|
|
221
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Total Accounts:', chalk.cyan(allAccountsData.length.toString()), col1) + chalk.cyan('\u2502') + fmtRow('Winning Trades:', chalk.green(stats.winningTrades.toString()), col2) + chalk.cyan('\u2551'));
|
|
222
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Total Balance:', totalBalanceColor('$' + totalBalance.toLocaleString()), col1) + chalk.cyan('\u2502') + fmtRow('Losing Trades:', chalk.red(stats.losingTrades.toString()), col2) + chalk.cyan('\u2551'));
|
|
223
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Starting Balance:', chalk.white('$' + totalStartingBalance.toLocaleString()), col1) + chalk.cyan('\u2502') + fmtRow('Win Rate:', parseFloat(winRate) >= 50 ? chalk.green(winRate + '%') : chalk.yellow(winRate + '%'), col2) + chalk.cyan('\u2551'));
|
|
224
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Total P&L:', pnlColor('$' + totalPnL.toLocaleString() + ' (' + returnPercent + '%)'), col1) + chalk.cyan('\u2502') + fmtRow('Long Trades:', chalk.white(stats.longTrades + ' (' + longWinRate + '%)'), col2) + chalk.cyan('\u2551'));
|
|
225
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Open Positions:', chalk.white(totalOpenPositions.toString()), col1) + chalk.cyan('\u2502') + fmtRow('Short Trades:', chalk.white(stats.shortTrades + ' (' + shortWinRate + '%)'), col2) + chalk.cyan('\u2551'));
|
|
226
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Open Orders:', chalk.white(totalOpenOrders.toString()), col1) + chalk.cyan('\u2502') + fmtRow('Volume:', chalk.white(stats.totalVolume + ' contracts'), col2) + chalk.cyan('\u2551'));
|
|
227
|
+
|
|
228
|
+
// P&L Metrics
|
|
229
|
+
draw2ColSeparator(boxWidth);
|
|
230
|
+
draw2ColHeader('P&L METRICS', 'RISK METRICS', boxWidth);
|
|
231
|
+
|
|
232
|
+
const pfColor = parseFloat(profitFactor) >= 1.5 ? chalk.green(profitFactor) : parseFloat(profitFactor) >= 1 ? chalk.yellow(profitFactor) : chalk.red(profitFactor);
|
|
233
|
+
|
|
234
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Net P&L:', netPnL >= 0 ? chalk.green('$' + netPnL.toFixed(2)) : chalk.red('$' + netPnL.toFixed(2)), col1) + chalk.cyan('\u2502') + fmtRow('Profit Factor:', pfColor, col2) + chalk.cyan('\u2551'));
|
|
235
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Gross Profit:', chalk.green('$' + stats.totalWinAmount.toFixed(2)), col1) + chalk.cyan('\u2502') + fmtRow('Max Consec. Wins:', chalk.green(stats.maxConsecutiveWins.toString()), col2) + chalk.cyan('\u2551'));
|
|
236
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Gross Loss:', chalk.red('-$' + stats.totalLossAmount.toFixed(2)), col1) + chalk.cyan('\u2502') + fmtRow('Max Consec. Loss:', chalk.red(stats.maxConsecutiveLosses.toString()), col2) + chalk.cyan('\u2551'));
|
|
237
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Avg Win:', chalk.green('$' + avgWin), col1) + chalk.cyan('\u2502') + fmtRow('Best Trade:', chalk.green('$' + stats.bestTrade.toFixed(2)), col2) + chalk.cyan('\u2551'));
|
|
238
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Avg Loss:', chalk.red('-$' + avgLoss), col1) + chalk.cyan('\u2502') + fmtRow('Worst Trade:', chalk.red('$' + stats.worstTrade.toFixed(2)), col2) + chalk.cyan('\u2551'));
|
|
239
|
+
|
|
240
|
+
drawBoxFooter(boxWidth);
|
|
241
|
+
|
|
242
|
+
// Equity Curve
|
|
243
|
+
console.log();
|
|
244
|
+
drawBoxHeader('EQUITY CURVE', boxWidth);
|
|
245
|
+
|
|
246
|
+
const chartInnerWidth = boxWidth - 2;
|
|
247
|
+
|
|
248
|
+
if (allTrades.length > 0) {
|
|
249
|
+
const yAxisWidth = 10;
|
|
250
|
+
const chartAreaWidth = chartInnerWidth - yAxisWidth - 4;
|
|
251
|
+
|
|
252
|
+
let equityData = [totalStartingBalance];
|
|
253
|
+
let equity = totalStartingBalance;
|
|
254
|
+
allTrades.forEach(trade => {
|
|
255
|
+
equity += (trade.profitAndLoss || trade.pnl || 0);
|
|
256
|
+
equityData.push(equity);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const maxDataPoints = chartAreaWidth - 5;
|
|
260
|
+
if (equityData.length > maxDataPoints) {
|
|
261
|
+
const step = Math.ceil(equityData.length / maxDataPoints);
|
|
262
|
+
equityData = equityData.filter((_, i) => i % step === 0);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const chartConfig = {
|
|
266
|
+
height: 10,
|
|
267
|
+
colors: [equityData[equityData.length - 1] < equityData[0] ? asciichart.red : asciichart.green],
|
|
268
|
+
format: (x) => ('$' + (x / 1000).toFixed(0) + 'K').padStart(yAxisWidth)
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const chart = asciichart.plot(equityData, chartConfig);
|
|
272
|
+
chart.split('\n').forEach(line => {
|
|
273
|
+
let chartLine = ' ' + line;
|
|
274
|
+
const len = chartLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
275
|
+
if (len < chartInnerWidth) chartLine += ' '.repeat(chartInnerWidth - len);
|
|
276
|
+
console.log(chalk.cyan('\u2551') + chartLine + chalk.cyan('\u2551'));
|
|
277
|
+
});
|
|
278
|
+
} else {
|
|
279
|
+
const msg = ' No trade data available';
|
|
280
|
+
console.log(chalk.cyan('\u2551') + chalk.gray(msg) + ' '.repeat(chartInnerWidth - msg.length) + chalk.cyan('\u2551'));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
drawBoxFooter(boxWidth);
|
|
284
|
+
console.log();
|
|
285
|
+
|
|
286
|
+
await inquirer.prompt([{ type: 'input', name: 'continue', message: 'Press Enter to continue...' }]);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
module.exports = { showStats };
|