Riy777 commited on
Commit
6537653
·
verified ·
1 Parent(s): f0aa03f

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +141 -176
trade_manager.py CHANGED
@@ -1,4 +1,4 @@
1
- # trade_manager.py (V25.3 - GEM-Architect: Separation of Concerns Edition)
2
 
3
  import asyncio
4
  import uuid
@@ -19,11 +19,12 @@ class TradeManager:
19
  self.sentry_tasks = {}
20
  self.running = True
21
 
22
- # إعدادات الرسوم (Simulation Fees)
23
- self.FEE_RATE = 0.001
24
 
25
- # إعدادات المراجعة الاستراتيجية (Oracle Re-check)
26
- self.ORACLE_CHECK_INTERVAL = 900 # كل 15 دقيقة
 
27
 
28
  self.ai_stats = {
29
  "hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
@@ -32,7 +33,7 @@ class TradeManager:
32
  }
33
 
34
  self.execution_lock = asyncio.Lock()
35
- print(f"🛡️ [TradeManager V25.3] Full Spot Commander Initialized.")
36
 
37
  async def initialize_sentry_exchanges(self):
38
  print("🛡️ [TradeManager] Syncing state with R2...")
@@ -41,84 +42,95 @@ class TradeManager:
41
  async def sync_internal_state_with_r2(self):
42
  try:
43
  open_trades_list = await self.r2.get_open_trades_async()
44
- self.open_positions = {trade['symbol']: trade for trade in open_trades_list}
45
- print(f" -> [Sync] Recovered {len(self.open_positions)} active trades.")
 
 
 
 
 
 
 
 
 
 
46
  except Exception as e:
47
  print(f"❌ [TradeManager] R2 Sync Failed: {e}")
48
- self.open_positions = {}
49
 
50
  # ==============================================================================
51
- # 🎯 L4 Sniper Execution Logic (Authority Delegated to Engine)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  # ==============================================================================
53
  async def select_and_execute_best_signal(self, oracle_approved_signals: List[Dict[str, Any]]):
54
- if len(self.open_positions) > 0:
55
- print(f"⛔ [TradeManager] Max positions reached. Skipping scan.")
56
- return
57
 
58
  if not self.processor.initialized:
59
  await self.processor.initialize()
60
 
61
  sniper_candidates = []
62
-
63
  print(f"\n🔎 [Sniper] Scanning {len(oracle_approved_signals)} candidates...")
64
 
65
  for signal in oracle_approved_signals:
66
  symbol = signal['symbol']
67
-
68
- # Spot Filter: نقبل فقط إشارات الشراء
69
- if signal.get('action_type') != 'BUY':
70
- continue
71
-
72
  if symbol in self.open_positions: continue
73
 
74
- # 1. جلب بيانات الدقيقة للقناص
75
  ohlcv_task = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
76
  ob_task = self.data_manager.get_order_book_snapshot(symbol)
77
  ohlcv_1m, order_book = await asyncio.gather(ohlcv_task, ob_task)
78
 
79
- if not ohlcv_1m or len(ohlcv_1m) < 100:
80
- print(f" -> ⚠️ [Skip] {symbol}: Insufficient 1m data.")
81
- continue
82
 
83
- # 2. استشارة القناص (هو من يمتلك القرار والعتبة)
84
- # نمرر له البيانات وننتظر الحكم (BUY/WAIT) + التفاصيل
85
  sniper_result = await self.processor.check_sniper_entry(ohlcv_1m, order_book)
86
-
87
  sniper_signal = sniper_result.get('signal', 'WAIT')
88
  final_conf = sniper_result.get('confidence_prob', 0.0)
89
 
90
- # استخراج التفاصيل للسجلات (الشفافية الكاملة)
91
- ml_score = sniper_result.get('ml_score', 0.0)
92
- ob_score = sniper_result.get('ob_score', 0.0)
93
- reason = sniper_result.get('reason', 'N/A')
94
-
95
- # طباعة السجل المفصل الذي طلبته
96
- log_msg = (f" -> 🔭 {symbol:<6} | Decision: {sniper_signal} | Score: {final_conf:.2f} "
97
- f"(ML:{ml_score:.2f} + OB:{ob_score:.2f})")
98
- print(log_msg)
99
 
100
- # 3. التحقق من التوافق (طاعة عمياء للمحرك)
101
- # لم نعد نفحص أي أرقام هنا، المحرك قام بذلك بالفعل
102
  if sniper_signal == 'BUY':
103
- print(f" ✅ [ACCEPTED] {symbol} approved by Sniper Engine.")
104
-
105
  signal['sniper_entry_price'] = sniper_result.get('entry_price', 0)
106
  signal['sniper_score'] = final_conf
107
  sniper_candidates.append(signal)
108
- else:
109
- # فقط نوضح سبب الرفض في حال أراد المستخدم المراجعة
110
- pass # الرسالة السابقة تكفي (Decision: WAIT)
111
 
