File size: 16,280 Bytes
bc727d7
b4f96f7
 
 
 
 
c881126
 
f01594d
 
c881126
b4f96f7
c881126
f01594d
c881126
f01594d
 
b4f96f7
f01594d
 
b4f96f7
f01594d
 
b4f96f7
a01528e
 
0f77fc8
 
b4f96f7
0f77fc8
c881126
 
 
 
 
b4f96f7
c881126
 
b4f96f7
bc727d7
0f77fc8
f01594d
b4f96f7
 
 
 
 
 
a01528e
c881126
 
 
b4f96f7
c881126
 
 
 
 
 
 
 
b4f96f7
c881126
b4f96f7
 
 
 
 
 
 
0f77fc8
b4f96f7
c881126
005898f
c881126
b4f96f7
 
 
 
0f77fc8
b4f96f7
 
 
 
 
 
 
 
 
 
 
 
 
0f77fc8
c881126
b4f96f7
c881126
0f77fc8
b4f96f7
 
 
 
0f77fc8
b4f96f7
 
0f77fc8
c881126
005898f
b4f96f7
c881126
b4f96f7
 
f01594d
b4f96f7
 
 
c881126
005898f
b4f96f7
 
bc727d7
 
005898f
c881126
b4f96f7
a01528e
b4f96f7
0f77fc8
c881126
0f77fc8
b4f96f7
 
 
0f77fc8
b4f96f7
 
 
 
a01528e
b4f96f7
 
 
0f77fc8
b4f96f7
0f77fc8
b4f96f7
c881126
b4f96f7
 
f01594d
b4f96f7
 
 
c881126
a01528e
b4f96f7
 
bc727d7
 
a01528e
c881126
b4f96f7
3bf4b49
b4f96f7
c881126
0f77fc8
c881126
b4f96f7
c881126
 
b4f96f7
 
 
 
 
 
 
 
 
0f77fc8
b4f96f7
 
be6b412
b4f96f7
 
 
 
 
c881126
b4f96f7
 
c881126
 
 
b4f96f7
 
 
 
 
 
 
0f77fc8
c881126
b4f96f7
 
 
 
 
 
 
 
 
 
 
 
c881126
b4f96f7
 
 
 
 
 
 
 
 
 
 
c881126
 
b4f96f7
c881126
b4f96f7
 
 
c881126
b4f96f7
 
 
c881126
 
b4f96f7
 
 
 
c881126
 
b4f96f7
c881126
 
 
b4f96f7
c881126
b4f96f7
c881126
b4f96f7
f01594d
c881126
b4f96f7
 
c881126
b4f96f7
f01594d
0f77fc8
005898f
c881126
b4f96f7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c881126
b4f96f7
 
 
 
c881126
 
 
b4f96f7
c881126
b4f96f7
c881126
b4f96f7
c881126
b4f96f7
 
 
 
 
 
 
 
 
 
 
c881126
b4f96f7
c881126
b4f96f7
 
 
 
0f77fc8
3cd8123
b4f96f7
 
bc727d7
b4f96f7
 
f01594d
c881126
b4f96f7
 
 
f01594d
0f77fc8
c881126
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# LLM.py (V13.7 - Production Fix - FULL PROMPTS)
import os
import traceback
import json
import re
import time
from datetime import datetime
from typing import Dict, Any, Optional
from openai import AsyncOpenAI, RateLimitError, APIError

# ==============================================================================
# 🔌 إعدادات الاتصال بالنموذج (Model Connection Settings)
# ==============================================================================
LLM_API_URL = os.getenv("LLM_API_URL", "https://integrate.api.nvidia.com/v1")
LLM_API_KEY = os.getenv("LLM_API_KEY")
LLM_MODEL = os.getenv("LLM_MODEL", "nvidia/llama-3.1-nemotron-ultra-253b-v1")

