Riy777 commited on
Commit
5fefc94
·
verified ·
1 Parent(s): 13e2730

Update LLM.py

Browse files
Files changed (1) hide show
  1. LLM.py +214 -227
LLM.py CHANGED
@@ -1,19 +1,17 @@
1
- # LLM.py (محدث بالكامل مع إصلاح NameError)
2
  import os, traceback, asyncio, json, time
3
- import re # ✅ استيراد مكتبة re
4
  from datetime import datetime
5
  from functools import wraps
6
  from backoff import on_exception, expo
7
  from openai import OpenAI, RateLimitError, APITimeoutError
8
  import numpy as np
9
  from sentiment_news import NewsFetcher
10
- # ✅ تعديل الاستيراد: parse_json_from_response لم يعد مستخدماً هنا بشكل مباشر لتحليل استجابة النموذج الرئيسية
11
  from helpers import validate_required_fields, format_technical_indicators, format_strategy_scores, format_candle_data_for_pattern_analysis, format_whale_analysis_for_llm, parse_json_from_response
12
- from ml_engine.processor import safe_json_parse # ✅ الإصلاح: استيراد المحلل الآمن من الموجه الجديد
13
-
14
- NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY")
15
- PRIMARY_MODEL = "nvidia/llama-3.1-nemotron-ultra-253b-v1"
16
 
 
 
17
  class PatternAnalysisEngine:
18
  # --- (كود PatternAnalysisEngine كما هو بدون تغيير) ---
19
  def __init__(self, llm_service):
@@ -180,8 +178,12 @@ class LLMService:
180
  self.news_fetcher = NewsFetcher()
181
  self.pattern_engine = PatternAnalysisEngine(self)
182
  self.semaphore = asyncio.Semaphore(5)
183
- self.r2_service = None # سيتم تعيينه من app.py
184
- self.learning_engine = None # 🔴 جديد: سيتم تعيينه من app.py
 
 
 
 
185
 
186
  def _rate_limit_nvidia_api(func):
187
  @wraps(func)
@@ -197,53 +199,65 @@ class LLMService:
197
 
198
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
199
  if not ohlcv_data:
200
- print(f"⚠️ لا توجد بيانات شموع لـ {symbol} - تخطي التحليل")
201
  return None
202
 
203
  total_candles = sum(len(data) for data in ohlcv_data.values() if data) if ohlcv_data else 0
204
  timeframes_count = len([tf for tf, data in ohlcv_data.items() if data and len(data) >= 10]) if ohlcv_data else 0
205
 
206
- # 🔴 إصلاح: استخدام (timeframes_count) بدلاً من (0)
207
- print(f" 📊 بيانات {symbol}: {total_candles} شمعة في {timeframes_count} إطار زمني")
208
-
209
  if total_candles < 30:
210
- print(f" ⚠️ بيانات شموع غير كافية لـ {symbol}: {total_candles} شمعة فقط")
211
  return None
212
 
213
  valid_timeframes = [tf for tf, candles in ohlcv_data.items() if candles and len(candles) >= 5]
214
  if not valid_timeframes:
215
- print(f" ⚠️ لا توجد أطر زمنية صالحة لـ {symbol}")
216
  return None
217
- print(f" ✅ أطر زمنية صالحة لـ {symbol}: {', '.join(valid_timeframes)}")
218
 
219
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
220
  whale_data = data_payload.get('whale_data', {})
221
 
222
- best_learned_exit = "None"
223
- learning_feedback = "No learning data yet."
224
- if self.learning_engine and self.learning_engine.initialized:
225
- best_learned_exit = await self.learning_engine.get_best_exit_profile(target_strategy)
226
- if best_learned_exit != "unknown":
227
- learning_feedback = f"Learning System Feedback: For the '{target_strategy}' strategy, the '{best_learned_exit}' exit profile has historically performed best. Please consider this."
228
 
229
- prompt = self._create_comprehensive_trading_prompt(data_payload, news_text, None, whale_data, learning_feedback)
 
 
 
 
 
 
 
 
230
 
 
 
 
 
 
 
 
 
 
 
 
231
  if self.r2_service:
232
  analysis_data = {
233
  'symbol': symbol,
234
  'current_price': data_payload.get('current_price'),
235
- 'final_score': data_payload.get('final_score'),
236
  'enhanced_final_score': data_payload.get('enhanced_final_score'),
237
  'target_strategy': target_strategy,
238
- 'learning_feedback_provided': learning_feedback,
 
239
  'whale_data_available': whale_data.get('data_available', False),
240
  'total_candles': total_candles,
241
  'timeframes_count': timeframes_count,
242
- 'valid_timeframes': valid_timeframes,
243
  'timestamp': datetime.now().isoformat()
244
  }
245
  await self.r2_service.save_llm_prompts_async(
246
- symbol, 'comprehensive_trading_decision_v2', prompt, analysis_data
247
  )
248
 
249
  async with self.semaphore:
@@ -257,11 +271,11 @@ class LLMService:
257
  decision_dict['timeframes_analyzed'] = timeframes_count
258
  return decision_dict
259
  else:
260
- print(f"❌ فشل تحليل النموذج الضخم لـ {symbol} - لا توجد قرارات بديلة")
261
  return None
262
 
263
  except Exception as e:
264
- print(f"❌ خطأ في قرار التداول لـ {data_payload.get('symbol', 'unknown')}: {e}")
265
  traceback.print_exc()
266
  return None
267
 
@@ -269,32 +283,37 @@ class LLMService:
269
  try:
270
  json_str = parse_json_from_response(response_text)
271
  if not json_str:
272
- print(f"❌ فشل استخراج JSON من استجابة النموذج لـ {symbol}")
273
  return None
274
 
275
  decision_data = safe_json_parse(json_str)
276
  if not decision_data:
277
- print(f"❌ فشل تحليل JSON (safe_json_parse) لـ {symbol}: {response_text}")
278
  return None
279
 
 
 
 
 
 
280
  required_fields = [
281
  'action', 'reasoning', 'risk_assessment', 'stop_loss', 'take_profit',
282
  'expected_target_minutes', 'confidence_level', 'pattern_identified_by_llm',
283
  'exit_profile', 'exit_parameters'
284
  ]
285
  if not validate_required_fields(decision_data, required_fields):
286
- print(f"❌ حقول مطلوبة مفقودة في استجابة النموذج لـ {symbol}")
287
  missing = [f for f in required_fields if f not in decision_data]
288
  print(f" MIA: {missing}")
289
  return None
290
 
291
  if not isinstance(decision_data['exit_parameters'], dict):
292
- print(f"❌ الحقل 'exit_parameters' ليس قاموساً (dict) صالحاً لـ {symbol}")
293
  return None
294
 
295
  action = decision_data.get('action')
296
  if action not in ['BUY', 'HOLD']:
297
- print(f"⚠️ النموذج اقترح إجراء غير مدعوم ({action}) لـ {symbol}. سيتم اعتباره HOLD.")
298
  decision_data['action'] = 'HOLD'
299
 
300
  if decision_data['action'] == 'BUY':
@@ -308,21 +327,24 @@ class LLMService:
308
 