112
  if not sniper_candidates:
113
  print(" -> 📉 No candidates passed the Sniper L4 check.")
114
  return
115
 
116
- # ترتيب حسب قوة Oracle + قوة القناص
117
- sniper_candidates.sort(
118
- key=lambda x: (x.get('confidence', 0) + x.get('sniper_score', 0)),
119
- reverse=True
120
- )
121
-
122
  best_signal = sniper_candidates[0]
123
 
124
  async with self.execution_lock:
@@ -127,13 +139,11 @@ class TradeManager:
127
  await self._execute_entry_from_signal(best_signal['symbol'], best_signal)
128
 
129
  # ==============================================================================
130
- # 🚀 Entry Execution (With Oracle V4 Params)
131
  # ==============================================================================
132
  async def _execute_entry_from_signal(self, symbol, signal_data):
133
  try:
134
  trade_id = str(uuid.uuid4())
135
-
136
- # تحديد سعر الدخول (الأفضلية لسعر القناص، ثم السعر الحالي)
137
  current_price = float(signal_data.get('sniper_entry_price', 0.0))
138
  if current_price <= 0.0:
139
  current_price = await self.data_manager.get_latest_price_async(symbol)
@@ -142,19 +152,15 @@ class TradeManager:
142
  current_capital = float(portfolio.get('current_capital_usd', 100.0))
143
  entry_fee_usd = current_capital * self.FEE_RATE
144
 
145
- # --- [Oracle V4 Params] ---
146
- # استخدام الأهداف الذكية القادمة من Oracle
147
- # إذا لم تتوفر (Fallback)، نستخدم حسابات بسيطة
148
  tp_price = float(signal_data.get('primary_tp', current_price * 1.05))
149
  sl_price = float(signal_data.get('sl_price', current_price * 0.95))
150
  oracle_strength = float(signal_data.get('strength', 0.5))
151
- oracle_class = signal_data.get('target_class', 'TP2')
152
 
153
  new_trade = {
154
  'id': trade_id,
155
  'symbol': symbol,
156
  'entry_price': current_price,
157
- 'direction': 'LONG', # Spot is always LONG
158
  'entry_time': datetime.now().isoformat(),
159
  'status': 'OPEN',
160
  'tp_price': tp_price,
@@ -162,175 +168,147 @@ class TradeManager:
162
  'last_update': datetime.now().isoformat(),
163
  'last_oracle_check': datetime.now().isoformat(),
164
  'strategy': 'OracleV4_Spot',
165
-
166
- # تخزين بيانات Oracle
167
  'initial_oracle_strength': oracle_strength,
168
- 'initial_oracle_class': oracle_class,
169
  'oracle_tp_map': signal_data.get('tp_map', {}),
170
-
171
  'entry_capital': current_capital,
172
  'entry_fee_usd': entry_fee_usd,
173
  'l1_score': float(signal_data.get('enhanced_final_score', 0.0)),
174
- 'decision_data': {
175
- 'components': signal_data.get('components', {}),
176
- 'oracle_conf': signal_data.get('confidence', 0)
177
- }
178
  }
179
 
180
  self.open_positions[symbol] = new_trade
181
  if self.watchlist: self.watchlist.clear()
182
 
183
- if portfolio.get('first_trade_timestamp') is None:
184
- portfolio['first_trade_timestamp'] = new_trade['entry_time']
185
- await self.r2.save_portfolio_state_async(portfolio)
186
-
187
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
188
 
 
189
  if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel()
190
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
191
 
192
- print(f"✅ [ENTRY] {symbol} @ {current_price} | TP: {tp_price} | SL: {sl_price} | Str: {oracle_strength:.2f}")
193
 
194
  except Exception as e:
195
  print(f"❌ [Entry Error] {symbol}: {e}")
196
  traceback.print_exc()
197
 
198
  # ==============================================================================
199
- # 🛡️ Hybrid Sentry + Strategic Oracle Layer
200
  # ==============================================================================
201
  async def _guardian_loop(self, symbol: str):
202
- print(f"🛡️ [Sentry] Guarding {symbol} (High/Low Simulation + Oracle)...")
203
  last_ai_check_time = 0
204
 
205
- while self.running and symbol in self.open_positions:
 
 
 
 
 
 
206
  try:
207
- await asyncio.sleep(1)
208
- trade = self.open_positions.get(symbol)
209
- if not trade: break
210
 
211
  # -------------------------------------------------------------
212
- # 1. محاكاة التلامس (High/Low Wicks Simulation)
213
  # -------------------------------------------------------------
214
- ohlcv = await self.data_manager.get_latest_ohlcv(symbol, '1m', limit=2)
215
  current_ticker_price = await self.data_manager.get_latest_price_async(symbol)
216
-
217
- if not ohlcv: continue
218
-
219
- current_candle = ohlcv[-1]
220
- candle_high = current_candle[2]
221
- candle_low = current_candle[3]
222
 
