Riy777 commited on
Commit
69e3ab6
·
verified ·
1 Parent(s): fa36606

Update LLM.py

Browse files
Files changed (1) hide show
  1. LLM.py +144 -86
LLM.py CHANGED
@@ -1,4 +1,4 @@
1
- # LLM.py
2
  import os, traceback, asyncio, json, time
3
  import re # ✅ استيراد مكتبة re
4
  from datetime import datetime
@@ -7,9 +7,8 @@ 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"
@@ -164,16 +163,11 @@ class PatternAnalysisEngine:
164
  'resistance': 0
165
  }
166
 
167
- # --- (analyze_chart_patterns و _parse_pattern_response لم تعد تُستخدم مباشرة للـ LLM prompt الأساسي، لكن قد تُستخدم في مكان آخر) ---
168
  async def analyze_chart_patterns(self, symbol, ohlcv_data):
169
- # ... (الكود الأصلي هنا - لم يتم تغييره)
170
- # هذا التحليل يتم الآن في الطبقة الثانية (MLProcessor)
171
- # ولا يتم إرسال نتيجته إلى الـ LLM في الطبقة الثالثة
172
- pass # Placeholder to indicate no change here
173
 
174
  def _parse_pattern_response(self, response_text):
175
- # ... (الكود الأصلي هنا - لم يتم تغييره)
176
- pass # Placeholder to indicate no change here
177
 
178
 
179
  class LLMService:
@@ -183,9 +177,10 @@ class LLMService:
183
  self.temperature = temperature
184
  self.client = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=self.api_key)
185
  self.news_fetcher = NewsFetcher()
186
- self.pattern_engine = PatternAnalysisEngine(self) # Still needed for formatting candle data
187
  self.semaphore = asyncio.Semaphore(5)
188
  self.r2_service = None # سيتم تعيينه من app.py
 
189
 
190
  def _rate_limit_nvidia_api(func):
191
  @wraps(func)
@@ -220,12 +215,17 @@ class LLMService:
220
  print(f" ✅ أطر زمنية صالحة لـ {symbol}: {', '.join(valid_timeframes)}")
221
 
222
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
223
- # ❗ لا نستخدم _get_pattern_analysis هنا لأن التحليل المسبق لا يُرسل
224
- # pattern_analysis = await self._get_pattern_analysis(data_payload) # <-- تم إزالة هذا السطر
225
  whale_data = data_payload.get('whale_data', {})
226
 
227
- # تعديل: تمرير None لـ pattern_analysis
228
- prompt = self._create_comprehensive_trading_prompt(data_payload, news_text, None, whale_data)
 
 
 
 
 
 
 
229
 
230
  if self.r2_service:
231
  analysis_data = {
@@ -234,7 +234,7 @@ class LLMService:
234
  'final_score': data_payload.get('final_score'),
235
  'enhanced_final_score': data_payload.get('enhanced_final_score'),
236
  'target_strategy': target_strategy,
237
- 'pattern_analysis': None, # تم التغيير: لا يوجد تحليل مسبق
238
  'whale_data_available': whale_data.get('data_available', False),
239
  'total_candles': total_candles,
240
  'timeframes_count': timeframes_count,
@@ -242,7 +242,7 @@ class LLMService:
242
  'timestamp': datetime.now().isoformat()
243
  }
244
  await self.r2_service.save_llm_prompts_async(
245
- symbol, 'comprehensive_trading_decision_llm_pattern', prompt, analysis_data # غيرت النوع قليلاً للتمييز
246
  )
247
 
248
  async with self.semaphore:
@@ -251,7 +251,6 @@ class LLMService:
251
  decision_dict = self._parse_llm_response_enhanced(response, target_strategy, symbol)
252
  if decision_dict:
253
  decision_dict['model_source'] = self.model_name
254
- # ❗ لا نضيف pattern_analysis هنا لأنه غير متوفر
255
  decision_dict['whale_data_integrated'] = whale_data.get('data_available', False)
256
  decision_dict['total_candles_analyzed'] = total_candles
257
  decision_dict['timeframes_analyzed'] = timeframes_count
@@ -277,10 +276,22 @@ class LLMService:
277
  print(f"❌ فشل تحليل JSON (safe_json_parse) لـ {symbol}: {response_text}")
278
  return None
279
 
280
- # تعديل: تحديث الحقول المطلوبة لتعكس التغييرات في الـ prompt
281
- required_fields = ['action', 'reasoning', 'risk_assessment', 'stop_loss', 'take_profit', 'expected_target_minutes', 'confidence_level', 'pattern_identified_by_llm']
 
 
 
 
282
  if not validate_required_fields(decision_data, required_fields):
283
  print(f"❌ حقول مطلوبة مفقودة في استجابة النموذج لـ {symbol}")
 
 
 
 
 
 
 
 
284
  return None
285
 
286
  action = decision_data.get('action')
@@ -302,22 +313,18 @@ class LLMService:
302
  print(f"❌ خطأ في تحليل استجابة النموذج لـ {symbol}: {e}")
303
  return None
304
 
305
- # ❗ دالة _get_pattern_analysis لم تعد مستخدمة هنا، لكن قد تُستخدم في إعادة التحليل
306
  async def _get_pattern_analysis(self, data_payload):