309
  return decision_data
310
  except Exception as e:
311
- print(f"❌ خطأ في تحليل استجابة النموذج لـ {symbol}: {e}")
312
  return None
313
 
314
  async def _get_pattern_analysis(self, data_payload):
315
- try:
316
- symbol = data_payload['symbol']
317
- ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
318
- if ohlcv_data:
319
- return await self.pattern_engine.analyze_chart_patterns(symbol, ohlcv_data)
320
- return None
321
- except Exception as e:
322
- print(f"❌ فشل تحليل الأنماط (قد يكون لإعادة التحليل) لـ {data_payload.get('symbol')}: {e}")
323
- return None
324
-
325
- def _create_comprehensive_trading_prompt(self, payload: dict, news_text: str, pattern_analysis: dict, whale_data: dict, learning_feedback: str) -> str:
 
 
 
326
  symbol = payload.get('symbol', 'N/A')
327
  current_price = payload.get('current_price', 'N/A')
328
  reasons = payload.get('reasons_for_candidacy', [])
@@ -331,24 +353,21 @@ class LLMService:
331
  strategy_scores = payload.get('strategy_scores', {})
332
  recommended_strategy = payload.get('recommended_strategy', 'N/A')
333
  target_strategy = payload.get('target_strategy', 'GENERIC')
334
- final_score = payload.get('final_score', 'N/A')
335
- enhanced_final_score = payload.get('enhanced_final_score', 'N/A') # 🔴 هذا هو المتغير الصحيح
336
- ohlcv_data = payload.get('raw_ohlcv') or payload.get('ohlcv', {})
337
 
338
- final_score_display = f"{final_score:.3f}" if isinstance(final_score, (int, float)) else str(final_score)
339
-
340
- # 🔴 --- بدء الإصلاح --- 🔴
341
- # تم تصحيح الخطأ هنا: استخدام enhanced_final_score بدلاً من enhanced_score
342
  enhanced_score_display = f"{enhanced_final_score:.3f}" if isinstance(enhanced_final_score, (int, float)) else str(enhanced_final_score)
343
- # 🔴 --- نهاية الإصلاح --- 🔴
344
 
345
  indicators_summary = format_technical_indicators(advanced_indicators)
346
  strategies_summary = format_strategy_scores(strategy_scores, recommended_strategy)
347
  whale_analysis_section = format_whale_analysis_for_llm(whale_data)
 
348
  candle_data_section = self._format_candle_data_comprehensive(ohlcv_data)
349
  market_context_section = self._format_market_context(sentiment_data)
350
 
351
- learning_feedback_section = f"🧠 LEARNING ENGINE FEEDBACK:\n{learning_feedback}"
 
 
 
352
 
353
  prompt = f"""
354
  COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
@@ -356,13 +375,15 @@ COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
356
  🚨 IMPORTANT SYSTEM CONSTRAINT: This is a SPOT TRADING system ONLY. Decisions MUST be limited to BUY (LONG) or HOLD. SHORT selling is NOT possible.
357
 
358
  🎯 STRATEGY CONTEXT:
359
- - Target Strategy: {target_strategy}
360
- - Recommended Strategy: {recommended_strategy}
361
- - Current Price: ${current_price}
362
- - System Score: {final_score_display}
363
- - Enhanced Score: {enhanced_score_display}
364
 
365
- {learning_feedback_section}
 
 
 
366
 
367
  📊 TECHNICAL INDICATORS (ALL TIMEFRAMES):
368
  {indicators_summary}
@@ -386,145 +407,67 @@ COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
386
  📋 REASONS FOR SYSTEM CANDIDACY (Layer 1 & 2 Screening):
387
  {chr(10).join([f"• {reason}" for reason in reasons]) if reasons else "No specific reasons provided"}
388
 
 
389
  🎯 TRADING DECISION INSTRUCTIONS (SPOT ONLY - LLM MUST ANALYZE PATTERNS AND DEFINE EXIT STRATEGY):
390
 
391
- 1. **PERFORM CHART PATTERN ANALYSIS:** Based *ONLY* on the provided 'RAW CANDLE DATA SUMMARY & STATISTICS' section above, identify relevant chart patterns (Triangles, Flags, Head & Shoulders, etc.) and candlestick patterns (Engulfing, Doji, etc.).
392
- 2. **INTEGRATE ALL DATA:** Combine YOUR pattern analysis with technicals, strategy analysis, whale activity, market context, news, and the 'LEARNING ENGINE FEEDBACK'.
393
  3. **ADHERE STRICTLY TO SPOT TRADING RULES:** Only decide 'BUY' (LONG) or 'HOLD'. DO NOT suggest 'SELL'.
394
  4. **DEFINE EXIT STRATEGY (CRITICAL):** If (and only if) action is 'BUY', you MUST define the dynamic exit strategy (Exit Profile) and its parameters. This profile will be executed by a separate tactical bot.
395
- - `"exit_profile"`: Choose one: "ATR_TRAILING" (Recommended for trends/breakouts), "FIXED_TARGET" (Recommended for mean reversion/scalping), "TIME_BASED" (Exit after X minutes regardless), "SIGNAL_BASED" (Emergency exit on opposite signal - *Use with caution*).
396
- - `"exit_parameters"`: Define parameters for the chosen profile.
397
- - For "ATR_TRAILING": {{"atr_multiplier": 2.0, "atr_period": 14, "break_even_trigger_percent": 1.5}} (break_even_trigger_percent moves stop to entry when profit hits 1.5%)
398
- - For "FIXED_TARGET": {{"time_stop_minutes": 120}} (Hard stop if target not hit in 120 mins)
399
- - For "TIME_BASED": {{"exit_after_minutes": 60}}
400
- - For "SIGNAL_BASED": {{"emergency_volume_spike_multiplier": 5.0}} (Exit if reverse volume spike > 5x average)
401
  5. **DEFINE HARD STOPS:** You must still provide the initial "hard" stop_loss (catastrophic failure stop) and the final "take_profit" target. The dynamic exit profile operates *within* these boundaries.
 
402
 
403
- OUTPUT FORMAT (JSON - SPOT ONLY - INCLUDE EXIT PROFILE):
404
  {{
405
  "action": "BUY/HOLD",
406
- "reasoning": "Detailed explanation integrating ALL data sources, starting with the patterns identified from the candle summary, and justifying the BUY or HOLD decision. Explain *why* the chosen exit_profile is appropriate.",
407
  "pattern_identified_by_llm": "Name of the primary pattern(s) identified (e.g., 'Bull Flag on 1H', 'No Clear Pattern')",
408
  "pattern_influence": "Explain how the identified pattern(s) influenced the decision.",
409
  "risk_assessment": "low/medium/high",
410
 
411
- "stop_loss": 0.000000, # Required if action is BUY (Hard stop loss), 0 if HOLD
412
- "take_profit": 0.000000, # Required if action is BUY (Final target), 0 if HOLD
413
 
414
- "exit_profile": "FIXED_TARGET", # (Required if BUY, "None" if HOLD). Choose from: "ATR_TRAILING", "FIXED_TARGET", "TIME_BASED", "SIGNAL_BASED"
415
- "exit_parameters": {{ "time_stop_minutes": 120 }}, # (Required if BUY, {{}} if HOLD). Must match the chosen exit_profile.
416
 
417
- "expected_target_minutes": 15, # Required if action is BUY (Time to reach final TP), 0 if HOLD
418
- "confidence_level": 0.85, # Confidence in the BUY or HOLD decision
419
- "strategy": "{target_strategy}", # The strategy context provided
420
  "whale_influence": "How whale data influenced the BUY/HOLD decision",
421
- "key_support_level": 0.000000, # Derived from candle data analysis
422
- "key_resistance_level": 0.000000, # Derived from candle data analysis
423
- "risk_reward_ratio": 2.5 # Calculated for the HARD SL/TP, 0 if HOLD
 
 
 
 
 
 
 
 
424
  }}
425
  """
