File size: 35,987 Bytes
df0e3a4
5309752
df0e3a4
75763fd
490e45e
361dbe0
bdb23a4
361dbe0
5309752
 
91219ff
bd24c07
5f1c540
df0e3a4
584b853
5f1c540
df0e3a4
361dbe0
75763fd
361dbe0
 
75763fd
584b853
 
3f02cf6
584b853
5f1c540
df0e3a4
5309752
5f1c540
361dbe0
 
 
5309752
 
 
 
361dbe0
5f1c540
6537653
3f02cf6
584b853
5309752
361dbe0
 
5d94479
 
 
361dbe0
b271aec
5309752
b271aec
 
 
 
584b853
361dbe0
5309752
84ea694
361dbe0
5309752
df0e3a4
 
361dbe0
c96093c
5309752
c96093c
 
 
 
 
4e90a93
5309752
b271aec
5309752
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b271aec
5309752
 
 
b271aec
 
 
 
 
 
5309752
b271aec
 
 
 
 
 
 
 
 
 
 
 
361dbe0
584b853
361dbe0
 
e3acf42
 
584b853
df0e3a4
 
3f42b72
75763fd
e3acf42
361dbe0
6537653
5309752
6537653
3f02cf6
584b853
 
 
 
6537653
 
 
584b853
6537653
 
 
 
 
584b853
 
 
6537653
e3acf42
6537653
 
 
5309752
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361dbe0
5309752
3f02cf6
361dbe0
8ae34e9
 
361dbe0
 
3f02cf6
85a5be5
361dbe0
d0d5e70
2dbc10d
 
 
e3acf42
 
 
2dbc10d
5309752
706140f
 
82033bd
8ae34e9
1b9d614
 
 
 
e3acf42
8ae34e9
 
82033bd
8ae34e9
584b853
 
 
 
361dbe0
 
 
8ae34e9
361dbe0
 
3f02cf6
3b6bd53
399389d
361dbe0
df0e3a4
 
361dbe0
 
5309752
361dbe0
5f1c540
 
 
 
 
 
 
 
 
b271aec
5309752
 
b271aec
5f1c540
 
5309752
 
5f1c540
 
 
 
 
 
 
5309752
3f02cf6
5f1c540
df0e3a4
 
 
 
 
 
584b853
361dbe0
85a5be5
3f02cf6
df0e3a4
82d46a6
5309752
 
 
584b853
5f1c540
584b853
706140f
5f1c540
706140f
5309752
 
706140f
 
 
584b853
 
361dbe0
584b853
 
 
 
 
 
 
 
 
 
5f1c540
584b853
 
5f1c540
b271aec
5309752
 
361dbe0
9fac145
361dbe0
75763fd
584b853
 
df0e3a4
 
584b853
df0e3a4
 
 
 
e3acf42
361dbe0
584b853
 
75763fd
361dbe0
 
5309752
 
c96093c
b271aec
bd24c07
361dbe0
75763fd
361dbe0
 
 
584b853
fc57cc5
82033bd
bd24c07
6537653
3f02cf6
e3acf42
 
 
3f02cf6
0b6828b
82033bd
584b853
 
3f02cf6
 
e3acf42
584b853
6537653
3f02cf6
 
82033bd
6537653
3f02cf6
 
e3acf42
82033bd
584b853
82033bd
58c6d25
 
 
248cb64
 
3f02cf6
 
e3acf42
3f02cf6
 
8e2600a
3f02cf6
5d94479
 
 
 
 
 
248cb64
fc57cc5
3f02cf6
8e2600a
5d94479
 
66489a8
248cb64
82033bd
fc57cc5
 
 
3f02cf6
82033bd
 
fc57cc5
82033bd
fc57cc5
85a5be5
3f02cf6
 
6537653
82033bd
 
 
66489a8
3f02cf6
 
85a5be5
82033bd
361dbe0
e3acf42
bd24c07
3f02cf6
 
 
584b853
3f02cf6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361dbe0
85a5be5
361dbe0
82033bd
 
 
 
e3acf42
3f02cf6
706140f
 
 
 
 
 
 
 
 
5309752
 
706140f
 
 
3f02cf6
e3acf42
3f02cf6
 
706140f
e3acf42
 
3f02cf6
e3acf42
5309752
 
 
82033bd
5309752
82033bd
 
 
 
 
3f02cf6
 
82033bd
 
584b853
 
82033bd
 
 
584b853
82033bd
 
 
584b853
82033bd
3f02cf6
e3acf42
 
c96093c
 
 
 
3f02cf6
c4b5eae
82033bd
5309752
75763fd
361dbe0
 
