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.
@@ -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
- winningPatterns: [],
87
- losingPatterns: [],
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: Date.now(),
194
- // Capture context at time of trade
195
- ticksBefore: supervisorState.ticks.slice(-100),
196
- signalUsed: supervisorState.signals[supervisorState.signals.length - 1] || null
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
- // Market context before trade
251
- priceAction: analyzePriceAction(trade.ticksBefore),
252
- volumeProfile: analyzeVolume(trade.ticksBefore),
253
- volatility: calculateVolatility(trade.ticksBefore),
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
- // Signal characteristics
256
- signalConfidence: trade.signalUsed?.confidence || null,
257
- entryPrice: trade.price || trade.signalUsed?.entry,
258
- stopLoss: trade.signalUsed?.stopLoss,
259
- takeProfit: trade.signalUsed?.takeProfit
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 50 winning patterns
265
- if (supervisorState.winningPatterns.length > 50) {
266
- supervisorState.winningPatterns = supervisorState.winningPatterns.slice(-50);
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 50 losing patterns
271
- if (supervisorState.losingPatterns.length > 50) {
272
- supervisorState.losingPatterns = supervisorState.losingPatterns.slice(-50);
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 for context
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
- ANALYZE the performance data and LEARN from the losing trades.
525
- Suggest SPECIFIC optimizations to improve win rate and reduce losses.
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 learned from losing trades",
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
- Stats:
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
- Losing Trade Analysis:
549
- ${performanceData.losingTradeAnalysis ? `
550
- - Common trend at entry: ${performanceData.losingTradeAnalysis.commonTrend}
551
- - Avg volatility: ${(performanceData.losingTradeAnalysis.avgVolatility * 100).toFixed(3)}%
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
- Recent Trades:
557
- ${performanceData.recentTrades.map(t => `${t.side} @ ${t.price} $${t.pnl?.toFixed(2)}`).join('\n')}
558
-
559
- What should we LEARN and OPTIMIZE?`;
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
  };