hedgequantx 2.5.35 → 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.
|
@@ -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
|
+
};
|