223
- max_price_seen = max(current_ticker_price, candle_high)
224
- min_price_seen = min(current_ticker_price, candle_low)
225
-
226
- # Spot Long Logic Check
227
- if max_price_seen >= trade['tp_price']:
228
- print(f"🎯 [TP HIT] Price {max_price_seen} touched Target {trade['tp_price']}")
229
  async with self.execution_lock:
230
  await self._execute_exit(symbol, trade['tp_price'], "TP_HIT")
231
  break
232
-
233
- if min_price_seen <= trade['sl_price']:
234
- print(f"🛑 [SL HIT] Price {min_price_seen} touched Stop {trade['sl_price']}")
235
  async with self.execution_lock:
236
  await self._execute_exit(symbol, trade['sl_price'], "SL_HIT")
237
- break
238
 
239
  # -------------------------------------------------------------
240
- # 2. الحارس الهجين (Hybrid Guardian - AI Check)
241
  # -------------------------------------------------------------
242
  if time.time() - last_ai_check_time > 60:
 
 
 
243
  t1 = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
244
  t5 = self.data_manager.get_latest_ohlcv(symbol, '5m', 500)
245
  t15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 500)
246
- d1, d5, d15 = await asyncio.gather(t1, t5, t15)
247
 
248
- if d1 and d5 and d15 and len(d1) >= 500:
249
- decision = self.processor.consult_guardian(d1, d5, d15, trade['entry_price'])
 
 
 
 
 
 
 
 
 
250
  action = decision.get('action', 'HOLD')
251
  scores = decision.get('scores', {})
 
 
 
 
 
252
 
253
  if action in ['EXIT_HARD', 'EXIT_SOFT']:
254
- print(f"🤖 [Guardian] {action}: {decision.get('reason')}")
255
  async with self.execution_lock:
256
  await self._execute_exit(symbol, current_ticker_price, f"AI_{action}", ai_scores=scores)
257
  break
 
 
 
 
 
258
  last_ai_check_time = time.time()
259
-
260
  self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
261
 
262
  # -------------------------------------------------------------
263
- # 3. الطبقة الاستراتيجية (Oracle V4 Re-Evaluation) [NEW]
264
  # -------------------------------------------------------------
265
- last_oracle_check = datetime.fromisoformat(trade.get('last_oracle_check', datetime.now().isoformat()))
266
- if (datetime.now() - last_oracle_check).total_seconds() > self.ORACLE_CHECK_INTERVAL:
267
- # تحديث التوقيت أولاً لمنع التكرار
268
  self.open_positions[symbol]['last_oracle_check'] = datetime.now().isoformat()
269
- # استشارة الجنرال
270
  await self._consult_oracle_strategy_update(symbol, trade)
271
 
272
- except asyncio.CancelledError: break
 
 
273
  except Exception as e:
274
- print(f"❌ [Sentry Error] {symbol}: {e}")
275
- await asyncio.sleep(5)
 
276
 
277
  async def _consult_oracle_strategy_update(self, symbol, trade):
278
- """
279
- Oracle V4 Strategy Update:
280
- - هل تحول الاتجاه إلى هبوط؟ -> خروج.
281
- - هل ضعفت القوة؟ -> تقليص الهدف.
282
- """
283
  try:
284
- # 1. تجميع البيانات
285
- # نحتاج محاكاة دورة تحليلية مصغرة للحصول على Scores + OHLCV
286
- # نطلب من DataManager جلب البيانات الأساسية
287
  tasks = [self.data_manager.get_latest_ohlcv(symbol, tf, limit=100) for tf in ["15m", "1h", "4h"]]
288
  results = await asyncio.gather(*tasks)
289
  ohlcv_data = {tf: res for tf, res in zip(["15m", "1h", "4h"], results) if res}
290
 
291
  if '1h' not in ohlcv_data: return
292
-
293
- curr_p = await self.data_manager.get_latest_price_async(symbol)
294
- raw_data = {'symbol': symbol, 'ohlcv': ohlcv_data, 'current_price': curr_p}
295
 
296
- # 2. تشغيل الطبقة الثانية (Titan/MC) للحصول على Scores محدثة
297
- l2_analysis = await self.processor.process_compound_signal(raw_data)
298
- if not l2_analysis: return
299
-
300
- # 3. استشارة Oracle V4
301
- oracle_verdict = await self.processor.consult_oracle(l2_analysis)
302
-
303
- # 4. اتخاذ القرار
304
- # الحالة أ: Oracle يقول WAIT أو يتوقع هبوط
305
- # (تذكر: Oracle V4 يعيد WAIT إذا كان التوقع Short في Spot Mode)
306
- if oracle_verdict.get('action') == 'WAIT' or oracle_verdict.get('direction') == 'SHORT':
307
- print(f"🚨 [Oracle Command] Outlook turned Bearish for {symbol}. Exiting...")
308
- await self.force_exit_by_manager(symbol, reason="Oracle_Bearish_Flip")
309
- return
310
-
311
- # الحالة ب: ضعف الزخم (Weakness)
312
- current_strength = oracle_verdict.get('strength', 0.5)
313
- initial_strength = trade.get('initial_oracle_strength', 0.5)
314
 