426
  return prompt
 
427
 
428
 
429
  def _format_candle_data_comprehensive(self, ohlcv_data):
430
- """تنسيق شامل لبيانات الشموع الخام"""
431
- if not ohlcv_data:
432
- return "No raw candle data available for analysis"
433
-
434
- try:
435
- timeframes_available = []
436
- total_candles = 0
437
-
438
- for timeframe, candles in ohlcv_data.items():
439
- if candles and len(candles) >= 5:
440
- timeframes_available.append(f"{timeframe.upper()} ({len(candles)} candles)")
441
- total_candles += len(candles)
442
-
443
- if not timeframes_available:
444
- return "Insufficient candle data across all timeframes"
445
-
446
- summary = f"📊 Available Timeframes: {', '.join(timeframes_available)}\n"
447
- summary += f"📈 Total Candles Available: {total_candles}\n\n"
448
-
449
- raw_candle_analysis_text = self.pattern_engine._format_chart_data_for_llm(ohlcv_data)
450
-
451
- summary += raw_candle_analysis_text
452
-
453
- return summary
454
- except Exception as e:
455
- return f"Error formatting raw candle data: {str(e)}"
456
 
457
  def _analyze_timeframe_candles(self, candles, timeframe):
458
- """تحليل الشموع لإطار زمني محدد - (تستخدم داخلياً بواسطة _format_raw_candle_data)"""
459
- try:
460
- if len(candles) < 10:
461
- return f"Insufficient data ({len(candles)} candles)"
462
-
463
- recent_candles = candles[-15:]
464
-
465
- closes = [c[4] for c in recent_candles]
466
- opens = [c[1] for c in recent_candles]
467
- highs = [c[2] for c in recent_candles]
468
- lows = [c[3] for c in recent_candles]
469
- volumes = [c[5] for c in recent_candles]
470
-
471
- current_price = closes[-1]
472
- first_price = closes[0]
473
- price_change = ((current_price - first_price) / first_price) * 100 if first_price > 0 else 0
474
-
475
- if price_change > 2: trend = "🟢 UPTREND"
476
- elif price_change < -2: trend = "🔴 DOWNTREND"
477
- else: trend = "⚪ SIDEWAYS"
478
-
479
- high_max = max(highs)
480
- low_min = min(lows)
481
- volatility = ((high_max - low_min) / low_min) * 100 if low_min > 0 else 0
482
-
483
- avg_volume = sum(volumes) / len(volumes) if volumes else 1
484
- current_volume = volumes[-1] if volumes else 0
485
- volume_ratio = current_volume / avg_volume if avg_volume > 0 else 1
486
-
487
- green_candles = sum(1 for i in range(len(closes)) if closes[i] > opens[i])
488
- red_candles = len(closes) - green_candles
489
- candle_ratio = green_candles / len(closes) if closes else 0
490
-
491
- analysis = [
492
- f"📈 Trend: {trend} ({price_change:+.2f}%)",
493
- f"🌊 Volatility: {volatility:.2f}%",
494
- f"📦 Volume: {volume_ratio:.2f}x average",
495
- f"🕯️ Candles: {green_candles}🟢/{red_candles}🔴 ({candle_ratio:.1%} green)",
496
- f"💰 Range: {low_min:.6f} - {high_max:.6f}",
497
- f"🎯 Current: {current_price:.6f}"
498
- ]
499
-
500
- return "\n".join(analysis)
501
- except Exception as e:
502
- return f"Analysis error: {str(e)}"
503
 
504
  def _format_market_context(self, sentiment_data):
505
- """تنسيق سياق السوق"""
506
- if not sentiment_data or sentiment_data.get('data_quality', 'LOW') == 'LOW':
507
- return "Market context data not available or incomplete."
508
-
509
- btc_sentiment = sentiment_data.get('btc_sentiment', 'N/A')
510
- fear_greed = sentiment_data.get('fear_and_greed_index', 'N/A')
511
- market_trend = sentiment_data.get('market_trend', 'N/A')
512
-
513
- lines = [
514
- f"• Bitcoin Sentiment: {btc_sentiment}",
515
- f"• Fear & Greed Index: {fear_greed} ({sentiment_data.get('sentiment_class', 'Neutral')})",
516
- f"• Overall Market Trend: {market_trend.replace('_', ' ').title() if isinstance(market_trend, str) else 'N/A'}"
517
- ]
518
-
519
- general_whale = sentiment_data.get('general_whale_activity', {})
520
- if general_whale and general_whale.get('sentiment') != 'NEUTRAL':
521
- whale_sentiment = general_whale.get('sentiment', 'N/A')
522
- critical_alert = general_whale.get('critical_alert', False)
523
- lines.append(f"• General Whale Sentiment: {whale_sentiment.replace('_', ' ').title() if isinstance(whale_sentiment, str) else 'N/A'}")
524
- if critical_alert:
525
- lines.append(" ⚠️ CRITICAL WHALE ALERT ACTIVE")
526
-
527
- return "\n".join(lines)
528
 
529
 
530
  async def re_analyze_trade_async(self, trade_data: dict, processed_data: dict):
@@ -534,21 +477,39 @@ OUTPUT FORMAT (JSON - SPOT ONLY - INCLUDE EXIT PROFILE):
534
 
535
  ohlcv_data = processed_data.get('raw_ohlcv') or processed_data.get('ohlcv')
536
  if not ohlcv_data:
537
- print(f"⚠️ لا توجد بيانات شموع محدثة لـ {symbol} - تخطي إعادة التحليل")
538
  return None
539
 
540
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
541
  pattern_analysis = await self._get_pattern_analysis(processed_data)
542
  whale_data = processed_data.get('whale_data', {})
543
 
544
- best_learned_exit = "None"
545
- learning_feedback = "No learning data for re-analysis."
546
- if self.learning_engine and self.learning_engine.initialized:
547
- best_learned_exit = await self.learning_engine.get_best_exit_profile(original_strategy)
548
- if best_learned_exit != "unknown":
549
- learning_feedback = f"Learning System Feedback: For the '{original_strategy}' strategy, the '{best_learned_exit}' exit profile is typically best. Does this still apply?"
 
 
 
 
 
 
 
 
550
 