# بارامترات التوليد (مضبوطة لصرامة التحليل)
LLM_TEMPERATURE = 0.2
LLM_TOP_P = 0.7
LLM_MAX_TOKENS = 16384  # السماح بأقصى طول استجابة ممكن
LLM_FREQUENCY_PENALTY = 0.8
LLM_PRESENCE_PENALTY = 0.5
CLIENT_TIMEOUT = 300.0  # 5 دقائق مهلة للردود المعقدة

class LLMService:
    def __init__(self):
        if not LLM_API_KEY:
            raise ValueError("❌ [LLM FATAL] LLM_API_KEY environment variable is missing!")
        
        self.client = AsyncOpenAI(
            base_url=LLM_API_URL,
            api_key=LLM_API_KEY,
            timeout=CLIENT_TIMEOUT
        )
        # سيتم حقن هذه التبعيات لاحقاً من app.py
        self.r2_service = None
        self.learning_hub = None 
        
        print(f"🧠 [LLM V13.7] Omniscient Brain Online. Model: {LLM_MODEL}")

    async def _call_llm(self, prompt: str) -> Optional[str]:
        """
        تنفيذ استدعاء API للنموذج مع تفعيل وضع التفكير العميق (Nemotron Specific).
        """
        # ⚠️ عبارة تفعيل وضع التفكير الخاصة بنموذج Nemotron
        system_prompt_trigger = "detailed thinking on"

        try:
            response = await self.client.chat.completions.create(
                model=LLM_MODEL,
                messages=[
                    {"role": "system", "content": system_prompt_trigger},
                    {"role": "user", "content": prompt}
                ],
                temperature=LLM_TEMPERATURE,
                top_p=LLM_TOP_P,
                max_tokens=LLM_MAX_TOKENS,
                frequency_penalty=LLM_FREQUENCY_PENALTY,
                presence_penalty=LLM_PRESENCE_PENALTY,
                stream=False,
                response_format={"type": "json_object"} # إجبار النموذج على الرد بصيغة JSON
            )
            
            if response.choices and response.choices[0].message.content:
                return response.choices[0].message.content
            else:
                print("⚠️ [LLM Warning] Received empty response from model.")
                return None
                
        except Exception as e:
            print(f"❌ [LLM Call Error] API request failed: {e}")
            return None

    def _parse_json_secure(self, text: str) -> Optional[Dict]:
        """
        محلل JSON قوي يحاول استخراج أول كائن JSON صالح من النص،
        حتى لو كان الرد يحتوي على نصوص إضافية قبل أو بعد الـ JSON.
        """
        try:
            # البحث عن نمط {...} عبر الأسطر المتعددة
            json_match = re.search(r'\{.*\}', text, re.DOTALL)
            if json_match:
                return json.loads(json_match.group(0))
            else:
                print("⚠️ [LLM Parser] No JSON object found in response text.")
                return None
        except json.JSONDecodeError as e:
            print(f"⚠️ [LLM Parser] JSON decode failed: {e}")
            return None
        except Exception as e:
            print(f"❌ [LLM Parser] Unexpected error: {e}")
            return None

    # ==================================================================
    # 🧠 الوظيفة الرئيسية 1: قرار الدخول الاستراتيجي (Layer 3)
    # ==================================================================
    async def get_trading_decision(self, candidate_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        تحليل بيانات المرشح الكاملة واتخاذ قرار نهائي بالمراقبة أو الرفض.
        """
        symbol = candidate_data.get('symbol', 'UNKNOWN_ASSET')
        try:
            # 1. جلب سياق التعلم المؤسسي (Institutional Memory)
            learning_context = "Playbook: No specific prior learning records found for this context."
            if self.learning_hub:
                learning_context = await self.learning_hub.get_active_context_for_llm("general", f"{symbol} entry analysis")

            # 2. بناء البرومبت الشامل (Heavyweight Prompt)
            prompt = self._create_heavyweight_entry_prompt(candidate_data, learning_context)
            
            # 3. استشارة النموذج
            response_text = await self._call_llm(prompt)
            if not response_text: return None
            
            # 4. تحليل الرد
            decision = self._parse_json_secure(response_text)

            # 5. أرشفة عملية اتخاذ القرار (للشفافية والتدقيق المستقبلي)
            if self.r2_service and decision:
                # [FIX APPLIED]: Changed save_llm_prompt_async to save_llm_prompts_async (plural)
                await self.r2_service.save_llm_prompts_async(symbol, "entry_decision_full", prompt, response_text)

            return decision

        except Exception as e:
            print(f"❌ [LLM Entry Error] Critical failure for {symbol}: {e}")
            traceback.print_exc()
            return None

    # ==================================================================
    # 🔄 الوظيفة الرئيسية 2: إعادة التحليل الدوري (Strategic Re-eval)
    # ==================================================================
    async def re_analyze_trade_async(self, trade_data: Dict[str, Any], current_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        مراجعة صفقة مفتوحة بناءً على تغيرات السوق وإصدار أوامر تحديث أو خروج.
        """
        symbol = trade_data.get('symbol', 'UNKNOWN_ASSET')
        try:
            # 1. جلب سياق التعلم الخاص بالاستراتيجية
            strategy_name = trade_data.get('entry_reason', 'GENERIC_STRATEGY')
            learning_context = "Playbook: Stick to original trading plan unless validated invalidation criteria are met."
            if self.learning_hub:
                learning_context = await self.learning_hub.get_active_context_for_llm("strategy", f"{symbol} re-eval {strategy_name}")

            # 2. بناء برومبت إعادة التحليل
            prompt = self._create_heavyweight_reanalysis_prompt(trade_data, current_data, learning_context)
            
            # 3. استشارة النموذج
            response_text = await self._call_llm(prompt)
            if not response_text: return None
            
            # 4. تحليل الرد
            decision = self._parse_json_secure(response_text)

            # 5. الأرشفة
            if self.r2_service and decision:
                # [FIX APPLIED]: Changed save_llm_prompt_async to save_llm_prompts_async (plural)
                await self.r2_service.save_llm_prompts_async(symbol, "re_analysis_full", prompt, response_text)

            return decision

        except Exception as e:
            print(f"❌ [LLM Re-Eval Error] Critical failure for {symbol}: {e}")
            return None

    # ==================================================================
    # 📝 قسم هندسة البرومبتات الكاملة (Full Prompt Engineering)
    # ==================================================================
    def _create_heavyweight_entry_prompt(self, data: Dict[str, Any], learning_context: str) -> str:
        """
        إنشاء برومبت ضخم يحتوي على كل التفاصيل المتاحة بدون أي اختصار.
        """
        symbol = data.get('symbol', 'UNKNOWN')
        current_price = data.get('current_price', 0.0)
        
        # --- بيانات الطبقات السابقة (Preliminary Scores) ---
        l1_total_score = data.get('enhanced_final_score', 0.0)
        l2_total_score = data.get('layer2_score', 0.0)
        
        titan_raw_score = data.get('titan_details', {}).get('score', 0.0)
        titan_trend_label = "STRONG_UPTREND" if titan_raw_score > 0.7 else "UPTREND" if titan_raw_score > 0.5 else "NEUTRAL/WEAK"
        
        pat_data = data.get('pattern_details', {})
        pattern_name = pat_data.get('pattern_detected', 'None detected')
        pattern_confidence = pat_data.get('pattern_confidence', 0.0)
        
        mc_prob = data.get('components', {}).get('mc_score', 0.0)

        # --- الأدلة الخام (Raw Evidence Data) ---
        # 1. بيانات الحيتان
        whale = data.get('whale_data', {})
        whale_1h = whale.get('exchange_flows', {})
        whale_24h = whale.get('accumulation_analysis_24h', {})
        
        whale_evidence_block = f"""
   - [1H Window] Net Flow to Exchanges: ${whale_1h.get('net_flow_usd', 0):,.2f}
   - [1H Window] Deposit Tx Count: {whale_1h.get('deposit_count', 0)} | Withdrawal Tx Count: {whale_1h.get('withdrawal_count', 0)}
   - [24H Window] Net Accumulation Flow: ${whale_24h.get('net_flow_usd', 0):,.2f}
   - [24H Window] Total Large Transactions: {whale_24h.get('whale_transfers_count', 0)}
   - [24H Window] Relative Flow Impact: {whale_24h.get('relative_net_flow_percent', 0):.4f}% of daily volume
"""

        # 2. نص الأخبار الكامل
        raw_news_text = data.get('news_text', 'No specific news articles found for this asset in the last 12 hours.')

        # 3. لقطة حركة السعر (OHLCV Snapshot)
        ohlcv_data = data.get('ohlcv_sample', {})
        price_snapshot_block = ""
        for tf, candle in ohlcv_data.items():
            if candle and len(candle) >= 6:
                # Format: [Timestamp, Open, High, Low, Close, Volume]
                price_snapshot_block += f"   - {tf.upper()} Frame: Open={candle[1]}, High={candle[2]}, Low={candle[3]}, Close={candle[4]}, Vol={candle[5]}\n"

        # --- تجميع البرومبت النهائي ---
        return f"""
YOU ARE THE OMNISCIENT BRAIN. A skeptical, master-level crypto trading AI with absolute veto power.
Your goal is to validate the preliminary findings of your sub-systems and make the FINAL GO/NO-GO decision for asset: {symbol}.
Current Market Price: {current_price}

========== 🧠 PART 1: SUB-SYSTEM REPORTS (PRELIMINARY OPINIONS) ==========
Your subordinate analytical layers have flagged this asset with the following scores:
* Layer 1 Technical Score: {l1_total_score:.4f} / 1.0
  - Titan ML Trend Model: {titan_raw_score:.4f} ({titan_trend_label})
  - Chart Pattern Recognition: {pattern_name} (Confidence: {pattern_confidence:.2f})
  - Monte Carlo Win Probability (1H): {mc_prob:.4f}
* Layer 2 Enhanced Score: {l2_total_score:.4f} / 1.0 (Adjusted for initial whale/news sentiment)

========== 🔍 PART 2: RAW EVIDENCE FOR VERIFICATION (THE TRUTH) ==========
Do NOT trust the scores above blindly. Verify them against this raw data yourself:

[A] RAW PRICE ACTION SNAPSHOT (Latest Closed Candles OHLCV):
{price_snapshot_block}
-> VERIFICATION TASK: Does this raw price action actually confirm the 'Titan Trend' reported above? Look for contradictions.

[B] RAW WHALE ON-CHAIN ACTIVITY (Flows & Accumulation):
{whale_evidence_block}
-> VERIFICATION TASK: Is there hidden distribution (selling) occurring despite the technical uptrend? High inflows to exchanges are a red flag.

[C] RAW NEWSWIRE FEED (Latest Headlines & Summaries):
\"\"\"
{raw_news_text}
\"\"\"
-> VERIFICATION TASK: Read the text above. Are there any immediate red flags, FUD (Fear, Uncertainty, Doubt), or regulatory risks that the sentiment score might have missed?

========== 📖 PART 3: INSTITUTIONAL MEMORY (LEARNING PLAYBOOK) ==========
The following rules have been learned from previous trading outcomes:
{learning_context}

========== 🛑 FINAL DECISION TASK ==========
Perform a deep, step-by-step internal analysis (triggered by your 'detailed thinking' mode).
Compare PART 1 (Opinions) vs PART 2 (Facts).
If FACTS strongly contradict OPINIONS, you MUST reject the trade regardless of the high scores.

REQUIRED OUTPUT (Strict JSON format ONLY, no other text):
{{
  "action": "WATCH" or "IGNORE",
  "confidence_level": 0.00 to 1.00 (Two decimal places),
  "reasoning": "A rigorous, professional justification citing specific raw evidence points that swayed your decision.",
  "strategy_directive": "MOMENTUM_BREAKOUT" or "DIP_ACCUMULATION" or "SCALP_REVERSAL",
  "key_risk_factor": "Identify the single biggest risk factor based on the raw evidence provided."
}}
"""

    def _create_heavyweight_reanalysis_prompt(self, trade: Dict, current: Dict, learning_context: str) -> str:
        """
        إنشاء برومبت مفصل لإعادة تقييم صفقة مفتوحة، مقارنة ظروف الدخول بالوضع الحالي.
        """
        symbol = trade.get('symbol', 'UNKNOWN')
        entry_price = trade.get('entry_price', 0.0)
        current_price = current.get('current_price', 0.0)
        
        # حساب مدة الصفقة بالدقائق
        try:
            entry_time = datetime.fromisoformat(trade.get('entry_time').replace('Z', '+00:00'))
            duration_minutes = (datetime.now(entry_time.tzinfo) - entry_time).total_seconds() / 60
        except:
            duration_minutes = 0.0
            
        pnl_percentage = ((current_price - entry_price) / entry_price) * 100
        
        # البيانات الحالية المقارنة
        titan_score_now = current.get('titan_score', 0.0)
        
        whale_now = current.get('whale_data', {})
        whale_1h_net = whale_now.get('exchange_flows', {}).get('net_flow_usd', 0.0)
        whale_24h_net = whale_now.get('accumulation_analysis_24h', {}).get('net_flow_usd', 0.0)
        
        news_text_now = current.get('news_text', 'No new significant news.')

        return f"""
ROLE: Omniscient Brain (Trade Guardian Mode).
EVENT: Mandatory periodic strategic re-evaluation of an OPEN POSITION.
ASSET: {symbol}
TIME IN TRADE: {duration_minutes:.1f} minutes

========== 📉 CURRENT POSITION STATUS ==========
* Entry Price: {entry_price}
* Current Market Price: {current_price}
* Unrealized PnL: {pnl_percentage:+.2f}%
* Original Entry Reason: "{trade.get('entry_reason', 'N/A')}"
* Current Targets -> TP: {trade.get('tp_price', 'N/A')} | SL: {trade.get('sl_price', 'N/A')}

========== 🆕 CHANGED MARKET CONDITIONS (RAW DATA UPDATE) ==========
1. ML Trend Model Update (Titan): Currently at {titan_score_now:.4f}
   (Is the trend weakening compared to entry?)

2. Fresh Whale Activity (Last 1H Window): Net Flow ${whale_1h_net:,.0f}
   (Positive value = potential selling pressure flowing to exchanges. Negative = accumulation.)

3. 24H Cumulative Whale Flow: Net ${whale_24h_net:,.0f}

4. Latest News Update Raw Text:
   \"\"\"{news_text_now[:1500]}\"\"\"

========== 📖 PLAYBOOK & STRATEGY GUIDELINES ==========
{learning_context}

========== 🛡️ GUARDIAN DECISION TASK ==========
Analyze if the original investment thesis is still valid based on the NEW raw data above.
Do NOT recommend closing just because of small fluctuations. Look for FUNDAMENTAL thesis INVALIDATION.

REQUIRED OUTPUT (Strict JSON format ONLY):
{{
  "action": "HOLD" or "EMERGENCY_EXIT" or "UPDATE_TARGETS",
  "new_tp": null or a specific new float value (if action is UPDATE_TARGETS),
  "new_sl": null or a specific new float value (if action is UPDATE_TARGETS),
  "reasoning": "Concise professional assessment of current risk vs original thesis based on new data."
}}
"""
print("✅ LLM Service V13.4 (Heavyweight Omniscient Brain) Loaded - NO SHORTCUTS")