hedgequantx 1.2.33 → 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 +121 -9
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hedgequantx",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.35",
|
|
4
4
|
"description": "Prop Futures Algo Trading CLI - Connect to Topstep, Alpha Futures, and other prop firms",
|
|
5
5
|
"main": "src/app.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"figlet": "^1.7.0",
|
|
48
48
|
"inquirer": "^7.3.3",
|
|
49
49
|
"ora": "^5.4.1",
|
|
50
|
+
"protobufjs": "^8.0.0",
|
|
50
51
|
"ws": "^8.18.3"
|
|
51
52
|
}
|
|
52
53
|
}
|
package/src/app.js
CHANGED
|
@@ -11,7 +11,8 @@ const { execSync } = require('child_process');
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
|
|
13
13
|
const { ProjectXService, connections } = require('./services');
|
|
14
|
-
const {
|
|
14
|
+
const { RithmicService } = require('./services/rithmic');
|
|
15
|
+
const { PROPFIRM_CHOICES, getPropFirmsByPlatform, getPropFirm } = require('./config');
|
|
15
16
|
const { getDevice, getSeparator, printLogo, getLogoWidth, drawBoxHeader, drawBoxFooter, centerText, createBoxMenu } = require('./ui');
|
|
16
17
|
const { validateUsername, validatePassword, maskSensitive } = require('./security');
|
|
17
18
|
|
|
@@ -22,6 +23,7 @@ const { algoTradingMenu } = require('./pages/algo');
|
|
|
22
23
|
|
|
23
24
|
// Current service reference
|
|
24
25
|
let currentService = null;
|
|
26
|
+
let currentPlatform = null; // 'projectx' or 'rithmic'
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Displays the application banner with stats if connected
|
|
@@ -193,17 +195,20 @@ const projectXMenu = async () => {
|
|
|
193
195
|
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM', innerWidth)) + chalk.cyan('║'));
|
|
194
196
|
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
195
197
|
|
|
196
|
-
// Display in 3 columns
|
|
198
|
+
// Display in 3 columns with fixed width alignment
|
|
197
199
|
const rows = Math.ceil(numbered.length / numCols);
|
|
200
|
+
const maxNum = numbered.length;
|
|
201
|
+
const numWidth = maxNum >= 10 ? 4 : 3; // [XX] or [X]
|
|
202
|
+
|
|
198
203
|
for (let row = 0; row < rows; row++) {
|
|
199
204
|
let line = '';
|
|
200
205
|
for (let col = 0; col < numCols; col++) {
|
|
201
206
|
const idx = row + col * rows;
|
|
202
207
|
if (idx < numbered.length) {
|
|
203
208
|
const item = numbered[idx];
|
|
204
|
-
const
|
|
205
|
-
const coloredText = chalk.cyan(`[${
|
|
206
|
-
const textLen =
|
|
209
|
+
const numStr = item.num.toString().padStart(2, ' ');
|
|
210
|
+
const coloredText = chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
|
|
211
|
+
const textLen = 4 + 1 + item.name.length; // [XX] + space + name
|
|
207
212
|
const padding = colWidth - textLen - 2;
|
|
208
213
|
line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
|
|
209
214
|
} else {
|
|
@@ -254,6 +259,107 @@ const projectXMenu = async () => {
|
|
|
254
259
|
await service.getUser();
|
|
255
260
|
connections.add('projectx', service, service.propfirm.name);
|
|
256
261
|
currentService = service;
|
|
262
|
+
currentPlatform = 'projectx';
|
|
263
|
+
spinner.succeed(`Connected to ${service.propfirm.name}`);
|
|
264
|
+
return service;
|
|
265
|
+
} else {
|
|
266
|
+
spinner.fail(result.error || 'Authentication failed');
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
spinner.fail(error.message);
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Rithmic platform connection menu
|
|
277
|
+
*/
|
|
278
|
+
const rithmicMenu = async () => {
|
|
279
|
+
const propfirms = getPropFirmsByPlatform('Rithmic');
|
|
280
|
+
const boxWidth = getLogoWidth();
|
|
281
|
+
const innerWidth = boxWidth - 2;
|
|
282
|
+
const numCols = 3;
|
|
283
|
+
const colWidth = Math.floor(innerWidth / numCols);
|
|
284
|
+
|
|
285
|
+
// Build numbered list
|
|
286
|
+
const numbered = propfirms.map((pf, i) => ({
|
|
287
|
+
num: i + 1,
|
|
288
|
+
key: pf.key,
|
|
289
|
+
name: pf.displayName,
|
|
290
|
+
systemName: pf.rithmicSystem
|
|
291
|
+
}));
|
|
292
|
+
|
|
293
|
+
// PropFirm selection box
|
|
294
|
+
console.log();
|
|
295
|
+
console.log(chalk.cyan('╔' + '═'.repeat(innerWidth) + '╗'));
|
|
296
|
+
console.log(chalk.cyan('║') + chalk.white.bold(centerText('SELECT PROPFIRM (RITHMIC)', innerWidth)) + chalk.cyan('║'));
|
|
297
|
+
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
298
|
+
|
|
299
|
+
// Display in 3 columns with fixed width alignment
|
|
300
|
+
const rows = Math.ceil(numbered.length / numCols);
|
|
301
|
+
|
|
302
|
+
for (let row = 0; row < rows; row++) {
|
|
303
|
+
let line = '';
|
|
304
|
+
for (let col = 0; col < numCols; col++) {
|
|
305
|
+
const idx = row + col * rows;
|
|
306
|
+
if (idx < numbered.length) {
|
|
307
|
+
const item = numbered[idx];
|
|
308
|
+
const numStr = item.num.toString().padStart(2, ' ');
|
|
309
|
+
const coloredText = chalk.cyan(`[${numStr}]`) + ' ' + chalk.white(item.name);
|
|
310
|
+
const textLen = 4 + 1 + item.name.length;
|
|
311
|
+
const padding = colWidth - textLen - 2;
|
|
312
|
+
line += ' ' + coloredText + ' '.repeat(Math.max(0, padding));
|
|
313
|
+
} else {
|
|
314
|
+
line += ' '.repeat(colWidth);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const lineLen = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
318
|
+
const adjust = innerWidth - lineLen;
|
|
319
|
+
console.log(chalk.cyan('║') + line + ' '.repeat(Math.max(0, adjust)) + chalk.cyan('║'));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
console.log(chalk.cyan('║') + ' '.repeat(innerWidth) + chalk.cyan('║'));
|
|
323
|
+
const backText = ' ' + chalk.red('[X] Back');
|
|
324
|
+
const backLen = '[X] Back'.length + 2;
|
|
325
|
+
console.log(chalk.cyan('║') + backText + ' '.repeat(innerWidth - backLen) + chalk.cyan('║'));
|
|
326
|
+
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
327
|
+
console.log();
|
|
328
|
+
|
|
329
|
+
const validInputs = numbered.map(n => n.num.toString());
|
|
330
|
+
validInputs.push('x', 'X');
|
|
331
|
+
|
|
332
|
+
const { action } = await inquirer.prompt([
|
|
333
|
+
{
|
|
334
|
+
type: 'input',
|
|
335
|
+
name: 'action',
|
|
336
|
+
message: chalk.cyan(`Enter choice (1-${numbered.length}/X):`),
|
|
337
|
+
validate: (input) => {
|
|
338
|
+
if (validInputs.includes(input)) return true;
|
|
339
|
+
return `Please enter 1-${numbered.length} or X`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
]);
|
|
343
|
+
|
|
344
|
+
if (action.toLowerCase() === 'x') return null;
|
|
345
|
+
|
|
346
|
+
const selectedIdx = parseInt(action) - 1;
|
|
347
|
+
const selectedPropfirm = numbered[selectedIdx];
|
|
348
|
+
|
|
349
|
+
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
350
|
+
const spinner = ora('Connecting to Rithmic...').start();
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const service = new RithmicService(selectedPropfirm.key);
|
|
354
|
+
const result = await service.login(credentials.username, credentials.password);
|
|
355
|
+
|
|
356
|
+
if (result.success) {
|
|
357
|
+
spinner.text = 'Fetching accounts...';
|
|
358
|
+
await service.getTradingAccounts();
|
|
359
|
+
|
|
360
|
+
connections.add('rithmic', service, service.propfirm.name);
|
|
361
|
+
currentService = service;
|
|
362
|
+
currentPlatform = 'rithmic';
|
|
257
363
|
spinner.succeed(`Connected to ${service.propfirm.name}`);
|
|
258
364
|
return service;
|
|
259
365
|
} else {
|
|
@@ -291,7 +397,7 @@ const mainMenu = async () => {
|
|
|
291
397
|
console.log(chalk.cyan('║') + leftText + ' '.repeat(Math.max(0, leftPad)) + rightText + ' '.repeat(Math.max(0, rightPad)) + chalk.cyan('║'));
|
|
292
398
|
};
|
|
293
399
|
|
|
294
|
-
menuRow(chalk.cyan('[1] ProjectX'), chalk.
|
|
400
|
+
menuRow(chalk.cyan('[1] ProjectX'), chalk.cyan('[2] Rithmic'));
|
|
295
401
|
menuRow(chalk.gray('[3] Tradovate (Coming Soon)'), chalk.red('[X] Exit'));
|
|
296
402
|
|
|
297
403
|
console.log(chalk.cyan('╚' + '═'.repeat(innerWidth) + '╝'));
|
|
@@ -301,11 +407,11 @@ const mainMenu = async () => {
|
|
|
301
407
|
{
|
|
302
408
|
type: 'input',
|
|
303
409
|
name: 'action',
|
|
304
|
-
message: chalk.cyan('Enter choice (1/X):'),
|
|
410
|
+
message: chalk.cyan('Enter choice (1/2/X):'),
|
|
305
411
|
validate: (input) => {
|
|
306
|
-
const valid = ['1', 'x', 'X'];
|
|
412
|
+
const valid = ['1', '2', 'x', 'X'];
|
|
307
413
|
if (valid.includes(input)) return true;
|
|
308
|
-
return 'Please enter 1 or X';
|
|
414
|
+
return 'Please enter 1, 2 or X';
|
|
309
415
|
}
|
|
310
416
|
}
|
|
311
417
|
]);
|
|
@@ -313,6 +419,7 @@ const mainMenu = async () => {
|
|
|
313
419
|
// Map input to action
|
|
314
420
|
const actionMap = {
|
|
315
421
|
'1': 'projectx',
|
|
422
|
+
'2': 'rithmic',
|
|
316
423
|
'x': 'exit',
|
|
317
424
|
'X': 'exit'
|
|
318
425
|
};
|
|
@@ -509,6 +616,11 @@ const run = async () => {
|
|
|
509
616
|
const service = await projectXMenu();
|
|
510
617
|
if (service) currentService = service;
|
|
511
618
|
}
|
|
619
|
+
|
|
620
|
+
if (choice === 'rithmic') {
|
|
621
|
+
const service = await rithmicMenu();
|
|
622
|
+
if (service) currentService = service;
|
|
623
|
+
}
|
|
512
624
|
} else {
|
|
513
625
|
const action = await dashboardMenu(currentService);
|
|
514
626
|
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rithmic Connection Manager
|
|
3
|
+
* Handles WebSocket connection and heartbeat
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const WebSocket = require('ws');
|
|
7
|
+
const EventEmitter = require('events');
|
|
8
|
+
const { proto } = require('./protobuf');
|
|
9
|
+
const { REQ, RES, INFRA_TYPE } = require('./constants');
|
|
10
|
+
|
|
11
|
+
class RithmicConnection extends EventEmitter {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.ws = null;
|
|
15
|
+
this.config = null;
|
|
16
|
+
this.state = 'DISCONNECTED';
|
|
17
|
+
this.heartbeatTimer = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get isConnected() {
|
|
21
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get connectionState() {
|
|
25
|
+
return this.state;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Connect to Rithmic server
|
|
30
|
+
*/
|
|
31
|
+
async connect(config) {
|
|
32
|
+
this.config = config;
|
|
33
|
+
this.state = 'CONNECTING';
|
|
34
|
+
|
|
35
|
+
await proto.load();
|
|
36
|
+
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
this.ws = new WebSocket(config.uri, { rejectUnauthorized: false });
|
|
39
|
+
|
|
40
|
+
this.ws.on('open', () => {
|
|
41
|
+
this.state = 'CONNECTED';
|
|
42
|
+
this.emit('connected');
|
|
43
|
+
resolve(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
this.ws.on('message', (data) => {
|
|
47
|
+
this.handleMessage(data);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.ws.on('error', (err) => {
|
|
51
|
+
this.state = 'ERROR';
|
|
52
|
+
this.emit('error', err);
|
|
53
|
+
reject(err);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this.ws.on('close', (code, reason) => {
|
|
57
|
+
this.state = 'DISCONNECTED';
|
|
58
|
+
this.stopHeartbeat();
|
|
59
|
+
this.emit('disconnected', { code, reason: reason?.toString() });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Timeout
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
if (this.state === 'CONNECTING') {
|
|
65
|
+
reject(new Error('Connection timeout'));
|
|
66
|
+
}
|
|
67
|
+
}, 15000);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Disconnect
|
|
73
|
+
*/
|
|
74
|
+
async disconnect() {
|
|
75
|
+
this.stopHeartbeat();
|
|
76
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
77
|
+
this.send('RequestLogout', { templateId: REQ.LOGOUT, userMsg: ['HQX'] });
|
|
78
|
+
this.ws.close(1000, 'bye');
|
|
79
|
+
}
|
|
80
|
+
this.ws = null;
|
|
81
|
+
this.state = 'DISCONNECTED';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Send a protobuf message
|
|
86
|
+
*/
|
|
87
|
+
send(typeName, data) {
|
|
88
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
89
|
+
throw new Error('Not connected');
|
|
90
|
+
}
|
|
91
|
+
const buffer = proto.encode(typeName, data);
|
|
92
|
+
this.ws.send(buffer);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Login to system
|
|
97
|
+
*/
|
|
98
|
+
login(infraType = 'ORDER_PLANT') {
|
|
99
|
+
if (!this.config) throw new Error('No config');
|
|
100
|
+
|
|
101
|
+
this.send('RequestLogin', {
|
|
102
|
+
templateId: REQ.LOGIN,
|
|
103
|
+
templateVersion: '3.9',
|
|
104
|
+
userMsg: ['HQX'],
|
|
105
|
+
user: this.config.userId,
|
|
106
|
+
password: this.config.password,
|
|
107
|
+
appName: this.config.appName || 'HQX-CLI',
|
|
108
|
+
appVersion: this.config.appVersion || '1.0.0',
|
|
109
|
+
systemName: this.config.systemName,
|
|
110
|
+
infraType: INFRA_TYPE[infraType],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* List available systems
|
|
116
|
+
*/
|
|
117
|
+
listSystems() {
|
|
118
|
+
this.send('RequestRithmicSystemInfo', {
|
|
119
|
+
templateId: REQ.SYSTEM_INFO,
|
|
120
|
+
userMsg: ['HQX'],
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Handle incoming message
|
|
126
|
+
*/
|
|
127
|
+
handleMessage(data) {
|
|
128
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
129
|
+
const templateId = proto.getTemplateId(buffer);
|
|
130
|
+
|
|
131
|
+
switch (templateId) {
|
|
132
|
+
case RES.LOGIN:
|
|
133
|
+
this.onLoginResponse(buffer);
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case RES.HEARTBEAT:
|
|
137
|
+
// OK
|
|
138
|
+
break;
|
|
139
|
+
|
|
140
|
+
case RES.SYSTEM_INFO:
|
|
141
|
+
this.onSystemInfo(buffer);
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
default:
|
|
145
|
+
// Forward to listeners
|
|
146
|
+
this.emit('message', { templateId, data: buffer });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
onLoginResponse(data) {
|
|
151
|
+
try {
|
|
152
|
+
const res = proto.decode('ResponseLogin', data);
|
|
153
|
+
|
|
154
|
+
if (res.rpCode?.[0] === '0') {
|
|
155
|
+
this.state = 'LOGGED_IN';
|
|
156
|
+
this.startHeartbeat(res.heartbeatInterval || 60);
|
|
157
|
+
this.emit('loggedIn', {
|
|
158
|
+
fcmId: res.fcmId,
|
|
159
|
+
ibId: res.ibId,
|
|
160
|
+
heartbeatInterval: res.heartbeatInterval,
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
const errorCode = res.rpCode?.[0] || 'UNKNOWN';
|
|
164
|
+
const errorMsg = res.rpCode?.[1] || 'Login failed';
|
|
165
|
+
this.emit('loginFailed', { code: errorCode, message: errorMsg });
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {
|
|
168
|
+
this.emit('loginFailed', { code: 'DECODE_ERROR', message: e.message });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
onSystemInfo(data) {
|
|
173
|
+
try {
|
|
174
|
+
const res = proto.decode('ResponseRithmicSystemInfo', data);
|
|
175
|
+
|
|
176
|
+
if (res.rpCode?.[0] === '0') {
|
|
177
|
+
this.emit('systems', res.systemName || []);
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
// Ignore
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
startHeartbeat(intervalSec) {
|
|
185
|
+
this.stopHeartbeat();
|
|
186
|
+
this.heartbeatTimer = setInterval(() => {
|
|
187
|
+
try {
|
|
188
|
+
this.send('RequestHeartbeat', { templateId: REQ.HEARTBEAT });
|
|
189
|
+
} catch (e) {
|
|
190
|
+
// Ignore
|
|
191
|
+
}
|
|
192
|
+
}, (intervalSec - 5) * 1000);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
stopHeartbeat() {
|
|
196
|
+
if (this.heartbeatTimer) {
|
|
197
|
+
clearInterval(this.heartbeatTimer);
|
|
198
|
+
this.heartbeatTimer = null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = { RithmicConnection };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rithmic Constants
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Endpoints
|
|
6
|
+
const RITHMIC_ENDPOINTS = {
|
|
7
|
+
TEST: 'wss://rituz00100.rithmic.com:443',
|
|
8
|
+
PAPER: 'wss://ritpa11120.11.rithmic.com:443',
|
|
9
|
+
LIVE: 'wss://ritpz01000.01.rithmic.com:443',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// System names for PropFirms
|
|
13
|
+
const RITHMIC_SYSTEMS = {
|
|
14
|
+
TEST: 'Rithmic Test',
|
|
15
|
+
PAPER: 'Rithmic Paper Trading',
|
|
16
|
+
APEX: 'Apex',
|
|
17
|
+
TOPSTEP: 'TopstepTrader',
|
|
18
|
+
MES_CAPITAL: 'MES Capital',
|
|
19
|
+
BULENOX: 'Bulenox',
|
|
20
|
+
TRADEFUNDRR: 'TradeFundrr',
|
|
21
|
+
THE_TRADING_PIT: 'TheTradingPit',
|
|
22
|
+
FUNDED_FUTURES_NETWORK: 'FundedFuturesNetwork',
|
|
23
|
+
PROPSHOP_TRADER: 'PropShopTrader',
|
|
24
|
+
FOUR_PROP_TRADER: '4PropTrader',
|
|
25
|
+
DAY_TRADERS: 'DayTraders.com',
|
|
26
|
+
TEN_X_FUTURES: '10XFutures',
|
|
27
|
+
LUCID_TRADING: 'LucidTrading',
|
|
28
|
+
THRIVE_TRADING: 'ThriveTrading',
|
|
29
|
+
LEGENDS_TRADING: 'LegendsTrading',
|
|
30
|
+
EARN_2_TRADE: 'Earn2Trade',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Infrastructure types
|
|
34
|
+
const INFRA_TYPE = {
|
|
35
|
+
TICKER_PLANT: 1,
|
|
36
|
+
ORDER_PLANT: 2,
|
|
37
|
+
HISTORY_PLANT: 3,
|
|
38
|
+
PNL_PLANT: 4,
|
|
39
|
+
REPOSITORY_PLANT: 5,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Request template IDs
|
|
43
|
+
const REQ = {
|
|
44
|
+
LOGIN: 10,
|
|
45
|
+
LOGOUT: 12,
|
|
46
|
+
SYSTEM_INFO: 16,
|
|
47
|
+
HEARTBEAT: 18,
|
|
48
|
+
MARKET_DATA: 100,
|
|
49
|
+
LOGIN_INFO: 300,
|
|
50
|
+
ACCOUNT_LIST: 302,
|
|
51
|
+
ACCOUNT_RMS: 304,
|
|
52
|
+
PRODUCT_RMS: 306,
|
|
53
|
+
ORDER_UPDATES: 308,
|
|
54
|
+
TRADE_ROUTES: 310,
|
|
55
|
+
NEW_ORDER: 312,
|
|
56
|
+
MODIFY_ORDER: 314,
|
|
57
|
+
CANCEL_ORDER: 316,
|
|
58
|
+
SHOW_ORDER_HISTORY_DATES: 318,
|
|
59
|
+
SHOW_ORDERS: 320,
|
|
60
|
+
SHOW_ORDER_HISTORY: 324,
|
|
61
|
+
BRACKET_ORDER: 330,
|
|
62
|
+
CANCEL_ALL_ORDERS: 346,
|
|
63
|
+
EXIT_POSITION: 3504,
|
|
64
|
+
PNL_POSITION_SNAPSHOT: 400,
|
|
65
|
+
PNL_POSITION_UPDATES: 402,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Response template IDs
|
|
69
|
+
const RES = {
|
|
70
|
+
LOGIN: 11,
|
|
71
|
+
LOGOUT: 13,
|
|
72
|
+
SYSTEM_INFO: 17,
|
|
73
|
+
HEARTBEAT: 19,
|
|
74
|
+
MARKET_DATA: 101,
|
|
75
|
+
LOGIN_INFO: 301,
|
|
76
|
+
ACCOUNT_LIST: 303,
|
|
77
|
+
ACCOUNT_RMS: 305,
|
|
78
|
+
PRODUCT_RMS: 307,
|
|
79
|
+
ORDER_UPDATES: 309,
|
|
80
|
+
TRADE_ROUTES: 311,
|
|
81
|
+
NEW_ORDER: 313,
|
|
82
|
+
MODIFY_ORDER: 315,
|
|
83
|
+
CANCEL_ORDER: 317,
|
|
84
|
+
SHOW_ORDER_HISTORY_DATES: 319,
|
|
85
|
+
SHOW_ORDERS: 321,
|
|
86
|
+
SHOW_ORDER_HISTORY: 325,
|
|
87
|
+
BRACKET_ORDER: 331,
|
|
88
|
+
CANCEL_ALL_ORDERS: 347,
|
|
89
|
+
EXIT_POSITION: 3505,
|
|
90
|
+
PNL_POSITION_SNAPSHOT: 401,
|
|
91
|
+
PNL_POSITION_UPDATES: 403,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Streaming template IDs
|
|
95
|
+
const STREAM = {
|
|
96
|
+
LAST_TRADE: 150,
|
|
97
|
+
BBO: 151,
|
|
98
|
+
TRADE_ROUTE_UPDATE: 350,
|
|
99
|
+
ORDER_NOTIFICATION: 351,
|
|
100
|
+
EXCHANGE_NOTIFICATION: 352,
|
|
101
|
+
BRACKET_UPDATE: 353,
|
|
102
|
+
INSTRUMENT_PNL_UPDATE: 450,
|
|
103
|
+
ACCOUNT_PNL_UPDATE: 451,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Proto files to load
|
|
107
|
+
const PROTO_FILES = [
|
|
108
|
+
'base.proto',
|
|
109
|
+
'request_heartbeat.proto',
|
|
110
|
+
'response_heartbeat.proto',
|
|
111
|
+
'request_rithmic_system_info.proto',
|
|
112
|
+
'response_rithmic_system_info.proto',
|
|
113
|
+
'request_login.proto',
|
|
114
|
+
'response_login.proto',
|
|
115
|
+
'request_logout.proto',
|
|
116
|
+
'response_logout.proto',
|
|
117
|
+
'request_login_info.proto',
|
|
118
|
+
'response_login_info.proto',
|
|
119
|
+
'request_account_list.proto',
|
|
120
|
+
'response_account_list.proto',
|
|
121
|
+
'request_trade_routes.proto',
|
|
122
|
+
'response_trade_routes.proto',
|
|
123
|
+
'request_subscribe_for_order_updates.proto',
|
|
124
|
+
'response_subscribe_for_order_updates.proto',
|
|
125
|
+
'request_new_order.proto',
|
|
126
|
+
'response_new_order.proto',
|
|
127
|
+
'request_cancel_all_orders.proto',
|
|
128
|
+
'rithmic_order_notification.proto',
|
|
129
|
+
'exchange_order_notification.proto',
|
|
130
|
+
'request_show_orders.proto',
|
|
131
|
+
'response_show_orders.proto',
|
|
132
|
+
'request_show_order_history.proto',
|
|
133
|
+
'response_show_order_history.proto',
|
|
134
|
+
'request_show_order_history_dates.proto',
|
|
135
|
+
'response_show_order_history_dates.proto',
|
|
136
|
+
'request_market_data_update.proto',
|
|
137
|
+
'response_market_data_update.proto',
|
|
138
|
+
'last_trade.proto',
|
|
139
|
+
'best_bid_offer.proto',
|
|
140
|
+
'request_pnl_position_snapshot.proto',
|
|
141
|
+
'response_pnl_position_snapshot.proto',
|
|
142
|
+
'request_pnl_position_updates.proto',
|
|
143
|
+
'response_pnl_position_updates.proto',
|
|
144
|
+
'account_pnl_position_update.proto',
|
|
145
|
+
'instrument_pnl_position_update.proto',
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
RITHMIC_ENDPOINTS,
|
|
150
|
+
RITHMIC_SYSTEMS,
|
|
151
|
+
INFRA_TYPE,
|
|
152
|
+
REQ,
|
|
153
|
+
RES,
|
|
154
|
+
STREAM,
|
|
155
|
+
PROTO_FILES,
|
|
156
|
+
};
|