hedgequantx 1.3.5 → 1.3.6
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
package/src/pages/accounts.js
CHANGED
|
@@ -80,12 +80,14 @@ const showAccounts = async (service) => {
|
|
|
80
80
|
const pf2 = acc2 ? chalk.magenta(acc2.propfirm || 'Unknown') : '';
|
|
81
81
|
console.log(chalk.cyan('║') + fmtRow('PropFirm:', pf1, col1) + chalk.cyan('│') + (acc2 ? fmtRow('PropFirm:', pf2, col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
82
82
|
|
|
83
|
-
// Balance
|
|
84
|
-
const bal1 = acc1.balance
|
|
85
|
-
const bal2 = acc2 ?
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
83
|
+
// Balance - show '--' if null (not available from API)
|
|
84
|
+
const bal1 = acc1.balance;
|
|
85
|
+
const bal2 = acc2 ? acc2.balance : null;
|
|
86
|
+
const balStr1 = bal1 !== null && bal1 !== undefined ? '$' + bal1.toLocaleString() : '--';
|
|
87
|
+
const balStr2 = bal2 !== null && bal2 !== undefined ? '$' + bal2.toLocaleString() : '--';
|
|
88
|
+
const balColor1 = bal1 === null ? chalk.gray : (bal1 >= 0 ? chalk.green : chalk.red);
|
|
89
|
+
const balColor2 = bal2 === null ? chalk.gray : (bal2 >= 0 ? chalk.green : chalk.red);
|
|
90
|
+
console.log(chalk.cyan('║') + fmtRow('Balance:', balColor1(balStr1), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Balance:', balColor2(balStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
89
91
|
|
|
90
92
|
// Status
|
|
91
93
|
const status1 = ACCOUNT_STATUS[acc1.status] || { text: 'Unknown', color: 'gray' };
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
* Rithmic Accounts Module
|
|
3
3
|
* Account fetching, PnL, and positions
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* P&L Data Sources:
|
|
6
|
+
* - accountBalance: from PNL_PLANT API
|
|
7
|
+
* - openPositionPnl: unrealized P&L from API
|
|
8
|
+
* - closedPositionPnl: realized P&L from API
|
|
9
|
+
* - dayPnl: total day P&L from API
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
const { REQ } = require('./constants');
|
|
9
13
|
|
|
10
|
-
// Debug mode
|
|
14
|
+
// Debug mode
|
|
11
15
|
const DEBUG = process.env.HQX_DEBUG === '1';
|
|
12
16
|
const debug = (...args) => DEBUG && console.log('[Rithmic]', ...args);
|
|
13
17
|
|
|
@@ -66,87 +70,88 @@ const fetchAccounts = async (service) => {
|
|
|
66
70
|
};
|
|
67
71
|
|
|
68
72
|
/**
|
|
69
|
-
* Get trading accounts
|
|
70
|
-
* No estimation, no simulation
|
|
71
|
-
*
|
|
73
|
+
* Get trading accounts with P&L data from API
|
|
72
74
|
* @param {RithmicService} service - The Rithmic service instance
|
|
73
75
|
*/
|
|
74
76
|
const getTradingAccounts = async (service) => {
|
|
77
|
+
debug('getTradingAccounts called');
|
|
78
|
+
|
|
75
79
|
// Fetch accounts if not already loaded
|
|
76
80
|
if (service.accounts.length === 0 && service.orderConn && service.loginInfo) {
|
|
77
81
|
try {
|
|
78
82
|
await fetchAccounts(service);
|
|
83
|
+
debug('Accounts fetched:', service.accounts.length);
|
|
79
84
|
} catch (e) {
|
|
80
|
-
|
|
85
|
+
debug('Failed to fetch accounts:', e.message);
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
// Request fresh P&L data
|
|
89
|
+
// Request fresh P&L data if PnL connection exists
|
|
85
90
|
if (service.pnlConn && service.accounts.length > 0) {
|
|
91
|
+
debug('Requesting P&L snapshot...');
|
|
86
92
|
await requestPnLSnapshot(service);
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
|
|
90
|
-
// Get P&L data from
|
|
91
|
-
const pnlData = service.accountPnL.get(acc.accountId);
|
|
95
|
+
let tradingAccounts = service.accounts.map((acc) => {
|
|
96
|
+
// Get P&L data from accountPnL map (populated by PNL_PLANT messages)
|
|
97
|
+
const pnlData = service.accountPnL.get(acc.accountId) || {};
|
|
98
|
+
debug(`Account ${acc.accountId} pnlData:`, pnlData);
|
|
92
99
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
// Use API values if available, otherwise use defaults
|
|
101
|
+
const accountBalance = parseFloat(pnlData.accountBalance || 0);
|
|
102
|
+
const openPnL = parseFloat(pnlData.openPositionPnl || 0);
|
|
103
|
+
const closedPnL = parseFloat(pnlData.closedPositionPnl || 0);
|
|
104
|
+
const dayPnL = parseFloat(pnlData.dayPnl || 0);
|
|
98
105
|
|
|
99
|
-
if
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
// Balance: use API value if available, otherwise default
|
|
107
|
+
const balance = accountBalance > 0 ? accountBalance : service.propfirm.defaultBalance;
|
|
108
|
+
const startingBalance = service.propfirm.defaultBalance;
|
|
109
|
+
|
|
110
|
+
// P&L: prefer dayPnl from API, otherwise calculate
|
|
111
|
+
let profitAndLoss = 0;
|
|
112
|
+
if (dayPnL !== 0) {
|
|
113
|
+
profitAndLoss = dayPnL;
|
|
114
|
+
} else if (openPnL !== 0 || closedPnL !== 0) {
|
|
115
|
+
profitAndLoss = openPnL + closedPnL;
|
|
116
|
+
} else if (accountBalance > 0) {
|
|
117
|
+
profitAndLoss = accountBalance - startingBalance;
|
|
105
118
|
}
|
|
106
119
|
|
|
107
|
-
|
|
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
|
-
}
|
|
120
|
+
debug(` balance: ${balance}, P&L: ${profitAndLoss}`);
|
|
114
121
|
|
|
115
122
|
return {
|
|
116
123
|
accountId: hashAccountId(acc.accountId),
|
|
117
124
|
rithmicAccountId: acc.accountId,
|
|
118
125
|
accountName: acc.accountName || acc.accountId,
|
|
119
126
|
name: acc.accountName || acc.accountId,
|
|
120
|
-
// From API only - null if not available
|
|
121
127
|
balance: balance,
|
|
122
|
-
|
|
123
|
-
openPnL: openPnL, // Unrealized P&L from API
|
|
128
|
+
startingBalance: startingBalance,
|
|
124
129
|
profitAndLoss: profitAndLoss,
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
openPnL: openPnL,
|
|
131
|
+
todayPnL: closedPnL,
|
|
127
132
|
status: 0,
|
|
128
133
|
platform: 'Rithmic',
|
|
129
134
|
propfirm: service.propfirm.name,
|
|
130
135
|
};
|
|
131
136
|
});
|
|
132
137
|
|
|
133
|
-
// Fallback if no accounts
|
|
138
|
+
// Fallback if no accounts
|
|
134
139
|
if (tradingAccounts.length === 0 && service.user) {
|
|
135
140
|
const userName = service.user.userName || 'Unknown';
|
|
136
|
-
tradingAccounts
|
|
141
|
+
tradingAccounts = [{
|
|
137
142
|
accountId: hashAccountId(userName),
|
|
138
143
|
rithmicAccountId: userName,
|
|
139
144
|
accountName: userName,
|
|
140
145
|
name: userName,
|
|
141
|
-
balance:
|
|
142
|
-
startingBalance:
|
|
143
|
-
|
|
144
|
-
openPnL:
|
|
145
|
-
|
|
146
|
+
balance: service.propfirm.defaultBalance,
|
|
147
|
+
startingBalance: service.propfirm.defaultBalance,
|
|
148
|
+
profitAndLoss: 0,
|
|
149
|
+
openPnL: 0,
|
|
150
|
+
todayPnL: 0,
|
|
146
151
|
status: 0,
|
|
147
152
|
platform: 'Rithmic',
|
|
148
153
|
propfirm: service.propfirm.name,
|
|
149
|
-
}
|
|
154
|
+
}];
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
return { success: true, accounts: tradingAccounts };
|
|
@@ -157,8 +162,13 @@ const getTradingAccounts = async (service) => {
|
|
|
157
162
|
* @param {RithmicService} service - The Rithmic service instance
|
|
158
163
|
*/
|
|
159
164
|
const requestPnLSnapshot = async (service) => {
|
|
160
|
-
if (!service.pnlConn || !service.loginInfo)
|
|
165
|
+
if (!service.pnlConn || !service.loginInfo) {
|
|
166
|
+
debug('Cannot request P&L - no pnlConn or loginInfo');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
161
169
|
|
|
170
|
+
debug('Requesting P&L for', service.accounts.length, 'accounts');
|
|
171
|
+
|
|
162
172
|
for (const acc of service.accounts) {
|
|
163
173
|
service.pnlConn.send('RequestPnLPositionSnapshot', {
|
|
164
174
|
templateId: REQ.PNL_POSITION_SNAPSHOT,
|
|
@@ -169,8 +179,9 @@ const requestPnLSnapshot = async (service) => {
|
|
|
169
179
|
});
|
|
170
180
|
}
|
|
171
181
|
|
|
172
|
-
// Wait for
|
|
173
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
182
|
+
// Wait for responses
|
|
183
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
184
|
+
debug('P&L snapshot complete, accountPnL size:', service.accountPnL.size);
|
|
174
185
|
};
|
|
175
186
|
|
|
176
187
|
/**
|
|
@@ -193,11 +204,11 @@ const subscribePnLUpdates = (service) => {
|
|
|
193
204
|
};
|
|
194
205
|
|
|
195
206
|
/**
|
|
196
|
-
* Get positions
|
|
207
|
+
* Get positions
|
|
197
208
|
* @param {RithmicService} service - The Rithmic service instance
|
|
198
209
|
*/
|
|
199
210
|
const getPositions = async (service) => {
|
|
200
|
-
//
|
|
211
|
+
// Connect to PnL if needed
|
|
201
212
|
if (!service.pnlConn && service.credentials) {
|
|
202
213
|
await service.connectPnL(service.credentials.username, service.credentials.password);
|
|
203
214
|
await requestPnLSnapshot(service);
|
|
@@ -208,10 +219,8 @@ const getPositions = async (service) => {
|
|
|
208
219
|
exchange: pos.exchange,
|
|
209
220
|
quantity: pos.quantity,
|
|
210
221
|
averagePrice: pos.averagePrice,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
realizedPnl: pos.closedPnl !== undefined ? pos.closedPnl : null,
|
|
214
|
-
dayPnl: pos.dayPnl !== undefined ? pos.dayPnl : null,
|
|
222
|
+
unrealizedPnl: pos.openPnl,
|
|
223
|
+
realizedPnl: pos.closedPnl,
|
|
215
224
|
side: pos.quantity > 0 ? 'LONG' : 'SHORT',
|
|
216
225
|
}));
|
|
217
226
|
|
|
@@ -94,16 +94,21 @@ class RithmicService extends EventEmitter {
|
|
|
94
94
|
|
|
95
95
|
this.credentials = { username, password };
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
})
|
|
97
|
+
// Connect to PNL_PLANT for balance/P&L data
|
|
98
|
+
try {
|
|
99
|
+
await this.connectPnL(username, password);
|
|
100
|
+
if (this.pnlConn) {
|
|
101
|
+
await requestPnLSnapshot(this);
|
|
102
|
+
subscribePnLUpdates(this);
|
|
103
|
+
}
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// PnL connection failed, continue without it
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get accounts with P&L data (if available)
|
|
109
|
+
const result = await getTradingAccounts(this);
|
|
105
110
|
|
|
106
|
-
resolve({ success: true, user: this.user, accounts:
|
|
111
|
+
resolve({ success: true, user: this.user, accounts: result.accounts });
|
|
107
112
|
});
|
|
108
113
|
|
|
109
114
|
this.orderConn.once('loginFailed', (data) => {
|