307
- # ... (الكود الأصلي هنا)
308
  try:
309
  symbol = data_payload['symbol']
310
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
311
  if ohlcv_data:
312
- # Note: This analysis result is used ONLY for re-analysis prompts now.
313
  return await self.pattern_engine.analyze_chart_patterns(symbol, ohlcv_data)
314
  return None
315
  except Exception as e:
316
  print(f"❌ فشل تحليل الأنماط (قد يكون لإعادة التحليل) لـ {data_payload.get('symbol')}: {e}")
317
  return None
318
 
319
- def _create_comprehensive_trading_prompt(self, payload: dict, news_text: str, pattern_analysis: dict, whale_data: dict) -> str:
320
- # ❗ pattern_analysis لم يعد مستخدماً هنا وسيتم تجاهله (أو يكون None)
321
  symbol = payload.get('symbol', 'N/A')
322
  current_price = payload.get('current_price', 'N/A')
323
  reasons = payload.get('reasons_for_candidacy', [])
@@ -331,15 +338,16 @@ class LLMService:
331
  ohlcv_data = payload.get('raw_ohlcv') or payload.get('ohlcv', {})
332
 
333
  final_score_display = f"{final_score:.3f}" if isinstance(final_score, (int, float)) else str(final_score)
334
- enhanced_score_display = f"{enhanced_final_score:.3f}" if isinstance(enhanced_final_score, (int, float)) else str(enhanced_final_score)
335
 
336
  indicators_summary = format_technical_indicators(advanced_indicators)
337
  strategies_summary = format_strategy_scores(strategy_scores, recommended_strategy)
338
- # ❗ تم حذف pattern_summary = self._format_pattern_analysis(pattern_analysis)
339
  whale_analysis_section = format_whale_analysis_for_llm(whale_data)
340
- candle_data_section = self._format_candle_data_comprehensive(ohlcv_data) # يعتمد على PatternAnalysisEngine للتنسيق
341
  market_context_section = self._format_market_context(sentiment_data)
342
- # ⚠️ ملاحظة: تأكد من أن market_context_section ليست فارغة
 
 
343
 
344
  prompt = f"""
345
  COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
@@ -353,6 +361,8 @@ COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
353
  - System Score: {final_score_display}
354
  - Enhanced Score: {enhanced_score_display}
355
 
 
 
356
  📊 TECHNICAL INDICATORS (ALL TIMEFRAMES):
357
  {indicators_summary}
358
 
@@ -375,40 +385,46 @@ COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
375
  📋 REASONS FOR SYSTEM CANDIDACY (Layer 1 & 2 Screening):
376
  {chr(10).join([f"• {reason}" for reason in reasons]) if reasons else "No specific reasons provided"}
377
 
378
- 🎯 TRADING DECISION INSTRUCTIONS (SPOT ONLY - LLM MUST ANALYZE PATTERNS):
379
-
380
- 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, Double Tops/Bottoms, etc.) and candlestick patterns (Engulfing, Doji, Hammer, etc.) across the available timeframes. Determine the likely direction and strength implied by these patterns.
381
- 2. **INTEGRATE ALL DATA:** Combine YOUR pattern analysis with the technical indicators, strategy analysis, whale activity, market context, and news.
382
- 3. **ADHERE STRICTLY TO SPOT TRADING RULES:** Only decide 'BUY' (for a LONG position) if your comprehensive analysis indicates a high probability of upward movement. If the analysis suggests downward movement or high uncertainty, the ONLY valid action is 'HOLD'. DO NOT suggest 'SELL'.
383
- 4. **RISK ASSESSMENT:** Evaluate the risk-reward ratio specifically for a potential 'BUY' (LONG) trade based on support/resistance levels derived from the candle data summary.
384
- 5. **JUSTIFY YOUR DECISION:** Clearly explain your reasoning, detailing how YOUR pattern analysis and other data points led to the final 'BUY' or 'HOLD' decision.
385
-
386
- CRITICAL: You MUST explicitly state the pattern(s) you identified from the candle data summary and how they influenced your decision. Provide specific price levels (Stop Loss, Take Profit) suitable for a BUY/LONG trade if recommending 'BUY'.
387
-
388
- OUTPUT FORMAT (JSON - SPOT ONLY):
 
 
 
 
389
  {{
390
  "action": "BUY/HOLD",
391
- "reasoning": "Detailed explanation integrating ALL data sources, starting with the patterns identified from the candle summary, and justifying the BUY or HOLD decision based on SPOT trading logic.",
392
- "pattern_identified_by_llm": "Name of the primary pattern(s) identified by analyzing the provided candle data summary (e.g., 'Bull Flag on 1H', 'Double Bottom on 4H', 'No Clear Pattern')",
393
- "pattern_influence": "Explain how the identified pattern(s) influenced the decision (e.g., 'Bull flag confirmed uptrend, supporting BUY', 'Descending triangle indicated weakness, leading to HOLD')",
394
  "risk_assessment": "low/medium/high",
395
- "stop_loss": 0.000000, # Required if action is BUY, 0 if HOLD
396
- "take_profit": 0.000000, # Required if action is BUY, 0 if HOLD
397
- "expected_target_minutes": 15, # Required if action is BUY, 0 if HOLD
 
 
 
 
 
398
  "confidence_level": 0.85, # Confidence in the BUY or HOLD decision
399
  "strategy": "{target_strategy}", # The strategy context provided
400
- "whale_influence": "How whale data influenced the BUY/HOLD decision (e.g., 'Lack of whale activity supported HOLD', 'Whale buying pressure reinforced BUY')",
401
  "key_support_level": 0.000000, # Derived from candle data analysis
402
  "key_resistance_level": 0.000000, # Derived from candle data analysis
403
- "risk_reward_ratio": 2.5 # Calculated ONLY for a potential BUY trade, 0 if HOLD
404
  }}
405
  """
