hedgequantx 2.7.12 → 2.7.13
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/pages/accounts.js +33 -6
- package/src/services/rithmic/accounts.js +120 -1
- package/src/services/rithmic/constants.js +2 -0
- package/src/services/rithmic/handlers.js +43 -0
- package/src/services/rithmic/proto/request_account_rms_info.proto +20 -0
- package/src/services/rithmic/proto/response_account_rms_info.proto +61 -0
package/package.json
CHANGED
package/src/pages/accounts.js
CHANGED
|
@@ -116,14 +116,41 @@ const showAccounts = async (service) => {
|
|
|
116
116
|
const pnlColor2 = pnl2 === null || pnl2 === undefined ? chalk.gray : (pnl2 >= 0 ? chalk.green : chalk.red);
|
|
117
117
|
console.log(chalk.cyan('║') + fmtRow('P&L:', pnlColor1(pnlStr1), col1) + chalk.cyan('│') + (acc2 ? fmtRow('P&L:', pnlColor2(pnlStr2), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
118
118
|
|
|
119
|
-
// Status
|
|
120
|
-
const
|
|
121
|
-
|
|
119
|
+
// Status - handle both string from API and numeric lookup
|
|
120
|
+
const getStatusDisplay = (status) => {
|
|
121
|
+
if (!status && status !== 0) return { text: '--', color: 'gray' };
|
|
122
|
+
if (typeof status === 'string') {
|
|
123
|
+
// Direct string from Rithmic API (e.g., "Active", "Disabled")
|
|
124
|
+
const lowerStatus = status.toLowerCase();
|
|
125
|
+
if (lowerStatus.includes('active') || lowerStatus.includes('open')) return { text: status, color: 'green' };
|
|
126
|
+
if (lowerStatus.includes('disabled') || lowerStatus.includes('closed')) return { text: status, color: 'red' };
|
|
127
|
+
if (lowerStatus.includes('halt')) return { text: status, color: 'red' };
|
|
128
|
+
return { text: status, color: 'yellow' };
|
|
129
|
+
}
|
|
130
|
+
return ACCOUNT_STATUS[status] || { text: 'Unknown', color: 'gray' };
|
|
131
|
+
};
|
|
132
|
+
const status1 = getStatusDisplay(acc1.status);
|
|
133
|
+
const status2 = acc2 ? getStatusDisplay(acc2.status) : null;
|
|
122
134
|
console.log(chalk.cyan('║') + fmtRow('Status:', chalk[status1.color](status1.text), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Status:', chalk[status2.color](status2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
123
135
|
|
|
124
|
-
// Type
|
|
125
|
-
const
|
|
126
|
-
|
|
136
|
+
// Type/Algorithm - handle both string from API and numeric lookup
|
|
137
|
+
const getTypeDisplay = (type, algorithm) => {
|
|
138
|
+
// Prefer algorithm from RMS info if available
|
|
139
|
+
const value = algorithm || type;
|
|
140
|
+
if (!value && value !== 0) return { text: '--', color: 'gray' };
|
|
141
|
+
if (typeof value === 'string') {
|
|
142
|
+
// Direct string from Rithmic API
|
|
143
|
+
const lowerValue = value.toLowerCase();
|
|
144
|
+
if (lowerValue.includes('eval')) return { text: value, color: 'yellow' };
|
|
145
|
+
if (lowerValue.includes('live') || lowerValue.includes('funded')) return { text: value, color: 'green' };
|
|
146
|
+
if (lowerValue.includes('sim') || lowerValue.includes('demo')) return { text: value, color: 'gray' };
|
|
147
|
+
if (lowerValue.includes('express')) return { text: value, color: 'magenta' };
|
|
148
|
+
return { text: value, color: 'cyan' };
|
|
149
|
+
}
|
|
150
|
+
return ACCOUNT_TYPE[value] || { text: 'Unknown', color: 'white' };
|
|
151
|
+
};
|
|
152
|
+
const type1 = getTypeDisplay(acc1.type, acc1.algorithm);
|
|
153
|
+
const type2 = acc2 ? getTypeDisplay(acc2.type, acc2.algorithm) : null;
|
|
127
154
|
console.log(chalk.cyan('║') + fmtRow('Type:', chalk[type1.color](type1.text), col1) + chalk.cyan('│') + (acc2 ? fmtRow('Type:', chalk[type2.color](type2.text), col2) : ' '.repeat(col2)) + chalk.cyan('║'));
|
|
128
155
|
|
|
129
156
|
if (i + 2 < allAccounts.length) {
|
|
@@ -28,6 +28,108 @@ const hashAccountId = (str) => {
|
|
|
28
28
|
return Math.abs(hash);
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Fetch account RMS info (status, limits) from ORDER_PLANT
|
|
33
|
+
* @param {RithmicService} service - The Rithmic service instance
|
|
34
|
+
* @param {string} accountId - The account ID to fetch RMS info for
|
|
35
|
+
*/
|
|
36
|
+
const fetchAccountRmsInfo = async (service, accountId) => {
|
|
37
|
+
if (!service.orderConn || !service.loginInfo) {
|
|
38
|
+
debug('fetchAccountRmsInfo: no connection or loginInfo');
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Initialize map if needed
|
|
43
|
+
if (!service.accountRmsInfo) service.accountRmsInfo = new Map();
|
|
44
|
+
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
const timeout = setTimeout(() => {
|
|
47
|
+
debug('fetchAccountRmsInfo: timeout for', accountId);
|
|
48
|
+
resolve(service.accountRmsInfo.get(accountId) || null);
|
|
49
|
+
}, 3000);
|
|
50
|
+
|
|
51
|
+
const onRmsInfo = (rmsInfo) => {
|
|
52
|
+
if (rmsInfo.accountId === accountId) {
|
|
53
|
+
debug('fetchAccountRmsInfo: received for', accountId);
|
|
54
|
+
clearTimeout(timeout);
|
|
55
|
+
service.removeListener('accountRmsInfoReceived', onRmsInfo);
|
|
56
|
+
resolve(rmsInfo);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
service.on('accountRmsInfoReceived', onRmsInfo);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
debug('fetchAccountRmsInfo: sending RequestAccountRmsInfo for', accountId);
|
|
63
|
+
service.orderConn.send('RequestAccountRmsInfo', {
|
|
64
|
+
templateId: REQ.ACCOUNT_RMS,
|
|
65
|
+
userMsg: ['HQX'],
|
|
66
|
+
fcmId: service.loginInfo.fcmId,
|
|
67
|
+
ibId: service.loginInfo.ibId,
|
|
68
|
+
userType: 3, // USER_TYPE_TRADER
|
|
69
|
+
});
|
|
70
|
+
} catch (e) {
|
|
71
|
+
debug('fetchAccountRmsInfo: error', e.message);
|
|
72
|
+
clearTimeout(timeout);
|
|
73
|
+
service.removeListener('accountRmsInfoReceived', onRmsInfo);
|
|
74
|
+
resolve(null);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Fetch RMS info for all accounts
|
|
81
|
+
* @param {RithmicService} service - The Rithmic service instance
|
|
82
|
+
*/
|
|
83
|
+
const fetchAllAccountsRmsInfo = async (service) => {
|
|
84
|
+
if (!service.orderConn || !service.loginInfo || service.accounts.length === 0) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
debug('fetchAllAccountsRmsInfo: fetching for', service.accounts.length, 'accounts');
|
|
89
|
+
|
|
90
|
+
// Initialize map if needed
|
|
91
|
+
if (!service.accountRmsInfo) service.accountRmsInfo = new Map();
|
|
92
|
+
|
|
93
|
+
return new Promise((resolve) => {
|
|
94
|
+
let receivedCount = 0;
|
|
95
|
+
const expectedCount = service.accounts.length;
|
|
96
|
+
|
|
97
|
+
const timeout = setTimeout(() => {
|
|
98
|
+
debug('fetchAllAccountsRmsInfo: timeout, received', receivedCount, 'of', expectedCount);
|
|
99
|
+
service.removeListener('accountRmsInfoReceived', onRmsInfo);
|
|
100
|
+
resolve();
|
|
101
|
+
}, 5000);
|
|
102
|
+
|
|
103
|
+
const onRmsInfo = (rmsInfo) => {
|
|
104
|
+
receivedCount++;
|
|
105
|
+
debug('fetchAllAccountsRmsInfo: received', receivedCount, 'of', expectedCount);
|
|
106
|
+
if (receivedCount >= expectedCount) {
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
service.removeListener('accountRmsInfoReceived', onRmsInfo);
|
|
109
|
+
resolve();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
service.on('accountRmsInfoReceived', onRmsInfo);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
// Request RMS info - one request returns all accounts
|
|
116
|
+
debug('fetchAllAccountsRmsInfo: sending RequestAccountRmsInfo');
|
|
117
|
+
service.orderConn.send('RequestAccountRmsInfo', {
|
|
118
|
+
templateId: REQ.ACCOUNT_RMS,
|
|
119
|
+
userMsg: ['HQX'],
|
|
120
|
+
fcmId: service.loginInfo.fcmId,
|
|
121
|
+
ibId: service.loginInfo.ibId,
|
|
122
|
+
userType: 3, // USER_TYPE_TRADER
|
|
123
|
+
});
|
|
124
|
+
} catch (e) {
|
|
125
|
+
debug('fetchAllAccountsRmsInfo: error', e.message);
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
service.removeListener('accountRmsInfoReceived', onRmsInfo);
|
|
128
|
+
resolve();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
31
133
|
/**
|
|
32
134
|
* Fetch accounts from ORDER_PLANT
|
|
33
135
|
* @param {RithmicService} service - The Rithmic service instance
|
|
@@ -103,12 +205,22 @@ const getTradingAccounts = async (service) => {
|
|
|
103
205
|
await requestPnLSnapshot(service);
|
|
104
206
|
}
|
|
105
207
|
|
|
208
|
+
// Fetch RMS info (status, limits) for all accounts
|
|
209
|
+
if (service.orderConn && service.accounts.length > 0) {
|
|
210
|
+
debug('Fetching account RMS info...');
|
|
211
|
+
await fetchAllAccountsRmsInfo(service);
|
|
212
|
+
}
|
|
213
|
+
|
|
106
214
|
let tradingAccounts = service.accounts.map((acc) => {
|
|
107
215
|
// Get P&L data from accountPnL map (populated by PNL_PLANT messages)
|
|
108
216
|
const pnlData = service.accountPnL.get(acc.accountId) || {};
|
|
109
217
|
debug(`Account ${acc.accountId} pnlData:`, JSON.stringify(pnlData));
|
|
110
218
|
debug(` accountPnL map size:`, service.accountPnL.size);
|
|
111
219
|
|
|
220
|
+
// Get RMS info (status) from accountRmsInfo map
|
|
221
|
+
const rmsInfo = service.accountRmsInfo?.get(acc.accountId) || {};
|
|
222
|
+
debug(`Account ${acc.accountId} rmsInfo:`, JSON.stringify(rmsInfo));
|
|
223
|
+
|
|
112
224
|
// REAL DATA FROM RITHMIC ONLY - NO DEFAULTS
|
|
113
225
|
const accountBalance = pnlData.accountBalance ? parseFloat(pnlData.accountBalance) : null;
|
|
114
226
|
const openPnL = pnlData.openPositionPnl ? parseFloat(pnlData.openPositionPnl) : null;
|
|
@@ -124,7 +236,12 @@ const getTradingAccounts = async (service) => {
|
|
|
124
236
|
profitAndLoss: dayPnL !== null ? dayPnL : (openPnL !== null || closedPnL !== null ? (openPnL || 0) + (closedPnL || 0) : null),
|
|
125
237
|
openPnL: openPnL,
|
|
126
238
|
todayPnL: closedPnL,
|
|
127
|
-
status:
|
|
239
|
+
status: rmsInfo.status || null, // Real status from API
|
|
240
|
+
algorithm: rmsInfo.algorithm || null, // Trading algorithm/type
|
|
241
|
+
lossLimit: rmsInfo.lossLimit || null,
|
|
242
|
+
minAccountBalance: rmsInfo.minAccountBalance || null,
|
|
243
|
+
buyLimit: rmsInfo.buyLimit || null,
|
|
244
|
+
sellLimit: rmsInfo.sellLimit || null,
|
|
128
245
|
platform: 'Rithmic',
|
|
129
246
|
propfirm: service.propfirm.name,
|
|
130
247
|
};
|
|
@@ -208,6 +325,8 @@ const getPositions = async (service) => {
|
|
|
208
325
|
module.exports = {
|
|
209
326
|
hashAccountId,
|
|
210
327
|
fetchAccounts,
|
|
328
|
+
fetchAccountRmsInfo,
|
|
329
|
+
fetchAllAccountsRmsInfo,
|
|
211
330
|
getTradingAccounts,
|
|
212
331
|
requestPnLSnapshot,
|
|
213
332
|
subscribePnLUpdates,
|
|
@@ -165,6 +165,8 @@ const PROTO_FILES = [
|
|
|
165
165
|
'response_product_codes.proto',
|
|
166
166
|
'request_front_month_contract.proto',
|
|
167
167
|
'response_front_month_contract.proto',
|
|
168
|
+
'request_account_rms_info.proto',
|
|
169
|
+
'response_account_rms_info.proto',
|
|
168
170
|
];
|
|
169
171
|
|
|
170
172
|
// NO STATIC DATA - All contract/symbol info comes from Rithmic API
|
|
@@ -29,6 +29,10 @@ const createOrderHandler = (service) => {
|
|
|
29
29
|
debug('Handling ACCOUNT_LIST (303)');
|
|
30
30
|
handleAccountList(service, data);
|
|
31
31
|
break;
|
|
32
|
+
case RES.ACCOUNT_RMS:
|
|
33
|
+
debug('Handling ACCOUNT_RMS (305)');
|
|
34
|
+
handleAccountRmsInfo(service, data);
|
|
35
|
+
break;
|
|
32
36
|
case RES.TRADE_ROUTES:
|
|
33
37
|
handleTradeRoutes(service, data);
|
|
34
38
|
break;
|
|
@@ -211,6 +215,45 @@ const handleInstrumentPnLUpdate = (service, data) => {
|
|
|
211
215
|
}
|
|
212
216
|
};
|
|
213
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Handle account RMS info response (status, limits, etc.)
|
|
220
|
+
*/
|
|
221
|
+
const handleAccountRmsInfo = (service, data) => {
|
|
222
|
+
try {
|
|
223
|
+
const res = proto.decode('ResponseAccountRmsInfo', data);
|
|
224
|
+
debug('Decoded Account RMS Info:', JSON.stringify(res));
|
|
225
|
+
|
|
226
|
+
if (res.accountId) {
|
|
227
|
+
const rmsInfo = {
|
|
228
|
+
accountId: res.accountId,
|
|
229
|
+
status: res.status || null,
|
|
230
|
+
currency: res.currency || null,
|
|
231
|
+
algorithm: res.algorithm || null,
|
|
232
|
+
lossLimit: res.lossLimit || null,
|
|
233
|
+
minAccountBalance: res.minAccountBalance || null,
|
|
234
|
+
minMarginBalance: res.minMarginBalance || null,
|
|
235
|
+
buyLimit: res.buyLimit || null,
|
|
236
|
+
sellLimit: res.sellLimit || null,
|
|
237
|
+
maxOrderQuantity: res.maxOrderQuantity || null,
|
|
238
|
+
autoLiquidate: res.autoLiquidate || null,
|
|
239
|
+
autoLiquidateThreshold: res.autoLiquidateThreshold || null,
|
|
240
|
+
};
|
|
241
|
+
debug('Account RMS Info for', res.accountId, ':', rmsInfo);
|
|
242
|
+
|
|
243
|
+
// Store RMS info in service
|
|
244
|
+
if (!service.accountRmsInfo) service.accountRmsInfo = new Map();
|
|
245
|
+
service.accountRmsInfo.set(res.accountId, rmsInfo);
|
|
246
|
+
|
|
247
|
+
service.emit('accountRmsInfoReceived', rmsInfo);
|
|
248
|
+
} else if (res.rpCode?.[0] === '0') {
|
|
249
|
+
debug('Account RMS Info complete signal');
|
|
250
|
+
service.emit('accountRmsInfoComplete');
|
|
251
|
+
}
|
|
252
|
+
} catch (e) {
|
|
253
|
+
debug('Error decoding Account RMS Info:', e.message);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
214
257
|
/**
|
|
215
258
|
* Handle exchange order notification (fills/trades)
|
|
216
259
|
* NotifyType: 5 = FILL
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message RequestAccountRmsInfo
|
|
5
|
+
{
|
|
6
|
+
// PB_OFFSET = 100000, is the offset added for each MNM field id
|
|
7
|
+
|
|
8
|
+
enum UserType {
|
|
9
|
+
USER_TYPE_FCM = 1;
|
|
10
|
+
USER_TYPE_IB = 2;
|
|
11
|
+
USER_TYPE_TRADER = 3;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
required int32 template_id = 154467; // PB_OFFSET + MNM_TEMPLATE_ID
|
|
15
|
+
repeated string user_msg = 132760; // PB_OFFSET + MNM_USER_MSG
|
|
16
|
+
|
|
17
|
+
optional string fcm_id = 154013; // PB_OFFSET + MNM_FCM_ID
|
|
18
|
+
optional string ib_id = 154014; // PB_OFFSET + MNM_IB_ID
|
|
19
|
+
optional UserType user_type = 154036; // PB_OFFSET + MNM_USER_TYPE
|
|
20
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message ResponseAccountRmsInfo
|
|
5
|
+
{
|
|
6
|
+
// PB_OFFSET = 100000, is the offset added for each MNM field id
|
|
7
|
+
|
|
8
|
+
// below enum is just for reference only, not used in this message
|
|
9
|
+
enum PresenceBits {
|
|
10
|
+
BUY_LIMIT = 1;
|
|
11
|
+
SELL_LIMIT = 2;
|
|
12
|
+
LOSS_LIMIT = 4;
|
|
13
|
+
MAX_ORDER_QUANTITY = 8;
|
|
14
|
+
MIN_ACCOUNT_BALANCE = 16;
|
|
15
|
+
MIN_MARGIN_BALANCE = 32;
|
|
16
|
+
ALGORITHM = 64;
|
|
17
|
+
STATUS = 128;
|
|
18
|
+
CURRENCY = 256;
|
|
19
|
+
DEFAULT_COMMISSION = 512;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
enum AutoLiquidateFlag {
|
|
23
|
+
ENABLED = 1;
|
|
24
|
+
DISABLED = 2;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
required int32 template_id = 154467; // PB_OFFSET + MNM_TEMPLATE_ID
|
|
29
|
+
repeated string user_msg = 132760; // PB_OFFSET + MNM_USER_MSG
|
|
30
|
+
repeated string rq_handler_rp_code = 132764; // PB_OFFSET + MNM_REQUEST_HANDLER_RESPONSE_CODE
|
|
31
|
+
repeated string rp_code = 132766; // PB_OFFSET + MNM_RESPONSE_CODE
|
|
32
|
+
|
|
33
|
+
optional uint32 presence_bits = 153622; // PB_OFFSET + MNM_FIELD_PRESENCE_BITS
|
|
34
|
+
|
|
35
|
+
optional string fcm_id = 154013; // PB_OFFSET + MNM_FCM_ID
|
|
36
|
+
optional string ib_id = 154014; // PB_OFFSET + MNM_IB_ID
|
|
37
|
+
optional string account_id = 154008; // PB_OFFSET + MNM_ACCOUNT_ID
|
|
38
|
+
|
|
39
|
+
optional string currency = 154383; // PB_OFFSET + MNM_ACCOUNT_CURRENCY
|
|
40
|
+
optional string status = 154003; // PB_OFFSET + MNM_ACCOUNT_STATUS
|
|
41
|
+
optional string algorithm = 150142; // PB_OFFSET + MNM_ALGORITHM
|
|
42
|
+
|
|
43
|
+
optional string auto_liquidate_criteria = 131036; // PB_OFFSET + MNM_ACCOUNT_AUTO_LIQUIDATE_CRITERIA
|
|
44
|
+
|
|
45
|
+
optional AutoLiquidateFlag auto_liquidate = 131035; // PB_OFFSET + MNM_ACCOUNT_AUTO_LIQUIDATE
|
|
46
|
+
optional AutoLiquidateFlag disable_on_auto_liquidate = 131038; // PB_OFFSET + MNM_DISABLE_ON_AUTO_LIQUIDATE_FLAG
|
|
47
|
+
|
|
48
|
+
optional double auto_liquidate_threshold = 131037; // PB_OFFSET + MNM_ACCOUNT_AUTO_LIQUIDATE_THRESHOLD
|
|
49
|
+
optional double auto_liquidate_max_min_account_balance = 131039; // PB_OFFSET + MNM_AUTO_LIQ_MAX_MIN_ACCOUNT_BALANCE
|
|
50
|
+
|
|
51
|
+
optional double loss_limit = 154019; // PB_OFFSET + MNM_LOSS_LIMIT
|
|
52
|
+
optional double min_account_balance = 156968; // PB_OFFSET + MNM_MINIMUM_ACCOUNT_BALANCE
|
|
53
|
+
optional double min_margin_balance = 156976; // PB_OFFSET + MNM_MIN_MARGIN_BALANCE
|
|
54
|
+
optional double default_commission = 153368; // PB_OFFSET + MNM_DEFAULT_COMMISSION
|
|
55
|
+
|
|
56
|
+
optional int32 buy_limit = 154009; // PB_OFFSET + MNM_BUY_LIMIT
|
|
57
|
+
optional int32 max_order_quantity = 110105; // PB_OFFSET + MNM_MAX_LIMIT_QUAN
|
|
58
|
+
optional int32 sell_limit = 154035; // PB_OFFSET + MNM_SELL_LIMIT
|
|
59
|
+
|
|
60
|
+
optional bool check_min_account_balance = 156972; // PB_OFFSET + MNM_CHECK_MIN_ACCT_BALANCE
|
|
61
|
+
}
|