315
- if current_strength < (initial_strength * 0.6):
316
- print(f"⚠️ [Oracle Command] Momentum fading ({initial_strength:.2f}->{current_strength:.2f}). Lowering TP.")
317
-
318
- tp_map = trade.get('oracle_tp_map', {})
319
- conservative_tp = tp_map.get('TP1')
320
- current_tp = trade['tp_price']
321
 
322
- # تأكد أن الهدف الجديد أعلى من السعر الحالي (نحن في صفقة شراء)
323
- if conservative_tp and conservative_tp > curr_p:
324
- # تحديث الهدف إذا كان أقرب من الهدف الحالي
325
- if conservative_tp < current_tp:
326
- self.open_positions[symbol]['tp_price'] = conservative_tp
327
- print(f" -> TP updated to {conservative_tp} (TP1)")
328
-
329
  except Exception as e:
330
  print(f"⚠️ [Oracle Re-Eval Error] {e}")
331
 
332
  # ==============================================================================
333
- # 👻 Ghost Monitor & Learning (Restored)
334
  # ==============================================================================
335
  def _launch_post_exit_analysis(self, symbol, exit_price, exit_time, position_size_usd, ai_scores=None, trade_obj=None):
336
  asyncio.create_task(self._analyze_after_exit_task(symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj))
@@ -345,13 +323,11 @@ class TradeManager:
345
  self.ai_stats[key]["missed"] += abs(usd_impact)
346
 
347
  async def _analyze_after_exit_task(self, symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj):
348
- await asyncio.sleep(900) # 15 min check
349
  try:
350
  curr = await self.data_manager.get_latest_price_async(symbol)
351
  if curr == 0: return
352
 
353
- # Spot Logic: الخروج الجيد هو عندما يهبط السعر بعد بيعنا
354
- # Exit Price > Current Price => Good Exit (Saved money)
355
  change_pct = (curr - exit_price) / exit_price
356
  usd_impact = change_pct * position_size_usd
357
  is_good_exit = change_pct < 0
@@ -361,19 +337,13 @@ class TradeManager:
361
  if ai_scores.get('v2', 0) >= 0.60: self._update_specific_stat("v2", is_good_exit, usd_impact)
362
  if ai_scores.get('v3', 0) >= 0.75: self._update_specific_stat("v3", is_good_exit, usd_impact)
363
 
364
- record = {"symbol": symbol, "exit_price": exit_price, "price_15m": curr, "usd_impact": usd_impact, "verdict": "SUCCESS" if is_good_exit else "MISS"}
365
- await self.r2.append_deep_steward_audit(record)
366
-
367
  if self.learning_hub and trade_obj:
368
  trade_obj['pnl_percent'] = trade_obj.get('profit_pct', 0.0)
369
  await self.learning_hub.analyze_trade_and_learn(trade_obj, trade_obj.get('exit_reason', 'UNKNOWN'))
370
 
371
  except Exception as e:
372
- print(f"⚠️ [Ghost/Learning Error] {e}")
373
 
374
- # ==============================================================================
375
- # 🔴 Exit Logic (Spot PnL)
376
- # ==============================================================================
377
  async def _execute_exit(self, symbol, price, reason, ai_scores=None):
378
  if symbol not in self.open_positions: return
379
  try:
@@ -381,13 +351,12 @@ class TradeManager:
381
  entry_price = float(trade['entry_price'])
382
  exit_price = float(price)
383
  entry_capital = float(trade.get('entry_capital', 100.0))
384
- entry_fee_usd = float(trade.get('entry_fee_usd', 0.0))
385
 
386
- # Spot PnL Logic
387
  exit_value_gross = (exit_price / entry_price) * entry_capital
388
  exit_fee_usd = exit_value_gross * self.FEE_RATE
389
- net_exit_value = exit_value_gross - exit_fee_usd
390
- net_pnl_usd = net_exit_value - entry_capital
391
  net_profit_pct = (net_pnl_usd / entry_capital) * 100
392
 
393
  trade.update({
@@ -396,14 +365,12 @@ class TradeManager:
396
  'exit_reason': reason,
397
  'profit_pct': net_profit_pct,
398
  'net_pnl_usd': net_pnl_usd,
399
- 'fees_paid_usd': entry_fee_usd + exit_fee_usd
 
400
  })
401
 
402
  portfolio = await self.r2.get_portfolio_state_async()
403
- current_total_cap = float(portfolio.get('current_capital_usd', 100.0))
404
- new_cap = current_total_cap + net_pnl_usd
405
-
406
- portfolio['current_capital_usd'] = new_cap
407
  portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
408
 
409
  if net_pnl_usd >= 0:
@@ -419,19 +386,19 @@ class TradeManager:
419
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
420
  await self.r2.append_to_closed_trades_history(trade)
421
 
422
- print(f"✅ [EXIT] {symbol} | Net PnL: {net_profit_pct:.2f}% (${net_pnl_usd:.2f}) | {reason}")
423
 
424
- self._launch_post_exit_analysis(symbol, exit_price, trade.get('exit_time'), entry_capital, ai_scores, trade)
425
 
426
  if symbol in self.sentry_tasks:
427
  self.sentry_tasks[symbol].cancel()
428
  del self.sentry_tasks[symbol]
 
 
429
 
430
  except Exception as e:
431
  print(f"❌ [Exit Error] {e}")
432
  traceback.print_exc()
433
- if symbol not in self.open_positions:
434
- self.open_positions[symbol] = trade
435
 
436
  async def force_exit_by_manager(self, symbol, reason):
437
  p = await self.data_manager.get_latest_price_async(symbol)
@@ -439,9 +406,7 @@ class TradeManager:
439
  await self._execute_exit(symbol, p, reason)
440
 
441
  async def start_sentry_loops(self):
442
- for symbol in list(self.open_positions.keys()):
443
- if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
444
- self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
445
 
446
  async def stop_sentry_loops(self):
447
  self.running = False
 
1
+ # trade_manager.py (V27.7 - GEM-Architect: Watchdog & Resurrection Edition)
2
 
3
  import asyncio
4
  import uuid
 
19
  self.sentry_tasks = {}
20
  self.running = True
21
 
22
+ # نظام نقل الرسائل للواجهة (UI Bridge)
23
+ self.latest_guardian_log = "🛡️ Guardian System Initialized."
24
 
25
+ # إعدادات الرسوم
26
+ self.FEE_RATE = 0.001
27
+ self.ORACLE_CHECK_INTERVAL = 900
28
 
29
  self.ai_stats = {
30
  "hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
 
33
  }
34
 
35
  self.execution_lock = asyncio.Lock()
36
+ print(f"🛡️ [TradeManager V27.7] Watchdog System Online.")
37
 
38
  async def initialize_sentry_exchanges(self):
39
  print("🛡️ [TradeManager] Syncing state with R2...")
 
42
  async def sync_internal_state_with_r2(self):
43
  try:
44
  open_trades_list = await self.r2.get_open_trades_async()
45
+ # نحافظ على الصفقات الموجودة في الذاكرة لتجنب قتل الحلقات عن طريق الخطأ
46
+ current_ids = set(self.open_positions.keys())
47
+
48
+ for trade in open_trades_list:
49
+ sym = trade['symbol']
50
+ if sym not in self.open_positions:
51
+ self.open_positions[sym] = trade
52
+ print(f" -> [Sync] Restored trade for {sym} from DB.")
53
+
54
+ # (اختياري) تنظيف الصفقات التي اختفت من قاعدة البيانات
55
+ # self.open_positions = {k: v for k, v in self.open_positions.items() if k in [t['symbol'] for t in open_trades_list]}
56
+
57
  except Exception as e:
58
  print(f"❌ [TradeManager] R2 Sync Failed: {e}")
 
59
 
60
  # ==============================================================================
61
+ # 🐕 Watchdog: Ensure Guardians are ALWAYS Running
62
+ # ==============================================================================
63
+ async def ensure_active_guardians(self):
64
+ """
65
+ تقوم هذه الدالة بفحص كل صفقة مفتوحة.
66
+ إذا لم يكن لها "حارس" (Task) يعمل، تقوم بإنشاء واحد فوراً.
67
+ تُستدعى هذه الدالة بشكل دوري من الطيار الآلي.
68
+ """
69
+ active_symbols = list(self.open_positions.keys())
70
+ if not active_symbols:
71
+ return "💤 No active trades."
72
+
73
+ restored_count = 0
74
+ status_msgs = []
75
+
76
+ for symbol in active_symbols:
77
+ # التحقق: هل المهمة موجودة؟ وهل هي تعمل؟
78
+ task = self.sentry_tasks.get(symbol)
79
+ is_alive = task and not task.done()
80
+
81
+ if not is_alive:
82
+ print(f"🚨 [Watchdog] Found DEAD guardian for {symbol}. Resurrecting...")
83
+ self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
84
+ restored_count += 1
85
+ status_msgs.append(f"♻️ Resurrected {symbol}")
86
+ else:
87
+ status_msgs.append(f"✅ {symbol} Active")
88
+
89
+ # تحديث السجل للواجهة
90
+ self.latest_guardian_log = f"🛡️ Guardians: {', '.join(status_msgs)}"
91
+ if restored_count > 0:
92
+ return f"⚠️ Watchdog restored {restored_count} guardians."
93
+ return "✅ All guardians active."
94
+
95
+ # ==============================================================================
96
+ # 🎯 L4 Sniper Execution Logic
97
  # ==============================================================================
