hedgequantx 2.5.39 → 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/pages/stats.js +10 -3
- package/src/services/ai/strategy-supervisor.js +1200 -45
|
@@ -8,12 +8,477 @@
|
|
|
8
8
|
* 2. LEARN - Analyze winning/losing trades to identify patterns
|
|
9
9
|
* 3. OPTIMIZE - Suggest and apply parameter improvements
|
|
10
10
|
* 4. SUPERVISE - Monitor risk and intervene when necessary
|
|
11
|
+
* 5. PERSIST - Save learned patterns and optimizations between sessions
|
|
11
12
|
*
|
|
12
13
|
* In CONSENSUS mode (2+ agents), ALL agents must agree before applying changes.
|
|
13
14
|
*/
|
|
14
15
|
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
15
19
|
const { analyzePerformance, getMarketAdvice, callAI } = require('./client');
|
|
16
20
|
|
|
21
|
+
// Path for persisted learning data
|
|
22
|
+
const DATA_DIR = path.join(os.homedir(), '.hqx');
|
|
23
|
+
const LEARNING_FILE = path.join(DATA_DIR, 'ai-learning.json');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Load persisted learning data from disk
|
|
27
|
+
* Contains full memory of strategy behavior over weeks/months
|
|
28
|
+
*/
|
|
29
|
+
const loadLearningData = () => {
|
|
30
|
+
try {
|
|
31
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
32
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(LEARNING_FILE)) {
|
|
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
|
+
|
|
44
|
+
return {
|
|
45
|
+
// Pattern memory
|
|
46
|
+
winningPatterns: data.winningPatterns || [],
|
|
47
|
+
losingPatterns: data.losingPatterns || [],
|
|
48
|
+
|
|
49
|
+
// Optimization history
|
|
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
|
|
74
|
+
totalSessions: data.totalSessions || 0,
|
|
75
|
+
totalTrades: data.totalTrades || 0,
|
|
76
|
+
totalWins: data.totalWins || 0,
|
|
77
|
+
totalLosses: data.totalLosses || 0,
|
|
78
|
+
lifetimePnL: data.lifetimePnL || 0,
|
|
79
|
+
|
|
80
|
+
lastUpdated: data.lastUpdated || null,
|
|
81
|
+
firstSession: data.firstSession || null
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
// Silent fail - start fresh
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
winningPatterns: [],
|
|
90
|
+
losingPatterns: [],
|
|
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
|
+
},
|
|
103
|
+
totalSessions: 0,
|
|
104
|
+
totalTrades: 0,
|
|
105
|
+
totalWins: 0,
|
|
106
|
+
totalLosses: 0,
|
|
107
|
+
lifetimePnL: 0,
|
|
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'
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Save learning data to disk
|
|
222
|
+
* Full memory of strategy behavior over 1 month
|
|
223
|
+
*/
|
|
224
|
+
const saveLearningData = () => {
|
|
225
|
+
try {
|
|
226
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
227
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Load existing data first
|
|
231
|
+
const existing = loadLearningData();
|
|
232
|
+
|
|
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
|
|
333
|
+
const dataToSave = {
|
|
334
|
+
// Patterns - keep last 100 of each type
|
|
335
|
+
winningPatterns: mergePatterns(existing.winningPatterns, supervisorState.winningPatterns, 100),
|
|
336
|
+
losingPatterns: mergePatterns(existing.losingPatterns, supervisorState.losingPatterns, 100),
|
|
337
|
+
|
|
338
|
+
// Optimizations history - keep last 50
|
|
339
|
+
optimizations: [...existing.optimizations, ...supervisorState.optimizations].slice(-50),
|
|
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
|
+
|
|
354
|
+
// Lifetime stats
|
|
355
|
+
totalSessions: existing.totalSessions + 1,
|
|
356
|
+
totalTrades: existing.totalTrades + supervisorState.performance.trades,
|
|
357
|
+
totalWins: existing.totalWins + supervisorState.performance.wins,
|
|
358
|
+
totalLosses: existing.totalLosses + supervisorState.performance.losses,
|
|
359
|
+
lifetimePnL: existing.lifetimePnL + supervisorState.performance.totalPnL,
|
|
360
|
+
|
|
361
|
+
// Metadata
|
|
362
|
+
firstSession: existing.firstSession || now.toISOString(),
|
|
363
|
+
lastUpdated: now.toISOString()
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
fs.writeFileSync(LEARNING_FILE, JSON.stringify(dataToSave, null, 2));
|
|
367
|
+
return true;
|
|
368
|
+
} catch (e) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
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
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Merge pattern arrays, keeping most recent unique entries
|
|
427
|
+
*/
|
|
428
|
+
const mergePatterns = (existing, current, maxCount) => {
|
|
429
|
+
const merged = [...existing, ...current];
|
|
430
|
+
|
|
431
|
+
// Sort by timestamp descending (most recent first)
|
|
432
|
+
merged.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
433
|
+
|
|
434
|
+
// Keep only maxCount most recent
|
|
435
|
+
return merged.slice(0, maxCount);
|
|
436
|
+
};
|
|
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
|
+
|
|
17
482
|
// Singleton supervisor state
|
|
18
483
|
let supervisorState = {
|
|
19
484
|
active: false,
|
|
@@ -22,11 +487,17 @@ let supervisorState = {
|
|
|
22
487
|
service: null,
|
|
23
488
|
accountId: null,
|
|
24
489
|
|
|
490
|
+
// Current symbol being traded
|
|
491
|
+
currentSymbol: null,
|
|
492
|
+
|
|
25
493
|
// Real-time data (synced with strategy)
|
|
26
494
|
ticks: [],
|
|
27
495
|
signals: [],
|
|
28
496
|
trades: [],
|
|
29
497
|
|
|
498
|
+
// Symbol-specific levels learned this session
|
|
499
|
+
symbolLevels: [],
|
|
500
|
+
|
|
30
501
|
// Learning data
|
|
31
502
|
winningPatterns: [],
|
|
32
503
|
losingPatterns: [],
|
|
@@ -61,7 +532,16 @@ let supervisorState = {
|
|
|
61
532
|
// Behavior history for graph (action over time)
|
|
62
533
|
// Values: 0=PAUSE, 1=CAUTIOUS, 2=NORMAL, 3=AGGRESSIVE
|
|
63
534
|
behaviorHistory: [],
|
|
64
|
-
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: {}
|
|
65
545
|
};
|
|
66
546
|
|
|
67
547
|
// Analysis interval
|
|
@@ -69,10 +549,14 @@ let analysisInterval = null;
|
|
|
69
549
|
|
|
70
550
|
/**
|
|
71
551
|
* Initialize supervisor with strategy and agents
|
|
552
|
+
* Loads previous learning data to continue improving
|
|
72
553
|
*/
|
|
73
554
|
const initialize = (strategy, agents, service, accountId) => {
|
|
74
555
|
const now = Date.now();
|
|
75
556
|
|
|
557
|
+
// Load previously learned patterns and optimizations
|
|
558
|
+
const previousLearning = loadLearningData();
|
|
559
|
+
|
|
76
560
|
supervisorState = {
|
|
77
561
|
...supervisorState,
|
|
78
562
|
active: true,
|
|
@@ -83,8 +567,17 @@ const initialize = (strategy, agents, service, accountId) => {
|
|
|
83
567
|
ticks: [],
|
|
84
568
|
signals: [],
|
|
85
569
|
trades: [],
|
|
86
|
-
|
|
87
|
-
|
|
570
|
+
// Restore previous learning
|
|
571
|
+
winningPatterns: previousLearning.winningPatterns || [],
|
|
572
|
+
losingPatterns: previousLearning.losingPatterns || [],
|
|
573
|
+
previousOptimizations: previousLearning.optimizations || [],
|
|
574
|
+
lifetimeStats: {
|
|
575
|
+
sessions: previousLearning.totalSessions || 0,
|
|
576
|
+
trades: previousLearning.totalTrades || 0,
|
|
577
|
+
wins: previousLearning.totalWins || 0,
|
|
578
|
+
losses: previousLearning.totalLosses || 0,
|
|
579
|
+
pnl: previousLearning.lifetimePnL || 0
|
|
580
|
+
},
|
|
88
581
|
performance: {
|
|
89
582
|
trades: 0,
|
|
90
583
|
wins: 0,
|
|
@@ -124,7 +617,7 @@ const initialize = (strategy, agents, service, accountId) => {
|
|
|
124
617
|
};
|
|
125
618
|
|
|
126
619
|
/**
|
|
127
|
-
* Stop supervisor
|
|
620
|
+
* Stop supervisor and save learned data
|
|
128
621
|
*/
|
|
129
622
|
const stop = () => {
|
|
130
623
|
if (analysisInterval) {
|
|
@@ -132,11 +625,16 @@ const stop = () => {
|
|
|
132
625
|
analysisInterval = null;
|
|
133
626
|
}
|
|
134
627
|
|
|
628
|
+
// Save all learned data before stopping
|
|
629
|
+
const saved = saveLearningData();
|
|
630
|
+
|
|
135
631
|
const summary = {
|
|
136
632
|
...supervisorState.performance,
|
|
137
633
|
optimizationsApplied: supervisorState.optimizations.length,
|
|
138
634
|
winningPatterns: supervisorState.winningPatterns.length,
|
|
139
|
-
losingPatterns: supervisorState.losingPatterns.length
|
|
635
|
+
losingPatterns: supervisorState.losingPatterns.length,
|
|
636
|
+
dataSaved: saved,
|
|
637
|
+
lifetimeStats: supervisorState.lifetimeStats
|
|
140
638
|
};
|
|
141
639
|
|
|
142
640
|
supervisorState.active = false;
|
|
@@ -184,16 +682,96 @@ const feedSignal = (signal) => {
|
|
|
184
682
|
/**
|
|
185
683
|
* Feed trade result (called when a trade completes)
|
|
186
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
|
|
187
692
|
*/
|
|
188
693
|
const feedTradeResult = (trade) => {
|
|
189
694
|
if (!supervisorState.active) return;
|
|
190
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
|
|
191
754
|
const tradeData = {
|
|
192
755
|
...trade,
|
|
193
|
-
timestamp:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
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
|
|
197
775
|
};
|
|
198
776
|
|
|
199
777
|
supervisorState.trades.push(tradeData);
|
|
@@ -203,6 +781,9 @@ const feedTradeResult = (trade) => {
|
|
|
203
781
|
perf.trades++;
|
|
204
782
|
perf.totalPnL += trade.pnl || 0;
|
|
205
783
|
|
|
784
|
+
// Determine outcome
|
|
785
|
+
const outcome = trade.pnl > 0 ? 'win' : trade.pnl < 0 ? 'loss' : 'breakeven';
|
|
786
|
+
|
|
206
787
|
if (trade.pnl > 0) {
|
|
207
788
|
perf.wins++;
|
|
208
789
|
perf.winStreak++;
|
|
@@ -221,6 +802,22 @@ const feedTradeResult = (trade) => {
|
|
|
221
802
|
learnFromTrade(tradeData, 'loss');
|
|
222
803
|
}
|
|
223
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
|
+
|
|
224
821
|
// Update drawdown
|
|
225
822
|
if (perf.totalPnL > perf.peakPnL) {
|
|
226
823
|
perf.peakPnL = perf.totalPnL;
|
|
@@ -236,44 +833,275 @@ const feedTradeResult = (trade) => {
|
|
|
236
833
|
}
|
|
237
834
|
};
|
|
238
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
|
+
|
|
239
998
|
/**
|
|
240
999
|
* Learn from a completed trade
|
|
241
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
|
|
242
1007
|
*/
|
|
243
1008
|
const learnFromTrade = (trade, result) => {
|
|
1009
|
+
// Build comprehensive pattern from trade context
|
|
244
1010
|
const pattern = {
|
|
245
1011
|
timestamp: trade.timestamp,
|
|
246
1012
|
result,
|
|
247
1013
|
pnl: trade.pnl,
|
|
248
1014
|
direction: trade.direction || trade.side,
|
|
1015
|
+
symbol: trade.symbol,
|
|
249
1016
|
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
},
|
|
254
1032
|
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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,
|
|
1041
|
+
|
|
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
|
+
}
|
|
260
1064
|
};
|
|
261
1065
|
|
|
262
1066
|
if (result === 'win') {
|
|
263
1067
|
supervisorState.winningPatterns.push(pattern);
|
|
264
|
-
// Keep last
|
|
265
|
-
if (supervisorState.winningPatterns.length >
|
|
266
|
-
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);
|
|
267
1071
|
}
|
|
268
1072
|
} else {
|
|
269
1073
|
supervisorState.losingPatterns.push(pattern);
|
|
270
|
-
// Keep last
|
|
271
|
-
if (supervisorState.losingPatterns.length >
|
|
272
|
-
supervisorState.losingPatterns = supervisorState.losingPatterns.slice(-
|
|
1074
|
+
// Keep last 100 losing patterns
|
|
1075
|
+
if (supervisorState.losingPatterns.length > 100) {
|
|
1076
|
+
supervisorState.losingPatterns = supervisorState.losingPatterns.slice(-100);
|
|
273
1077
|
}
|
|
274
1078
|
}
|
|
275
1079
|
};
|
|
276
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
|
+
|
|
277
1105
|
/**
|
|
278
1106
|
* Analyze price action from ticks
|
|
279
1107
|
*/
|
|
@@ -341,6 +1169,8 @@ const calculateVolatility = (ticks) => {
|
|
|
341
1169
|
/**
|
|
342
1170
|
* Main analysis and optimization loop
|
|
343
1171
|
* Called periodically and after significant events
|
|
1172
|
+
*
|
|
1173
|
+
* Uses BOTH session data AND historical data for decisions
|
|
344
1174
|
*/
|
|
345
1175
|
const analyzeAndOptimize = async () => {
|
|
346
1176
|
if (!supervisorState.active || supervisorState.agents.length === 0) return;
|
|
@@ -350,6 +1180,51 @@ const analyzeAndOptimize = async () => {
|
|
|
350
1180
|
// Skip if not enough data
|
|
351
1181
|
if (perf.trades < 3) return;
|
|
352
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
|
+
|
|
353
1228
|
// Prepare performance data for AI analysis
|
|
354
1229
|
const performanceData = {
|
|
355
1230
|
trades: perf.trades,
|
|
@@ -370,12 +1245,19 @@ const analyzeAndOptimize = async () => {
|
|
|
370
1245
|
avgLoss: perf.losses > 0 ?
|
|
371
1246
|
Math.abs(supervisorState.trades.filter(t => t.pnl < 0).reduce((s, t) => s + t.pnl, 0) / perf.losses) : 0,
|
|
372
1247
|
|
|
373
|
-
// Recent trades
|
|
1248
|
+
// Recent trades with FULL context
|
|
374
1249
|
recentTrades: supervisorState.trades.slice(-10).map(t => ({
|
|
375
1250
|
side: t.direction || t.side,
|
|
376
1251
|
qty: t.qty,
|
|
377
1252
|
price: t.price,
|
|
378
|
-
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
|
|
379
1261
|
})),
|
|
380
1262
|
|
|
381
1263
|
// Pattern summaries
|
|
@@ -383,9 +1265,175 @@ const analyzeAndOptimize = async () => {
|
|
|
383
1265
|
losingPatternCount: supervisorState.losingPatterns.length,
|
|
384
1266
|
|
|
385
1267
|
// Common characteristics of losing trades
|
|
386
|
-
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]
|
|
387
1278
|
};
|
|
388
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) => {
|
|
389
1437
|
// Get optimization suggestions from all agents
|
|
390
1438
|
const suggestions = [];
|
|
391
1439
|
|
|
@@ -512,6 +1560,7 @@ const analyzeLosingPatterns = () => {
|
|
|
512
1560
|
|
|
513
1561
|
/**
|
|
514
1562
|
* Get optimization suggestion from a single agent
|
|
1563
|
+
* Provides RICH context including historical data
|
|
515
1564
|
*/
|
|
516
1565
|
const getOptimizationFromAgent = async (agent, performanceData) => {
|
|
517
1566
|
const systemPrompt = `You are an AI supervisor for HQX Ultra Scalping, a professional futures trading strategy.
|
|
@@ -521,42 +1570,107 @@ The strategy uses:
|
|
|
521
1570
|
- Statistical models (z-score, standard deviation)
|
|
522
1571
|
- Dynamic risk management (Kelly criterion)
|
|
523
1572
|
|
|
524
|
-
|
|
525
|
-
|
|
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.
|
|
526
1581
|
|
|
527
1582
|
Respond in JSON:
|
|
528
1583
|
{
|
|
529
|
-
"assessment": "brief assessment",
|
|
1584
|
+
"assessment": "brief assessment of current conditions",
|
|
530
1585
|
"action": "AGGRESSIVE|NORMAL|CAUTIOUS|PAUSE",
|
|
531
1586
|
"sizeMultiplier": 0.5-1.5,
|
|
532
1587
|
"optimizations": [
|
|
533
|
-
{"param": "name", "direction": "increase|decrease", "amount": "10%", "reason": "why"}
|
|
1588
|
+
{"param": "name", "direction": "increase|decrease", "amount": "10%", "reason": "why based on data"}
|
|
534
1589
|
],
|
|
535
|
-
"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",
|
|
536
1593
|
"confidence": 0-100
|
|
537
1594
|
}`;
|
|
538
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
|
+
|
|
539
1653
|
const prompt = `STRATEGY PERFORMANCE ANALYSIS
|
|
540
1654
|
|
|
541
|
-
|
|
1655
|
+
SESSION STATS:
|
|
542
1656
|
- Trades: ${performanceData.trades} (${performanceData.wins}W / ${performanceData.losses}L)
|
|
543
1657
|
- Win Rate: ${(performanceData.winRate * 100).toFixed(1)}%
|
|
544
1658
|
- P&L: $${performanceData.pnl.toFixed(2)}
|
|
545
1659
|
- Max Drawdown: $${performanceData.maxDrawdown.toFixed(2)}
|
|
546
1660
|
- Current Streak: ${performanceData.winStreak > 0 ? performanceData.winStreak + ' wins' : performanceData.lossStreak + ' losses'}
|
|
1661
|
+
- Current Session: ${performanceData.currentSession} (${performanceData.currentHour}:00)
|
|
1662
|
+
${historicalContext}
|
|
1663
|
+
${patternContext}
|
|
547
1664
|
|
|
548
|
-
|
|
549
|
-
${performanceData.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
- Avg signal confidence: ${(performanceData.losingTradeAnalysis.avgConfidence * 100).toFixed(1)}%
|
|
553
|
-
- Total losing patterns: ${performanceData.losingTradeAnalysis.count}
|
|
554
|
-
` : 'Not enough data'}
|
|
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')}
|
|
555
1669
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
What
|
|
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?`;
|
|
560
1674
|
|
|
561
1675
|
try {
|
|
562
1676
|
const response = await callAI(agent, prompt, systemPrompt);
|
|
@@ -768,6 +1882,44 @@ const getLearningStats = () => {
|
|
|
768
1882
|
};
|
|
769
1883
|
};
|
|
770
1884
|
|
|
1885
|
+
/**
|
|
1886
|
+
* Get lifetime stats across all sessions
|
|
1887
|
+
* Shows cumulative learning progress
|
|
1888
|
+
*/
|
|
1889
|
+
const getLifetimeStats = () => {
|
|
1890
|
+
const saved = loadLearningData();
|
|
1891
|
+
|
|
1892
|
+
return {
|
|
1893
|
+
totalSessions: saved.totalSessions,
|
|
1894
|
+
totalTrades: saved.totalTrades,
|
|
1895
|
+
totalWins: saved.totalWins,
|
|
1896
|
+
totalLosses: saved.totalLosses,
|
|
1897
|
+
lifetimeWinRate: saved.totalTrades > 0 ?
|
|
1898
|
+
((saved.totalWins / saved.totalTrades) * 100).toFixed(1) + '%' : 'N/A',
|
|
1899
|
+
lifetimePnL: saved.lifetimePnL,
|
|
1900
|
+
patternsLearned: {
|
|
1901
|
+
winning: saved.winningPatterns?.length || 0,
|
|
1902
|
+
losing: saved.losingPatterns?.length || 0
|
|
1903
|
+
},
|
|
1904
|
+
optimizationsApplied: saved.optimizations?.length || 0,
|
|
1905
|
+
lastUpdated: saved.lastUpdated
|
|
1906
|
+
};
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
/**
|
|
1910
|
+
* Clear all learned data (reset AI memory)
|
|
1911
|
+
*/
|
|
1912
|
+
const clearLearningData = () => {
|
|
1913
|
+
try {
|
|
1914
|
+
if (fs.existsSync(LEARNING_FILE)) {
|
|
1915
|
+
fs.unlinkSync(LEARNING_FILE);
|
|
1916
|
+
}
|
|
1917
|
+
return true;
|
|
1918
|
+
} catch (e) {
|
|
1919
|
+
return false;
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
|
|
771
1923
|
module.exports = {
|
|
772
1924
|
initialize,
|
|
773
1925
|
stop,
|
|
@@ -779,5 +1931,8 @@ module.exports = {
|
|
|
779
1931
|
getStatus,
|
|
780
1932
|
analyzeAndOptimize,
|
|
781
1933
|
getBehaviorHistory,
|
|
782
|
-
getLearningStats
|
|
1934
|
+
getLearningStats,
|
|
1935
|
+
getLifetimeStats,
|
|
1936
|
+
clearLearningData,
|
|
1937
|
+
loadLearningData
|
|
783
1938
|
};
|