hedgequantx 1.3.3 → 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.3",
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": {
@@ -7,6 +7,10 @@
7
7
 
8
8
  const https = require('https');
9
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);
10
14
  const {
11
15
  validateUsername,
12
16
  validatePassword,
@@ -148,52 +152,96 @@ class ProjectXService {
148
152
  // ==================== ACCOUNTS ====================
149
153
 
150
154
  /**
151
- * Get trading accounts - ONLY returns values from API
152
- * P&L comes from: today's trades + open positions (both from API)
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.
153
163
  */
154
164
  async getTradingAccounts() {
155
165
  try {
166
+ // 1. Get accounts
156
167
  const response = await this._request(this.propfirm.userApi, '/TradingAccount', 'GET');
168
+ debug('getTradingAccounts response:', JSON.stringify(response.data, null, 2));
169
+
157
170
  if (response.statusCode !== 200) {
158
171
  return { success: false, accounts: [], error: 'Failed to get accounts' };
159
172
  }
160
173
 
161
174
  const accounts = Array.isArray(response.data) ? response.data : [];
175
+
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));
183
+ }
184
+ } catch (e) {
185
+ debug('Failed to get templates:', e.message);
186
+ }
187
+
162
188
  const enrichedAccounts = [];
163
189
 
164
190
  for (const account of accounts) {
165
- // Start with RAW API data only
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
+ );
198
+
166
199
  const enriched = {
167
200
  accountId: account.accountId,
168
201
  accountName: account.accountName,
169
- balance: account.balance, // From API
170
- status: account.status, // From API
171
- type: account.type, // From API
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
172
206
  platform: 'ProjectX',
173
207
  propfirm: this.propfirm.name,
174
- // P&L fields - will be populated from API calls
175
- todayPnL: null,
176
208
  openPnL: null,
177
209
  profitAndLoss: null,
178
- startingBalance: null,
179
210
  };
180
211
 
181
- // Only fetch P&L for active accounts
212
+ // Get P&L for active accounts only
182
213
  if (account.status === 0) {
183
- // Get today's realized P&L from trades API
184
- const todayPnL = await this._getTodayRealizedPnL(account.accountId);
185
- enriched.todayPnL = todayPnL;
186
-
187
- // Get unrealized P&L from open positions API
188
- const openPnL = await this._getOpenPositionsPnL(account.accountId);
189
- enriched.openPnL = openPnL;
190
-
191
- // Total P&L = realized + unrealized (both from API)
192
- if (todayPnL !== null || openPnL !== null) {
193
- enriched.profitAndLoss = (todayPnL || 0) + (openPnL || 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
232
+ }
233
+ } catch (e) {
234
+ debug('Failed to get positions:', e.message);
194
235
  }
195
236
  }
196
237
 
238
+ debug(`Account ${account.accountId}:`, {
239
+ balance: enriched.balance,
240
+ startingBalance: enriched.startingBalance,
241
+ openPnL: enriched.openPnL,
242
+ profitAndLoss: enriched.profitAndLoss
243
+ });
244
+
197
245
  enrichedAccounts.push(enriched);
198
246
  }
199
247
 
@@ -203,75 +251,6 @@ class ProjectXService {
203
251
  }
204
252
  }
205
253
 
206
- /**
207
- * Get today's realized P&L from Trade API
208
- * Returns null if API fails (not 0)
209
- * @private
210
- */
211
- async _getTodayRealizedPnL(accountId) {
212
- try {
213
- const now = new Date();
214
- const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
215
-
216
- const response = await this._request(
217
- this.propfirm.gatewayApi, '/api/Trade/search', 'POST',
218
- {
219
- accountId: accountId,
220
- startTimestamp: startOfDay.toISOString(),
221
- endTimestamp: now.toISOString()
222
- }
223
- );
224
-
225
- if (response.statusCode === 200 && response.data) {
226
- const trades = Array.isArray(response.data)
227
- ? response.data
228
- : (response.data.trades || []);
229
-
230
- // Sum P&L from API response only
231
- let totalPnL = 0;
232
- for (const trade of trades) {
233
- if (trade.profitAndLoss !== undefined && trade.profitAndLoss !== null) {
234
- totalPnL += trade.profitAndLoss;
235
- }
236
- }
237
- return totalPnL;
238
- }
239
- return null; // API failed - return null, not 0
240
- } catch (e) {
241
- return null;
242
- }
243
- }
244
-
245
- /**
246
- * Get unrealized P&L from open positions API
247
- * Returns null if API fails (not 0)
248
- * @private
249
- */
250
- async _getOpenPositionsPnL(accountId) {
251
- try {
252
- const response = await this._request(
253
- this.propfirm.gatewayApi, '/api/Position/searchOpen', 'POST',
254
- { accountId: accountId }
255
- );
256
-
257
- if (response.statusCode === 200 && response.data) {
258
- const positions = response.data.positions || response.data || [];
259
- if (Array.isArray(positions)) {
260
- let totalPnL = 0;
261
- for (const pos of positions) {
262
- if (pos.profitAndLoss !== undefined && pos.profitAndLoss !== null) {
263
- totalPnL += pos.profitAndLoss;
264
- }
265
- }
266
- return totalPnL;
267
- }
268
- }
269
- return null;
270
- } catch (e) {
271
- return null;
272
- }
273
- }
274
-
275
254
  // ==================== TRADING ====================
276
255
 
277
256
  async getPositions(accountId) {
@@ -7,6 +7,10 @@
7
7
 
8
8
  const { REQ } = require('./constants');
9
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
+
10
14
  /**
11
15
  * Hash account ID to numeric (for compatibility)
12
16
  */
package/src/ui/index.js CHANGED
@@ -29,15 +29,8 @@ 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
- // Remove any raw mode that might be left from previous operations
34
- if (process.stdin.isTTY && process.stdin.isRaw) {
35
- process.stdin.setRawMode(false);
36
- }
37
- // Remove any lingering keypress listeners
38
- process.stdin.removeAllListeners('keypress');
39
- process.stdin.removeAllListeners('data');
40
- // Resume stdin so inquirer can take control
41
34
  if (process.stdin.isPaused()) {
42
35
  process.stdin.resume();
43
36
  }