hedgequantx 2.9.182 → 2.9.183
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 +1 -1
- package/src/app.js +8 -21
- package/src/menus/connect.js +10 -22
- package/src/services/session.js +75 -164
- package/src/services/rithmic-broker/client.js +0 -321
- package/src/services/rithmic-broker/daemon-reconnect.js +0 -353
- package/src/services/rithmic-broker/daemon.js +0 -469
- package/src/services/rithmic-broker/index.js +0 -46
- package/src/services/rithmic-broker/manager.js +0 -264
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -271,31 +271,18 @@ const run = async () => {
|
|
|
271
271
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
272
272
|
|
|
273
273
|
if (credentials) {
|
|
274
|
-
const spinner = ora({ text: '
|
|
274
|
+
const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
|
|
275
275
|
try {
|
|
276
|
-
//
|
|
277
|
-
const {
|
|
276
|
+
// Direct connection to Rithmic (no daemon)
|
|
277
|
+
const { RithmicService } = require('./services/rithmic');
|
|
278
278
|
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
if (!daemonResult.success) {
|
|
282
|
-
spinner.fail('FAILED TO START BROKER DAEMON');
|
|
283
|
-
console.log(chalk.yellow(` → ${daemonResult.error}`));
|
|
284
|
-
await new Promise(r => setTimeout(r, 3000));
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
spinner.text = 'CONNECTING TO RITHMIC...';
|
|
289
|
-
const client = new RithmicBrokerClient(selectedPropfirm.key);
|
|
290
|
-
const result = await client.login(credentials.username, credentials.password);
|
|
279
|
+
const service = new RithmicService(selectedPropfirm.key);
|
|
280
|
+
const result = await service.login(credentials.username, credentials.password);
|
|
291
281
|
|
|
292
282
|
if (result.success) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
connections.add('rithmic', client, selectedPropfirm.name);
|
|
297
|
-
spinner.succeed(`CONNECTED TO ${selectedPropfirm.name.toUpperCase()} (${accResult.accounts?.length || 0} ACCOUNTS)`);
|
|
298
|
-
currentService = client;
|
|
283
|
+
connections.add('rithmic', service, selectedPropfirm.name);
|
|
284
|
+
spinner.succeed(`CONNECTED TO ${selectedPropfirm.name.toUpperCase()} (${result.accounts?.length || 0} ACCOUNTS)`);
|
|
285
|
+
currentService = service;
|
|
299
286
|
await refreshStats();
|
|
300
287
|
await new Promise(r => setTimeout(r, 1500));
|
|
301
288
|
} else {
|
package/src/menus/connect.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Connection Menus - Rithmic
|
|
2
|
+
* Connection Menus - Direct Rithmic Connection
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const chalk = require('chalk');
|
|
6
6
|
const ora = require('ora');
|
|
7
7
|
|
|
8
8
|
const { connections } = require('../services');
|
|
9
|
-
const {
|
|
9
|
+
const { RithmicService } = require('../services/rithmic');
|
|
10
10
|
const { PROPFIRM_CHOICES } = require('../config');
|
|
11
|
-
const { getLogoWidth, centerText, prepareStdin, displayBanner
|
|
11
|
+
const { getLogoWidth, centerText, prepareStdin, displayBanner, clearScreen } = require('../ui');
|
|
12
12
|
const { validateUsername, validatePassword } = require('../security');
|
|
13
13
|
const { prompts } = require('../utils');
|
|
14
14
|
|
|
@@ -38,7 +38,6 @@ const loginPrompt = async (propfirmName) => {
|
|
|
38
38
|
* Rithmic menu - Main connection menu
|
|
39
39
|
*/
|
|
40
40
|
const rithmicMenu = async () => {
|
|
41
|
-
// Clear screen and show banner
|
|
42
41
|
clearScreen();
|
|
43
42
|
displayBanner();
|
|
44
43
|
|
|
@@ -87,29 +86,18 @@ const rithmicMenu = async () => {
|
|
|
87
86
|
const credentials = await loginPrompt(selectedPropfirm.name);
|
|
88
87
|
if (!credentials) return null;
|
|
89
88
|
|
|
90
|
-
const spinner = ora({ text: '
|
|
89
|
+
const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
|
|
91
90
|
|
|
92
91
|
try {
|
|
93
|
-
//
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
spinner.fail('FAILED TO START BROKER DAEMON');
|
|
97
|
-
console.log(chalk.yellow(` → ${daemonResult.error}`));
|
|
98
|
-
await new Promise(r => setTimeout(r, 3000));
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
spinner.text = 'CONNECTING TO RITHMIC...';
|
|
103
|
-
const client = new RithmicBrokerClient(selectedPropfirm.key);
|
|
104
|
-
const result = await client.login(credentials.username, credentials.password);
|
|
92
|
+
// Direct connection to Rithmic (no daemon)
|
|
93
|
+
const service = new RithmicService(selectedPropfirm.key);
|
|
94
|
+
const result = await service.login(credentials.username, credentials.password);
|
|
105
95
|
|
|
106
96
|
if (result.success) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
connections.add('rithmic', client, client.propfirm.name || selectedPropfirm.name);
|
|
110
|
-
spinner.succeed(`CONNECTED TO ${selectedPropfirm.name.toUpperCase()} (${accResult.accounts?.length || 0} ACCOUNTS)`);
|
|
97
|
+
connections.add('rithmic', service, selectedPropfirm.name);
|
|
98
|
+
spinner.succeed(`CONNECTED TO ${selectedPropfirm.name.toUpperCase()} (${result.accounts?.length || 0} ACCOUNTS)`);
|
|
111
99
|
await new Promise(r => setTimeout(r, 1500));
|
|
112
|
-
return
|
|
100
|
+
return service;
|
|
113
101
|
} else {
|
|
114
102
|
// Detailed error messages for common Rithmic issues
|
|
115
103
|
const err = (result.error || '').toLowerCase();
|
package/src/services/session.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Secure session management - Rithmic
|
|
2
|
+
* @fileoverview Secure session management - Direct Rithmic Connection
|
|
3
3
|
* @module services/session
|
|
4
|
+
*
|
|
5
|
+
* NO DAEMON - Direct connection to Rithmic API
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
const fs = require('fs');
|
|
7
9
|
const path = require('path');
|
|
8
10
|
const os = require('os');
|
|
9
11
|
const crypto = require('crypto');
|
|
10
|
-
const { encrypt, decrypt
|
|
12
|
+
const { encrypt, decrypt } = require('../security/encryption');
|
|
11
13
|
const { SECURITY } = require('../config/settings');
|
|
12
14
|
const { logger } = require('../utils/logger');
|
|
13
15
|
|
|
@@ -23,7 +25,6 @@ const storage = {
|
|
|
23
25
|
_ensureDir() {
|
|
24
26
|
if (!fs.existsSync(SESSION_DIR)) {
|
|
25
27
|
fs.mkdirSync(SESSION_DIR, { recursive: true, mode: SECURITY.DIR_PERMISSIONS });
|
|
26
|
-
log.debug('Created session directory');
|
|
27
28
|
}
|
|
28
29
|
},
|
|
29
30
|
|
|
@@ -33,7 +34,6 @@ const storage = {
|
|
|
33
34
|
const data = JSON.stringify(sessions);
|
|
34
35
|
const encrypted = encrypt(data);
|
|
35
36
|
fs.writeFileSync(SESSION_FILE, encrypted, { mode: SECURITY.FILE_PERMISSIONS });
|
|
36
|
-
log.debug('Session saved', { count: sessions.length });
|
|
37
37
|
return true;
|
|
38
38
|
} catch (err) {
|
|
39
39
|
log.error('Failed to save session', { error: err.message });
|
|
@@ -51,14 +51,11 @@ const storage = {
|
|
|
51
51
|
const decrypted = decrypt(encrypted);
|
|
52
52
|
|
|
53
53
|
if (!decrypted) {
|
|
54
|
-
log.warn('Session decryption failed - clearing');
|
|
55
54
|
this.clear();
|
|
56
55
|
return [];
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
log.debug('Session loaded', { count: sessions.length });
|
|
61
|
-
return sessions;
|
|
58
|
+
return JSON.parse(decrypted);
|
|
62
59
|
} catch (err) {
|
|
63
60
|
log.error('Failed to load session', { error: err.message });
|
|
64
61
|
this.clear();
|
|
@@ -74,7 +71,6 @@ const storage = {
|
|
|
74
71
|
fs.writeFileSync(SESSION_FILE, crypto.randomBytes(size));
|
|
75
72
|
}
|
|
76
73
|
fs.unlinkSync(SESSION_FILE);
|
|
77
|
-
log.debug('Session cleared securely');
|
|
78
74
|
}
|
|
79
75
|
return true;
|
|
80
76
|
} catch (err) {
|
|
@@ -84,27 +80,25 @@ const storage = {
|
|
|
84
80
|
},
|
|
85
81
|
};
|
|
86
82
|
|
|
87
|
-
// Lazy load
|
|
88
|
-
let
|
|
89
|
-
const
|
|
90
|
-
if (!RithmicBrokerClient) {
|
|
91
|
-
({ RithmicBrokerClient, manager: brokerManager } = require('./rithmic-broker'));
|
|
92
|
-
}
|
|
83
|
+
// Lazy load RithmicService
|
|
84
|
+
let RithmicService;
|
|
85
|
+
const loadRithmicService = () => {
|
|
93
86
|
if (!RithmicService) {
|
|
94
87
|
({ RithmicService } = require('./rithmic'));
|
|
95
88
|
}
|
|
89
|
+
return RithmicService;
|
|
96
90
|
};
|
|
97
91
|
|
|
98
|
-
// Direct mode flag - bypass daemon completely
|
|
99
|
-
const DIRECT_MODE = process.env.HQX_DIRECT === '1' || true; // Default to direct for now
|
|
100
|
-
|
|
101
92
|
/**
|
|
102
|
-
* Multi-connection manager
|
|
93
|
+
* Multi-connection manager - Direct Rithmic connections
|
|
103
94
|
*/
|
|
104
95
|
const connections = {
|
|
105
96
|
/** @type {Array<{type: string, service: Object, propfirm: string, propfirmKey: string, connectedAt: Date}>} */
|
|
106
97
|
services: [],
|
|
107
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Add a new connection
|
|
101
|
+
*/
|
|
108
102
|
add(type, service, propfirm = null) {
|
|
109
103
|
this.services.push({
|
|
110
104
|
type,
|
|
@@ -118,7 +112,7 @@ const connections = {
|
|
|
118
112
|
},
|
|
119
113
|
|
|
120
114
|
/**
|
|
121
|
-
* Sanitize account data
|
|
115
|
+
* Sanitize account data
|
|
122
116
|
*/
|
|
123
117
|
_sanitizeAccount(acc) {
|
|
124
118
|
if (!acc || typeof acc !== 'object' || !acc.accountId) return null;
|
|
@@ -130,14 +124,14 @@ const connections = {
|
|
|
130
124
|
};
|
|
131
125
|
},
|
|
132
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Save sessions to encrypted storage
|
|
129
|
+
*/
|
|
133
130
|
saveToStorage() {
|
|
134
|
-
// Load existing sessions to preserve AI agents
|
|
135
131
|
const existingSessions = storage.load();
|
|
136
132
|
const aiSessions = existingSessions.filter(s => s.type === 'ai');
|
|
137
133
|
|
|
138
|
-
// Build Rithmic sessions - INCLUDE accounts to avoid Rithmic API limit on restore
|
|
139
134
|
const rithmicSessions = this.services.map(conn => {
|
|
140
|
-
// Sanitize accounts to prevent corrupted data
|
|
141
135
|
const rawAccounts = conn.service.accounts || [];
|
|
142
136
|
const accounts = rawAccounts.map(a => this._sanitizeAccount(a)).filter(Boolean);
|
|
143
137
|
|
|
@@ -146,80 +140,17 @@ const connections = {
|
|
|
146
140
|
propfirm: conn.propfirm,
|
|
147
141
|
propfirmKey: conn.service.propfirmKey || conn.propfirmKey,
|
|
148
142
|
credentials: conn.service.credentials,
|
|
149
|
-
accounts,
|
|
143
|
+
accounts,
|
|
150
144
|
};
|
|
151
145
|
});
|
|
152
146
|
|
|
153
|
-
// Merge: AI sessions + Rithmic sessions
|
|
154
147
|
storage.save([...aiSessions, ...rithmicSessions]);
|
|
155
|
-
log.debug('Session saved', { rithmicCount: rithmicSessions.length, hasAccounts: rithmicSessions.some(s => s.accounts?.length > 0) });
|
|
156
148
|
},
|
|
157
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Restore sessions from storage - Direct connection to Rithmic
|
|
152
|
+
*/
|
|
158
153
|
async restoreFromStorage() {
|
|
159
|
-
loadServices();
|
|
160
|
-
|
|
161
|
-
// DIRECT MODE: Bypass daemon, connect directly to Rithmic
|
|
162
|
-
if (DIRECT_MODE) {
|
|
163
|
-
log.info('Direct mode enabled - bypassing daemon');
|
|
164
|
-
|
|
165
|
-
const sessions = storage.load();
|
|
166
|
-
const rithmicSessions = sessions.filter(s => s.type === 'rithmic');
|
|
167
|
-
|
|
168
|
-
if (!rithmicSessions.length) {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
for (const session of rithmicSessions) {
|
|
173
|
-
try {
|
|
174
|
-
await this._restoreSessionDirect(session);
|
|
175
|
-
} catch (err) {
|
|
176
|
-
log.warn('Failed to restore session direct', { error: err.message });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return this.services.length > 0;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Check if daemon is already running with active connections
|
|
184
|
-
const daemonStatus = await brokerManager.getStatus();
|
|
185
|
-
|
|
186
|
-
if (daemonStatus.running && daemonStatus.connections?.length > 0) {
|
|
187
|
-
// Daemon has active connections - just create clients (NO API calls)
|
|
188
|
-
log.info('Daemon active, restoring from broker', { connections: daemonStatus.connections.length });
|
|
189
|
-
|
|
190
|
-
for (const conn of daemonStatus.connections) {
|
|
191
|
-
const client = new RithmicBrokerClient(conn.propfirmKey);
|
|
192
|
-
await client.connect();
|
|
193
|
-
|
|
194
|
-
// Get accounts from daemon cache
|
|
195
|
-
const accountsResult = await client.getTradingAccounts();
|
|
196
|
-
client.accounts = accountsResult.accounts || [];
|
|
197
|
-
|
|
198
|
-
// Cache credentials locally for sync access (fetch from daemon)
|
|
199
|
-
try {
|
|
200
|
-
const creds = await client.getRithmicCredentialsAsync();
|
|
201
|
-
if (creds && creds.userId && creds.password) {
|
|
202
|
-
client.credentials = { username: creds.userId, password: creds.password };
|
|
203
|
-
client.propfirm = { name: conn.propfirmKey, systemName: creds.systemName, gateway: creds.gateway };
|
|
204
|
-
}
|
|
205
|
-
} catch (e) {
|
|
206
|
-
log.warn('Failed to cache credentials', { propfirm: conn.propfirmKey, error: e.message });
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
this.services.push({
|
|
210
|
-
type: 'rithmic',
|
|
211
|
-
service: client,
|
|
212
|
-
propfirm: conn.propfirm,
|
|
213
|
-
propfirmKey: conn.propfirmKey,
|
|
214
|
-
connectedAt: new Date(conn.connectedAt),
|
|
215
|
-
});
|
|
216
|
-
log.debug('Restored from broker', { propfirm: conn.propfirmKey, hasCreds: !!client.credentials });
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return this.services.length > 0;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Daemon not running or no connections - check local storage
|
|
223
154
|
const sessions = storage.load();
|
|
224
155
|
const rithmicSessions = sessions.filter(s => s.type === 'rithmic');
|
|
225
156
|
|
|
@@ -227,87 +158,69 @@ const connections = {
|
|
|
227
158
|
return false;
|
|
228
159
|
}
|
|
229
160
|
|
|
230
|
-
log.info('Restoring sessions
|
|
161
|
+
log.info('Restoring sessions', { count: rithmicSessions.length });
|
|
231
162
|
|
|
232
163
|
for (const session of rithmicSessions) {
|
|
233
164
|
try {
|
|
234
165
|
await this._restoreSession(session);
|
|
235
166
|
} catch (err) {
|
|
236
|
-
log.warn('Failed to restore session', {
|
|
167
|
+
log.warn('Failed to restore session', { error: err.message });
|
|
237
168
|
}
|
|
238
169
|
}
|
|
239
170
|
|
|
240
171
|
return this.services.length > 0;
|
|
241
172
|
},
|
|
242
|
-
|
|
173
|
+
|
|
243
174
|
/**
|
|
244
|
-
* Restore session using direct RithmicService
|
|
175
|
+
* Restore a single session using direct RithmicService
|
|
245
176
|
*/
|
|
246
|
-
async
|
|
177
|
+
async _restoreSession(session) {
|
|
247
178
|
const { type, propfirm, propfirmKey } = session;
|
|
248
179
|
|
|
249
|
-
if (type
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// Validate cached accounts
|
|
253
|
-
let validAccounts = null;
|
|
254
|
-
if (session.accounts && Array.isArray(session.accounts)) {
|
|
255
|
-
validAccounts = session.accounts
|
|
256
|
-
.map(a => this._sanitizeAccount(a))
|
|
257
|
-
.filter(Boolean);
|
|
258
|
-
if (validAccounts.length === 0) validAccounts = null;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Login with cached accounts to avoid API limit
|
|
262
|
-
const loginOptions = validAccounts ? { skipFetchAccounts: true, cachedAccounts: validAccounts } : {};
|
|
263
|
-
const result = await service.login(session.credentials.username, session.credentials.password, loginOptions);
|
|
264
|
-
|
|
265
|
-
if (result.success) {
|
|
266
|
-
this.services.push({
|
|
267
|
-
type,
|
|
268
|
-
service,
|
|
269
|
-
propfirm,
|
|
270
|
-
propfirmKey,
|
|
271
|
-
connectedAt: new Date(),
|
|
272
|
-
});
|
|
273
|
-
log.info('Direct session restored', { propfirm, accounts: service.accounts?.length || 0 });
|
|
274
|
-
}
|
|
180
|
+
if (type !== 'rithmic' || !session.credentials) {
|
|
181
|
+
return;
|
|
275
182
|
}
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
async _restoreSession(session) {
|
|
279
|
-
const { type, propfirm, propfirmKey } = session;
|
|
280
183
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
184
|
+
const Service = loadRithmicService();
|
|
185
|
+
const service = new Service(propfirmKey || 'apex_rithmic');
|
|
186
|
+
|
|
187
|
+
// Validate cached accounts
|
|
188
|
+
let validAccounts = null;
|
|
189
|
+
if (session.accounts && Array.isArray(session.accounts)) {
|
|
190
|
+
validAccounts = session.accounts
|
|
191
|
+
.map(a => this._sanitizeAccount(a))
|
|
192
|
+
.filter(Boolean);
|
|
193
|
+
if (validAccounts.length === 0) validAccounts = null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Login with cached accounts to avoid Rithmic API limit
|
|
197
|
+
const loginOptions = validAccounts
|
|
198
|
+
? { skipFetchAccounts: true, cachedAccounts: validAccounts }
|
|
199
|
+
: {};
|
|
200
|
+
|
|
201
|
+
const result = await service.login(
|
|
202
|
+
session.credentials.username,
|
|
203
|
+
session.credentials.password,
|
|
204
|
+
loginOptions
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (result.success) {
|
|
208
|
+
this.services.push({
|
|
209
|
+
type,
|
|
210
|
+
service,
|
|
211
|
+
propfirm,
|
|
212
|
+
propfirmKey,
|
|
213
|
+
connectedAt: new Date(),
|
|
214
|
+
});
|
|
215
|
+
log.info('Session restored', { propfirm, accounts: service.accounts?.length || 0 });
|
|
216
|
+
} else {
|
|
217
|
+
log.warn('Session restore failed', { propfirm, error: result.error });
|
|
308
218
|
}
|
|
309
219
|
},
|
|
310
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Remove a connection by index
|
|
223
|
+
*/
|
|
311
224
|
remove(index) {
|
|
312
225
|
if (index < 0 || index >= this.services.length) return;
|
|
313
226
|
|
|
@@ -342,6 +255,9 @@ const connections = {
|
|
|
342
255
|
return this.services.length;
|
|
343
256
|
},
|
|
344
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Get all accounts from all connections
|
|
260
|
+
*/
|
|
345
261
|
async getAllAccounts() {
|
|
346
262
|
const allAccounts = [];
|
|
347
263
|
|
|
@@ -367,6 +283,9 @@ const connections = {
|
|
|
367
283
|
return allAccounts;
|
|
368
284
|
},
|
|
369
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Get service for a specific account
|
|
288
|
+
*/
|
|
370
289
|
getServiceForAccount(accountId) {
|
|
371
290
|
for (const conn of this.services) {
|
|
372
291
|
if (!conn.service?.accounts) continue;
|
|
@@ -386,22 +305,14 @@ const connections = {
|
|
|
386
305
|
return this.services.length > 0;
|
|
387
306
|
},
|
|
388
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Disconnect all connections
|
|
310
|
+
*/
|
|
389
311
|
async disconnectAll() {
|
|
390
|
-
loadServices();
|
|
391
|
-
|
|
392
|
-
// Stop the broker daemon (closes all Rithmic connections)
|
|
393
|
-
try {
|
|
394
|
-
await brokerManager.stop();
|
|
395
|
-
log.info('Broker daemon stopped');
|
|
396
|
-
} catch (err) {
|
|
397
|
-
log.warn('Broker stop failed', { error: err.message });
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Disconnect local clients
|
|
401
312
|
for (const conn of this.services) {
|
|
402
313
|
try {
|
|
403
314
|
if (conn.service?.disconnect) {
|
|
404
|
-
conn.service.disconnect();
|
|
315
|
+
await conn.service.disconnect();
|
|
405
316
|
}
|
|
406
317
|
} catch (err) {
|
|
407
318
|
log.warn('Disconnect failed', { type: conn.type, error: err.message });
|