hedgequantx 1.3.2 → 1.3.4
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/package.json +1 -1
- package/src/app.js +45 -21
- package/src/services/projectx/index.js +136 -3
- package/src/services/rithmic/accounts.js +61 -16
- package/src/services/tradovate/index.js +24 -7
- package/src/ui/index.js +3 -8
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -70,30 +70,40 @@ const banner = async () => {
|
|
|
70
70
|
const version = require('../package.json').version;
|
|
71
71
|
|
|
72
72
|
// Get stats if connected (only active accounts: status === 0)
|
|
73
|
+
// STRICT: Only display values from API, no estimation
|
|
73
74
|
let statsInfo = null;
|
|
74
75
|
if (connections.count() > 0) {
|
|
75
76
|
try {
|
|
76
77
|
const allAccounts = await connections.getAllAccounts();
|
|
77
78
|
const activeAccounts = allAccounts.filter(acc => acc.status === 0);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let
|
|
79
|
+
|
|
80
|
+
// Sum only non-null values from API
|
|
81
|
+
let totalBalance = null;
|
|
82
|
+
let totalPnl = null;
|
|
83
|
+
let hasBalanceData = false;
|
|
84
|
+
let hasPnlData = false;
|
|
81
85
|
|
|
82
86
|
activeAccounts.forEach(account => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
// Balance: only sum if API returned a value
|
|
88
|
+
if (account.balance !== null && account.balance !== undefined) {
|
|
89
|
+
totalBalance = (totalBalance || 0) + account.balance;
|
|
90
|
+
hasBalanceData = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// P&L: only sum if API returned a value
|
|
94
|
+
if (account.profitAndLoss !== null && account.profitAndLoss !== undefined) {
|
|
95
|
+
totalPnl = (totalPnl || 0) + account.profitAndLoss;
|
|
96
|
+
hasPnlData = true;
|
|
97
|
+
}
|
|
86
98
|
});
|
|
87
99
|
|
|
88
|
-
const pnl = totalPnl !== 0 ? totalPnl : (totalBalance - totalStartingBalance);
|
|
89
|
-
const pnlPercent = totalStartingBalance > 0 ? ((pnl / totalStartingBalance) * 100).toFixed(1) : '0.0';
|
|
90
|
-
|
|
91
100
|
statsInfo = {
|
|
92
101
|
connections: connections.count(),
|
|
93
102
|
accounts: activeAccounts.length,
|
|
94
|
-
balance: totalBalance,
|
|
95
|
-
pnl:
|
|
96
|
-
|
|
103
|
+
balance: hasBalanceData ? totalBalance : null,
|
|
104
|
+
pnl: hasPnlData ? totalPnl : null,
|
|
105
|
+
// No percentage calculation if no verified data
|
|
106
|
+
pnlPercent: null
|
|
97
107
|
};
|
|
98
108
|
} catch (e) {
|
|
99
109
|
// Ignore errors
|
|
@@ -168,19 +178,33 @@ const banner = async () => {
|
|
|
168
178
|
console.log(chalk.cyan('║') + chalk.white(centerText(tagline, innerWidth)) + chalk.cyan('║'));
|
|
169
179
|
|
|
170
180
|
// Stats bar if connected
|
|
181
|
+
// STRICT: Only display verified values from API, show '--' for unavailable data
|
|
171
182
|
if (statsInfo) {
|
|
172
183
|
console.log(chalk.cyan('╠' + '═'.repeat(innerWidth) + '╣'));
|
|
173
184
|
|
|
174
|
-
const pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
175
|
-
const pnlSign = statsInfo.pnl >= 0 ? '+' : '';
|
|
176
|
-
|
|
177
185
|
const connStr = `Connections: ${statsInfo.connections}`;
|
|
178
186
|
const accStr = `Accounts: ${statsInfo.accounts}`;
|
|
179
|
-
|
|
180
|
-
|
|
187
|
+
|
|
188
|
+
// Balance: show '--' if not available from API
|
|
189
|
+
const balStr = statsInfo.balance !== null
|
|
190
|
+
? `Balance: $${statsInfo.balance.toLocaleString()}`
|
|
191
|
+
: `Balance: --`;
|
|
192
|
+
const balColor = statsInfo.balance !== null ? chalk.green : chalk.gray;
|
|
193
|
+
|
|
194
|
+
// P&L: show '--' if not available from API
|
|
195
|
+
let pnlDisplay;
|
|
196
|
+
let pnlColor;
|
|
197
|
+
if (statsInfo.pnl !== null) {
|
|
198
|
+
const pnlSign = statsInfo.pnl >= 0 ? '+' : '';
|
|
199
|
+
pnlColor = statsInfo.pnl >= 0 ? chalk.green : chalk.red;
|
|
200
|
+
pnlDisplay = `$${statsInfo.pnl.toLocaleString()} (${pnlSign}${statsInfo.pnl.toFixed(1)})`;
|
|
201
|
+
} else {
|
|
202
|
+
pnlColor = chalk.gray;
|
|
203
|
+
pnlDisplay = '--';
|
|
204
|
+
}
|
|
181
205
|
|
|
182
206
|
// Build full stats text and calculate padding
|
|
183
|
-
const statsText = `${connStr} ${accStr} ${balStr} ${
|
|
207
|
+
const statsText = `${connStr} ${accStr} ${balStr} P&L: ${pnlDisplay}`;
|
|
184
208
|
const statsLen = statsText.length;
|
|
185
209
|
const statsLeftPad = Math.floor((innerWidth - statsLen) / 2);
|
|
186
210
|
const statsRightPad = innerWidth - statsLen - statsLeftPad;
|
|
@@ -188,9 +212,9 @@ const banner = async () => {
|
|
|
188
212
|
console.log(chalk.cyan('║') + ' '.repeat(statsLeftPad) +
|
|
189
213
|
chalk.white(connStr) + ' ' +
|
|
190
214
|
chalk.white(accStr) + ' ' +
|
|
191
|
-
chalk.white('Balance: ') +
|
|
192
|
-
chalk.white('P&L: ') + pnlColor(
|
|
193
|
-
' '.repeat(statsRightPad) + chalk.cyan('║')
|
|
215
|
+
chalk.white('Balance: ') + balColor(statsInfo.balance !== null ? `$${statsInfo.balance.toLocaleString()}` : '--') + ' ' +
|
|
216
|
+
chalk.white('P&L: ') + pnlColor(pnlDisplay) +
|
|
217
|
+
' '.repeat(Math.max(0, statsRightPad)) + chalk.cyan('║')
|
|
194
218
|
);
|
|
195
219
|
}
|
|
196
220
|
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ProjectX API Service
|
|
3
3
|
* Main service for ProjectX prop firm connections
|
|
4
|
+
*
|
|
5
|
+
* STRICT RULE: Display ONLY values returned by API. No estimation, no simulation.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
const https = require('https');
|
|
7
9
|
const { PROPFIRMS } = require('../../config');
|
|
10
|
+
|
|
11
|
+
// Debug mode - set HQX_DEBUG=1 to enable
|
|
12
|
+
const DEBUG = process.env.HQX_DEBUG === '1';
|
|
13
|
+
const debug = (...args) => DEBUG && console.log('[ProjectX]', ...args);
|
|
8
14
|
const {
|
|
9
15
|
validateUsername,
|
|
10
16
|
validatePassword,
|
|
@@ -145,18 +151,145 @@ class ProjectXService {
|
|
|
145
151
|
|
|
146
152
|
// ==================== ACCOUNTS ====================
|
|
147
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Get trading accounts - ONLY returns values from API
|
|
156
|
+
* For banner display: returns basic account info quickly
|
|
157
|
+
* Use getAccountPnL() for detailed P&L data
|
|
158
|
+
*/
|
|
148
159
|
async getTradingAccounts() {
|
|
149
160
|
try {
|
|
150
161
|
const response = await this._request(this.propfirm.userApi, '/TradingAccount', 'GET');
|
|
151
|
-
|
|
152
|
-
|
|
162
|
+
debug('getTradingAccounts response:', JSON.stringify(response.data, null, 2));
|
|
163
|
+
|
|
164
|
+
if (response.statusCode !== 200) {
|
|
165
|
+
return { success: false, accounts: [], error: 'Failed to get accounts' };
|
|
153
166
|
}
|
|
154
|
-
|
|
167
|
+
|
|
168
|
+
const accounts = Array.isArray(response.data) ? response.data : [];
|
|
169
|
+
|
|
170
|
+
// Return RAW API data only - no additional calls for speed
|
|
171
|
+
const enrichedAccounts = accounts.map(account => ({
|
|
172
|
+
accountId: account.accountId,
|
|
173
|
+
accountName: account.accountName,
|
|
174
|
+
balance: account.balance, // From API
|
|
175
|
+
status: account.status, // From API
|
|
176
|
+
type: account.type, // From API
|
|
177
|
+
platform: 'ProjectX',
|
|
178
|
+
propfirm: this.propfirm.name,
|
|
179
|
+
// P&L not available from /TradingAccount endpoint
|
|
180
|
+
todayPnL: null,
|
|
181
|
+
openPnL: null,
|
|
182
|
+
profitAndLoss: null,
|
|
183
|
+
startingBalance: null,
|
|
184
|
+
}));
|
|
185
|
+
|
|
186
|
+
return { success: true, accounts: enrichedAccounts };
|
|
155
187
|
} catch (error) {
|
|
156
188
|
return { success: false, accounts: [], error: error.message };
|
|
157
189
|
}
|
|
158
190
|
}
|
|
159
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Get detailed P&L for a specific account
|
|
194
|
+
* Call this separately when P&L details are needed (e.g., stats page)
|
|
195
|
+
*/
|
|
196
|
+
async getAccountPnL(accountId) {
|
|
197
|
+
const todayPnL = await this._getTodayRealizedPnL(accountId);
|
|
198
|
+
const openPnL = await this._getOpenPositionsPnL(accountId);
|
|
199
|
+
|
|
200
|
+
let totalPnL = null;
|
|
201
|
+
if (todayPnL !== null || openPnL !== null) {
|
|
202
|
+
totalPnL = (todayPnL || 0) + (openPnL || 0);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
debug(`Account ${accountId} P&L:`, { todayPnL, openPnL, totalPnL });
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
todayPnL,
|
|
209
|
+
openPnL,
|
|
210
|
+
profitAndLoss: totalPnL
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get today's realized P&L from Trade API
|
|
216
|
+
* Returns null if API fails (not 0)
|
|
217
|
+
* @private
|
|
218
|
+
*/
|
|
219
|
+
async _getTodayRealizedPnL(accountId) {
|
|
220
|
+
try {
|
|
221
|
+
const now = new Date();
|
|
222
|
+
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
223
|
+
|
|
224
|
+
const response = await this._request(
|
|
225
|
+
this.propfirm.gatewayApi, '/api/Trade/search', 'POST',
|
|
226
|
+
{
|
|
227
|
+
accountId: accountId,
|
|
228
|
+
startTimestamp: startOfDay.toISOString(),
|
|
229
|
+
endTimestamp: now.toISOString()
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
if (response.statusCode === 200 && response.data) {
|
|
234
|
+
const trades = Array.isArray(response.data)
|
|
235
|
+
? response.data
|
|
236
|
+
: (response.data.trades || []);
|
|
237
|
+
|
|
238
|
+
debug(`_getTodayRealizedPnL: ${trades.length} trades found`);
|
|
239
|
+
|
|
240
|
+
// Sum P&L from API response only
|
|
241
|
+
let totalPnL = 0;
|
|
242
|
+
for (const trade of trades) {
|
|
243
|
+
if (trade.profitAndLoss !== undefined && trade.profitAndLoss !== null) {
|
|
244
|
+
totalPnL += trade.profitAndLoss;
|
|
245
|
+
debug(` Trade P&L: ${trade.profitAndLoss}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
debug(` Total realized P&L: ${totalPnL}`);
|
|
249
|
+
return totalPnL;
|
|
250
|
+
}
|
|
251
|
+
debug('_getTodayRealizedPnL: API failed or no data');
|
|
252
|
+
return null; // API failed - return null, not 0
|
|
253
|
+
} catch (e) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get unrealized P&L from open positions API
|
|
260
|
+
* Returns null if API fails (not 0)
|
|
261
|
+
* @private
|
|
262
|
+
*/
|
|
263
|
+
async _getOpenPositionsPnL(accountId) {
|
|
264
|
+
try {
|
|
265
|
+
const response = await this._request(
|
|
266
|
+
this.propfirm.gatewayApi, '/api/Position/searchOpen', 'POST',
|
|
267
|
+
{ accountId: accountId }
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
if (response.statusCode === 200 && response.data) {
|
|
271
|
+
const positions = response.data.positions || response.data || [];
|
|
272
|
+
debug(`_getOpenPositionsPnL: ${positions.length} positions found`);
|
|
273
|
+
|
|
274
|
+
if (Array.isArray(positions)) {
|
|
275
|
+
let totalPnL = 0;
|
|
276
|
+
for (const pos of positions) {
|
|
277
|
+
if (pos.profitAndLoss !== undefined && pos.profitAndLoss !== null) {
|
|
278
|
+
totalPnL += pos.profitAndLoss;
|
|
279
|
+
debug(` Position ${pos.symbolId || 'unknown'} P&L: ${pos.profitAndLoss}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
debug(` Total open P&L: ${totalPnL}`);
|
|
283
|
+
return totalPnL;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
debug('_getOpenPositionsPnL: API failed or no data');
|
|
287
|
+
return null;
|
|
288
|
+
} catch (e) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
160
293
|
// ==================== TRADING ====================
|
|
161
294
|
|
|
162
295
|
async getPositions(accountId) {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rithmic Accounts Module
|
|
3
3
|
* Account fetching, PnL, and positions
|
|
4
|
+
*
|
|
5
|
+
* STRICT RULE: Display ONLY values returned by API. No estimation, no simulation.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
const { REQ } = require('./constants');
|
|
7
9
|
|
|
10
|
+
// Debug mode - set HQX_DEBUG=1 to enable
|
|
11
|
+
const DEBUG = process.env.HQX_DEBUG === '1';
|
|
12
|
+
const debug = (...args) => DEBUG && console.log('[Rithmic]', ...args);
|
|
13
|
+
|
|
8
14
|
/**
|
|
9
15
|
* Hash account ID to numeric (for compatibility)
|
|
10
16
|
*/
|
|
@@ -60,10 +66,13 @@ const fetchAccounts = async (service) => {
|
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
/**
|
|
63
|
-
* Get trading accounts
|
|
69
|
+
* Get trading accounts - ONLY returns values from API
|
|
70
|
+
* No estimation, no simulation
|
|
71
|
+
*
|
|
64
72
|
* @param {RithmicService} service - The Rithmic service instance
|
|
65
73
|
*/
|
|
66
74
|
const getTradingAccounts = async (service) => {
|
|
75
|
+
// Fetch accounts if not already loaded
|
|
67
76
|
if (service.accounts.length === 0 && service.orderConn && service.loginInfo) {
|
|
68
77
|
try {
|
|
69
78
|
await fetchAccounts(service);
|
|
@@ -72,40 +81,72 @@ const getTradingAccounts = async (service) => {
|
|
|
72
81
|
}
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
// Request fresh P&L data from API
|
|
85
|
+
if (service.pnlConn && service.accounts.length > 0) {
|
|
86
|
+
await requestPnLSnapshot(service);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const tradingAccounts = service.accounts.map((acc) => {
|
|
90
|
+
// Get P&L data from API (stored in accountPnL map from handlers.js)
|
|
91
|
+
const pnlData = service.accountPnL.get(acc.accountId);
|
|
92
|
+
|
|
93
|
+
// ONLY use values that came from API - null if not available
|
|
94
|
+
let balance = null;
|
|
95
|
+
let todayPnL = null;
|
|
96
|
+
let openPnL = null;
|
|
97
|
+
let closedPnL = null;
|
|
98
|
+
|
|
99
|
+
if (pnlData) {
|
|
100
|
+
// These values come directly from Rithmic API via handleAccountPnLUpdate
|
|
101
|
+
balance = pnlData.accountBalance !== undefined ? pnlData.accountBalance : null;
|
|
102
|
+
openPnL = pnlData.openPositionPnl !== undefined ? pnlData.openPositionPnl : null;
|
|
103
|
+
closedPnL = pnlData.closedPositionPnl !== undefined ? pnlData.closedPositionPnl : null;
|
|
104
|
+
todayPnL = pnlData.dayPnl !== undefined ? pnlData.dayPnl : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Total P&L from API only
|
|
108
|
+
let profitAndLoss = null;
|
|
109
|
+
if (todayPnL !== null) {
|
|
110
|
+
profitAndLoss = todayPnL;
|
|
111
|
+
} else if (openPnL !== null || closedPnL !== null) {
|
|
112
|
+
profitAndLoss = (openPnL || 0) + (closedPnL || 0);
|
|
113
|
+
}
|
|
80
114
|
|
|
81
115
|
return {
|
|
82
116
|
accountId: hashAccountId(acc.accountId),
|
|
83
117
|
rithmicAccountId: acc.accountId,
|
|
84
118
|
accountName: acc.accountName || acc.accountId,
|
|
85
119
|
name: acc.accountName || acc.accountId,
|
|
120
|
+
// From API only - null if not available
|
|
86
121
|
balance: balance,
|
|
87
|
-
|
|
122
|
+
todayPnL: closedPnL, // Realized P&L from API
|
|
123
|
+
openPnL: openPnL, // Unrealized P&L from API
|
|
88
124
|
profitAndLoss: profitAndLoss,
|
|
125
|
+
// No estimation - these are null
|
|
126
|
+
startingBalance: null,
|
|
89
127
|
status: 0,
|
|
90
128
|
platform: 'Rithmic',
|
|
91
129
|
propfirm: service.propfirm.name,
|
|
92
130
|
};
|
|
93
131
|
});
|
|
94
132
|
|
|
133
|
+
// Fallback if no accounts found
|
|
95
134
|
if (tradingAccounts.length === 0 && service.user) {
|
|
96
135
|
const userName = service.user.userName || 'Unknown';
|
|
97
|
-
tradingAccounts
|
|
136
|
+
tradingAccounts.push({
|
|
98
137
|
accountId: hashAccountId(userName),
|
|
99
138
|
rithmicAccountId: userName,
|
|
100
139
|
accountName: userName,
|
|
101
140
|
name: userName,
|
|
102
|
-
balance:
|
|
103
|
-
startingBalance:
|
|
104
|
-
|
|
141
|
+
balance: null,
|
|
142
|
+
startingBalance: null,
|
|
143
|
+
todayPnL: null,
|
|
144
|
+
openPnL: null,
|
|
145
|
+
profitAndLoss: null,
|
|
105
146
|
status: 0,
|
|
106
147
|
platform: 'Rithmic',
|
|
107
148
|
propfirm: service.propfirm.name,
|
|
108
|
-
}
|
|
149
|
+
});
|
|
109
150
|
}
|
|
110
151
|
|
|
111
152
|
return { success: true, accounts: tradingAccounts };
|
|
@@ -128,7 +169,8 @@ const requestPnLSnapshot = async (service) => {
|
|
|
128
169
|
});
|
|
129
170
|
}
|
|
130
171
|
|
|
131
|
-
|
|
172
|
+
// Wait for P&L data to arrive
|
|
173
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
132
174
|
};
|
|
133
175
|
|
|
134
176
|
/**
|
|
@@ -151,10 +193,11 @@ const subscribePnLUpdates = (service) => {
|
|
|
151
193
|
};
|
|
152
194
|
|
|
153
195
|
/**
|
|
154
|
-
* Get positions
|
|
196
|
+
* Get positions - ONLY returns values from API
|
|
155
197
|
* @param {RithmicService} service - The Rithmic service instance
|
|
156
198
|
*/
|
|
157
199
|
const getPositions = async (service) => {
|
|
200
|
+
// Ensure PnL connection is active
|
|
158
201
|
if (!service.pnlConn && service.credentials) {
|
|
159
202
|
await service.connectPnL(service.credentials.username, service.credentials.password);
|
|
160
203
|
await requestPnLSnapshot(service);
|
|
@@ -165,8 +208,10 @@ const getPositions = async (service) => {
|
|
|
165
208
|
exchange: pos.exchange,
|
|
166
209
|
quantity: pos.quantity,
|
|
167
210
|
averagePrice: pos.averagePrice,
|
|
168
|
-
|
|
169
|
-
|
|
211
|
+
// From API only
|
|
212
|
+
unrealizedPnl: pos.openPnl !== undefined ? pos.openPnl : null,
|
|
213
|
+
realizedPnl: pos.closedPnl !== undefined ? pos.closedPnl : null,
|
|
214
|
+
dayPnl: pos.dayPnl !== undefined ? pos.dayPnl : null,
|
|
170
215
|
side: pos.quantity > 0 ? 'LONG' : 'SHORT',
|
|
171
216
|
}));
|
|
172
217
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tradovate Service
|
|
3
3
|
* Main service for Tradovate prop firm connections (Apex, TakeProfitTrader)
|
|
4
|
+
*
|
|
5
|
+
* STRICT RULE: Display ONLY values returned by API. No estimation, no simulation.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
const https = require('https');
|
|
@@ -112,7 +114,8 @@ class TradovateService extends EventEmitter {
|
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
/**
|
|
115
|
-
* Get trading accounts
|
|
117
|
+
* Get trading accounts - ONLY returns values from API
|
|
118
|
+
* No estimation, no simulation
|
|
116
119
|
*/
|
|
117
120
|
async getTradingAccounts() {
|
|
118
121
|
if (this.accounts.length === 0) {
|
|
@@ -121,20 +124,34 @@ class TradovateService extends EventEmitter {
|
|
|
121
124
|
|
|
122
125
|
const tradingAccounts = this.accounts.map((acc) => {
|
|
123
126
|
const cb = acc.cashBalance || {};
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
127
|
+
|
|
128
|
+
// ONLY use values from API - null if not available
|
|
129
|
+
const balance = cb.totalCashValue !== undefined ? cb.totalCashValue :
|
|
130
|
+
(cb.netLiquidatingValue !== undefined ? cb.netLiquidatingValue : null);
|
|
131
|
+
|
|
132
|
+
// P&L from API only - null if not available
|
|
133
|
+
const realizedPnL = cb.realizedPnL !== undefined ? cb.realizedPnL : null;
|
|
134
|
+
const openPnL = cb.openPnL !== undefined ? cb.openPnL : null;
|
|
135
|
+
|
|
136
|
+
// Total P&L from API
|
|
137
|
+
let profitAndLoss = null;
|
|
138
|
+
if (cb.totalPnL !== undefined) {
|
|
139
|
+
profitAndLoss = cb.totalPnL;
|
|
140
|
+
} else if (realizedPnL !== null || openPnL !== null) {
|
|
141
|
+
profitAndLoss = (realizedPnL || 0) + (openPnL || 0);
|
|
142
|
+
}
|
|
128
143
|
|
|
129
144
|
return {
|
|
130
145
|
accountId: acc.id,
|
|
131
146
|
tradovateAccountId: acc.id,
|
|
132
147
|
accountName: acc.name,
|
|
133
148
|
name: acc.name,
|
|
149
|
+
// From API only - null if not available
|
|
134
150
|
balance: balance,
|
|
135
|
-
|
|
136
|
-
profitAndLoss: profitAndLoss,
|
|
151
|
+
todayPnL: realizedPnL,
|
|
137
152
|
openPnL: openPnL,
|
|
153
|
+
profitAndLoss: profitAndLoss,
|
|
154
|
+
startingBalance: null, // API doesn't provide this
|
|
138
155
|
status: acc.active ? 0 : 3,
|
|
139
156
|
platform: 'Tradovate',
|
|
140
157
|
propfirm: this.propfirm.name,
|
package/src/ui/index.js
CHANGED
|
@@ -29,16 +29,11 @@ const { createBoxMenu } = require('./menu');
|
|
|
29
29
|
* This fixes input leaking to bash after session restore or algo trading
|
|
30
30
|
*/
|
|
31
31
|
const prepareStdin = () => {
|
|
32
|
+
// Minimal intervention - just ensure stdin is flowing
|
|
32
33
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
process.stdin.setRawMode(false);
|
|
34
|
+
if (process.stdin.isPaused()) {
|
|
35
|
+
process.stdin.resume();
|
|
36
36
|
}
|
|
37
|
-
// Remove any lingering keypress listeners
|
|
38
|
-
process.stdin.removeAllListeners('keypress');
|
|
39
|
-
process.stdin.removeAllListeners('data');
|
|
40
|
-
// Pause stdin so inquirer can take control
|
|
41
|
-
process.stdin.pause();
|
|
42
37
|
} catch (e) {
|
|
43
38
|
// Ignore errors
|
|
44
39
|
}
|