551
- prompt = self._create_re_analysis_prompt(trade_data, processed_data, news_text, pattern_analysis, whale_data, learning_feedback)
 
 
 
 
 
 
 
 
 
 
552
 
553
  if self.r2_service:
554
  analysis_data = {
@@ -556,12 +517,12 @@ OUTPUT FORMAT (JSON - SPOT ONLY - INCLUDE EXIT PROFILE):
556
  'entry_price': trade_data.get('entry_price'),
557
  'current_price': processed_data.get('current_price'),
558
  'original_strategy': original_strategy,
559
- 'learning_feedback_provided': learning_feedback,
560
- 'pattern_analysis': pattern_analysis,
561
  'whale_data_available': whale_data.get('data_available', False)
562
  }
563
  await self.r2_service.save_llm_prompts_async(
564
- symbol, 'trade_reanalysis_v2', prompt, analysis_data
565
  )
566
 
567
  async with self.semaphore:
@@ -573,11 +534,11 @@ OUTPUT FORMAT (JSON - SPOT ONLY - INCLUDE EXIT PROFILE):
573
  re_analysis_dict['whale_data_integrated'] = whale_data.get('data_available', False)
574
  return re_analysis_dict
575
  else:
576
- print(f"❌ فشل إعادة تحليل النموذج الضخم لـ {symbol}")
577
  return None
578
 
579
  except Exception as e:
580
- print(f"❌ خطأ في إعادة تحليل LLM: {e}")
581
  traceback.print_exc()
582
  return None
583
 
@@ -589,26 +550,25 @@ OUTPUT FORMAT (JSON - SPOT ONLY - INCLUDE EXIT PROFILE):
589
 
590
  decision_data = safe_json_parse(json_str)
591
  if not decision_data:
592
- print(f"❌ فشل تحليل JSON (safe_json_parse) لإعادة التحليل لـ {symbol}: {response_text}")
593
  return None
594
 
595
  action = decision_data.get('action')
596
  if action not in ['HOLD', 'CLOSE_TRADE', 'UPDATE_TRADE']:
597
- print(f"⚠️ النموذج اقترح إجراء إعادة تحليل غير مدعوم ({action}) لـ {symbol}. سيتم اعتباره HOLD.")
598
  decision_data['action'] = 'HOLD'
599
 
600
  if action == 'UPDATE_TRADE':
601
  required_update_fields = ['new_stop_loss', 'new_take_profit', 'new_exit_profile', 'new_exit_parameters']
602
  if not validate_required_fields(decision_data, required_update_fields):
603
- print(f"❌ حقول مطلوبة مفقودة لـ UPDATE_TRADE لـ {symbol}")
604
  missing = [f for f in required_update_fields if f not in decision_data]
605
  print(f" MIA: {missing}")
606
  decision_data['action'] = 'HOLD'
607
  elif not isinstance(decision_data['new_exit_parameters'], dict):
608
- print(f"❌ الحقل 'new_exit_parameters' ليس قاموساً صالحاً لـ {symbol}")
609
  decision_data['action'] = 'HOLD'
610
 
611
-
612
  strategy_value = decision_data.get('strategy')
613
  if not strategy_value or strategy_value == 'unknown':
614
  decision_data['strategy'] = fallback_strategy
@@ -618,17 +578,29 @@ OUTPUT FORMAT (JSON - SPOT ONLY - INCLUDE EXIT PROFILE):
618
  print(f"Error parsing re-analysis response for {symbol}: {e}")
619
  return None
620
 
621
- def _create_re_analysis_prompt(self, trade_data: dict, processed_data: dict, news_text: str, pattern_analysis: dict, whale_data: dict, learning_feedback: str) -> str:
 
 
 
 
 
 
 
 
 
 
 
622
  symbol = trade_data.get('symbol', 'N/A')
623
  entry_price = trade_data.get('entry_price', 'N/A')
624
  current_price = processed_data.get('current_price', 'N/A')
625
  strategy = trade_data.get('strategy', 'GENERIC')
626
- original_trade_type = "LONG" # SPOT only
627
 
628
  current_exit_profile = trade_data.get('decision_data', {}).get('exit_profile', 'N/A')
629
  current_exit_params = json.dumps(trade_data.get('decision_data', {}).get('exit_parameters', {}))
630
 
631
- learning_feedback_section = f"🧠 LEARNING ENGINE FEEDBACK:\n{learning_feedback}"
 
 
632
 
633
  try:
634
  price_change = ((current_price - entry_price) / entry_price) * 100 if entry_price else 0
@@ -650,15 +622,18 @@ TRADE RE-ANALYSIS FOR {symbol} (SPOT ONLY - Currently Open LONG Position)
650
  🚨 IMPORTANT SYSTEM CONSTRAINT: This is a SPOT TRADING system ONLY. The open trade is LONG. Re-analysis should decide to HOLD, CLOSE, or UPDATE this LONG position. SHORT selling is NOT possible.
651
 
652
  📊 CURRENT TRADE CONTEXT:
653
- - Strategy: {strategy}
654
- - Entry Price: {entry_price} (LONG position)
655
- - Current Price: {current_price}
656
- - Current Performance: {price_change_display}
657
- - Trade Age: {trade_data.get('hold_duration_minutes', 'N/A')} minutes
658
- - Current Exit Profile: {current_exit_profile}
659
- - Current Exit Parameters: {current_exit_params}
660
-
661
- {learning_feedback_section}
 
 
 
662
 
663
  🔄 UPDATED TECHNICAL ANALYSIS:
664
  {indicators_summary}
@@ -679,51 +654,63 @@ TRADE RE-ANALYSIS FOR {symbol} (SPOT ONLY - Currently Open LONG Position)
679
  📰 LATEST NEWS:
680
  {news_text if news_text else "No significant news found"}
681
 
 
682
  🎯 RE-ANALYSIS INSTRUCTIONS (SPOT - LONG POSITION):
683
 
684
- 1. **ANALYZE UPDATED DATA:** Evaluate if the original LONG thesis still holds based on the updated raw candle data summary, technicals, patterns (provided above), whale activity, market context, and learning feedback.
685
- 2. **VALIDATE PATTERNS:** Consider the 'UPDATED PATTERN ANALYSIS RESULTS' provided. Does the recent price action confirm or invalidate these patterns?
686
- 3. **DECIDE ACTION (HOLD/CLOSE/UPDATE):** Based on the comprehensive analysis, decide whether to HOLD, CLOSE_TRADE (exit the LONG position), or UPDATE_TRADE (adjust SL/TP and/or the Exit Profile for the LONG position).
687
- 4. **IF UPDATING (CRITICAL):** If action is UPDATE_TRADE, you MUST provide:
688
- - `new_stop_loss` (New hard stop)
689
- - `new_take_profit` (New final target)
690
- - `new_exit_profile`: (e.g., "ATR_TRAILING") - Can be the same or different.
691
- - `new_exit_parameters`: (e.g., {{"atr_multiplier": 1.5}}) - Must match the new profile.
692
- 5. **PROVIDE DETAILS:** Justify your decision clearly, integrating all data points.
693
 
694
  CRITICAL: The decision must be one of HOLD, CLOSE_TRADE, or UPDATE_TRADE for the existing LONG position.