98
  async def select_and_execute_best_signal(self, oracle_approved_signals: List[Dict[str, Any]]):
99
+ if len(self.open_positions) > 0: return
 
 
100
 
101
  if not self.processor.initialized:
102
  await self.processor.initialize()
103
 
104
  sniper_candidates = []
 
105
  print(f"\n🔎 [Sniper] Scanning {len(oracle_approved_signals)} candidates...")
106
 
107
  for signal in oracle_approved_signals:
108
  symbol = signal['symbol']
109
+ if signal.get('action_type') != 'BUY': continue
 
 
 
 
110
  if symbol in self.open_positions: continue
111
 
 
112
  ohlcv_task = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
113
  ob_task = self.data_manager.get_order_book_snapshot(symbol)
114
  ohlcv_1m, order_book = await asyncio.gather(ohlcv_task, ob_task)
115
 
116
+ if not ohlcv_1m or len(ohlcv_1m) < 100: continue
 
 
117
 
 
 
118
  sniper_result = await self.processor.check_sniper_entry(ohlcv_1m, order_book)
 
119
  sniper_signal = sniper_result.get('signal', 'WAIT')
120
  final_conf = sniper_result.get('confidence_prob', 0.0)
121
 
122
+ print(f" -> 🔭 {symbol:<6} | Decision: {sniper_signal} | Score: {final_conf:.2f}")
 
 
 
 
 
 
 
 
123
 
 
 
124
  if sniper_signal == 'BUY':
 
 
125
  signal['sniper_entry_price'] = sniper_result.get('entry_price', 0)
126
  signal['sniper_score'] = final_conf
127
  sniper_candidates.append(signal)
 
 
 
128
 
129
  if not sniper_candidates:
130
  print(" -> 📉 No candidates passed the Sniper L4 check.")
131
  return
132
 
133
+ sniper_candidates.sort(key=lambda x: (x.get('confidence', 0) + x.get('sniper_score', 0)), reverse=True)
 
 
 
 
 
134
  best_signal = sniper_candidates[0]
135
 
136
  async with self.execution_lock:
 
139
  await self._execute_entry_from_signal(best_signal['symbol'], best_signal)
140
 
141
  # ==============================================================================
142
+ # 🚀 Entry Execution
143
  # ==============================================================================
144
  async def _execute_entry_from_signal(self, symbol, signal_data):
145
  try:
146
  trade_id = str(uuid.uuid4())
 
 
147
  current_price = float(signal_data.get('sniper_entry_price', 0.0))
148
  if current_price <= 0.0:
149
  current_price = await self.data_manager.get_latest_price_async(symbol)
 
152
  current_capital = float(portfolio.get('current_capital_usd', 100.0))
153
  entry_fee_usd = current_capital * self.FEE_RATE
154
 
 
 
 
155
  tp_price = float(signal_data.get('primary_tp', current_price * 1.05))
156
  sl_price = float(signal_data.get('sl_price', current_price * 0.95))
157
  oracle_strength = float(signal_data.get('strength', 0.5))
 
158
 
159
  new_trade = {
160
  'id': trade_id,
161
  'symbol': symbol,
162
  'entry_price': current_price,
163
+ 'direction': 'LONG',
164
  'entry_time': datetime.now().isoformat(),
165
  'status': 'OPEN',
166
  'tp_price': tp_price,
 
168
  'last_update': datetime.now().isoformat(),
169
  'last_oracle_check': datetime.now().isoformat(),
170
  'strategy': 'OracleV4_Spot',
 
 
171
  'initial_oracle_strength': oracle_strength,
172
+ 'initial_oracle_class': signal_data.get('target_class', 'TP2'),
173
  'oracle_tp_map': signal_data.get('tp_map', {}),
 
174
  'entry_capital': current_capital,
175
  'entry_fee_usd': entry_fee_usd,
176
  'l1_score': float(signal_data.get('enhanced_final_score', 0.0)),
 
 
 
 
177
  }
178
 
179
  self.open_positions[symbol] = new_trade
180
  if self.watchlist: self.watchlist.clear()
181
 
 
 
 
 
182
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
183
 
184
+ # Start Guardian immediately
185
  if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel()
186
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
187
 
188
+ print(f"✅ [ENTRY] {symbol} @ {current_price} | TP: {tp_price} | SL: {sl_price}")
189
 
190
  except Exception as e:
191
  print(f"❌ [Entry Error] {symbol}: {e}")
192
  traceback.print_exc()
193
 
194
  # ==============================================================================
195
+ # 🛡️ Hybrid Sentry (The Independent Loop)
196
  # ==============================================================================
197
  async def _guardian_loop(self, symbol: str):
198
+ print(f"🛡️ [Sentry] STARTING WATCH for {symbol}...")
199
  last_ai_check_time = 0
200
 
