hedgequantx 2.9.181 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.181",
3
+ "version": "2.9.183",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
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: 'STARTING BROKER DAEMON...', color: 'yellow' }).start();
274
+ const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
275
275
  try {
276
- // Use BrokerClient to go through daemon (persists connections)
277
- const { RithmicBrokerClient, manager: brokerManager } = require('./services/rithmic-broker');
276
+ // Direct connection to Rithmic (no daemon)
277
+ const { RithmicService } = require('./services/rithmic');
278
278
 
279
- // Ensure daemon is running
280
- const daemonResult = await brokerManager.ensureRunning();
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
- spinner.text = 'FETCHING ACCOUNTS...';
294
- const accResult = await client.getTradingAccounts();
295
- client.accounts = accResult.accounts || [];
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 {
@@ -1,14 +1,14 @@
1
1
  /**
2
- * Connection Menus - Rithmic Only
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 { RithmicBrokerClient, manager: brokerManager } = require('../services/rithmic-broker');
9
+ const { RithmicService } = require('../services/rithmic');
10
10
  const { PROPFIRM_CHOICES } = require('../config');
11
- const { getLogoWidth, centerText, prepareStdin, displayBanner , clearScreen } = require('../ui');
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: 'STARTING BROKER DAEMON...', color: 'yellow' }).start();
89
+ const spinner = ora({ text: 'CONNECTING TO RITHMIC...', color: 'yellow' }).start();
91
90
 
92
91
  try {
93
- // Ensure broker daemon is running
94
- const daemonResult = await brokerManager.ensureRunning();
95
- if (!daemonResult.success) {
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
- spinner.text = 'FETCHING ACCOUNTS...';
108
- const accResult = await client.getTradingAccounts();
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 client;
100
+ return service;
113
101
  } else {
114
102
  // Detailed error messages for common Rithmic issues
115
103
  const err = (result.error || '').toLowerCase();
@@ -1,13 +1,15 @@
1
1
  /**
2
- * @fileoverview Secure session management - Rithmic Only
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, maskSensitive } = require('../security/encryption');
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
- const sessions = JSON.parse(decrypted);
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,21 +80,25 @@ const storage = {
84
80
  },
85
81
  };
86
82
 
87
- // Lazy load services to avoid circular dependencies
88
- let RithmicBrokerClient, brokerManager;
89
- const loadServices = () => {
90
- if (!RithmicBrokerClient) {
91
- ({ RithmicBrokerClient, manager: brokerManager } = require('./rithmic-broker'));
83
+ // Lazy load RithmicService
84
+ let RithmicService;
85
+ const loadRithmicService = () => {
86
+ if (!RithmicService) {
87
+ ({ RithmicService } = require('./rithmic'));
92
88
  }
89
+ return RithmicService;
93
90
  };
94
91
 
95
92
  /**
96
- * Multi-connection manager (Rithmic only)
93
+ * Multi-connection manager - Direct Rithmic connections
97
94
  */
98
95
  const connections = {
99
96
  /** @type {Array<{type: string, service: Object, propfirm: string, propfirmKey: string, connectedAt: Date}>} */
100
97
  services: [],
101
98
 
99
+ /**
100
+ * Add a new connection
101
+ */
102
102
  add(type, service, propfirm = null) {
103
103
  this.services.push({
104
104
  type,
@@ -112,7 +112,7 @@ const connections = {
112
112
  },
113
113
 
114
114
  /**
115
- * Sanitize account data to prevent corrupted data from being saved
115
+ * Sanitize account data
116
116
  */
117
117
  _sanitizeAccount(acc) {
118
118
  if (!acc || typeof acc !== 'object' || !acc.accountId) return null;
@@ -124,14 +124,14 @@ const connections = {
124
124
  };
125
125
  },
126
126
 
127
+ /**
128
+ * Save sessions to encrypted storage
129
+ */
127
130
  saveToStorage() {
128
- // Load existing sessions to preserve AI agents
129
131
  const existingSessions = storage.load();
130
132
  const aiSessions = existingSessions.filter(s => s.type === 'ai');
131
133
 
132
- // Build Rithmic sessions - INCLUDE accounts to avoid Rithmic API limit on restore
133
134
  const rithmicSessions = this.services.map(conn => {
134
- // Sanitize accounts to prevent corrupted data
135
135
  const rawAccounts = conn.service.accounts || [];
136
136
  const accounts = rawAccounts.map(a => this._sanitizeAccount(a)).filter(Boolean);
137
137
 
@@ -140,58 +140,17 @@ const connections = {
140
140
  propfirm: conn.propfirm,
141
141
  propfirmKey: conn.service.propfirmKey || conn.propfirmKey,
142
142
  credentials: conn.service.credentials,
143
- accounts, // CRITICAL: Cache sanitized accounts to avoid 2000 GetAccounts limit
143
+ accounts,
144
144
  };
145
145
  });
146
146
 
147
- // Merge: AI sessions + Rithmic sessions
148
147
  storage.save([...aiSessions, ...rithmicSessions]);
149
- log.debug('Session saved', { rithmicCount: rithmicSessions.length, hasAccounts: rithmicSessions.some(s => s.accounts?.length > 0) });
150
148
  },
151
149
 
150
+ /**
151
+ * Restore sessions from storage - Direct connection to Rithmic
152
+ */
152
153
  async restoreFromStorage() {
153
- loadServices();
154
-
155
- // Check if daemon is already running with active connections
156
- const daemonStatus = await brokerManager.getStatus();
157
-
158
- if (daemonStatus.running && daemonStatus.connections?.length > 0) {
159
- // Daemon has active connections - just create clients (NO API calls)
160
- log.info('Daemon active, restoring from broker', { connections: daemonStatus.connections.length });
161
-
162
- for (const conn of daemonStatus.connections) {
163
- const client = new RithmicBrokerClient(conn.propfirmKey);
164
- await client.connect();
165
-
166
- // Get accounts from daemon cache
167
- const accountsResult = await client.getTradingAccounts();
168
- client.accounts = accountsResult.accounts || [];
169
-
170
- // Cache credentials locally for sync access (fetch from daemon)
171
- try {
172
- const creds = await client.getRithmicCredentialsAsync();
173
- if (creds && creds.userId && creds.password) {
174
- client.credentials = { username: creds.userId, password: creds.password };
175
- client.propfirm = { name: conn.propfirmKey, systemName: creds.systemName, gateway: creds.gateway };
176
- }
177
- } catch (e) {
178
- log.warn('Failed to cache credentials', { propfirm: conn.propfirmKey, error: e.message });
179
- }
180
-
181
- this.services.push({
182
- type: 'rithmic',
183
- service: client,
184
- propfirm: conn.propfirm,
185
- propfirmKey: conn.propfirmKey,
186
- connectedAt: new Date(conn.connectedAt),
187
- });
188
- log.debug('Restored from broker', { propfirm: conn.propfirmKey, hasCreds: !!client.credentials });
189
- }
190
-
191
- return this.services.length > 0;
192
- }
193
-
194
- // Daemon not running or no connections - check local storage
195
154
  const sessions = storage.load();
196
155
  const rithmicSessions = sessions.filter(s => s.type === 'rithmic');
197
156
 
@@ -199,52 +158,69 @@ const connections = {
199
158
  return false;
200
159
  }
201
160
 
202
- log.info('Restoring sessions via broker', { count: rithmicSessions.length });
161
+ log.info('Restoring sessions', { count: rithmicSessions.length });
203
162
 
204
163
  for (const session of rithmicSessions) {
205
164
  try {
206
165
  await this._restoreSession(session);
207
166
  } catch (err) {
208
- log.warn('Failed to restore session', { type: session.type, error: err.message });
167
+ log.warn('Failed to restore session', { error: err.message });
209
168
  }
210
169
  }
211
170
 
212
171
  return this.services.length > 0;
213
172
  },
214
173
 
174
+ /**
175
+ * Restore a single session using direct RithmicService
176
+ */
215
177
  async _restoreSession(session) {
216
178
  const { type, propfirm, propfirmKey } = session;
217
179
 
218
- // Use broker client (daemon handles persistence)
219
- if (type === 'rithmic' && session.credentials) {
220
- const client = new RithmicBrokerClient(propfirmKey || 'apex_rithmic');
221
-
222
- // Validate cached accounts before using
223
- let validAccounts = null;
224
- if (session.accounts && Array.isArray(session.accounts)) {
225
- validAccounts = session.accounts
226
- .map(a => this._sanitizeAccount(a))
227
- .filter(Boolean);
228
- if (validAccounts.length === 0) validAccounts = null;
229
- }
230
-
231
- // CRITICAL: Pass validated cached accounts to avoid Rithmic's 2000 GetAccounts limit
232
- const loginOptions = validAccounts ? { cachedAccounts: validAccounts } : {};
233
- const result = await client.login(session.credentials.username, session.credentials.password, loginOptions);
234
-
235
- if (result.success) {
236
- this.services.push({
237
- type,
238
- service: client,
239
- propfirm,
240
- propfirmKey,
241
- connectedAt: new Date(),
242
- });
243
- log.debug('Rithmic session restored via broker', { hasCachedAccounts: !!validAccounts, accountCount: validAccounts?.length || 0 });
244
- }
180
+ if (type !== 'rithmic' || !session.credentials) {
181
+ return;
182
+ }
183
+
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 });
245
218
  }
246
219
  },
247
220
 
221
+ /**
222
+ * Remove a connection by index
223
+ */
248
224
  remove(index) {
249
225
  if (index < 0 || index >= this.services.length) return;
250
226
 
@@ -279,6 +255,9 @@ const connections = {
279
255
  return this.services.length;
280
256
  },
281
257
 
258
+ /**
259
+ * Get all accounts from all connections
260
+ */
282
261
  async getAllAccounts() {
283
262
  const allAccounts = [];
284
263
 
@@ -304,6 +283,9 @@ const connections = {
304
283
  return allAccounts;
305
284
  },
306
285
 
286
+ /**
287
+ * Get service for a specific account
288
+ */
307
289
  getServiceForAccount(accountId) {
308
290
  for (const conn of this.services) {
309
291
  if (!conn.service?.accounts) continue;
@@ -323,22 +305,14 @@ const connections = {
323
305
  return this.services.length > 0;
324
306
  },
325
307
 
308
+ /**
309
+ * Disconnect all connections
310
+ */
326
311
  async disconnectAll() {
327
- loadServices();
328
-
329
- // Stop the broker daemon (closes all Rithmic connections)
330
- try {
331
- await brokerManager.stop();
332
- log.info('Broker daemon stopped');
333
- } catch (err) {
334
- log.warn('Broker stop failed', { error: err.message });
335
- }
336
-
337
- // Disconnect local clients
338
312
  for (const conn of this.services) {
339
313
  try {
340
314
  if (conn.service?.disconnect) {
341
- conn.service.disconnect();
315
+ await conn.service.disconnect();
342
316
  }
343
317
  } catch (err) {
344
318
  log.warn('Disconnect failed', { type: conn.type, error: err.message });