695
 
696
  OUTPUT FORMAT (JSON - SPOT RE-ANALYSIS):
697
  {{
698
  "action": "HOLD/CLOSE_TRADE/UPDATE_TRADE",
699
- "reasoning": "Comprehensive justification for HOLD, CLOSE, or UPDATE of the LONG position, based on updated analysis. If UPDATE, explain why the new exit profile/parameters are better.",
700
 
701
- "new_stop_loss": 0.000000, # (Required if UPDATE_TRADE, else 0)
702
- "new_take_profit": 0.000000, # (Required if UPDATE_TRADE, else 0)
703
- "new_exit_profile": "None", # (Required if UPDATE_TRADE, else "None")
704
- "new_exit_parameters": {{}}, # (Required if UPDATE_TRADE, else {{}})
705
 
706
- "new_expected_minutes": 15, # If action is UPDATE_TRADE or HOLD (new expectation), else 0
707
- "confidence_level": 0.85, # Confidence in the re-analysis decision
708
- "strategy": "{strategy}", # Original strategy context
709
  "whale_influence_reanalysis": "How updated whale data influenced the decision",
710
  "pattern_influence_reanalysis": "How updated candle patterns AND provided patterns influenced the decision",
711
- "risk_adjustment": "low/medium/high" # Current risk level if HOLDING
 
 
 
 
 
 
 
 
712
  }}
713
  """
714
  return prompt
 
715
 
716
  def _format_pattern_analysis(self, pattern_analysis):
717
- """تنسيق تحليل الأنماط للنموذج الضخم"""
718
  if not pattern_analysis or not pattern_analysis.get('pattern_detected') or pattern_analysis.get('pattern_detected') == 'no_clear_pattern':
719
  return "No clear chart pattern detected by the system."
720
 
721
  pattern = pattern_analysis.get('pattern_detected', 'N/A')
722
  confidence = pattern_analysis.get('pattern_confidence', 0)
723
  direction = pattern_analysis.get('predicted_direction', 'N/A')
724
- timeframe = pattern_analysis.get('timeframe', 'N/A')
 
 
 
725
 
726
- return f"System Pattern Analysis: Detected '{pattern}' on {timeframe} timeframe with {confidence:.2f} confidence. Predicted direction: {direction}."
727
 
728
  @_rate_limit_nvidia_api
729
  async def _call_llm(self, prompt: str) -> str:
@@ -759,4 +746,4 @@ OUTPUT FORMAT (JSON - SPOT RE-ANALYSIS):
759
  print(f"❌ Final failure in _call_llm after backoff retries: {e}")
760
  raise
761
 
762
- print("✅ LLM Service loaded - V2 (Dynamic Exit Profiles & Learning Feedback) - FIX: NameError")
 
1
+ # LLM.py (Updated to integrate LearningHub and English-only prompts)
2
  import os, traceback, asyncio, json, time
3
+ import re
4
  from datetime import datetime
5
  from functools import wraps
6
  from backoff import on_exception, expo
7
  from openai import OpenAI, RateLimitError, APITimeoutError
8
  import numpy as np
9
  from sentiment_news import NewsFetcher
 
10
  from helpers import validate_required_fields, format_technical_indicators, format_strategy_scores, format_candle_data_for_pattern_analysis, format_whale_analysis_for_llm, parse_json_from_response
11
+ from ml_engine.processor import safe_json_parse
 
 
 
12
 
13
+ # (Note: PatternAnalysisEngine code remains unchanged, so it is omitted here for brevity)
14
+ # ... (PatternAnalysisEngine class code as before) ...
15
  class PatternAnalysisEngine:
16
  # --- (كود PatternAnalysisEngine كما هو بدون تغيير) ---
17
  def __init__(self, llm_service):
 
178
  self.news_fetcher = NewsFetcher()
179
  self.pattern_engine = PatternAnalysisEngine(self)
180
  self.semaphore = asyncio.Semaphore(5)
181
+ self.r2_service = None # (Set from app.py)
182
+
183
+ # 🔴 --- START OF CHANGE --- 🔴
184
+ # Renamed from self.learning_engine to self.learning_hub
185
+ self.learning_hub = None # (Set from app.py, expects LearningHubManager)
186
+ # 🔴 --- END OF CHANGE --- 🔴
187
 
188
  def _rate_limit_nvidia_api(func):
189
  @wraps(func)
 
199
 
200
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
201
  if not ohlcv_data:
202
+ print(f"⚠️ No candle data for {symbol} - skipping analysis")
203
  return None
204
 
205
  total_candles = sum(len(data) for data in ohlcv_data.values() if data) if ohlcv_data else 0
206
  timeframes_count = len([tf for tf, data in ohlcv_data.items() if data and len(data) >= 10]) if ohlcv_data else 0
207
 
 
 
 
208
  if total_candles < 30:
209
+ print(f" ⚠️ Insufficient candle data for {symbol}: {total_candles} candles")
210
  return None
211
 
212
  valid_timeframes = [tf for tf, candles in ohlcv_data.items() if candles and len(candles) >= 5]
213
  if not valid_timeframes:
214
+ print(f" ⚠️ No valid timeframes for {symbol}")
215
  return None
 
216
 
217
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
218
  whale_data = data_payload.get('whale_data', {})
219
 
220
+ # 🔴 --- START OF CHANGE --- 🔴
221
+ # (Fetch learning context from the new hub)
222
+ statistical_feedback = "No statistical learning data yet."
223
+ active_context_playbook = "No active learning rules available."
 
 
224
 
225
+ if self.learning_hub and self.learning_hub.initialized:
226
+ # 1. Get Statistical Feedback (Slow-learner)
227
+ statistical_feedback = await self.learning_hub.get_statistical_feedback_for_llm(target_strategy)
228
+
229
+ # 2. Get Active Context / Deltas (Fast-learner)
230
+ active_context_playbook = await self.learning_hub.get_active_context_for_llm(
231
+ domain="strategy",
232
+ query=f"{target_strategy} {symbol}" # (Query with strategy and symbol)
233
+ )
234
 
235
+ # (Pass new context to the prompt creator)
236
+ prompt = self._create_comprehensive_trading_prompt(
237
+ data_payload,
238
+ news_text,
239
+ None,
240
+ whale_data,
241
+ statistical_feedback,
242
+ active_context_playbook
243
+ )
244
+ # 🔴 --- END OF CHANGE --- 🔴
245
+
246
  if self.r2_service:
247
  analysis_data = {
248
  'symbol': symbol,
249
  'current_price': data_payload.get('current_price'),
 
250
  'enhanced_final_score': data_payload.get('enhanced_final_score'),
251
  'target_strategy': target_strategy,
252
+ 'statistical_feedback': statistical_feedback,
253
+ 'active_context_playbook': active_context_playbook,
254
  'whale_data_available': whale_data.get('data_available', False),
255
  'total_candles': total_candles,
256
  'timeframes_count': timeframes_count,
 
257
  'timestamp': datetime.now().isoformat()
258
  }
259
  await self.r2_service.save_llm_prompts_async(
260
+ symbol, 'comprehensive_trading_decision_v3_hub', prompt, analysis_data
261
  )
262
 
263
  async with self.semaphore:
 
271
  decision_dict['timeframes_analyzed'] = timeframes_count
272
  return decision_dict
273
  else:
274
+ print(f"❌ LLM parsing failed for {symbol} - no fallback decisions")
275
  return None
276
 
277
  except Exception as e:
278
+ print(f"❌ Error in get_trading_decision for {data_payload.get('symbol', 'unknown')}: {e}")
279
  traceback.print_exc()
280
  return None
281
 
 
283
  try:
284
  json_str = parse_json_from_response(response_text)
285
  if not json_str:
286
+ print(f"❌ Failed to extract JSON from LLM response for {symbol}")
287
  return None
288
 
289
  decision_data = safe_json_parse(json_str)
290
  if not decision_data:
291
+ print(f"❌ Failed to parse JSON (safe_json_parse) for {symbol}: {response_text}")
292
  return None
293
 
294
+ # (This check is for the trading decision, not the reflector response)
295
+ if fallback_strategy == "reflection" or fallback_strategy == "distillation":
296
+ # (If this is a reflector/curator call, just return the data)
297
+ return decision_data
298
+
299
  required_fields = [
300
  'action', 'reasoning', 'risk_assessment', 'stop_loss', 'take_profit',
301
  'expected_target_minutes', 'confidence_level', 'pattern_identified_by_llm',
302
  'exit_profile', 'exit_parameters'
303
  ]
304
  if not validate_required_fields(decision_data, required_fields):
305
+ print(f"❌ Missing required fields in LLM response for {symbol}")
306
  missing = [f for f in required_fields if f not in decision_data]
307
  print(f" MIA: {missing}")
308
  return None
309
 
310
  if not isinstance(decision_data['exit_parameters'], dict):
311
+ print(f"❌ 'exit_parameters' is not a valid dict for {symbol}")
312
  return None
313
 
314
  action = decision_data.get('action')
315
  if action not in ['BUY', 'HOLD']:
316
+ print(f"⚠️ LLM suggested unsupported action ({action}) for {symbol}. Forcing HOLD.")
317
  decision_data['action'] = 'HOLD'
318
 
319
  if decision_data['action'] == 'BUY':
 
327
 
328
  return decision_data
329
  except Exception as e:
330
+ print(f"❌ Error parsing LLM response for {symbol}: {e}")
331
  return None
332
 
333
  async def _get_pattern_analysis(self, data_payload):
334
+ # (Omitted for brevity - same as original file)
335
+ pass
336
+
337
+ # 🔴 --- START OF PROMPT CHANGE --- 🔴
338
+ def _create_comprehensive_trading_prompt(
339
+ self,
340
+ payload: dict,
341
+ news_text: str,
342
+ pattern_analysis: dict, # (This is the old system, now deprecated, but we leave the arg)
343
+ whale_data: dict,
344
+ statistical_feedback: str, # (NEW from Hub)
345
+ active_context_playbook: str # (NEW from Hub)
346
+ ) -> str:
347
+
348
  symbol = payload.get('symbol', 'N/A')
349
  current_price = payload.get('current_price', 'N/A')
350
  reasons = payload.get('reasons_for_candidacy', [])
 
353
  strategy_scores = payload.get('strategy_scores', {})
354
  recommended_strategy = payload.get('recommended_strategy', 'N/A')
355
  target_strategy = payload.get('target_strategy', 'GENERIC')
356
+ enhanced_final_score = payload.get('enhanced_final_score', 0)
 
 
357
 
 
 
 
 
358
  enhanced_score_display = f"{enhanced_final_score:.3f}" if isinstance(enhanced_final_score, (int, float)) else str(enhanced_final_score)
 
359
 
360
  indicators_summary = format_technical_indicators(advanced_indicators)
361
  strategies_summary = format_strategy_scores(strategy_scores, recommended_strategy)
362
  whale_analysis_section = format_whale_analysis_for_llm(whale_data)
363
+ ohlcv_data = payload.get('raw_ohlcv') or payload.get('ohlcv', {})
364
  candle_data_section = self._format_candle_data_comprehensive(ohlcv_data)
365
  market_context_section = self._format_market_context(sentiment_data)
366
 
367
+ # (New sections from the Learning Hub)
368
+ statistical_feedback_section = f"🧠 STATISTICAL FEEDBACK (Slow-Learner):\n{statistical_feedback}"
369
+ playbook_section = f"📚 LEARNING PLAYBOOK (Fast-Learner Active Rules):\n{active_context_playbook}"
370
+
371
 
372
  prompt = f"""
