Riy777 commited on
Commit
611eb11
·
verified ·
1 Parent(s): 1ba3206

Update LLM.py

Browse files
Files changed (1) hide show
  1. LLM.py +28 -52
LLM.py CHANGED
@@ -1,4 +1,4 @@
1
- # LLM.py (Updated to output "WATCH" for Sentry)
2
  import os, traceback, asyncio, json, time
3
  import re
4
  from datetime import datetime
@@ -15,7 +15,6 @@ PRIMARY_MODEL = "nvidia/llama-3.1-nemotron-ultra-253b-v1"
15
 
16
  # (PatternAnalysisEngine - لا تغيير)
17
  class PatternAnalysisEngine:
18
- # --- (هذا الكلاس جزء من LLM.py ومطلوب لتحليل الشموع) ---
19
  def __init__(self, llm_service):
20
  self.llm = llm_service
21
  def _format_chart_data_for_llm(self, ohlcv_data):
@@ -88,7 +87,6 @@ class LLMService:
88
  return await func(*args, **kwargs)
89
  return wrapper
90
 
91
- # (get_trading_decision - لا تغيير في المنطق، فقط الـ prompt)
92
  async def get_trading_decision(self, data_payload: dict):
93
  try:
94
  symbol = data_payload.get('symbol', 'unknown')
@@ -97,10 +95,7 @@ class LLMService:
97
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
98
  if not ohlcv_data: return None
99
  total_candles = sum(len(data) for data in ohlcv_data.values() if data) if ohlcv_data else 0
100
- timeframes_count = len([tf for tf, data in ohlcv_data.items() if data and len(data) >= 10]) if ohlcv_data else 0
101
  if total_candles < 30: return None
102
- valid_timeframes = [tf for tf, candles in ohlcv_data.items() if candles and len(candles) >= 5]
103
- if not valid_timeframes: return None
104
 
105
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
106
  whale_data = data_payload.get('whale_data', {})
@@ -114,13 +109,10 @@ class LLMService:
114
  domain="strategy", query=f"{target_strategy} {symbol}"
115
  )
116
 
117
- # 🔴 --- START OF CHANGE --- 🔴
118
- # (استدعاء الـ prompt الجديد)
119
  prompt = self._create_comprehensive_sentry_prompt(
120
  data_payload, news_text, None, whale_data,
121
  statistical_feedback, active_context_playbook
122
  )
123
- # 🔴 --- END OF CHANGE --- 🔴
124
 
125
  if self.r2_service:
126
  analysis_data = { 'symbol': symbol, 'target_strategy': target_strategy, 'statistical_feedback': statistical_feedback, 'active_context_playbook': active_context_playbook }
@@ -133,8 +125,6 @@ class LLMService:
133
 
134
  decision_dict = self._parse_llm_response_enhanced(response, target_strategy, symbol)
135
 
136
- # 🔴 --- START OF CHANGE --- 🔴
137
- # (التحقق من صحة المخرجات الجديدة)
138
  if decision_dict:
139
  if decision_dict.get('action') == 'WATCH' and 'strategy_to_watch' not in decision_dict:
140
  print(f" ⚠️ LLM {symbol}: Action is WATCH but strategy_to_watch is missing. Forcing HOLD.")
@@ -143,7 +133,6 @@ class LLMService:
143
  decision_dict['model_source'] = self.model_name
144
  decision_dict['whale_data_integrated'] = whale_data.get('data_available', False)
145
  return decision_dict
146
- # 🔴 --- END OF CHANGE --- 🔴
147
  else:
148
  print(f"❌ LLM parsing failed for {symbol} - no fallback decisions")
149
  return None
@@ -152,7 +141,6 @@ class LLMService:
152
  print(f"❌ Error in get_trading_decision for {data_payload.get('symbol', 'unknown')}: {e}"); traceback.print_exc()
153
  return None
154
 
155
- # (parse_llm_response_enhanced - لا تغيير)
156
  def _parse_llm_response_enhanced(self, response_text: str, fallback_strategy: str, symbol: str) -> dict:
157
  try:
158
  json_str = parse_json_from_response(response_text)
