Riy777 commited on
Commit
005898f
·
verified ·
1 Parent(s): f2021cb

Update LLM.py

Browse files
Files changed (1) hide show
  1. LLM.py +176 -143
LLM.py CHANGED
@@ -1,5 +1,6 @@
1
  # LLM.py
2
  import os, traceback, asyncio, json, time
 
3
  from datetime import datetime
4
  from functools import wraps
5
  from backoff import on_exception, expo
@@ -18,9 +19,9 @@ class PatternAnalysisEngine:
18
 
19
  def _format_chart_data_for_llm(self, ohlcv_data):
20
  """تنسيق شامل لبيانات الشموع الخام لتحليل الأنماط"""
21
- if not ohlcv_data:
22
  return "Insufficient chart data for pattern analysis"
23
-
24
  try:
25
  # استخدام جميع الأطر الزمنية المتاحة مع البيانات الخام
26
  all_timeframes = []
@@ -29,58 +30,58 @@ class PatternAnalysisEngine:
29
  # تمرير البيانات الخام مباشرة للنموذج
30
  raw_candle_summary = self._format_raw_candle_data(candles, timeframe)
31
  all_timeframes.append(f"=== {timeframe.upper()} TIMEFRAME ({len(candles)} CANDLES) ===\n{raw_candle_summary}")
32
-
33
  return "\n\n".join(all_timeframes) if all_timeframes else "No sufficient timeframe data available"
34
- except Exception as e:
35
  return f"Error formatting chart data: {str(e)}"
36
-
37
  def _format_raw_candle_data(self, candles, timeframe):
38
  """تنسيق بيانات الشموع الخام بشكل مفصل للنموذج"""
39
  try:
40
  if len(candles) < 10:
41
  return f"Only {len(candles)} candles available - insufficient for deep pattern analysis"
42
-
43
  # أخذ آخر 50 شمعة كحد أقصى لتجنب السياق الطويل جداً
44
  analysis_candles = candles[-50:] if len(candles) > 50 else candles
45
-
46
  summary = []
47
  summary.append(f"Total candles: {len(candles)} (showing last {len(analysis_candles)})")
48
  summary.append("Recent candles (newest to oldest):")
49
-
50
  # عرض آخر 15 شمعة بالتفصيل
51
  for i in range(min(15, len(analysis_candles))):
52
  idx = len(analysis_candles) - 1 - i
53
  candle = analysis_candles[idx]
54
-
55
  # تحويل الطابع الزمني
56
  try:
57
  timestamp = datetime.fromtimestamp(candle[0] / 1000).strftime('%Y-%m-%d %H:%M:%S')
58
  except:
59
  timestamp = "unknown"
60
-
61
  open_price, high, low, close, volume = candle[1], candle[2], candle[3], candle[4], candle[5]
62
-
63
  candle_type = "🟢 BULLISH" if close > open_price else "🔴 BEARISH" if close < open_price else "⚪ NEUTRAL"
64
  body_size = abs(close - open_price)
65
  body_percent = (body_size / open_price * 100) if open_price > 0 else 0
66
-
67
  wick_upper = high - max(open_price, close)
68
  wick_lower = min(open_price, close) - low
69
  total_range = high - low
70
-
71
  if total_range > 0:
72
  body_ratio = (body_size / total_range) * 100
73
  upper_wick_ratio = (wick_upper / total_range) * 100
74
  lower_wick_ratio = (wick_lower / total_range) * 100
75
  else:
76
  body_ratio = upper_wick_ratio = lower_wick_ratio = 0
77
-
78
  summary.append(f"{i+1:2d}. {timestamp} | {candle_type}")
79
  summary.append(f" O:{open_price:.8f} H:{high:.8f} L:{low:.8f} C:{close:.8f}")
80
  summary.append(f" Body: {body_percent:.2f}% | Body/Range: {body_ratio:.1f}%")
81
  summary.append(f" Wicks: Upper {upper_wick_ratio:.1f}% / Lower {lower_wick_ratio:.1f}%")
82
  summary.append(f" Volume: {volume:,.0f}")
83
-
84
  # إضافة تحليل إحصائي
85
  if len(analysis_candles) >= 20:
86
  stats = self._calculate_candle_statistics(analysis_candles)
@@ -91,12 +92,12 @@ class PatternAnalysisEngine:
91
  summary.append(f"• Trend: {stats['trend']}")
92
  summary.append(f"• Support: {stats['support']:.6f}")
93
  summary.append(f"• Resistance: {stats['resistance']:.6f}")
94
-
95
  return "\n".join(summary)
96
-
97
  except Exception as e:
98
  return f"Error formatting raw candle data: {str(e)}"
99
-
100
  def _calculate_candle_statistics(self, candles):
101
  """حساب الإحصائيات الأساسية للشموع"""
102
  try:
@@ -104,16 +105,16 @@ class PatternAnalysisEngine:
104
  opens = [c[1] for c in candles]
105
  highs = [c[2] for c in candles]
106
  lows = [c[3] for c in candles]
107
-
108
  # حساب التغير في السعر
109
  first_close = closes[0]
110
  last_close = closes[-1]
111
  price_change = ((last_close - first_close) / first_close) * 100
112
-
113
  # حساب متوسط حجم الجسم
114
  body_sizes = [abs(close - open) for open, close in zip(opens, closes)]
115
  avg_body = (sum(body_sizes) / len(body_sizes)) / first_close * 100
116
-
117
  # حساب ATR مبسط
118
  true_ranges = []
119
  for i in range(1, len(candles)):
@@ -122,9 +123,9 @@ class PatternAnalysisEngine:
122
  tr2 = abs(high - prev_close)
123
  tr3 = abs(low - prev_close)
124
  true_ranges.append(max(tr1, tr2, tr3))
125
-
126
  atr = sum(true_ranges) / len(true_ranges) if true_ranges else 0
127
-
128
  # تحديد الاتجاه
129
  if price_change > 3:
130
  trend = "STRONG UPTREND"
@@ -136,11 +137,11 @@ class PatternAnalysisEngine:
136
  trend = "DOWNTREND"
137
  else:
138
  trend = "SIDEWAYS"
139
-
140
  # مستويات الدعم والمقاومة المبسطة
141
  support = min(lows)
142
  resistance = max(highs)
143
-
144
  return {
145
  'price_change': price_change,
146
  'avg_body': avg_body,
@@ -149,7 +150,7 @@ class PatternAnalysisEngine:
149
  'support': support,
150
  'resistance': resistance
151
  }
152
-
153
  except Exception as e:
154
  return {
155
  'price_change': 0,
@@ -159,14 +160,14 @@ class PatternAnalysisEngine:
159
  'support': 0,
160
  'resistance': 0
161
  }
162
-
163
  async def analyze_chart_patterns(self, symbol, ohlcv_data):
164
  try:
165
  if not ohlcv_data:
166
  return {"pattern_detected": "insufficient_data", "pattern_confidence": 0.1, "pattern_analysis": "No candle data available"}
167
-
168
  chart_text = self._format_chart_data_for_llm(ohlcv_data)
