hedgequantx 1.3.4 → 1.3.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -152,12 +152,18 @@ class ProjectXService {
152
152
  // ==================== ACCOUNTS ====================
153
153
 
154
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
155
+ * Get trading accounts with REAL P&L from API
156
+ *
157
+ * Data sources (all from userApi):
158
+ * - /TradingAccount: accountId, accountName, balance, status, type
159
+ * - /AccountTemplate/userTemplates: startingBalance
160
+ * - /Position?accountId=X: profitAndLoss (unrealized P&L from open positions)
161
+ *
162
+ * All values come from API. No estimation.
158
163
  */
159
164
  async getTradingAccounts() {
160
165
  try {
166
+ // 1. Get accounts
161
167
  const response = await this._request(this.propfirm.userApi, '/TradingAccount', 'GET');
162
168
  debug('getTradingAccounts response:', JSON.stringify(response.data, null, 2));
163
169
 
@@ -167,126 +173,81 @@ class ProjectXService {
167
173
 
168
174
  const accounts = Array.isArray(response.data) ? response.data : [];
169
175
 
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 };
187
- } catch (error) {
188
- return { success: false, accounts: [], error: error.message };
189
- }
190
- }
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
- }
176
+ // 2. Get account templates (for startingBalance)
177
+ let templates = [];
178
+ try {
179
+ const templateRes = await this._request(this.propfirm.userApi, '/AccountTemplate/userTemplates', 'GET');
180
+ if (templateRes.statusCode === 200 && Array.isArray(templateRes.data)) {
181
+ templates = templateRes.data;
182
+ debug('Templates:', JSON.stringify(templates, null, 2));
247
183
  }
248
- debug(` Total realized P&L: ${totalPnL}`);
249
- return totalPnL;
184
+ } catch (e) {
185
+ debug('Failed to get templates:', e.message);
250
186
  }
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
187
 
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
- );
188
+ const enrichedAccounts = [];
269
189
 
270
- if (response.statusCode === 200 && response.data) {
271
- const positions = response.data.positions || response.data || [];
272
- debug(`_getOpenPositionsPnL: ${positions.length} positions found`);
190
+ for (const account of accounts) {
191
+ // Find matching template for startingBalance
192
+ const template = templates.find(t =>
193
+ account.accountName && (
194
+ account.accountName.includes(t.title) ||
195
+ t.title.includes(account.accountName)
196
+ )
197
+ );
273
198
 
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}`);
199
+ const enriched = {
200
+ accountId: account.accountId,
201
+ accountName: account.accountName,
202
+ balance: account.balance, // From /TradingAccount
203
+ status: account.status, // From /TradingAccount
204
+ type: account.type, // From /TradingAccount
205
+ startingBalance: template?.startingBalance || null, // From /AccountTemplate
206
+ platform: 'ProjectX',
207
+ propfirm: this.propfirm.name,
208
+ openPnL: null,
209
+ profitAndLoss: null,
210
+ };
211
+
212
+ // Get P&L for active accounts only
213
+ if (account.status === 0) {
214
+ // Get unrealized P&L from /Position endpoint (userApi)
215
+ try {
216
+ const posRes = await this._request(
217
+ this.propfirm.userApi,
218
+ `/Position?accountId=${account.accountId}`,
219
+ 'GET'
220
+ );
221
+ debug(`Positions for ${account.accountId}:`, JSON.stringify(posRes.data, null, 2));
222
+
223
+ if (posRes.statusCode === 200 && Array.isArray(posRes.data)) {
224
+ let openPnL = 0;
225
+ for (const pos of posRes.data) {
226
+ if (pos.profitAndLoss !== undefined && pos.profitAndLoss !== null) {
227
+ openPnL += pos.profitAndLoss;
228
+ }
229
+ }
230
+ enriched.openPnL = openPnL;
231
+ enriched.profitAndLoss = openPnL; // Open P&L from positions
280
232
  }
233
+ } catch (e) {
234
+ debug('Failed to get positions:', e.message);
281
235
  }
282
- debug(` Total open P&L: ${totalPnL}`);
283
- return totalPnL;
284
236
  }
237
+
238
+ debug(`Account ${account.accountId}:`, {
239
+ balance: enriched.balance,
240
+ startingBalance: enriched.startingBalance,
241
+ openPnL: enriched.openPnL,
242
+ profitAndLoss: enriched.profitAndLoss
243
+ });
244
+
245
+ enrichedAccounts.push(enriched);
285
246
  }
286
- debug('_getOpenPositionsPnL: API failed or no data');
287
- return null;
288
- } catch (e) {
289
- return null;
247
+
248
+ return { success: true, accounts: enrichedAccounts };
249
+ } catch (error) {
250
+ return { success: false, accounts: [], error: error.message };
290
251
  }
291
252
  }
292
253