hedgequantx 2.5.40 → 2.5.41

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