Update LLM.py
Browse files
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 |
-
|
| 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 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
decision_data['trade_type'] = 'LONG'
|
| 199 |
else:
|
| 200 |
decision_data['trade_type'] = None
|
| 201 |
|
| 202 |
-
|
|
|
|
| 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 |
-
|
| 335 |
-
|
| 336 |
-
|
| 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 -
|
| 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')")
|