169
-
170
  prompt = f"""
171
  ANALYZE CHART PATTERNS FOR {symbol}
172
 
@@ -197,7 +198,7 @@ OUTPUT FORMAT (JSON):
197
  "pattern_analysis": "Detailed explanation covering multiple timeframes and specific candle patterns",
198
  "timeframe_confirmations": {{
199
  "1h": "pattern_details",
200
- "4h": "pattern_details",
201
  "1d": "pattern_details"
202
  }},
203
  "candlestick_patterns": ["Hammer", "Bullish Engulfing", ...],
@@ -216,18 +217,18 @@ OUTPUT FORMAT (JSON):
216
  def _parse_pattern_response(self, response_text):
217
  try:
218
  json_str = parse_json_from_response(response_text)
219
- if not json_str:
220
  return {"pattern_detected": "parse_error", "pattern_confidence": 0.1, "pattern_analysis": "Could not parse pattern analysis response"}
221
-
222
  # ✅ الإصلاح: استخدام safe_json_parse بدلاً من json.loads
223
  pattern_data = safe_json_parse(json_str)
224
  if not pattern_data:
225
- return {"pattern_detected": "parse_error", "pattern_confidence": 0.1, "pattern_analysis": f"Failed to parse JSON string: {json_str[:200]}"}
226
-
227
  required = ['pattern_detected', 'pattern_confidence', 'predicted_direction']
228
- if not validate_required_fields(pattern_data, required):
229
  return {"pattern_detected": "incomplete_data", "pattern_confidence": 0.1, "pattern_analysis": "Incomplete pattern analysis data"}
230
-
231
  return pattern_data
232
  except Exception as e:
233
  print(f"Error parsing pattern response: {e}")
@@ -247,7 +248,7 @@ class LLMService:
247
  def _rate_limit_nvidia_api(func):
248
  @wraps(func)
249
  @on_exception(expo, RateLimitError, max_tries=5)
250
- async def wrapper(*args, **kwargs):
251
  return await func(*args, **kwargs)
252
  return wrapper
253
 
@@ -255,43 +256,43 @@ class LLMService:
255
  try:
256
  symbol = data_payload.get('symbol', 'unknown')
257
  target_strategy = data_payload.get('target_strategy', 'GENERIC')
258
-
259
  # ✅ التحقق من بيانات الشموع بشكل صحيح - الإصلاح الرئيسي هنا
260
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
261
  if not ohlcv_data:
262
  print(f"⚠️ لا توجد بيانات شموع لـ {symbol} - تخطي التحليل")
263
  return None
264
-
265
  # ✅ حساب إجمالي الشموع المتاحة
266
  total_candles = sum(len(data) for data in ohlcv_data.values() if data) if ohlcv_data else 0
267
  timeframes_count = len([tf for tf, data in ohlcv_data.items() if data and len(data) >= 10]) if ohlcv_data else 0
268
-
269
  print(f" 📊 بيانات {symbol}: {total_candles} شمعة في {timeframes_count} إطار زمني")
270
-
271
  if total_candles < 30: # تخفيف الشرط من 50 إلى 30 شمعة
272
  print(f" ⚠️ بيانات شموع غير كافية لـ {symbol}: {total_candles} شمعة فقط")
273
  return None
274
-
275
  # ✅ تأكيد وجود بيانات شموع صالحة
276
  valid_timeframes = []
277
  for timeframe, candles in ohlcv_data.items():
278
  if candles and len(candles) >= 5: # تخفيف الشرط
279
  valid_timeframes.append(timeframe)
280
-
281
  if not valid_timeframes:
282
  print(f" ⚠️ لا توجد أطر زمنية صالحة لـ {symbol}")
283
  return None
284
-
285
  print(f" ✅ أطر زمنية صالحة لـ {symbol}: {', '.join(valid_timeframes)}")
286
-
287
  # جلب جميع البيانات المطلوبة
288
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
289
  pattern_analysis = await self._get_pattern_analysis(data_payload)
290
  whale_data = data_payload.get('whale_data', {})
291
-
292
  # إنشاء الـ prompt الشامل
293
  prompt = self._create_comprehensive_trading_prompt(data_payload, news_text, pattern_analysis, whale_data)
294
-
295
  # ✅ حفظ الـ Prompt في R2 قبل إرساله للنموذج
296
  if self.r2_service:
297
  analysis_data = {
@@ -310,10 +311,10 @@ class LLMService:
310
  await self.r2_service.save_llm_prompts_async(
311
  symbol, 'comprehensive_trading_decision', prompt, analysis_data
312
  )
313
-
314
- async with self.semaphore:
315
  response = await self._call_llm(prompt)
316
-
317
  decision_dict = self._parse_llm_response_enhanced(response, target_strategy, symbol)
318
  if decision_dict:
319
  decision_dict['model_source'] = self.model_name
@@ -321,36 +322,53 @@ class LLMService:
321
  decision_dict['whale_data_integrated'] = whale_data.get('data_available', False)
322
  decision_dict['total_candles_analyzed'] = total_candles
323
  decision_dict['timeframes_analyzed'] = timeframes_count
 
 
 
324
  return decision_dict
325
  else:
326
  print(f"❌ فشل تحليل النموذج الضخم لـ {symbol} - لا توجد قرارات بديلة")
327
  return None
328
-
329
  except Exception as e:
330
  print(f"❌ خطأ في قرار التداول لـ {data_payload.get('symbol', 'unknown')}: {e}")
331
  traceback.print_exc()
332
  return None
333
-
334
  def _parse_llm_response_enhanced(self, response_text: str, fallback_strategy: str, symbol: str) -> dict:
335
  try:
336
  json_str = parse_json_from_response(response_text)
337
- if not json_str:
338
  print(f"❌ فشل استخراج JSON من استجابة النموذج لـ {symbol}")
339
  return None
340
 
341
  # ✅ الإصلاح: استخدام safe_json_parse بدلاً من json.loads
342
  decision_data = safe_json_parse(json_str)
343
  if not decision_data:
344
- print(f"❌ فشل تحليل JSON (safe_json_parse) لـ {symbol}: {response_text}")
345
- return None
346
 
347
- required_fields = ['action', 'reasoning', 'risk_assessment', 'trade_type', 'stop_loss', 'take_profit', 'expected_target_minutes', 'confidence_level']
348
- if not validate_required_fields(decision_data, required_fields):
 
 
349
  print(f"❌ حقول مطلوبة مفقودة في استجابة النموذج لـ {symbol}")
350
  return None
351
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  strategy_value = decision_data.get('strategy')
353
- if not strategy_value or strategy_value == 'unknown':
354
  decision_data['strategy'] = fallback_strategy
355
 
356
  return decision_data
@@ -363,16 +381,16 @@ class LLMService:
363
  symbol = data_payload['symbol']
364
  # ✅ استخدام raw_ohlcv أولاً ثم ohlcv - الإصلاح الرئيسي
365
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
366
-
367
  if ohlcv_data:
368
  # ✅ تمرير البيانات الخام مباشرة لمحرك تحليل الأنماط
369
  return await self.pattern_engine.analyze_chart_patterns(symbol, ohlcv_data)
370
-
371
  return None
372
  except Exception as e:
373
  print(f"❌ فشل تحليل الأنماط لـ {data_payload.get('symbol')}: {e}")
374
  return None
375
-
376
  def _create_comprehensive_trading_prompt(self, payload: dict, news_text: str, pattern_analysis: dict, whale_data: dict) -> str:
377
  symbol = payload.get('symbol', 'N/A')
378
  current_price = payload.get('current_price', 'N/A')
@@ -386,7 +404,7 @@ class LLMService:
386
  enhanced_final_score = payload.get('enhanced_final_score', 'N/A')
387
  # ✅ استخدام raw_ohlcv أولاً - الإصلاح الرئيسي
388
  ohlcv_data = payload.get('raw_ohlcv') or payload.get('ohlcv', {})
389
-
390
  final_score_display = f"{final_score:.3f}" if isinstance(final_score, (int, float)) else str(final_score)
391
  enhanced_score_display = f"{enhanced_final_score:.3f}" if isinstance(enhanced_final_score, (int, float)) else str(enhanced_final_score)
392
 
@@ -401,6 +419,8 @@ class LLMService:
401
  prompt = f"""