406
  return prompt
407
 
408
 
409
- # ❗ تم حذف دالة _format_pattern_analysis لأنها لم تعد مستخدمة هنا
410
-
411
-
412
  def _format_candle_data_comprehensive(self, ohlcv_data):
413
  """تنسيق شامل لبيانات الشموع الخام"""
414
  if not ohlcv_data:
@@ -429,8 +445,6 @@ OUTPUT FORMAT (JSON - SPOT ONLY):
429
  summary = f"📊 Available Timeframes: {', '.join(timeframes_available)}\n"
430
  summary += f"📈 Total Candles Available: {total_candles}\n\n"
431
 
432
- # استخدام محرك الأنماط فقط لتنسيق البيانات الخام نصياً
433
- # The LLM will perform the actual pattern *analysis* based on this text.
434
  raw_candle_analysis_text = self.pattern_engine._format_chart_data_for_llm(ohlcv_data)
435
 
436
  summary += raw_candle_analysis_text
@@ -488,7 +502,6 @@ OUTPUT FORMAT (JSON - SPOT ONLY):
488
 
489
  def _format_market_context(self, sentiment_data):
490
  """تنسيق سياق السوق"""
491
- # ⚠️ تأكد من أن هذه الدالة تتلقى بيانات صالحة من app.py/data_manager.py
492
  if not sentiment_data or sentiment_data.get('data_quality', 'LOW') == 'LOW':
493
  return "Market context data not available or incomplete."
494
 
@@ -497,13 +510,11 @@ OUTPUT FORMAT (JSON - SPOT ONLY):
497
  market_trend = sentiment_data.get('market_trend', 'N/A') # e.g., 'bull_market', 'bear_market', 'sideways_market'
498
 
499
  lines = [
500
- # "🌍 GLOBAL MARKET CONTEXT:", # Changed title for clarity
501
  f"• Bitcoin Sentiment: {btc_sentiment}",
502
  f"• Fear & Greed Index: {fear_greed} ({sentiment_data.get('sentiment_class', 'Neutral')})",
503
  f"• Overall Market Trend: {market_trend.replace('_', ' ').title() if isinstance(market_trend, str) else 'N/A'}"
504
  ]
505
 
506
- # Including General Whale Activity Sentiment if available
507
  general_whale = sentiment_data.get('general_whale_activity', {})
508
  if general_whale and general_whale.get('sentiment') != 'NEUTRAL': # Only show if not neutral
509
  whale_sentiment = general_whale.get('sentiment', 'N/A')
@@ -526,12 +537,18 @@ OUTPUT FORMAT (JSON - SPOT ONLY):
526
  return None
527
 
528
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
529
- # ❗ نحتاج إلى تحليل الأنماط *هنا* لإعادة التحليل
530
  pattern_analysis = await self._get_pattern_analysis(processed_data)
531
  whale_data = processed_data.get('whale_data', {})
532
 
533
- # تعديل: تمرير pattern_analysis لدالة إنشاء الـ prompt
534
- prompt = self._create_re_analysis_prompt(trade_data, processed_data, news_text, pattern_analysis, whale_data)
 
 
 
 
 
 
 
535
 
536
  if self.r2_service:
537
  analysis_data = {
@@ -539,11 +556,12 @@ OUTPUT FORMAT (JSON - SPOT ONLY):
539
  'entry_price': trade_data.get('entry_price'),
540
  'current_price': processed_data.get('current_price'),
541
  'original_strategy': original_strategy,
542
- 'pattern_analysis': pattern_analysis, # نتيجة التحليل المسبق
 
543
  'whale_data_available': whale_data.get('data_available', False)
544
  }
545
  await self.r2_service.save_llm_prompts_async(
546
- symbol, 'trade_reanalysis_with_pattern', prompt, analysis_data # تمييز النوع
547
  )
548
 
549
  async with self.semaphore:
@@ -579,6 +597,19 @@ OUTPUT FORMAT (JSON - SPOT ONLY):
579
  print(f"⚠️ النموذج اقترح إجراء إعادة تحليل غير مدعوم ({action}) لـ {symbol}. سيتم اعتباره HOLD.")
580
  decision_data['action'] = 'HOLD'
581
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  strategy_value = decision_data.get('strategy')
583
  if not strategy_value or strategy_value == 'unknown':
584
  decision_data['strategy'] = fallback_strategy