201
+ # حلقة لا نهائية طالما الصفقة موجودة
202
+ while self.running:
203
+ # 1. Check if trade still exists in memory
204
+ if symbol not in self.open_positions:
205
+ print(f"👋 [Sentry] Trade {symbol} closed/removed. Stopping sentry.")
206
+ break
207
+
208
  try:
209
+ await asyncio.sleep(1) # Fast tick for price checks
210
+ trade = self.open_positions[symbol]
 
211
 
212
  # -------------------------------------------------------------
213
+ # 1. محاكاة التلامس السريعة (Fast Price Check)
214
  # -------------------------------------------------------------
215
+ # نستخدم السعر اللحظي لتوفير استدعاءات الـ API
216
  current_ticker_price = await self.data_manager.get_latest_price_async(symbol)
 
 
 
 
 
 
217
 
218
+ # Check TP/SL
219
+ if current_ticker_price >= trade['tp_price']:
220
+ print(f"🎯 [TP HIT] Price {current_ticker_price} >= {trade['tp_price']}")
 
 
 
221
  async with self.execution_lock:
222
  await self._execute_exit(symbol, trade['tp_price'], "TP_HIT")
223
  break
224
+
225
+ if current_ticker_price <= trade['sl_price']:
226
+ print(f"🛑 [SL HIT] Price {current_ticker_price} <= {trade['sl_price']}")
227
  async with self.execution_lock:
228
  await self._execute_exit(symbol, trade['sl_price'], "SL_HIT")
229
+ break
230
 
231
  # -------------------------------------------------------------
232
+ # 2. الحارس الهجين (The Hybrid Guardian - Every 60s)
233
  # -------------------------------------------------------------
234
  if time.time() - last_ai_check_time > 60:
235
+ self.latest_guardian_log = f"🧠 Guardian Analyzing {symbol}..."
236
+
237
+ # جلب البيانات بشكل متزامن
238
  t1 = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
239
  t5 = self.data_manager.get_latest_ohlcv(symbol, '5m', 500)
240
  t15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 500)
 
241
 
242
+ try:
243
+ d1, d5, d15 = await asyncio.gather(t1, t5, t15)
244
+ except Exception as fetch_err:
245
+ print(f"⚠️ [Sentry Warning] Data fetch failed for {symbol}: {fetch_err}")
246
+ await asyncio.sleep(5) # انتظر قليلاً وحاول في الدورة القادمة
247
+ continue
248
+
249
+ if d1 and d5 and d15 and len(d1) >= 200:
250
+ # استشارة الحارس
251
+ decision = self.processor.consult_guardian(d1, d5, d15, float(trade['entry_price']))
252
+
253
  action = decision.get('action', 'HOLD')
254
  scores = decision.get('scores', {})
255
+ reason = decision.get('reason', 'N/A')
256
+
257
+ # تحديث السجل للواجهة
258
+ score_txt = f"V2:{scores.get('v2',0):.2f}|V3:{scores.get('v3',0):.2f}"
259
+ self.latest_guardian_log = f"🛡️ {symbol}: {action} ({score_txt})"
260
 
261
  if action in ['EXIT_HARD', 'EXIT_SOFT']:
262
+ print(f"🤖 [Guardian Trigger] {symbol} -> {action}: {reason}")
263
  async with self.execution_lock:
264
  await self._execute_exit(symbol, current_ticker_price, f"AI_{action}", ai_scores=scores)
265
  break
266
+ else:
267
+ # Log Safe Status
268
+ # print(f" 🛡️ [Guardian] {symbol} Safe. {reason}")
269
+ pass
270
+
271
  last_ai_check_time = time.time()
 
272
  self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
273
 
274
  # -------------------------------------------------------------
275
+ # 3. Oracle Strategy Update (Every 15 mins)
276
  # -------------------------------------------------------------
277
+ last_check_str = trade.get('last_oracle_check', datetime.now().isoformat())
278
+ if (datetime.now() - datetime.fromisoformat(last_check_str)).total_seconds() > self.ORACLE_CHECK_INTERVAL:
 
279
  self.open_positions[symbol]['last_oracle_check'] = datetime.now().isoformat()
 
280
  await self._consult_oracle_strategy_update(symbol, trade)
281
 
282
+ except asyncio.CancelledError:
283
+ print(f"🛑 [Sentry] Task Cancelled for {symbol}")
284
+ break
285
  except Exception as e:
286
+ print(f"❌ [Sentry Critical Error] {symbol}: {e}")
287
+ traceback.print_exc()
288
+ await asyncio.sleep(10) # Backoff on crash to avoid CPU spin
289
 
290
  async def _consult_oracle_strategy_update(self, symbol, trade):
 
 
 
 
 
291
  try:
 
 
 
292
  tasks = [self.data_manager.get_latest_ohlcv(symbol, tf, limit=100) for tf in ["15m", "1h", "4h"]]
293
  results = await asyncio.gather(*tasks)
294
  ohlcv_data = {tf: res for tf, res in zip(["15m", "1h", "4h"], results) if res}
295
 
296
  if '1h' not in ohlcv_data: return
 
 
 