402
  COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
403
 
 
 
404
  🎯 STRATEGY CONTEXT:
405
  - Target Strategy: {target_strategy}
406
  - Recommended Strategy: {recommended_strategy}
@@ -432,47 +452,47 @@ COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
432
  📋 REASONS FOR CANDIDACY:
433
  {chr(10).join([f"• {reason}" for reason in reasons]) if reasons else "No specific reasons provided"}
434
 
435
- 🎯 TRADING DECISION INSTRUCTIONS:
436
 
437
- 1. ANALYZE ALL PROVIDED DATA: technical indicators, whale activity, raw candle patterns, market context
438
- 2. FOCUS ON RAW CANDLE DATA for pattern recognition and price action analysis
439
- 3. CONSIDER STRATEGY ALIGNMENT: {target_strategy}
440
- 4. EVALUATE RISK-REWARD RATIO based on support/resistance levels from candle data
441
- 5. INTEGRATE WHALE ACTIVITY signals into your decision
442
- 6. ASSESS PATTERN STRENGTH and timeframe confirmations from raw candles
443
- 7. CONSIDER MARKET SENTIMENT impact
444
 
445
- CRITICAL: You MUST provide specific price levels and time expectations based on the raw candle analysis.
446
 
447
- OUTPUT FORMAT (JSON):
448
  {{
449
- "action": "BUY/SELL/HOLD",
450
- "reasoning": "Detailed explanation integrating ALL data sources with emphasis on raw candle patterns and price action",
451
  "risk_assessment": "low/medium/high",
452
- "trade_type": "LONG/SHORT",
453
- "stop_loss": 0.000000,
454
- "take_profit": 0.000000,
455
- "expected_target_minutes": 15,
456
- "confidence_level": 0.85,
457
  "strategy": "{target_strategy}",
458
- "whale_influence": "How whale data influenced the decision",
459
- "pattern_influence": "How raw candle patterns influenced the decision",
460
  "key_support_level": 0.000000,
461
  "key_resistance_level": 0.000000,
462
- "risk_reward_ratio": 2.5
463
  }}
464
  """
465
  return prompt
466
 
467
  def _format_pattern_analysis(self, pattern_analysis):
468
- if not pattern_analysis:
469
  return "No clear patterns detected across analyzed timeframes"
470
-
471
  confidence = pattern_analysis.get('pattern_confidence', 0)
472
  pattern_name = pattern_analysis.get('pattern_detected', 'unknown')
473
  predicted_direction = pattern_analysis.get('predicted_direction', 'N/A')
474
  movement_percent = pattern_analysis.get('predicted_movement_percent', 'N/A')
475
-
476
  analysis_lines = [
477
  f"🎯 Pattern: {pattern_name}",
478
  f"📊 Confidence: {confidence:.1%}",
@@ -480,49 +500,49 @@ OUTPUT FORMAT (JSON):
480
  f"💰 Expected Movement: {movement_percent}%",
481
  f"📝 Analysis: {pattern_analysis.get('pattern_analysis', 'No detailed analysis')}"
482
  ]
483
-
484
  # إضافة مستويات الدعم والمقاومة إذا كانت متوفرة
485
  support_levels = pattern_analysis.get('key_support_levels', [])
486
  resistance_levels = pattern_analysis.get('key_resistance_levels', [])
487
-
488
  if support_levels:
489
  analysis_lines.append(f"🛟 Support Levels: {', '.join([f'{level:.6f}' for level in support_levels[:3]])}")
490
  if resistance_levels:
491
  analysis_lines.append(f"🚧 Resistance Levels: {', '.join([f'{level:.6f}' for level in resistance_levels[:3]])}")
492
-
493
  # إضافة أنماط الشموع إذا كانت متوفرة
494
  candlestick_patterns = pattern_analysis.get('candlestick_patterns', [])
495
  if candlestick_patterns:
496
  analysis_lines.append(f"🕯️ Candlestick Patterns: {', '.join(candlestick_patterns)}")
497
-
498
  return "\n".join(analysis_lines)
499
 
500
  def _format_candle_data_comprehensive(self, ohlcv_data):
501
  """تنسيق شامل لبيانات الشموع الخام"""
502
  if not ohlcv_data:
503
  return "No raw candle data available for analysis"
504
-
505
  try:
506
  timeframes_available = []
507
  total_candles = 0
508
-
509
  for timeframe, candles in ohlcv_data.items():
510
  if candles and len(candles) >= 5: # تخفيف الشرط
511
  timeframes_available.append(f"{timeframe.upper()} ({len(candles)} candles)")
512
  total_candles += len(candles)
513
-
514
  if not timeframes_available:
515
  return "Insufficient candle data across all timeframes"
516
-
517
  summary = f"📊 Available Timeframes: {', '.join(timeframes_available)}\n"
518
  summary += f"📈 Total Candles Available: {total_candles}\n\n"
519
-
520
  # استخدام محرك الأنماط لتنسيق البيانات الخام
521
  pattern_engine = PatternAnalysisEngine(self)
522
  raw_candle_analysis = pattern_engine._format_chart_data_for_llm(ohlcv_data)
523
-
524
  summary += raw_candle_analysis
525
-
526
  return summary
527
  except Exception as e:
528
  return f"Error formatting raw candle data: {str(e)}"
@@ -532,20 +552,20 @@ OUTPUT FORMAT (JSON):
532
  try:
533
  if len(candles) < 10: # تخفيف الشرط
534
  return f"Insufficient data ({len(candles)} candles)"
535
-
536
  recent_candles = candles[-15:] # آخر 15 شمعة فقط
537
-
538
  # حساب المتغيرات الأساسية
539
  closes = [c[4] for c in recent_candles]
540
  opens = [c[1] for c in recent_candles]
541
  highs = [c[2] for c in recent_candles]
542
  lows = [c[3] for c in recent_candles]
543
  volumes = [c[5] for c in recent_candles]
544
-
545
  current_price = closes[-1]
546
  first_price = closes[0]
547
  price_change = ((current_price - first_price) / first_price) * 100
548
-
549
  # تحليل الاتجاه
550
  if price_change > 2:
551
  trend = "🟢 UPTREND"
@@ -553,22 +573,22 @@ OUTPUT FORMAT (JSON):
553
  trend = "🔴 DOWNTREND"
554
  else:
555
  trend = "⚪ SIDEWAYS"
556
-
557
  # تحليل التقلب
558
  high_max = max(highs)
559
  low_min = min(lows)
560
  volatility = ((high_max - low_min) / low_min) * 100
561
-
562
  # تحليل الحجم
563
  avg_volume = sum(volumes) / len(volumes)
564
  current_volume = volumes[-1]
565
  volume_ratio = current_volume / avg_volume if avg_volume > 0 else 1
566
-
567
  # تحليل الشموع
568
  green_candles = sum(1 for i in range(len(closes)) if closes[i] > opens[i])
569
  red_candles = len(closes) - green_candles
570
  candle_ratio = green_candles / len(closes)
571
-
572
  analysis = [
573
  f"📈 Trend: {trend} ({price_change:+.2f}%)",
574
  f"🌊 Volatility: {volatility:.2f}%",
@@ -577,7 +597,7 @@ OUTPUT FORMAT (JSON):
577
  f"💰 Range: {low_min:.6f} - {high_max:.6f}",
578
  f"🎯 Current: {current_price:.6f}"
579
  ]
580
-
581
  return "\n".join(analysis)
582
  except Exception as e:
583
  return f"Analysis error: {str(e)}"
@@ -586,18 +606,18 @@ OUTPUT FORMAT (JSON):
586
  """تنسيق سياق السوق"""
587
  if not sentiment_data:
588
  return "No market context data available"
589
-
590
  btc_sentiment = sentiment_data.get('btc_sentiment', 'N/A')
591
  fear_greed = sentiment_data.get('fear_and_greed_index', 'N/A')
592
  market_trend = sentiment_data.get('market_trend', 'N/A')
593
-
594
  lines = [
595
  "🌍 MARKET CONTEXT:",
596
  f"• Bitcoin Sentiment: {btc_sentiment}",
597
  f"• Fear & Greed Index: {fear_greed}",
598
  f"• Market Trend: {market_trend}"
599
  ]
600
-
601
  general_whale = sentiment_data.get('general_whale_activity', {})
602
  if general_whale:
603
  whale_sentiment = general_whale.get('sentiment', 'N/A')
@@ -605,27 +625,27 @@ OUTPUT FORMAT (JSON):
605
  lines.append(f"• General Whale Sentiment: {whale_sentiment}")
606
  if critical_alert:
607
  lines.append("• ⚠️ CRITICAL WHALE ALERT")
608
-
609
  return "\n".join(lines)
610
 
611
  async def re_analyze_trade_async(self, trade_data: dict, processed_data: dict):
612
  try:
613
  symbol = trade_data['symbol']
614
  original_strategy = trade_data.get('strategy', 'GENERIC')
615
-
616
  # ✅ التحقق من بيانات الشموع المحدثة - الإصلاح الرئيسي
617
  ohlcv_data = processed_data.get('raw_ohlcv') or processed_data.get('ohlcv')
618
  if not ohlcv_data:
619
  print(f"⚠️ لا توجد بيانات شموع محدثة لـ {symbol} - تخطي إعادة التحليل")
620
  return None
621
-
622
  # جلب جميع البيانات المحدثة
623
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
624
  pattern_analysis = await self._get_pattern_analysis(processed_data)
625
  whale_data = processed_data.get('whale_data', {})
626
-
627
  prompt = self._create_re_analysis_prompt(trade_data, processed_data, news_text, pattern_analysis, whale_data)
628
-
629
  # ✅ حفظ الـ Prompt في R2
630
  if self.r2_service:
631
  analysis_data = {
@@ -639,10 +659,10 @@ OUTPUT FORMAT (JSON):
639
  await self.r2_service.save_llm_prompts_async(
640
  symbol, 'trade_reanalysis', prompt, analysis_data
641
  )
642
-
643
- async with self.semaphore:
644
  response = await self._call_llm(prompt)
645
-
646
  re_analysis_dict = self._parse_re_analysis_response(response, original_strategy, symbol)
647
  if re_analysis_dict:
648
  re_analysis_dict['model_source'] = self.model_name
@@ -651,7 +671,7 @@ OUTPUT FORMAT (JSON):
651
  else:
652
  print(f"❌ فشل إعادة تحليل النموذج الضخم لـ {symbol}")
653
  return None
654
-
655
  except Exception as e:
656
  print(f"❌ خطأ في إعادة تحليل LLM: {e}")
657
  traceback.print_exc()
@@ -660,17 +680,23 @@ OUTPUT FORMAT (JSON):
660
  def _parse_re_analysis_response(self, response_text: str, fallback_strategy: str, symbol: str) -> dict:
661
  try:
662
  json_str = parse_json_from_response(response_text)
663
- if not json_str:
664
  return None
665
 
666
  # ✅ الإصلاح: استخدام safe_json_parse بدلاً من json.loads
667
  decision_data = safe_json_parse(json_str)
668
  if not decision_data:
669
- print(f"❌ فشل تحليل JSON (safe_json_parse) لإعادة التحليل لـ {symbol}: {response_text}")
670
- return None
 
 
 
 
 
 
671
 
672
  strategy_value = decision_data.get('strategy')
673
- if not strategy_value or strategy_value == 'unknown':
674
  decision_data['strategy'] = fallback_strategy
675
 
676
  return decision_data
@@ -683,24 +709,29 @@ OUTPUT FORMAT (JSON):
683
  entry_price = trade_data.get('entry_price', 'N/A')
684
  current_price = processed_data.get('current_price', 'N/A')
685
  strategy = trade_data.get('strategy', 'GENERIC')
686
-
687
- try:
 
 
 
688
  price_change = ((current_price - entry_price) / entry_price) * 100
689
  price_change_display = f"{price_change:+.2f}%"
690
- except (TypeError, ZeroDivisionError):
691
  price_change_display = "N/A"
692
-
693
  indicators_summary = format_technical_indicators(processed_data.get('advanced_indicators', {}))
694
  pattern_summary = self._format_pattern_analysis(pattern_analysis)
695
  whale_analysis_section = format_whale_analysis_for_llm(whale_data)
696
  market_context_section = self._format_market_context(processed_data.get('sentiment_data', {}))
697
 
698
  prompt = f"""
