hedgequantx 2.9.49 → 2.9.51

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.49",
3
+ "version": "2.9.51",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
package/src/lib/data.js CHANGED
@@ -113,6 +113,7 @@ class MarketDataFeed extends EventEmitter {
113
113
  });
114
114
 
115
115
  this.subscriptions.add(key);
116
+ this.emit('subscribed', `${symbol}:${exchange}`);
116
117
  } catch (e) {
117
118
  this.emit('error', e);
118
119
  throw e;
@@ -172,9 +173,17 @@ class MarketDataFeed extends EventEmitter {
172
173
  _handleMessage(msg) {
173
174
  const { templateId, data } = msg;
174
175
 
176
+ // Debug: log all message template IDs
177
+ if (!this._seenTemplates) this._seenTemplates = new Set();
178
+ if (!this._seenTemplates.has(templateId)) {
179
+ this._seenTemplates.add(templateId);
180
+ this.emit('debug', `New message type: templateId=${templateId}`);
181
+ }
182
+
175
183
  switch (templateId) {
176
184
  case RES.MARKET_DATA:
177
185
  // Subscription confirmed
186
+ this.emit('debug', 'Market data subscription confirmed');
178
187
  break;
179
188
 
180
189
  case STREAM.LAST_TRADE:
@@ -186,7 +195,10 @@ class MarketDataFeed extends EventEmitter {
186
195
  break;
187
196
 
188
197
  default:
189
- // Ignore other messages
198
+ // Log unknown messages for debugging
199
+ if (templateId && !this._seenTemplates.has(`logged_${templateId}`)) {
200
+ this._seenTemplates.add(`logged_${templateId}`);
201
+ }
190
202
  break;
191
203
  }
192
204
  }
@@ -93,7 +93,20 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
93
93
 
94
94
  // Handle strategy signals
95
95
  strategy.on('signal', async (signal) => {
96
- if (!running || pendingOrder || currentPosition !== 0) return;
96
+ ui.addLog('info', `SIGNAL DETECTED: ${signal.direction?.toUpperCase()}`);
97
+
98
+ if (!running) {
99
+ ui.addLog('info', 'Signal ignored: not running');
100
+ return;
101
+ }
102
+ if (pendingOrder) {
103
+ ui.addLog('info', 'Signal ignored: order pending');
104
+ return;
105
+ }
106
+ if (currentPosition !== 0) {
107
+ ui.addLog('info', `Signal ignored: position open (${currentPosition})`);
108
+ return;
109
+ }
97
110
 
98
111
  let { direction, entry, stopLoss, takeProfit, confidence } = signal;
99
112
  let orderSize = contracts;
@@ -185,25 +198,85 @@ const executeAlgo = async ({ service, account, contract, config, strategy: strat
185
198
  });
186
199
 
187
200
  // Handle market data ticks
201
+ let lastPrice = null;
202
+ let lastBid = null;
203
+ let lastAsk = null;
204
+ let ticksPerSecond = 0;
205
+ let lastTickSecond = Math.floor(Date.now() / 1000);
206
+
188
207
  marketFeed.on('tick', (tick) => {
189
208
  tickCount++;
190
209
  const latencyStart = Date.now();
210
+ const currentSecond = Math.floor(Date.now() / 1000);
211
+
212
+ // Count ticks per second
213
+ if (currentSecond === lastTickSecond) {
214
+ ticksPerSecond++;
215
+ } else {
216
+ ticksPerSecond = 1;
217
+ lastTickSecond = currentSecond;
218
+ }
191
219
 
192
220
  aiContext.recentTicks.push(tick);
193
221
  if (aiContext.recentTicks.length > aiContext.maxTicks) aiContext.recentTicks.shift();
194
222
 
223
+ // Smart logs for tick flow
224
+ const price = tick.price || tick.tradePrice;
225
+ const bid = tick.bid || tick.bidPrice;
226
+ const ask = tick.ask || tick.askPrice;
227
+
228
+ // Log first tick
229
+ if (tickCount === 1) {
230
+ ui.addLog('info', `First tick received @ ${price?.toFixed(2) || 'N/A'}`);
231
+ ui.addLog('info', `Tick type: ${tick.type || 'unknown'}`);
232
+ }
233
+
234
+ // Log price changes
235
+ if (price && lastPrice && price !== lastPrice) {
236
+ const direction = price > lastPrice ? 'UP' : 'DOWN';
237
+ const change = Math.abs(price - lastPrice).toFixed(2);
238
+ if (tickCount <= 10 || tickCount % 50 === 0) {
239
+ ui.addLog('info', `Price ${direction} ${change} -> ${price.toFixed(2)}`);
240
+ }
241
+ }
242
+
243
+ // Log bid/ask spread
244
+ if (bid && ask && (bid !== lastBid || ask !== lastAsk)) {
245
+ const spread = (ask - bid).toFixed(2);
246
+ if (tickCount <= 5) {
247
+ ui.addLog('info', `Spread: ${spread} (Bid: ${bid.toFixed(2)} / Ask: ${ask.toFixed(2)})`);
248
+ }
249
+ }
250
+
251
+ lastPrice = price;
252
+ lastBid = bid;
253
+ lastAsk = ask;
254
+
195
255
  strategy.processTick({
196
256
  contractId: tick.contractId || contractId,
197
- price: tick.price, bid: tick.bid, ask: tick.ask,
198
- volume: tick.volume || 1, side: tick.lastTradeSide || 'unknown',
257
+ price: price, bid: bid, ask: ask,
258
+ volume: tick.volume || tick.size || 1,
259
+ side: tick.side || tick.lastTradeSide || 'unknown',
199
260
  timestamp: tick.timestamp || Date.now()
200
261
  });
201
262
 
202
263
  stats.latency = Date.now() - latencyStart;
203
- if (tickCount % 100 === 0) ui.addLog('info', `Tick #${tickCount} @ ${tick.price?.toFixed(2) || 'N/A'}`);
264
+
265
+ // Periodic status logs
266
+ if (tickCount === 10) ui.addLog('info', `Receiving ticks... (${ticksPerSecond}/sec)`);
267
+ if (tickCount === 50) ui.addLog('info', `50 ticks processed, strategy analyzing...`);
268
+ if (tickCount % 200 === 0) {
269
+ ui.addLog('info', `Tick #${tickCount} @ ${price?.toFixed(2) || 'N/A'} | ${ticksPerSecond}/sec`);
270
+ }
204
271
  });
205
272
 
206
- marketFeed.on('connected', () => { stats.connected = true; ui.addLog('connected', 'Market data connected!'); });
273
+ marketFeed.on('connected', () => {
274
+ stats.connected = true;
275
+ ui.addLog('connected', 'Market data connected!');
276
+ ui.addLog('info', 'Subscribing to market data...');
277
+ });
278
+ marketFeed.on('subscribed', (symbol) => ui.addLog('info', `Subscribed to ${symbol}`));
279
+ marketFeed.on('debug', (msg) => ui.addLog('info', msg));
207
280
  marketFeed.on('error', (err) => ui.addLog('error', `Market: ${err.message}`));
208
281
  marketFeed.on('disconnected', () => { stats.connected = false; ui.addLog('error', 'Market disconnected'); });
209
282
 
@@ -14,6 +14,7 @@ const { getActiveAgentCount, getSupervisionConfig, getActiveAgents } = require('
14
14
  const { launchCopyTrading } = require('./copy-executor');
15
15
  const { runPreflightCheck, formatPreflightResults, getPreflightSummary } = require('../../services/ai-supervision');
16
16
  const { getAvailableStrategies } = require('../../lib/m');
17
+ const { getLastCopyTradingConfig, saveCopyTradingConfig } = require('../../services/algo-config');
17
18
 
18
19
  /**
19
20
  * Copy Trading Menu
@@ -54,6 +55,98 @@ const copyTradingMenu = async () => {
54
55
 
55
56
  spinner.succeed(`Found ${activeAccounts.length} active accounts`);
56
57
 
58
+ // Check for saved config
59
+ const lastConfig = getLastCopyTradingConfig();
60
+
61
+ if (lastConfig) {
62
+ // Try to find matching accounts
63
+ const matchingLead = activeAccounts.find(acc =>
64
+ acc.accountId === lastConfig.leadAccountId ||
65
+ acc.accountName === lastConfig.leadAccountName
66
+ );
67
+
68
+ if (matchingLead && lastConfig.followerAccountIds?.length > 0) {
69
+ const matchingFollowers = lastConfig.followerAccountIds
70
+ .map(id => activeAccounts.find(acc => acc.accountId === id || acc.accountName === id))
71
+ .filter(Boolean);
72
+
73
+ if (matchingFollowers.length > 0) {
74
+ console.log();
75
+ console.log(chalk.cyan(' Last configuration found:'));
76
+ console.log(chalk.gray(` Lead: ${lastConfig.leadAccountName} (${lastConfig.leadPropfirm})`));
77
+ console.log(chalk.gray(` Followers: ${matchingFollowers.length} account(s)`));
78
+ console.log(chalk.gray(` Symbol: ${lastConfig.symbol}`));
79
+ console.log(chalk.gray(` Strategy: ${lastConfig.strategyName}`));
80
+ console.log(chalk.gray(` Lead contracts: ${lastConfig.leadContracts} | Follower: ${lastConfig.followerContracts}`));
81
+ console.log(chalk.gray(` Target: $${lastConfig.dailyTarget} | Risk: $${lastConfig.maxRisk}`));
82
+ console.log();
83
+
84
+ const reuseConfig = await prompts.confirmPrompt('Use last configuration?', true);
85
+
86
+ if (reuseConfig) {
87
+ // Load contracts to find symbol
88
+ const leadService = matchingLead.service || connections.getServiceForAccount(matchingLead.accountId);
89
+ const contractsResult = await leadService.getContracts();
90
+ const contract = contractsResult.success
91
+ ? contractsResult.contracts.find(c => c.symbol === lastConfig.symbol)
92
+ : null;
93
+
94
+ // Find strategy
95
+ const strategies = getAvailableStrategies();
96
+ const strategy = strategies.find(s => s.id === lastConfig.strategyId);
97
+
98
+ if (contract && strategy) {
99
+ console.log(chalk.green(' ✓ Configuration loaded'));
100
+
101
+ // Check for AI Supervision
102
+ const agentCount = getActiveAgentCount();
103
+ let supervisionConfig = null;
104
+
105
+ if (agentCount > 0) {
106
+ console.log();
107
+ console.log(chalk.cyan(` ${agentCount} AI Agent(s) available for supervision`));
108
+ const enableAI = await prompts.confirmPrompt('Enable AI Supervision?', true);
109
+
110
+ if (enableAI) {
111
+ const agents = getActiveAgents();
112
+ const preflightResults = await runPreflightCheck(agents);
113
+ const lines = formatPreflightResults(preflightResults, 60);
114
+ for (const line of lines) console.log(line);
115
+ const summary = getPreflightSummary(preflightResults);
116
+ console.log();
117
+ console.log(` ${summary.text}`);
118
+
119
+ if (!preflightResults.success) {
120
+ console.log(chalk.red(' Cannot start algo - fix agent connections first.'));
121
+ await prompts.waitForEnter();
122
+ return;
123
+ }
124
+ supervisionConfig = getSupervisionConfig();
125
+ }
126
+ }
127
+
128
+ const confirm = await prompts.confirmPrompt('Start Copy Trading?', true);
129
+ if (!confirm) return;
130
+
131
+ await launchCopyTrading({
132
+ lead: { account: matchingLead, contracts: lastConfig.leadContracts },
133
+ followers: matchingFollowers.map(f => ({ account: f, contracts: lastConfig.followerContracts })),
134
+ contract,
135
+ strategy,
136
+ dailyTarget: lastConfig.dailyTarget,
137
+ maxRisk: lastConfig.maxRisk,
138
+ showNames: lastConfig.showNames,
139
+ supervisionConfig
140
+ });
141
+ return;
142
+ } else {
143
+ console.log(chalk.yellow(' Symbol or strategy no longer available, please reconfigure'));
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+
57
150
  // Step 1: Select LEAD Account
58
151
  console.log();
59
152
  console.log(chalk.cyan.bold(' STEP 1: SELECT LEAD ACCOUNT'));
@@ -204,6 +297,22 @@ const copyTradingMenu = async () => {
204
297
  const confirm = await prompts.confirmPrompt('Start Copy Trading?', true);
205
298
  if (!confirm) return;
206
299
 
300
+ // Save config for next time
301
+ saveCopyTradingConfig({
302
+ leadAccountId: leadAccount.accountId || leadAccount.rithmicAccountId,
303
+ leadAccountName: leadAccount.accountName || leadAccount.rithmicAccountId || leadAccount.accountId,
304
+ leadPropfirm: leadAccount.propfirm || leadAccount.platform || 'Unknown',
305
+ followerAccountIds: followers.map(f => f.accountId || f.rithmicAccountId),
306
+ symbol: contract.symbol,
307
+ strategyId: strategy.id,
308
+ strategyName: strategy.name,
309
+ leadContracts,
310
+ followerContracts,
311
+ dailyTarget,
312
+ maxRisk,
313
+ showNames
314
+ });
315
+
207
316
  await launchCopyTrading({
208
317
  lead: { account: leadAccount, contracts: leadContracts },
209
318
  followers: followers.map(f => ({ account: f, contracts: followerContracts })),
@@ -13,6 +13,7 @@ const { executeAlgo } = require('./algo-executor');
13
13
  const { getActiveAgentCount, getSupervisionConfig, getActiveAgents } = require('../ai-agents');
14
14
  const { runPreflightCheck, formatPreflightResults, getPreflightSummary } = require('../../services/ai-supervision');
15
15
  const { getAvailableStrategies } = require('../../lib/m');
16
+ const { getLastOneAccountConfig, saveOneAccountConfig } = require('../../services/algo-config');
16
17
 
17
18
 
18
19
 
@@ -54,37 +55,112 @@ const oneAccountMenu = async (service) => {
54
55
 
55
56
  spinner.succeed(`Found ${activeAccounts.length} active account(s)`);
56
57
 
57
- // Select account - display RAW API fields
58
- const options = activeAccounts.map(acc => {
59
- // Use what API returns: rithmicAccountId or accountName for Rithmic
60
- const name = acc.accountName || acc.rithmicAccountId || acc.accountId;
61
- const balance = acc.balance !== null && acc.balance !== undefined
62
- ? ` - $${acc.balance.toLocaleString()}`
63
- : '';
64
- return {
65
- label: `${name} (${acc.propfirm || acc.platform || 'Unknown'})${balance}`,
66
- value: acc
67
- };
68
- });
69
- options.push({ label: '< Back', value: 'back' });
70
-
71
- const selectedAccount = await prompts.selectOption('Select Account:', options);
72
- if (!selectedAccount || selectedAccount === 'back') return;
73
-
74
- // Use the service attached to the account (from getAllAccounts), fallback to getServiceForAccount
75
- const accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
76
-
77
- // Select symbol
78
- const contract = await selectSymbol(accountService, selectedAccount);
79
- if (!contract) return;
80
-
81
- // Select strategy
82
- const strategy = await selectStrategy();
83
- if (!strategy) return;
58
+ // Check for saved config
59
+ const lastConfig = getLastOneAccountConfig();
60
+ let selectedAccount = null;
61
+ let contract = null;
62
+ let strategy = null;
63
+ let config = null;
64
+ let accountService = null;
65
+
66
+ if (lastConfig) {
67
+ // Try to find matching account and offer to reuse config
68
+ const matchingAccount = activeAccounts.find(acc =>
69
+ acc.accountId === lastConfig.accountId ||
70
+ acc.rithmicAccountId === lastConfig.accountId ||
71
+ acc.accountName === lastConfig.accountName
72
+ );
73
+
74
+ if (matchingAccount) {
75
+ console.log();
76
+ console.log(chalk.cyan(' Last configuration found:'));
77
+ console.log(chalk.gray(` Account: ${lastConfig.accountName} (${lastConfig.propfirm})`));
78
+ console.log(chalk.gray(` Symbol: ${lastConfig.symbol}`));
79
+ console.log(chalk.gray(` Strategy: ${lastConfig.strategyName}`));
80
+ console.log(chalk.gray(` Contracts: ${lastConfig.contracts} | Target: $${lastConfig.dailyTarget} | Risk: $${lastConfig.maxRisk}`));
81
+ console.log();
82
+
83
+ const reuseConfig = await prompts.confirmPrompt('Use last configuration?', true);
84
+
85
+ if (reuseConfig) {
86
+ selectedAccount = matchingAccount;
87
+ accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
88
+
89
+ // Load contracts to find the saved symbol
90
+ const contractsResult = await accountService.getContracts();
91
+ if (contractsResult.success) {
92
+ contract = contractsResult.contracts.find(c => c.symbol === lastConfig.symbol);
93
+ }
94
+
95
+ // Find strategy
96
+ const strategies = getAvailableStrategies();
97
+ strategy = strategies.find(s => s.id === lastConfig.strategyId);
98
+
99
+ // Restore config
100
+ if (contract && strategy) {
101
+ config = {
102
+ contracts: lastConfig.contracts,
103
+ dailyTarget: lastConfig.dailyTarget,
104
+ maxRisk: lastConfig.maxRisk,
105
+ showName: lastConfig.showName
106
+ };
107
+ console.log(chalk.green(' ✓ Configuration loaded'));
108
+ } else {
109
+ console.log(chalk.yellow(' Symbol or strategy no longer available, please reconfigure'));
110
+ selectedAccount = null;
111
+ }
112
+ }
113
+ }
114
+ }
84
115
 
85
- // Configure algo
86
- const config = await configureAlgo(selectedAccount, contract, strategy);
87
- if (!config) return;
116
+ // If no saved config used, go through normal selection
117
+ if (!selectedAccount) {
118
+ // Select account - display RAW API fields
119
+ const options = activeAccounts.map(acc => {
120
+ // Use what API returns: rithmicAccountId or accountName for Rithmic
121
+ const name = acc.accountName || acc.rithmicAccountId || acc.accountId;
122
+ const balance = acc.balance !== null && acc.balance !== undefined
123
+ ? ` - $${acc.balance.toLocaleString()}`
124
+ : '';
125
+ return {
126
+ label: `${name} (${acc.propfirm || acc.platform || 'Unknown'})${balance}`,
127
+ value: acc
128
+ };
129
+ });
130
+ options.push({ label: '< Back', value: 'back' });
131
+
132
+ selectedAccount = await prompts.selectOption('Select Account:', options);
133
+ if (!selectedAccount || selectedAccount === 'back') return;
134
+
135
+ // Use the service attached to the account (from getAllAccounts), fallback to getServiceForAccount
136
+ accountService = selectedAccount.service || connections.getServiceForAccount(selectedAccount.accountId) || service;
137
+
138
+ // Select symbol
139
+ contract = await selectSymbol(accountService, selectedAccount);
140
+ if (!contract) return;
141
+
142
+ // Select strategy
143
+ strategy = await selectStrategy();
144
+ if (!strategy) return;
145
+
146
+ // Configure algo
147
+ config = await configureAlgo(selectedAccount, contract, strategy);
148
+ if (!config) return;
149
+
150
+ // Save config for next time
151
+ saveOneAccountConfig({
152
+ accountId: selectedAccount.accountId || selectedAccount.rithmicAccountId,
153
+ accountName: selectedAccount.accountName || selectedAccount.rithmicAccountId || selectedAccount.accountId,
154
+ propfirm: selectedAccount.propfirm || selectedAccount.platform || 'Unknown',
155
+ symbol: contract.symbol,
156
+ strategyId: strategy.id,
157
+ strategyName: strategy.name,
158
+ contracts: config.contracts,
159
+ dailyTarget: config.dailyTarget,
160
+ maxRisk: config.maxRisk,
161
+ showName: config.showName
162
+ });
163
+ }
88
164
 
89
165
  // Check for AI Supervision BEFORE asking to start
90
166
  const agentCount = getActiveAgentCount();
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Algo Config Storage - Persist algo configuration between sessions
3
+ * Saves to ~/.hqx/algo-config.json
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+
10
+ const CONFIG_DIR = path.join(os.homedir(), '.hqx');
11
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'algo-config.json');
12
+
13
+ /**
14
+ * Ensure config directory exists
15
+ */
16
+ const ensureConfigDir = () => {
17
+ if (!fs.existsSync(CONFIG_DIR)) {
18
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
19
+ }
20
+ };
21
+
22
+ /**
23
+ * Load saved config
24
+ * @returns {Object|null} Saved config or null
25
+ */
26
+ const loadConfig = () => {
27
+ try {
28
+ if (fs.existsSync(CONFIG_FILE)) {
29
+ const data = fs.readFileSync(CONFIG_FILE, 'utf8');
30
+ return JSON.parse(data);
31
+ }
32
+ } catch (e) {
33
+ // Ignore errors, return null
34
+ }
35
+ return null;
36
+ };
37
+
38
+ /**
39
+ * Save config
40
+ * @param {Object} config - Config to save
41
+ */
42
+ const saveConfig = (config) => {
43
+ try {
44
+ ensureConfigDir();
45
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
46
+ } catch (e) {
47
+ // Ignore save errors
48
+ }
49
+ };
50
+
51
+ /**
52
+ * Get last config for one-account mode
53
+ * @returns {Object|null}
54
+ */
55
+ const getLastOneAccountConfig = () => {
56
+ const config = loadConfig();
57
+ return config?.oneAccount || null;
58
+ };
59
+
60
+ /**
61
+ * Save one-account config
62
+ * @param {Object} data - { accountId, accountName, propfirm, symbol, strategyId, contracts, dailyTarget, maxRisk, showName }
63
+ */
64
+ const saveOneAccountConfig = (data) => {
65
+ const config = loadConfig() || {};
66
+ config.oneAccount = {
67
+ ...data,
68
+ savedAt: Date.now()
69
+ };
70
+ saveConfig(config);
71
+ };
72
+
73
+ /**
74
+ * Get last config for copy-trading mode
75
+ * @returns {Object|null}
76
+ */
77
+ const getLastCopyTradingConfig = () => {
78
+ const config = loadConfig();
79
+ return config?.copyTrading || null;
80
+ };
81
+
82
+ /**
83
+ * Save copy-trading config
84
+ * @param {Object} data - { masterAccountId, followerAccountIds, symbol, strategyId, contracts, dailyTarget, maxRisk }
85
+ */
86
+ const saveCopyTradingConfig = (data) => {
87
+ const config = loadConfig() || {};
88
+ config.copyTrading = {
89
+ ...data,
90
+ savedAt: Date.now()
91
+ };
92
+ saveConfig(config);
93
+ };
94
+
95
+ module.exports = {
96
+ loadConfig,
97
+ saveConfig,
98
+ getLastOneAccountConfig,
99
+ saveOneAccountConfig,
100
+ getLastCopyTradingConfig,
101
+ saveCopyTradingConfig,
102
+ };