hedgequantx 1.2.34 → 1.2.35
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 +2 -1
- package/src/app.js +114 -5
- package/src/services/rithmic/connection.js +203 -0
- package/src/services/rithmic/constants.js +156 -0
- package/src/services/rithmic/index.js +487 -0
- package/src/services/rithmic/proto/account_pnl_position_update.proto +59 -0
- package/src/services/rithmic/proto/base.proto +7 -0
- package/src/services/rithmic/proto/best_bid_offer.proto +39 -0
- package/src/services/rithmic/proto/exchange_order_notification.proto +140 -0
- package/src/services/rithmic/proto/instrument_pnl_position_update.proto +50 -0
- package/src/services/rithmic/proto/last_trade.proto +53 -0
- package/src/services/rithmic/proto/request_account_list.proto +20 -0
- package/src/services/rithmic/proto/request_cancel_all_orders.proto +15 -0
- package/src/services/rithmic/proto/request_heartbeat.proto +13 -0
- package/src/services/rithmic/proto/request_login.proto +28 -0
- package/src/services/rithmic/proto/request_login_info.proto +10 -0
- package/src/services/rithmic/proto/request_logout.proto +10 -0
- package/src/services/rithmic/proto/request_market_data_update.proto +42 -0
- package/src/services/rithmic/proto/request_new_order.proto +84 -0
- package/src/services/rithmic/proto/request_pnl_position_snapshot.proto +14 -0
- package/src/services/rithmic/proto/request_pnl_position_updates.proto +20 -0
- package/src/services/rithmic/proto/request_rithmic_system_info.proto +8 -0
- package/src/services/rithmic/proto/request_show_order_history.proto +16 -0
- package/src/services/rithmic/proto/request_show_order_history_dates.proto +10 -0
- package/src/services/rithmic/proto/request_show_order_history_summary.proto +14 -0
- package/src/services/rithmic/proto/request_show_orders.proto +14 -0
- package/src/services/rithmic/proto/request_subscribe_for_order_updates.proto +14 -0
- package/src/services/rithmic/proto/request_tick_bar_replay.proto +48 -0
- package/src/services/rithmic/proto/request_trade_routes.proto +11 -0
- package/src/services/rithmic/proto/response_account_list.proto +18 -0
- package/src/services/rithmic/proto/response_heartbeat.proto +14 -0
- package/src/services/rithmic/proto/response_login.proto +18 -0
- package/src/services/rithmic/proto/response_login_info.proto +24 -0
- package/src/services/rithmic/proto/response_logout.proto +11 -0
- package/src/services/rithmic/proto/response_market_data_update.proto +9 -0
- package/src/services/rithmic/proto/response_new_order.proto +18 -0
- package/src/services/rithmic/proto/response_pnl_position_snapshot.proto +11 -0
- package/src/services/rithmic/proto/response_pnl_position_updates.proto +11 -0
- package/src/services/rithmic/proto/response_rithmic_system_info.proto +12 -0
- package/src/services/rithmic/proto/response_show_order_history.proto +11 -0
- package/src/services/rithmic/proto/response_show_order_history_dates.proto +13 -0
- package/src/services/rithmic/proto/response_show_order_history_summary.proto +11 -0
- package/src/services/rithmic/proto/response_show_orders.proto +11 -0
- package/src/services/rithmic/proto/response_subscribe_for_order_updates.proto +11 -0
- package/src/services/rithmic/proto/response_tick_bar_replay.proto +40 -0
- package/src/services/rithmic/proto/response_trade_routes.proto +19 -0
- package/src/services/rithmic/proto/rithmic_order_notification.proto +124 -0
- package/src/services/rithmic/protobuf.js +259 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rithmic Service
|
|
3
|
+
* Main service for Rithmic prop firm connections
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const EventEmitter = require('events');
|
|
7
|
+
const { RithmicConnection } = require('./connection');
|
|
8
|
+
const { proto, decodeAccountPnL } = require('./protobuf');
|
|
9
|
+
const { RITHMIC_ENDPOINTS, RITHMIC_SYSTEMS, REQ, RES, STREAM } = require('./constants');
|
|
10
|
+
|
|
11
|
+
class RithmicService extends EventEmitter {
|
|
12
|
+
constructor(propfirmKey) {
|
|
13
|
+
super();
|
|
14
|
+
this.propfirmKey = propfirmKey;
|
|
15
|
+
this.propfirm = this.getPropFirmConfig(propfirmKey);
|
|
16
|
+
this.orderConn = null;
|
|
17
|
+
this.pnlConn = null;
|
|
18
|
+
this.loginInfo = null;
|
|
19
|
+
this.accounts = [];
|
|
20
|
+
this.accountPnL = new Map(); // accountId -> pnl data
|
|
21
|
+
this.user = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get PropFirm configuration
|
|
26
|
+
*/
|
|
27
|
+
getPropFirmConfig(key) {
|
|
28
|
+
const propfirms = {
|
|
29
|
+
'apex': { name: 'Apex Trader Funding', systemName: RITHMIC_SYSTEMS.APEX, defaultBalance: 300000 },
|
|
30
|
+
'topstep_r': { name: 'Topstep (Rithmic)', systemName: RITHMIC_SYSTEMS.TOPSTEP, defaultBalance: 150000 },
|
|
31
|
+
'bulenox_r': { name: 'Bulenox (Rithmic)', systemName: RITHMIC_SYSTEMS.BULENOX, defaultBalance: 150000 },
|
|
32
|
+
'earn2trade': { name: 'Earn2Trade', systemName: RITHMIC_SYSTEMS.EARN_2_TRADE, defaultBalance: 150000 },
|
|
33
|
+
'mescapital': { name: 'MES Capital', systemName: RITHMIC_SYSTEMS.MES_CAPITAL, defaultBalance: 150000 },
|
|
34
|
+
'tradefundrr': { name: 'TradeFundrr', systemName: RITHMIC_SYSTEMS.TRADEFUNDRR, defaultBalance: 150000 },
|
|
35
|
+
'thetradingpit': { name: 'The Trading Pit', systemName: RITHMIC_SYSTEMS.THE_TRADING_PIT, defaultBalance: 150000 },
|
|
36
|
+
'fundedfutures': { name: 'Funded Futures Network', systemName: RITHMIC_SYSTEMS.FUNDED_FUTURES_NETWORK, defaultBalance: 150000 },
|
|
37
|
+
'propshop': { name: 'PropShop Trader', systemName: RITHMIC_SYSTEMS.PROPSHOP_TRADER, defaultBalance: 150000 },
|
|
38
|
+
'4proptrader': { name: '4PropTrader', systemName: RITHMIC_SYSTEMS.FOUR_PROP_TRADER, defaultBalance: 150000 },
|
|
39
|
+
'daytraders': { name: 'DayTraders.com', systemName: RITHMIC_SYSTEMS.DAY_TRADERS, defaultBalance: 150000 },
|
|
40
|
+
'10xfutures': { name: '10X Futures', systemName: RITHMIC_SYSTEMS.TEN_X_FUTURES, defaultBalance: 150000 },
|
|
41
|
+
'lucidtrading': { name: 'Lucid Trading', systemName: RITHMIC_SYSTEMS.LUCID_TRADING, defaultBalance: 150000 },
|
|
42
|
+
'thrivetrading': { name: 'Thrive Trading', systemName: RITHMIC_SYSTEMS.THRIVE_TRADING, defaultBalance: 150000 },
|
|
43
|
+
'legendstrading': { name: 'Legends Trading', systemName: RITHMIC_SYSTEMS.LEGENDS_TRADING, defaultBalance: 150000 },
|
|
44
|
+
};
|
|
45
|
+
return propfirms[key] || { name: key, systemName: 'Rithmic Paper Trading', defaultBalance: 150000 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Login to Rithmic
|
|
50
|
+
*/
|
|
51
|
+
async login(username, password) {
|
|
52
|
+
try {
|
|
53
|
+
// Connect to ORDER_PLANT
|
|
54
|
+
this.orderConn = new RithmicConnection();
|
|
55
|
+
|
|
56
|
+
const config = {
|
|
57
|
+
uri: RITHMIC_ENDPOINTS.PAPER,
|
|
58
|
+
systemName: this.propfirm.systemName,
|
|
59
|
+
userId: username,
|
|
60
|
+
password: password,
|
|
61
|
+
appName: 'HQX-CLI',
|
|
62
|
+
appVersion: '1.0.0',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await this.orderConn.connect(config);
|
|
66
|
+
|
|
67
|
+
// Setup message handler for ORDER_PLANT
|
|
68
|
+
this.orderConn.on('message', (msg) => this.handleOrderMessage(msg));
|
|
69
|
+
|
|
70
|
+
// Login
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const timeout = setTimeout(() => {
|
|
73
|
+
reject(new Error('Login timeout'));
|
|
74
|
+
}, 15000);
|
|
75
|
+
|
|
76
|
+
this.orderConn.once('loggedIn', async (data) => {
|
|
77
|
+
clearTimeout(timeout);
|
|
78
|
+
this.loginInfo = data;
|
|
79
|
+
this.user = { userName: username };
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Get accounts
|
|
83
|
+
await this.fetchAccounts();
|
|
84
|
+
resolve({ success: true });
|
|
85
|
+
} catch (e) {
|
|
86
|
+
resolve({ success: false, error: e.message });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
this.orderConn.once('loginFailed', (data) => {
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
resolve({ success: false, error: data.message || 'Login failed' });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.orderConn.login('ORDER_PLANT');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return { success: false, error: error.message };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Connect to PNL_PLANT for balance data
|
|
105
|
+
*/
|
|
106
|
+
async connectPnL(username, password) {
|
|
107
|
+
try {
|
|
108
|
+
this.pnlConn = new RithmicConnection();
|
|
109
|
+
|
|
110
|
+
const config = {
|
|
111
|
+
uri: RITHMIC_ENDPOINTS.PAPER,
|
|
112
|
+
systemName: this.propfirm.systemName,
|
|
113
|
+
userId: username,
|
|
114
|
+
password: password,
|
|
115
|
+
appName: 'HQX-CLI',
|
|
116
|
+
appVersion: '1.0.0',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
await this.pnlConn.connect(config);
|
|
120
|
+
this.pnlConn.on('message', (msg) => this.handlePnLMessage(msg));
|
|
121
|
+
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
const timeout = setTimeout(() => resolve(false), 10000);
|
|
124
|
+
|
|
125
|
+
this.pnlConn.once('loggedIn', () => {
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
resolve(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this.pnlConn.once('loginFailed', () => {
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
resolve(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
this.pnlConn.login('PNL_PLANT');
|
|
136
|
+
});
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Fetch accounts from ORDER_PLANT
|
|
144
|
+
*/
|
|
145
|
+
async fetchAccounts() {
|
|
146
|
+
if (!this.orderConn || !this.loginInfo) {
|
|
147
|
+
throw new Error('Not connected');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Request login info first
|
|
151
|
+
await this.requestLoginInfo();
|
|
152
|
+
|
|
153
|
+
// Then request accounts
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
const accounts = [];
|
|
156
|
+
let completed = false;
|
|
157
|
+
|
|
158
|
+
const timeout = setTimeout(() => {
|
|
159
|
+
if (!completed) {
|
|
160
|
+
completed = true;
|
|
161
|
+
this.accounts = accounts;
|
|
162
|
+
resolve(accounts);
|
|
163
|
+
}
|
|
164
|
+
}, 5000);
|
|
165
|
+
|
|
166
|
+
const handleAccount = (account) => {
|
|
167
|
+
accounts.push(account);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
this.once('accountReceived', handleAccount);
|
|
171
|
+
this.once('accountListComplete', () => {
|
|
172
|
+
if (!completed) {
|
|
173
|
+
completed = true;
|
|
174
|
+
clearTimeout(timeout);
|
|
175
|
+
this.accounts = accounts;
|
|
176
|
+
resolve(accounts);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Request account list
|
|
181
|
+
this.orderConn.send('RequestAccountList', {
|
|
182
|
+
templateId: REQ.ACCOUNT_LIST,
|
|
183
|
+
userMsg: ['HQX'],
|
|
184
|
+
fcmId: this.loginInfo.fcmId,
|
|
185
|
+
ibId: this.loginInfo.ibId,
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Request login info
|
|
192
|
+
*/
|
|
193
|
+
async requestLoginInfo() {
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
const timeout = setTimeout(() => resolve(), 3000);
|
|
196
|
+
|
|
197
|
+
this.once('loginInfoReceived', (info) => {
|
|
198
|
+
clearTimeout(timeout);
|
|
199
|
+
this.loginInfo = { ...this.loginInfo, ...info };
|
|
200
|
+
resolve(info);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
this.orderConn.send('RequestLoginInfo', {
|
|
204
|
+
templateId: REQ.LOGIN_INFO,
|
|
205
|
+
userMsg: ['HQX'],
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get trading accounts (formatted like ProjectX)
|
|
212
|
+
*/
|
|
213
|
+
async getTradingAccounts() {
|
|
214
|
+
if (this.accounts.length === 0) {
|
|
215
|
+
await this.fetchAccounts();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const tradingAccounts = this.accounts.map((acc, index) => {
|
|
219
|
+
const pnl = this.accountPnL.get(acc.accountId) || {};
|
|
220
|
+
const balance = parseFloat(pnl.accountBalance || pnl.marginBalance || pnl.cashOnHand || 0) || this.propfirm.defaultBalance;
|
|
221
|
+
const startingBalance = this.propfirm.defaultBalance;
|
|
222
|
+
const profitAndLoss = balance - startingBalance;
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
accountId: this.hashAccountId(acc.accountId),
|
|
226
|
+
rithmicAccountId: acc.accountId,
|
|
227
|
+
accountName: acc.accountName || acc.accountId,
|
|
228
|
+
name: acc.accountName || acc.accountId,
|
|
229
|
+
balance: balance,
|
|
230
|
+
startingBalance: startingBalance,
|
|
231
|
+
profitAndLoss: profitAndLoss,
|
|
232
|
+
status: 0, // Active
|
|
233
|
+
platform: 'Rithmic',
|
|
234
|
+
propfirm: this.propfirm.name,
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return { success: true, accounts: tradingAccounts };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Request PnL snapshot for accounts
|
|
243
|
+
*/
|
|
244
|
+
async requestPnLSnapshot() {
|
|
245
|
+
if (!this.pnlConn || !this.loginInfo) return;
|
|
246
|
+
|
|
247
|
+
for (const acc of this.accounts) {
|
|
248
|
+
this.pnlConn.send('RequestPnLPositionSnapshot', {
|
|
249
|
+
templateId: REQ.PNL_POSITION_SNAPSHOT,
|
|
250
|
+
userMsg: ['HQX'],
|
|
251
|
+
fcmId: acc.fcmId || this.loginInfo.fcmId,
|
|
252
|
+
ibId: acc.ibId || this.loginInfo.ibId,
|
|
253
|
+
accountId: acc.accountId,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Wait for responses
|
|
258
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Subscribe to PnL updates
|
|
263
|
+
*/
|
|
264
|
+
subscribePnLUpdates() {
|
|
265
|
+
if (!this.pnlConn || !this.loginInfo) return;
|
|
266
|
+
|
|
267
|
+
for (const acc of this.accounts) {
|
|
268
|
+
this.pnlConn.send('RequestPnLPositionUpdates', {
|
|
269
|
+
templateId: REQ.PNL_POSITION_UPDATES,
|
|
270
|
+
userMsg: ['HQX'],
|
|
271
|
+
request: 1, // Subscribe
|
|
272
|
+
fcmId: acc.fcmId || this.loginInfo.fcmId,
|
|
273
|
+
ibId: acc.ibId || this.loginInfo.ibId,
|
|
274
|
+
accountId: acc.accountId,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Handle ORDER_PLANT messages
|
|
281
|
+
*/
|
|
282
|
+
handleOrderMessage(msg) {
|
|
283
|
+
const { templateId, data } = msg;
|
|
284
|
+
|
|
285
|
+
switch (templateId) {
|
|
286
|
+
case RES.LOGIN_INFO:
|
|
287
|
+
this.onLoginInfo(data);
|
|
288
|
+
break;
|
|
289
|
+
case RES.ACCOUNT_LIST:
|
|
290
|
+
this.onAccountList(data);
|
|
291
|
+
break;
|
|
292
|
+
case RES.TRADE_ROUTES:
|
|
293
|
+
this.onTradeRoutes(data);
|
|
294
|
+
break;
|
|
295
|
+
case STREAM.EXCHANGE_NOTIFICATION:
|
|
296
|
+
this.onExchangeNotification(data);
|
|
297
|
+
break;
|
|
298
|
+
case STREAM.ORDER_NOTIFICATION:
|
|
299
|
+
this.onOrderNotification(data);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Handle PNL_PLANT messages
|
|
306
|
+
*/
|
|
307
|
+
handlePnLMessage(msg) {
|
|
308
|
+
const { templateId, data } = msg;
|
|
309
|
+
|
|
310
|
+
switch (templateId) {
|
|
311
|
+
case RES.PNL_POSITION_SNAPSHOT:
|
|
312
|
+
case RES.PNL_POSITION_UPDATES:
|
|
313
|
+
// OK response
|
|
314
|
+
break;
|
|
315
|
+
case STREAM.ACCOUNT_PNL_UPDATE:
|
|
316
|
+
this.onAccountPnLUpdate(data);
|
|
317
|
+
break;
|
|
318
|
+
case STREAM.INSTRUMENT_PNL_UPDATE:
|
|
319
|
+
this.onInstrumentPnLUpdate(data);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
onLoginInfo(data) {
|
|
325
|
+
try {
|
|
326
|
+
const res = proto.decode('ResponseLoginInfo', data);
|
|
327
|
+
this.emit('loginInfoReceived', {
|
|
328
|
+
fcmId: res.fcmId,
|
|
329
|
+
ibId: res.ibId,
|
|
330
|
+
firstName: res.firstName,
|
|
331
|
+
lastName: res.lastName,
|
|
332
|
+
userType: res.userType,
|
|
333
|
+
});
|
|
334
|
+
} catch (e) {
|
|
335
|
+
// Ignore
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
onAccountList(data) {
|
|
340
|
+
try {
|
|
341
|
+
const res = proto.decode('ResponseAccountList', data);
|
|
342
|
+
|
|
343
|
+
if (res.rpCode?.[0] === '0') {
|
|
344
|
+
// End of list
|
|
345
|
+
this.emit('accountListComplete');
|
|
346
|
+
} else if (res.accountId) {
|
|
347
|
+
const account = {
|
|
348
|
+
fcmId: res.fcmId,
|
|
349
|
+
ibId: res.ibId,
|
|
350
|
+
accountId: res.accountId,
|
|
351
|
+
accountName: res.accountName,
|
|
352
|
+
accountCurrency: res.accountCurrency,
|
|
353
|
+
};
|
|
354
|
+
this.accounts.push(account);
|
|
355
|
+
this.emit('accountReceived', account);
|
|
356
|
+
}
|
|
357
|
+
} catch (e) {
|
|
358
|
+
// Ignore
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
onTradeRoutes(data) {
|
|
363
|
+
try {
|
|
364
|
+
const res = proto.decode('ResponseTradeRoutes', data);
|
|
365
|
+
this.emit('tradeRoutes', res);
|
|
366
|
+
} catch (e) {
|
|
367
|
+
// Ignore
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
onAccountPnLUpdate(data) {
|
|
372
|
+
try {
|
|
373
|
+
const pnl = decodeAccountPnL(data);
|
|
374
|
+
if (pnl.accountId) {
|
|
375
|
+
this.accountPnL.set(pnl.accountId, {
|
|
376
|
+
accountBalance: parseFloat(pnl.accountBalance || 0),
|
|
377
|
+
cashOnHand: parseFloat(pnl.cashOnHand || 0),
|
|
378
|
+
marginBalance: parseFloat(pnl.marginBalance || 0),
|
|
379
|
+
openPositionPnl: parseFloat(pnl.openPositionPnl || 0),
|
|
380
|
+
closedPositionPnl: parseFloat(pnl.closedPositionPnl || 0),
|
|
381
|
+
dayPnl: parseFloat(pnl.dayPnl || 0),
|
|
382
|
+
});
|
|
383
|
+
this.emit('pnlUpdate', pnl);
|
|
384
|
+
}
|
|
385
|
+
} catch (e) {
|
|
386
|
+
// Ignore
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
onInstrumentPnLUpdate(data) {
|
|
391
|
+
// Handle instrument-level PnL if needed
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
onExchangeNotification(data) {
|
|
395
|
+
this.emit('exchangeNotification', data);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
onOrderNotification(data) {
|
|
399
|
+
this.emit('orderNotification', data);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Hash account ID to numeric (for compatibility)
|
|
404
|
+
*/
|
|
405
|
+
hashAccountId(str) {
|
|
406
|
+
let hash = 0;
|
|
407
|
+
for (let i = 0; i < str.length; i++) {
|
|
408
|
+
const char = str.charCodeAt(i);
|
|
409
|
+
hash = (hash << 5) - hash + char;
|
|
410
|
+
hash = hash & hash;
|
|
411
|
+
}
|
|
412
|
+
return Math.abs(hash);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get user info
|
|
417
|
+
*/
|
|
418
|
+
async getUser() {
|
|
419
|
+
return this.user;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Check market hours (same as ProjectX)
|
|
424
|
+
*/
|
|
425
|
+
checkMarketHours() {
|
|
426
|
+
const now = new Date();
|
|
427
|
+
const utcDay = now.getUTCDay();
|
|
428
|
+
const utcHour = now.getUTCHours();
|
|
429
|
+
const utcMinute = now.getUTCMinutes();
|
|
430
|
+
const utcTime = utcHour * 60 + utcMinute;
|
|
431
|
+
|
|
432
|
+
// CME Futures: Sunday 5PM CT - Friday 4PM CT
|
|
433
|
+
// CT = UTC-6 (CST) or UTC-5 (CDT)
|
|
434
|
+
const ctOffset = this.isDST(now) ? 5 : 6;
|
|
435
|
+
const ctHour = (utcHour - ctOffset + 24) % 24;
|
|
436
|
+
const ctDay = utcHour < ctOffset ? (utcDay + 6) % 7 : utcDay;
|
|
437
|
+
|
|
438
|
+
// Market closed Saturday all day
|
|
439
|
+
if (ctDay === 6) {
|
|
440
|
+
return { isOpen: false, message: 'Market closed (Saturday)' };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Sunday before 5PM CT
|
|
444
|
+
if (ctDay === 0 && ctHour < 17) {
|
|
445
|
+
return { isOpen: false, message: 'Market opens Sunday 5:00 PM CT' };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Friday after 4PM CT
|
|
449
|
+
if (ctDay === 5 && ctHour >= 16) {
|
|
450
|
+
return { isOpen: false, message: 'Market closed (Friday after 4PM CT)' };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Daily maintenance 4PM-5PM CT (Mon-Thu)
|
|
454
|
+
if (ctHour === 16 && ctDay >= 1 && ctDay <= 4) {
|
|
455
|
+
return { isOpen: false, message: 'Daily maintenance (4:00-5:00 PM CT)' };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return { isOpen: true, message: 'Market is open' };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
isDST(date) {
|
|
462
|
+
const jan = new Date(date.getFullYear(), 0, 1);
|
|
463
|
+
const jul = new Date(date.getFullYear(), 6, 1);
|
|
464
|
+
const stdOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
|
|
465
|
+
return date.getTimezoneOffset() < stdOffset;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Disconnect all connections
|
|
470
|
+
*/
|
|
471
|
+
async disconnect() {
|
|
472
|
+
if (this.orderConn) {
|
|
473
|
+
await this.orderConn.disconnect();
|
|
474
|
+
this.orderConn = null;
|
|
475
|
+
}
|
|
476
|
+
if (this.pnlConn) {
|
|
477
|
+
await this.pnlConn.disconnect();
|
|
478
|
+
this.pnlConn = null;
|
|
479
|
+
}
|
|
480
|
+
this.accounts = [];
|
|
481
|
+
this.accountPnL.clear();
|
|
482
|
+
this.loginInfo = null;
|
|
483
|
+
this.user = null;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
module.exports = { RithmicService, RITHMIC_SYSTEMS, RITHMIC_ENDPOINTS };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message AccountPnLPositionUpdate
|
|
5
|
+
{
|
|
6
|
+
required int32 template_id = 154467;
|
|
7
|
+
optional bool is_snapshot = 110121;
|
|
8
|
+
|
|
9
|
+
optional string fcm_id = 154013;
|
|
10
|
+
optional string ib_id = 154014;
|
|
11
|
+
optional string account_id = 154008;
|
|
12
|
+
|
|
13
|
+
optional int32 fill_buy_qty = 154041;
|
|
14
|
+
optional int32 fill_sell_qty = 154042;
|
|
15
|
+
|
|
16
|
+
optional int32 order_buy_qty = 154037;
|
|
17
|
+
optional int32 order_sell_qty = 154038;
|
|
18
|
+
|
|
19
|
+
optional int32 buy_qty = 154260;
|
|
20
|
+
optional int32 sell_qty = 154261;
|
|
21
|
+
|
|
22
|
+
optional string open_long_options_value = 157105;
|
|
23
|
+
optional string open_short_options_value = 157106;
|
|
24
|
+
optional string closed_options_value = 157107;
|
|
25
|
+
optional string option_cash_reserved = 157111;
|
|
26
|
+
optional string rms_account_commission = 157113;
|
|
27
|
+
|
|
28
|
+
optional string open_position_pnl = 156961;
|
|
29
|
+
optional int32 open_position_quantity = 156962;
|
|
30
|
+
optional string closed_position_pnl = 156963;
|
|
31
|
+
|
|
32
|
+
optional int32 closed_position_quantity = 156964;
|
|
33
|
+
optional int32 net_quantity = 156967;
|
|
34
|
+
|
|
35
|
+
optional string excess_buy_margin = 156991;
|
|
36
|
+
optional string margin_balance = 156977;
|
|
37
|
+
optional string min_margin_balance = 156976;
|
|
38
|
+
optional string min_account_balance = 156968;
|
|
39
|
+
optional string account_balance = 156970;
|
|
40
|
+
|
|
41
|
+
optional string cash_on_hand = 156971;
|
|
42
|
+
optional string option_closed_pnl = 157118;
|
|
43
|
+
optional string percent_maximum_allowable_loss = 156965;
|
|
44
|
+
optional string option_open_pnl = 157117;
|
|
45
|
+
optional string mtm_account = 154262;
|
|
46
|
+
optional string available_buying_power = 157015;
|
|
47
|
+
optional string used_buying_power = 157014;
|
|
48
|
+
optional string reserved_buying_power = 157013;
|
|
49
|
+
optional string excess_sell_margin = 156992;
|
|
50
|
+
|
|
51
|
+
optional string day_open_pnl = 157954;
|
|
52
|
+
optional string day_closed_pnl = 157955;
|
|
53
|
+
optional string day_pnl = 157956;
|
|
54
|
+
optional string day_open_pnl_offset = 157957;
|
|
55
|
+
optional string day_closed_pnl_offset = 157958;
|
|
56
|
+
|
|
57
|
+
optional int32 ssboe = 150100;
|
|
58
|
+
optional int32 usecs = 150101;
|
|
59
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
package rti;
|
|
3
|
+
|
|
4
|
+
message BestBidOffer
|
|
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
|
+
BID = 1;
|
|
11
|
+
ASK = 2;
|
|
12
|
+
LEAN_PRICE = 4;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
required int32 template_id = 154467; // PB_OFFSET + MNM_TEMPLATE_ID
|
|
16
|
+
optional string symbol = 110100; // PB_OFFSET + MNM_SYMBOL
|
|
17
|
+
optional string exchange = 110101; // PB_OFFSET + MNM_EXCHANGE
|
|
18
|
+
|
|
19
|
+
optional uint32 presence_bits = 149138; // PB_OFFSET + MNM_PRICING_INDICATOR
|
|
20
|
+
optional uint32 clear_bits = 154571; // PB_OFFSET + MNM_DISPLAY_INDICATOR
|
|
21
|
+
optional bool is_snapshot = 110121; // PB_OFFSET + MNM_UPDATE_TYPE
|
|
22
|
+
|
|
23
|
+
optional double bid_price = 100022; // PB_OFFSET + MNM_BID_PRICE
|
|
24
|
+
optional int32 bid_size = 100030; // PB_OFFSET + MNM_BID_SIZE
|
|
25
|
+
optional int32 bid_orders = 154403; // PB_OFFSET + MNM_BID_NO_OF_ORDERS
|
|
26
|
+
optional int32 bid_implicit_size = 154867; // PB_OFFSET + MNM_BID_IMPLICIT_SIZE
|
|
27
|
+
optional string bid_time = 100266;
|
|
28
|
+
|
|
29
|
+
optional double ask_price = 100025; // PB_OFFSET + MNM_ASK_PRICE
|
|
30
|
+
optional int32 ask_size = 100031; // PB_OFFSET + MNM_ASK_SIZE
|
|
31
|
+
optional int32 ask_orders = 154404; // PB_OFFSET + MNM_ASK_NO_OF_ORDERS
|
|
32
|
+
optional int32 ask_implicit_size = 154868; // PB_OFFSET + MNM_ASK_IMPLICIT_SIZE
|
|
33
|
+
optional string ask_time = 100267;
|
|
34
|
+
|
|
35
|
+
optional double lean_price = 154909; // PB_OFFSET + MNM_LEAN_PRICE
|
|
36
|
+
|
|
37
|
+
optional int32 ssboe = 150100; // PB_OFFSET + MNM_SECONDS_SINCE_BOE
|
|
38
|
+
optional int32 usecs = 150101; // PB_OFFSET + MNM_USECS
|
|
39
|
+
}
|