hedgequantx 2.9.224 → 2.9.226
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/app.js
CHANGED
|
@@ -78,10 +78,17 @@ function createDaemonProxyService(client, propfirm, credentials = null) {
|
|
|
78
78
|
// Return credentials for algo trading market data connection
|
|
79
79
|
if (!storedCredentials) return null;
|
|
80
80
|
const { RITHMIC_ENDPOINTS } = require('./services/rithmic');
|
|
81
|
+
const { getPropFirm } = require('./config/propfirms');
|
|
82
|
+
|
|
83
|
+
// Get the proper rithmicSystem from propfirm config
|
|
84
|
+
const propfirmKey = propfirm?.key || 'apex_rithmic';
|
|
85
|
+
const propfirmConfig = getPropFirm(propfirmKey);
|
|
86
|
+
const systemName = propfirmConfig?.rithmicSystem || propfirm?.rithmicSystem || propfirm?.name || 'Apex';
|
|
87
|
+
|
|
81
88
|
return {
|
|
82
89
|
userId: storedCredentials.username,
|
|
83
90
|
password: storedCredentials.password,
|
|
84
|
-
systemName
|
|
91
|
+
systemName,
|
|
85
92
|
gateway: RITHMIC_ENDPOINTS?.CHICAGO || 'wss://rprotocol.rithmic.com:443',
|
|
86
93
|
};
|
|
87
94
|
},
|
|
@@ -54,17 +54,30 @@ function createHandlers(daemon) {
|
|
|
54
54
|
const result = await daemon.rithmic.login(username, password);
|
|
55
55
|
|
|
56
56
|
if (result.success) {
|
|
57
|
+
// Get rithmicSystem from config
|
|
58
|
+
const { getPropFirm } = require('../../config/propfirms');
|
|
59
|
+
const propfirmConfig = getPropFirm(propfirmKey);
|
|
60
|
+
|
|
57
61
|
daemon.propfirm = {
|
|
58
62
|
key: propfirmKey,
|
|
59
63
|
name: daemon.rithmic.propfirm.name,
|
|
64
|
+
rithmicSystem: propfirmConfig?.rithmicSystem || daemon.rithmic.propfirm.systemName || 'Apex',
|
|
60
65
|
};
|
|
61
66
|
|
|
67
|
+
// Save credentials for auto-reconnect
|
|
68
|
+
daemon._saveCredentials(username, password);
|
|
69
|
+
daemon._lastRithmicResponse = Date.now();
|
|
70
|
+
|
|
71
|
+
// Start health monitoring
|
|
72
|
+
daemon._startHealthCheck();
|
|
73
|
+
|
|
62
74
|
// Save session for restore
|
|
63
75
|
const { storage } = require('../session');
|
|
64
76
|
storage.save([{
|
|
65
77
|
type: 'rithmic',
|
|
66
78
|
propfirm: daemon.propfirm.name,
|
|
67
79
|
propfirmKey,
|
|
80
|
+
rithmicSystem: daemon.propfirm.rithmicSystem,
|
|
68
81
|
credentials: { username, password },
|
|
69
82
|
accounts: daemon.rithmic.accounts,
|
|
70
83
|
}]);
|
|
@@ -109,10 +122,23 @@ function createHandlers(daemon) {
|
|
|
109
122
|
);
|
|
110
123
|
|
|
111
124
|
if (result.success) {
|
|
125
|
+
// Get rithmicSystem from config or session
|
|
126
|
+
const { getPropFirm } = require('../../config/propfirms');
|
|
127
|
+
const propfirmConfig = getPropFirm(propfirmKey);
|
|
128
|
+
|
|
112
129
|
daemon.propfirm = {
|
|
113
130
|
key: propfirmKey,
|
|
114
131
|
name: daemon.rithmic.propfirm.name,
|
|
132
|
+
rithmicSystem: propfirmConfig?.rithmicSystem || rithmicSession.rithmicSystem || daemon.rithmic.propfirm.systemName || 'Apex',
|
|
115
133
|
};
|
|
134
|
+
|
|
135
|
+
// Save credentials for auto-reconnect
|
|
136
|
+
daemon._saveCredentials(credentials.username, credentials.password);
|
|
137
|
+
daemon._lastRithmicResponse = Date.now();
|
|
138
|
+
|
|
139
|
+
// Start health monitoring
|
|
140
|
+
daemon._startHealthCheck();
|
|
141
|
+
|
|
116
142
|
log.info('Session restored', { propfirm: daemon.propfirm.name });
|
|
117
143
|
} else {
|
|
118
144
|
daemon.rithmic = null;
|
|
@@ -129,6 +155,10 @@ function createHandlers(daemon) {
|
|
|
129
155
|
}
|
|
130
156
|
|
|
131
157
|
async function handleLogout(socket, id) {
|
|
158
|
+
// Stop health monitoring
|
|
159
|
+
daemon._stopHealthCheck();
|
|
160
|
+
daemon._savedCredentials = null;
|
|
161
|
+
|
|
132
162
|
if (daemon.rithmic) {
|
|
133
163
|
await daemon.rithmic.disconnect();
|
|
134
164
|
daemon.rithmic = null;
|
|
@@ -280,6 +310,13 @@ function createHandlers(daemon) {
|
|
|
280
310
|
|
|
281
311
|
// Return credentials for algo trading market data
|
|
282
312
|
const { RITHMIC_ENDPOINTS } = require('../rithmic');
|
|
313
|
+
const { getPropFirm } = require('../../config/propfirms');
|
|
314
|
+
|
|
315
|
+
// Get proper rithmicSystem from config
|
|
316
|
+
const propfirmKey = rithmicSession.propfirmKey || daemon.propfirm?.key || 'apex_rithmic';
|
|
317
|
+
const propfirmConfig = getPropFirm(propfirmKey);
|
|
318
|
+
const systemName = propfirmConfig?.rithmicSystem || daemon.propfirm?.name || rithmicSession.propfirm || 'Apex';
|
|
319
|
+
|
|
283
320
|
daemon._send(socket, createMessage(MSG_TYPE.CREDENTIALS, {
|
|
284
321
|
success: true,
|
|
285
322
|
credentials: {
|
|
@@ -289,9 +326,10 @@ function createHandlers(daemon) {
|
|
|
289
326
|
rithmicCredentials: {
|
|
290
327
|
userId: rithmicSession.credentials.username,
|
|
291
328
|
password: rithmicSession.credentials.password,
|
|
292
|
-
systemName
|
|
329
|
+
systemName,
|
|
293
330
|
gateway: RITHMIC_ENDPOINTS?.CHICAGO || 'wss://rprotocol.rithmic.com:443',
|
|
294
331
|
},
|
|
332
|
+
propfirmKey,
|
|
295
333
|
}, id));
|
|
296
334
|
}
|
|
297
335
|
|
|
@@ -335,10 +335,16 @@ class DaemonProxyService extends EventEmitter {
|
|
|
335
335
|
// For daemon mode, return stored credentials
|
|
336
336
|
if (this.credentials && this.propfirmKey) {
|
|
337
337
|
const { RITHMIC_ENDPOINTS } = require('../rithmic');
|
|
338
|
+
const { getPropFirm } = require('../../config/propfirms');
|
|
339
|
+
|
|
340
|
+
// Get proper rithmicSystem from config
|
|
341
|
+
const propfirmConfig = getPropFirm(this.propfirmKey);
|
|
342
|
+
const systemName = propfirmConfig?.rithmicSystem || this.propfirm?.rithmicSystem || 'Apex';
|
|
343
|
+
|
|
338
344
|
return {
|
|
339
345
|
userId: this.credentials.username,
|
|
340
346
|
password: this.credentials.password,
|
|
341
|
-
systemName
|
|
347
|
+
systemName,
|
|
342
348
|
gateway: RITHMIC_ENDPOINTS?.CHICAGO || 'wss://rprotocol.rithmic.com:443',
|
|
343
349
|
};
|
|
344
350
|
}
|
|
@@ -56,6 +56,18 @@ class DaemonServer extends EventEmitter {
|
|
|
56
56
|
|
|
57
57
|
/** @type {Object} Message handlers */
|
|
58
58
|
this.handlers = createHandlers(this);
|
|
59
|
+
|
|
60
|
+
/** @type {NodeJS.Timer|null} Health check interval */
|
|
61
|
+
this._healthCheckInterval = null;
|
|
62
|
+
|
|
63
|
+
/** @type {number} Last successful Rithmic response timestamp */
|
|
64
|
+
this._lastRithmicResponse = 0;
|
|
65
|
+
|
|
66
|
+
/** @type {boolean} Is currently reconnecting */
|
|
67
|
+
this._isReconnecting = false;
|
|
68
|
+
|
|
69
|
+
/** @type {Object} Saved credentials for reconnection */
|
|
70
|
+
this._savedCredentials = null;
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
/**
|
|
@@ -264,6 +276,138 @@ class DaemonServer extends EventEmitter {
|
|
|
264
276
|
*/
|
|
265
277
|
_setupRithmicEvents() {
|
|
266
278
|
setupRithmicEvents(this);
|
|
279
|
+
|
|
280
|
+
// Monitor disconnection
|
|
281
|
+
if (this.rithmic) {
|
|
282
|
+
this.rithmic.on('disconnected', (info) => {
|
|
283
|
+
log.warn('Rithmic disconnected', info);
|
|
284
|
+
this._handleRithmicDisconnect();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
this.rithmic.on('reconnected', () => {
|
|
288
|
+
log.info('Rithmic reconnected');
|
|
289
|
+
this._lastRithmicResponse = Date.now();
|
|
290
|
+
this._isReconnecting = false;
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Start health monitoring
|
|
297
|
+
*/
|
|
298
|
+
_startHealthCheck() {
|
|
299
|
+
if (this._healthCheckInterval) return;
|
|
300
|
+
|
|
301
|
+
// Check every 30 seconds
|
|
302
|
+
this._healthCheckInterval = setInterval(() => {
|
|
303
|
+
this._performHealthCheck();
|
|
304
|
+
}, 30000);
|
|
305
|
+
|
|
306
|
+
log.debug('Health check started');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Stop health monitoring
|
|
311
|
+
*/
|
|
312
|
+
_stopHealthCheck() {
|
|
313
|
+
if (this._healthCheckInterval) {
|
|
314
|
+
clearInterval(this._healthCheckInterval);
|
|
315
|
+
this._healthCheckInterval = null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Perform health check on Rithmic connection
|
|
321
|
+
*/
|
|
322
|
+
async _performHealthCheck() {
|
|
323
|
+
if (!this.rithmic || this._isReconnecting) return;
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
// Try to get accounts as a health check (uses cache, no API call)
|
|
327
|
+
const result = await this.rithmic.getTradingAccounts();
|
|
328
|
+
|
|
329
|
+
if (result.success) {
|
|
330
|
+
this._lastRithmicResponse = Date.now();
|
|
331
|
+
} else {
|
|
332
|
+
log.warn('Health check failed', { error: result.error });
|
|
333
|
+
|
|
334
|
+
// If no response for 2 minutes, try reconnect
|
|
335
|
+
const timeSinceLastResponse = Date.now() - this._lastRithmicResponse;
|
|
336
|
+
if (timeSinceLastResponse > 120000) {
|
|
337
|
+
log.warn('Rithmic unresponsive for 2 minutes, attempting reconnect');
|
|
338
|
+
this._handleRithmicDisconnect();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch (err) {
|
|
342
|
+
log.error('Health check error', { error: err.message });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Handle Rithmic disconnection - attempt reconnect
|
|
348
|
+
*/
|
|
349
|
+
async _handleRithmicDisconnect() {
|
|
350
|
+
if (this._isReconnecting) {
|
|
351
|
+
log.debug('Already reconnecting, skipping');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!this._savedCredentials) {
|
|
356
|
+
log.warn('Cannot reconnect: no saved credentials');
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this._isReconnecting = true;
|
|
361
|
+
|
|
362
|
+
// Notify clients
|
|
363
|
+
this._broadcast(createMessage(MSG_TYPE.EVENT_DISCONNECTED, {
|
|
364
|
+
reason: 'Connection lost, reconnecting...',
|
|
365
|
+
reconnecting: true,
|
|
366
|
+
}));
|
|
367
|
+
|
|
368
|
+
// Use reconnect module
|
|
369
|
+
const { handleAutoReconnect } = require('../rithmic/reconnect');
|
|
370
|
+
|
|
371
|
+
// Prepare service with credentials
|
|
372
|
+
if (this.rithmic) {
|
|
373
|
+
this.rithmic.credentials = this._savedCredentials;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
await handleAutoReconnect(this.rithmic);
|
|
378
|
+
|
|
379
|
+
if (this.rithmic && this.rithmic.accounts?.length > 0) {
|
|
380
|
+
this._lastRithmicResponse = Date.now();
|
|
381
|
+
this._isReconnecting = false;
|
|
382
|
+
|
|
383
|
+
// Notify clients of reconnection
|
|
384
|
+
this._broadcast(createMessage(MSG_TYPE.EVENT_RECONNECTED, {
|
|
385
|
+
propfirm: this.propfirm,
|
|
386
|
+
accounts: this.rithmic.accounts.length,
|
|
387
|
+
}));
|
|
388
|
+
|
|
389
|
+
log.info('Reconnection successful');
|
|
390
|
+
}
|
|
391
|
+
} catch (err) {
|
|
392
|
+
log.error('Reconnection failed', { error: err.message });
|
|
393
|
+
this._isReconnecting = false;
|
|
394
|
+
|
|
395
|
+
// Schedule retry in 5 minutes
|
|
396
|
+
setTimeout(() => {
|
|
397
|
+
if (this.isRunning && !this._isReconnecting) {
|
|
398
|
+
this._handleRithmicDisconnect();
|
|
399
|
+
}
|
|
400
|
+
}, 300000);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Save credentials for reconnection
|
|
406
|
+
* @param {string} username
|
|
407
|
+
* @param {string} password
|
|
408
|
+
*/
|
|
409
|
+
_saveCredentials(username, password) {
|
|
410
|
+
this._savedCredentials = { username, password };
|
|
267
411
|
}
|
|
268
412
|
|
|
269
413
|
/**
|
|
@@ -296,6 +440,9 @@ class DaemonServer extends EventEmitter {
|
|
|
296
440
|
async stop() {
|
|
297
441
|
log.info('Stopping daemon...');
|
|
298
442
|
|
|
443
|
+
// Stop health monitoring
|
|
444
|
+
this._stopHealthCheck();
|
|
445
|
+
|
|
299
446
|
// Stop all algo sessions
|
|
300
447
|
for (const [id, session] of this.algoSessions) {
|
|
301
448
|
try {
|