hedgequantx 2.6.163 → 2.7.0
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 +6 -3
- 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
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Stats equity curve chart
|
|
3
|
+
* @module pages/stats/chart
|
|
4
|
+
*
|
|
5
|
+
* STRICT RULE: Display ONLY values from API - NO estimation/simulation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const asciichart = require('asciichart');
|
|
10
|
+
const { getLogoWidth, drawBoxHeader, drawBoxFooter } = require('../../ui');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render equity curve chart
|
|
14
|
+
* @param {Object} data - Chart data
|
|
15
|
+
* @param {Array} data.allTrades - All trades from API
|
|
16
|
+
* @param {number} data.totalStartingBalance - Starting balance
|
|
17
|
+
* @param {Object} data.connectionTypes - Connection type counts
|
|
18
|
+
*/
|
|
19
|
+
const renderEquityCurve = (data) => {
|
|
20
|
+
const { allTrades, totalStartingBalance, connectionTypes } = data;
|
|
21
|
+
const boxWidth = getLogoWidth();
|
|
22
|
+
const chartInnerWidth = boxWidth - 2;
|
|
23
|
+
|
|
24
|
+
console.log();
|
|
25
|
+
drawBoxHeader('EQUITY CURVE', boxWidth);
|
|
26
|
+
|
|
27
|
+
if (allTrades.length > 0) {
|
|
28
|
+
const yAxisWidth = 10;
|
|
29
|
+
const chartAreaWidth = chartInnerWidth - yAxisWidth - 4;
|
|
30
|
+
|
|
31
|
+
// Build equity curve from trades P&L (100% API data)
|
|
32
|
+
let equityData = [totalStartingBalance || 100000];
|
|
33
|
+
let eqVal = equityData[0];
|
|
34
|
+
allTrades.forEach(trade => {
|
|
35
|
+
eqVal += (trade.profitAndLoss || trade.pnl || 0);
|
|
36
|
+
equityData.push(eqVal);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Downsample if too many data points
|
|
40
|
+
const maxDataPoints = chartAreaWidth - 5;
|
|
41
|
+
if (equityData.length > maxDataPoints) {
|
|
42
|
+
const step = Math.ceil(equityData.length / maxDataPoints);
|
|
43
|
+
equityData = equityData.filter((_, i) => i % step === 0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Chart configuration
|
|
47
|
+
const chartConfig = {
|
|
48
|
+
height: 10,
|
|
49
|
+
colors: [equityData[equityData.length - 1] < equityData[0] ? asciichart.red : asciichart.green],
|
|
50
|
+
format: (x) => ('$' + (x / 1000).toFixed(0) + 'K').padStart(yAxisWidth)
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Render chart
|
|
54
|
+
const chart = asciichart.plot(equityData, chartConfig);
|
|
55
|
+
chart.split('\n').forEach(line => {
|
|
56
|
+
let chartLine = ' ' + line;
|
|
57
|
+
const len = chartLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
58
|
+
if (len < chartInnerWidth) chartLine += ' '.repeat(chartInnerWidth - len);
|
|
59
|
+
console.log(chalk.cyan('\u2551') + chartLine + chalk.cyan('\u2551'));
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
// No trade data message
|
|
63
|
+
const msg = connectionTypes.rithmic > 0
|
|
64
|
+
? ' No trade history (Rithmic does not provide trade history API)'
|
|
65
|
+
: ' No trade data available';
|
|
66
|
+
console.log(chalk.cyan('\u2551') + chalk.gray(msg) + ' '.repeat(Math.max(0, chartInnerWidth - msg.length)) + chalk.cyan('\u2551'));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
drawBoxFooter(boxWidth);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
renderEquityCurve,
|
|
74
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Stats display/rendering functions - Rithmic Only
|
|
3
|
+
* @module pages/stats/display
|
|
4
|
+
*
|
|
5
|
+
* STRICT RULE: Display ONLY values from API - NO estimation/simulation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const { getLogoWidth, drawBoxHeader, drawBoxFooter, getColWidths, draw2ColHeader, draw2ColSeparator, fmtRow } = require('../../ui');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Render account overview and trading performance section
|
|
13
|
+
*/
|
|
14
|
+
const renderOverview = (data) => {
|
|
15
|
+
const { connectionTypes, activeAccounts, totalBalance, totalPnL, totalStartingBalance,
|
|
16
|
+
totalOpenPositions, totalOpenOrders, stats, metrics,
|
|
17
|
+
hasBalanceData, hasPnLData, hasTradeData, connections } = data;
|
|
18
|
+
|
|
19
|
+
const boxWidth = getLogoWidth();
|
|
20
|
+
const { col1, col2 } = getColWidths(boxWidth);
|
|
21
|
+
|
|
22
|
+
// Connection type string (Rithmic only)
|
|
23
|
+
const connTypeStr = connectionTypes.rithmic > 0 ? `Rithmic(${connectionTypes.rithmic})` : '';
|
|
24
|
+
|
|
25
|
+
// Format balance/P&L
|
|
26
|
+
const balanceStr = hasBalanceData ? '$' + totalBalance.toLocaleString(undefined, {minimumFractionDigits: 2}) : 'N/A';
|
|
27
|
+
const pnlStr = hasPnLData
|
|
28
|
+
? (totalPnL >= 0 ? '+' : '') + '$' + totalPnL.toLocaleString(undefined, {minimumFractionDigits: 2}) + (metrics.returnPercent !== 'N/A' ? ' (' + metrics.returnPercent + '%)' : '')
|
|
29
|
+
: 'N/A';
|
|
30
|
+
const startBalStr = totalStartingBalance > 0 ? '$' + totalStartingBalance.toLocaleString(undefined, {minimumFractionDigits: 2}) : 'N/A';
|
|
31
|
+
|
|
32
|
+
// Colors
|
|
33
|
+
const totalBalanceColor = hasBalanceData ? (totalBalance >= 0 ? chalk.green : chalk.red) : chalk.gray;
|
|
34
|
+
const pnlColor = hasPnLData ? (totalPnL >= 0 ? chalk.green : chalk.red) : chalk.gray;
|
|
35
|
+
|
|
36
|
+
drawBoxHeader('HQX STATS', boxWidth);
|
|
37
|
+
draw2ColHeader('ACCOUNT OVERVIEW', 'TRADING PERFORMANCE', boxWidth);
|
|
38
|
+
|
|
39
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Connections:', chalk.cyan(connTypeStr || String(connections || 1)), col1) + chalk.cyan('\u2502') + fmtRow('Total Trades:', hasTradeData || stats.totalTrades > 0 ? chalk.white(String(stats.totalTrades)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
40
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Total Accounts:', chalk.cyan(String(activeAccounts.length)), col1) + chalk.cyan('\u2502') + fmtRow('Winning Trades:', hasTradeData || stats.winningTrades > 0 ? chalk.green(String(stats.winningTrades)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
41
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Total Balance:', totalBalanceColor(balanceStr), col1) + chalk.cyan('\u2502') + fmtRow('Losing Trades:', hasTradeData || stats.losingTrades > 0 ? chalk.red(String(stats.losingTrades)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
42
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Starting Balance:', chalk.white(startBalStr), col1) + chalk.cyan('\u2502') + fmtRow('Win Rate:', metrics.winRate !== 'N/A' ? (parseFloat(metrics.winRate) >= 50 ? chalk.green(metrics.winRate + '%') : chalk.yellow(metrics.winRate + '%')) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
43
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Total P&L:', pnlColor(pnlStr), col1) + chalk.cyan('\u2502') + fmtRow('Long Trades:', hasTradeData ? chalk.white(stats.longTrades + (metrics.longWinRate !== 'N/A' ? ' (' + metrics.longWinRate + '%)' : '')) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
44
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Open Positions:', chalk.white(String(totalOpenPositions)), col1) + chalk.cyan('\u2502') + fmtRow('Short Trades:', hasTradeData ? chalk.white(stats.shortTrades + (metrics.shortWinRate !== 'N/A' ? ' (' + metrics.shortWinRate + '%)' : '')) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
45
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Open Orders:', chalk.white(String(totalOpenOrders)), col1) + chalk.cyan('\u2502') + fmtRow('Volume:', hasTradeData ? chalk.white(stats.totalVolume + ' contracts') : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Render P&L metrics section
|
|
50
|
+
*/
|
|
51
|
+
const renderPnLMetrics = (data) => {
|
|
52
|
+
const { stats, metrics, hasTradeData } = data;
|
|
53
|
+
const boxWidth = getLogoWidth();
|
|
54
|
+
const { col1, col2 } = getColWidths(boxWidth);
|
|
55
|
+
|
|
56
|
+
draw2ColSeparator(boxWidth);
|
|
57
|
+
draw2ColHeader('P&L METRICS', 'RISK METRICS', boxWidth);
|
|
58
|
+
|
|
59
|
+
const pfColor = metrics.profitFactor === '∞' ? chalk.green(metrics.profitFactor)
|
|
60
|
+
: metrics.profitFactor === 'N/A' ? chalk.gray(metrics.profitFactor)
|
|
61
|
+
: parseFloat(metrics.profitFactor) >= 1.5 ? chalk.green(metrics.profitFactor)
|
|
62
|
+
: parseFloat(metrics.profitFactor) >= 1 ? chalk.yellow(metrics.profitFactor)
|
|
63
|
+
: chalk.red(metrics.profitFactor);
|
|
64
|
+
|
|
65
|
+
const worstTradeStr = stats.worstTrade < 0 ? '-$' + Math.abs(stats.worstTrade).toFixed(2) : '$' + stats.worstTrade.toFixed(2);
|
|
66
|
+
const netPnLStr = hasTradeData ? (metrics.netPnL >= 0 ? chalk.green('$' + metrics.netPnL.toFixed(2)) : chalk.red('-$' + Math.abs(metrics.netPnL).toFixed(2))) : chalk.gray('N/A');
|
|
67
|
+
|
|
68
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Net P&L:', netPnLStr, col1) + chalk.cyan('\u2502') + fmtRow('Profit Factor:', pfColor, col2) + chalk.cyan('\u2551'));
|
|
69
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Gross Profit:', hasTradeData ? chalk.green('$' + stats.totalWinAmount.toFixed(2)) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('Max Consec. Wins:', hasTradeData ? chalk.green(String(stats.maxConsecutiveWins)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
70
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Gross Loss:', hasTradeData ? chalk.red('-$' + stats.totalLossAmount.toFixed(2)) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('Max Consec. Loss:', hasTradeData ? (stats.maxConsecutiveLosses > 0 ? chalk.red(String(stats.maxConsecutiveLosses)) : chalk.green('0')) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
71
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Avg Win:', hasTradeData ? chalk.green('$' + metrics.avgWin) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('Best Trade:', hasTradeData ? chalk.green('$' + stats.bestTrade.toFixed(2)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
72
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Avg Loss:', hasTradeData ? (stats.losingTrades > 0 ? chalk.red('-$' + metrics.avgLoss) : chalk.green('$0.00')) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('Worst Trade:', hasTradeData ? (stats.worstTrade < 0 ? chalk.red(worstTradeStr) : chalk.green(worstTradeStr)) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Render quantitative metrics section
|
|
77
|
+
*/
|
|
78
|
+
const renderQuantMetrics = (data) => {
|
|
79
|
+
const { quantMetrics, metrics, hasTradeData } = data;
|
|
80
|
+
const boxWidth = getLogoWidth();
|
|
81
|
+
const { col1, col2 } = getColWidths(boxWidth);
|
|
82
|
+
|
|
83
|
+
draw2ColSeparator(boxWidth);
|
|
84
|
+
draw2ColHeader('QUANTITATIVE METRICS', 'ADVANCED RATIOS', boxWidth);
|
|
85
|
+
|
|
86
|
+
const calmarRatio = quantMetrics.maxDrawdown > 0 && quantMetrics.returnPercent !== 'N/A'
|
|
87
|
+
? (parseFloat(quantMetrics.returnPercent) / quantMetrics.maxDrawdown).toFixed(2) : 'N/A';
|
|
88
|
+
|
|
89
|
+
const sharpeColor = quantMetrics.sharpeRatio === 'N/A' ? chalk.gray : parseFloat(quantMetrics.sharpeRatio) >= 1 ? chalk.green : parseFloat(quantMetrics.sharpeRatio) >= 0.5 ? chalk.yellow : chalk.red;
|
|
90
|
+
const sortinoColor = quantMetrics.sortinoRatio === 'N/A' ? chalk.gray : parseFloat(quantMetrics.sortinoRatio) >= 1.5 ? chalk.green : parseFloat(quantMetrics.sortinoRatio) >= 0.5 ? chalk.yellow : chalk.red;
|
|
91
|
+
const ddColor = quantMetrics.maxDrawdown === 0 ? chalk.gray : quantMetrics.maxDrawdown <= 5 ? chalk.green : quantMetrics.maxDrawdown <= 15 ? chalk.yellow : chalk.red;
|
|
92
|
+
const rrColor = metrics.riskRewardRatio === 'N/A' ? chalk.gray : parseFloat(metrics.riskRewardRatio) >= 2 ? chalk.green : parseFloat(metrics.riskRewardRatio) >= 1 ? chalk.yellow : chalk.red;
|
|
93
|
+
|
|
94
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Sharpe Ratio:', sharpeColor(quantMetrics.sharpeRatio), col1) + chalk.cyan('\u2502') + fmtRow('Risk/Reward:', rrColor(metrics.riskRewardRatio), col2) + chalk.cyan('\u2551'));
|
|
95
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Sortino Ratio:', sortinoColor(quantMetrics.sortinoRatio), col1) + chalk.cyan('\u2502') + fmtRow('Calmar Ratio:', calmarRatio === 'N/A' ? chalk.gray(calmarRatio) : chalk.white(calmarRatio), col2) + chalk.cyan('\u2551'));
|
|
96
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Max Drawdown:', hasTradeData && quantMetrics.maxDrawdown > 0 ? ddColor(quantMetrics.maxDrawdown.toFixed(2) + '%') : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('Expectancy:', hasTradeData ? (metrics.expectancy >= 0 ? chalk.green('$' + metrics.expectancy.toFixed(2)) : chalk.red('$' + metrics.expectancy.toFixed(2))) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
97
|
+
console.log(chalk.cyan('\u2551') + fmtRow('Std Deviation:', hasTradeData ? chalk.white('$' + quantMetrics.stdDev.toFixed(2)) : chalk.gray('N/A'), col1) + chalk.cyan('\u2502') + fmtRow('Avg Trade:', hasTradeData ? (quantMetrics.avgReturn >= 0 ? chalk.green('$' + quantMetrics.avgReturn.toFixed(2)) : chalk.red('$' + quantMetrics.avgReturn.toFixed(2))) : chalk.gray('N/A'), col2) + chalk.cyan('\u2551'));
|
|
98
|
+
|
|
99
|
+
drawBoxFooter(boxWidth);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Render trades history section
|
|
104
|
+
*/
|
|
105
|
+
const renderTradesHistory = (data) => {
|
|
106
|
+
const { allTrades } = data;
|
|
107
|
+
const boxWidth = getLogoWidth();
|
|
108
|
+
const innerWidth = boxWidth - 2;
|
|
109
|
+
|
|
110
|
+
console.log();
|
|
111
|
+
drawBoxHeader('TRADES HISTORY', boxWidth);
|
|
112
|
+
|
|
113
|
+
const extractSymbol = (contractId) => {
|
|
114
|
+
if (!contractId) return 'N/A';
|
|
115
|
+
if (contractId.length <= 10) return contractId;
|
|
116
|
+
return contractId.substring(0, 10);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (allTrades.length > 0) {
|
|
120
|
+
const colTime = 10;
|
|
121
|
+
const colSymbol = 12;
|
|
122
|
+
const colPrice = 12;
|
|
123
|
+
const colPnl = 12;
|
|
124
|
+
const colSide = 6;
|
|
125
|
+
const separators = 15;
|
|
126
|
+
const fixedWidth = colTime + colSymbol + colPrice + colPnl + colSide + separators;
|
|
127
|
+
const colAccount = Math.max(10, innerWidth - fixedWidth);
|
|
128
|
+
|
|
129
|
+
const header = ` ${'Time'.padEnd(colTime)}| ${'Symbol'.padEnd(colSymbol)}| ${'Price'.padEnd(colPrice)}| ${'P&L'.padEnd(colPnl)}| ${'Side'.padEnd(colSide)}| ${'Account'.padEnd(colAccount - 2)}`;
|
|
130
|
+
console.log(chalk.cyan('\u2551') + chalk.white(header) + chalk.cyan('\u2551'));
|
|
131
|
+
console.log(chalk.cyan('\u2551') + chalk.gray('\u2500'.repeat(innerWidth)) + chalk.cyan('\u2551'));
|
|
132
|
+
|
|
133
|
+
const recentTrades = allTrades.slice(-10).reverse();
|
|
134
|
+
|
|
135
|
+
for (const trade of recentTrades) {
|
|
136
|
+
const timestamp = trade.creationTimestamp || trade.timestamp;
|
|
137
|
+
const time = timestamp ? new Date(timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }) : '--:--';
|
|
138
|
+
const symbol = extractSymbol(trade.contractId || trade.symbol);
|
|
139
|
+
const price = (trade.price || 0).toFixed(2);
|
|
140
|
+
const pnl = trade.profitAndLoss || trade.pnl || 0;
|
|
141
|
+
const pnlText = pnl >= 0 ? `+$${pnl.toFixed(0)}` : `-$${Math.abs(pnl).toFixed(0)}`;
|
|
142
|
+
const side = trade.side === 0 ? 'BUY' : trade.side === 1 ? 'SELL' : 'N/A';
|
|
143
|
+
const accountName = (trade.accountName || 'N/A').substring(0, colAccount - 3);
|
|
144
|
+
|
|
145
|
+
const timeStr = time.padEnd(colTime);
|
|
146
|
+
const symbolStr = symbol.padEnd(colSymbol);
|
|
147
|
+
const priceStr = price.padEnd(colPrice);
|
|
148
|
+
const pnlStr = pnlText.padEnd(colPnl);
|
|
149
|
+
const sideStr = side.padEnd(colSide);
|
|
150
|
+
const accountStr = accountName.padEnd(colAccount - 2);
|
|
151
|
+
|
|
152
|
+
const pnlColored = pnl >= 0 ? chalk.green(pnlStr) : chalk.red(pnlStr);
|
|
153
|
+
const sideColored = trade.side === 0 ? chalk.green(sideStr) : chalk.red(sideStr);
|
|
154
|
+
|
|
155
|
+
const row = ` ${timeStr}| ${symbolStr}| ${priceStr}| ${pnlColored}| ${sideColored}| ${accountStr}`;
|
|
156
|
+
console.log(chalk.cyan('\u2551') + row + chalk.cyan('\u2551'));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (allTrades.length > 10) {
|
|
160
|
+
const moreMsg = ` ... and ${allTrades.length - 10} more trades`;
|
|
161
|
+
console.log(chalk.cyan('\u2551') + moreMsg.padEnd(innerWidth) + chalk.cyan('\u2551'));
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
const msg = ' No trade history (Rithmic API limitation)';
|
|
165
|
+
console.log(chalk.cyan('\u2551') + chalk.gray(msg.padEnd(innerWidth)) + chalk.cyan('\u2551'));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
drawBoxFooter(boxWidth);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Render HQX score section
|
|
173
|
+
*/
|
|
174
|
+
const renderHQXScore = (data) => {
|
|
175
|
+
const { hqxScoreData, hasTradeData, stats } = data;
|
|
176
|
+
|
|
177
|
+
if (!hasTradeData && stats.totalTrades === 0) return;
|
|
178
|
+
|
|
179
|
+
const boxWidth = getLogoWidth();
|
|
180
|
+
const innerWidth = boxWidth - 2;
|
|
181
|
+
|
|
182
|
+
console.log();
|
|
183
|
+
drawBoxHeader('HQX SCORE', boxWidth);
|
|
184
|
+
|
|
185
|
+
const scoreColor = hqxScoreData.hqxScore >= 70 ? chalk.green : hqxScoreData.hqxScore >= 50 ? chalk.yellow : chalk.red;
|
|
186
|
+
|
|
187
|
+
const makeBar = (score, width = 30) => {
|
|
188
|
+
const filled = Math.round((score / 100) * width);
|
|
189
|
+
const empty = width - filled;
|
|
190
|
+
const color = score >= 70 ? chalk.green : score >= 50 ? chalk.yellow : chalk.red;
|
|
191
|
+
return color('\u2588'.repeat(filled)) + chalk.gray('\u2591'.repeat(empty));
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const labelWidth = 18;
|
|
195
|
+
|
|
196
|
+
const overallLine = ` OVERALL SCORE: ${scoreColor(String(hqxScoreData.hqxScore))} / 100 [Grade: ${scoreColor(hqxScoreData.scoreGrade)}]`;
|
|
197
|
+
const overallVisLen = overallLine.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
198
|
+
console.log(chalk.cyan('\u2551') + overallLine + ' '.repeat(innerWidth - overallVisLen) + chalk.cyan('\u2551'));
|
|
199
|
+
console.log(chalk.cyan('\u2551') + chalk.gray('\u2500'.repeat(innerWidth)) + chalk.cyan('\u2551'));
|
|
200
|
+
|
|
201
|
+
for (const metric of hqxScoreData.breakdown) {
|
|
202
|
+
const label = (' ' + metric.name + ':').padEnd(labelWidth);
|
|
203
|
+
const bar = makeBar(metric.score);
|
|
204
|
+
const pct = (metric.score.toFixed(0) + '%').padStart(5);
|
|
205
|
+
const line = label + bar + ' ' + pct;
|
|
206
|
+
const visLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
207
|
+
console.log(chalk.cyan('\u2551') + chalk.white(label) + bar + ' ' + chalk.white(pct) + ' '.repeat(innerWidth - visLen) + chalk.cyan('\u2551'));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
drawBoxFooter(boxWidth);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Render data source notice
|
|
215
|
+
*/
|
|
216
|
+
const renderNotice = () => {
|
|
217
|
+
console.log();
|
|
218
|
+
console.log(chalk.gray(' Note: Rithmic API provides balance/P&L only. Trade history not available.'));
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
module.exports = {
|
|
222
|
+
renderOverview,
|
|
223
|
+
renderPnLMetrics,
|
|
224
|
+
renderQuantMetrics,
|
|
225
|
+
renderTradesHistory,
|
|
226
|
+
renderHQXScore,
|
|
227
|
+
renderNotice,
|
|
228
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Stats Page - Rithmic Only
|
|
3
|
+
* @module pages/stats
|
|
4
|
+
*
|
|
5
|
+
* STRICT RULE: Display ONLY values returned by API
|
|
6
|
+
* - Rithmic: Uses PNL_PLANT for balance/P&L, ORDER_PLANT for accounts
|
|
7
|
+
* - NO estimation, NO simulation, NO mock data
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const ora = require('ora');
|
|
11
|
+
|
|
12
|
+
const { connections } = require('../../services');
|
|
13
|
+
const { prompts } = require('../../utils');
|
|
14
|
+
const { aggregateStats, calculateDerivedMetrics, calculateQuantMetrics, calculateHQXScore } = require('./metrics');
|
|
15
|
+
const { renderOverview, renderPnLMetrics, renderQuantMetrics, renderTradesHistory, renderHQXScore, renderNotice } = require('./display');
|
|
16
|
+
const { renderEquityCurve } = require('./chart');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetch account data from all connections
|
|
20
|
+
*/
|
|
21
|
+
const fetchAccountsData = async (allConns) => {
|
|
22
|
+
const connectionTypes = { rithmic: 0 };
|
|
23
|
+
let allAccountsData = [];
|
|
24
|
+
|
|
25
|
+
for (const conn of allConns) {
|
|
26
|
+
const connType = conn.type || 'rithmic';
|
|
27
|
+
const propfirmName = conn.propfirm || conn.type || 'Unknown';
|
|
28
|
+
|
|
29
|
+
if (connType === 'rithmic') connectionTypes.rithmic++;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const result = await conn.service.getTradingAccounts();
|
|
33
|
+
if (result.success && result.accounts && result.accounts.length > 0) {
|
|
34
|
+
result.accounts.forEach(account => {
|
|
35
|
+
allAccountsData.push({
|
|
36
|
+
...account,
|
|
37
|
+
propfirm: propfirmName,
|
|
38
|
+
connectionType: connType,
|
|
39
|
+
service: conn.service
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// Silently skip failed connections
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Remove duplicates by accountId
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
allAccountsData = allAccountsData.filter(acc => {
|
|
51
|
+
const id = String(acc.accountId);
|
|
52
|
+
if (seen.has(id)) return false;
|
|
53
|
+
seen.add(id);
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return { connectionTypes, allAccountsData };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Aggregate data from active accounts
|
|
62
|
+
*/
|
|
63
|
+
const aggregateAccountData = async (activeAccounts) => {
|
|
64
|
+
let totalBalance = 0;
|
|
65
|
+
let totalPnL = 0;
|
|
66
|
+
let totalStartingBalance = 0;
|
|
67
|
+
let allTrades = [];
|
|
68
|
+
let totalOpenPositions = 0;
|
|
69
|
+
let totalOpenOrders = 0;
|
|
70
|
+
let hasBalanceData = false;
|
|
71
|
+
let hasPnLData = false;
|
|
72
|
+
let hasTradeData = false;
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < activeAccounts.length; i++) {
|
|
75
|
+
const account = activeAccounts[i];
|
|
76
|
+
const svc = account.service;
|
|
77
|
+
const connType = account.connectionType || 'rithmic';
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
// Balance from API
|
|
81
|
+
if (account.balance !== null && account.balance !== undefined) {
|
|
82
|
+
totalBalance += account.balance;
|
|
83
|
+
hasBalanceData = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// P&L from API (NEVER calculated locally)
|
|
87
|
+
if (account.profitAndLoss !== null && account.profitAndLoss !== undefined) {
|
|
88
|
+
totalPnL += account.profitAndLoss;
|
|
89
|
+
hasPnLData = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Starting balance
|
|
93
|
+
if (account.startingBalance !== null && account.startingBalance !== undefined) {
|
|
94
|
+
totalStartingBalance += account.startingBalance;
|
|
95
|
+
} else if (account.balance !== null && account.balance !== undefined) {
|
|
96
|
+
const pnl = account.profitAndLoss || 0;
|
|
97
|
+
totalStartingBalance += (account.balance - pnl);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Positions from API
|
|
101
|
+
try {
|
|
102
|
+
const posResult = await svc.getPositions(account.accountId);
|
|
103
|
+
if (posResult.success && posResult.positions) {
|
|
104
|
+
totalOpenPositions += posResult.positions.length;
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {}
|
|
107
|
+
|
|
108
|
+
// Orders from API
|
|
109
|
+
try {
|
|
110
|
+
const ordResult = await svc.getOrders(account.accountId);
|
|
111
|
+
if (ordResult.success && ordResult.orders) {
|
|
112
|
+
totalOpenOrders += ordResult.orders.filter(o => o.status === 1 || o.status === 'Working').length;
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {}
|
|
115
|
+
|
|
116
|
+
// Lifetime stats (Rithmic returns null)
|
|
117
|
+
if (typeof svc.getLifetimeStats === 'function') {
|
|
118
|
+
try {
|
|
119
|
+
const lifetimeResult = await svc.getLifetimeStats(account.accountId);
|
|
120
|
+
if (lifetimeResult.success && lifetimeResult.stats) {
|
|
121
|
+
account.lifetimeStats = lifetimeResult.stats;
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Trade history (Rithmic doesn't provide this)
|
|
127
|
+
if (typeof svc.getTradeHistory === 'function') {
|
|
128
|
+
try {
|
|
129
|
+
const tradesResult = await svc.getTradeHistory(account.accountId, 30);
|
|
130
|
+
if (tradesResult.success && tradesResult.trades && tradesResult.trades.length > 0) {
|
|
131
|
+
hasTradeData = true;
|
|
132
|
+
allTrades = allTrades.concat(tradesResult.trades.map(t => ({
|
|
133
|
+
...t,
|
|
134
|
+
accountName: account.accountName,
|
|
135
|
+
propfirm: account.propfirm,
|
|
136
|
+
connectionType: connType
|
|
137
|
+
})));
|
|
138
|
+
}
|
|
139
|
+
} catch (e) {}
|
|
140
|
+
}
|
|
141
|
+
} catch (e) {}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
totalBalance,
|
|
146
|
+
totalPnL,
|
|
147
|
+
totalStartingBalance,
|
|
148
|
+
allTrades,
|
|
149
|
+
totalOpenPositions,
|
|
150
|
+
totalOpenOrders,
|
|
151
|
+
hasBalanceData,
|
|
152
|
+
hasPnLData,
|
|
153
|
+
hasTradeData,
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Show Stats Page
|
|
159
|
+
*/
|
|
160
|
+
const showStats = async (service) => {
|
|
161
|
+
let spinner;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
spinner = ora({ text: 'Loading stats...', color: 'yellow' }).start();
|
|
165
|
+
|
|
166
|
+
// Get all connections
|
|
167
|
+
const allConns = connections.count() > 0
|
|
168
|
+
? connections.getAll()
|
|
169
|
+
: (service ? [{ service, propfirm: service.propfirm?.name || 'Unknown', type: 'rithmic' }] : []);
|
|
170
|
+
|
|
171
|
+
if (allConns.length === 0) {
|
|
172
|
+
spinner.fail('No connections found');
|
|
173
|
+
await prompts.waitForEnter();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Fetch accounts data
|
|
178
|
+
const { connectionTypes, allAccountsData } = await fetchAccountsData(allConns);
|
|
179
|
+
|
|
180
|
+
if (allAccountsData.length === 0) {
|
|
181
|
+
spinner.fail('No accounts found');
|
|
182
|
+
await prompts.waitForEnter();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Filter active accounts (status === 0)
|
|
187
|
+
const activeAccounts = allAccountsData.filter(acc => acc.status === 0);
|
|
188
|
+
|
|
189
|
+
if (activeAccounts.length === 0) {
|
|
190
|
+
spinner.fail('No active accounts found');
|
|
191
|
+
await prompts.waitForEnter();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Aggregate account data from APIs
|
|
196
|
+
const accountData = await aggregateAccountData(activeAccounts);
|
|
197
|
+
|
|
198
|
+
spinner.succeed('Stats loaded');
|
|
199
|
+
console.log();
|
|
200
|
+
|
|
201
|
+
// Calculate stats from API data
|
|
202
|
+
const stats = aggregateStats(activeAccounts, accountData.allTrades);
|
|
203
|
+
const metrics = calculateDerivedMetrics(stats, accountData.totalStartingBalance, accountData.totalPnL);
|
|
204
|
+
const quantMetrics = calculateQuantMetrics(accountData.allTrades, accountData.totalStartingBalance, accountData.totalPnL);
|
|
205
|
+
const hqxScoreData = calculateHQXScore(stats, metrics, accountData.totalStartingBalance);
|
|
206
|
+
|
|
207
|
+
// Prepare display data
|
|
208
|
+
const displayData = {
|
|
209
|
+
connectionTypes,
|
|
210
|
+
activeAccounts,
|
|
211
|
+
connections: connections.count() || 1,
|
|
212
|
+
stats,
|
|
213
|
+
metrics,
|
|
214
|
+
quantMetrics,
|
|
215
|
+
hqxScoreData,
|
|
216
|
+
allTrades: accountData.allTrades,
|
|
217
|
+
...accountData,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Render all sections
|
|
221
|
+
renderOverview(displayData);
|
|
222
|
+
renderPnLMetrics(displayData);
|
|
223
|
+
renderQuantMetrics(displayData);
|
|
224
|
+
renderEquityCurve(displayData);
|
|
225
|
+
renderTradesHistory(displayData);
|
|
226
|
+
renderHQXScore(displayData);
|
|
227
|
+
renderNotice();
|
|
228
|
+
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (spinner) spinner.fail('Error: ' + error.message);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await prompts.waitForEnter();
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
module.exports = { showStats };
|