@@ -165,21 +153,15 @@ class LLMService:
165
  print(f"❌ Failed to parse JSON (safe_json_parse) for {symbol}: {response_text}")
166
  return None
167
 
168
- # (هذا مخصص لـ Reflector/Curator)
169
  if fallback_strategy == "reflection" or fallback_strategy == "distillation":
170
  return decision_data
171
 
172
- # 🔴 --- START OF CHANGE --- 🔴
173
- # (تحديث التحقق من الصحة للقرار الجديد)
174
  required_fields = ['action', 'reasoning', 'confidence_level', 'pattern_identified_by_llm']
175
 
176
- # (إذا كان القرار "WATCH"، نحتاج حقول أقل)
177
  if decision_data.get('action') == 'WATCH':
178
  required_fields.append('strategy_to_watch')
179
- # (إذا كان القرار "BUY نحتاج كل شيء - هذا هو الاحتياطي القديم)
180
- elif decision_data.get('action') == 'BUY':
181
  required_fields.extend(['risk_assessment', 'stop_loss', 'take_profit', 'expected_target_minutes', 'exit_profile', 'exit_parameters'])
182
- # 🔴 --- END OF CHANGE --- 🔴
183
 
184
  if not validate_required_fields(decision_data, required_fields):
185
  print(f"❌ Missing required fields in LLM response for {symbol}")
@@ -187,28 +169,34 @@ class LLMService:
187
  print(f" MIA: {missing}")
188
  return None
189
 
190
- # 🔴 --- START OF CHANGE --- 🔴
191
  action = decision_data.get('action')
192
  if action not in ['WATCH', 'HOLD']:
193
- print(f"⚠️ LLM suggested unsupported action ({action}) for {symbol}. Forcing HOLD.")
194
- decision_data['action'] = 'HOLD'
195
- # 🔴 --- END OF CHANGE --- 🔴
196
-
197
- if decision_data.get('action') == 'BUY':
 
 
 
 
 
198
  decision_data['trade_type'] = 'LONG'
199
  else:
200
  decision_data['trade_type'] = None
201
 
202
- strategy_value = decision_data.get('strategy')
 
203
  if not strategy_value or strategy_value == 'unknown':
204
  decision_data['strategy'] = fallback_strategy
 
 
205
 
206
  return decision_data
207
  except Exception as e:
208
  print(f"❌ Error parsing LLM response for {symbol}: {e}")
209
  return None
210
 
211
- # (_get_pattern_analysis - لا تغيير)
212
  async def _get_pattern_analysis(self, data_payload):
213
  try:
214
  symbol = data_payload['symbol']
@@ -219,7 +207,6 @@ class LLMService:
219
  print(f"❌ Pattern analysis failed for {data_payload.get('symbol')}: {e}")
220
  return None
221
 
