hedgequantx 2.9.225 → 2.9.227
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,9 +54,14 @@ 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
|
|
|
62
67
|
// Save credentials for auto-reconnect
|
|
@@ -72,6 +77,7 @@ function createHandlers(daemon) {
|
|
|
72
77
|
type: 'rithmic',
|
|
73
78
|
propfirm: daemon.propfirm.name,
|
|
74
79
|
propfirmKey,
|
|
80
|
+
rithmicSystem: daemon.propfirm.rithmicSystem,
|
|
75
81
|
credentials: { username, password },
|
|
76
82
|
accounts: daemon.rithmic.accounts,
|
|
77
83
|
}]);
|
|
@@ -116,9 +122,14 @@ function createHandlers(daemon) {
|
|
|
116
122
|
);
|
|
117
123
|
|
|
118
124
|
if (result.success) {
|
|
125
|
+
// Get rithmicSystem from config or session
|
|
126
|
+
const { getPropFirm } = require('../../config/propfirms');
|
|
127
|
+
const propfirmConfig = getPropFirm(propfirmKey);
|
|
128
|
+
|
|
119
129
|
daemon.propfirm = {
|
|
120
130
|
key: propfirmKey,
|
|
121
131
|
name: daemon.rithmic.propfirm.name,
|
|
132
|
+
rithmicSystem: propfirmConfig?.rithmicSystem || rithmicSession.rithmicSystem || daemon.rithmic.propfirm.systemName || 'Apex',
|
|
122
133
|
};
|
|
123
134
|
|
|
124
135
|
// Save credentials for auto-reconnect
|
|
@@ -299,6 +310,13 @@ function createHandlers(daemon) {
|
|
|
299
310
|
|
|
300
311
|
// Return credentials for algo trading market data
|
|
301
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
|
+
|
|
302
320
|
daemon._send(socket, createMessage(MSG_TYPE.CREDENTIALS, {
|
|
303
321
|
success: true,
|
|
304
322
|
credentials: {
|
|
@@ -308,9 +326,10 @@ function createHandlers(daemon) {
|
|
|
308
326
|
rithmicCredentials: {
|
|
309
327
|
userId: rithmicSession.credentials.username,
|
|
310
328
|
password: rithmicSession.credentials.password,
|
|
311
|
-
systemName
|
|
329
|
+
systemName,
|
|
312
330
|
gateway: RITHMIC_ENDPOINTS?.CHICAGO || 'wss://rprotocol.rithmic.com:443',
|
|
313
331
|
},
|
|
332
|
+
propfirmKey,
|
|
314
333
|
}, id));
|
|
315
334
|
}
|
|
316
335
|
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
'use strict';
|
|
36
36
|
|
|
37
37
|
const fs = require('fs');
|
|
38
|
-
const
|
|
38
|
+
const path = require('path');
|
|
39
|
+
const { spawn, execSync } = require('child_process');
|
|
39
40
|
const { SOCKET_PATH, PID_FILE, SOCKET_DIR } = require('./constants');
|
|
40
41
|
const { DaemonServer } = require('./server');
|
|
41
42
|
const { DaemonClient, getDaemonClient } = require('./client');
|
|
@@ -43,6 +44,11 @@ const { logger } = require('../../utils/logger');
|
|
|
43
44
|
|
|
44
45
|
const log = logger.scope('Daemon');
|
|
45
46
|
|
|
47
|
+
/** Isolated daemon directory - survives npm updates */
|
|
48
|
+
const DAEMON_INSTALL_DIR = path.join(SOCKET_DIR, 'daemon');
|
|
49
|
+
const DAEMON_ENTRY = path.join(DAEMON_INSTALL_DIR, 'cli-daemon.js');
|
|
50
|
+
const DAEMON_VERSION_FILE = path.join(DAEMON_INSTALL_DIR, 'version.txt');
|
|
51
|
+
|
|
46
52
|
/**
|
|
47
53
|
* Check if daemon is running
|
|
48
54
|
* @returns {boolean}
|
|
@@ -86,6 +92,150 @@ function getDaemonPid() {
|
|
|
86
92
|
}
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Get current package version
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
function getCurrentVersion() {
|
|
100
|
+
try {
|
|
101
|
+
const pkg = require('../../../package.json');
|
|
102
|
+
return pkg.version;
|
|
103
|
+
} catch (_) {
|
|
104
|
+
return '0.0.0';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get installed daemon version
|
|
110
|
+
* @returns {string|null}
|
|
111
|
+
*/
|
|
112
|
+
function getInstalledDaemonVersion() {
|
|
113
|
+
try {
|
|
114
|
+
if (fs.existsSync(DAEMON_VERSION_FILE)) {
|
|
115
|
+
return fs.readFileSync(DAEMON_VERSION_FILE, 'utf8').trim();
|
|
116
|
+
}
|
|
117
|
+
} catch (_) {}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Install daemon to isolated directory
|
|
123
|
+
* This copies all necessary files so daemon survives npm updates
|
|
124
|
+
* @returns {boolean}
|
|
125
|
+
*/
|
|
126
|
+
function installDaemon() {
|
|
127
|
+
const currentVersion = getCurrentVersion();
|
|
128
|
+
const installedVersion = getInstalledDaemonVersion();
|
|
129
|
+
|
|
130
|
+
// Skip if already installed with same version
|
|
131
|
+
if (installedVersion === currentVersion && fs.existsSync(DAEMON_ENTRY)) {
|
|
132
|
+
log.debug('Daemon already installed', { version: currentVersion });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
log.info('Installing daemon to isolated directory', { version: currentVersion });
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Create daemon directory
|
|
140
|
+
if (!fs.existsSync(DAEMON_INSTALL_DIR)) {
|
|
141
|
+
fs.mkdirSync(DAEMON_INSTALL_DIR, { recursive: true, mode: 0o700 });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Get source directory (where this package is installed)
|
|
145
|
+
const srcDir = path.resolve(__dirname, '../../..');
|
|
146
|
+
|
|
147
|
+
// Files/directories to copy for daemon to work
|
|
148
|
+
const filesToCopy = [
|
|
149
|
+
'src/cli-daemon.js',
|
|
150
|
+
'src/services/daemon',
|
|
151
|
+
'src/services/rithmic',
|
|
152
|
+
'src/services/session.js',
|
|
153
|
+
'src/services/index.js',
|
|
154
|
+
'src/config',
|
|
155
|
+
'src/security',
|
|
156
|
+
'src/utils',
|
|
157
|
+
'package.json',
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
// Copy each file/directory
|
|
161
|
+
for (const file of filesToCopy) {
|
|
162
|
+
const srcPath = path.join(srcDir, file);
|
|
163
|
+
const destPath = path.join(DAEMON_INSTALL_DIR, file);
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(srcPath)) {
|
|
166
|
+
log.warn('Source not found', { file });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Create parent directory
|
|
171
|
+
const destDir = path.dirname(destPath);
|
|
172
|
+
if (!fs.existsSync(destDir)) {
|
|
173
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Copy file or directory
|
|
177
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
178
|
+
copyDirSync(srcPath, destPath);
|
|
179
|
+
} else {
|
|
180
|
+
fs.copyFileSync(srcPath, destPath);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Copy node_modules that daemon needs
|
|
185
|
+
const nodeModulesDir = path.join(srcDir, 'node_modules');
|
|
186
|
+
const destNodeModules = path.join(DAEMON_INSTALL_DIR, 'node_modules');
|
|
187
|
+
|
|
188
|
+
// Essential dependencies for daemon
|
|
189
|
+
const deps = ['protobufjs', 'ws', 'long', 'signale', 'chalk', 'figures'];
|
|
190
|
+
|
|
191
|
+
if (!fs.existsSync(destNodeModules)) {
|
|
192
|
+
fs.mkdirSync(destNodeModules, { recursive: true });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const dep of deps) {
|
|
196
|
+
const srcDep = path.join(nodeModulesDir, dep);
|
|
197
|
+
const destDep = path.join(destNodeModules, dep);
|
|
198
|
+
|
|
199
|
+
if (fs.existsSync(srcDep) && !fs.existsSync(destDep)) {
|
|
200
|
+
copyDirSync(srcDep, destDep);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Write version file
|
|
205
|
+
fs.writeFileSync(DAEMON_VERSION_FILE, currentVersion);
|
|
206
|
+
|
|
207
|
+
log.info('Daemon installed successfully');
|
|
208
|
+
return true;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
log.error('Failed to install daemon', { error: err.message });
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Recursively copy directory
|
|
217
|
+
* @param {string} src
|
|
218
|
+
* @param {string} dest
|
|
219
|
+
*/
|
|
220
|
+
function copyDirSync(src, dest) {
|
|
221
|
+
if (!fs.existsSync(dest)) {
|
|
222
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
226
|
+
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
const srcPath = path.join(src, entry.name);
|
|
229
|
+
const destPath = path.join(dest, entry.name);
|
|
230
|
+
|
|
231
|
+
if (entry.isDirectory()) {
|
|
232
|
+
copyDirSync(srcPath, destPath);
|
|
233
|
+
} else {
|
|
234
|
+
fs.copyFileSync(srcPath, destPath);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
89
239
|
/**
|
|
90
240
|
* Start daemon in foreground (blocking)
|
|
91
241
|
* Used when running `hqx --daemon`
|
|
@@ -147,7 +297,7 @@ async function startDaemonForeground() {
|
|
|
147
297
|
}
|
|
148
298
|
|
|
149
299
|
/**
|
|
150
|
-
* Start daemon in background
|
|
300
|
+
* Start daemon in background (from isolated directory)
|
|
151
301
|
* @returns {Promise<boolean>}
|
|
152
302
|
*/
|
|
153
303
|
async function startDaemonBackground() {
|
|
@@ -156,8 +306,60 @@ async function startDaemonBackground() {
|
|
|
156
306
|
return true;
|
|
157
307
|
}
|
|
158
308
|
|
|
309
|
+
// Install daemon to isolated directory first
|
|
310
|
+
const installed = installDaemon();
|
|
311
|
+
if (!installed) {
|
|
312
|
+
log.error('Failed to install daemon');
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Check if isolated daemon entry exists
|
|
317
|
+
if (!fs.existsSync(DAEMON_ENTRY)) {
|
|
318
|
+
log.error('Daemon entry not found', { path: DAEMON_ENTRY });
|
|
319
|
+
// Fallback to direct execution
|
|
320
|
+
return startDaemonDirect();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return new Promise((resolve) => {
|
|
324
|
+
// Spawn daemon from isolated directory (survives npm updates)
|
|
325
|
+
const daemon = spawn(process.execPath, [DAEMON_ENTRY], {
|
|
326
|
+
detached: true,
|
|
327
|
+
stdio: 'ignore',
|
|
328
|
+
cwd: DAEMON_INSTALL_DIR,
|
|
329
|
+
env: {
|
|
330
|
+
...process.env,
|
|
331
|
+
NODE_PATH: path.join(DAEMON_INSTALL_DIR, 'node_modules'),
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
daemon.unref();
|
|
336
|
+
|
|
337
|
+
// Wait for daemon to start
|
|
338
|
+
let attempts = 0;
|
|
339
|
+
const maxAttempts = 30;
|
|
340
|
+
|
|
341
|
+
const check = setInterval(() => {
|
|
342
|
+
attempts++;
|
|
343
|
+
|
|
344
|
+
if (isDaemonRunning()) {
|
|
345
|
+
clearInterval(check);
|
|
346
|
+
log.debug('Daemon started from isolated directory');
|
|
347
|
+
resolve(true);
|
|
348
|
+
} else if (attempts >= maxAttempts) {
|
|
349
|
+
clearInterval(check);
|
|
350
|
+
log.error('Daemon failed to start');
|
|
351
|
+
resolve(false);
|
|
352
|
+
}
|
|
353
|
+
}, 100);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Start daemon directly (fallback if isolation fails)
|
|
359
|
+
* @returns {Promise<boolean>}
|
|
360
|
+
*/
|
|
361
|
+
function startDaemonDirect() {
|
|
159
362
|
return new Promise((resolve) => {
|
|
160
|
-
// Spawn daemon process
|
|
161
363
|
const daemon = spawn(process.execPath, [
|
|
162
364
|
require.resolve('../../cli-daemon'),
|
|
163
365
|
], {
|
|
@@ -167,7 +369,6 @@ async function startDaemonBackground() {
|
|
|
167
369
|
|
|
168
370
|
daemon.unref();
|
|
169
371
|
|
|
170
|
-
// Wait for daemon to start
|
|
171
372
|
let attempts = 0;
|
|
172
373
|
const maxAttempts = 20;
|
|
173
374
|
|
|
@@ -176,7 +377,7 @@ async function startDaemonBackground() {
|
|
|
176
377
|
|
|
177
378
|
if (isDaemonRunning()) {
|
|
178
379
|
clearInterval(check);
|
|
179
|
-
log.debug('Daemon started');
|
|
380
|
+
log.debug('Daemon started (direct mode)');
|
|
180
381
|
resolve(true);
|
|
181
382
|
} else if (attempts >= maxAttempts) {
|
|
182
383
|
clearInterval(check);
|
|
@@ -248,6 +449,11 @@ module.exports = {
|
|
|
248
449
|
getDaemonClient,
|
|
249
450
|
ensureDaemon,
|
|
250
451
|
|
|
452
|
+
// Installation
|
|
453
|
+
installDaemon,
|
|
454
|
+
getCurrentVersion,
|
|
455
|
+
getInstalledDaemonVersion,
|
|
456
|
+
|
|
251
457
|
// Utilities
|
|
252
458
|
isDaemonRunning,
|
|
253
459
|
getDaemonPid,
|
|
@@ -255,4 +461,5 @@ module.exports = {
|
|
|
255
461
|
// Constants
|
|
256
462
|
SOCKET_PATH,
|
|
257
463
|
PID_FILE,
|
|
464
|
+
DAEMON_INSTALL_DIR,
|
|
258
465
|
};
|
|
@@ -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
|
}
|