699
- TRADE RE-ANALYSIS FOR {symbol}
 
 
700
 
701
  📊 TRADE CONTEXT:
702
  - Strategy: {strategy}
703
- - Entry Price: {entry_price}
704
  - Current Price: {current_price}
705
  - Performance: {price_change_display}
706
  - Trade Age: {trade_data.get('hold_duration_minutes', 'N/A')} minutes
@@ -720,26 +751,28 @@ TRADE RE-ANALYSIS FOR {symbol}
720
  📰 LATEST NEWS:
721
  {news_text if news_text else "No significant news found"}
722
 
723
- 🎯 RE-ANALYSIS INSTRUCTIONS:
724
 
725
- 1. Evaluate if the original thesis still holds based on updated raw candle data
726
- 2. Consider new whale activity and pattern developments
727
- 3. Assess current risk-reward ratio using latest price action
728
- 4. Decide whether to hold, close, or adjust the trade based on comprehensive analysis
729
- 5. Provide specific updated levels if adjusting
730
 
731
- OUTPUT FORMAT (JSON):
 
 
732
  {{
733
- "action": "HOLD/CLOSE_TRADE/UPDATE_TRADE",
734
- "reasoning": "Comprehensive justification based on updated analysis with emphasis on recent candle patterns",
735
- "new_stop_loss": 0.000000,
736
- "new_take_profit": 0.000000,
737
- "new_expected_minutes": 15,
738
- "confidence_level": 0.85,
739
  "strategy": "{strategy}",
740
- "whale_influence_reanalysis": "How updated whale data influenced decision",
741
- "pattern_influence_reanalysis": "How updated raw candle patterns influenced decision",
742
- "risk_adjustment": "low/medium/high"
743
  }}