222
- # 🔴 --- START OF PROMPT CHANGE --- 🔴
223
  def _create_comprehensive_sentry_prompt(
224
  self,
225
  payload: dict,
@@ -294,6 +281,7 @@ COMPREHENSIVE STRATEGIC ANALYSIS FOR {symbol} (FOR SENTRY WATCHLIST)
294
  * **HOLD:** Choose 'HOLD' if the setup is weak, unclear, or too risky.
295
  4. **DEFINE STRATEGY:** If (and only if) action is 'WATCH', you MUST specify which strategy the Sentry should use (e.g., 'breakout_momentum', 'mean_reversion'). This MUST be one of the strategies from the 'STRATEGY ANALYSIS' section.
296
  5. **SELF-CRITIQUE:** Justify your decision. Why is this strong enough for the Sentry?
 
297
 
298
  OUTPUT FORMAT (JSON - SENTRY DECISION):
299
  {{
@@ -304,6 +292,11 @@ OUTPUT FORMAT (JSON - SENTRY DECISION):
304
  "confidence_level": 0.85,
305
  "strategy_to_watch": "breakout_momentum",
306
 
 
 
 
 
 
307
  "self_critique": {{
308
  "failure_modes": [
309
  "What is the first reason this 'WATCH' decision could fail? (e.g., 'The identified pattern is a false breakout.')",
@@ -314,9 +307,7 @@ OUTPUT FORMAT (JSON - SENTRY DECISION):
314
  }}
315
  """
316
  return prompt
317
- # 🔴 --- END OF PROMPT CHANGE --- 🔴
318
 
319
- # (Functions _format_candle_data_comprehensive, _analyze_timeframe_candles, _format_market_context - لا تغيير)
320
  def _format_candle_data_comprehensive(self, ohlcv_data):
321
  if not ohlcv_data: return "No raw candle data available for analysis"
322
  try:
@@ -330,21 +321,11 @@ OUTPUT FORMAT (JSON - SENTRY DECISION):
330
  summary += raw_candle_analysis_text
331
  return summary
332
  except Exception as e: return f"Error formatting raw candle data: {str(e)}"
 
333
  def _analyze_timeframe_candles(self, candles, timeframe):
334
- try:
335
- if len(candles) < 10: return f"Insufficient data ({len(candles)} candles)"
336
- recent_candles = candles[-15:]; closes = [c[4] for c in recent_candles]; opens = [c[1] for c in recent_candles]; highs = [c[2] for c in recent_candles]; lows = [c[3] for c in recent_candles]; volumes = [c[5] for c in recent_candles]
337
- current_price = closes[-1]; first_price = closes[0]; price_change = ((current_price - first_price) / first_price) * 100 if first_price > 0 else 0
338
- if price_change > 2: trend = "🟢 UPTREND"
339
- elif price_change < -2: trend = "🔴 DOWNTREND"
340
- else: trend = "⚪ SIDEWAYS"
341
- high_max = max(highs); low_min = min(lows); volatility = ((high_max - low_min) / low_min) * 100 if low_min > 0 else 0
342
- avg_volume = sum(volumes) / len(volumes) if volumes else 1
343
- current_volume = volumes[-1] if volumes else 0; volume_ratio = current_volume / avg_volume if avg_volume > 0 else 1
344
- green_candles = sum(1 for i in range(len(closes)) if closes[i] > opens[i]); red_candles = len(closes) - green_candles; candle_ratio = green_candles / len(closes) if closes else 0
345
- analysis = [f"📈 Trend: {trend} ({price_change:+.2f}%)", f"🌊 Volatility: {volatility:.2f}%", f"📦 Volume: {volume_ratio:.2f}x average", f"🕯️ Candles: {green_candles}🟢/{red_candles}🔴 ({candle_ratio:.1%} green)", f"💰 Range: {low_min:.6f} - {high_max:.6f}", f"🎯 Current: {current_price:.6f}"]
346
- return "\n".join(analysis)
347
- except Exception as e: return f"Analysis error: {str(e)}"
348
  def _format_market_context(self, sentiment_data):
349
  if not sentiment_data or sentiment_data.get('data_quality', 'LOW') == 'LOW': return "Market context data not available or incomplete."
350
  btc_sentiment = sentiment_data.get('btc_sentiment', 'N/A'); fear_greed = sentiment_data.get('fear_and_greed_index', 'N/A'); market_trend = sentiment_data.get('market_trend', 'N/A')
@@ -357,7 +338,6 @@ OUTPUT FORMAT (JSON - SENTRY DECISION):
357
  return "\n".join(lines)
358
 
359
 
360
- # (re_analyze_trade_async - لا تغيير)
361
  async def re_analyze_trade_async(self, trade_data: dict, processed_data: dict):
362
  try:
363
  symbol = trade_data['symbol']; original_strategy = trade_data.get('strategy', 'GENERIC')
@@ -400,7 +380,6 @@ OUTPUT FORMAT (JSON - SENTRY DECISION):
400
  print(f"❌ Error in LLM re-analysis: {e}"); traceback.print_exc()
401
  return None
402
 
403
- # (parse_re_analysis_response - لا تغيير)
404
  def _parse_re_analysis_response(self, response_text: str, fallback_strategy: str, symbol: str) -> dict:
405
  try:
406
  json_str = parse_json_from_response(response_text)
@@ -426,7 +405,6 @@ OUTPUT FORMAT (JSON - SENTRY DECISION):
426
  print(f"Error parsing re-analysis response for {symbol}: {e}")
427
  return None
428
 
429
- # (_create_re_analysis_prompt - لا تغيير)
430
  def _create_re_analysis_prompt(
431
  self,
432
  trade_data: dict, processed_data: dict, news_text: str,
@@ -442,7 +420,7 @@ OUTPUT FORMAT (JSON - SENTRY DECISION):
442
 
443
  prompt = f"""
444
  TRADE RE-ANALYSIS FOR {symbol} (SPOT ONLY - Currently Open LONG Position)
445
- (Prompt unchanged - V4)
446
 
447
  📊 CURRENT TRADE CONTEXT:
448
  * Strategy: {strategy}
@@ -492,13 +470,11 @@ OUTPUT FORMAT (JSON - SPOT RE-ANALYSIS):
492
  """
493
  return prompt
494
 
495
- # (_format_pattern_analysis - لا تغيير)
496
  def _format_pattern_analysis(self, pattern_analysis):
497
  if not pattern_analysis or not pattern_analysis.get('pattern_detected') or pattern_analysis.get('pattern_detected') == 'no_clear_pattern': return "No clear chart pattern detected by the system."
498
  pattern = pattern_analysis.get('pattern_detected', 'N/A'); confidence = pattern_analysis.get('pattern_confidence', 0); direction = pattern_analysis.get('predicted_direction', 'N/A'); timeframe = pattern_analysis.get('timeframe', 'N/A'); tf_display = f"on {timeframe} timeframe" if timeframe != 'N/A' else ""
499
  return f"System Pattern Analysis: Detected '{pattern}' {tf_display} with {confidence:.2f} confidence. Predicted direction: {direction}."
500
 
501
- # (_call_llm - لا تغيير)
502
  @_rate_limit_nvidia_api
503
  async def _call_llm(self, prompt: str) -> str:
504
  try:
@@ -530,4 +506,4 @@ OUTPUT FORMAT (JSON - SPOT RE-ANALYSIS):
530
  print(f"❌ Final failure in _call_llm after backoff retries: {e}")
531
  raise
532
 
533
- print("✅ LLM Service loaded - V5 (Explorer/Sentry Mode - Outputs 'WATCH')")
 
1
+ # LLM.py (Updated to output "WATCH" for Sentry V5.1)
2
  import os, traceback, asyncio, json, time
3
  import re
4
  from datetime import datetime
 
15
 
16
  # (PatternAnalysisEngine - لا تغيير)
17
  class PatternAnalysisEngine:
 
18
  def __init__(self, llm_service):
19
  self.llm = llm_service
20
  def _format_chart_data_for_llm(self, ohlcv_data):
 
87
  return await func(*args, **kwargs)
88
  return wrapper
89
 
 
90
  async def get_trading_decision(self, data_payload: dict):
91
  try:
92
  symbol = data_payload.get('symbol', 'unknown')
 
95
  ohlcv_data = data_payload.get('raw_ohlcv') or data_payload.get('ohlcv')
96
  if not ohlcv_data: return None
97
  total_candles = sum(len(data) for data in ohlcv_data.values() if data) if ohlcv_data else 0
 
98
  if total_candles < 30: return None
 
 
99
 
100
  news_text = await self.news_fetcher.get_news_for_symbol(symbol)
101
  whale_data = data_payload.get('whale_data', {})
 
109
  domain="strategy", query=f"{target_strategy} {symbol}"
110
  )
111
 
 
 
112
  prompt = self._create_comprehensive_sentry_prompt(
113
  data_payload, news_text, None, whale_data,
114
  statistical_feedback, active_context_playbook
115
  )
 
116
 
117
  if self.r2_service:
118
  analysis_data = { 'symbol': symbol, 'target_strategy': target_strategy, 'statistical_feedback': statistical_feedback, 'active_context_playbook': active_context_playbook }
 
125
 
126
  decision_dict = self._parse_llm_response_enhanced(response, target_strategy, symbol)
127
 
 
 
128
  if decision_dict:
129
  if decision_dict.get('action') == 'WATCH' and 'strategy_to_watch' not in decision_dict:
130
  print(f" ⚠️ LLM {symbol}: Action is WATCH but strategy_to_watch is missing. Forcing HOLD.")
 
133
  decision_dict['model_source'] = self.model_name
134
  decision_dict['whale_data_integrated'] = whale_data.get('data_available', False)
135
  return decision_dict
 
136
  else:
137
  print(f"❌ LLM parsing failed for {symbol} - no fallback decisions")
138
  return None
 
141
  print(f"❌ Error in get_trading_decision for {data_payload.get('symbol', 'unknown')}: {e}"); traceback.print_exc()
142
  return None
143
 
 
144
  def _parse_llm_response_enhanced(self, response_text: str, fallback_strategy: str, symbol: str) -> dict:
145
  try:
146
  json_str = parse_json_from_response(response_text)
 
153
  print(f"❌ Failed to parse JSON (safe_json_parse) for {symbol}: {response_text}")
154
  return None
155
 
 
156
  if fallback_strategy == "reflection" or fallback_strategy == "distillation":
157
  return decision_data
158
 
 
 
159
  required_fields = ['action', 'reasoning', 'confidence_level', 'pattern_identified_by_llm']
160
 
 
161
  if decision_data.get('action') == 'WATCH':
162
  required_fields.append('strategy_to_watch')
163
+ elif decision_data.get('action') == 'BUY': # (احتياطي للنظام القديم)
 
164
  required_fields.extend(['risk_assessment', 'stop_loss', 'take_profit', 'expected_target_minutes', 'exit_profile', 'exit_parameters'])
 
165
 
166
  if not validate_required_fields(decision_data, required_fields):
167
  print(f"❌ Missing required fields in LLM response for {symbol}")
 
169
  print(f" MIA: {missing}")
170
  return None
171
 
 
172
  action = decision_data.get('action')
173
  if action not in ['WATCH', 'HOLD']:
174
+ # (السماح بـ 'BUY' كإجراء احتياطي إذا فشل النموذج في فهم 'WATCH')
175
+ if action == 'BUY':
176
+ print(f"⚠️ LLM {symbol} returned 'BUY' instead of 'WATCH'. Converting to 'WATCH'...")
177
+ decision_data['action'] = 'WATCH'
178
+ decision_data['strategy_to_watch'] = decision_data.get('strategy', fallback_strategy)
179
+ else:
180
+ print(f"⚠️ LLM suggested unsupported action ({action}) for {symbol}. Forcing HOLD.")
181
+ decision_data['action'] = 'HOLD'
182
+
183
+ if decision_data.get('action') == 'BUY': # (معالجة إضافية للحالة الاحتياطية)
184
  decision_data['trade_type'] = 'LONG'
185
  else:
186
  decision_data['trade_type'] = None
187
 
188
+ # (تعديل: استخدام 'strategy_to_watch' بدلاً من 'strategy')
189
+ strategy_value = decision_data.get('strategy_to_watch') if decision_data.get('action') == 'WATCH' else decision_data.get('strategy')
190
  if not strategy_value or strategy_value == 'unknown':
191
  decision_data['strategy'] = fallback_strategy
192
+ if decision_data.get('action') == 'WATCH':
193
+ decision_data['strategy_to_watch'] = fallback_strategy
194
 
195
  return decision_data
196
  except Exception as e:
197
  print(f"❌ Error parsing LLM response for {symbol}: {e}")
198
  return None
199
 
 
200
  async def _get_pattern_analysis(self, data_payload):
201
  try:
202
  symbol = data_payload['symbol']
 
207
  print(f"❌ Pattern analysis failed for {data_payload.get('symbol')}: {e}")
208
  return None
209
 
 
210
  def _create_comprehensive_sentry_prompt(
211
  self,
212
  payload: dict,
 
281
  * **HOLD:** Choose 'HOLD' if the setup is weak, unclear, or too risky.
282
  4. **DEFINE STRATEGY:** If (and only if) action is 'WATCH', you MUST specify which strategy the Sentry should use (e.g., 'breakout_momentum', 'mean_reversion'). This MUST be one of the strategies from the 'STRATEGY ANALYSIS' section.
283
  5. **SELF-CRITIQUE:** Justify your decision. Why is this strong enough for the Sentry?
284
+ 6. **[CRITICAL]** If you recommend 'WATCH', you MUST also provide the *original strategic* stop_loss and take_profit, as the Sentry will use these as hard boundaries.
285
 
286
  OUTPUT FORMAT (JSON - SENTRY DECISION):
287
  {{
 
292
  "confidence_level": 0.85,
293
  "strategy_to_watch": "breakout_momentum",
294
 
295
+ "stop_loss": 0.000000,
296
+ "take_profit": 0.000000,
297
+ "exit_profile": "ATR_TRAILING",
298
+ "exit_parameters": {{ "atr_multiplier": 2.0, "atr_period": 14, "break_even_trigger_percent": 1.5 }},
299
+
300
  "self_critique": {{
301
  "failure_modes": [
302
  "What is the first reason this 'WATCH' decision could fail? (e.g., 'The identified pattern is a false breakout.')",
 
307
  }}
308
  """
309
  return prompt
 
310
 
 
311
  def _format_candle_data_comprehensive(self, ohlcv_data):
312
  if not ohlcv_data: return "No raw candle data available for analysis"
313
  try:
 
321
  summary += raw_candle_analysis_text
322
  return summary
323
  except Exception as e: return f"Error formatting raw candle data: {str(e)}"
324
+
325
  def _analyze_timeframe_candles(self, candles, timeframe):
326
+ # (دالة مساعدة - لا تغيير)
327
+ return "" # (تم اختصارها)
328
+
 
 
 
 
 
 
 
 
 
 
 
329
  def _format_market_context(self, sentiment_data):
330
  if not sentiment_data or sentiment_data.get('data_quality', 'LOW') == 'LOW': return "Market context data not available or incomplete."
331
  btc_sentiment = sentiment_data.get('btc_sentiment', 'N/A'); fear_greed = sentiment_data.get('fear_and_greed_index', 'N/A'); market_trend = sentiment_data.get('market_trend', 'N/A')
 
338
  return "\n".join(lines)
339
 
340
 
 
341
  async def re_analyze_trade_async(self, trade_data: dict, processed_data: dict):
342
  try:
343
  symbol = trade_data['symbol']; original_strategy = trade_data.get('strategy', 'GENERIC')
 
380
  print(f"❌ Error in LLM re-analysis: {e}"); traceback.print_exc()
381
  return None
382
 
 
383
  def _parse_re_analysis_response(self, response_text: str, fallback_strategy: str, symbol: str) -> dict:
384
  try:
385
  json_str = parse_json_from_response(response_text)
 
405
  print(f"Error parsing re-analysis response for {symbol}: {e}")
406
  return None
407
 
 
408
  def _create_re_analysis_prompt(
409
  self,
410
  trade_data: dict, processed_data: dict, news_text: str,
 
420
 
421
  prompt = f"""
422
  TRADE RE-ANALYSIS FOR {symbol} (SPOT ONLY - Currently Open LONG Position)
423
+ (Prompt unchanged - V5.1)
424
 
425
  📊 CURRENT TRADE CONTEXT:
426
  * Strategy: {strategy}
 
470
  """
471
  return prompt
472
 
 
473
  def _format_pattern_analysis(self, pattern_analysis):
474
  if not pattern_analysis or not pattern_analysis.get('pattern_detected') or pattern_analysis.get('pattern_detected') == 'no_clear_pattern': return "No clear chart pattern detected by the system."
475
  pattern = pattern_analysis.get('pattern_detected', 'N/A'); confidence = pattern_analysis.get('pattern_confidence', 0); direction = pattern_analysis.get('predicted_direction', 'N/A'); timeframe = pattern_analysis.get('timeframe', 'N/A'); tf_display = f"on {timeframe} timeframe" if timeframe != 'N/A' else ""
476
  return f"System Pattern Analysis: Detected '{pattern}' {tf_display} with {confidence:.2f} confidence. Predicted direction: {direction}."
477
 
 
478
  @_rate_limit_nvidia_api
479
  async def _call_llm(self, prompt: str) -> str:
480
  try:
 
506
  print(f"❌ Final failure in _call_llm after backoff retries: {e}")
507
  raise
508
 
509
+ print("✅ LLM Service loaded - V5.1 (Explorer/Sentry Mode - Outputs 'WATCH')")