3f02cf6
 
82d46a6
8e2600a
 
3f02cf6
df0e3a4
8e2600a
 
 
 
66489a8
8e2600a
82d46a6
8e2600a
 
584b853
 
8e2600a
df0e3a4
5309752
82033bd
 
8e2600a
82033bd
8e2600a
82033bd
 
 
8e2600a
82033bd
 
b271aec
 
 
 
 
 
 
 
 
 
 
 
82033bd
 
 
85a5be5
b271aec
584b853
66489a8
5309752
66489a8
 
 
 
 
 
 
 
 
 
 
 
 
 
b271aec
5309752
66489a8
 
 
 
 
584b853
5f1c540
584b853
 
 
 
5309752
 
66489a8
 
e3acf42
3f02cf6
361dbe0
 
3f02cf6
e028f8e
 
 
 
 
 
 
 
 
 
 
 
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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
# ============================================================
# 🛡️ trade_manager.py (V64.0 - GEM-Architect: Ultimate Integrity)
# ============================================================

import asyncio
import uuid
import time
import traceback
import json
from datetime import datetime, timedelta
from typing import List, Dict, Any

# استيراد المكونات الأساسية
from smart_portfolio import SmartPortfolio
from ml_engine.processor import SystemLimits 
from governance_engine import GovernanceEngine