@@ -588,13 +619,19 @@ OUTPUT FORMAT (JSON - SPOT ONLY):
588
  print(f"Error parsing re-analysis response for {symbol}: {e}")
589
  return None
590
 
591
- def _create_re_analysis_prompt(self, trade_data: dict, processed_data: dict, news_text: str, pattern_analysis: dict, whale_data: dict) -> str:
592
- # ❗ pattern_analysis مطلوب هنا
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
  original_trade_type = "LONG" # SPOT only
 
 
 
 
 
 
 
598
 
599
  try:
600
  price_change = ((current_price - entry_price) / entry_price) * 100 if entry_price else 0
@@ -603,11 +640,9 @@ OUTPUT FORMAT (JSON - SPOT ONLY):
603
  price_change_display = "N/A"
604
 
605
  indicators_summary = format_technical_indicators(processed_data.get('advanced_indicators', {}))
606
- # ❗ استخدام دالة التنسيق _format_pattern_analysis هنا
607
  pattern_summary = self._format_pattern_analysis(pattern_analysis) if pattern_analysis else "Pattern analysis data not available for re-analysis."
608
  whale_analysis_section = format_whale_analysis_for_llm(whale_data)
609
  market_context_section = self._format_market_context(processed_data.get('sentiment_data', {}))
610
- # ❗ إضافة ملخص الشموع الخام لإعادة التحليل أيضاً
611
  ohlcv_data = processed_data.get('raw_ohlcv') or processed_data.get('ohlcv', {})
612
  candle_data_section = self._format_candle_data_comprehensive(ohlcv_data)
613
 
@@ -623,6 +658,10 @@ TRADE RE-ANALYSIS FOR {symbol} (SPOT ONLY - Currently Open LONG Position)
623
  - Current Price: {current_price}
624
  - Current Performance: {price_change_display}
625
  - Trade Age: {trade_data.get('hold_duration_minutes', 'N/A')} minutes
 
 
 
 
626
 
627
  🔄 UPDATED TECHNICAL ANALYSIS:
628
  {indicators_summary}
@@ -645,30 +684,50 @@ TRADE RE-ANALYSIS FOR {symbol} (SPOT ONLY - Currently Open LONG Position)
645
 
646
  🎯 RE-ANALYSIS INSTRUCTIONS (SPOT - LONG POSITION):
647
 
648
- 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, and market context.
649
- 2. **FOCUS ON RECENT PRICE ACTION:** Pay close attention to the latest candle formations and volume in the 'UPDATED RAW CANDLE DATA' section.
650
- 3. **VALIDATE PATTERNS:** Consider the 'UPDATED PATTERN ANALYSIS RESULTS' provided. Does the recent price action confirm or invalidate these patterns?
651
- 4. **ASSESS RISK:** Evaluate the current risk-reward ratio for HOLDING the LONG position using latest price action and updated support/resistance levels derived from the candle data.
652
- 5. **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 for the LONG position). DO NOT suggest SHORTING.
653
- 6. **PROVIDE DETAILS:** If updating, provide specific new SL/TP levels. Justify your decision clearly, integrating all data points.
 
 
 
654
 
655
- CRITICAL: The decision must be one of HOLD, CLOSE_TRADE, or UPDATE_TRADE for the existing LONG position. Explain how recent candle data and provided patterns influenced your decision.
656
 
657
  OUTPUT FORMAT (JSON - SPOT RE-ANALYSIS):
658
  {{
659
  "action": "HOLD/CLOSE_TRADE/UPDATE_TRADE",
660
- "reasoning": "Comprehensive justification for HOLD, CLOSE, or UPDATE of the LONG position, based on updated analysis with emphasis on recent candle patterns and the provided pattern analysis results.",
661
- "new_stop_loss": 0.000000, # If action is UPDATE_TRADE, else 0
662
- "new_take_profit": 0.000000, # If action is UPDATE_TRADE, else 0
 
 
 
 
663
  "new_expected_minutes": 15, # If action is UPDATE_TRADE or HOLD (new expectation), else 0
664
  "confidence_level": 0.85, # Confidence in the re-analysis decision
665
  "strategy": "{strategy}", # Original strategy context
666
- "whale_influence_reanalysis": "How updated whale data influenced the decision for the LONG trade",
667
- "pattern_influence_reanalysis": "How updated raw candle patterns AND the provided pattern analysis results influenced the decision",
668
  "risk_adjustment": "low/medium/high" # Current risk level if HOLDING
669
  }}