297
 
298
+ curr_p = await self.data_manager.get_latest_price_async(symbol)
299
+ l2_analysis = await self.processor.process_compound_signal({'symbol': symbol, 'ohlcv': ohlcv_data, 'current_price': curr_p})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
+ if l2_analysis:
302
+ oracle_verdict = await self.processor.consult_oracle(l2_analysis)
 
 
 
 
303
 
304
+ if oracle_verdict.get('action') == 'WAIT' or oracle_verdict.get('direction') == 'SHORT':
305
+ print(f"🚨 [Oracle Command] Bearish Flip for {symbol}. Exiting...")
306
+ await self.force_exit_by_manager(symbol, reason="Oracle_Bearish_Flip")
 
 
 
 
307
  except Exception as e:
308
  print(f"⚠️ [Oracle Re-Eval Error] {e}")
309
 
310
  # ==============================================================================
311
+ # 👻 Post-Exit Logic
312
  # ==============================================================================
313
  def _launch_post_exit_analysis(self, symbol, exit_price, exit_time, position_size_usd, ai_scores=None, trade_obj=None):
314
  asyncio.create_task(self._analyze_after_exit_task(symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj))
 
323
  self.ai_stats[key]["missed"] += abs(usd_impact)
324
 
325
  async def _analyze_after_exit_task(self, symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj):
326
+ await asyncio.sleep(900)
327
  try:
328
  curr = await self.data_manager.get_latest_price_async(symbol)
329
  if curr == 0: return
330
 
 
 
331
  change_pct = (curr - exit_price) / exit_price
332
  usd_impact = change_pct * position_size_usd
333
  is_good_exit = change_pct < 0
 
337
  if ai_scores.get('v2', 0) >= 0.60: self._update_specific_stat("v2", is_good_exit, usd_impact)
338
  if ai_scores.get('v3', 0) >= 0.75: self._update_specific_stat("v3", is_good_exit, usd_impact)
339
 
 
 
 
340
  if self.learning_hub and trade_obj:
341
  trade_obj['pnl_percent'] = trade_obj.get('profit_pct', 0.0)
342
  await self.learning_hub.analyze_trade_and_learn(trade_obj, trade_obj.get('exit_reason', 'UNKNOWN'))
343
 
344
  except Exception as e:
345
+ print(f"⚠️ [Ghost Error] {e}")
346
 
 
 
 
347
  async def _execute_exit(self, symbol, price, reason, ai_scores=None):
348
  if symbol not in self.open_positions: return
349
  try:
 
351
  entry_price = float(trade['entry_price'])
352
  exit_price = float(price)
353
  entry_capital = float(trade.get('entry_capital', 100.0))
 
354
 
355
+ # PnL Calc
356
  exit_value_gross = (exit_price / entry_price) * entry_capital
357
  exit_fee_usd = exit_value_gross * self.FEE_RATE
358
+ entry_fee_usd = float(trade.get('entry_fee_usd', 0.0))
359
+ net_pnl_usd = (exit_value_gross - exit_fee_usd) - entry_capital
360
  net_profit_pct = (net_pnl_usd / entry_capital) * 100
361
 
362
  trade.update({
 
365
  'exit_reason': reason,
366
  'profit_pct': net_profit_pct,
367
  'net_pnl_usd': net_pnl_usd,
368
+ 'fees_paid_usd': entry_fee_usd + exit_fee_usd,
369
+ 'exit_time': datetime.now().isoformat()
370
  })
371
 
372
  portfolio = await self.r2.get_portfolio_state_async()
373
+ portfolio['current_capital_usd'] = float(portfolio.get('current_capital_usd', 100.0)) + net_pnl_usd
 
 
 
374
  portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
375
 
376
  if net_pnl_usd >= 0:
 
386
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
387
  await self.r2.append_to_closed_trades_history(trade)
388
 
389
+ print(f"✅ [EXIT] {symbol} | PnL: {net_profit_pct:.2f}% | {reason}")
390
 
391
+ self._launch_post_exit_analysis(symbol, exit_price, datetime.now().isoformat(), entry_capital, ai_scores, trade)
392
 
393
  if symbol in self.sentry_tasks:
394
  self.sentry_tasks[symbol].cancel()
395
  del self.sentry_tasks[symbol]
396
+
397
+ self.latest_guardian_log = f"✅ Closed {symbol} ({reason})"
398
 
399
  except Exception as e:
400
  print(f"❌ [Exit Error] {e}")
401
  traceback.print_exc()
 
 
402
 
403
  async def force_exit_by_manager(self, symbol, reason):
404
  p = await self.data_manager.get_latest_price_async(symbol)
 
406
  await self._execute_exit(symbol, p, reason)
407
 
408
  async def start_sentry_loops(self):
409
+ await self.ensure_active_guardians() # Uses the new watchdog method directly
 
 
410
 
411
  async def stop_sentry_loops(self):
412
  self.running = False