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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.7.12",
3
+ "version": "2.7.13",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -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 status1 = ACCOUNT_STATUS[acc1.status] || { text: 'Unknown', color: 'gray' };
121
- const status2 = acc2 ? (ACCOUNT_STATUS[acc2.status] || { text: 'Unknown', color: 'gray' }) : null;
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 type1 = ACCOUNT_TYPE[acc1.type] || { text: 'Unknown', color: 'white' };
126
- const type2 = acc2 ? (ACCOUNT_TYPE[acc2.type] || { text: 'Unknown', color: 'white' }) : null;
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: 0,
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
+ }