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 +1 -1
- package/src/lib/data.js +13 -1
- package/src/pages/algo/algo-executor.js +78 -5
- package/src/pages/algo/copy-trading.js +109 -0
- package/src/pages/algo/one-account.js +106 -30
- package/src/services/algo-config.js +102 -0
package/package.json
CHANGED
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
|
-
//
|
|
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
|
-
|
|
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:
|
|
198
|
-
volume: tick.volume ||
|
|
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
|
-
|
|
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', () => {
|
|
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
|
-
//
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
};
|