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
|
@@ -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
|
|
152
|
-
*
|
|
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
|
-
//
|
|
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,
|
|
170
|
-
status: account.status,
|
|
171
|
-
type: account.type,
|
|
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
|
-
//
|
|
212
|
+
// Get P&L for active accounts only
|
|
182
213
|
if (account.status === 0) {
|
|
183
|
-
// Get
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
}
|