hedgequantx 2.5.40 → 2.5.41
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/services/ai/strategy-supervisor.js +1042 -48
package/package.json
CHANGED
|
@@ -24,7 +24,7 @@ const LEARNING_FILE = path.join(DATA_DIR, 'ai-learning.json');
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Load persisted learning data from disk
|
|
27
|
-
*
|
|
27
|
+
* Contains full memory of strategy behavior over weeks/months
|
|
28
28
|
*/
|
|
29
29
|
const loadLearningData = () => {
|
|
30
30
|
try {
|
|
@@ -34,16 +34,51 @@ const loadLearningData = () => {
|
|
|
34
34
|
|
|
35
35
|
if (fs.existsSync(LEARNING_FILE)) {
|
|
36
36
|
const data = JSON.parse(fs.readFileSync(LEARNING_FILE, 'utf8'));
|
|
37
|
+
|
|
38
|
+
// Clean old sessions (keep only last 1 month / 31 days)
|
|
39
|
+
const oneMonthAgo = Date.now() - (31 * 24 * 60 * 60 * 1000);
|
|
40
|
+
const sessions = (data.sessions || []).filter(s =>
|
|
41
|
+
new Date(s.date).getTime() > oneMonthAgo
|
|
42
|
+
);
|
|
43
|
+
|
|
37
44
|
return {
|
|
45
|
+
// Pattern memory
|
|
38
46
|
winningPatterns: data.winningPatterns || [],
|
|
39
47
|
losingPatterns: data.losingPatterns || [],
|
|
48
|
+
|
|
49
|
+
// Optimization history
|
|
40
50
|
optimizations: data.optimizations || [],
|
|
51
|
+
|
|
52
|
+
// Symbol-specific data (NQ, ES, etc.)
|
|
53
|
+
symbols: data.symbols || {},
|
|
54
|
+
|
|
55
|
+
// Full session history (last 30 days)
|
|
56
|
+
sessions: sessions,
|
|
57
|
+
|
|
58
|
+
// Hourly performance analysis
|
|
59
|
+
hourlyStats: data.hourlyStats || {},
|
|
60
|
+
|
|
61
|
+
// Day of week analysis
|
|
62
|
+
dayOfWeekStats: data.dayOfWeekStats || {},
|
|
63
|
+
|
|
64
|
+
// Strategy behavior profile
|
|
65
|
+
strategyProfile: data.strategyProfile || {
|
|
66
|
+
bestHours: [],
|
|
67
|
+
worstHours: [],
|
|
68
|
+
avgWinStreak: 0,
|
|
69
|
+
avgLossStreak: 0,
|
|
70
|
+
preferredConditions: null
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Lifetime stats
|
|
41
74
|
totalSessions: data.totalSessions || 0,
|
|
42
75
|
totalTrades: data.totalTrades || 0,
|
|
43
76
|
totalWins: data.totalWins || 0,
|
|
44
77
|
totalLosses: data.totalLosses || 0,
|
|
45
78
|
lifetimePnL: data.lifetimePnL || 0,
|
|
46
|
-
|
|
79
|
+
|
|
80
|
+
lastUpdated: data.lastUpdated || null,
|
|
81
|
+
firstSession: data.firstSession || null
|
|
47
82
|
};
|
|
48
83
|
}
|
|
49
84
|
} catch (e) {
|
|
@@ -54,18 +89,137 @@ const loadLearningData = () => {
|
|
|
54
89
|
winningPatterns: [],
|
|
55
90
|
losingPatterns: [],
|
|
56
91
|
optimizations: [],
|
|
92
|
+
symbols: {},
|
|
93
|
+
sessions: [],
|
|
94
|
+
hourlyStats: {},
|
|
95
|
+
dayOfWeekStats: {},
|
|
96
|
+
strategyProfile: {
|
|
97
|
+
bestHours: [],
|
|
98
|
+
worstHours: [],
|
|
99
|
+
avgWinStreak: 0,
|
|
100
|
+
avgLossStreak: 0,
|
|
101
|
+
preferredConditions: null
|
|
102
|
+
},
|
|
57
103
|
totalSessions: 0,
|
|
58
104
|
totalTrades: 0,
|
|
59
105
|
totalWins: 0,
|
|
60
106
|
totalLosses: 0,
|
|
61
107
|
lifetimePnL: 0,
|
|
62
|
-
lastUpdated: null
|
|
108
|
+
lastUpdated: null,
|
|
109
|
+
firstSession: null
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get or create symbol data structure
|
|
115
|
+
*/
|
|
116
|
+
const getSymbolData = (symbolName) => {
|
|
117
|
+
const data = loadLearningData();
|
|
118
|
+
if (!data.symbols[symbolName]) {
|
|
119
|
+
return {
|
|
120
|
+
name: symbolName,
|
|
121
|
+
levels: [], // Key price levels
|
|
122
|
+
sessions: [], // Trading sessions history
|
|
123
|
+
patterns: [], // Symbol-specific patterns
|
|
124
|
+
stats: {
|
|
125
|
+
trades: 0,
|
|
126
|
+
wins: 0,
|
|
127
|
+
losses: 0,
|
|
128
|
+
pnl: 0
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return data.symbols[symbolName];
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Record a key price level for a symbol
|
|
137
|
+
* Called when trades happen at significant levels
|
|
138
|
+
*/
|
|
139
|
+
const recordPriceLevel = (symbolName, price, type, outcome) => {
|
|
140
|
+
// type: 'support', 'resistance', 'breakout', 'rejection'
|
|
141
|
+
// outcome: 'win', 'loss', 'neutral'
|
|
142
|
+
|
|
143
|
+
const level = {
|
|
144
|
+
price: Math.round(price * 4) / 4, // Round to nearest 0.25
|
|
145
|
+
type,
|
|
146
|
+
outcome,
|
|
147
|
+
timestamp: Date.now(),
|
|
148
|
+
date: new Date().toISOString().split('T')[0],
|
|
149
|
+
hour: new Date().getHours()
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (!supervisorState.currentSymbol) {
|
|
153
|
+
supervisorState.currentSymbol = symbolName;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!supervisorState.symbolLevels) {
|
|
157
|
+
supervisorState.symbolLevels = [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check if level already exists (within 2 ticks)
|
|
161
|
+
const tickSize = symbolName.includes('NQ') ? 0.25 : 0.25;
|
|
162
|
+
const existing = supervisorState.symbolLevels.find(l =>
|
|
163
|
+
Math.abs(l.price - level.price) <= tickSize * 2
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (existing) {
|
|
167
|
+
// Update existing level
|
|
168
|
+
existing.touches = (existing.touches || 1) + 1;
|
|
169
|
+
existing.lastTouch = Date.now();
|
|
170
|
+
existing.outcomes = existing.outcomes || [];
|
|
171
|
+
existing.outcomes.push(outcome);
|
|
172
|
+
} else {
|
|
173
|
+
// New level
|
|
174
|
+
level.touches = 1;
|
|
175
|
+
level.outcomes = [outcome];
|
|
176
|
+
supervisorState.symbolLevels.push(level);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Analyze current price against known levels
|
|
182
|
+
* Returns nearby important levels
|
|
183
|
+
*/
|
|
184
|
+
const analyzeNearbyLevels = (symbolName, currentPrice) => {
|
|
185
|
+
const data = loadLearningData();
|
|
186
|
+
const symbolData = data.symbols[symbolName];
|
|
187
|
+
|
|
188
|
+
if (!symbolData || !symbolData.levels || symbolData.levels.length === 0) {
|
|
189
|
+
return { nearbyLevels: [], message: 'No historical levels' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const tickSize = symbolName.includes('NQ') ? 0.25 : 0.25;
|
|
193
|
+
const range = tickSize * 20; // Look within 20 ticks
|
|
194
|
+
|
|
195
|
+
const nearbyLevels = symbolData.levels
|
|
196
|
+
.filter(l => Math.abs(l.price - currentPrice) <= range)
|
|
197
|
+
.sort((a, b) => Math.abs(a.price - currentPrice) - Math.abs(b.price - currentPrice))
|
|
198
|
+
.slice(0, 5) // Top 5 nearest levels
|
|
199
|
+
.map(l => {
|
|
200
|
+
const winRate = l.outcomes ?
|
|
201
|
+
l.outcomes.filter(o => o === 'win').length / l.outcomes.length : 0;
|
|
202
|
+
return {
|
|
203
|
+
price: l.price,
|
|
204
|
+
distance: Math.round((l.price - currentPrice) / tickSize),
|
|
205
|
+
type: l.type,
|
|
206
|
+
touches: l.touches || 1,
|
|
207
|
+
winRate: Math.round(winRate * 100),
|
|
208
|
+
direction: l.price > currentPrice ? 'above' : 'below'
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
nearbyLevels,
|
|
214
|
+
message: nearbyLevels.length > 0 ?
|
|
215
|
+
`${nearbyLevels.length} known levels nearby` :
|
|
216
|
+
'No known levels nearby'
|
|
63
217
|
};
|
|
64
218
|
};
|
|
65
219
|
|
|
66
220
|
/**
|
|
67
221
|
* Save learning data to disk
|
|
68
|
-
*
|
|
222
|
+
* Full memory of strategy behavior over 1 month
|
|
69
223
|
*/
|
|
70
224
|
const saveLearningData = () => {
|
|
71
225
|
try {
|
|
@@ -76,24 +230,137 @@ const saveLearningData = () => {
|
|
|
76
230
|
// Load existing data first
|
|
77
231
|
const existing = loadLearningData();
|
|
78
232
|
|
|
79
|
-
|
|
233
|
+
const now = new Date();
|
|
234
|
+
const currentHour = now.getHours();
|
|
235
|
+
const currentDay = now.getDay(); // 0=Sunday, 1=Monday, etc.
|
|
236
|
+
const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
|
237
|
+
|
|
238
|
+
// Merge symbol data
|
|
239
|
+
const symbols = { ...existing.symbols };
|
|
240
|
+
if (supervisorState.currentSymbol) {
|
|
241
|
+
const sym = supervisorState.currentSymbol;
|
|
242
|
+
if (!symbols[sym]) {
|
|
243
|
+
symbols[sym] = {
|
|
244
|
+
name: sym,
|
|
245
|
+
levels: [],
|
|
246
|
+
sessions: [],
|
|
247
|
+
stats: { trades: 0, wins: 0, losses: 0, pnl: 0 },
|
|
248
|
+
hourlyStats: {},
|
|
249
|
+
dayOfWeekStats: {}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Merge levels - keep last 50 most important
|
|
254
|
+
const existingLevels = symbols[sym].levels || [];
|
|
255
|
+
const newLevels = supervisorState.symbolLevels || [];
|
|
256
|
+
symbols[sym].levels = mergeLevels(existingLevels, newLevels, 50);
|
|
257
|
+
|
|
258
|
+
// Add current session summary
|
|
259
|
+
symbols[sym].sessions.push({
|
|
260
|
+
date: now.toISOString(),
|
|
261
|
+
hour: currentHour,
|
|
262
|
+
dayOfWeek: dayNames[currentDay],
|
|
263
|
+
trades: supervisorState.performance.trades,
|
|
264
|
+
wins: supervisorState.performance.wins,
|
|
265
|
+
losses: supervisorState.performance.losses,
|
|
266
|
+
pnl: supervisorState.performance.totalPnL,
|
|
267
|
+
levelsWorked: newLevels.length,
|
|
268
|
+
maxWinStreak: supervisorState.performance.maxWinStreak,
|
|
269
|
+
maxLossStreak: supervisorState.performance.maxLossStreak
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Keep only last 31 days of sessions per symbol
|
|
273
|
+
const oneMonthAgo = Date.now() - (31 * 24 * 60 * 60 * 1000);
|
|
274
|
+
symbols[sym].sessions = symbols[sym].sessions.filter(s =>
|
|
275
|
+
new Date(s.date).getTime() > oneMonthAgo
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Update symbol stats
|
|
279
|
+
symbols[sym].stats.trades += supervisorState.performance.trades;
|
|
280
|
+
symbols[sym].stats.wins += supervisorState.performance.wins;
|
|
281
|
+
symbols[sym].stats.losses += supervisorState.performance.losses;
|
|
282
|
+
symbols[sym].stats.pnl += supervisorState.performance.totalPnL;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Update hourly stats (which hours perform best)
|
|
286
|
+
const hourlyStats = { ...existing.hourlyStats };
|
|
287
|
+
const hourKey = String(currentHour);
|
|
288
|
+
if (!hourlyStats[hourKey]) {
|
|
289
|
+
hourlyStats[hourKey] = { trades: 0, wins: 0, losses: 0, pnl: 0 };
|
|
290
|
+
}
|
|
291
|
+
hourlyStats[hourKey].trades += supervisorState.performance.trades;
|
|
292
|
+
hourlyStats[hourKey].wins += supervisorState.performance.wins;
|
|
293
|
+
hourlyStats[hourKey].losses += supervisorState.performance.losses;
|
|
294
|
+
hourlyStats[hourKey].pnl += supervisorState.performance.totalPnL;
|
|
295
|
+
|
|
296
|
+
// Update day of week stats
|
|
297
|
+
const dayOfWeekStats = { ...existing.dayOfWeekStats };
|
|
298
|
+
const dayKey = dayNames[currentDay];
|
|
299
|
+
if (!dayOfWeekStats[dayKey]) {
|
|
300
|
+
dayOfWeekStats[dayKey] = { trades: 0, wins: 0, losses: 0, pnl: 0 };
|
|
301
|
+
}
|
|
302
|
+
dayOfWeekStats[dayKey].trades += supervisorState.performance.trades;
|
|
303
|
+
dayOfWeekStats[dayKey].wins += supervisorState.performance.wins;
|
|
304
|
+
dayOfWeekStats[dayKey].losses += supervisorState.performance.losses;
|
|
305
|
+
dayOfWeekStats[dayKey].pnl += supervisorState.performance.totalPnL;
|
|
306
|
+
|
|
307
|
+
// Build strategy profile from data
|
|
308
|
+
const strategyProfile = buildStrategyProfile(hourlyStats, dayOfWeekStats, existing.sessions);
|
|
309
|
+
|
|
310
|
+
// Current session record
|
|
311
|
+
const currentSession = {
|
|
312
|
+
date: now.toISOString(),
|
|
313
|
+
symbol: supervisorState.currentSymbol,
|
|
314
|
+
hour: currentHour,
|
|
315
|
+
dayOfWeek: dayNames[currentDay],
|
|
316
|
+
trades: supervisorState.performance.trades,
|
|
317
|
+
wins: supervisorState.performance.wins,
|
|
318
|
+
losses: supervisorState.performance.losses,
|
|
319
|
+
pnl: supervisorState.performance.totalPnL,
|
|
320
|
+
maxWinStreak: supervisorState.performance.maxWinStreak,
|
|
321
|
+
maxLossStreak: supervisorState.performance.maxLossStreak,
|
|
322
|
+
optimizationsApplied: supervisorState.optimizations.length,
|
|
323
|
+
levelsLearned: (supervisorState.symbolLevels || []).length
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Merge sessions (keep 1 month)
|
|
327
|
+
const oneMonthAgo = Date.now() - (31 * 24 * 60 * 60 * 1000);
|
|
328
|
+
const sessions = [...existing.sessions, currentSession].filter(s =>
|
|
329
|
+
new Date(s.date).getTime() > oneMonthAgo
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Build data to save
|
|
80
333
|
const dataToSave = {
|
|
81
|
-
// Patterns - keep last 100 of each type
|
|
334
|
+
// Patterns - keep last 100 of each type
|
|
82
335
|
winningPatterns: mergePatterns(existing.winningPatterns, supervisorState.winningPatterns, 100),
|
|
83
336
|
losingPatterns: mergePatterns(existing.losingPatterns, supervisorState.losingPatterns, 100),
|
|
84
337
|
|
|
85
338
|
// Optimizations history - keep last 50
|
|
86
339
|
optimizations: [...existing.optimizations, ...supervisorState.optimizations].slice(-50),
|
|
87
340
|
|
|
341
|
+
// Symbol-specific data
|
|
342
|
+
symbols,
|
|
343
|
+
|
|
344
|
+
// Full session history (1 month)
|
|
345
|
+
sessions,
|
|
346
|
+
|
|
347
|
+
// Performance by hour and day
|
|
348
|
+
hourlyStats,
|
|
349
|
+
dayOfWeekStats,
|
|
350
|
+
|
|
351
|
+
// Strategy behavior profile
|
|
352
|
+
strategyProfile,
|
|
353
|
+
|
|
88
354
|
// Lifetime stats
|
|
89
|
-
totalSessions: existing.totalSessions +
|
|
355
|
+
totalSessions: existing.totalSessions + 1,
|
|
90
356
|
totalTrades: existing.totalTrades + supervisorState.performance.trades,
|
|
91
357
|
totalWins: existing.totalWins + supervisorState.performance.wins,
|
|
92
358
|
totalLosses: existing.totalLosses + supervisorState.performance.losses,
|
|
93
359
|
lifetimePnL: existing.lifetimePnL + supervisorState.performance.totalPnL,
|
|
94
360
|
|
|
95
361
|
// Metadata
|
|
96
|
-
|
|
362
|
+
firstSession: existing.firstSession || now.toISOString(),
|
|
363
|
+
lastUpdated: now.toISOString()
|
|
97
364
|
};
|
|
98
365
|
|
|
99
366
|
fs.writeFileSync(LEARNING_FILE, JSON.stringify(dataToSave, null, 2));
|
|
@@ -103,6 +370,58 @@ const saveLearningData = () => {
|
|
|
103
370
|
}
|
|
104
371
|
};
|
|
105
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Build strategy profile from historical data
|
|
375
|
+
* Identifies best/worst hours, days, and patterns
|
|
376
|
+
*/
|
|
377
|
+
const buildStrategyProfile = (hourlyStats, dayOfWeekStats, sessions) => {
|
|
378
|
+
// Find best and worst hours
|
|
379
|
+
const hours = Object.entries(hourlyStats)
|
|
380
|
+
.map(([hour, stats]) => ({
|
|
381
|
+
hour: parseInt(hour),
|
|
382
|
+
winRate: stats.trades > 0 ? (stats.wins / stats.trades) * 100 : 0,
|
|
383
|
+
pnl: stats.pnl,
|
|
384
|
+
trades: stats.trades
|
|
385
|
+
}))
|
|
386
|
+
.filter(h => h.trades >= 3) // Need at least 3 trades to be significant
|
|
387
|
+
.sort((a, b) => b.winRate - a.winRate);
|
|
388
|
+
|
|
389
|
+
const bestHours = hours.slice(0, 3).map(h => h.hour);
|
|
390
|
+
const worstHours = hours.slice(-3).map(h => h.hour);
|
|
391
|
+
|
|
392
|
+
// Find best and worst days
|
|
393
|
+
const days = Object.entries(dayOfWeekStats)
|
|
394
|
+
.map(([day, stats]) => ({
|
|
395
|
+
day,
|
|
396
|
+
winRate: stats.trades > 0 ? (stats.wins / stats.trades) * 100 : 0,
|
|
397
|
+
pnl: stats.pnl,
|
|
398
|
+
trades: stats.trades
|
|
399
|
+
}))
|
|
400
|
+
.filter(d => d.trades >= 3)
|
|
401
|
+
.sort((a, b) => b.winRate - a.winRate);
|
|
402
|
+
|
|
403
|
+
const bestDays = days.slice(0, 2).map(d => d.day);
|
|
404
|
+
const worstDays = days.slice(-2).map(d => d.day);
|
|
405
|
+
|
|
406
|
+
// Calculate average streaks from sessions
|
|
407
|
+
let totalWinStreaks = 0, totalLossStreaks = 0, streakCount = 0;
|
|
408
|
+
for (const session of sessions) {
|
|
409
|
+
if (session.maxWinStreak) totalWinStreaks += session.maxWinStreak;
|
|
410
|
+
if (session.maxLossStreak) totalLossStreaks += session.maxLossStreak;
|
|
411
|
+
streakCount++;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
bestHours,
|
|
416
|
+
worstHours,
|
|
417
|
+
bestDays,
|
|
418
|
+
worstDays,
|
|
419
|
+
avgWinStreak: streakCount > 0 ? Math.round(totalWinStreaks / streakCount * 10) / 10 : 0,
|
|
420
|
+
avgLossStreak: streakCount > 0 ? Math.round(totalLossStreaks / streakCount * 10) / 10 : 0,
|
|
421
|
+
totalSessionsAnalyzed: sessions.length
|
|
422
|
+
};
|
|
423
|
+
};
|
|
424
|
+
|
|
106
425
|
/**
|
|
107
426
|
* Merge pattern arrays, keeping most recent unique entries
|
|
108
427
|
*/
|
|
@@ -116,6 +435,50 @@ const mergePatterns = (existing, current, maxCount) => {
|
|
|
116
435
|
return merged.slice(0, maxCount);
|
|
117
436
|
};
|
|
118
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Merge price levels, prioritizing most touched and most recent
|
|
440
|
+
*/
|
|
441
|
+
const mergeLevels = (existing, current, maxCount) => {
|
|
442
|
+
const merged = [...existing];
|
|
443
|
+
|
|
444
|
+
for (const level of current) {
|
|
445
|
+
const tickSize = 0.25;
|
|
446
|
+
const existingIdx = merged.findIndex(l =>
|
|
447
|
+
Math.abs(l.price - level.price) <= tickSize * 2
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
if (existingIdx >= 0) {
|
|
451
|
+
// Update existing level
|
|
452
|
+
merged[existingIdx].touches = (merged[existingIdx].touches || 1) + (level.touches || 1);
|
|
453
|
+
merged[existingIdx].lastTouch = Math.max(merged[existingIdx].lastTouch || 0, level.lastTouch || level.timestamp || 0);
|
|
454
|
+
merged[existingIdx].outcomes = [...(merged[existingIdx].outcomes || []), ...(level.outcomes || [])].slice(-20);
|
|
455
|
+
} else {
|
|
456
|
+
merged.push(level);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Sort by importance (touches * recency)
|
|
461
|
+
const now = Date.now();
|
|
462
|
+
merged.sort((a, b) => {
|
|
463
|
+
const scoreA = (a.touches || 1) * (1 / (1 + (now - (a.lastTouch || a.timestamp || 0)) / 86400000));
|
|
464
|
+
const scoreB = (b.touches || 1) * (1 / (1 + (now - (b.lastTouch || b.timestamp || 0)) / 86400000));
|
|
465
|
+
return scoreB - scoreA;
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
return merged.slice(0, maxCount);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* DEPRECATED - moved inline
|
|
473
|
+
*/
|
|
474
|
+
const mergePatternsDEP = (existing, current, maxCount) => {
|
|
475
|
+
const merged = [...existing, ...current];
|
|
476
|
+
merged.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
477
|
+
|
|
478
|
+
// Keep only maxCount most recent
|
|
479
|
+
return merged.slice(0, maxCount);
|
|
480
|
+
};
|
|
481
|
+
|
|
119
482
|
// Singleton supervisor state
|
|
120
483
|
let supervisorState = {
|
|
121
484
|
active: false,
|
|
@@ -124,11 +487,17 @@ let supervisorState = {
|
|
|
124
487
|
service: null,
|
|
125
488
|
accountId: null,
|
|
126
489
|
|
|
490
|
+
// Current symbol being traded
|
|
491
|
+
currentSymbol: null,
|
|
492
|
+
|
|
127
493
|
// Real-time data (synced with strategy)
|
|
128
494
|
ticks: [],
|
|
129
495
|
signals: [],
|
|
130
496
|
trades: [],
|
|
131
497
|
|
|
498
|
+
// Symbol-specific levels learned this session
|
|
499
|
+
symbolLevels: [],
|
|
500
|
+
|
|
132
501
|
// Learning data
|
|
133
502
|
winningPatterns: [],
|
|
134
503
|
losingPatterns: [],
|
|
@@ -163,7 +532,16 @@ let supervisorState = {
|
|
|
163
532
|
// Behavior history for graph (action over time)
|
|
164
533
|
// Values: 0=PAUSE, 1=CAUTIOUS, 2=NORMAL, 3=AGGRESSIVE
|
|
165
534
|
behaviorHistory: [],
|
|
166
|
-
behaviorStartTime: null
|
|
535
|
+
behaviorStartTime: null,
|
|
536
|
+
|
|
537
|
+
// Lifetime stats loaded from previous sessions
|
|
538
|
+
lifetimeStats: null,
|
|
539
|
+
|
|
540
|
+
// Previous sessions memory (loaded on init)
|
|
541
|
+
previousSessions: [],
|
|
542
|
+
|
|
543
|
+
// Hourly performance tracking
|
|
544
|
+
hourlyPerformance: {}
|
|
167
545
|
};
|
|
168
546
|
|
|
169
547
|
// Analysis interval
|
|
@@ -304,16 +682,96 @@ const feedSignal = (signal) => {
|
|
|
304
682
|
/**
|
|
305
683
|
* Feed trade result (called when a trade completes)
|
|
306
684
|
* This is where LEARNING happens
|
|
685
|
+
*
|
|
686
|
+
* Captures CONTEXT that API doesn't provide:
|
|
687
|
+
* - Market conditions before entry (volatility, trend, volume)
|
|
688
|
+
* - Price action patterns
|
|
689
|
+
* - Time-based context (hour, day, session)
|
|
690
|
+
* - AI state at time of trade
|
|
691
|
+
* - Price levels interaction
|
|
307
692
|
*/
|
|
308
693
|
const feedTradeResult = (trade) => {
|
|
309
694
|
if (!supervisorState.active) return;
|
|
310
695
|
|
|
696
|
+
const now = Date.now();
|
|
697
|
+
const currentHour = new Date().getHours();
|
|
698
|
+
const currentDay = new Date().getDay();
|
|
699
|
+
const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
|
700
|
+
|
|
701
|
+
// Get ticks before trade for context analysis
|
|
702
|
+
const ticksBefore = supervisorState.ticks.slice(-100);
|
|
703
|
+
const signalUsed = supervisorState.signals[supervisorState.signals.length - 1] || null;
|
|
704
|
+
|
|
705
|
+
// ========== CAPTURE MARKET CONTEXT (NOT IN API) ==========
|
|
706
|
+
const marketContext = {
|
|
707
|
+
// Price action analysis
|
|
708
|
+
priceAction: analyzePriceAction(ticksBefore),
|
|
709
|
+
|
|
710
|
+
// Volatility at entry
|
|
711
|
+
volatility: calculateVolatility(ticksBefore),
|
|
712
|
+
|
|
713
|
+
// Volume profile
|
|
714
|
+
volumeProfile: analyzeVolume(ticksBefore),
|
|
715
|
+
|
|
716
|
+
// Trend strength (using last 50 vs last 20 ticks)
|
|
717
|
+
trendStrength: calculateTrendStrength(ticksBefore),
|
|
718
|
+
|
|
719
|
+
// Price range in last N ticks
|
|
720
|
+
recentRange: calculateRecentRange(ticksBefore, 50),
|
|
721
|
+
|
|
722
|
+
// Speed of price movement
|
|
723
|
+
priceVelocity: calculatePriceVelocity(ticksBefore, 20)
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
// ========== CAPTURE TIME CONTEXT (NOT IN API) ==========
|
|
727
|
+
const timeContext = {
|
|
728
|
+
hour: currentHour,
|
|
729
|
+
dayOfWeek: dayNames[currentDay],
|
|
730
|
+
dayNumber: currentDay,
|
|
731
|
+
// Trading session (US market hours)
|
|
732
|
+
session: getMarketSession(currentHour),
|
|
733
|
+
// Minutes since session open
|
|
734
|
+
minutesSinceOpen: getMinutesSinceOpen(currentHour),
|
|
735
|
+
timestamp: now
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
// ========== CAPTURE AI STATE (NOT IN API) ==========
|
|
739
|
+
const aiContext = {
|
|
740
|
+
action: supervisorState.currentAdvice.action,
|
|
741
|
+
sizeMultiplier: supervisorState.currentAdvice.sizeMultiplier,
|
|
742
|
+
reason: supervisorState.currentAdvice.reason,
|
|
743
|
+
currentWinStreak: supervisorState.performance.winStreak,
|
|
744
|
+
currentLossStreak: supervisorState.performance.lossStreak,
|
|
745
|
+
sessionPnL: supervisorState.performance.totalPnL,
|
|
746
|
+
sessionTrades: supervisorState.performance.trades
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// ========== CAPTURE PRICE LEVEL INTERACTION ==========
|
|
750
|
+
const entryPrice = trade.price || signalUsed?.entry;
|
|
751
|
+
const levelContext = entryPrice ? analyzePriceLevelInteraction(entryPrice, ticksBefore) : null;
|
|
752
|
+
|
|
753
|
+
// Build enriched trade data
|
|
311
754
|
const tradeData = {
|
|
312
755
|
...trade,
|
|
313
|
-
timestamp:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
signalUsed:
|
|
756
|
+
timestamp: now,
|
|
757
|
+
|
|
758
|
+
// Signal that generated this trade
|
|
759
|
+
signalUsed: signalUsed ? {
|
|
760
|
+
confidence: signalUsed.confidence,
|
|
761
|
+
entry: signalUsed.entry,
|
|
762
|
+
stopLoss: signalUsed.stopLoss,
|
|
763
|
+
takeProfit: signalUsed.takeProfit,
|
|
764
|
+
direction: signalUsed.direction
|
|
765
|
+
} : null,
|
|
766
|
+
|
|
767
|
+
// Context NOT available in API
|
|
768
|
+
marketContext,
|
|
769
|
+
timeContext,
|
|
770
|
+
aiContext,
|
|
771
|
+
levelContext,
|
|
772
|
+
|
|
773
|
+
// Store symbol for level learning
|
|
774
|
+
symbol: trade.symbol || supervisorState.currentSymbol
|
|
317
775
|
};
|
|
318
776
|
|
|
319
777
|
supervisorState.trades.push(tradeData);
|
|
@@ -323,6 +781,9 @@ const feedTradeResult = (trade) => {
|
|
|
323
781
|
perf.trades++;
|
|
324
782
|
perf.totalPnL += trade.pnl || 0;
|
|
325
783
|
|
|
784
|
+
// Determine outcome
|
|
785
|
+
const outcome = trade.pnl > 0 ? 'win' : trade.pnl < 0 ? 'loss' : 'breakeven';
|
|
786
|
+
|
|
326
787
|
if (trade.pnl > 0) {
|
|
327
788
|
perf.wins++;
|
|
328
789
|
perf.winStreak++;
|
|
@@ -341,6 +802,22 @@ const feedTradeResult = (trade) => {
|
|
|
341
802
|
learnFromTrade(tradeData, 'loss');
|
|
342
803
|
}
|
|
343
804
|
|
|
805
|
+
// Record price level with outcome (for future reference)
|
|
806
|
+
if (entryPrice && tradeData.symbol) {
|
|
807
|
+
const levelType = determineLevelType(entryPrice, ticksBefore);
|
|
808
|
+
recordPriceLevel(tradeData.symbol, entryPrice, levelType, outcome);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Update hourly performance tracking
|
|
812
|
+
const hourKey = String(currentHour);
|
|
813
|
+
if (!supervisorState.hourlyPerformance[hourKey]) {
|
|
814
|
+
supervisorState.hourlyPerformance[hourKey] = { trades: 0, wins: 0, losses: 0, pnl: 0 };
|
|
815
|
+
}
|
|
816
|
+
supervisorState.hourlyPerformance[hourKey].trades++;
|
|
817
|
+
if (trade.pnl > 0) supervisorState.hourlyPerformance[hourKey].wins++;
|
|
818
|
+
if (trade.pnl < 0) supervisorState.hourlyPerformance[hourKey].losses++;
|
|
819
|
+
supervisorState.hourlyPerformance[hourKey].pnl += trade.pnl || 0;
|
|
820
|
+
|
|
344
821
|
// Update drawdown
|
|
345
822
|
if (perf.totalPnL > perf.peakPnL) {
|
|
346
823
|
perf.peakPnL = perf.totalPnL;
|
|
@@ -356,44 +833,275 @@ const feedTradeResult = (trade) => {
|
|
|
356
833
|
}
|
|
357
834
|
};
|
|
358
835
|
|
|
836
|
+
/**
|
|
837
|
+
* Calculate trend strength from ticks
|
|
838
|
+
* Compares short-term vs medium-term trend
|
|
839
|
+
*/
|
|
840
|
+
const calculateTrendStrength = (ticks) => {
|
|
841
|
+
if (!ticks || ticks.length < 20) return { strength: 0, direction: 'unknown' };
|
|
842
|
+
|
|
843
|
+
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
844
|
+
if (prices.length < 20) return { strength: 0, direction: 'unknown' };
|
|
845
|
+
|
|
846
|
+
// Short-term trend (last 20)
|
|
847
|
+
const shortTerm = prices.slice(-20);
|
|
848
|
+
const shortChange = shortTerm[shortTerm.length - 1] - shortTerm[0];
|
|
849
|
+
|
|
850
|
+
// Medium-term trend (last 50 or all)
|
|
851
|
+
const mediumTerm = prices.slice(-50);
|
|
852
|
+
const mediumChange = mediumTerm[mediumTerm.length - 1] - mediumTerm[0];
|
|
853
|
+
|
|
854
|
+
// Strength = how aligned are short and medium trends
|
|
855
|
+
const aligned = (shortChange > 0 && mediumChange > 0) || (shortChange < 0 && mediumChange < 0);
|
|
856
|
+
const avgRange = Math.abs(Math.max(...prices) - Math.min(...prices)) || 1;
|
|
857
|
+
|
|
858
|
+
return {
|
|
859
|
+
strength: aligned ? Math.min(1, (Math.abs(shortChange) + Math.abs(mediumChange)) / avgRange) : 0,
|
|
860
|
+
direction: mediumChange > 0 ? 'bullish' : mediumChange < 0 ? 'bearish' : 'neutral',
|
|
861
|
+
shortTermDirection: shortChange > 0 ? 'up' : shortChange < 0 ? 'down' : 'flat',
|
|
862
|
+
aligned
|
|
863
|
+
};
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Calculate recent price range
|
|
868
|
+
*/
|
|
869
|
+
const calculateRecentRange = (ticks, count) => {
|
|
870
|
+
if (!ticks || ticks.length === 0) return { high: 0, low: 0, range: 0 };
|
|
871
|
+
|
|
872
|
+
const prices = ticks.slice(-count).map(t => t.price).filter(Boolean);
|
|
873
|
+
if (prices.length === 0) return { high: 0, low: 0, range: 0 };
|
|
874
|
+
|
|
875
|
+
const high = Math.max(...prices);
|
|
876
|
+
const low = Math.min(...prices);
|
|
877
|
+
|
|
878
|
+
return { high, low, range: high - low };
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Calculate price velocity (speed of movement)
|
|
883
|
+
*/
|
|
884
|
+
const calculatePriceVelocity = (ticks, count) => {
|
|
885
|
+
if (!ticks || ticks.length < 2) return { velocity: 0, acceleration: 0 };
|
|
886
|
+
|
|
887
|
+
const recentTicks = ticks.slice(-count);
|
|
888
|
+
if (recentTicks.length < 2) return { velocity: 0, acceleration: 0 };
|
|
889
|
+
|
|
890
|
+
const prices = recentTicks.map(t => t.price).filter(Boolean);
|
|
891
|
+
if (prices.length < 2) return { velocity: 0, acceleration: 0 };
|
|
892
|
+
|
|
893
|
+
// Velocity = price change per tick
|
|
894
|
+
const totalChange = prices[prices.length - 1] - prices[0];
|
|
895
|
+
const velocity = totalChange / prices.length;
|
|
896
|
+
|
|
897
|
+
// Acceleration = change in velocity
|
|
898
|
+
const midPoint = Math.floor(prices.length / 2);
|
|
899
|
+
const firstHalfVelocity = (prices[midPoint] - prices[0]) / midPoint;
|
|
900
|
+
const secondHalfVelocity = (prices[prices.length - 1] - prices[midPoint]) / (prices.length - midPoint);
|
|
901
|
+
const acceleration = secondHalfVelocity - firstHalfVelocity;
|
|
902
|
+
|
|
903
|
+
return { velocity, acceleration };
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Get current market session
|
|
908
|
+
*/
|
|
909
|
+
const getMarketSession = (hour) => {
|
|
910
|
+
// US Eastern Time sessions (adjust if needed)
|
|
911
|
+
if (hour >= 9 && hour < 12) return 'morning';
|
|
912
|
+
if (hour >= 12 && hour < 14) return 'midday';
|
|
913
|
+
if (hour >= 14 && hour < 16) return 'afternoon';
|
|
914
|
+
if (hour >= 16 && hour < 18) return 'close';
|
|
915
|
+
if (hour >= 18 || hour < 9) return 'overnight';
|
|
916
|
+
return 'unknown';
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Get minutes since market open (9:30 AM ET)
|
|
921
|
+
*/
|
|
922
|
+
const getMinutesSinceOpen = (hour) => {
|
|
923
|
+
const marketOpenHour = 9;
|
|
924
|
+
const marketOpenMinute = 30;
|
|
925
|
+
const now = new Date();
|
|
926
|
+
const currentMinutes = hour * 60 + now.getMinutes();
|
|
927
|
+
const openMinutes = marketOpenHour * 60 + marketOpenMinute;
|
|
928
|
+
return Math.max(0, currentMinutes - openMinutes);
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Analyze price level interaction
|
|
933
|
+
* Determines if entry was near support/resistance
|
|
934
|
+
*/
|
|
935
|
+
const analyzePriceLevelInteraction = (entryPrice, ticks) => {
|
|
936
|
+
if (!ticks || ticks.length < 20) return null;
|
|
937
|
+
|
|
938
|
+
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
939
|
+
if (prices.length < 20) return null;
|
|
940
|
+
|
|
941
|
+
const high = Math.max(...prices);
|
|
942
|
+
const low = Math.min(...prices);
|
|
943
|
+
const range = high - low || 1;
|
|
944
|
+
|
|
945
|
+
// Position within range (0 = at low, 1 = at high)
|
|
946
|
+
const positionInRange = (entryPrice - low) / range;
|
|
947
|
+
|
|
948
|
+
// Distance to recent high/low
|
|
949
|
+
const distanceToHigh = high - entryPrice;
|
|
950
|
+
const distanceToLow = entryPrice - low;
|
|
951
|
+
|
|
952
|
+
// Determine level type
|
|
953
|
+
let levelType = 'middle';
|
|
954
|
+
if (positionInRange > 0.8) levelType = 'near_high';
|
|
955
|
+
else if (positionInRange < 0.2) levelType = 'near_low';
|
|
956
|
+
else if (positionInRange > 0.6) levelType = 'upper_middle';
|
|
957
|
+
else if (positionInRange < 0.4) levelType = 'lower_middle';
|
|
958
|
+
|
|
959
|
+
return {
|
|
960
|
+
positionInRange: Math.round(positionInRange * 100) / 100,
|
|
961
|
+
distanceToHigh,
|
|
962
|
+
distanceToLow,
|
|
963
|
+
recentHigh: high,
|
|
964
|
+
recentLow: low,
|
|
965
|
+
levelType
|
|
966
|
+
};
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Determine what type of level this price represents
|
|
971
|
+
*/
|
|
972
|
+
const determineLevelType = (price, ticks) => {
|
|
973
|
+
if (!ticks || ticks.length < 10) return 'unknown';
|
|
974
|
+
|
|
975
|
+
const prices = ticks.map(t => t.price).filter(Boolean);
|
|
976
|
+
if (prices.length < 10) return 'unknown';
|
|
977
|
+
|
|
978
|
+
const high = Math.max(...prices);
|
|
979
|
+
const low = Math.min(...prices);
|
|
980
|
+
const range = high - low || 1;
|
|
981
|
+
const tickSize = 0.25;
|
|
982
|
+
|
|
983
|
+
// Check if near recent high (potential resistance)
|
|
984
|
+
if (Math.abs(price - high) <= tickSize * 4) return 'resistance';
|
|
985
|
+
|
|
986
|
+
// Check if near recent low (potential support)
|
|
987
|
+
if (Math.abs(price - low) <= tickSize * 4) return 'support';
|
|
988
|
+
|
|
989
|
+
// Check for breakout (above recent high)
|
|
990
|
+
if (price > high) return 'breakout_high';
|
|
991
|
+
|
|
992
|
+
// Check for breakdown (below recent low)
|
|
993
|
+
if (price < low) return 'breakout_low';
|
|
994
|
+
|
|
995
|
+
return 'middle';
|
|
996
|
+
};
|
|
997
|
+
|
|
359
998
|
/**
|
|
360
999
|
* Learn from a completed trade
|
|
361
1000
|
* Extracts patterns from winning and losing trades
|
|
1001
|
+
*
|
|
1002
|
+
* Stores ENRICHED context that API doesn't provide:
|
|
1003
|
+
* - Market conditions (volatility, trend, velocity)
|
|
1004
|
+
* - Time context (hour, session, day)
|
|
1005
|
+
* - AI state (what action was recommended)
|
|
1006
|
+
* - Price level interaction
|
|
362
1007
|
*/
|
|
363
1008
|
const learnFromTrade = (trade, result) => {
|
|
1009
|
+
// Build comprehensive pattern from trade context
|
|
364
1010
|
const pattern = {
|
|
365
1011
|
timestamp: trade.timestamp,
|
|
366
1012
|
result,
|
|
367
1013
|
pnl: trade.pnl,
|
|
368
1014
|
direction: trade.direction || trade.side,
|
|
1015
|
+
symbol: trade.symbol,
|
|
369
1016
|
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
1017
|
+
// ========== MARKET CONTEXT (from feedTradeResult) ==========
|
|
1018
|
+
marketContext: trade.marketContext || {
|
|
1019
|
+
priceAction: { trend: 'unknown', strength: 0 },
|
|
1020
|
+
volatility: 0,
|
|
1021
|
+
volumeProfile: { trend: 'unknown' },
|
|
1022
|
+
trendStrength: { strength: 0, direction: 'unknown' },
|
|
1023
|
+
priceVelocity: { velocity: 0, acceleration: 0 }
|
|
1024
|
+
},
|
|
1025
|
+
|
|
1026
|
+
// ========== TIME CONTEXT ==========
|
|
1027
|
+
timeContext: trade.timeContext || {
|
|
1028
|
+
hour: new Date().getHours(),
|
|
1029
|
+
dayOfWeek: 'unknown',
|
|
1030
|
+
session: 'unknown'
|
|
1031
|
+
},
|
|
1032
|
+
|
|
1033
|
+
// ========== AI STATE AT TRADE ==========
|
|
1034
|
+
aiContext: trade.aiContext || {
|
|
1035
|
+
action: 'NORMAL',
|
|
1036
|
+
sizeMultiplier: 1.0
|
|
1037
|
+
},
|
|
1038
|
+
|
|
1039
|
+
// ========== PRICE LEVEL ==========
|
|
1040
|
+
levelContext: trade.levelContext || null,
|
|
374
1041
|
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
1042
|
+
// ========== SIGNAL CHARACTERISTICS ==========
|
|
1043
|
+
signal: trade.signalUsed ? {
|
|
1044
|
+
confidence: trade.signalUsed.confidence,
|
|
1045
|
+
entry: trade.signalUsed.entry,
|
|
1046
|
+
stopLoss: trade.signalUsed.stopLoss,
|
|
1047
|
+
takeProfit: trade.signalUsed.takeProfit,
|
|
1048
|
+
direction: trade.signalUsed.direction
|
|
1049
|
+
} : null,
|
|
1050
|
+
|
|
1051
|
+
// ========== DERIVED METRICS ==========
|
|
1052
|
+
derived: {
|
|
1053
|
+
// Was this trade during a "good" hour (based on historical data)?
|
|
1054
|
+
hourlyWinRate: getHourlyWinRate(trade.timeContext?.hour),
|
|
1055
|
+
// Was AI cautious or aggressive?
|
|
1056
|
+
aiWasCautious: trade.aiContext?.action === 'CAUTIOUS' || trade.aiContext?.action === 'PAUSE',
|
|
1057
|
+
// Was entry near key level?
|
|
1058
|
+
nearKeyLevel: trade.levelContext?.levelType === 'support' || trade.levelContext?.levelType === 'resistance',
|
|
1059
|
+
// High volatility trade?
|
|
1060
|
+
highVolatility: (trade.marketContext?.volatility || 0) > 0.002,
|
|
1061
|
+
// Strong trend alignment?
|
|
1062
|
+
trendAligned: trade.marketContext?.trendStrength?.aligned || false
|
|
1063
|
+
}
|
|
380
1064
|
};
|
|
381
1065
|
|
|
382
1066
|
if (result === 'win') {
|
|
383
1067
|
supervisorState.winningPatterns.push(pattern);
|
|
384
|
-
// Keep last
|
|
385
|
-
if (supervisorState.winningPatterns.length >
|
|
386
|
-
supervisorState.winningPatterns = supervisorState.winningPatterns.slice(-
|
|
1068
|
+
// Keep last 100 winning patterns (increased for better learning)
|
|
1069
|
+
if (supervisorState.winningPatterns.length > 100) {
|
|
1070
|
+
supervisorState.winningPatterns = supervisorState.winningPatterns.slice(-100);
|
|
387
1071
|
}
|
|
388
1072
|
} else {
|
|
389
1073
|
supervisorState.losingPatterns.push(pattern);
|
|
390
|
-
// Keep last
|
|
391
|
-
if (supervisorState.losingPatterns.length >
|
|
392
|
-
supervisorState.losingPatterns = supervisorState.losingPatterns.slice(-
|
|
1074
|
+
// Keep last 100 losing patterns
|
|
1075
|
+
if (supervisorState.losingPatterns.length > 100) {
|
|
1076
|
+
supervisorState.losingPatterns = supervisorState.losingPatterns.slice(-100);
|
|
393
1077
|
}
|
|
394
1078
|
}
|
|
395
1079
|
};
|
|
396
1080
|
|
|
1081
|
+
/**
|
|
1082
|
+
* Get historical win rate for a specific hour
|
|
1083
|
+
* Returns null if not enough data
|
|
1084
|
+
*/
|
|
1085
|
+
const getHourlyWinRate = (hour) => {
|
|
1086
|
+
if (hour === null || hour === undefined) return null;
|
|
1087
|
+
|
|
1088
|
+
// Check session data first
|
|
1089
|
+
const hourKey = String(hour);
|
|
1090
|
+
const sessionHourly = supervisorState.hourlyPerformance[hourKey];
|
|
1091
|
+
if (sessionHourly && sessionHourly.trades >= 3) {
|
|
1092
|
+
return sessionHourly.wins / sessionHourly.trades;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Check historical data
|
|
1096
|
+
const saved = loadLearningData();
|
|
1097
|
+
const historicalHourly = saved.hourlyStats?.[hourKey];
|
|
1098
|
+
if (historicalHourly && historicalHourly.trades >= 5) {
|
|
1099
|
+
return historicalHourly.wins / historicalHourly.trades;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
return null;
|
|
1103
|
+
};
|
|
1104
|
+
|
|
397
1105
|
/**
|
|
398
1106
|
* Analyze price action from ticks
|
|
399
1107
|
*/
|
|
@@ -461,6 +1169,8 @@ const calculateVolatility = (ticks) => {
|
|
|
461
1169
|
/**
|
|
462
1170
|
* Main analysis and optimization loop
|
|
463
1171
|
* Called periodically and after significant events
|
|
1172
|
+
*
|
|
1173
|
+
* Uses BOTH session data AND historical data for decisions
|
|
464
1174
|
*/
|
|
465
1175
|
const analyzeAndOptimize = async () => {
|
|
466
1176
|
if (!supervisorState.active || supervisorState.agents.length === 0) return;
|
|
@@ -470,6 +1180,51 @@ const analyzeAndOptimize = async () => {
|
|
|
470
1180
|
// Skip if not enough data
|
|
471
1181
|
if (perf.trades < 3) return;
|
|
472
1182
|
|
|
1183
|
+
// Load historical data for context
|
|
1184
|
+
const historicalData = loadLearningData();
|
|
1185
|
+
const strategyProfile = historicalData.strategyProfile || {};
|
|
1186
|
+
|
|
1187
|
+
// Get current time context
|
|
1188
|
+
const currentHour = new Date().getHours();
|
|
1189
|
+
const currentDay = new Date().getDay();
|
|
1190
|
+
const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
|
1191
|
+
|
|
1192
|
+
// ========== HISTORICAL INSIGHTS ==========
|
|
1193
|
+
const historicalInsights = {
|
|
1194
|
+
// Is current hour historically good or bad?
|
|
1195
|
+
currentHourStats: historicalData.hourlyStats?.[String(currentHour)] || null,
|
|
1196
|
+
isGoodHour: (strategyProfile.bestHours || []).includes(currentHour),
|
|
1197
|
+
isBadHour: (strategyProfile.worstHours || []).includes(currentHour),
|
|
1198
|
+
|
|
1199
|
+
// Is current day historically good or bad?
|
|
1200
|
+
currentDayStats: historicalData.dayOfWeekStats?.[dayNames[currentDay]] || null,
|
|
1201
|
+
isGoodDay: (strategyProfile.bestDays || []).includes(dayNames[currentDay]),
|
|
1202
|
+
isBadDay: (strategyProfile.worstDays || []).includes(dayNames[currentDay]),
|
|
1203
|
+
|
|
1204
|
+
// Historical averages to compare against
|
|
1205
|
+
avgWinStreak: strategyProfile.avgWinStreak || 0,
|
|
1206
|
+
avgLossStreak: strategyProfile.avgLossStreak || 0,
|
|
1207
|
+
|
|
1208
|
+
// Lifetime context
|
|
1209
|
+
lifetimeWinRate: historicalData.totalTrades > 0
|
|
1210
|
+
? historicalData.totalWins / historicalData.totalTrades
|
|
1211
|
+
: null,
|
|
1212
|
+
lifetimeSessions: historicalData.totalSessions || 0
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
// ========== PATTERN ANALYSIS ==========
|
|
1216
|
+
const patternAnalysis = {
|
|
1217
|
+
// Analyze what conditions led to wins vs losses
|
|
1218
|
+
winningConditions: analyzeWinningConditions(),
|
|
1219
|
+
losingConditions: analyzeLosingConditions(),
|
|
1220
|
+
|
|
1221
|
+
// Best/worst time patterns from this session
|
|
1222
|
+
sessionHourlyPerformance: supervisorState.hourlyPerformance,
|
|
1223
|
+
|
|
1224
|
+
// Price level effectiveness
|
|
1225
|
+
levelEffectiveness: analyzeLevelEffectiveness()
|
|
1226
|
+
};
|
|
1227
|
+
|
|
473
1228
|
// Prepare performance data for AI analysis
|
|
474
1229
|
const performanceData = {
|
|
475
1230
|
trades: perf.trades,
|
|
@@ -490,12 +1245,19 @@ const analyzeAndOptimize = async () => {
|
|
|
490
1245
|
avgLoss: perf.losses > 0 ?
|
|
491
1246
|
Math.abs(supervisorState.trades.filter(t => t.pnl < 0).reduce((s, t) => s + t.pnl, 0) / perf.losses) : 0,
|
|
492
1247
|
|
|
493
|
-
// Recent trades
|
|
1248
|
+
// Recent trades with FULL context
|
|
494
1249
|
recentTrades: supervisorState.trades.slice(-10).map(t => ({
|
|
495
1250
|
side: t.direction || t.side,
|
|
496
1251
|
qty: t.qty,
|
|
497
1252
|
price: t.price,
|
|
498
|
-
pnl: t.pnl
|
|
1253
|
+
pnl: t.pnl,
|
|
1254
|
+
// Include context that API doesn't have
|
|
1255
|
+
hour: t.timeContext?.hour,
|
|
1256
|
+
session: t.timeContext?.session,
|
|
1257
|
+
volatility: t.marketContext?.volatility,
|
|
1258
|
+
trendDirection: t.marketContext?.trendStrength?.direction,
|
|
1259
|
+
aiAction: t.aiContext?.action,
|
|
1260
|
+
nearLevel: t.levelContext?.levelType
|
|
499
1261
|
})),
|
|
500
1262
|
|
|
501
1263
|
// Pattern summaries
|
|
@@ -503,9 +1265,175 @@ const analyzeAndOptimize = async () => {
|
|
|
503
1265
|
losingPatternCount: supervisorState.losingPatterns.length,
|
|
504
1266
|
|
|
505
1267
|
// Common characteristics of losing trades
|
|
506
|
-
losingTradeAnalysis: analyzeLosingPatterns()
|
|
1268
|
+
losingTradeAnalysis: analyzeLosingPatterns(),
|
|
1269
|
+
|
|
1270
|
+
// ========== NEW: Historical context for AI ==========
|
|
1271
|
+
historicalInsights,
|
|
1272
|
+
patternAnalysis,
|
|
1273
|
+
|
|
1274
|
+
// Current time context
|
|
1275
|
+
currentHour,
|
|
1276
|
+
currentSession: getMarketSession(currentHour),
|
|
1277
|
+
currentDay: dayNames[currentDay]
|
|
507
1278
|
};
|
|
508
1279
|
|
|
1280
|
+
// Get suggestions from agents and apply
|
|
1281
|
+
await processAgentSuggestions(performanceData);
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Analyze conditions that led to winning trades
|
|
1286
|
+
*/
|
|
1287
|
+
const analyzeWinningConditions = () => {
|
|
1288
|
+
const patterns = supervisorState.winningPatterns;
|
|
1289
|
+
if (patterns.length < 3) return null;
|
|
1290
|
+
|
|
1291
|
+
// Aggregate conditions
|
|
1292
|
+
const conditions = {
|
|
1293
|
+
// Market context
|
|
1294
|
+
avgVolatility: 0,
|
|
1295
|
+
trendDirections: { bullish: 0, bearish: 0, neutral: 0 },
|
|
1296
|
+
trendAligned: 0,
|
|
1297
|
+
|
|
1298
|
+
// Time context
|
|
1299
|
+
hours: {},
|
|
1300
|
+
sessions: {},
|
|
1301
|
+
|
|
1302
|
+
// AI context
|
|
1303
|
+
aiActions: { AGGRESSIVE: 0, NORMAL: 0, CAUTIOUS: 0, PAUSE: 0 },
|
|
1304
|
+
|
|
1305
|
+
// Level context
|
|
1306
|
+
nearKeyLevel: 0,
|
|
1307
|
+
levelTypes: {}
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
for (const p of patterns) {
|
|
1311
|
+
// Market
|
|
1312
|
+
conditions.avgVolatility += p.marketContext?.volatility || 0;
|
|
1313
|
+
const trend = p.marketContext?.trendStrength?.direction || 'unknown';
|
|
1314
|
+
if (conditions.trendDirections[trend] !== undefined) conditions.trendDirections[trend]++;
|
|
1315
|
+
if (p.derived?.trendAligned) conditions.trendAligned++;
|
|
1316
|
+
|
|
1317
|
+
// Time
|
|
1318
|
+
const hour = p.timeContext?.hour;
|
|
1319
|
+
if (hour !== undefined) conditions.hours[hour] = (conditions.hours[hour] || 0) + 1;
|
|
1320
|
+
const session = p.timeContext?.session;
|
|
1321
|
+
if (session) conditions.sessions[session] = (conditions.sessions[session] || 0) + 1;
|
|
1322
|
+
|
|
1323
|
+
// AI
|
|
1324
|
+
const action = p.aiContext?.action || 'NORMAL';
|
|
1325
|
+
if (conditions.aiActions[action] !== undefined) conditions.aiActions[action]++;
|
|
1326
|
+
|
|
1327
|
+
// Levels
|
|
1328
|
+
if (p.derived?.nearKeyLevel) conditions.nearKeyLevel++;
|
|
1329
|
+
const levelType = p.levelContext?.levelType;
|
|
1330
|
+
if (levelType) conditions.levelTypes[levelType] = (conditions.levelTypes[levelType] || 0) + 1;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
conditions.avgVolatility /= patterns.length;
|
|
1334
|
+
conditions.trendAlignedPct = (conditions.trendAligned / patterns.length) * 100;
|
|
1335
|
+
conditions.nearKeyLevelPct = (conditions.nearKeyLevel / patterns.length) * 100;
|
|
1336
|
+
conditions.count = patterns.length;
|
|
1337
|
+
|
|
1338
|
+
// Find best hour
|
|
1339
|
+
const bestHour = Object.entries(conditions.hours).sort((a, b) => b[1] - a[1])[0];
|
|
1340
|
+
conditions.bestHour = bestHour ? parseInt(bestHour[0]) : null;
|
|
1341
|
+
|
|
1342
|
+
// Find best session
|
|
1343
|
+
const bestSession = Object.entries(conditions.sessions).sort((a, b) => b[1] - a[1])[0];
|
|
1344
|
+
conditions.bestSession = bestSession ? bestSession[0] : null;
|
|
1345
|
+
|
|
1346
|
+
return conditions;
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
/**
|
|
1350
|
+
* Analyze conditions that led to losing trades
|
|
1351
|
+
*/
|
|
1352
|
+
const analyzeLosingConditions = () => {
|
|
1353
|
+
const patterns = supervisorState.losingPatterns;
|
|
1354
|
+
if (patterns.length < 3) return null;
|
|
1355
|
+
|
|
1356
|
+
const conditions = {
|
|
1357
|
+
avgVolatility: 0,
|
|
1358
|
+
trendDirections: { bullish: 0, bearish: 0, neutral: 0 },
|
|
1359
|
+
trendAligned: 0,
|
|
1360
|
+
hours: {},
|
|
1361
|
+
sessions: {},
|
|
1362
|
+
aiActions: { AGGRESSIVE: 0, NORMAL: 0, CAUTIOUS: 0, PAUSE: 0 },
|
|
1363
|
+
nearKeyLevel: 0,
|
|
1364
|
+
levelTypes: {},
|
|
1365
|
+
highVolatility: 0
|
|
1366
|
+
};
|
|
1367
|
+
|
|
1368
|
+
for (const p of patterns) {
|
|
1369
|
+
conditions.avgVolatility += p.marketContext?.volatility || 0;
|
|
1370
|
+
const trend = p.marketContext?.trendStrength?.direction || 'unknown';
|
|
1371
|
+
if (conditions.trendDirections[trend] !== undefined) conditions.trendDirections[trend]++;
|
|
1372
|
+
if (p.derived?.trendAligned) conditions.trendAligned++;
|
|
1373
|
+
if (p.derived?.highVolatility) conditions.highVolatility++;
|
|
1374
|
+
|
|
1375
|
+
const hour = p.timeContext?.hour;
|
|
1376
|
+
if (hour !== undefined) conditions.hours[hour] = (conditions.hours[hour] || 0) + 1;
|
|
1377
|
+
const session = p.timeContext?.session;
|
|
1378
|
+
if (session) conditions.sessions[session] = (conditions.sessions[session] || 0) + 1;
|
|
1379
|
+
|
|
1380
|
+
const action = p.aiContext?.action || 'NORMAL';
|
|
1381
|
+
if (conditions.aiActions[action] !== undefined) conditions.aiActions[action]++;
|
|
1382
|
+
|
|
1383
|
+
if (p.derived?.nearKeyLevel) conditions.nearKeyLevel++;
|
|
1384
|
+
const levelType = p.levelContext?.levelType;
|
|
1385
|
+
if (levelType) conditions.levelTypes[levelType] = (conditions.levelTypes[levelType] || 0) + 1;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
conditions.avgVolatility /= patterns.length;
|
|
1389
|
+
conditions.trendAlignedPct = (conditions.trendAligned / patterns.length) * 100;
|
|
1390
|
+
conditions.nearKeyLevelPct = (conditions.nearKeyLevel / patterns.length) * 100;
|
|
1391
|
+
conditions.highVolatilityPct = (conditions.highVolatility / patterns.length) * 100;
|
|
1392
|
+
conditions.count = patterns.length;
|
|
1393
|
+
|
|
1394
|
+
// Find worst hour
|
|
1395
|
+
const worstHour = Object.entries(conditions.hours).sort((a, b) => b[1] - a[1])[0];
|
|
1396
|
+
conditions.worstHour = worstHour ? parseInt(worstHour[0]) : null;
|
|
1397
|
+
|
|
1398
|
+
// Find worst session
|
|
1399
|
+
const worstSession = Object.entries(conditions.sessions).sort((a, b) => b[1] - a[1])[0];
|
|
1400
|
+
conditions.worstSession = worstSession ? worstSession[0] : null;
|
|
1401
|
+
|
|
1402
|
+
return conditions;
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
* Analyze effectiveness of trading at key price levels
|
|
1407
|
+
*/
|
|
1408
|
+
const analyzeLevelEffectiveness = () => {
|
|
1409
|
+
const allPatterns = [...supervisorState.winningPatterns, ...supervisorState.losingPatterns];
|
|
1410
|
+
if (allPatterns.length < 5) return null;
|
|
1411
|
+
|
|
1412
|
+
const levelStats = {};
|
|
1413
|
+
|
|
1414
|
+
for (const p of allPatterns) {
|
|
1415
|
+
const levelType = p.levelContext?.levelType || 'unknown';
|
|
1416
|
+
if (!levelStats[levelType]) {
|
|
1417
|
+
levelStats[levelType] = { wins: 0, losses: 0, total: 0 };
|
|
1418
|
+
}
|
|
1419
|
+
levelStats[levelType].total++;
|
|
1420
|
+
if (p.result === 'win') levelStats[levelType].wins++;
|
|
1421
|
+
else levelStats[levelType].losses++;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// Calculate win rate per level type
|
|
1425
|
+
for (const type of Object.keys(levelStats)) {
|
|
1426
|
+
const stats = levelStats[type];
|
|
1427
|
+
stats.winRate = stats.total > 0 ? (stats.wins / stats.total) * 100 : 0;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
return levelStats;
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* Process agent suggestions and apply optimizations
|
|
1435
|
+
*/
|
|
1436
|
+
const processAgentSuggestions = async (performanceData) => {
|
|
509
1437
|
// Get optimization suggestions from all agents
|
|
510
1438
|
const suggestions = [];
|
|
511
1439
|
|
|
@@ -632,6 +1560,7 @@ const analyzeLosingPatterns = () => {
|
|
|
632
1560
|
|
|
633
1561
|
/**
|
|
634
1562
|
* Get optimization suggestion from a single agent
|
|
1563
|
+
* Provides RICH context including historical data
|
|
635
1564
|
*/
|
|
636
1565
|
const getOptimizationFromAgent = async (agent, performanceData) => {
|
|
637
1566
|
const systemPrompt = `You are an AI supervisor for HQX Ultra Scalping, a professional futures trading strategy.
|
|
@@ -641,42 +1570,107 @@ The strategy uses:
|
|
|
641
1570
|
- Statistical models (z-score, standard deviation)
|
|
642
1571
|
- Dynamic risk management (Kelly criterion)
|
|
643
1572
|
|
|
644
|
-
|
|
645
|
-
|
|
1573
|
+
You have access to:
|
|
1574
|
+
1. CURRENT SESSION data (trades, P&L, streaks)
|
|
1575
|
+
2. HISTORICAL data (which hours/days perform best, pattern analysis)
|
|
1576
|
+
3. MARKET CONTEXT (volatility, trend, price levels)
|
|
1577
|
+
|
|
1578
|
+
ANALYZE ALL data and provide SPECIFIC, ACTIONABLE recommendations.
|
|
1579
|
+
|
|
1580
|
+
IMPORTANT: Your recommendations affect REAL MONEY. Be conservative when uncertain.
|
|
646
1581
|
|
|
647
1582
|
Respond in JSON:
|
|
648
1583
|
{
|
|
649
|
-
"assessment": "brief assessment",
|
|
1584
|
+
"assessment": "brief assessment of current conditions",
|
|
650
1585
|
"action": "AGGRESSIVE|NORMAL|CAUTIOUS|PAUSE",
|
|
651
1586
|
"sizeMultiplier": 0.5-1.5,
|
|
652
1587
|
"optimizations": [
|
|
653
|
-
{"param": "name", "direction": "increase|decrease", "amount": "10%", "reason": "why"}
|
|
1588
|
+
{"param": "name", "direction": "increase|decrease", "amount": "10%", "reason": "why based on data"}
|
|
654
1589
|
],
|
|
655
|
-
"learnings": "what we
|
|
1590
|
+
"learnings": "what patterns we identified from the data",
|
|
1591
|
+
"timeAdvice": "should we trade now based on historical hour/day performance?",
|
|
1592
|
+
"riskAdvice": "specific risk management suggestion",
|
|
656
1593
|
"confidence": 0-100
|
|
657
1594
|
}`;
|
|
658
1595
|
|
|
1596
|
+
// Build historical context string
|
|
1597
|
+
const hist = performanceData.historicalInsights || {};
|
|
1598
|
+
const hourStats = hist.currentHourStats;
|
|
1599
|
+
const dayStats = hist.currentDayStats;
|
|
1600
|
+
|
|
1601
|
+
let historicalContext = '';
|
|
1602
|
+
if (hist.lifetimeSessions > 0) {
|
|
1603
|
+
historicalContext = `
|
|
1604
|
+
HISTORICAL DATA (${hist.lifetimeSessions} sessions):
|
|
1605
|
+
- Lifetime Win Rate: ${hist.lifetimeWinRate ? (hist.lifetimeWinRate * 100).toFixed(1) + '%' : 'N/A'}
|
|
1606
|
+
- Current Hour (${performanceData.currentHour}:00): ${hist.isGoodHour ? 'HISTORICALLY GOOD' : hist.isBadHour ? 'HISTORICALLY BAD' : 'NEUTRAL'}
|
|
1607
|
+
${hourStats ? `(${hourStats.trades} trades, ${hourStats.wins}W/${hourStats.losses}L, $${hourStats.pnl?.toFixed(2) || 0})` : ''}
|
|
1608
|
+
- Current Day (${performanceData.currentDay}): ${hist.isGoodDay ? 'HISTORICALLY GOOD' : hist.isBadDay ? 'HISTORICALLY BAD' : 'NEUTRAL'}
|
|
1609
|
+
${dayStats ? `(${dayStats.trades} trades, ${dayStats.wins}W/${dayStats.losses}L, $${dayStats.pnl?.toFixed(2) || 0})` : ''}
|
|
1610
|
+
- Avg Win Streak: ${hist.avgWinStreak?.toFixed(1) || 'N/A'}, Avg Loss Streak: ${hist.avgLossStreak?.toFixed(1) || 'N/A'}`;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// Build pattern analysis string
|
|
1614
|
+
const patterns = performanceData.patternAnalysis || {};
|
|
1615
|
+
let patternContext = '';
|
|
1616
|
+
|
|
1617
|
+
if (patterns.winningConditions) {
|
|
1618
|
+
const wc = patterns.winningConditions;
|
|
1619
|
+
patternContext += `
|
|
1620
|
+
WINNING TRADE PATTERNS (${wc.count} trades):
|
|
1621
|
+
- Best Hour: ${wc.bestHour !== null ? wc.bestHour + ':00' : 'N/A'}
|
|
1622
|
+
- Best Session: ${wc.bestSession || 'N/A'}
|
|
1623
|
+
- Avg Volatility: ${(wc.avgVolatility * 100).toFixed(3)}%
|
|
1624
|
+
- Trend Aligned: ${wc.trendAlignedPct?.toFixed(0) || 0}%
|
|
1625
|
+
- Near Key Level: ${wc.nearKeyLevelPct?.toFixed(0) || 0}%`;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
if (patterns.losingConditions) {
|
|
1629
|
+
const lc = patterns.losingConditions;
|
|
1630
|
+
patternContext += `
|
|
1631
|
+
|
|
1632
|
+
LOSING TRADE PATTERNS (${lc.count} trades):
|
|
1633
|
+
- Worst Hour: ${lc.worstHour !== null ? lc.worstHour + ':00' : 'N/A'}
|
|
1634
|
+
- Worst Session: ${lc.worstSession || 'N/A'}
|
|
1635
|
+
- Avg Volatility: ${(lc.avgVolatility * 100).toFixed(3)}%
|
|
1636
|
+
- High Volatility: ${lc.highVolatilityPct?.toFixed(0) || 0}%
|
|
1637
|
+
- Trend Aligned: ${lc.trendAlignedPct?.toFixed(0) || 0}%`;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
if (patterns.levelEffectiveness) {
|
|
1641
|
+
const le = patterns.levelEffectiveness;
|
|
1642
|
+
const levelSummary = Object.entries(le)
|
|
1643
|
+
.filter(([k, v]) => v.total >= 2)
|
|
1644
|
+
.map(([k, v]) => `${k}: ${v.winRate.toFixed(0)}% (${v.total} trades)`)
|
|
1645
|
+
.join(', ');
|
|
1646
|
+
if (levelSummary) {
|
|
1647
|
+
patternContext += `
|
|
1648
|
+
|
|
1649
|
+
LEVEL EFFECTIVENESS: ${levelSummary}`;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
659
1653
|
const prompt = `STRATEGY PERFORMANCE ANALYSIS
|
|
660
1654
|
|
|
661
|
-
|
|
1655
|
+
SESSION STATS:
|
|
662
1656
|
- Trades: ${performanceData.trades} (${performanceData.wins}W / ${performanceData.losses}L)
|
|
663
1657
|
- Win Rate: ${(performanceData.winRate * 100).toFixed(1)}%
|
|
664
1658
|
- P&L: $${performanceData.pnl.toFixed(2)}
|
|
665
1659
|
- Max Drawdown: $${performanceData.maxDrawdown.toFixed(2)}
|
|
666
1660
|
- Current Streak: ${performanceData.winStreak > 0 ? performanceData.winStreak + ' wins' : performanceData.lossStreak + ' losses'}
|
|
1661
|
+
- Current Session: ${performanceData.currentSession} (${performanceData.currentHour}:00)
|
|
1662
|
+
${historicalContext}
|
|
1663
|
+
${patternContext}
|
|
667
1664
|
|
|
668
|
-
|
|
669
|
-
${performanceData.
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
- Avg signal confidence: ${(performanceData.losingTradeAnalysis.avgConfidence * 100).toFixed(1)}%
|
|
673
|
-
- Total losing patterns: ${performanceData.losingTradeAnalysis.count}
|
|
674
|
-
` : 'Not enough data'}
|
|
675
|
-
|
|
676
|
-
Recent Trades:
|
|
677
|
-
${performanceData.recentTrades.map(t => `${t.side} @ ${t.price} → $${t.pnl?.toFixed(2)}`).join('\n')}
|
|
1665
|
+
RECENT TRADES (with context):
|
|
1666
|
+
${performanceData.recentTrades.map(t =>
|
|
1667
|
+
`${t.side} @ ${t.price} → $${t.pnl?.toFixed(2)} | ${t.session || 'N/A'} | vol:${t.volatility ? (t.volatility * 100).toFixed(2) + '%' : 'N/A'} | trend:${t.trendDirection || 'N/A'} | level:${t.nearLevel || 'N/A'}`
|
|
1668
|
+
).join('\n')}
|
|
678
1669
|
|
|
679
|
-
|
|
1670
|
+
Based on ALL this data:
|
|
1671
|
+
1. What patterns do you see in winning vs losing trades?
|
|
1672
|
+
2. Should we trade now (considering hour/day history)?
|
|
1673
|
+
3. What SPECIFIC optimizations would improve performance?`;
|
|
680
1674
|
|
|
681
1675
|
try {
|
|
682
1676
|
const response = await callAI(agent, prompt, systemPrompt);
|