hedgequantx 2.9.59 → 2.9.61

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.59",
3
+ "version": "2.9.61",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -216,9 +216,9 @@ const decodeProductCodes = (buffer) => {
216
216
  } else if (wireType === 2) {
217
217
  const [val, newOff] = readString(data, offset);
218
218
  offset = newOff;
219
- // Field IDs from response_product_codes.proto
219
+ // Field IDs from Rithmic API
220
220
  if (fieldNumber === 110101) result.exchange = val; // exchange
221
- if (fieldNumber === 110102) result.productCode = val; // product_code (ES, MES, MNQ, etc.)
221
+ if (fieldNumber === 100749) result.productCode = val; // product_code (ES, MES, MNQ, etc.)
222
222
  if (fieldNumber === 110103) result.productName = val; // product_name
223
223
  } else {
224
224
  break;
@@ -30,8 +30,9 @@ const PNL_FIELDS = {
30
30
  const SYMBOL_FIELDS = {
31
31
  TEMPLATE_ID: 154467,
32
32
  RP_CODE: 132766,
33
+ RP_CODE_2: 132764, // Another rp_code field
33
34
  EXCHANGE: 110101,
34
- PRODUCT_CODE: 110102, // Base symbol (ES, NQ, MNQ)
35
+ PRODUCT_CODE: 100749, // Base symbol (ES, NQ, MNQ) - actual field ID from Rithmic
35
36
  PRODUCT_NAME: 110103, // Product name
36
37
  SYMBOL: 110100, // Full contract symbol (ESH26)
37
38
  TRADING_SYMBOL: 157095, // Trading symbol
@@ -262,43 +263,45 @@ function decodeInstrumentPnL(buffer) {
262
263
 
263
264
  /**
264
265
  * Decode ResponseProductCodes (template 112) - list of available symbols
265
- * @param {Buffer} buffer - Raw protobuf buffer
266
+ * @param {Buffer} buffer - Raw protobuf buffer (with 4-byte length prefix)
266
267
  * @returns {Object} Decoded product codes
267
268
  */
268
269
  function decodeProductCodes(buffer) {
270
+ // Skip 4-byte length prefix
271
+ const data = buffer.length > 4 ? buffer.slice(4) : buffer;
269
272
  const result = { rpCode: [] };
270
273
  let offset = 0;
271
274
 
272
- while (offset < buffer.length) {
275
+ while (offset < data.length) {
273
276
  try {
274
- const [tag, tagOffset] = readVarint(buffer, offset);
277
+ const [tag, tagOffset] = readVarint(data, offset);
275
278
  const wireType = tag & 0x7;
276
279
  const fieldNumber = tag >>> 3;
277
280
  offset = tagOffset;
278
281
 
279
282
  switch (fieldNumber) {
280
283
  case SYMBOL_FIELDS.TEMPLATE_ID:
281
- [result.templateId, offset] = readVarint(buffer, offset);
284
+ [result.templateId, offset] = readVarint(data, offset);
282
285
  break;
283
286
  case SYMBOL_FIELDS.RP_CODE:
284
287
  let rpCode;
285
- [rpCode, offset] = readLengthDelimited(buffer, offset);
288
+ [rpCode, offset] = readLengthDelimited(data, offset);
286
289
  result.rpCode.push(rpCode);
287
290
  break;
288
291
  case SYMBOL_FIELDS.EXCHANGE:
289
- [result.exchange, offset] = readLengthDelimited(buffer, offset);
292
+ [result.exchange, offset] = readLengthDelimited(data, offset);
290
293
  break;
291
294
  case SYMBOL_FIELDS.PRODUCT_CODE:
292
- [result.productCode, offset] = readLengthDelimited(buffer, offset);
295
+ [result.productCode, offset] = readLengthDelimited(data, offset);
293
296
  break;
294
297
  case SYMBOL_FIELDS.PRODUCT_NAME:
295
- [result.productName, offset] = readLengthDelimited(buffer, offset);
298
+ [result.productName, offset] = readLengthDelimited(data, offset);
296
299
  break;
297
300
  case SYMBOL_FIELDS.USER_MSG:
298
- [result.userMsg, offset] = readLengthDelimited(buffer, offset);
301
+ [result.userMsg, offset] = readLengthDelimited(data, offset);
299
302
  break;
300
303
  default:
301
- offset = skipField(buffer, offset, wireType);
304
+ offset = skipField(data, offset, wireType);
302
305
  }
303
306
  } catch (error) {
304
307
  break;
@@ -112,27 +112,36 @@ const connections = {
112
112
  },
113
113
 
114
114
  saveToStorage() {
115
- const sessions = this.services.map(conn => ({
115
+ // Load existing sessions to preserve AI agents
116
+ const existingSessions = storage.load();
117
+ const aiSessions = existingSessions.filter(s => s.type === 'ai');
118
+
119
+ // Build Rithmic sessions
120
+ const rithmicSessions = this.services.map(conn => ({
116
121
  type: conn.type,
117
122
  propfirm: conn.propfirm,
118
123
  propfirmKey: conn.service.propfirmKey || conn.propfirmKey,
119
124
  credentials: conn.service.credentials,
120
125
  }));
121
126
 
122
- storage.save(sessions);
127
+ // Merge: AI sessions + Rithmic sessions
128
+ storage.save([...aiSessions, ...rithmicSessions]);
123
129
  },
124
130
 
125
131
  async restoreFromStorage() {
126
132
  loadServices();
127
133
  const sessions = storage.load();
128
134
 
129
- if (!sessions.length) {
135
+ // Filter only Rithmic sessions (AI sessions are managed by ai-agents.js)
136
+ const rithmicSessions = sessions.filter(s => s.type === 'rithmic');
137
+
138
+ if (!rithmicSessions.length) {
130
139
  return false;
131
140
  }
132
141
 
133
- log.info('Restoring sessions', { count: sessions.length });
142
+ log.info('Restoring sessions', { count: rithmicSessions.length });
134
143
 
135
- for (const session of sessions) {
144
+ for (const session of rithmicSessions) {
136
145
  try {
137
146
  await this._restoreSession(session);
138
147
  } catch (err) {
@@ -1,19 +1,15 @@
1
1
  /**
2
- * @fileoverview Centralized prompts utility with animated yellow spinner
2
+ * @fileoverview Centralized prompts utility
3
3
  * @module utils/prompts
4
4
  *
5
- * Custom readline-based prompts with animated spinner that runs
6
- * while waiting for user input. Uses inquirer only for complex
7
- * prompts (password, list selection).
5
+ * Simple prompts using inquirer for user input.
6
+ * No spinners on prompts - spinners are only for data loading (use ora).
8
7
  */
9
8
 
10
9
  const inquirer = require('inquirer');
11
10
  const readline = require('readline');
12
11
  const chalk = require('chalk');
13
12
 
14
- // Spinner frames for yellow waiting indicator
15
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
16
-
17
13
  /** @type {readline.Interface|null} */
18
14
  let rl = null;
19
15
 
@@ -48,305 +44,45 @@ const closeReadline = () => {
48
44
  };
49
45
 
50
46
  /**
51
- * Animated spinner prompt using raw readline
52
- * Spinner animates while waiting for user input
53
- * @param {string} message - Prompt message
54
- * @returns {Promise<string>}
55
- */
56
- const animatedPrompt = (message) => {
57
- return new Promise((resolve) => {
58
- prepareStdin();
59
- closeReadline();
60
-
61
- let frameIndex = 0;
62
- let userInput = '';
63
- let cursorPos = 0;
64
-
65
- // Enable raw mode for character-by-character input
66
- if (process.stdin.isTTY) {
67
- process.stdin.setRawMode(true);
68
- }
69
- process.stdin.resume();
70
-
71
- const render = () => {
72
- const spinner = chalk.yellow(SPINNER_FRAMES[frameIndex]);
73
- const line = `\r${spinner} ${message} ${userInput}`;
74
- process.stdout.write('\r\x1b[K'); // Clear line
75
- process.stdout.write(line);
76
- };
77
-
78
- // Animate spinner every 80ms
79
- const spinnerInterval = setInterval(() => {
80
- frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
81
- render();
82
- }, 80);
83
-
84
- render();
85
-
86
- const onData = (key) => {
87
- const char = key.toString();
88
-
89
- // Enter key
90
- if (char === '\r' || char === '\n') {
91
- clearInterval(spinnerInterval);
92
- process.stdin.removeListener('data', onData);
93
- if (process.stdin.isTTY) {
94
- process.stdin.setRawMode(false);
95
- }
96
- process.stdout.write('\n');
97
- resolve(userInput);
98
- return;
99
- }
100
-
101
- // Ctrl+C
102
- if (char === '\x03') {
103
- clearInterval(spinnerInterval);
104
- process.stdin.removeListener('data', onData);
105
- if (process.stdin.isTTY) {
106
- process.stdin.setRawMode(false);
107
- }
108
- process.stdout.write('\n');
109
- process.exit(0);
110
- }
111
-
112
- // Backspace
113
- if (char === '\x7f' || char === '\b') {
114
- if (userInput.length > 0) {
115
- userInput = userInput.slice(0, -1);
116
- render();
117
- }
118
- return;
119
- }
120
-
121
- // Regular printable character
122
- if (char >= ' ' && char <= '~') {
123
- userInput += char;
124
- render();
125
- }
126
- };
127
-
128
- process.stdin.on('data', onData);
129
- });
130
- };
131
-
132
- /**
133
- * Animated Y/N confirm prompt
134
- * Shows [Y/n] or [y/N] based on default
135
- * @param {string} message - Prompt message
136
- * @param {boolean} defaultVal - Default value
137
- * @returns {Promise<boolean>}
138
- */
139
- const animatedConfirm = (message, defaultVal = true) => {
140
- return new Promise((resolve) => {
141
- prepareStdin();
142
- closeReadline();
143
-
144
- let frameIndex = 0;
145
- const hint = defaultVal ? '[Y/n]' : '[y/N]';
146
-
147
- if (process.stdin.isTTY) {
148
- process.stdin.setRawMode(true);
149
- }
150
- process.stdin.resume();
151
-
152
- const render = () => {
153
- const spinner = chalk.yellow(SPINNER_FRAMES[frameIndex]);
154
- process.stdout.write('\r\x1b[K');
155
- process.stdout.write(`${spinner} ${message} ${chalk.dim(hint)} `);
156
- };
157
-
158
- const spinnerInterval = setInterval(() => {
159
- frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
160
- render();
161
- }, 80);
162
-
163
- render();
164
-
165
- const onData = (key) => {
166
- const char = key.toString().toLowerCase();
167
-
168
- // Enter = use default
169
- if (char === '\r' || char === '\n') {
170
- cleanup();
171
- process.stdout.write(defaultVal ? 'Yes' : 'No');
172
- process.stdout.write('\n');
173
- resolve(defaultVal);
174
- return;
175
- }
176
-
177
- // Y = yes
178
- if (char === 'y') {
179
- cleanup();
180
- process.stdout.write('Yes');
181
- process.stdout.write('\n');
182
- resolve(true);
183
- return;
184
- }
185
-
186
- // N = no
187
- if (char === 'n') {
188
- cleanup();
189
- process.stdout.write('No');
190
- process.stdout.write('\n');
191
- resolve(false);
192
- return;
193
- }
194
-
195
- // Ctrl+C
196
- if (char === '\x03') {
197
- cleanup();
198
- process.stdout.write('\n');
199
- process.exit(0);
200
- }
201
- };
202
-
203
- const cleanup = () => {
204
- clearInterval(spinnerInterval);
205
- process.stdin.removeListener('data', onData);
206
- if (process.stdin.isTTY) {
207
- process.stdin.setRawMode(false);
208
- }
209
- };
210
-
211
- process.stdin.on('data', onData);
212
- });
213
- };
214
-
215
- /**
216
- * Animated list selection with arrow keys
217
- * @param {string} message - Prompt message
218
- * @param {Array<{name: string, value: any}>} choices - Options
219
- * @returns {Promise<any>}
220
- */
221
- const animatedSelect = (message, choices) => {
222
- return new Promise((resolve) => {
223
- prepareStdin();
224
- closeReadline();
225
-
226
- let frameIndex = 0;
227
- let selectedIndex = 0;
228
- const validChoices = choices.filter(c => !c.disabled);
229
-
230
- if (process.stdin.isTTY) {
231
- process.stdin.setRawMode(true);
232
- }
233
- process.stdin.resume();
234
-
235
- const render = () => {
236
- const spinner = chalk.yellow(SPINNER_FRAMES[frameIndex]);
237
- // Move cursor to start and clear down
238
- process.stdout.write('\r\x1b[K');
239
- process.stdout.write(`${spinner} ${message}\n`);
240
-
241
- validChoices.forEach((choice, i) => {
242
- process.stdout.write('\x1b[K'); // Clear line
243
- if (i === selectedIndex) {
244
- process.stdout.write(`${chalk.cyan('❯')} ${chalk.cyan(choice.name)}\n`);
245
- } else {
246
- process.stdout.write(` ${choice.name}\n`);
247
- }
248
- });
249
-
250
- // Move cursor back up
251
- process.stdout.write(`\x1b[${validChoices.length + 1}A`);
252
- };
253
-
254
- const spinnerInterval = setInterval(() => {
255
- frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
256
- render();
257
- }, 80);
258
-
259
- render();
260
-
261
- let escapeSeq = '';
262
-
263
- const onData = (key) => {
264
- const char = key.toString();
265
-
266
- // Handle escape sequences (arrow keys)
267
- if (char === '\x1b') {
268
- escapeSeq = char;
269
- return;
270
- }
271
-
272
- if (escapeSeq === '\x1b' && char === '[') {
273
- escapeSeq += char;
274
- return;
275
- }
276
-
277
- if (escapeSeq === '\x1b[') {
278
- escapeSeq = '';
279
- // Up arrow
280
- if (char === 'A') {
281
- selectedIndex = Math.max(0, selectedIndex - 1);
282
- render();
283
- return;
284
- }
285
- // Down arrow
286
- if (char === 'B') {
287
- selectedIndex = Math.min(validChoices.length - 1, selectedIndex + 1);
288
- render();
289
- return;
290
- }
291
- }
292
-
293
- // Enter = select
294
- if (char === '\r' || char === '\n') {
295
- cleanup();
296
- // Clear the menu lines
297
- process.stdout.write('\r\x1b[K');
298
- for (let i = 0; i < validChoices.length; i++) {
299
- process.stdout.write('\x1b[B\x1b[K');
300
- }
301
- process.stdout.write(`\x1b[${validChoices.length}A`);
302
- process.stdout.write(`${chalk.yellow('⠋')} ${message} ${chalk.cyan(validChoices[selectedIndex].name)}\n`);
303
- resolve(validChoices[selectedIndex].value);
304
- return;
305
- }
306
-
307
- // Ctrl+C
308
- if (char === '\x03') {
309
- cleanup();
310
- process.stdout.write('\n');
311
- process.exit(0);
312
- }
313
- };
314
-
315
- const cleanup = () => {
316
- clearInterval(spinnerInterval);
317
- process.stdin.removeListener('data', onData);
318
- if (process.stdin.isTTY) {
319
- process.stdin.setRawMode(false);
320
- }
321
- };
322
-
323
- process.stdin.on('data', onData);
324
- });
325
- };
326
-
327
- /**
328
- * Wait for Enter key with animated spinner
329
- * @param {string} [message='Press Enter to continue...'] - Message to display
47
+ * Wait for Enter key
48
+ * @param {string} [message='Press Enter to continue...'] - Message
330
49
  * @returns {Promise<void>}
331
50
  */
332
51
  const waitForEnter = async (message = 'Press Enter to continue...') => {
333
- await animatedPrompt(message);
52
+ closeReadline();
53
+ prepareStdin();
54
+
55
+ await inquirer.prompt([{
56
+ type: 'input',
57
+ name: 'continue',
58
+ message,
59
+ prefix: '',
60
+ }]);
334
61
  };
335
62
 
336
63
  /**
337
- * Text input with animated spinner
64
+ * Text input
338
65
  * @param {string} message - Prompt message
339
66
  * @param {string} [defaultVal=''] - Default value
340
67
  * @returns {Promise<string>}
341
68
  */
342
69
  const textInput = async (message, defaultVal = '') => {
343
- const displayMsg = defaultVal ? `${message} (${defaultVal})` : message;
344
- const value = await animatedPrompt(displayMsg);
345
- return value || defaultVal;
70
+ closeReadline();
71
+ prepareStdin();
72
+
73
+ const { value } = await inquirer.prompt([{
74
+ type: 'input',
75
+ name: 'value',
76
+ message,
77
+ default: defaultVal,
78
+ prefix: '',
79
+ }]);
80
+
81
+ return value;
346
82
  };
347
83
 
348
84
  /**
349
- * Password input (masked) - uses inquirer for masking
85
+ * Password input (masked)
350
86
  * @param {string} message - Prompt message
351
87
  * @returns {Promise<string>}
352
88
  */
@@ -357,7 +93,7 @@ const passwordInput = async (message) => {
357
93
  const { value } = await inquirer.prompt([{
358
94
  type: 'password',
359
95
  name: 'value',
360
- message: `${chalk.yellow('⠋')} ${message}`,
96
+ message,
361
97
  mask: '*',
362
98
  prefix: '',
363
99
  }]);
@@ -366,17 +102,33 @@ const passwordInput = async (message) => {
366
102
  };
367
103
 
368
104
  /**
369
- * Confirm prompt with animated spinner (Y/n)
105
+ * Confirm prompt with Yes/No selection
370
106
  * @param {string} message - Prompt message
371
107
  * @param {boolean} [defaultVal=true] - Default value
372
108
  * @returns {Promise<boolean>}
373
109
  */
374
110
  const confirmPrompt = async (message, defaultVal = true) => {
375
- return animatedConfirm(message, defaultVal);
111
+ closeReadline();
112
+ prepareStdin();
113
+
114
+ const choices = defaultVal
115
+ ? [{ name: 'Yes', value: true }, { name: 'No', value: false }]
116
+ : [{ name: 'No', value: false }, { name: 'Yes', value: true }];
117
+
118
+ const { value } = await inquirer.prompt([{
119
+ type: 'list',
120
+ name: 'value',
121
+ message,
122
+ choices,
123
+ prefix: '',
124
+ loop: false,
125
+ }]);
126
+
127
+ return value;
376
128
  };
377
129
 
378
130
  /**
379
- * Number input with animated spinner and validation
131
+ * Number input with validation
380
132
  * @param {string} message - Prompt message
381
133
  * @param {number} [defaultVal=1] - Default value
382
134
  * @param {number} [min=1] - Minimum value
@@ -384,40 +136,55 @@ const confirmPrompt = async (message, defaultVal = true) => {
384
136
  * @returns {Promise<number>}
385
137
  */
386
138
  const numberInput = async (message, defaultVal = 1, min = 1, max = 1000) => {
387
- const displayMsg = `${message} (${min}-${max}, default: ${defaultVal})`;
388
-
389
- while (true) {
390
- const value = await animatedPrompt(displayMsg);
391
-
392
- if (!value) return defaultVal;
393
-
394
- const num = parseInt(value, 10);
395
- if (isNaN(num)) {
396
- console.log(chalk.red('Please enter a valid number'));
397
- continue;
398
- }
399
- if (num < min || num > max) {
400
- console.log(chalk.red(`Please enter a number between ${min} and ${max}`));
401
- continue;
402
- }
403
- return num;
404
- }
139
+ closeReadline();
140
+ prepareStdin();
141
+
142
+ const { value } = await inquirer.prompt([{
143
+ type: 'input',
144
+ name: 'value',
145
+ message: `${message} (${min}-${max})`,
146
+ default: String(defaultVal),
147
+ prefix: '',
148
+ validate: (v) => {
149
+ const n = parseInt(v, 10);
150
+ if (isNaN(n)) return 'Enter a number';
151
+ if (n < min) return `Min: ${min}`;
152
+ if (n > max) return `Max: ${max}`;
153
+ return true;
154
+ },
155
+ }]);
156
+
157
+ return parseInt(value, 10) || defaultVal;
405
158
  };
406
159
 
407
160
  /**
408
- * Select from options with animated spinner and arrow keys
161
+ * Select from options with arrow keys
409
162
  * @param {string} message - Prompt message
410
163
  * @param {Array<{label: string, value: any, disabled?: boolean}>} options - Options
411
164
  * @returns {Promise<any>}
412
165
  */
413
166
  const selectOption = async (message, options) => {
414
- const choices = options.map(opt => ({
415
- name: opt.label,
416
- value: opt.value,
417
- disabled: opt.disabled || false,
418
- }));
167
+ closeReadline();
168
+ prepareStdin();
169
+
170
+ const choices = options.map(opt => {
171
+ if (opt.disabled) {
172
+ return new inquirer.Separator(opt.label);
173
+ }
174
+ return { name: opt.label, value: opt.value };
175
+ });
419
176
 
420
- return animatedSelect(message, choices);
177
+ const { value } = await inquirer.prompt([{
178
+ type: 'list',
179
+ name: 'value',
180
+ message,
181
+ choices,
182
+ prefix: '',
183
+ loop: false,
184
+ pageSize: 15,
185
+ }]);
186
+
187
+ return value;
421
188
  };
422
189
 
423
190
  module.exports = {