670
  """
671
  return prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
672
 
673
  @_rate_limit_nvidia_api
674
  async def _call_llm(self, prompt: str) -> str:
@@ -681,29 +740,28 @@ OUTPUT FORMAT (JSON - SPOT RE-ANALYSIS):
681
  messages=[{"role": "user", "content": prompt}],
682
  temperature=self.temperature,
683
  seed=int(time.time()), # Use time for seed
684
- max_tokens=4000 # Increased max_tokens slightly
685
  )
686
- # Basic validation of response content
687
  content = response.choices[0].message.content
688
  if content and '{' in content and '}' in content:
689
  return content
690
  else:
691
  print(f"⚠️ LLM returned invalid content (attempt {attempt+1}): {content[:100]}...")
692
- if attempt == 0: await asyncio.sleep(1) # Wait before retry
693
 
694
  except (RateLimitError, APITimeoutError) as e:
695
  print(f"❌ LLM API Error (Rate Limit/Timeout): {e}. Retrying via backoff...")
696
- raise # Let backoff handle retries for these specific errors
697
  except Exception as e:
698
  print(f"❌ Unexpected LLM API error (attempt {attempt+1}): {e}")
699
- if attempt == 0: await asyncio.sleep(2) # Wait longer for unexpected errors
700
- elif attempt == 1: raise # Raise error after second failed attempt
701
 
702
  print("❌ LLM failed to return valid content after retries.")
703
- return "" # Return empty string if all attempts fail
704
 
705
  except Exception as e:
706
  print(f"❌ Final failure in _call_llm after backoff retries: {e}")
707
- raise # Re-raise the exception after backoff fails
708
 
709
- print("✅ LLM Service loaded - Updated Prompts for SPOT ONLY & LLM Pattern Analysis")
 
1
+ # LLM.py (محدث بالكامل مع ملفات الخروج الديناميكية والتغذية الراجعة)
2
  import os, traceback, asyncio, json, time
3
  import re # ✅ استيراد مكتبة re
4
  from datetime import datetime
 
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
  NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY")
14
  PRIMARY_MODEL = "nvidia/llama-3.1-nemotron-ultra-253b-v1"
 
163
  'resistance': 0
164
  }
165
 
 
166
  async def analyze_chart_patterns(self, symbol, ohlcv_data):
167
+ pass
 
 
 
168
 
169
  def _parse_pattern_response(self, response_text):
170
+ pass
 
171
 
172
 
173
  class LLMService:
 
177
  self.temperature = temperature
178
  self.client = OpenAI(base_url="https://integrate.api.nvidia.com/v1", api_key=self.api_key)
179
  self.news_fetcher = NewsFetcher()
180
+ self.pattern_engine = PatternAnalysisEngine(self)
181
  self.semaphore = asyncio.Semaphore(5)
182
  self.r2_service = None # سيتم تعيينه من app.py
183
+ self.learning_engine = None # 🔴 جديد: سيتم تعيينه من app.py
184
 
185
  def _rate_limit_nvidia_api(func):
186
  @wraps(func)
 
215
  print(f" ✅ أطر زمنية صالحة لـ {symbol}: {', '.join(valid_timeframes)}")
216
 
217
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
 
 
218
  whale_data = data_payload.get('whale_data', {})
219
 
220
+ # 🔴 جديد: الحصول على تغذية راجعة من محرك التعلم
221
+ best_learned_exit = "None"
222
+ learning_feedback = "No learning data yet."
223
+ if self.learning_engine and self.learning_engine.initialized:
224
+ best_learned_exit = await self.learning_engine.get_best_exit_profile(target_strategy)
225
+ if best_learned_exit != "unknown":
226
+ learning_feedback = f"Learning System Feedback: For the '{target_strategy}' strategy, the '{best_learned_exit}' exit profile has historically performed best. Please consider this."
227
+
228
+ prompt = self._create_comprehensive_trading_prompt(data_payload, news_text, None, whale_data, learning_feedback)
229
 
230
  if self.r2_service:
231
  analysis_data = {
 
234
  'final_score': data_payload.get('final_score'),
235
  'enhanced_final_score': data_payload.get('enhanced_final_score'),
236
  'target_strategy': target_strategy,
237
+ 'learning_feedback_provided': learning_feedback, # 🔴 جديد
238
  'whale_data_available': whale_data.get('data_available', False),
239
  'total_candles': total_candles,
240
  'timeframes_count': timeframes_count,
 
242
  'timestamp': datetime.now().isoformat()
243
  }
244
  await self.r2_service.save_llm_prompts_async(
245
+ symbol, 'comprehensive_trading_decision_v2', prompt, analysis_data
246
  )
247
 
248
  async with self.semaphore:
 
251
  decision_dict = self._parse_llm_response_enhanced(response, target_strategy, symbol)
252
  if decision_dict:
253
  decision_dict['model_source'] = self.model_name
 
254
  decision_dict['whale_data_integrated'] = whale_data.get('data_available', False)
255
  decision_dict['total_candles_analyzed'] = total_candles
256
  decision_dict['timeframes_analyzed'] = timeframes_count
 
276
  print(f"❌ فشل تحليل JSON (safe_json_parse) لـ {symbol}: {response_text}")
277
  return None
278
 
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
+ # طباعة الحقول المفقودة
288
+ missing = [f for f in required_fields if f not in decision_data]
289
+ print(f" MIA: {missing}")
290
+ return None
291
+
292
+ # 🔴 التحقق من exit_parameters
293
+ if not isinstance(decision_data['exit_parameters'], dict):
294
+ print(f"❌ الحقل 'exit_parameters' ليس قاموساً (dict) صالحاً لـ {symbol}")
295
  return None
296
 
297
  action = decision_data.get('action')
 
313
  print(f"❌ خطأ في تحليل استجابة النموذج لـ {symbol}: {e}")
314
  return None
315
 
 
316
  async def _get_pattern_analysis(self, data_payload):
 
317
  try:
318
  symbol = data_payload['symbol']
319
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
320
  if ohlcv_data:
 
321
  return await self.pattern_engine.analyze_chart_patterns(symbol, ohlcv_data)
322
  return None
323
  except Exception as e:
324
  print(f"❌ فشل تحليل الأنماط (قد يكون لإعادة التحليل) لـ {data_payload.get('symbol')}: {e}")
325
  return None
326
 
327
+ def _create_comprehensive_trading_prompt(self, payload: dict, news_text: str, pattern_analysis: dict, whale_data: dict, learning_feedback: str) -> str:
 
328
  symbol = payload.get('symbol', 'N/A')
329
  current_price = payload.get('current_price', 'N/A')
330
  reasons = payload.get('reasons_for_candidacy', [])
 
338
  ohlcv_data = payload.get('raw_ohlcv') or payload.get('ohlcv', {})
339
 
340
  final_score_display = f"{final_score:.3f}" if isinstance(final_score, (int, float)) else str(final_score)
341
+ enhanced_score_display = f"{enhanced_score_display:.3f}" if isinstance(enhanced_score, (int, float)) else str(enhanced_score)
342
 
343
  indicators_summary = format_technical_indicators(advanced_indicators)
344
  strategies_summary = format_strategy_scores(strategy_scores, recommended_strategy)
 
345
  whale_analysis_section = format_whale_analysis_for_llm(whale_data)
346
+ candle_data_section = self._format_candle_data_comprehensive(ohlcv_data)
347
  market_context_section = self._format_market_context(sentiment_data)
348
+
349
+ # 🔴 جديد: إضافة التغذية الراجعة للتعلم
350
+ learning_feedback_section = f"🧠 LEARNING ENGINE FEEDBACK:\n{learning_feedback}"
351
 
352
  prompt = f"""