373
  COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
 
375
  🚨 IMPORTANT SYSTEM CONSTRAINT: This is a SPOT TRADING system ONLY. Decisions MUST be limited to BUY (LONG) or HOLD. SHORT selling is NOT possible.
376
 
377
  🎯 STRATEGY CONTEXT:
378
+ * Target Strategy: {target_strategy}
379
+ * Recommended Strategy: {recommended_strategy}
380
+ * Current Price: ${current_price}
381
+ * Enhanced System Score: {enhanced_score_display}
 
382
 
383
+ --- LEARNING HUB INPUT (CRITICAL) ---
384
+ {playbook_section}
385
+ {statistical_feedback_section}
386
+ --- END OF LEARNING INPUT ---
387
 
388
  📊 TECHNICAL INDICATORS (ALL TIMEFRAMES):
389
  {indicators_summary}
 
407
  📋 REASONS FOR SYSTEM CANDIDACY (Layer 1 & 2 Screening):
408
  {chr(10).join([f"• {reason}" for reason in reasons]) if reasons else "No specific reasons provided"}
409
 
410
+ ---
411
  🎯 TRADING DECISION INSTRUCTIONS (SPOT ONLY - LLM MUST ANALYZE PATTERNS AND DEFINE EXIT STRATEGY):
412
 
413
+ 1. **PERFORM CHART PATTERN ANALYSIS:** Based *ONLY* on the provided 'RAW CANDLE DATA SUMMARY & STATISTICS' section, identify relevant chart patterns (Triangles, Flags, etc.) and candlestick patterns (Engulfing, Doji, etc.).
414
+ 2. **INTEGRATE ALL DATA:** Combine YOUR pattern analysis with technicals, strategy analysis, whale activity, market context, news, and (most importantly) the 'LEARNING HUB INPUT'.
415
  3. **ADHERE STRICTLY TO SPOT TRADING RULES:** Only decide 'BUY' (LONG) or 'HOLD'. DO NOT suggest 'SELL'.
416
  4. **DEFINE EXIT STRATEGY (CRITICAL):** If (and only if) action is 'BUY', you MUST define the dynamic exit strategy (Exit Profile) and its parameters. This profile will be executed by a separate tactical bot.