class TradeManager:
    def __init__(self, r2_service, data_manager, processor): 
        self.r2 = r2_service
        self.data_manager = data_manager
        self.processor = processor 
        
        # ✅ سيتم حقنه من الخارج لربط حلقة التعلم
        self.learning_hub = None 
        
        # تهيئة المحفظة والحوكمة
        self.smart_portfolio = SmartPortfolio(r2_service, data_manager)
        self.governance = GovernanceEngine()
        
        self.open_positions = {}
        self.watchlist = {}
        self.sentry_tasks = {}
        
        # ✅ قائمة تذاكر التحقق المعلقة (Verification Tickets)
        self.pending_verifications = []
        
        self.running = True
        self.latest_guardian_log = "🛡️ Guardian & Governance Systems Online."
        self.FEE_RATE = 0.001 
        self.ORACLE_CHECK_INTERVAL = 900
        
        # إحصائيات الذكاء الاصطناعي (الحراس)
        self.ai_stats = {
            "hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
            "crash":  {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
            "giveback": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
            "stagnation": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0}
        }

        # 📊 إحصائيات أنواع العملات (Type Stats)
        self.type_stats = {
            "SAFE_BOTTOM": {"wins": 0, "losses": 0, "profit_usd": 0.0, "loss_usd": 0.0},
            "MOMENTUM_LAUNCH": {"wins": 0, "losses": 0, "profit_usd": 0.0, "loss_usd": 0.0}
        }
        
        self.execution_lock = asyncio.Lock()
        print(f"🛡️ [TradeManager V64.0] Full Systems Online (Entry Audit + Exit Audit).")

    async def initialize_sentry_exchanges(self):
        """تهيئة المحفظة واستعادة كافة البيانات"""
        print("🛡️ [TradeManager] Syncing state & Initializing Portfolio...")
        await self.smart_portfolio.initialize()
        await self.sync_internal_state_with_r2()
        
        # تحميل إحصائيات الحراس
        try:
            saved_stats = await self.r2.get_guardian_stats_async()
            if saved_stats: self.ai_stats = saved_stats
            print("   -> [Stats] Guardian metrics loaded from R2.")
        except Exception: pass

        # تحميل إحصائيات الأنواع
        await self._load_type_stats_from_r2()
        
        # ✅ تحميل تذاكر التحقق المعلقة وإطلاق المحرك الخلفي لها
        await self._load_pending_verifications()
        asyncio.create_task(self._verification_engine_loop())

    # ============================================================
    # 🕵️ Persistent Verification Engine (The Truth Machine - Entry)
    # ============================================================
    async def _load_pending_verifications(self):
        """تحميل التذاكر التي لم تكتمل مدتها من R2"""
        try:
            data = await self.r2.get_file_json_async("diagnostics/pending_verifications.json")
            if data:
                self.pending_verifications = data
                print(f"   🕵️ [Verification] Loaded {len(self.pending_verifications)} pending audits.")
        except Exception:
            self.pending_verifications = []

    async def _save_pending_verifications(self):
        """حفظ التذاكر الحالية لضمان عدم ضياعها عند إعادة التشغيل"""
        try:
            await self.r2.upload_json_async(self.pending_verifications, "diagnostics/pending_verifications.json")
        except Exception as e:
            print(f"❌ Error saving verifications: {e}")

    async def _register_verification_ticket(self, symbol, entry_price, size_usd, votes):
        """إنشاء تذكرة جديدة عند دخول الصفقة"""
        ticket = {
            "id": str(uuid.uuid4()),
            "symbol": symbol,
            "entry_price": float(entry_price),
            "size_usd": float(size_usd),
            "entry_time": datetime.now().isoformat(),
            "votes": votes, # من صوت بنعم؟
            "target_time": (datetime.now() + timedelta(hours=1)).isoformat() # متى يحين موعد الحكم؟
        }
        self.pending_verifications.append(ticket)
        await self._save_pending_verifications()
        print(f"   🎫 [Verification] Ticket created for {symbol}. Result in 1h.")

    async def _verification_engine_loop(self):
        """المحرك الخلفي: يعمل بشكل مستقل لفحص التذاكر المستحقة (Entry Models Audit)"""
        print("   ⚙️ [Verification Engine] Started background audit loop...")
        while self.running:
            try:
                await asyncio.sleep(60) # فحص كل دقيقة
                if not self.pending_verifications: continue
                
                now = datetime.now()
                updated = False
                remaining_tickets = []
                
                # نسخ القائمة للتعديل عليها بأمان
                tickets_to_process = list(self.pending_verifications)
                
                for ticket in tickets_to_process:
                    target_time = datetime.fromisoformat(ticket['target_time'])
                    
                    if now >= target_time:
                        # 🔔 حان وقت الحساب!
                        symbol = ticket['symbol']
                        entry_p = ticket['entry_price']
                        size = ticket['size_usd']
                        votes = ticket['votes']
                        
                        # جلب السعر الحالي (بغض النظر عن حالة الصفقة: مغلقة أو مفتوحة)
                        curr_p = await self.data_manager.get_latest_price_async(symbol)
                        
                        if curr_p > 0:
                            pnl_pct = (curr_p - entry_p) / entry_p
                            pnl_usd = pnl_pct * size
                            
                            is_win = pnl_pct > 0
                            result_str = "WIN" if is_win else "LOSS"
                            
                            print(f"   🕵️ [Audit Complete] {symbol}: {result_str} after 1h ({pnl_pct:+.2f}%)")
                            
                            # تحديث المصفوفة لكل نموذج صوت بنعم
                            model_updates = {}
                            models_to_track = ["Titan", "Patterns", "Oracle", "Sniper", "MonteCarlo_L", "MonteCarlo_A", "Governance"]
                            
                            for model in models_to_track:
                                if votes.get(model, False): # هل صوت النموذج بنعم؟
                                    model_updates[model] = {
                                        "wins": 1 if is_win else 0,
                                        "losses": 1 if not is_win else 0,
                                        "pnl": pnl_usd
                                    }
                            
                            if model_updates:
                                await self.r2.update_diagnostic_stats_async(model_updates)
                            
                            updated = True # تم معالجة تذكرة
                        else:
                            # السعر غير متوفر، نؤجلها للدورة القادمة
                            remaining_tickets.append(ticket)
                    else:
                        # لم يحن الوقت بعد
                        remaining_tickets.append(ticket)
                
                if updated:
                    self.pending_verifications = remaining_tickets
                    await self._save_pending_verifications()
                    
            except Exception as e:
                print(f"❌ [Verification Loop Error] {e}")
                await asyncio.sleep(60)

    # ============================================================
    # 📊 Type Stats & R2 Management
    # ============================================================
    async def _load_type_stats_from_r2(self):
        """تحميل إحصائيات أنواع العملات من R2"""
        try:
            saved_stats = await self.r2.get_file_json_async("stats/coin_type_performance_v1.json")
            if saved_stats:
                self.type_stats = saved_stats
                print("   📊 [Stats] Coin Type performance loaded.")
            else:
                print("   ℹ️ [Stats] No existing type stats found. Starting fresh.")
        except Exception:
            print("   ⚠️ [Stats] Error loading type stats.")

    async def _save_type_stats_to_r2(self):
        """حفظ إحصائيات الأنواع إلى R2"""
        try:
            await self.r2.upload_json_async(self.type_stats, "stats/coin_type_performance_v1.json")
        except Exception as e:
            print(f"❌ Failed to save type stats: {e}")

    async def sync_internal_state_with_r2(self):
        """استرجاع الصفقات المفتوحة من R2"""
        try:
            open_trades_list = await self.r2.get_open_trades_async()
            self.open_positions = {trade['symbol']: trade for trade in open_trades_list}
            print(f"   -> [Sync] Recovered {len(self.open_positions)} active trades.")
            
            total_allocated = sum(float(t.get('entry_capital', 0.0)) for t in self.open_positions.values())
            self.smart_portfolio.state["allocated_capital_usd"] = total_allocated
        except Exception as e:
            print(f"❌ [TradeManager] R2 Sync Failed: {e}")
            self.open_positions = {} 

    async def ensure_active_guardians(self):
        """التأكد من أن كل صفقة مفتوحة لها حارس"""
        active_symbols = list(self.open_positions.keys())
        if not active_symbols: return "💤 No active trades."
        
        restored_count = 0
        status_msgs = []
        
        for symbol in active_symbols:
            task = self.sentry_tasks.get(symbol)
            is_alive = task and not task.done()
            
            if not is_alive:
                print(f"🚨 [Watchdog] Found DEAD guardian for {symbol}. Resurrecting...")
                self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
                restored_count += 1
                status_msgs.append(f"♻️ Resurrected {symbol}")
            else: 
                status_msgs.append(f"✅ {symbol} Running")
                
        if restored_count > 0:
            self.latest_guardian_log = f"⚠️ Watchdog restored: {', '.join(status_msgs)}"
            return f"⚠️ Watchdog restored {restored_count} guardians."
        return "✅ All guardians active."

    # ============================================================
    # 🧠 Logic Layers
    # ============================================================
    def _snapshot_model_votes(self, signal_data: Dict[str, Any]) -> Dict[str, bool]:
        """
        تحدد أي النماذج صوّتت بـ 'شراء' وقت الدخول.
        """
        votes = {}
        limits = signal_data.get('dynamic_limits', {})
        comps = signal_data.get('components', {}) or {} 

        votes['Titan'] = comps.get('titan_score', signal_data.get('titan_score', 0)) > 0.5
        votes['Patterns'] = comps.get('patterns_score', signal_data.get('patterns_score', 0)) > 0.5
        
        oracle_thresh = limits.get('l3_oracle_thresh', SystemLimits.L3_CONFIDENCE_THRESHOLD)
        votes['Oracle'] = signal_data.get('confidence', 0) >= oracle_thresh

        sniper_thresh = limits.get('l4_sniper_thresh', SystemLimits.L4_ENTRY_THRESHOLD)
        votes['Sniper'] = signal_data.get('sniper_score', 0) >= sniper_thresh

        votes['MonteCarlo_L'] = comps.get('mc_score', 0.5) > 0.5
        votes['MonteCarlo_A'] = signal_data.get('mc_advanced_score', 0) > 0
        
        votes['Governance'] = signal_data.get('governance_grade', 'REJECT') != 'REJECT'

        return votes

    async def select_and_execute_best_signal(self, oracle_approved_signals: List[Dict[str, Any]]):
        """اختيار أفضل إشارة وتنفيذها"""
        if not self.processor.initialized: await self.processor.initialize()
        sniper_candidates = []
        print(f"\n🔎 [Sniper] Scanning {len(oracle_approved_signals)} candidates...")

        for signal in oracle_approved_signals:
            symbol = signal['symbol']
            if signal.get('action_type') != 'BUY': continue
            if symbol in self.open_positions: continue

            ohlcv_task = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000) 
            ob_task = self.data_manager.get_order_book_snapshot(symbol)
            ohlcv_1m, order_book = await asyncio.gather(ohlcv_task, ob_task)
            
            if not ohlcv_1m or len(ohlcv_1m) < 100:
                print(f"   -> ⚠️ [Skip] {symbol}: Insufficient 1m data.")
                continue
            
            # ✅ تمرير سياق الإشارة بالكامل
            sniper_result = await self.processor.check_sniper_entry(ohlcv_1m, order_book, context_data=signal)
            
            sniper_signal = sniper_result.get('signal', 'WAIT')
            final_conf = sniper_result.get('confidence_prob', 0.0)
            reason_str = sniper_result.get('reason', 'N/A')  # ✅ استخراج السبب

            # ✅ إضافة السبب للطباعة
            log_msg = (f"   -> 🔭 {symbol:<6} | Decision: {sniper_signal} | Score: {final_conf:.2f} | Reason: {reason_str}")
            print(log_msg)

            if sniper_signal == 'BUY':
                signal['sniper_entry_price'] = sniper_result.get('entry_price', 0)
                signal['sniper_score'] = final_conf
                
                if 'components' not in signal: signal['components'] = {}
                signal['components']['sniper_score'] = final_conf
                
                sniper_candidates.append(signal)

        if not sniper_candidates:
            print("   -> 📉 No candidates passed the Sniper L4 check.")
            return

        sniper_candidates.sort(key=lambda x: (x.get('confidence', 0) + x.get('sniper_score', 0)), reverse=True)
        best_signal = sniper_candidates[0]
        
        async with self.execution_lock:
            print(f"🚀 [EXECUTING] Attempting entry for best candidate: {best_signal['symbol']}")
            await self._execute_entry_from_signal(best_signal['symbol'], best_signal)

    async def _execute_entry_from_signal(self, symbol, signal_data):
        """تنفيذ الدخول الفعلي"""
        try:
            print(f"   🏛️ [Governance] Convening Senate for {symbol}...")
            
            t15_task = self.data_manager.get_latest_ohlcv(symbol, '15m', 200)
            t1h_task = self.data_manager.get_latest_ohlcv(symbol, '1h', 200)
            ob_task = self.data_manager.get_order_book_snapshot(symbol)
            
            t15, t1h, ob = await asyncio.gather(t15_task, t1h_task, ob_task)
            ohlcv_dict = {'15m': t15, '1h': t1h}
            
            strategy_type = signal_data.get('strategy_type', 'NORMAL')
            
            # تقييم الحوكمة
            gov_decision = await self.governance.evaluate_trade(symbol, ohlcv_dict, ob, strategy_type=strategy_type)
            
            if gov_decision['grade'] == 'REJECT':
                print(f"⛔ [Governance VETO] {symbol} Rejected. Grade: REJECT")
                return 

            print(f"   ✅ [Governance PASS] Grade: {gov_decision['grade']} | Score: {gov_decision['governance_score']:.1f}")
            
            signal_data['governance_grade'] = gov_decision['grade']
            signal_data['governance_score'] = gov_decision['governance_score']
            signal_data['governance_details'] = gov_decision['components']

            # طلب الموافقة المالية
            is_approved, plan = await self.smart_portfolio.request_entry_approval(signal_data, len(self.open_positions))
            
            if not is_approved:
                print(f"⛔ [Portfolio Rejection] {symbol}: {plan.get('reason')}")
                return

            approved_size_usd = plan['approved_size_usd']
            approved_tp = plan['approved_tp']
            
            trade_id = str(uuid.uuid4())
            current_price = float(signal_data.get('sniper_entry_price', 0.0))
            if current_price <= 0.0: current_price = await self.data_manager.get_latest_price_async(symbol)
            entry_fee_usd = approved_size_usd * self.FEE_RATE

            # ✅ لقطة التصويت (من وافق على هذه الصفقة؟)
            model_votes = self._snapshot_model_votes(signal_data)

            decision_snapshot = {
                'components': signal_data.get('components', {}),
                'oracle_conf': signal_data.get('confidence', 0),
                'governance_grade': gov_decision['grade'],
                'governance_score': gov_decision['governance_score'],
                'governance_details': gov_decision['components'],
                'system_confidence': plan.get('system_confidence', 0.5),
                'market_mood': plan.get('market_mood', 'N/A'),
                'regime_at_entry': getattr(SystemLimits, 'CURRENT_REGIME', 'UNKNOWN'),
                'dynamic_limits': signal_data.get('dynamic_limits', {}),
                'asset_regime': signal_data.get('asset_regime', 'UNKNOWN')
            }

            new_trade = {
                'id': trade_id, 
                'symbol': symbol, 
                'entry_price': current_price, 
                'direction': 'LONG',
                'entry_time': datetime.now().isoformat(), 
                'status': 'OPEN',
                'tp_price': approved_tp, 
                'sl_price': float(signal_data.get('sl_price', current_price * 0.95)),
                'last_update': datetime.now().isoformat(), 
                'last_oracle_check': datetime.now().isoformat(),
                'strategy': 'OracleV4_Governance_Hydra',
                'entry_capital': approved_size_usd, 
                'entry_fee_usd': entry_fee_usd,
                'decision_data': decision_snapshot, 
                'highest_price': current_price,
                'strategy_type': strategy_type,
                'model_votes': model_votes 
            }
            
            self.open_positions[symbol] = new_trade
            if self.watchlist: self.watchlist.clear()
            
            # حجز الأموال
            await self.smart_portfolio.register_new_position(approved_size_usd)
            
            # تحديث الحالة في R2
            portfolio_state = await self.r2.get_portfolio_state_async()
            if portfolio_state.get('first_trade_timestamp') is None:
                portfolio_state['first_trade_timestamp'] = new_trade['entry_time']
                await self.r2.save_portfolio_state_async(portfolio_state)
            
            await self.r2.save_open_trades_async(list(self.open_positions.values()))
            
            # تشغيل الحارس
            if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel()
            self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
            
            # ✅ إصدار تذكرة تحقق مستقلة
            await self._register_verification_ticket(symbol, current_price, approved_size_usd, model_votes)
            
            print(f"✅ [ENTRY] {symbol} @ {current_price} | Type: {strategy_type} | Grade: {gov_decision['grade']} | Size: ${approved_size_usd:.2f}")

        except Exception as e:
            print(f"❌ [Entry Error] {symbol}: {e}")
            traceback.print_exc()

    async def _guardian_loop(self, symbol: str):
        """حلقة الحراسة المستمرة (Guardian Loop)"""
        print(f"🛡️ [Dual-Core] STARTING WATCH for {symbol}...")
        last_ai_check_time = 0
        
        while self.running:
            if symbol not in self.open_positions: break
            try:
                await asyncio.sleep(1) 
                trade = self.open_positions.get(symbol)
                if not trade: break
                
                current_ticker_price = await self.data_manager.get_latest_price_async(symbol)
                
                # تحديث أعلى سعر وصل له السعر (للتبع)
                if 'highest_price' not in trade: trade['highest_price'] = float(trade['entry_price'])
                if current_ticker_price > float(trade['highest_price']): trade['highest_price'] = current_ticker_price

                # 1. فحص الهدف ووقف الخسارة الصلب
                if current_ticker_price >= trade['tp_price']:
                    print(f"🎯 [TP HIT] {symbol} @ {current_ticker_price}")
                    async with self.execution_lock: await self._execute_exit(symbol, trade['tp_price'], "TP_HIT")
                    break 
                if current_ticker_price <= trade['sl_price']:
                    print(f"🛑 [SL HIT] {symbol} @ {current_ticker_price}")
                    async with self.execution_lock: await self._execute_exit(symbol, trade['sl_price'], "SL_HIT")
                    break 

                # 2. فحص الذكاء الاصطناعي (كل دقيقة)
                if time.time() - last_ai_check_time > 60:
                    t1 = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000) 
                    t5 = self.data_manager.get_latest_ohlcv(symbol, '5m', 300)
                    t15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 200)
                    tob = self.data_manager.get_order_book_snapshot(symbol)
                    
                    try: d1, d5, d15, d_ob = await asyncio.gather(t1, t5, t15, tob)
                    except: continue 
                    
                    if d1 and d5 and d15 and len(d5) >= 6:
                        last_6_5m = d5[-6:]
                        vol_30m_sum = sum([float(c[5]) * float(c[4]) for c in last_6_5m]) 
                        
                        context_data = {
                            'entry_price': trade['entry_price'],
                            'tp_price': trade['tp_price'],
                            'sl_price': trade['sl_price'],
                            'entry_time': trade['entry_time'],
                            'oracle_conf': trade.get('decision_data', {}).get('oracle_conf', 0.8),
                            'system_conf': trade.get('decision_data', {}).get('system_confidence', 0.8),
                            'highest_price': float(trade['highest_price']),
                            'time_in_trade_mins': (datetime.now() - datetime.fromisoformat(trade['entry_time'])).total_seconds() / 60,
                            'volume_30m_usd': vol_30m_sum
                        }

                        # ✅ استدعاء الحراس
                        decision = self.processor.consult_dual_guardians(symbol, d1, d5, d15, context_data, order_book_snapshot=d_ob)
                        action = decision.get('action', 'HOLD')
                        reason = decision.get('reason', '')
                        ai_metrics = decision.get('probs') or decision.get('scores') or {}

                        self.latest_guardian_log = f"🛡️ {action} | {reason}"
                        
                        if action in ['EXIT_HARD', 'EXIT_SOFT']:
                            print(f"🐲 [Dual-Core Trigger] {action}: {reason}")
                            async with self.execution_lock:
                                await self._execute_exit(symbol, current_ticker_price, f"DualGuard_{action}", ai_scores=ai_metrics)
                            break
                        elif action in ['TIGHTEN_SL', 'TRAIL_SL']:
                             await self._handle_sl_update(symbol, action, trade, current_ticker_price)

                    last_ai_check_time = time.time()
                    self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
                
                # 3. إعادة فحص Oracle
                last_oracle = datetime.fromisoformat(trade.get('last_oracle_check', datetime.now().isoformat()))
                if (datetime.now() - last_oracle).total_seconds() > self.ORACLE_CHECK_INTERVAL:
                    self.open_positions[symbol]['last_oracle_check'] = datetime.now().isoformat()
                    await self._consult_oracle_strategy_update(symbol, trade)

            except asyncio.CancelledError: break
            except Exception as e:
                print(f"❌ [Sentry Error] {symbol}: {e}"); traceback.print_exc(); await asyncio.sleep(5)

    async def _handle_sl_update(self, symbol, action, trade, current_price):
        """تحديث وقف الخسارة ديناميكياً"""
        if action == 'TIGHTEN_SL':
            entry_p = float(trade['entry_price'])
            if float(trade['sl_price']) < entry_p:
                print(f"🛡️ [Dual-Core] TIGHTEN_SL -> Entry {entry_p}")
                self.open_positions[symbol]['sl_price'] = entry_p
                await self.r2.save_open_trades_async(list(self.open_positions.values()))

        elif action == 'TRAIL_SL':
            entry_p = float(trade['entry_price'])
            if current_price > entry_p:
                potential_sl = entry_p + ((current_price - entry_p) * 0.5)
                if potential_sl > float(trade['sl_price']):
                    print(f"🛡️ [Dual-Core] TRAIL_SL -> {potential_sl:.4f}")
                    self.open_positions[symbol]['sl_price'] = potential_sl
                    await self.r2.save_open_trades_async(list(self.open_positions.values()))

    async def _consult_oracle_strategy_update(self, symbol, trade):
        try:
            tasks = [self.data_manager.get_latest_ohlcv(symbol, tf, limit=100) for tf in ["15m", "1h", "4h"]]
            results = await asyncio.gather(*tasks)
            ohlcv_data = {tf: res for tf, res in zip(["15m", "1h", "4h"], results) if res}
            if '1h' not in ohlcv_data: return
            
            curr_p = await self.data_manager.get_latest_price_async(symbol)
            
            decision_data = trade.get('decision_data', {})
            saved_limits = decision_data.get('dynamic_limits', {})
            saved_regime = decision_data.get('asset_regime', 'UNKNOWN')

            raw_input = {
                'symbol': symbol, 
                'ohlcv': ohlcv_data, 
                'current_price': curr_p,
                'dynamic_limits': saved_limits,
                'asset_regime': saved_regime
            }

            l2 = await self.processor.process_compound_signal(raw_input)
            if not l2: return
            
            oracle = await self.processor.consult_oracle(l2)
            if oracle.get('action') == 'WAIT' or oracle.get('direction') == 'SHORT':
                print(f"🚨 [Oracle] Outlook Bearish (Re-Check). Exiting {symbol}...")
                await self.force_exit_by_manager(symbol, reason="Oracle_Bearish_Flip")
                return
        except Exception: pass

    # ============================================================
    # 🕵️ Post-Exit Analysis (The Guardian Auditor)
    # ============================================================
    def _launch_post_exit_analysis(self, symbol, exit_price, exit_time, position_size_usd, ai_scores=None, trade_obj=None):
        """إطلاق مهمة التدقيق الخلفي (Audit) لقرار الخروج"""
        asyncio.create_task(self._analyze_after_exit_task(symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj))

    def _update_specific_stat(self, key, is_good, usd_impact):
        if key not in self.ai_stats: return
        self.ai_stats[key]["total"] += 1
        if is_good: self.ai_stats[key]["good"] += 1; self.ai_stats[key]["saved"] += abs(usd_impact)
        else: self.ai_stats[key]["missed"] += abs(usd_impact)

    async def _analyze_after_exit_task(self, symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj):
        """مهمة التدقيق: هل كان الخروج صحيحاً؟"""
        await asyncio.sleep(900) # انتظار 15 دقيقة
        try:
            curr = await self.data_manager.get_latest_price_async(symbol)
            if curr == 0: return
            
            change_pct = (curr - exit_price) / exit_price
            usd_impact = change_pct * position_size_usd
            is_good_exit = change_pct < 0 
            
            self._update_specific_stat("hybrid", is_good_exit, usd_impact)
            
            record = {"symbol": symbol, "exit_price": exit_price, "price_15m": curr, "usd_impact": usd_impact, "verdict": "SUCCESS" if is_good_exit else "MISS"}
            await self.r2.append_deep_steward_audit(record)
            
            # 🔥 حفظ إحصائيات الحراس في R2 بعد التحديث
            await self.r2.save_guardian_stats_async(self.ai_stats)
            
        except Exception: pass

    async def _execute_exit(self, symbol, price, reason, ai_scores=None):
        """تنفيذ الخروج وتحديث الإحصائيات"""
        if symbol not in self.open_positions: return
        try:
            trade = self.open_positions.pop(symbol) 
            entry_price = float(trade['entry_price']); exit_price = float(price)
            entry_capital = float(trade.get('entry_capital', 100.0)); entry_fee = float(trade.get('entry_fee_usd', 0.0))
            
            exit_val_gross = (exit_price / entry_price) * entry_capital
            exit_fee = exit_val_gross * self.FEE_RATE
            total_fees = entry_fee + exit_fee
            
            gross_pnl_usd = exit_val_gross - entry_capital
            true_net_pnl_usd = gross_pnl_usd - total_fees
            true_net_pct = (true_net_pnl_usd / entry_capital) * 100
            
            # تحديث المحفظة
            await self.smart_portfolio.register_closed_position(entry_capital, gross_pnl_usd, total_fees)
            
            trade.update({
                'status': 'CLOSED', 'exit_price': exit_price, 'exit_reason': reason, 
                'profit_pct': true_net_pct, 'net_pnl_usd': true_net_pnl_usd, 'fees_paid_usd': total_fees,
                'exit_time': datetime.now().isoformat()
            })
            
            # 1. تحديث الإحصائيات العامة
            portfolio = await self.r2.get_portfolio_state_async()
            portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
            if true_net_pnl_usd >= 0:
                portfolio['winning_trades'] = portfolio.get('winning_trades', 0) + 1
                portfolio['total_profit_usd'] = portfolio.get('total_profit_usd', 0) + true_net_pnl_usd
                trade['result'] = 'WIN'
            else:
                portfolio['losing_trades'] = portfolio.get('losing_trades', 0) + 1
                portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0) + abs(true_net_pnl_usd)
                trade['result'] = 'LOSS'

            # ✅ 2. تحديث إحصائيات الأنواع (Persistent Type Stats)
            strat_type = trade.get('strategy_type', 'UNKNOWN')
            if strat_type in self.type_stats:
                if true_net_pnl_usd >= 0:
                    self.type_stats[strat_type]['wins'] += 1
                    self.type_stats[strat_type]['profit_usd'] += true_net_pnl_usd
                else:
                    self.type_stats[strat_type]['losses'] += 1
                    self.type_stats[strat_type]['loss_usd'] += true_net_pnl_usd 
                
                asyncio.create_task(self._save_type_stats_to_r2())

            await self.r2.save_portfolio_state_async(portfolio)
            await self.r2.save_open_trades_async(list(self.open_positions.values()))
            await self.r2.append_to_closed_trades_history(trade)
            
            print(f"✅ [EXIT] {symbol} | Type: {strat_type} | PnL: {true_net_pct:.2f}% (${true_net_pnl_usd:.2f}) | {reason}")
            
            # ==========================================================
            # 🏛️ إرسال البيانات لملف تدريب الحوكمة
            # ==========================================================
            try:
                decision_data = trade.get('decision_data', {})
                if 'governance_grade' in decision_data:
                    training_record = {
                        "symbol": symbol,
                        "entry_time": trade['entry_time'],
                        "exit_time": trade['exit_time'],
                        "governance_grade": decision_data['governance_grade'],
                        "governance_score": decision_data.get('governance_score', 0),
                        "governance_components": decision_data.get('governance_details', {}), # تفاصيل الـ 156 مؤشر
                        "entry_price": trade['entry_price'],
                        "exit_price": trade['exit_price'],
                        "profit_pct": true_net_pct,
                        "result": trade['result'],
                        "strategy_type": strat_type
                    }
                    asyncio.create_task(self.r2.append_governance_training_data(training_record))
            except Exception as ge:
                print(f"⚠️ [Learning Error] Failed to save governance training data: {ge}")

            # ==========================================================
            # 🧠 THE TACTICAL LEARNING LINK
            # ==========================================================
            if self.learning_hub:
                asyncio.create_task(self.learning_hub.register_trade_outcome(trade))

            # ✅ 3. إطلاق تحليل ما بعد الخروج (لتقييم الحارس)
            # لاحظ: تقييم "الدخول" يتم عبر التذاكر المستقلة، بينما هذا لتقييم "الخروج".
            self._launch_post_exit_analysis(symbol, exit_price, trade.get('exit_time'), entry_capital, ai_scores, trade)
            
            self.latest_guardian_log = f"✅ Closed {symbol} ({reason})"
            if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel(); del self.sentry_tasks[symbol]

        except Exception as e:
            print(f"❌ [Exit Error] {e}"); traceback.print_exc()
            if symbol not in self.open_positions: self.open_positions[symbol] = trade 

    async def force_exit_by_manager(self, symbol, reason):
        p = await self.data_manager.get_latest_price_async(symbol)
        async with self.execution_lock: await self._execute_exit(symbol, p, reason)

    async def start_sentry_loops(self):
        await self.ensure_active_guardians()

    async def stop_sentry_loops(self):
        self.running = False
        for task in self.sentry_tasks.values(): task.cancel()