353
  COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
 
361
  - System Score: {final_score_display}
362
  - Enhanced Score: {enhanced_score_display}
363
 
364
+ {learning_feedback_section}
365
+
366
  📊 TECHNICAL INDICATORS (ALL TIMEFRAMES):
367
  {indicators_summary}
368
 
 
385
  📋 REASONS FOR SYSTEM CANDIDACY (Layer 1 & 2 Screening):
386
  {chr(10).join([f"• {reason}" for reason in reasons]) if reasons else "No specific reasons provided"}
387
 
388
+ 🎯 TRADING DECISION INSTRUCTIONS (SPOT ONLY - LLM MUST ANALYZE PATTERNS AND DEFINE EXIT STRATEGY):
389
+
390
+ 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.).
391
+ 2. **INTEGRATE ALL DATA:** Combine YOUR pattern analysis with technicals, strategy analysis, whale activity, market context, news, and the 'LEARNING ENGINE FEEDBACK'.
392
+ 3. **ADHERE STRICTLY TO SPOT TRADING RULES:** Only decide 'BUY' (LONG) or 'HOLD'. DO NOT suggest 'SELL'.
393
+ 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.
394
+ - `"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*).
395
+ - `"exit_parameters"`: Define parameters for the chosen profile.
396
+ - 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%)
397
+ - For "FIXED_TARGET": {{"time_stop_minutes": 120}} (Hard stop if target not hit in 120 mins)
398
+ - For "TIME_BASED": {{"exit_after_minutes": 60}}
399
+ - For "SIGNAL_BASED": {{"emergency_volume_spike_multiplier": 5.0}} (Exit if reverse volume spike > 5x average)
400
+ 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.
401
+
402
+ OUTPUT FORMAT (JSON - SPOT ONLY - INCLUDE EXIT PROFILE):
403
  {{
404
  "action": "BUY/HOLD",
405
+ "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.",
406
+ "pattern_identified_by_llm": "Name of the primary pattern(s) identified (e.g., 'Bull Flag on 1H', 'No Clear Pattern')",
407
+ "pattern_influence": "Explain how the identified pattern(s) influenced the decision.",
408
  "risk_assessment": "low/medium/high",
409
+
410
+ "stop_loss": 0.000000, # Required if action is BUY (Hard stop loss), 0 if HOLD
411
+ "take_profit": 0.000000, # Required if action is BUY (Final target), 0 if HOLD
412
+
413
+ "exit_profile": "FIXED_TARGET", # (Required if BUY, "None" if HOLD). Choose from: "ATR_TRAILING", "FIXED_TARGET", "TIME_BASED", "SIGNAL_BASED"
414
+ "exit_parameters": {{ "time_stop_minutes": 120 }}, # (Required if BUY, {{}} if HOLD). Must match the chosen exit_profile.
415
+
416
+ "expected_target_minutes": 15, # Required if action is BUY (Time to reach final TP), 0 if HOLD
417
  "confidence_level": 0.85, # Confidence in the BUY or HOLD decision
418
  "strategy": "{target_strategy}", # The strategy context provided
419
+ "whale_influence": "How whale data influenced the BUY/HOLD decision",
420
  "key_support_level": 0.000000, # Derived from candle data analysis
421
  "key_resistance_level": 0.000000, # Derived from candle data analysis
422
+ "risk_reward_ratio": 2.5 # Calculated for the HARD SL/TP, 0 if HOLD
423
  }}
424
  """
425
  return prompt
426
 
427
 
 
 
 
428
  def _format_candle_data_comprehensive(self, ohlcv_data):
429
  """تنسيق شامل لبيانات الشموع الخام"""
430
  if not ohlcv_data:
 
445
  summary = f"📊 Available Timeframes: {', '.join(timeframes_available)}\n"
446
  summary += f"📈 Total Candles Available: {total_candles}\n\n"
447
 
 
 
448
  raw_candle_analysis_text = self.pattern_engine._format_chart_data_for_llm(ohlcv_data)
449
 
450
  summary += raw_candle_analysis_text
 
502
 
503
  def _format_market_context(self, sentiment_data):
504
  """تنسيق سياق السوق"""
 
505
  if not sentiment_data or sentiment_data.get('data_quality', 'LOW') == 'LOW':
506
  return "Market context data not available or incomplete."
507
 
 
510
  market_trend = sentiment_data.get('market_trend', 'N/A') # e.g., 'bull_market', 'bear_market', 'sideways_market'
511
 
512
  lines = [
 
513
  f"• Bitcoin Sentiment: {btc_sentiment}",
514
  f"• Fear & Greed Index: {fear_greed} ({sentiment_data.get('sentiment_class', 'Neutral')})",
515
  f"• Overall Market Trend: {market_trend.replace('_', ' ').title() if isinstance(market_trend, str) else 'N/A'}"
516
  ]
517
 
 
518
  general_whale = sentiment_data.get('general_whale_activity', {})
519
  if general_whale and general_whale.get('sentiment') != 'NEUTRAL': # Only show if not neutral
520
  whale_sentiment = general_whale.get('sentiment', 'N/A')
 
537
  return None
538
 
539
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
 
540
  pattern_analysis = await self._get_pattern_analysis(processed_data)
541
  whale_data = processed_data.get('whale_data', {})
542
 
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
  '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:
 
597
  print(f"⚠️ النموذج اقترح إجراء إعادة تحليل غير مدعوم ({action}) لـ {symbol}. سيتم اعتباره HOLD.")
598
  decision_data['action'] = 'HOLD'
599
 
600
+ # 🔴 تحديث: إذا كان UPDATE_TRADE، يجب أن يتضمن ملف خروج جديد
601
+ if action == 'UPDATE_TRADE':
602
+ required_update_fields = ['new_stop_loss', 'new_take_profit', 'new_exit_profile', 'new_exit_parameters']
603
+ if not validate_required_fields(decision_data, required_update_fields):
604
+ print(f"❌ حقول مطلوبة مفقودة لـ UPDATE_TRADE لـ {symbol}")
605
+ missing = [f for f in required_update_fields if f not in decision_data]
606
+ print(f" MIA: {missing}")
607
+ decision_data['action'] = 'HOLD' # العودة إلى HOLD إذا كان التحديث غير مكتمل
608
+ elif not isinstance(decision_data['new_exit_parameters'], dict):
609
+ print(f"❌ الحقل 'new_exit_parameters' ليس قاموساً صالحاً لـ {symbol}")
610
+ decision_data['action'] = 'HOLD'
611
+
612
+
613
  strategy_value = decision_data.get('strategy')
614
  if not strategy_value or strategy_value == 'unknown':
615
  decision_data['strategy'] = fallback_strategy
 
619
  print(f"Error parsing re-analysis response for {symbol}: {e}")
620
  return None
621
 
622
+ 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:
 
623
  symbol = trade_data.get('symbol', 'N/A')
624
  entry_price = trade_data.get('entry_price', 'N/A')
625
  current_price = processed_data.get('current_price', 'N/A')
626
  strategy = trade_data.get('strategy', 'GENERIC')
627
  original_trade_type = "LONG" # SPOT only
628
+
629
+ # 🔴 جديد: جلب ملف الخروج الحالي
630
+ current_exit_profile = trade_data.get('decision_data', {}).get('exit_profile', 'N/A')
631
+ current_exit_params = json.dumps(trade_data.get('decision_data', {}).get('exit_parameters', {}))
632
+
633
+ # 🔴 جديد: إضافة التغذية الراجعة للتعلم
634
+ learning_feedback_section = f"🧠 LEARNING ENGINE FEEDBACK:\n{learning_feedback}"
635
 
636
  try:
637
  price_change = ((current_price - entry_price) / entry_price) * 100 if entry_price else 0
 
640
  price_change_display = "N/A"
641
 
642
  indicators_summary = format_technical_indicators(processed_data.get('advanced_indicators', {}))
 
643
  pattern_summary = self._format_pattern_analysis(pattern_analysis) if pattern_analysis else "Pattern analysis data not available for re-analysis."
644
  whale_analysis_section = format_whale_analysis_for_llm(whale_data)
645
  market_context_section = self._format_market_context(processed_data.get('sentiment_data', {}))
 
646
  ohlcv_data = processed_data.get('raw_ohlcv') or processed_data.get('ohlcv', {})
647
  candle_data_section = self._format_candle_data_comprehensive(ohlcv_data)
648
 
 
658
  - Current Price: {current_price}
659
  - Current Performance: {price_change_display}
660
  - Trade Age: {trade_data.get('hold_duration_minutes', 'N/A')} minutes
661
+ - Current Exit Profile: {current_exit_profile}
662
+ - Current Exit Parameters: {current_exit_params}
663
+
664
+ {learning_feedback_section}
665
 
666
  🔄 UPDATED TECHNICAL ANALYSIS:
667
  {indicators_summary}
 
684
 
685
  🎯 RE-ANALYSIS INSTRUCTIONS (SPOT - LONG POSITION):
686
 
687
+ 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.
688
+ 2. **VALIDATE PATTERNS:** Consider the 'UPDATED PATTERN ANALYSIS RESULTS' provided. Does the recent price action confirm or invalidate these patterns?
689
+ 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).
690
+ 4. **IF UPDATING (CRITICAL):** If action is UPDATE_TRADE, you MUST provide:
691
+ - `new_stop_loss` (New hard stop)
692
+ - `new_take_profit` (New final target)
693
+ - `new_exit_profile`: (e.g., "ATR_TRAILING") - Can be the same or different.
694
+ - `new_exit_parameters`: (e.g., {{"atr_multiplier": 1.5}}) - Must match the new profile.
695
+ 5. **PROVIDE DETAILS:** Justify your decision clearly, integrating all data points.
696
 
697
+ CRITICAL: The decision must be one of HOLD, CLOSE_TRADE, or UPDATE_TRADE for the existing LONG position.
698
 
699
  OUTPUT FORMAT (JSON - SPOT RE-ANALYSIS):
700
  {{
701
  "action": "HOLD/CLOSE_TRADE/UPDATE_TRADE",
702
+ "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.",
703
+
704
+ "new_stop_loss": 0.000000, # (Required if UPDATE_TRADE, else 0)
705
+ "new_take_profit": 0.000000, # (Required if UPDATE_TRADE, else 0)
706
+ "new_exit_profile": "None", # (Required if UPDATE_TRADE, else "None")
707
+ "new_exit_parameters": {{}}, # (Required if UPDATE_TRADE, else {{}})
708
+
709
  "new_expected_minutes": 15, # If action is UPDATE_TRADE or HOLD (new expectation), else 0
710
  "confidence_level": 0.85, # Confidence in the re-analysis decision
711
  "strategy": "{strategy}", # Original strategy context
712
+ "whale_influence_reanalysis": "How updated whale data influenced the decision",
713
+ "pattern_influence_reanalysis": "How updated candle patterns AND provided patterns influenced the decision",
714
  "risk_adjustment": "low/medium/high" # Current risk level if HOLDING
715
  }}
716
  """
717
  return prompt
718
+
719
+ # ❗ دالة _format_pattern_analysis مُضافة هنا لأنها أُزيلت من الأعلى
720
+ def _format_pattern_analysis(self, pattern_analysis):
721
+ """تنسيق تحليل الأنماط للنموذج الضخم"""
722
+ if not pattern_analysis or not pattern_analysis.get('pattern_detected') or pattern_analysis.get('pattern_detected') == 'no_clear_pattern':
723
+ return "No clear chart pattern detected by the system."
724
+
725
+ pattern = pattern_analysis.get('pattern_detected', 'N/A')
726
+ confidence = pattern_analysis.get('pattern_confidence', 0)
727
+ direction = pattern_analysis.get('predicted_direction', 'N/A')
728
+ timeframe = pattern_analysis.get('timeframe', 'N/A')
729
+
730
+ return f"System Pattern Analysis: Detected '{pattern}' on {timeframe} timeframe with {confidence:.2f} confidence. Predicted direction: {direction}."
731
 
732
  @_rate_limit_nvidia_api
733
  async def _call_llm(self, prompt: str) -> str:
 
740
  messages=[{"role": "user", "content": prompt}],
741
  temperature=self.temperature,
742
  seed=int(time.time()), # Use time for seed
743
+ max_tokens=4000
744
  )
 
745
  content = response.choices[0].message.content
746
  if content and '{' in content and '}' in content:
747
  return content
748
  else:
749
  print(f"⚠️ LLM returned invalid content (attempt {attempt+1}): {content[:100]}...")
750
+ if attempt == 0: await asyncio.sleep(1)
751
 
752
  except (RateLimitError, APITimeoutError) as e:
753
  print(f"❌ LLM API Error (Rate Limit/Timeout): {e}. Retrying via backoff...")
754
+ raise
755
  except Exception as e:
756
  print(f"❌ Unexpected LLM API error (attempt {attempt+1}): {e}")
757
+ if attempt == 0: await asyncio.sleep(2)
758
+ elif attempt == 1: raise
759
 
760
  print("❌ LLM failed to return valid content after retries.")
761
+ return ""
762
 
763
  except Exception as e:
764
  print(f"❌ Final failure in _call_llm after backoff retries: {e}")
765
+ raise
766
 
767
+ print("✅ LLM Service loaded - V2 (Dynamic Exit Profiles & Learning Feedback)")