744
  """
745
  return prompt
 
1
  # LLM.py
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
 
19
 
20
  def _format_chart_data_for_llm(self, ohlcv_data):
21
  """تنسيق شامل لبيانات الشموع الخام لتحليل الأنماط"""
22
+ if not ohlcv_data:
23
  return "Insufficient chart data for pattern analysis"
24
+
25
  try:
26
  # استخدام جميع الأطر الزمنية المتاحة مع البيانات الخام
27
  all_timeframes = []
 
30
  # تمرير البيانات الخام مباشرة للنموذج
31
  raw_candle_summary = self._format_raw_candle_data(candles, timeframe)
32
  all_timeframes.append(f"=== {timeframe.upper()} TIMEFRAME ({len(candles)} CANDLES) ===\n{raw_candle_summary}")
33
+
34
  return "\n\n".join(all_timeframes) if all_timeframes else "No sufficient timeframe data available"
35
+ except Exception as e:
36
  return f"Error formatting chart data: {str(e)}"
37
+
38
  def _format_raw_candle_data(self, candles, timeframe):
39
  """تنسيق بيانات الشموع الخام بشكل مفصل للنموذج"""
40
  try:
41
  if len(candles) < 10:
42
  return f"Only {len(candles)} candles available - insufficient for deep pattern analysis"
43
+
44
  # أخذ آخر 50 شمعة كحد أقصى لتجنب السياق الطويل جداً
45
  analysis_candles = candles[-50:] if len(candles) > 50 else candles
46
+
47
  summary = []
48
  summary.append(f"Total candles: {len(candles)} (showing last {len(analysis_candles)})")
49
  summary.append("Recent candles (newest to oldest):")
50
+
51
  # عرض آخر 15 شمعة بالتفصيل
52
  for i in range(min(15, len(analysis_candles))):
53
  idx = len(analysis_candles) - 1 - i
54
  candle = analysis_candles[idx]
55
+
56
  # تحويل الطابع الزمني
57
  try:
58
  timestamp = datetime.fromtimestamp(candle[0] / 1000).strftime('%Y-%m-%d %H:%M:%S')
59
  except:
60
  timestamp = "unknown"
61
+
62
  open_price, high, low, close, volume = candle[1], candle[2], candle[3], candle[4], candle[5]
63
+
64
  candle_type = "🟢 BULLISH" if close > open_price else "🔴 BEARISH" if close < open_price else "⚪ NEUTRAL"
65
  body_size = abs(close - open_price)
66
  body_percent = (body_size / open_price * 100) if open_price > 0 else 0
67
+
68
  wick_upper = high - max(open_price, close)
69
  wick_lower = min(open_price, close) - low
70
  total_range = high - low
71
+
72
  if total_range > 0:
73
  body_ratio = (body_size / total_range) * 100
74
  upper_wick_ratio = (wick_upper / total_range) * 100
75
  lower_wick_ratio = (wick_lower / total_range) * 100
76
  else:
77
  body_ratio = upper_wick_ratio = lower_wick_ratio = 0
78
+
79
  summary.append(f"{i+1:2d}. {timestamp} | {candle_type}")
80
  summary.append(f" O:{open_price:.8f} H:{high:.8f} L:{low:.8f} C:{close:.8f}")
81
  summary.append(f" Body: {body_percent:.2f}% | Body/Range: {body_ratio:.1f}%")
82
  summary.append(f" Wicks: Upper {upper_wick_ratio:.1f}% / Lower {lower_wick_ratio:.1f}%")
83
  summary.append(f" Volume: {volume:,.0f}")
84
+
85
  # إضافة تحليل إحصائي
86
  if len(analysis_candles) >= 20:
87
  stats = self._calculate_candle_statistics(analysis_candles)
 
92
  summary.append(f"• Trend: {stats['trend']}")
93
  summary.append(f"• Support: {stats['support']:.6f}")
94
  summary.append(f"• Resistance: {stats['resistance']:.6f}")
95
+
96
  return "\n".join(summary)
97
+
98
  except Exception as e:
99
  return f"Error formatting raw candle data: {str(e)}"
100
+
101
  def _calculate_candle_statistics(self, candles):
102
  """حساب الإحصائيات الأساسية للشموع"""
103
  try:
 
105
  opens = [c[1] for c in candles]
106
  highs = [c[2] for c in candles]
107
  lows = [c[3] for c in candles]
108
+
109
  # حساب التغير في السعر
110
  first_close = closes[0]
111
  last_close = closes[-1]
112
  price_change = ((last_close - first_close) / first_close) * 100
113
+
114
  # حساب متوسط حجم الجسم
115
  body_sizes = [abs(close - open) for open, close in zip(opens, closes)]
116
  avg_body = (sum(body_sizes) / len(body_sizes)) / first_close * 100
117
+
118
  # حساب ATR مبسط
119
  true_ranges = []
120
  for i in range(1, len(candles)):
 
123
  tr2 = abs(high - prev_close)
124
  tr3 = abs(low - prev_close)
125
  true_ranges.append(max(tr1, tr2, tr3))
126
+
127
  atr = sum(true_ranges) / len(true_ranges) if true_ranges else 0
128
+
129
  # تحديد الاتجاه
130
  if price_change > 3:
131
  trend = "STRONG UPTREND"
 
137
  trend = "DOWNTREND"
138
  else:
139
  trend = "SIDEWAYS"
140
+
141
  # مستويات الدعم والمقاومة المبسطة
142
  support = min(lows)
143
  resistance = max(highs)
144
+
145
  return {
146
  'price_change': price_change,
147
  'avg_body': avg_body,
 
150
  'support': support,
151
  'resistance': resistance
152
  }
153
+
154
  except Exception as e:
155
  return {
156
  'price_change': 0,
 
160
  'support': 0,
161
  'resistance': 0
162
  }
163
+
164
  async def analyze_chart_patterns(self, symbol, ohlcv_data):
165
  try:
166
  if not ohlcv_data:
167
  return {"pattern_detected": "insufficient_data", "pattern_confidence": 0.1, "pattern_analysis": "No candle data available"}
168
+
169
  chart_text = self._format_chart_data_for_llm(ohlcv_data)
170
+
171
  prompt = f"""
172
  ANALYZE CHART PATTERNS FOR {symbol}
173
 
 
198
  "pattern_analysis": "Detailed explanation covering multiple timeframes and specific candle patterns",
199
  "timeframe_confirmations": {{
200
  "1h": "pattern_details",
201
+ "4h": "pattern_details",
202
  "1d": "pattern_details"
203
  }},
204
  "candlestick_patterns": ["Hammer", "Bullish Engulfing", ...],
 
217
  def _parse_pattern_response(self, response_text):
218
  try:
219
  json_str = parse_json_from_response(response_text)
220
+ if not json_str:
221
  return {"pattern_detected": "parse_error", "pattern_confidence": 0.1, "pattern_analysis": "Could not parse pattern analysis response"}
222
+
223
  # ✅ الإصلاح: استخدام safe_json_parse بدلاً من json.loads
224
  pattern_data = safe_json_parse(json_str)
225
  if not pattern_data:
226
+ return {"pattern_detected": "parse_error", "pattern_confidence": 0.1, "pattern_analysis": f"Failed to parse JSON string: {json_str[:200]}"}
227
+
228
  required = ['pattern_detected', 'pattern_confidence', 'predicted_direction']
229
+ if not validate_required_fields(pattern_data, required):
230
  return {"pattern_detected": "incomplete_data", "pattern_confidence": 0.1, "pattern_analysis": "Incomplete pattern analysis data"}
231
+
232
  return pattern_data
233
  except Exception as e:
234
  print(f"Error parsing pattern response: {e}")
 
248
  def _rate_limit_nvidia_api(func):
249
  @wraps(func)
250
  @on_exception(expo, RateLimitError, max_tries=5)
251
+ async def wrapper(*args, **kwargs):
252
  return await func(*args, **kwargs)
253
  return wrapper
254
 
 
256
  try:
257
  symbol = data_payload.get('symbol', 'unknown')
258
  target_strategy = data_payload.get('target_strategy', 'GENERIC')
259
+
260
  # ✅ التحقق من بيانات الشموع بشكل صحيح - الإصلاح الرئيسي هنا
261
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
262
  if not ohlcv_data:
263
  print(f"⚠️ لا توجد بيانات شموع لـ {symbol} - تخطي التحليل")
264
  return None