417
+ * `"exit_profile"`: Choose one: "ATR_TRAILING" (Recommended for trends/breakouts), "FIXED_TARGET" (Recommended for mean reversion/scalping), "TIME_BASED" (Exit after X minutes regardless).
418
+ * `"exit_parameters"`: Define parameters for the chosen profile, respecting the 'Statistical Feedback'.
419
+ * For "ATR_TRAILING": {{"atr_multiplier": 2.0, "atr_period": 14, "break_even_trigger_percent": 1.5}} (break_even_trigger_percent moves stop to entry when profit hits 1.5%)
420
+ * For "FIXED_TARGET": {{"time_stop_minutes": 120}} (Hard stop if target not hit in 120 mins)
421
+ * For "TIME_BASED": {{"exit_after_minutes": 60}}
 
422
  5. **DEFINE HARD STOPS:** You must still provide the initial "hard" stop_loss (catastrophic failure stop) and the final "take_profit" target. The dynamic exit profile operates *within* these boundaries.
423
+ 6. **SELF-CRITIQUE (Point 4 of Plan):** After defining the JSON, perform a self-critique. List potential failure modes for your decision and confirm your final answer.
424
 
425
+ OUTPUT FORMAT (JSON - SPOT ONLY - INCLUDE EXIT PROFILE AND SELF-CRITIQUE):
426
  {{
427
  "action": "BUY/HOLD",
428
+ "reasoning": "Detailed explanation integrating ALL data sources, starting with the patterns identified from the candle summary, and justifying the BUY or HOLD decision. Explain *why* the chosen exit_profile is appropriate, considering the Learning Hub feedback.",
429
  "pattern_identified_by_llm": "Name of the primary pattern(s) identified (e.g., 'Bull Flag on 1H', 'No Clear Pattern')",
430
  "pattern_influence": "Explain how the identified pattern(s) influenced the decision.",
431
  "risk_assessment": "low/medium/high",
432
 
433
+ "stop_loss": 0.000000,
434
+ "take_profit": 0.000000,
435
 
436
+ "exit_profile": "FIXED_TARGET",
437
+ "exit_parameters": {{ "time_stop_minutes": 120 }},
438
 
439
+ "expected_target_minutes": 15,
440
+ "confidence_level": 0.85,
441
+ "strategy": "{target_strategy}",
442
  "whale_influence": "How whale data influenced the BUY/HOLD decision",
443
+ "key_support_level": 0.000000,
444
+ "key_resistance_level": 0.000000,
445
+ "risk_reward_ratio": 2.5,
446
+
447
+ "self_critique": {{
448
+ "failure_modes": [
449
+ "What is the first reason this decision could fail? (e.g., 'The identified pattern is a false breakout.')",
450
+ "What is the second reason? (e.g., 'Whale data shows distribution, contradicting the technicals.')"
451
+ ],
452
+ "confidence_adjustment_reason": "Brief reason if confidence was adjusted post-critique."
453
+ }}
454
  }}
