hedgequantx 2.5.36 → 2.5.37
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
|
@@ -16,6 +16,10 @@ const { M1 } = require('../../../dist/lib/m/s1');
|
|
|
16
16
|
const { MarketDataFeed } = require('../../../dist/lib/data');
|
|
17
17
|
const { algoLogger } = require('./logger');
|
|
18
18
|
|
|
19
|
+
// AI Strategy Supervisor - observes, learns, and optimizes the strategy
|
|
20
|
+
const aiService = require('../../services/ai');
|
|
21
|
+
const StrategySupervisor = require('../../services/ai/strategy-supervisor');
|
|
22
|
+
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
/**
|
|
@@ -167,6 +171,7 @@ const configureAlgo = async (account, contract) => {
|
|
|
167
171
|
/**
|
|
168
172
|
* Launch algo trading - HQX Ultra Scalping Strategy
|
|
169
173
|
* Real-time market data + Strategy signals + Auto order execution
|
|
174
|
+
* AI Supervision: All connected agents monitor and supervise trading
|
|
170
175
|
*/
|
|
171
176
|
const launchAlgo = async (service, account, contract, config) => {
|
|
172
177
|
const { contracts, dailyTarget, maxRisk, showName } = config;
|
|
@@ -197,7 +202,9 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
197
202
|
losses: 0,
|
|
198
203
|
latency: 0,
|
|
199
204
|
connected: false,
|
|
200
|
-
startTime: Date.now()
|
|
205
|
+
startTime: Date.now(),
|
|
206
|
+
aiSupervision: false,
|
|
207
|
+
aiMode: null
|
|
201
208
|
};
|
|
202
209
|
|
|
203
210
|
let running = true;
|
|
@@ -213,6 +220,14 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
213
220
|
const strategy = M1;
|
|
214
221
|
strategy.initialize(contractId, tickSize, tickValue);
|
|
215
222
|
|
|
223
|
+
// Initialize AI Strategy Supervisor - agents observe, learn & optimize
|
|
224
|
+
const aiAgents = aiService.getAgents();
|
|
225
|
+
if (aiAgents.length > 0) {
|
|
226
|
+
const supervisorResult = StrategySupervisor.initialize(strategy, aiAgents, service, account.accountId);
|
|
227
|
+
stats.aiSupervision = supervisorResult.success;
|
|
228
|
+
stats.aiMode = supervisorResult.mode;
|
|
229
|
+
}
|
|
230
|
+
|
|
216
231
|
// Initialize Market Data Feed
|
|
217
232
|
const marketFeed = new MarketDataFeed({ propfirm: account.propfirm });
|
|
218
233
|
|
|
@@ -225,17 +240,44 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
225
240
|
algoLogger.engineStarting(ui, connectionType, dailyTarget, maxRisk);
|
|
226
241
|
algoLogger.marketOpen(ui, sessionName.toUpperCase(), etTime);
|
|
227
242
|
|
|
243
|
+
// Log AI supervision status
|
|
244
|
+
if (stats.aiSupervision) {
|
|
245
|
+
algoLogger.info(ui, 'AI SUPERVISION', `${aiAgents.length} agent(s) - ${stats.aiMode} mode - LEARNING ACTIVE`);
|
|
246
|
+
}
|
|
247
|
+
|
|
228
248
|
// Handle strategy signals
|
|
229
249
|
strategy.on('signal', async (signal) => {
|
|
230
250
|
if (!running || pendingOrder || currentPosition !== 0) return;
|
|
231
251
|
|
|
232
252
|
const { side, direction, entry, stopLoss, takeProfit, confidence } = signal;
|
|
233
253
|
|
|
254
|
+
// Feed signal to AI supervisor (agents observe the signal)
|
|
255
|
+
if (stats.aiSupervision) {
|
|
256
|
+
StrategySupervisor.feedSignal({ direction, entry, stopLoss, takeProfit, confidence });
|
|
257
|
+
|
|
258
|
+
// Check AI advice - agents may recommend caution based on learned patterns
|
|
259
|
+
const advice = StrategySupervisor.shouldTrade();
|
|
260
|
+
if (!advice.proceed) {
|
|
261
|
+
algoLogger.info(ui, 'AI HOLD', advice.reason);
|
|
262
|
+
return; // Skip - agents learned this pattern leads to losses
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
234
266
|
// Calculate position size with kelly
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const riskPct = Math.round((riskAmount / maxRisk) * 100);
|
|
267
|
+
let kelly = Math.min(0.25, confidence);
|
|
268
|
+
let riskAmount = Math.round(maxRisk * kelly);
|
|
238
269
|
|
|
270
|
+
// AI may adjust size based on learning
|
|
271
|
+
if (stats.aiSupervision) {
|
|
272
|
+
const advice = StrategySupervisor.getCurrentAdvice();
|
|
273
|
+
if (advice.sizeMultiplier && advice.sizeMultiplier !== 1.0) {
|
|
274
|
+
kelly = kelly * advice.sizeMultiplier;
|
|
275
|
+
riskAmount = Math.round(riskAmount * advice.sizeMultiplier);
|
|
276
|
+
algoLogger.info(ui, 'AI ADJUST', `Size x${advice.sizeMultiplier.toFixed(2)} - ${advice.reason}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const riskPct = Math.round((riskAmount / maxRisk) * 100);
|
|
239
281
|
algoLogger.positionSized(ui, contracts, kelly, riskAmount, riskPct);
|
|
240
282
|
|
|
241
283
|
// Place order via API
|
|
@@ -319,6 +361,11 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
319
361
|
timestamp: tick.timestamp || Date.now()
|
|
320
362
|
};
|
|
321
363
|
|
|
364
|
+
// Feed tick to AI supervisor (agents observe same data as strategy)
|
|
365
|
+
if (stats.aiSupervision) {
|
|
366
|
+
StrategySupervisor.feedTick(tickData);
|
|
367
|
+
}
|
|
368
|
+
|
|
322
369
|
strategy.processTick(tickData);
|
|
323
370
|
|
|
324
371
|
stats.latency = Date.now() - latencyStart;
|
|
@@ -443,6 +490,25 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
443
490
|
|
|
444
491
|
// Record in strategy for adaptation
|
|
445
492
|
strategy.recordTradeResult(pnl);
|
|
493
|
+
|
|
494
|
+
// Feed trade result to AI supervisor - THIS IS WHERE AGENTS LEARN
|
|
495
|
+
if (stats.aiSupervision) {
|
|
496
|
+
StrategySupervisor.feedTradeResult({
|
|
497
|
+
side,
|
|
498
|
+
qty: contracts,
|
|
499
|
+
price: exitPrice,
|
|
500
|
+
pnl,
|
|
501
|
+
symbol: symbolName,
|
|
502
|
+
direction: side
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Log if AI learned something
|
|
506
|
+
const status = StrategySupervisor.getStatus();
|
|
507
|
+
if (status.patternsLearned.winning + status.patternsLearned.losing > 0) {
|
|
508
|
+
algoLogger.info(ui, 'AI LEARNING',
|
|
509
|
+
`${status.patternsLearned.winning}W/${status.patternsLearned.losing}L patterns`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
446
512
|
}
|
|
447
513
|
}
|
|
448
514
|
} catch (e) {
|
|
@@ -510,6 +576,15 @@ const launchAlgo = async (service, account, contract, config) => {
|
|
|
510
576
|
clearInterval(refreshInterval);
|
|
511
577
|
clearInterval(pnlInterval);
|
|
512
578
|
|
|
579
|
+
// Stop AI Supervisor and get learning summary
|
|
580
|
+
if (stats.aiSupervision) {
|
|
581
|
+
const aiSummary = StrategySupervisor.stop();
|
|
582
|
+
stats.aiLearning = {
|
|
583
|
+
optimizations: aiSummary.optimizationsApplied || 0,
|
|
584
|
+
patternsLearned: (aiSummary.winningPatterns || 0) + (aiSummary.losingPatterns || 0)
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
513
588
|
// Disconnect market feed with timeout
|
|
514
589
|
try {
|
|
515
590
|
await Promise.race([
|
|
@@ -308,6 +308,145 @@ Analyze and provide recommendation.`;
|
|
|
308
308
|
}
|
|
309
309
|
};
|
|
310
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Analyze strategy performance and suggest optimizations
|
|
313
|
+
* Called periodically to help the strategy improve
|
|
314
|
+
*
|
|
315
|
+
* @param {Object} agent - AI agent
|
|
316
|
+
* @param {Object} performanceData - Strategy performance data
|
|
317
|
+
* @returns {Promise<Object|null>} Optimization suggestions
|
|
318
|
+
*/
|
|
319
|
+
const analyzePerformance = async (agent, performanceData) => {
|
|
320
|
+
if (!agent || !performanceData) return null;
|
|
321
|
+
|
|
322
|
+
const systemPrompt = `You are an AI supervisor for HQX Ultra Scalping, a professional prop firm futures trading strategy.
|
|
323
|
+
|
|
324
|
+
The strategy uses advanced mathematical models:
|
|
325
|
+
- Order flow analysis (delta, cumulative delta, absorption)
|
|
326
|
+
- Market microstructure (bid/ask imbalance, volume profile)
|
|
327
|
+
- Statistical edge detection (z-score, standard deviation bands)
|
|
328
|
+
- Dynamic risk management (Kelly criterion, volatility-adjusted sizing)
|
|
329
|
+
|
|
330
|
+
Your job is to analyze performance data and suggest parameter optimizations.
|
|
331
|
+
Be precise and actionable. Focus on improving win rate, reducing drawdown, and optimizing risk/reward.
|
|
332
|
+
|
|
333
|
+
Respond ONLY in valid JSON format:
|
|
334
|
+
{
|
|
335
|
+
"assessment": "brief performance assessment",
|
|
336
|
+
"winRateAnalysis": "analysis of win/loss patterns",
|
|
337
|
+
"riskAnalysis": "analysis of risk management",
|
|
338
|
+
"optimizations": [
|
|
339
|
+
{ "param": "parameter_name", "current": "current_value", "suggested": "new_value", "reason": "why" }
|
|
340
|
+
],
|
|
341
|
+
"marketCondition": "trending|ranging|volatile|calm",
|
|
342
|
+
"confidence": 0-100
|
|
343
|
+
}`;
|
|
344
|
+
|
|
345
|
+
const prompt = `STRATEGY PERFORMANCE DATA - ANALYZE AND OPTIMIZE
|
|
346
|
+
|
|
347
|
+
Session Stats:
|
|
348
|
+
- Trades: ${performanceData.trades || 0}
|
|
349
|
+
- Wins: ${performanceData.wins || 0}
|
|
350
|
+
- Losses: ${performanceData.losses || 0}
|
|
351
|
+
- Win Rate: ${performanceData.winRate ? (performanceData.winRate * 100).toFixed(1) + '%' : 'N/A'}
|
|
352
|
+
- Total P&L: $${performanceData.pnl?.toFixed(2) || '0.00'}
|
|
353
|
+
- Avg Win: $${performanceData.avgWin?.toFixed(2) || 'N/A'}
|
|
354
|
+
- Avg Loss: $${performanceData.avgLoss?.toFixed(2) || 'N/A'}
|
|
355
|
+
- Largest Win: $${performanceData.largestWin?.toFixed(2) || 'N/A'}
|
|
356
|
+
- Largest Loss: $${performanceData.largestLoss?.toFixed(2) || 'N/A'}
|
|
357
|
+
- Max Drawdown: $${performanceData.maxDrawdown?.toFixed(2) || 'N/A'}
|
|
358
|
+
- Profit Factor: ${performanceData.profitFactor?.toFixed(2) || 'N/A'}
|
|
359
|
+
|
|
360
|
+
Current Parameters:
|
|
361
|
+
- Position Size: ${performanceData.positionSize || 'N/A'} contracts
|
|
362
|
+
- Daily Target: $${performanceData.dailyTarget || 'N/A'}
|
|
363
|
+
- Max Risk: $${performanceData.maxRisk || 'N/A'}
|
|
364
|
+
- Symbol: ${performanceData.symbol || 'N/A'}
|
|
365
|
+
|
|
366
|
+
Recent Trades:
|
|
367
|
+
${performanceData.recentTrades?.map(t =>
|
|
368
|
+
`- ${t.side} ${t.qty}x @ ${t.price} → P&L: $${t.pnl?.toFixed(2) || 'N/A'}`
|
|
369
|
+
).join('\n') || 'No recent trades'}
|
|
370
|
+
|
|
371
|
+
Market Context:
|
|
372
|
+
- Volatility: ${performanceData.volatility || 'N/A'}
|
|
373
|
+
- Trend: ${performanceData.trend || 'N/A'}
|
|
374
|
+
- Session: ${performanceData.session || 'N/A'}
|
|
375
|
+
|
|
376
|
+
Analyze and suggest optimizations to improve performance.`;
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const response = await callAI(agent, prompt, systemPrompt);
|
|
380
|
+
if (!response) return null;
|
|
381
|
+
|
|
382
|
+
// Parse JSON from response
|
|
383
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
384
|
+
if (jsonMatch) {
|
|
385
|
+
return JSON.parse(jsonMatch[0]);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return null;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get real-time trading advice based on current market conditions
|
|
396
|
+
*
|
|
397
|
+
* @param {Object} agent - AI agent
|
|
398
|
+
* @param {Object} marketData - Current market data
|
|
399
|
+
* @returns {Promise<Object|null>} Trading advice
|
|
400
|
+
*/
|
|
401
|
+
const getMarketAdvice = async (agent, marketData) => {
|
|
402
|
+
if (!agent || !marketData) return null;
|
|
403
|
+
|
|
404
|
+
const systemPrompt = `You are an AI supervisor for HQX Ultra Scalping futures strategy.
|
|
405
|
+
Analyze real-time market data and provide actionable advice.
|
|
406
|
+
Be concise and precise. The strategy will use your recommendations.
|
|
407
|
+
|
|
408
|
+
Respond ONLY in valid JSON:
|
|
409
|
+
{
|
|
410
|
+
"action": "AGGRESSIVE|NORMAL|CAUTIOUS|PAUSE",
|
|
411
|
+
"sizeMultiplier": 0.5-1.5,
|
|
412
|
+
"reason": "brief reason",
|
|
413
|
+
"confidence": 0-100
|
|
414
|
+
}`;
|
|
415
|
+
|
|
416
|
+
const prompt = `REAL-TIME MARKET ANALYSIS
|
|
417
|
+
|
|
418
|
+
Current Price: ${marketData.price || 'N/A'}
|
|
419
|
+
Bid: ${marketData.bid || 'N/A'} | Ask: ${marketData.ask || 'N/A'}
|
|
420
|
+
Spread: ${marketData.spread || 'N/A'}
|
|
421
|
+
Volume: ${marketData.volume || 'N/A'}
|
|
422
|
+
Delta: ${marketData.delta || 'N/A'}
|
|
423
|
+
Volatility: ${marketData.volatility || 'N/A'}
|
|
424
|
+
|
|
425
|
+
Recent Price Action:
|
|
426
|
+
- High: ${marketData.high || 'N/A'}
|
|
427
|
+
- Low: ${marketData.low || 'N/A'}
|
|
428
|
+
- Range: ${marketData.range || 'N/A'}
|
|
429
|
+
|
|
430
|
+
Current Position: ${marketData.position || 'FLAT'}
|
|
431
|
+
Session P&L: $${marketData.pnl?.toFixed(2) || '0.00'}
|
|
432
|
+
|
|
433
|
+
What should the strategy do?`;
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
const response = await callAI(agent, prompt, systemPrompt);
|
|
437
|
+
if (!response) return null;
|
|
438
|
+
|
|
439
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
440
|
+
if (jsonMatch) {
|
|
441
|
+
return JSON.parse(jsonMatch[0]);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return null;
|
|
445
|
+
} catch (error) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
311
450
|
/**
|
|
312
451
|
* Fetch available models from Anthropic API (API Key auth)
|
|
313
452
|
* @param {string} apiKey - API key
|
|
@@ -429,6 +568,8 @@ const fetchOpenAIModels = async (endpoint, apiKey) => {
|
|
|
429
568
|
module.exports = {
|
|
430
569
|
callAI,
|
|
431
570
|
analyzeTrading,
|
|
571
|
+
analyzePerformance,
|
|
572
|
+
getMarketAdvice,
|
|
432
573
|
callOpenAICompatible,
|
|
433
574
|
callAnthropic,
|
|
434
575
|
callGemini,
|
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Strategy Supervisor
|
|
3
|
+
*
|
|
4
|
+
* Observes, learns from, and optimizes the HQX Ultra Scalping strategy in real-time.
|
|
5
|
+
*
|
|
6
|
+
* FUNCTIONS:
|
|
7
|
+
* 1. OBSERVE - Receive all market data, signals, and trades in real-time
|
|
8
|
+
* 2. LEARN - Analyze winning/losing trades to identify patterns
|
|
9
|
+
* 3. OPTIMIZE - Suggest and apply parameter improvements
|
|
10
|
+
* 4. SUPERVISE - Monitor risk and intervene when necessary
|
|
11
|
+
*
|
|
12
|
+
* In CONSENSUS mode (2+ agents), ALL agents must agree before applying changes.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { analyzePerformance, getMarketAdvice, callAI } = require('./client');
|
|
16
|
+
|
|
17
|
+
// Singleton supervisor state
|
|
18
|
+
let supervisorState = {
|
|
19
|
+
active: false,
|
|
20
|
+
agents: [],
|
|
21
|
+
strategy: null,
|
|
22
|
+
service: null,
|
|
23
|
+
accountId: null,
|
|
24
|
+
|
|
25
|
+
// Real-time data (synced with strategy)
|
|
26
|
+
ticks: [],
|
|
27
|
+
signals: [],
|
|
28
|
+
trades: [],
|
|
29
|
+
|
|
30
|
+
// Learning data
|
|
31
|
+
winningPatterns: [],
|
|
32
|
+
losingPatterns: [],
|
|
33
|
+
|
|
34
|
+
// Performance tracking
|
|
35
|
+
performance: {
|
|
36
|
+
trades: 0,
|
|
37
|
+
wins: 0,
|
|
38
|
+
losses: 0,
|
|
39
|
+
totalPnL: 0,
|
|
40
|
+
maxDrawdown: 0,
|
|
41
|
+
currentDrawdown: 0,
|
|
42
|
+
peakPnL: 0,
|
|
43
|
+
winStreak: 0,
|
|
44
|
+
lossStreak: 0,
|
|
45
|
+
maxWinStreak: 0,
|
|
46
|
+
maxLossStreak: 0
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// Optimization state
|
|
50
|
+
optimizations: [],
|
|
51
|
+
lastOptimizationTime: 0,
|
|
52
|
+
optimizationInterval: 60000, // Analyze every 60 seconds
|
|
53
|
+
|
|
54
|
+
// Current recommendations
|
|
55
|
+
currentAdvice: {
|
|
56
|
+
action: 'NORMAL',
|
|
57
|
+
sizeMultiplier: 1.0,
|
|
58
|
+
reason: 'Starting'
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Analysis interval
|
|
63
|
+
let analysisInterval = null;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initialize supervisor with strategy and agents
|
|
67
|
+
*/
|
|
68
|
+
const initialize = (strategy, agents, service, accountId) => {
|
|
69
|
+
supervisorState = {
|
|
70
|
+
...supervisorState,
|
|
71
|
+
active: true,
|
|
72
|
+
agents: agents || [],
|
|
73
|
+
strategy,
|
|
74
|
+
service,
|
|
75
|
+
accountId,
|
|
76
|
+
ticks: [],
|
|
77
|
+
signals: [],
|
|
78
|
+
trades: [],
|
|
79
|
+
winningPatterns: [],
|
|
80
|
+
losingPatterns: [],
|
|
81
|
+
performance: {
|
|
82
|
+
trades: 0,
|
|
83
|
+
wins: 0,
|
|
84
|
+
losses: 0,
|
|
85
|
+
totalPnL: 0,
|
|
86
|
+
maxDrawdown: 0,
|
|
87
|
+
currentDrawdown: 0,
|
|
88
|
+
peakPnL: 0,
|
|
89
|
+
winStreak: 0,
|
|
90
|
+
lossStreak: 0,
|
|
91
|
+
maxWinStreak: 0,
|
|
92
|
+
maxLossStreak: 0
|
|
93
|
+
},
|
|
94
|
+
optimizations: [],
|
|
95
|
+
lastOptimizationTime: Date.now()
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Start continuous analysis loop
|
|
99
|
+
if (analysisInterval) clearInterval(analysisInterval);
|
|
100
|
+
analysisInterval = setInterval(analyzeAndOptimize, supervisorState.optimizationInterval);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
success: true,
|
|
104
|
+
agents: agents.length,
|
|
105
|
+
mode: agents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL'
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Stop supervisor
|
|
111
|
+
*/
|
|
112
|
+
const stop = () => {
|
|
113
|
+
if (analysisInterval) {
|
|
114
|
+
clearInterval(analysisInterval);
|
|
115
|
+
analysisInterval = null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const summary = {
|
|
119
|
+
...supervisorState.performance,
|
|
120
|
+
optimizationsApplied: supervisorState.optimizations.length,
|
|
121
|
+
winningPatterns: supervisorState.winningPatterns.length,
|
|
122
|
+
losingPatterns: supervisorState.losingPatterns.length
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
supervisorState.active = false;
|
|
126
|
+
|
|
127
|
+
return summary;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Feed tick data (called on every market tick)
|
|
132
|
+
*/
|
|
133
|
+
const feedTick = (tick) => {
|
|
134
|
+
if (!supervisorState.active) return;
|
|
135
|
+
|
|
136
|
+
supervisorState.ticks.push({
|
|
137
|
+
...tick,
|
|
138
|
+
timestamp: Date.now()
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Keep last 5000 ticks for pattern analysis
|
|
142
|
+
if (supervisorState.ticks.length > 5000) {
|
|
143
|
+
supervisorState.ticks = supervisorState.ticks.slice(-5000);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Feed signal data (called when strategy generates a signal)
|
|
149
|
+
*/
|
|
150
|
+
const feedSignal = (signal) => {
|
|
151
|
+
if (!supervisorState.active) return;
|
|
152
|
+
|
|
153
|
+
const signalData = {
|
|
154
|
+
...signal,
|
|
155
|
+
timestamp: Date.now(),
|
|
156
|
+
ticksContext: supervisorState.ticks.slice(-50) // Last 50 ticks before signal
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
supervisorState.signals.push(signalData);
|
|
160
|
+
|
|
161
|
+
// Keep last 100 signals
|
|
162
|
+
if (supervisorState.signals.length > 100) {
|
|
163
|
+
supervisorState.signals = supervisorState.signals.slice(-100);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Feed trade result (called when a trade completes)
|
|
169
|
+
* This is where LEARNING happens
|
|
170
|
+
*/
|
|
171
|
+
const feedTradeResult = (trade) => {
|
|
172
|
+
if (!supervisorState.active) return;
|
|
173
|
+
|
|
174
|
+
const tradeData = {
|
|
175
|
+
...trade,
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
// Capture context at time of trade
|
|
178
|
+
ticksBefore: supervisorState.ticks.slice(-100),
|
|
179
|
+
signalUsed: supervisorState.signals[supervisorState.signals.length - 1] || null
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
supervisorState.trades.push(tradeData);
|
|
183
|
+
|
|
184
|
+
// Update performance metrics
|
|
185
|
+
const perf = supervisorState.performance;
|
|
186
|
+
perf.trades++;
|
|
187
|
+
perf.totalPnL += trade.pnl || 0;
|
|
188
|
+
|
|
189
|
+
if (trade.pnl > 0) {
|
|
190
|
+
perf.wins++;
|
|
191
|
+
perf.winStreak++;
|
|
192
|
+
perf.lossStreak = 0;
|
|
193
|
+
perf.maxWinStreak = Math.max(perf.maxWinStreak, perf.winStreak);
|
|
194
|
+
|
|
195
|
+
// Learn from winning trade
|
|
196
|
+
learnFromTrade(tradeData, 'win');
|
|
197
|
+
} else if (trade.pnl < 0) {
|
|
198
|
+
perf.losses++;
|
|
199
|
+
perf.lossStreak++;
|
|
200
|
+
perf.winStreak = 0;
|
|
201
|
+
perf.maxLossStreak = Math.max(perf.maxLossStreak, perf.lossStreak);
|
|
202
|
+
|
|
203
|
+
// Learn from losing trade
|
|
204
|
+
learnFromTrade(tradeData, 'loss');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Update drawdown
|
|
208
|
+
if (perf.totalPnL > perf.peakPnL) {
|
|
209
|
+
perf.peakPnL = perf.totalPnL;
|
|
210
|
+
perf.currentDrawdown = 0;
|
|
211
|
+
} else {
|
|
212
|
+
perf.currentDrawdown = perf.peakPnL - perf.totalPnL;
|
|
213
|
+
perf.maxDrawdown = Math.max(perf.maxDrawdown, perf.currentDrawdown);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Trigger immediate analysis after losing streaks
|
|
217
|
+
if (perf.lossStreak >= 3) {
|
|
218
|
+
analyzeAndOptimize();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Learn from a completed trade
|
|
224
|
+
* Extracts patterns from winning and losing trades
|
|
225
|
+
*/
|
|
226
|
+
const learnFromTrade = (trade, result) => {
|
|
227
|
+
const pattern = {
|
|
228
|
+
timestamp: trade.timestamp,
|
|
229
|
+
result,
|
|
230
|
+
pnl: trade.pnl,
|
|
231
|
+
direction: trade.direction || trade.side,
|
|
232
|
+
|
|
233
|
+
// Market context before trade
|
|
234
|
+
priceAction: analyzePriceAction(trade.ticksBefore),
|
|
235
|
+
volumeProfile: analyzeVolume(trade.ticksBefore),
|
|
236
|
+
volatility: calculateVolatility(trade.ticksBefore),
|
|
237
|
+
|
|
238
|
+
// Signal characteristics
|
|
239
|
+
signalConfidence: trade.signalUsed?.confidence || null,
|
|
240
|
+
entryPrice: trade.price || trade.signalUsed?.entry,
|
|
241
|
+
stopLoss: trade.signalUsed?.stopLoss,
|
|
242
|
+
takeProfit: trade.signalUsed?.takeProfit
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if (result === 'win') {
|
|
246
|
+
supervisorState.winningPatterns.push(pattern);
|
|
247
|
+
// Keep last 50 winning patterns
|
|
248
|
+
if (supervisorState.winningPatterns.length > 50) {
|
|
249
|
+
supervisorState.winningPatterns = supervisorState.winningPatterns.slice(-50);
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
supervisorState.losingPatterns.push(pattern);
|
|
253
|
+
// Keep last 50 losing patterns
|
|
254
|
+
if (supervisorState.losingPatterns.length > 50) {
|
|
255
|
+
supervisorState.losingPatterns = supervisorState.losingPatterns.slice(-50);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Analyze price action from ticks
|
|
262
|
+
*/
|
|
263
|
+
const analyzePriceAction = (ticks) => {
|
|
264
|
+
if (!ticks || ticks.length < 2) return { trend: 'unknown', strength: 0 };
|
|
265
|
+
|
|
266
|
+
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
267
|
+
if (prices.length < 2) return { trend: 'unknown', strength: 0 };
|
|
268
|
+
|
|
269
|
+
const first = prices[0];
|
|
270
|
+
const last = prices[prices.length - 1];
|
|
271
|
+
const change = last - first;
|
|
272
|
+
const range = Math.max(...prices) - Math.min(...prices);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
trend: change > 0 ? 'up' : change < 0 ? 'down' : 'flat',
|
|
276
|
+
strength: range > 0 ? Math.abs(change) / range : 0,
|
|
277
|
+
range,
|
|
278
|
+
change
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Analyze volume from ticks
|
|
284
|
+
*/
|
|
285
|
+
const analyzeVolume = (ticks) => {
|
|
286
|
+
if (!ticks || ticks.length === 0) return { total: 0, avg: 0, trend: 'unknown' };
|
|
287
|
+
|
|
288
|
+
const volumes = ticks.map(t => t.volume || 0);
|
|
289
|
+
const total = volumes.reduce((a, b) => a + b, 0);
|
|
290
|
+
const avg = total / volumes.length;
|
|
291
|
+
|
|
292
|
+
// Compare first half vs second half
|
|
293
|
+
const mid = Math.floor(volumes.length / 2);
|
|
294
|
+
const firstHalf = volumes.slice(0, mid).reduce((a, b) => a + b, 0);
|
|
295
|
+
const secondHalf = volumes.slice(mid).reduce((a, b) => a + b, 0);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
total,
|
|
299
|
+
avg,
|
|
300
|
+
trend: secondHalf > firstHalf * 1.2 ? 'increasing' : secondHalf < firstHalf * 0.8 ? 'decreasing' : 'stable'
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Calculate volatility from ticks
|
|
306
|
+
*/
|
|
307
|
+
const calculateVolatility = (ticks) => {
|
|
308
|
+
if (!ticks || ticks.length < 2) return 0;
|
|
309
|
+
|
|
310
|
+
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
311
|
+
if (prices.length < 2) return 0;
|
|
312
|
+
|
|
313
|
+
const returns = [];
|
|
314
|
+
for (let i = 1; i < prices.length; i++) {
|
|
315
|
+
returns.push((prices[i] - prices[i-1]) / prices[i-1]);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
|
|
319
|
+
const variance = returns.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / returns.length;
|
|
320
|
+
|
|
321
|
+
return Math.sqrt(variance);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Main analysis and optimization loop
|
|
326
|
+
* Called periodically and after significant events
|
|
327
|
+
*/
|
|
328
|
+
const analyzeAndOptimize = async () => {
|
|
329
|
+
if (!supervisorState.active || supervisorState.agents.length === 0) return;
|
|
330
|
+
|
|
331
|
+
const perf = supervisorState.performance;
|
|
332
|
+
|
|
333
|
+
// Skip if not enough data
|
|
334
|
+
if (perf.trades < 3) return;
|
|
335
|
+
|
|
336
|
+
// Prepare performance data for AI analysis
|
|
337
|
+
const performanceData = {
|
|
338
|
+
trades: perf.trades,
|
|
339
|
+
wins: perf.wins,
|
|
340
|
+
losses: perf.losses,
|
|
341
|
+
winRate: perf.trades > 0 ? perf.wins / perf.trades : 0,
|
|
342
|
+
pnl: perf.totalPnL,
|
|
343
|
+
maxDrawdown: perf.maxDrawdown,
|
|
344
|
+
currentDrawdown: perf.currentDrawdown,
|
|
345
|
+
winStreak: perf.winStreak,
|
|
346
|
+
lossStreak: perf.lossStreak,
|
|
347
|
+
maxWinStreak: perf.maxWinStreak,
|
|
348
|
+
maxLossStreak: perf.maxLossStreak,
|
|
349
|
+
|
|
350
|
+
// Calculate averages
|
|
351
|
+
avgWin: perf.wins > 0 ?
|
|
352
|
+
supervisorState.trades.filter(t => t.pnl > 0).reduce((s, t) => s + t.pnl, 0) / perf.wins : 0,
|
|
353
|
+
avgLoss: perf.losses > 0 ?
|
|
354
|
+
Math.abs(supervisorState.trades.filter(t => t.pnl < 0).reduce((s, t) => s + t.pnl, 0) / perf.losses) : 0,
|
|
355
|
+
|
|
356
|
+
// Recent trades for context
|
|
357
|
+
recentTrades: supervisorState.trades.slice(-10).map(t => ({
|
|
358
|
+
side: t.direction || t.side,
|
|
359
|
+
qty: t.qty,
|
|
360
|
+
price: t.price,
|
|
361
|
+
pnl: t.pnl
|
|
362
|
+
})),
|
|
363
|
+
|
|
364
|
+
// Pattern summaries
|
|
365
|
+
winningPatternCount: supervisorState.winningPatterns.length,
|
|
366
|
+
losingPatternCount: supervisorState.losingPatterns.length,
|
|
367
|
+
|
|
368
|
+
// Common characteristics of losing trades
|
|
369
|
+
losingTradeAnalysis: analyzeLosingPatterns()
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// Get optimization suggestions from all agents
|
|
373
|
+
const suggestions = [];
|
|
374
|
+
|
|
375
|
+
for (const agent of supervisorState.agents) {
|
|
376
|
+
try {
|
|
377
|
+
const suggestion = await getOptimizationFromAgent(agent, performanceData);
|
|
378
|
+
if (suggestion) {
|
|
379
|
+
suggestions.push({
|
|
380
|
+
agentId: agent.id,
|
|
381
|
+
agentName: agent.name,
|
|
382
|
+
...suggestion
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
} catch (e) {
|
|
386
|
+
// Silent fail for individual agent
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (suggestions.length === 0) return;
|
|
391
|
+
|
|
392
|
+
// Process suggestions based on mode
|
|
393
|
+
const isConsensus = supervisorState.agents.length >= 2;
|
|
394
|
+
|
|
395
|
+
if (isConsensus) {
|
|
396
|
+
// CONSENSUS MODE: All agents must agree
|
|
397
|
+
const consensusResult = buildConsensus(suggestions);
|
|
398
|
+
|
|
399
|
+
if (consensusResult.isUnanimous && consensusResult.optimizations.length > 0) {
|
|
400
|
+
// Apply unanimous optimizations
|
|
401
|
+
for (const opt of consensusResult.optimizations) {
|
|
402
|
+
applyOptimization(opt);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Update current advice based on consensus
|
|
407
|
+
if (consensusResult.action) {
|
|
408
|
+
supervisorState.currentAdvice = {
|
|
409
|
+
action: consensusResult.action,
|
|
410
|
+
sizeMultiplier: consensusResult.sizeMultiplier || 1.0,
|
|
411
|
+
reason: consensusResult.reason || 'Consensus recommendation'
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
// INDIVIDUAL MODE: Apply single agent's suggestions
|
|
416
|
+
const suggestion = suggestions[0];
|
|
417
|
+
|
|
418
|
+
if (suggestion.optimizations) {
|
|
419
|
+
for (const opt of suggestion.optimizations) {
|
|
420
|
+
applyOptimization(opt);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (suggestion.action) {
|
|
425
|
+
supervisorState.currentAdvice = {
|
|
426
|
+
action: suggestion.action,
|
|
427
|
+
sizeMultiplier: suggestion.sizeMultiplier || 1.0,
|
|
428
|
+
reason: suggestion.reason || 'Agent recommendation'
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
supervisorState.lastOptimizationTime = Date.now();
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Analyze patterns in losing trades
|
|
438
|
+
*/
|
|
439
|
+
const analyzeLosingPatterns = () => {
|
|
440
|
+
const patterns = supervisorState.losingPatterns;
|
|
441
|
+
if (patterns.length === 0) return null;
|
|
442
|
+
|
|
443
|
+
// Find common characteristics
|
|
444
|
+
const trends = patterns.map(p => p.priceAction?.trend).filter(Boolean);
|
|
445
|
+
const volatilities = patterns.map(p => p.volatility).filter(Boolean);
|
|
446
|
+
const confidences = patterns.map(p => p.signalConfidence).filter(Boolean);
|
|
447
|
+
|
|
448
|
+
const trendCounts = {};
|
|
449
|
+
for (const t of trends) {
|
|
450
|
+
trendCounts[t] = (trendCounts[t] || 0) + 1;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const avgVolatility = volatilities.length > 0 ?
|
|
454
|
+
volatilities.reduce((a, b) => a + b, 0) / volatilities.length : 0;
|
|
455
|
+
|
|
456
|
+
const avgConfidence = confidences.length > 0 ?
|
|
457
|
+
confidences.reduce((a, b) => a + b, 0) / confidences.length : 0;
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
commonTrend: Object.entries(trendCounts).sort((a, b) => b[1] - a[1])[0]?.[0] || 'unknown',
|
|
461
|
+
avgVolatility,
|
|
462
|
+
avgConfidence,
|
|
463
|
+
count: patterns.length
|
|
464
|
+
};
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Get optimization suggestion from a single agent
|
|
469
|
+
*/
|
|
470
|
+
const getOptimizationFromAgent = async (agent, performanceData) => {
|
|
471
|
+
const systemPrompt = `You are an AI supervisor for HQX Ultra Scalping, a professional futures trading strategy.
|
|
472
|
+
|
|
473
|
+
The strategy uses:
|
|
474
|
+
- Order flow analysis (delta, absorption, imbalance)
|
|
475
|
+
- Statistical models (z-score, standard deviation)
|
|
476
|
+
- Dynamic risk management (Kelly criterion)
|
|
477
|
+
|
|
478
|
+
ANALYZE the performance data and LEARN from the losing trades.
|
|
479
|
+
Suggest SPECIFIC optimizations to improve win rate and reduce losses.
|
|
480
|
+
|
|
481
|
+
Respond in JSON:
|
|
482
|
+
{
|
|
483
|
+
"assessment": "brief assessment",
|
|
484
|
+
"action": "AGGRESSIVE|NORMAL|CAUTIOUS|PAUSE",
|
|
485
|
+
"sizeMultiplier": 0.5-1.5,
|
|
486
|
+
"optimizations": [
|
|
487
|
+
{"param": "name", "direction": "increase|decrease", "amount": "10%", "reason": "why"}
|
|
488
|
+
],
|
|
489
|
+
"learnings": "what we learned from losing trades",
|
|
490
|
+
"confidence": 0-100
|
|
491
|
+
}`;
|
|
492
|
+
|
|
493
|
+
const prompt = `STRATEGY PERFORMANCE ANALYSIS
|
|
494
|
+
|
|
495
|
+
Stats:
|
|
496
|
+
- Trades: ${performanceData.trades} (${performanceData.wins}W / ${performanceData.losses}L)
|
|
497
|
+
- Win Rate: ${(performanceData.winRate * 100).toFixed(1)}%
|
|
498
|
+
- P&L: $${performanceData.pnl.toFixed(2)}
|
|
499
|
+
- Max Drawdown: $${performanceData.maxDrawdown.toFixed(2)}
|
|
500
|
+
- Current Streak: ${performanceData.winStreak > 0 ? performanceData.winStreak + ' wins' : performanceData.lossStreak + ' losses'}
|
|
501
|
+
|
|
502
|
+
Losing Trade Analysis:
|
|
503
|
+
${performanceData.losingTradeAnalysis ? `
|
|
504
|
+
- Common trend at entry: ${performanceData.losingTradeAnalysis.commonTrend}
|
|
505
|
+
- Avg volatility: ${(performanceData.losingTradeAnalysis.avgVolatility * 100).toFixed(3)}%
|
|
506
|
+
- Avg signal confidence: ${(performanceData.losingTradeAnalysis.avgConfidence * 100).toFixed(1)}%
|
|
507
|
+
- Total losing patterns: ${performanceData.losingTradeAnalysis.count}
|
|
508
|
+
` : 'Not enough data'}
|
|
509
|
+
|
|
510
|
+
Recent Trades:
|
|
511
|
+
${performanceData.recentTrades.map(t => `${t.side} @ ${t.price} → $${t.pnl?.toFixed(2)}`).join('\n')}
|
|
512
|
+
|
|
513
|
+
What should we LEARN and OPTIMIZE?`;
|
|
514
|
+
|
|
515
|
+
try {
|
|
516
|
+
const response = await callAI(agent, prompt, systemPrompt);
|
|
517
|
+
if (!response) return null;
|
|
518
|
+
|
|
519
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
520
|
+
if (jsonMatch) {
|
|
521
|
+
return JSON.parse(jsonMatch[0]);
|
|
522
|
+
}
|
|
523
|
+
return null;
|
|
524
|
+
} catch (e) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Build consensus from multiple agent suggestions
|
|
531
|
+
*/
|
|
532
|
+
const buildConsensus = (suggestions) => {
|
|
533
|
+
if (suggestions.length === 0) return { isUnanimous: false };
|
|
534
|
+
|
|
535
|
+
// Check action consensus
|
|
536
|
+
const actions = suggestions.map(s => s.action).filter(Boolean);
|
|
537
|
+
const allSameAction = actions.length === suggestions.length &&
|
|
538
|
+
actions.every(a => a === actions[0]);
|
|
539
|
+
|
|
540
|
+
// Check optimization consensus
|
|
541
|
+
const allOptimizations = suggestions.flatMap(s => s.optimizations || []);
|
|
542
|
+
const paramGroups = {};
|
|
543
|
+
|
|
544
|
+
for (const opt of allOptimizations) {
|
|
545
|
+
if (!opt.param) continue;
|
|
546
|
+
const key = `${opt.param}_${opt.direction}`;
|
|
547
|
+
if (!paramGroups[key]) {
|
|
548
|
+
paramGroups[key] = { ...opt, count: 0 };
|
|
549
|
+
}
|
|
550
|
+
paramGroups[key].count++;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Find unanimous optimizations (all agents agree)
|
|
554
|
+
const unanimousOptimizations = Object.values(paramGroups)
|
|
555
|
+
.filter(g => g.count === suggestions.length)
|
|
556
|
+
.map(g => ({
|
|
557
|
+
param: g.param,
|
|
558
|
+
direction: g.direction,
|
|
559
|
+
amount: g.amount,
|
|
560
|
+
reason: `Unanimous (${suggestions.length} agents)`
|
|
561
|
+
}));
|
|
562
|
+
|
|
563
|
+
// Average size multiplier
|
|
564
|
+
const multipliers = suggestions.map(s => s.sizeMultiplier || 1.0);
|
|
565
|
+
const avgMultiplier = multipliers.reduce((a, b) => a + b, 0) / multipliers.length;
|
|
566
|
+
|
|
567
|
+
// Average confidence
|
|
568
|
+
const confidences = suggestions.map(s => s.confidence || 50);
|
|
569
|
+
const avgConfidence = Math.round(confidences.reduce((a, b) => a + b, 0) / confidences.length);
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
isUnanimous: allSameAction && unanimousOptimizations.length > 0,
|
|
573
|
+
action: allSameAction ? actions[0] : 'CAUTIOUS',
|
|
574
|
+
sizeMultiplier: allSameAction ? avgMultiplier : 0.5,
|
|
575
|
+
optimizations: unanimousOptimizations,
|
|
576
|
+
confidence: avgConfidence,
|
|
577
|
+
reason: allSameAction ?
|
|
578
|
+
`${suggestions.length} agents agree` :
|
|
579
|
+
'Agents disagree - being cautious',
|
|
580
|
+
votes: actions.reduce((acc, a) => { acc[a] = (acc[a] || 0) + 1; return acc; }, {})
|
|
581
|
+
};
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Apply an optimization to the strategy
|
|
586
|
+
*/
|
|
587
|
+
const applyOptimization = (optimization) => {
|
|
588
|
+
const strategy = supervisorState.strategy;
|
|
589
|
+
if (!strategy) return false;
|
|
590
|
+
|
|
591
|
+
// Record the optimization
|
|
592
|
+
supervisorState.optimizations.push({
|
|
593
|
+
timestamp: Date.now(),
|
|
594
|
+
...optimization
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// Try to apply to strategy if it supports it
|
|
598
|
+
try {
|
|
599
|
+
if (typeof strategy.applyAIOptimization === 'function') {
|
|
600
|
+
strategy.applyAIOptimization(optimization);
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (typeof strategy.setParameter === 'function') {
|
|
605
|
+
strategy.setParameter(optimization.param, optimization.direction, optimization.amount);
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
} catch (e) {
|
|
609
|
+
// Strategy doesn't support this optimization
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return false;
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Get current advice for the strategy
|
|
617
|
+
* Called before each trade decision
|
|
618
|
+
*/
|
|
619
|
+
const getCurrentAdvice = () => {
|
|
620
|
+
if (!supervisorState.active) {
|
|
621
|
+
return { action: 'NORMAL', sizeMultiplier: 1.0, reason: 'No supervision' };
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return supervisorState.currentAdvice;
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Get supervision status
|
|
629
|
+
*/
|
|
630
|
+
const getStatus = () => {
|
|
631
|
+
return {
|
|
632
|
+
active: supervisorState.active,
|
|
633
|
+
agents: supervisorState.agents.length,
|
|
634
|
+
mode: supervisorState.agents.length >= 2 ? 'CONSENSUS' : 'INDIVIDUAL',
|
|
635
|
+
performance: supervisorState.performance,
|
|
636
|
+
currentAdvice: supervisorState.currentAdvice,
|
|
637
|
+
optimizationsApplied: supervisorState.optimizations.length,
|
|
638
|
+
patternsLearned: {
|
|
639
|
+
winning: supervisorState.winningPatterns.length,
|
|
640
|
+
losing: supervisorState.losingPatterns.length
|
|
641
|
+
},
|
|
642
|
+
lastOptimization: supervisorState.lastOptimizationTime
|
|
643
|
+
};
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Check if should proceed with trade based on AI advice
|
|
648
|
+
*/
|
|
649
|
+
const shouldTrade = () => {
|
|
650
|
+
if (!supervisorState.active) return { proceed: true, multiplier: 1.0 };
|
|
651
|
+
|
|
652
|
+
const advice = supervisorState.currentAdvice;
|
|
653
|
+
|
|
654
|
+
if (advice.action === 'PAUSE') {
|
|
655
|
+
return { proceed: false, reason: advice.reason };
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return {
|
|
659
|
+
proceed: true,
|
|
660
|
+
multiplier: advice.sizeMultiplier || 1.0,
|
|
661
|
+
action: advice.action
|
|
662
|
+
};
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
module.exports = {
|
|
666
|
+
initialize,
|
|
667
|
+
stop,
|
|
668
|
+
feedTick,
|
|
669
|
+
feedSignal,
|
|
670
|
+
feedTradeResult,
|
|
671
|
+
getCurrentAdvice,
|
|
672
|
+
shouldTrade,
|
|
673
|
+
getStatus,
|
|
674
|
+
analyzeAndOptimize
|
|
675
|
+
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { connections } = require('../session');
|
|
10
|
-
const { analyzeTrading } = require('./client');
|
|
10
|
+
const { analyzeTrading, analyzePerformance, getMarketAdvice } = require('./client');
|
|
11
11
|
|
|
12
12
|
let aiService = null;
|
|
13
13
|
|
|
@@ -567,6 +567,383 @@ class AISupervisor {
|
|
|
567
567
|
static getSessionCount() {
|
|
568
568
|
return supervisionSessions.size;
|
|
569
569
|
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Feed market tick to all agents (sync with strategy)
|
|
573
|
+
* Agents receive the same data as the strategy in real-time
|
|
574
|
+
*
|
|
575
|
+
* @param {Object} tick - Market tick data { price, bid, ask, volume, timestamp }
|
|
576
|
+
*/
|
|
577
|
+
static feedTick(tick) {
|
|
578
|
+
if (supervisionSessions.size === 0) return;
|
|
579
|
+
|
|
580
|
+
// Store latest tick in all sessions
|
|
581
|
+
for (const [agentId, session] of supervisionSessions.entries()) {
|
|
582
|
+
if (!session.marketData) {
|
|
583
|
+
session.marketData = { ticks: [], lastTick: null };
|
|
584
|
+
}
|
|
585
|
+
session.marketData.lastTick = tick;
|
|
586
|
+
session.marketData.ticks.push(tick);
|
|
587
|
+
|
|
588
|
+
// Keep only last 1000 ticks to prevent memory bloat
|
|
589
|
+
if (session.marketData.ticks.length > 1000) {
|
|
590
|
+
session.marketData.ticks = session.marketData.ticks.slice(-1000);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Feed strategy signal to all agents (sync with strategy)
|
|
597
|
+
* Agents see every signal the strategy generates
|
|
598
|
+
*
|
|
599
|
+
* @param {Object} signal - Strategy signal { direction, entry, stopLoss, takeProfit, confidence }
|
|
600
|
+
*/
|
|
601
|
+
static feedSignal(signal) {
|
|
602
|
+
if (supervisionSessions.size === 0) return;
|
|
603
|
+
|
|
604
|
+
const signalData = {
|
|
605
|
+
timestamp: Date.now(),
|
|
606
|
+
direction: signal.direction,
|
|
607
|
+
entry: signal.entry,
|
|
608
|
+
stopLoss: signal.stopLoss,
|
|
609
|
+
takeProfit: signal.takeProfit,
|
|
610
|
+
confidence: signal.confidence
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// Store signal in all sessions
|
|
614
|
+
for (const [agentId, session] of supervisionSessions.entries()) {
|
|
615
|
+
if (!session.signals) {
|
|
616
|
+
session.signals = [];
|
|
617
|
+
}
|
|
618
|
+
session.signals.push(signalData);
|
|
619
|
+
|
|
620
|
+
// Keep only last 100 signals
|
|
621
|
+
if (session.signals.length > 100) {
|
|
622
|
+
session.signals = session.signals.slice(-100);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Feed trade execution to all agents (sync with strategy)
|
|
629
|
+
* Agents see every trade executed
|
|
630
|
+
*
|
|
631
|
+
* @param {Object} trade - Trade data { side, qty, price, pnl, symbol }
|
|
632
|
+
*/
|
|
633
|
+
static feedTrade(trade) {
|
|
634
|
+
if (supervisionSessions.size === 0) return;
|
|
635
|
+
|
|
636
|
+
const tradeData = {
|
|
637
|
+
timestamp: Date.now(),
|
|
638
|
+
side: trade.side,
|
|
639
|
+
qty: trade.qty,
|
|
640
|
+
price: trade.price,
|
|
641
|
+
pnl: trade.pnl,
|
|
642
|
+
symbol: trade.symbol
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// Store trade in all sessions
|
|
646
|
+
for (const [agentId, session] of supervisionSessions.entries()) {
|
|
647
|
+
if (!session.trades) {
|
|
648
|
+
session.trades = [];
|
|
649
|
+
}
|
|
650
|
+
session.trades.push(tradeData);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Update current position for all agents
|
|
656
|
+
*
|
|
657
|
+
* @param {Object} position - Position data { qty, side, entryPrice, pnl }
|
|
658
|
+
*/
|
|
659
|
+
static updatePosition(position) {
|
|
660
|
+
if (supervisionSessions.size === 0) return;
|
|
661
|
+
|
|
662
|
+
for (const [agentId, session] of supervisionSessions.entries()) {
|
|
663
|
+
session.currentPosition = {
|
|
664
|
+
timestamp: Date.now(),
|
|
665
|
+
qty: position.qty,
|
|
666
|
+
side: position.side,
|
|
667
|
+
entryPrice: position.entryPrice,
|
|
668
|
+
pnl: position.pnl
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Update P&L for all agents
|
|
675
|
+
*
|
|
676
|
+
* @param {number} pnl - Current session P&L
|
|
677
|
+
* @param {number} balance - Account balance
|
|
678
|
+
*/
|
|
679
|
+
static updatePnL(pnl, balance) {
|
|
680
|
+
if (supervisionSessions.size === 0) return;
|
|
681
|
+
|
|
682
|
+
for (const [agentId, session] of supervisionSessions.entries()) {
|
|
683
|
+
session.currentPnL = pnl;
|
|
684
|
+
session.currentBalance = balance;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Check if agents recommend intervention (PAUSE, REDUCE_SIZE, etc.)
|
|
690
|
+
* In CONSENSUS mode, ALL agents must agree to continue trading
|
|
691
|
+
*
|
|
692
|
+
* @returns {Object} { shouldContinue: boolean, action: string, reason: string }
|
|
693
|
+
*/
|
|
694
|
+
static checkIntervention() {
|
|
695
|
+
if (supervisionSessions.size === 0) {
|
|
696
|
+
return { shouldContinue: true, action: 'CONTINUE', reason: 'No AI supervision active' };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Get last consensus or individual decision
|
|
700
|
+
const consensus = this.getConsensus();
|
|
701
|
+
|
|
702
|
+
if (consensus && consensus.isUnanimous) {
|
|
703
|
+
if (consensus.action === 'PAUSE' || consensus.action === 'STOP') {
|
|
704
|
+
return { shouldContinue: false, action: consensus.action, reason: 'AI agents recommend pause' };
|
|
705
|
+
}
|
|
706
|
+
if (consensus.action === 'REDUCE_SIZE') {
|
|
707
|
+
return { shouldContinue: true, action: 'REDUCE_SIZE', reason: 'AI agents recommend reducing size' };
|
|
708
|
+
}
|
|
709
|
+
} else if (consensus && !consensus.isUnanimous) {
|
|
710
|
+
// Agents disagree - be conservative, don't take new trades
|
|
711
|
+
return { shouldContinue: false, action: 'HOLD', reason: 'AI agents disagree - waiting for consensus' };
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return { shouldContinue: true, action: 'CONTINUE', reason: 'AI supervision active' };
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Get real-time sync status for display
|
|
719
|
+
* Shows what data the agents are receiving
|
|
720
|
+
*
|
|
721
|
+
* @returns {Object} Sync status
|
|
722
|
+
*/
|
|
723
|
+
static getSyncStatus() {
|
|
724
|
+
if (supervisionSessions.size === 0) {
|
|
725
|
+
return { synced: false, agents: 0 };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const firstSession = supervisionSessions.values().next().value;
|
|
729
|
+
|
|
730
|
+
return {
|
|
731
|
+
synced: true,
|
|
732
|
+
agents: supervisionSessions.size,
|
|
733
|
+
lastTick: firstSession?.marketData?.lastTick?.timestamp || null,
|
|
734
|
+
tickCount: firstSession?.marketData?.ticks?.length || 0,
|
|
735
|
+
signalCount: firstSession?.signals?.length || 0,
|
|
736
|
+
tradeCount: firstSession?.trades?.length || 0,
|
|
737
|
+
currentPnL: firstSession?.currentPnL || 0,
|
|
738
|
+
currentPosition: firstSession?.currentPosition || null
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Request strategy optimization from all agents
|
|
744
|
+
* Agents analyze performance data and suggest improvements
|
|
745
|
+
* In CONSENSUS mode, only unanimous suggestions are applied
|
|
746
|
+
*
|
|
747
|
+
* @param {Object} performanceData - Strategy performance data
|
|
748
|
+
* @returns {Promise<Object|null>} Optimization suggestions (consensus)
|
|
749
|
+
*/
|
|
750
|
+
static async requestOptimization(performanceData) {
|
|
751
|
+
if (supervisionSessions.size === 0) return null;
|
|
752
|
+
|
|
753
|
+
const allSessions = Array.from(supervisionSessions.values());
|
|
754
|
+
const suggestions = [];
|
|
755
|
+
|
|
756
|
+
// Get optimization suggestions from each agent
|
|
757
|
+
for (const session of allSessions) {
|
|
758
|
+
try {
|
|
759
|
+
const suggestion = await analyzePerformance(session.agent, performanceData);
|
|
760
|
+
if (suggestion) {
|
|
761
|
+
suggestions.push({
|
|
762
|
+
agentId: session.agentId,
|
|
763
|
+
agentName: session.agent.name,
|
|
764
|
+
...suggestion
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
} catch (e) {
|
|
768
|
+
// Silent fail for individual agent
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (suggestions.length === 0) return null;
|
|
773
|
+
|
|
774
|
+
// If single agent, return its suggestion
|
|
775
|
+
if (suggestions.length === 1) {
|
|
776
|
+
return {
|
|
777
|
+
mode: 'INDIVIDUAL',
|
|
778
|
+
...suggestions[0]
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// CONSENSUS MODE: Find common optimizations
|
|
783
|
+
const consensusOptimizations = [];
|
|
784
|
+
const allOptimizations = suggestions.flatMap(s => s.optimizations || []);
|
|
785
|
+
|
|
786
|
+
// Group by parameter name
|
|
787
|
+
const paramGroups = {};
|
|
788
|
+
for (const opt of allOptimizations) {
|
|
789
|
+
if (!opt.param) continue;
|
|
790
|
+
if (!paramGroups[opt.param]) {
|
|
791
|
+
paramGroups[opt.param] = [];
|
|
792
|
+
}
|
|
793
|
+
paramGroups[opt.param].push(opt);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Find unanimous suggestions (all agents agree on direction)
|
|
797
|
+
for (const [param, opts] of Object.entries(paramGroups)) {
|
|
798
|
+
if (opts.length === suggestions.length) {
|
|
799
|
+
// All agents suggested this param - check if they agree on direction
|
|
800
|
+
const directions = opts.map(o => {
|
|
801
|
+
const current = parseFloat(o.current) || 0;
|
|
802
|
+
const suggested = parseFloat(o.suggested) || 0;
|
|
803
|
+
return suggested > current ? 'increase' : suggested < current ? 'decrease' : 'same';
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
const allSame = directions.every(d => d === directions[0]);
|
|
807
|
+
if (allSame && directions[0] !== 'same') {
|
|
808
|
+
// Unanimous - use average of suggested values
|
|
809
|
+
const avgSuggested = opts.reduce((sum, o) => sum + (parseFloat(o.suggested) || 0), 0) / opts.length;
|
|
810
|
+
consensusOptimizations.push({
|
|
811
|
+
param,
|
|
812
|
+
current: opts[0].current,
|
|
813
|
+
suggested: avgSuggested.toFixed(2),
|
|
814
|
+
reason: `Unanimous (${suggestions.length} agents agree)`,
|
|
815
|
+
direction: directions[0]
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Calculate average confidence
|
|
822
|
+
const avgConfidence = Math.round(
|
|
823
|
+
suggestions.reduce((sum, s) => sum + (s.confidence || 0), 0) / suggestions.length
|
|
824
|
+
);
|
|
825
|
+
|
|
826
|
+
// Determine consensus market condition
|
|
827
|
+
const conditions = suggestions.map(s => s.marketCondition).filter(Boolean);
|
|
828
|
+
const conditionCounts = {};
|
|
829
|
+
for (const c of conditions) {
|
|
830
|
+
conditionCounts[c] = (conditionCounts[c] || 0) + 1;
|
|
831
|
+
}
|
|
832
|
+
const consensusCondition = Object.entries(conditionCounts)
|
|
833
|
+
.sort((a, b) => b[1] - a[1])[0]?.[0] || 'unknown';
|
|
834
|
+
|
|
835
|
+
return {
|
|
836
|
+
mode: 'CONSENSUS',
|
|
837
|
+
agentCount: suggestions.length,
|
|
838
|
+
isUnanimous: consensusOptimizations.length > 0,
|
|
839
|
+
optimizations: consensusOptimizations,
|
|
840
|
+
marketCondition: consensusCondition,
|
|
841
|
+
confidence: avgConfidence,
|
|
842
|
+
individualSuggestions: suggestions
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Get real-time market advice from all agents
|
|
848
|
+
* Used for dynamic position sizing and risk adjustment
|
|
849
|
+
*
|
|
850
|
+
* @param {Object} marketData - Current market data
|
|
851
|
+
* @returns {Promise<Object|null>} Market advice (consensus)
|
|
852
|
+
*/
|
|
853
|
+
static async getMarketAdvice(marketData) {
|
|
854
|
+
if (supervisionSessions.size === 0) return null;
|
|
855
|
+
|
|
856
|
+
const allSessions = Array.from(supervisionSessions.values());
|
|
857
|
+
const advices = [];
|
|
858
|
+
|
|
859
|
+
// Get advice from each agent
|
|
860
|
+
for (const session of allSessions) {
|
|
861
|
+
try {
|
|
862
|
+
const advice = await getMarketAdvice(session.agent, marketData);
|
|
863
|
+
if (advice) {
|
|
864
|
+
advices.push({
|
|
865
|
+
agentId: session.agentId,
|
|
866
|
+
agentName: session.agent.name,
|
|
867
|
+
...advice
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
} catch (e) {
|
|
871
|
+
// Silent fail
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (advices.length === 0) return null;
|
|
876
|
+
|
|
877
|
+
// Single agent
|
|
878
|
+
if (advices.length === 1) {
|
|
879
|
+
return {
|
|
880
|
+
mode: 'INDIVIDUAL',
|
|
881
|
+
...advices[0]
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// CONSENSUS: All agents must agree on action
|
|
886
|
+
const actions = advices.map(a => a.action);
|
|
887
|
+
const allSameAction = actions.every(a => a === actions[0]);
|
|
888
|
+
|
|
889
|
+
if (allSameAction) {
|
|
890
|
+
// Unanimous action - average the size multiplier
|
|
891
|
+
const avgMultiplier = advices.reduce((sum, a) => sum + (a.sizeMultiplier || 1), 0) / advices.length;
|
|
892
|
+
const avgConfidence = Math.round(advices.reduce((sum, a) => sum + (a.confidence || 0), 0) / advices.length);
|
|
893
|
+
|
|
894
|
+
return {
|
|
895
|
+
mode: 'CONSENSUS',
|
|
896
|
+
isUnanimous: true,
|
|
897
|
+
action: actions[0],
|
|
898
|
+
sizeMultiplier: Math.round(avgMultiplier * 100) / 100,
|
|
899
|
+
confidence: avgConfidence,
|
|
900
|
+
reason: `${advices.length} agents unanimous`,
|
|
901
|
+
agentCount: advices.length
|
|
902
|
+
};
|
|
903
|
+
} else {
|
|
904
|
+
// Agents disagree - be conservative
|
|
905
|
+
return {
|
|
906
|
+
mode: 'CONSENSUS',
|
|
907
|
+
isUnanimous: false,
|
|
908
|
+
action: 'CAUTIOUS',
|
|
909
|
+
sizeMultiplier: 0.5,
|
|
910
|
+
confidence: 0,
|
|
911
|
+
reason: 'Agents disagree - reducing exposure',
|
|
912
|
+
agentCount: advices.length,
|
|
913
|
+
votes: actions.reduce((acc, a) => { acc[a] = (acc[a] || 0) + 1; return acc; }, {})
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Apply optimization to strategy
|
|
920
|
+
* Called when agents have consensus on improvements
|
|
921
|
+
*
|
|
922
|
+
* @param {Object} strategy - Strategy instance (M1)
|
|
923
|
+
* @param {Object} optimization - Optimization to apply
|
|
924
|
+
* @returns {boolean} Success
|
|
925
|
+
*/
|
|
926
|
+
static applyOptimization(strategy, optimization) {
|
|
927
|
+
if (!strategy || !optimization) return false;
|
|
928
|
+
|
|
929
|
+
try {
|
|
930
|
+
// Check if strategy has optimization method
|
|
931
|
+
if (typeof strategy.applyOptimization === 'function') {
|
|
932
|
+
strategy.applyOptimization(optimization);
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// Fallback: try to set individual parameters
|
|
937
|
+
if (typeof strategy.setParameter === 'function' && optimization.param) {
|
|
938
|
+
strategy.setParameter(optimization.param, optimization.suggested);
|
|
939
|
+
return true;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return false;
|
|
943
|
+
} catch (e) {
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
570
947
|
}
|
|
571
948
|
|
|
572
949
|
module.exports = AISupervisor;
|