265
+
266
  # ✅ حساب إجمالي الشموع المتاحة
267
  total_candles = sum(len(data) for data in ohlcv_data.values() if data) if ohlcv_data else 0
268
  timeframes_count = len([tf for tf, data in ohlcv_data.items() if data and len(data) >= 10]) if ohlcv_data else 0
269
+
270
  print(f" 📊 بيانات {symbol}: {total_candles} شمعة في {timeframes_count} إطار زمني")
271
+
272
  if total_candles < 30: # تخفيف الشرط من 50 إلى 30 شمعة
273
  print(f" ⚠️ بيانات شموع غير كافية لـ {symbol}: {total_candles} شمعة فقط")
274
  return None
275
+
276
  # ✅ تأكيد وجود بيانات شموع صالحة
277
  valid_timeframes = []
278
  for timeframe, candles in ohlcv_data.items():
279
  if candles and len(candles) >= 5: # تخفيف الشرط
280
  valid_timeframes.append(timeframe)
281
+
282
  if not valid_timeframes:
283
  print(f" ⚠️ لا توجد أطر زمنية صالحة لـ {symbol}")
284
  return None
285
+
286
  print(f" ✅ أطر زمنية صالحة لـ {symbol}: {', '.join(valid_timeframes)}")
287
+
288
  # جلب جميع البيانات المطلوبة
289
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
290
  pattern_analysis = await self._get_pattern_analysis(data_payload)
291
  whale_data = data_payload.get('whale_data', {})
292
+
293
  # إنشاء الـ prompt الشامل
294
  prompt = self._create_comprehensive_trading_prompt(data_payload, news_text, pattern_analysis, whale_data)
295
+
296
  # ✅ حفظ الـ Prompt في R2 قبل إرساله للنموذج
297
  if self.r2_service:
298
  analysis_data = {
 
311
  await self.r2_service.save_llm_prompts_async(
312
  symbol, 'comprehensive_trading_decision', prompt, analysis_data
313
  )
314
+
315
+ async with self.semaphore:
316
  response = await self._call_llm(prompt)
317
+
318
  decision_dict = self._parse_llm_response_enhanced(response, target_strategy, symbol)
319
  if decision_dict:
320
  decision_dict['model_source'] = self.model_name
 
322
  decision_dict['whale_data_integrated'] = whale_data.get('data_available', False)
323
  decision_dict['total_candles_analyzed'] = total_candles
324
  decision_dict['timeframes_analyzed'] = timeframes_count
325
+ # ✅ الإصلاح: التأكد من أن نوع الصفقة هو LONG إذا كان القرار BUY
326
+ if decision_dict.get('action') == 'BUY':
327
+ decision_dict['trade_type'] = 'LONG'
328
  return decision_dict
329
  else:
330
  print(f"❌ فشل تحليل النموذج الضخم لـ {symbol} - لا توجد قرارات بديلة")
331
  return None
332
+
333
  except Exception as e:
334
  print(f"❌ خطأ في قرار التداول لـ {data_payload.get('symbol', 'unknown')}: {e}")
335
  traceback.print_exc()
336
  return None
337
+
338
  def _parse_llm_response_enhanced(self, response_text: str, fallback_strategy: str, symbol: str) -> dict:
339
  try:
340
  json_str = parse_json_from_response(response_text)
341
+ if not json_str:
342
  print(f"❌ فشل استخراج JSON من استجابة النموذج لـ {symbol}")
343
  return None
344
 
345
  # ✅ الإصلاح: استخدام safe_json_parse بدلاً من json.loads
346
  decision_data = safe_json_parse(json_str)
347
  if not decision_data:
348
+ print(f"❌ فشل تحليل JSON (safe_json_parse) لـ {symbol}: {response_text}")
349
+ return None
350
 
351
+ # الإصلاح: تعديل الحقول المطلوبة لـ SPOT
352
+ required_fields = ['action', 'reasoning', 'risk_assessment', 'stop_loss', 'take_profit', 'expected_target_minutes', 'confidence_level']
353
+ # 'trade_type' لم يعد مطلوباً هنا لأنه دائماً LONG أو غير محدد (HOLD)
354
+ if not validate_required_fields(decision_data, required_fields):
355
  print(f"❌ حقول مطلوبة مفقودة في استجابة النموذج لـ {symbol}")
356
  return None
357
 
358
+ # ✅ الإصلاح: التحقق من أن الإجراء هو BUY أو HOLD فقط
359
+ action = decision_data.get('action')
360
+ if action not in ['BUY', 'HOLD']:
361
+ print(f"⚠️ النموذج اقترح إ��راء غير مدعوم ({action}) لـ {symbol}. سيتم اعتباره HOLD.")
362
+ decision_data['action'] = 'HOLD' # فرض HOLD إذا كان الإجراء غير صالح
363
+
364
+ # ✅ الإصلاح: تحديد trade_type بناءً على الإجراء
365
+ if decision_data['action'] == 'BUY':
366
+ decision_data['trade_type'] = 'LONG'
367
+ else: # إذا كان HOLD
368
+ decision_data['trade_type'] = None # لا يوجد نوع صفقة لـ HOLD
369
+
370
  strategy_value = decision_data.get('strategy')
371
+ if not strategy_value or strategy_value == 'unknown':
372
  decision_data['strategy'] = fallback_strategy
373
 
374
  return decision_data
 
381
  symbol = data_payload['symbol']
382
  # ✅ استخدام raw_ohlcv أولاً ثم ohlcv - الإصلاح الرئيسي
383
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
384
+
385
  if ohlcv_data:
386
  # ✅ تمرير البيانات الخام مباشرة لمحرك تحليل الأنماط
387
  return await self.pattern_engine.analyze_chart_patterns(symbol, ohlcv_data)
388
+
389
  return None
390
  except Exception as e:
391
  print(f"❌ فشل تحليل الأنماط لـ {data_payload.get('symbol')}: {e}")
392
  return None
393
+
394
  def _create_comprehensive_trading_prompt(self, payload: dict, news_text: str, pattern_analysis: dict, whale_data: dict) -> str:
395
  symbol = payload.get('symbol', 'N/A')
396
  current_price = payload.get('current_price', 'N/A')
 
404
  enhanced_final_score = payload.get('enhanced_final_score', 'N/A')
405
  # ✅ استخدام raw_ohlcv أولاً - الإصلاح الرئيسي
406
  ohlcv_data = payload.get('raw_ohlcv') or payload.get('ohlcv', {})
407
+
408
  final_score_display = f"{final_score:.3f}" if isinstance(final_score, (int, float)) else str(final_score)
409
  enhanced_score_display = f"{enhanced_final_score:.3f}" if isinstance(enhanced_final_score, (int, float)) else str(enhanced_final_score)
410
 
 
419
  prompt = f"""
420
  COMPREHENSIVE TRADING ANALYSIS FOR {symbol}
421
 
422
+ 🚨 IMPORTANT SYSTEM CONSTRAINT: This is a SPOT TRADING system ONLY. Decisions MUST be limited to BUY (LONG) or HOLD. SHORT selling is NOT possible.
423
+
424
  🎯 STRATEGY CONTEXT:
425
  - Target Strategy: {target_strategy}
426
  - Recommended Strategy: {recommended_strategy}
 
452
  📋 REASONS FOR CANDIDACY:
453
  {chr(10).join([f"• {reason}" for reason in reasons]) if reasons else "No specific reasons provided"}
454
 
455
+ 🎯 TRADING DECISION INSTRUCTIONS (SPOT ONLY):
456
 
457
+ 1. ANALYZE ALL PROVIDED DATA: technical indicators, whale activity, raw candle patterns, market context.
458
+ 2. FOCUS ON RAW CANDLE DATA for pattern recognition and price action analysis.
459
+ 3. ADHERE STRICTLY TO SPOT TRADING RULES: Only consider BUY (LONG) opportunities or HOLD. DO NOT suggest SELL (SHORT).
460
+ 4. EVALUATE RISK-REWARD RATIO based on support/resistance for a LONG position.
461
+ 5. INTEGRATE WHALE ACTIVITY signals into your decision (relevant for potential buy pressure or lack thereof).
462
+ 6. ASSESS PATTERN STRENGTH and timeframe confirmations from raw candles, looking for bullish signals.
463
+ 7. CONSIDER MARKET SENTIMENT impact.
464
 
465
+ CRITICAL: Provide specific price levels (Stop Loss, Take Profit) suitable for a BUY/LONG trade OR justify a HOLD decision. If analysis suggests a price decrease, the ONLY valid action is HOLD.
466
 
467
+ OUTPUT FORMAT (JSON - SPOT ONLY):
468
  {{
469
+ "action": "BUY/HOLD", # Only BUY or HOLD allowed
470
+ "reasoning": "Detailed explanation integrating ALL data sources, justifying BUY or HOLD based on SPOT trading logic.",
471
  "risk_assessment": "low/medium/high",
472
+ # "trade_type": "LONG", # Implicitly LONG if action is BUY
473
+ "stop_loss": 0.000000, # Required if action is BUY
474
+ "take_profit": 0.000000, # Required if action is BUY
475
+ "expected_target_minutes": 15, # Required if action is BUY
476
+ "confidence_level": 0.85, # Confidence in the BUY or HOLD decision
477
  "strategy": "{target_strategy}",
478
+ "whale_influence": "How whale data influenced the BUY/HOLD decision",
479
+ "pattern_influence": "How raw candle patterns influenced the BUY/HOLD decision",
480
  "key_support_level": 0.000000,
481
  "key_resistance_level": 0.000000,
482
+ "risk_reward_ratio": 2.5 # Calculated for the potential BUY trade
483
  }}
484
  """
485
  return prompt
486
 
487
  def _format_pattern_analysis(self, pattern_analysis):
488
+ if not pattern_analysis:
489
  return "No clear patterns detected across analyzed timeframes"
490
+
491
  confidence = pattern_analysis.get('pattern_confidence', 0)
492
  pattern_name = pattern_analysis.get('pattern_detected', 'unknown')
493
  predicted_direction = pattern_analysis.get('predicted_direction', 'N/A')
494
  movement_percent = pattern_analysis.get('predicted_movement_percent', 'N/A')
495
+
496
  analysis_lines = [
497
  f"🎯 Pattern: {pattern_name}",
498
  f"📊 Confidence: {confidence:.1%}",
 
500
  f"💰 Expected Movement: {movement_percent}%",
501
  f"📝 Analysis: {pattern_analysis.get('pattern_analysis', 'No detailed analysis')}"
502
  ]
503
+
504
  # إضافة مستويات الدعم والمقاومة إذا كانت متوفرة
505
  support_levels = pattern_analysis.get('key_support_levels', [])
506
  resistance_levels = pattern_analysis.get('key_resistance_levels', [])
507
+
508
  if support_levels:
509
  analysis_lines.append(f"🛟 Support Levels: {', '.join([f'{level:.6f}' for level in support_levels[:3]])}")
510
  if resistance_levels:
511
  analysis_lines.append(f"🚧 Resistance Levels: {', '.join([f'{level:.6f}' for level in resistance_levels[:3]])}")
512
+
513
  # إضافة أنماط الشموع إذا كانت متوفرة
514
  candlestick_patterns = pattern_analysis.get('candlestick_patterns', [])
515
  if candlestick_patterns:
516
  analysis_lines.append(f"🕯️ Candlestick Patterns: {', '.join(candlestick_patterns)}")
517
+
518
  return "\n".join(analysis_lines)
519
 
520
  def _format_candle_data_comprehensive(self, ohlcv_data):
521
  """تنسيق شامل لبيانات الشموع الخام"""
522
  if not ohlcv_data:
523
  return "No raw candle data available for analysis"
524
+
525
  try:
526
  timeframes_available = []
527
  total_candles = 0
528
+
529
  for timeframe, candles in ohlcv_data.items():
530
  if candles and len(candles) >= 5: # تخفيف الشرط
531
  timeframes_available.append(f"{timeframe.upper()} ({len(candles)} candles)")
532
  total_candles += len(candles)
533
+
534
  if not timeframes_available:
535
  return "Insufficient candle data across all timeframes"
536
+
537
  summary = f"📊 Available Timeframes: {', '.join(timeframes_available)}\n"
538
  summary += f"📈 Total Candles Available: {total_candles}\n\n"
539
+
540
  # استخدام محرك الأنماط لتنسيق البيانات الخام
541
  pattern_engine = PatternAnalysisEngine(self)
542
  raw_candle_analysis = pattern_engine._format_chart_data_for_llm(ohlcv_data)
543
+
544
  summary += raw_candle_analysis
545
+
546
  return summary
547
  except Exception as e:
548
  return f"Error formatting raw candle data: {str(e)}"
 
552
  try:
553
  if len(candles) < 10: # تخفيف الشرط
554
  return f"Insufficient data ({len(candles)} candles)"
555
+
556
  recent_candles = candles[-15:] # آخر 15 شمعة فقط
557
+
558
  # حساب المتغيرات الأساسية
559
  closes = [c[4] for c in recent_candles]
560
  opens = [c[1] for c in recent_candles]
561
  highs = [c[2] for c in recent_candles]
562
  lows = [c[3] for c in recent_candles]
563
  volumes = [c[5] for c in recent_candles]
564
+
565
  current_price = closes[-1]
566
  first_price = closes[0]
567
  price_change = ((current_price - first_price) / first_price) * 100
568
+
569
  # تحليل الاتجاه
570
  if price_change > 2:
571
  trend = "🟢 UPTREND"
 
573
  trend = "🔴 DOWNTREND"
574
  else:
575
  trend = "⚪ SIDEWAYS"
576
+
577
  # تحليل التقلب
578
  high_max = max(highs)
579
  low_min = min(lows)
580
  volatility = ((high_max - low_min) / low_min) * 100
581
+
582
  # تحليل الحجم
583
  avg_volume = sum(volumes) / len(volumes)
584
  current_volume = volumes[-1]
585
  volume_ratio = current_volume / avg_volume if avg_volume > 0 else 1
586
+
587
  # تحليل الشموع
588
  green_candles = sum(1 for i in range(len(closes)) if closes[i] > opens[i])
589
  red_candles = len(closes) - green_candles
590
  candle_ratio = green_candles / len(closes)
591
+
592
  analysis = [
593
  f"📈 Trend: {trend} ({price_change:+.2f}%)",
594
  f"🌊 Volatility: {volatility:.2f}%",
 
597
  f"💰 Range: {low_min:.6f} - {high_max:.6f}",
598
  f"🎯 Current: {current_price:.6f}"
599
  ]
600
+
601
  return "\n".join(analysis)
602
  except Exception as e:
603
  return f"Analysis error: {str(e)}"
 
606
  """تنسيق سياق السوق"""
607
  if not sentiment_data:
608
  return "No market context data available"
609
+
610
  btc_sentiment = sentiment_data.get('btc_sentiment', 'N/A')
611
  fear_greed = sentiment_data.get('fear_and_greed_index', 'N/A')
612
  market_trend = sentiment_data.get('market_trend', 'N/A')
613
+
614
  lines = [
615
  "🌍 MARKET CONTEXT:",
616
  f"• Bitcoin Sentiment: {btc_sentiment}",
617
  f"• Fear & Greed Index: {fear_greed}",
618
  f"• Market Trend: {market_trend}"
619
  ]
620
+
621
  general_whale = sentiment_data.get('general_whale_activity', {})
622
  if general_whale:
623
  whale_sentiment = general_whale.get('sentiment', 'N/A')
 
625
  lines.append(f"• General Whale Sentiment: {whale_sentiment}")
626
  if critical_alert:
627
  lines.append("• ⚠️ CRITICAL WHALE ALERT")
628
+
629
  return "\n".join(lines)
630
 
631
  async def re_analyze_trade_async(self, trade_data: dict, processed_data: dict):
632
  try:
633
  symbol = trade_data['symbol']
634
  original_strategy = trade_data.get('strategy', 'GENERIC')
635
+
636
  # ✅ التحقق من بيانات الشموع المحدثة - الإصلاح الرئيسي
637
  ohlcv_data = processed_data.get('raw_ohlcv') or processed_data.get('ohlcv')
638
  if not ohlcv_data:
639
  print(f"⚠️ لا توجد بيانات شموع محدثة لـ {symbol} - تخطي إعادة التحليل")
640
  return None
641
+
642
  # جلب جميع البيانات المحدثة
643
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
644
  pattern_analysis = await self._get_pattern_analysis(processed_data)
645
  whale_data = processed_data.get('whale_data', {})
646
+
647
  prompt = self._create_re_analysis_prompt(trade_data, processed_data, news_text, pattern_analysis, whale_data)
648
+
649
  # ✅ حفظ الـ Prompt في R2
650
  if self.r2_service:
651
  analysis_data = {
 
659
  await self.r2_service.save_llm_prompts_async(
660
  symbol, 'trade_reanalysis', prompt, analysis_data
661
  )
662
+
663
+ async with self.semaphore:
664
  response = await self._call_llm(prompt)
665
+
666
  re_analysis_dict = self._parse_re_analysis_response(response, original_strategy, symbol)
667
  if re_analysis_dict:
668
  re_analysis_dict['model_source'] = self.model_name
 
671
  else:
672
  print(f"❌ فشل إعادة تحليل النموذج الضخم لـ {symbol}")
673
  return None
674
+
675
  except Exception as e:
676
  print(f"❌ خطأ في إعادة تحليل LLM: {e}")
677
  traceback.print_exc()
 
680
  def _parse_re_analysis_response(self, response_text: str, fallback_strategy: str, symbol: str) -> dict:
681
  try:
682
  json_str = parse_json_from_response(response_text)
683
+ if not json_str:
684
  return None
685
 
686
  # ✅ الإصلاح: استخدام safe_json_parse بدلاً من json.loads
687
  decision_data = safe_json_parse(json_str)
688
  if not decision_data:
689
+ print(f"❌ فشل تحليل JSON (safe_json_parse) لإعادة التحليل لـ {symbol}: {response_text}")
690
+ return None
691
+
692
+ # ✅ الإصلاح: ��لتحقق من أن الإجراء هو HOLD, CLOSE_TRADE, أو UPDATE_TRADE
693
+ action = decision_data.get('action')
694
+ if action not in ['HOLD', 'CLOSE_TRADE', 'UPDATE_TRADE']:
695
+ print(f"⚠️ النموذج اقترح إجراء إعادة تحليل غير مدعوم ({action}) لـ {symbol}. سيتم اعتباره HOLD.")
696
+ decision_data['action'] = 'HOLD' # فرض HOLD
697
 
698
  strategy_value = decision_data.get('strategy')
699
+ if not strategy_value or strategy_value == 'unknown':
700
  decision_data['strategy'] = fallback_strategy
701
 
702
  return decision_data
 
709
  entry_price = trade_data.get('entry_price', 'N/A')
710
  current_price = processed_data.get('current_price', 'N/A')
711
  strategy = trade_data.get('strategy', 'GENERIC')
712
+ # ✅ الإصلاح: التأكيد على أن الصفقة الحالية هي LONG
713
+ original_trade_type = "LONG" # Since the system is SPOT only
714
+
715
+ try:
716
+ # حساب PnL بناءً على LONG
717
  price_change = ((current_price - entry_price) / entry_price) * 100
718
  price_change_display = f"{price_change:+.2f}%"
719
+ except (TypeError, ZeroDivisionError):
720
  price_change_display = "N/A"
721
+
722
  indicators_summary = format_technical_indicators(processed_data.get('advanced_indicators', {}))
723
  pattern_summary = self._format_pattern_analysis(pattern_analysis)
724
  whale_analysis_section = format_whale_analysis_for_llm(whale_data)
725
  market_context_section = self._format_market_context(processed_data.get('sentiment_data', {}))
726
 
727
  prompt = f"""
728
+ TRADE RE-ANALYSIS FOR {symbol} (SPOT ONLY)
729
+
730
+ 🚨 IMPORTANT SYSTEM CONSTRAINT: This is a SPOT TRADING system ONLY. The open trade is implicitly LONG. Re-analysis should decide to HOLD, CLOSE, or UPDATE this LONG position. SHORT selling is NOT possible.
731
 
732
  📊 TRADE CONTEXT:
733
  - Strategy: {strategy}
734
+ - Entry Price: {entry_price} (LONG position)
735
  - Current Price: {current_price}
736
  - Performance: {price_change_display}
737
  - Trade Age: {trade_data.get('hold_duration_minutes', 'N/A')} minutes
 
751
  📰 LATEST NEWS:
752
  {news_text if news_text else "No significant news found"}
753
 
754
+ 🎯 RE-ANALYSIS INSTRUCTIONS (SPOT - LONG POSITION):
755
 
756
+ 1. Evaluate if the original LONG thesis still holds based on updated raw candle data.
757
+ 2. Consider new whale activity and pattern developments (bullish or bearish implications for the LONG trade).
758
+ 3. Assess current risk-reward ratio for HOLDING the LONG position using latest price action.
759
+ 4. Decide whether to HOLD, CLOSE_TRADE (exit the LONG position), or UPDATE_TRADE (adjust SL/TP for the LONG position).
760
+ 5. Provide specific updated levels if adjusting the LONG trade. DO NOT suggest SHORTING.
761
 
762
+ CRITICAL: The decision must be one of HOLD, CLOSE_TRADE, or UPDATE_TRADE for the existing LONG position.
763
+
764
+ OUTPUT FORMAT (JSON - SPOT RE-ANALYSIS):
765
  {{
766
+ "action": "HOLD/CLOSE_TRADE/UPDATE_TRADE", # Only these three actions are allowed
767
+ "reasoning": "Comprehensive justification for HOLD, CLOSE, or UPDATE of the LONG position, based on updated analysis with emphasis on recent candle patterns and SPOT logic.",
768
+ "new_stop_loss": 0.000000, # If updating the LONG trade
769
+ "new_take_profit": 0.000000, # If updating the LONG trade
770
+ "new_expected_minutes": 15, # If updating the LONG trade
771
+ "confidence_level": 0.85, # Confidence in the re-analysis decision
772
  "strategy": "{strategy}",
773
+ "whale_influence_reanalysis": "How updated whale data influenced the decision for the LONG trade",
774
+ "pattern_influence_reanalysis": "How updated raw candle patterns influenced the decision for the LONG trade",
775
+ "risk_adjustment": "low/medium/high" # Risk associated with continuing to HOLD
776
  }}
777
  """
778
  return prompt