455
  """
456
  return prompt
457
+ # 🔴 --- END OF PROMPT CHANGE --- 🔴
458
 
459
 
460
  def _format_candle_data_comprehensive(self, ohlcv_data):
461
+ # (Omitted for brevity - same as original file)
462
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
 
464
  def _analyze_timeframe_candles(self, candles, timeframe):
465
+ # (Omitted for brevity - same as original file)
466
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
 
468
  def _format_market_context(self, sentiment_data):
469
+ # (Omitted for brevity - same as original file)
470
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
 
472
 
473
  async def re_analyze_trade_async(self, trade_data: dict, processed_data: dict):
 
477
 
478
  ohlcv_data = processed_data.get('raw_ohlcv') or processed_data.get('ohlcv')
479
  if not ohlcv_data:
480
+ print(f"⚠️ No updated candle data for {symbol} - skipping re-analysis")
481
  return None
482
 
483
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
484
  pattern_analysis = await self._get_pattern_analysis(processed_data)
485
  whale_data = processed_data.get('whale_data', {})
486
 
487
+ # 🔴 --- START OF CHANGE --- 🔴
488
+ # (Fetch learning context from the new hub for re-analysis)
489
+ statistical_feedback = "No statistical learning data yet."
490
+ active_context_playbook = "No active learning rules available."
491
+
492
+ if self.learning_hub and self.learning_hub.initialized:
493
+ # 1. Get Statistical Feedback (Slow-learner)
494
+ statistical_feedback = await self.learning_hub.get_statistical_feedback_for_llm(original_strategy)
495
+
496
+ # 2. Get Active Context / Deltas (Fast-learner)
497
+ active_context_playbook = await self.learning_hub.get_active_context_for_llm(
498
+ domain="strategy",
499
+ query=f"{original_strategy} {symbol} re-analysis"
500
+ )
501
 
502
+ # (Pass new context to the prompt creator)
503
+ prompt = self._create_re_analysis_prompt(
504
+ trade_data,
505
+ processed_data,
506
+ news_text,
507
+ pattern_analysis,
508
+ whale_data,
509
+ statistical_feedback,
510
+ active_context_playbook
511
+ )
512
+ # 🔴 --- END OF CHANGE --- 🔴
513
 
514
  if self.r2_service:
515
  analysis_data = {
 
517
  'entry_price': trade_data.get('entry_price'),
518
  'current_price': processed_data.get('current_price'),
519
  'original_strategy': original_strategy,
520
+ 'statistical_feedback': statistical_feedback,
521
+ 'active_context_playbook': active_context_playbook,
522
  'whale_data_available': whale_data.get('data_available', False)
523
  }
524
  await self.r2_service.save_llm_prompts_async(
525
+ symbol, 'trade_reanalysis_v3_hub', prompt, analysis_data
526
  )
527
 
528
  async with self.semaphore:
 
534
  re_analysis_dict['whale_data_integrated'] = whale_data.get('data_available', False)
535
  return re_analysis_dict
536
  else:
537
+ print(f"❌ LLM re-analysis parsing failed for {symbol}")
538
  return None
539
 
540
  except Exception as e:
541
+ print(f"❌ Error in LLM re-analysis: {e}")
542
  traceback.print_exc()
543
  return None
544
 
 
550
 
551
  decision_data = safe_json_parse(json_str)
552
  if not decision_data:
553
+ print(f"❌ Failed to parse JSON (safe_json_parse) for re-analysis of {symbol}: {response_text}")
554
  return None
555
 
556
  action = decision_data.get('action')
557
  if action not in ['HOLD', 'CLOSE_TRADE', 'UPDATE_TRADE']:
558
+ print(f"⚠️ LLM suggested unsupported re-analysis action ({action}) for {symbol}. Forcing HOLD.")
559
  decision_data['action'] = 'HOLD'
560
 
561
  if action == 'UPDATE_TRADE':
562
  required_update_fields = ['new_stop_loss', 'new_take_profit', 'new_exit_profile', 'new_exit_parameters']
563
  if not validate_required_fields(decision_data, required_update_fields):
564
+ print(f"❌ Missing required fields for UPDATE_TRADE for {symbol}")
565
  missing = [f for f in required_update_fields if f not in decision_data]
566
  print(f" MIA: {missing}")
567
  decision_data['action'] = 'HOLD'
568
  elif not isinstance(decision_data['new_exit_parameters'], dict):
569
+ print(f"❌ 'new_exit_parameters' is not a valid dict for {symbol}")
570
  decision_data['action'] = 'HOLD'
571
 
 
572
  strategy_value = decision_data.get('strategy')
573
  if not strategy_value or strategy_value == 'unknown':
574
  decision_data['strategy'] = fallback_strategy
 
578
  print(f"Error parsing re-analysis response for {symbol}: {e}")
579
  return None
580
 
581
+ # 🔴 --- START OF PROMPT CHANGE --- 🔴
582
+ def _create_re_analysis_prompt(
583
+ self,
584
+ trade_data: dict,
585
+ processed_data: dict,
586
+ news_text: str,
587
+ pattern_analysis: dict,
588
+ whale_data: dict,
589
+ statistical_feedback: str, # (NEW from Hub)
590
+ active_context_playbook: str # (NEW from Hub)
591
+ ) -> str:
592
+
593
  symbol = trade_data.get('symbol', 'N/A')
594
  entry_price = trade_data.get('entry_price', 'N/A')
595
  current_price = processed_data.get('current_price', 'N/A')
596
  strategy = trade_data.get('strategy', 'GENERIC')
 
597
 
598
  current_exit_profile = trade_data.get('decision_data', {}).get('exit_profile', 'N/A')
599
  current_exit_params = json.dumps(trade_data.get('decision_data', {}).get('exit_parameters', {}))
600
 
601
+ # (New sections from the Learning Hub)
602
+ statistical_feedback_section = f"🧠 STATISTICAL FEEDBACK (Slow-Learner):\n{statistical_feedback}"
603
+ playbook_section = f"📚 LEARNING PLAYBOOK (Fast-Learner Active Rules):\n{active_context_playbook}"
604
 
605
  try:
606
  price_change = ((current_price - entry_price) / entry_price) * 100 if entry_price else 0
 
622
  🚨 IMPORTANT SYSTEM CONSTRAINT: This is a SPOT TRADING system ONLY. The open trade is LONG. Re-analysis should decide to HOLD, CLOSE, or UPDATE this LONG position. SHORT selling is NOT possible.
623
 
624
  📊 CURRENT TRADE CONTEXT:
625
+ * Strategy: {strategy}
626
+ * Entry Price: {entry_price} (LONG position)
627
+ * Current Price: {current_price}
628
+ * Current Performance: {price_change_display}
629
+ * Trade Age: {trade_data.get('hold_duration_minutes', 'N/A')} minutes
630
+ * Current Exit Profile: {current_exit_profile}
631
+ * Current Exit Parameters: {current_exit_params}
632
+
633
+ --- LEARNING HUB INPUT (CRITICAL) ---
634
+ {playbook_section}
635
+ {statistical_feedback_section}
636
+ --- END OF LEARNING INPUT ---
637
 
638
  🔄 UPDATED TECHNICAL ANALYSIS:
639
  {indicators_summary}
 
654
  📰 LATEST NEWS:
655
  {news_text if news_text else "No significant news found"}
656
 
657
+ ---
658
  🎯 RE-ANALYSIS INSTRUCTIONS (SPOT - LONG POSITION):
659
 
660
+ 1. **ANALYZE UPDATED DATA:** Evaluate if the original LONG thesis still holds based on the updated raw candle data, technicals, patterns, whale activity, market context, and (most importantly) the 'LEARNING HUB INPUT'.
661
+ 2. **DECIDE ACTION (HOLD/CLOSE/UPDATE):** Based on the comprehensive analysis, decide whether to HOLD, CLOSE_TRADE (exit the LONG position), or UPDATE_TRADE (adjust SL/TP and/or the Exit Profile for the LONG position).
662
+ 3. **IF UPDATING (CRITICAL):** If action is UPDATE_TRADE, you MUST provide:
663
+ * `new_stop_loss` (New hard stop)
664
+ * `new_take_profit` (New final target)
665
+ * `new_exit_profile`: (e.g., "ATR_TRAILING") - Can be the same or different.
666
+ * `new_exit_parameters`: (e.g., {{"atr_multiplier": 1.5}}) - Must match the new profile.
667
+ 4. **SELF-CRITIQUE:** Perform a self-critique. What is the risk of this re-analysis decision?
 
668
 
669
  CRITICAL: The decision must be one of HOLD, CLOSE_TRADE, or UPDATE_TRADE for the existing LONG position.
670
 
671
  OUTPUT FORMAT (JSON - SPOT RE-ANALYSIS):
672
  {{
673
  "action": "HOLD/CLOSE_TRADE/UPDATE_TRADE",
674
+ "reasoning": "Comprehensive justification for HOLD, CLOSE, or UPDATE of the LONG position, based on updated analysis. If UPDATE, explain why the new exit profile/parameters are better, referencing the Learning Hub input.",
675
 
676
+ "new_stop_loss": 0.000000,
677
+ "new_take_profit": 0.000000,
678
+ "new_exit_profile": "None",
679
+ "new_exit_parameters": {{}},
680
 
681
+ "new_expected_minutes": 15,
682
+ "confidence_level": 0.85,
683
+ "strategy": "{strategy}",
684
  "whale_influence_reanalysis": "How updated whale data influenced the decision",
685
  "pattern_influence_reanalysis": "How updated candle patterns AND provided patterns influenced the decision",
686
+ "risk_adjustment": "low/medium/high",
687
+
688
+ "self_critique": {{
689
+ "failure_modes": [
690
+ "What is the primary risk of this new decision? (e.g., 'Holding this position increases exposure to market volatility.')",
691
+ "What is the second risk? (e.g., 'Closing now might miss a future rebound.')"
692
+ ],
693
+ "confidence_adjustment_reason": "Brief reason if confidence was adjusted post-critique."
694
+ }}
695
  }}
696
  """
697
  return prompt
698
+ # 🔴 --- END OF PROMPT CHANGE --- 🔴
699
 
700
  def _format_pattern_analysis(self, pattern_analysis):
701
+ """Helper to format pattern analysis for the LLM"""
702
  if not pattern_analysis or not pattern_analysis.get('pattern_detected') or pattern_analysis.get('pattern_detected') == 'no_clear_pattern':
703
  return "No clear chart pattern detected by the system."
704
 
705
  pattern = pattern_analysis.get('pattern_detected', 'N/A')
706
  confidence = pattern_analysis.get('pattern_confidence', 0)
707
  direction = pattern_analysis.get('predicted_direction', 'N/A')
708
+ timeframe = pattern_analysis.get('timeframe', 'N/A') # (This key might not exist, need to check patterns.py)
709
+
710
+ # (Assuming timeframe is part of the top-level analysis)
711
+ tf_display = f"on {timeframe} timeframe" if timeframe != 'N/A' else ""
712
 
713
+ return f"System Pattern Analysis: Detected '{pattern}' {tf_display} with {confidence:.2f} confidence. Predicted direction: {direction}."
714
 
715
  @_rate_limit_nvidia_api
716
  async def _call_llm(self, prompt: str) -> str:
 
746
  print(f"❌ Final failure in _call_llm after backoff retries: {e}")
747
  raise
748
 
749
+ print("✅ LLM Service loaded - V3 (Integrated Learning Hub, English-only Prompts, Self-Critique)")