Riy777 commited on
Commit
82033bd
·
verified ·
1 Parent(s): e3bba69

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +206 -152
trade_manager.py CHANGED
@@ -1,10 +1,10 @@
1
- # trade_manager.py (V25.0 - GEM-Architect: Strategic Oracle Integration)
2
 
3
  import asyncio
4
  import uuid
5
  import time
6
  import traceback
7
- from datetime import datetime, timedelta
8
  from typing import List, Dict, Any
9
 
10
  class TradeManager:
@@ -32,7 +32,7 @@ class TradeManager:
32
  }
33
 
34
  self.execution_lock = asyncio.Lock()
35
- print(f"🛡️ [TradeManager V25.0] Strategic Commander Initialized.")
36
 
37
  async def initialize_sentry_exchanges(self):
38
  print("🛡️ [TradeManager] Syncing state with R2...")
@@ -48,12 +48,9 @@ class TradeManager:
48
  self.open_positions = {}
49
 
50
  # ==============================================================================
51
- # 🎯 L4 Sniper Execution Logic (Oracle Directed)
52
  # ==============================================================================
53
  async def select_and_execute_best_signal(self, oracle_approved_signals: List[Dict[str, Any]]):
54
- """
55
- يتلقى إشارات Oracle V2، ويستخدم Sniper (القديم) فقط لاختيار أفضل نقطة دخول.
56
- """
57
  if len(self.open_positions) > 0:
58
  print(f"⛔ [TradeManager] Max positions reached. Skipping scan.")
59
  return
@@ -65,12 +62,14 @@ class TradeManager:
65
 
66
  for signal in oracle_approved_signals:
67
  symbol = signal['symbol']
68
- oracle_direction = signal.get('action_type', 'BUY') # BUY or SELL from Oracle
69
 
 
 
 
 
70
  if symbol in self.open_positions: continue
71
 
72
  # 1. جلب بيانات الدقيقة للقناص
73
- # القناص يحتاج بيانات دقيقة ليعمل
74
  ohlcv_task = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
75
  ob_task = self.data_manager.get_order_book_snapshot(symbol)
76
  ohlcv_1m, order_book = await asyncio.gather(ohlcv_task, ob_task)
@@ -79,35 +78,20 @@ class TradeManager:
79
  print(f" -> [Sniper Skip] {symbol} (Insufficient 1m data).")
80
  continue
81
 
82
- # 2. استشارة القناص (Tactical Check)
83
- # نمرر له البيانات ليفحص هل الدخول مناسب الآن؟
84
- # ملاحظة: نستخدم check_sniper_entry الموجودة في Processor والتي تستدعي SniperEngine
85
  sniper_result = await self.processor.check_sniper_entry(ohlcv_1m, order_book)
86
 
87
- # 3. التحقق من التوافق (Alignment Check)
88
- # Oracle يقول "شراء"، هل القناص يرى دخول "شراء" جيد؟
89
- sniper_signal = sniper_result.get('signal', 'WAIT') # BUY/SELL/WAIT
90
  sniper_conf = sniper_result.get('confidence_prob', 0.0)
91
 
92
- # شرط الموافقة:
93
- # 1. القناص يعطي إشارة توافق اتجاه Oracle (أو على الأقل لا تعارضه بقوة)
94
- # 2. إذا كان القناص بسيطاً جداً ويعطي BUY دائماً عند القاع، نتأكد أننا في Oracle BUY.
95
-
96
- is_compatible = False
97
- if oracle_direction == 'BUY' and sniper_signal == 'BUY':
98
- is_compatible = True
99
- elif oracle_direction == 'SELL' and sniper_signal == 'SELL':
100
- is_compatible = True
101
-
102
- if is_compatible and sniper_conf >= 0.60:
103
- print(f" -> 🎯 [Sniper Confirmed] {symbol} | Oracle:{oracle_direction} + Sniper:{sniper_signal}")
104
 
105
- # دمج معلومات القناص مع إشارة Oracle
106
- signal['sniper_entry_price'] = sniper_result.get('entry_price', 0) # إذا كان القناص يقترح سعراً
107
  signal['sniper_score'] = sniper_conf
108
  sniper_candidates.append(signal)
109
- else:
110
- print(f" -> 🔸 [Sniper Mismatch] {symbol} | Oracle:{oracle_direction} vs Sniper:{sniper_signal}")
111
 
112
  if not sniper_candidates:
113
  return
@@ -125,7 +109,7 @@ class TradeManager:
125
  await self._execute_entry_from_signal(best_signal['symbol'], best_signal)
126
 
127
  # ==============================================================================
128
- # 🚀 Entry Execution (With Oracle V2 Parameters)
129
  # ==============================================================================
130
  async def _execute_entry_from_signal(self, symbol, signal_data):
131
  try:
@@ -140,45 +124,39 @@ class TradeManager:
140
  current_capital = float(portfolio.get('current_capital_usd', 100.0))
141
  entry_fee_usd = current_capital * self.FEE_RATE
142
 
143
- # --- [Oracle V2 Integration] ---
144
- # استخدام الأهداف الذكية من Oracle
145
- tp_price = float(signal_data.get('primary_tp', 0.0))
146
- sl_price = float(signal_data.get('sl_price', 0.0))
 
147
  oracle_strength = float(signal_data.get('strength', 0.5))
148
  oracle_class = signal_data.get('target_class', 'TP2')
149
- direction = signal_data.get('action_type', 'BUY')
150
-
151
- # Fallback (Safety Net)
152
- if tp_price <= 0 or sl_price <= 0:
153
- atr_mock = current_price * 0.02
154
- if direction == 'BUY':
155
- tp_price = current_price * 1.04
156
- sl_price = current_price * 0.98
157
- else:
158
- tp_price = current_price * 0.96
159
- sl_price = current_price * 1.02
160
 
161
  new_trade = {
162
  'id': trade_id,
163
  'symbol': symbol,
164
  'entry_price': current_price,
165
- 'direction': direction, # LONG/SHORT support
166
  'entry_time': datetime.now().isoformat(),
167
  'status': 'OPEN',
168
  'tp_price': tp_price,
169
  'sl_price': sl_price,
170
  'last_update': datetime.now().isoformat(),
171
- 'last_oracle_check': datetime.now().isoformat(), # للتوقيت
172
- 'strategy': f"OracleV2_{direction}",
173
 
174
- # تخزين بيانات Oracle الأصلية للمقارنة لاحقاً
175
  'initial_oracle_strength': oracle_strength,
176
  'initial_oracle_class': oracle_class,
177
  'oracle_tp_map': signal_data.get('tp_map', {}),
178
 
179
  'entry_capital': current_capital,
180
  'entry_fee_usd': entry_fee_usd,
181
- 'l1_score': float(signal_data.get('enhanced_final_score', 0.0))
 
 
 
 
182
  }
183
 
184
  self.open_positions[symbol] = new_trade
@@ -190,11 +168,10 @@ class TradeManager:
190
 
191
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
192
 
193
- # تفعيل الحارس
194
  if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel()
195
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
196
 
197
- print(f"✅ [ENTRY] {symbol} ({direction}) @ {current_price} | TP: {tp_price} | SL: {sl_price} | Str: {oracle_strength:.2f}")
198
 
199
  except Exception as e:
200
  print(f"❌ [Entry Error] {symbol}: {e}")
@@ -204,7 +181,8 @@ class TradeManager:
204
  # 🛡️ Hybrid Sentry + Strategic Oracle Layer
205
  # ==============================================================================
206
  async def _guardian_loop(self, symbol: str):
207
- print(f"🛡️ [Sentry] Guarding {symbol}...")
 
208
 
209
  while self.running and symbol in self.open_positions:
210
  try:
@@ -213,47 +191,65 @@ class TradeManager:
213
  if not trade: break
214
 
215
  # -------------------------------------------------------------
216
- # 1. التفتيش الروتيني (Guardian Execution)
217
  # -------------------------------------------------------------
218
- # هذا الجزء لا يفهم الاستراتيجية، هو فقط ينفذ الأوامر (TP/SL/Panic)
219
  ohlcv = await self.data_manager.get_latest_ohlcv(symbol, '1m', limit=2)
220
- curr_price = await self.data_manager.get_latest_price_async(symbol)
 
 
 
 
 
 
221
 
222
- if ohlcv:
223
- candle_high = ohlcv[-1][2]
224
- candle_low = ohlcv[-1][3]
225
- direction = trade.get('direction', 'BUY')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
- # منطق الخروج (LONG)
228
- if direction == 'BUY':
229
- if max(curr_price, candle_high) >= trade['tp_price']:
230
- await self._execute_exit(symbol, trade['tp_price'], "TP_HIT")
231
- break
232
- if min(curr_price, candle_low) <= trade['sl_price']:
233
- await self._execute_exit(symbol, trade['sl_price'], "SL_HIT")
 
 
234
  break
 
235
 
236
- # منطق الخروج (SHORT)
237
- elif direction == 'SELL':
238
- if min(curr_price, candle_low) <= trade['tp_price']: # ربح الشورت في النزول
239
- await self._execute_exit(symbol, trade['tp_price'], "TP_HIT")
240
- break
241
- if max(curr_price, candle_high) >= trade['sl_price']: # خسارة الشورت في الصعود
242
- await self._execute_exit(symbol, trade['sl_price'], "SL_HIT")
243
- break
244
-
245
  # -------------------------------------------------------------
246
- # 2. الطبقة الاستراتيجية (Oracle Re-Evaluation) - System 2
247
  # -------------------------------------------------------------
248
- # نفحص هل حان وقت استشارة الجنرال؟
249
- last_check = datetime.fromisoformat(trade.get('last_oracle_check', datetime.now().isoformat()))
250
- time_since_check = (datetime.now() - last_check).total_seconds()
251
-
252
- if time_since_check > self.ORACLE_CHECK_INTERVAL:
253
- # نعم، مر الوقت المحدد. استدعاء Oracle
254
- await self._consult_oracle_strategy_update(symbol, trade)
255
- # تحديث التوقيت
256
  self.open_positions[symbol]['last_oracle_check'] = datetime.now().isoformat()
 
 
257
 
258
  except asyncio.CancelledError: break
259
  except Exception as e:
@@ -262,115 +258,173 @@ class TradeManager:
262
 
263
  async def _consult_oracle_strategy_update(self, symbol, trade):
264
  """
265
- المنطق الاستراتيجي: يسأل Oracle ويعدل تعليمات الحارس (Trade Params)
 
 
266
  """
267
  try:
268
- # 1. جلب بيانات جديدة
269
- # نحتاج أن نمرر البيانات بنفس الطريقة التي يتوقعها Processor
270
- raw_data = await self.data_manager.get_analysis_data_packet(symbol) # دالة مساعدة مفترضة أو تجميع يدوي
271
- if not raw_data: return
272
-
273
- # 2. استدعاء Oracle عبر Processor (لضمان وجود الدرجات والميزات)
274
- # بما أننا في منتصف الصفقة، قد لا نملك Titan Score محدث.
275
- # سنطلب من Processor إجراء تحليل سريع
276
- new_analysis = await self.processor.process_compound_signal(raw_data)
277
- if not new_analysis: return
 
278
 
279
- oracle_verdict = await self.processor.consult_oracle(new_analysis)
 
 
280
 
281
- # 3. اتخاذ القرارات (Logic Layer)
 
 
 
 
 
 
 
 
 
 
 
282
  current_strength = oracle_verdict.get('strength', 0.5)
283
  initial_strength = trade.get('initial_oracle_strength', 0.5)
284
- current_direction = oracle_verdict.get('action_type', 'BUY') # الاتجاه الحالي حسب Oracle
285
- trade_direction = trade.get('direction', 'BUY')
286
 
287
- print(f"🧠 [Oracle Re-Eval] {symbol}: Str {initial_strength:.2f} -> {current_strength:.2f} | Dir: {current_direction}")
288
-
289
- # السيناريو أ: انعكاس الاتجاه (Dangerous!)
290
- if current_direction != trade_direction:
291
- # إذا كانت الثقة عالية في الانعكاس، اخرج فوراً
292
- if oracle_verdict.get('confidence', 0) > 0.75:
293
- print(f"🚨 [Oracle Command] THESIS FAILED. Inverting... Exiting {symbol}.")
294
- await self.force_exit_by_manager(symbol, reason="Oracle_Trend_Reversal")
295
- return
296
-
297
- # السيناريو ب: ضعف الزخم (Weakness)
298
- # إذا انخفضت القوة بشكل ملحوظ (مثلاً 30% أقل)
299
- if current_strength < (initial_strength * 0.7):
300
- print(f"⚠️ [Oracle Command] Momentum fading. Tightening TP.")
301
 
302
- # تقليص الهدف: إذا كنا نطمح لـ TP3، نرضى بـ TP1 أو TP2
303
- current_tp = trade['tp_price']
304
  tp_map = trade.get('oracle_tp_map', {})
305
  conservative_tp = tp_map.get('TP1')
 
306
 
307
- # تعديل الهدف في الذاكرة (الحارس سينفذ هذا الهدف الجديد)
308
- if conservative_tp:
309
- # تأكد أن الهدف الجديد مازال منطقياً (أعلى من السعر الحالي للشراء)
310
- curr_p = await self.data_manager.get_latest_price_async(symbol)
311
- is_valid = (trade_direction == 'BUY' and conservative_tp > curr_p) or \
312
- (trade_direction == 'SELL' and conservative_tp < curr_p)
313
-
314
- if is_valid and abs(conservative_tp - curr_p) < abs(current_tp - curr_p):
315
  self.open_positions[symbol]['tp_price'] = conservative_tp
316
  print(f" -> TP updated to {conservative_tp} (TP1)")
317
 
318
- # السيناريو ج: زيادة القوة (Booming) - اختياري
319
- # يمكن رفع الوقف (Trailing) هنا
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
  except Exception as e:
322
- print(f"⚠️ [Re-Eval Warning] {e}")
323
 
324
  # ==============================================================================
325
- # 🔴 Exit & Helpers (Standard)
326
  # ==============================================================================
327
- async def _execute_exit(self, symbol, price, reason):
328
- # (نفس دالة الخروج السابقة مع دعم الـ Fees والـ PnL)
329
- # ... [Keep previous implementation code for _execute_exit] ...
330
- # اختصاراً للمساحة، سأفترض بقاء الكود السابق كما هو لأنه لم يتغير منطقياً
331
  if symbol not in self.open_positions: return
332
  try:
333
  trade = self.open_positions.pop(symbol)
334
  entry_price = float(trade['entry_price'])
335
  exit_price = float(price)
336
  entry_capital = float(trade.get('entry_capital', 100.0))
 
337
 
338
- # حساب PnL مع دعم Long/Short
339
- direction = trade.get('direction', 'BUY')
340
- if direction == 'BUY':
341
- pnl_gross_pct = (exit_price - entry_price) / entry_price
342
- else:
343
- pnl_gross_pct = (entry_price - exit_price) / entry_price # Short Profit
344
-
345
- gross_profit_usd = pnl_gross_pct * entry_capital
346
- fees_usd = entry_capital * self.FEE_RATE * 2 # Entry + Exit
347
- net_pnl_usd = gross_profit_usd - fees_usd
348
  net_profit_pct = (net_pnl_usd / entry_capital) * 100
349
 
350
- # تحديث المحفظة والسجلات (كما في الكود السابق)
351
  trade.update({
352
- 'status': 'CLOSED', 'exit_price': exit_price, 'exit_reason': reason,
353
- 'profit_pct': net_profit_pct, 'net_pnl_usd': net_pnl_usd, 'fees_paid_usd': fees_usd
 
 
 
 
354
  })
 
 
 
 
355
 
356
- await self.r2.append_to_closed_trades_history(trade)
357
- # ... (Update portfolio logic) ...
358
 
359
- print(f"✅ [EXIT] {symbol} ({direction}) | Net: {net_profit_pct:.2f}% (${net_pnl_usd:.2f}) | {reason}")
 
 
 
 
 
 
 
 
 
 
 
360
 
361
- if symbol in self.sentry_tasks:
 
 
 
 
362
  self.sentry_tasks[symbol].cancel()
363
  del self.sentry_tasks[symbol]
364
 
365
  except Exception as e:
366
  print(f"❌ [Exit Error] {e}")
367
  traceback.print_exc()
 
 
368
 
369
  async def force_exit_by_manager(self, symbol, reason):
370
  p = await self.data_manager.get_latest_price_async(symbol)
371
  async with self.execution_lock:
372
  await self._execute_exit(symbol, p, reason)
373
-
 
 
 
 
 
374
  async def stop_sentry_loops(self):
375
  self.running = False
376
  for task in self.sentry_tasks.values(): task.cancel()
 
1
+ # trade_manager.py (V25.2 - GEM-Architect: Full Spot Edition with Oracle V4)
2
 
3
  import asyncio
4
  import uuid
5
  import time
6
  import traceback
7
+ from datetime import datetime
8
  from typing import List, Dict, Any
9
 
10
  class TradeManager:
 
32
  }
33
 
34
  self.execution_lock = asyncio.Lock()
35
+ print(f"🛡️ [TradeManager V25.2] Full Spot Commander Initialized.")
36
 
37
  async def initialize_sentry_exchanges(self):
38
  print("🛡️ [TradeManager] Syncing state with R2...")
 
48
  self.open_positions = {}
49
 
50
  # ==============================================================================
51
+ # 🎯 L4 Sniper Execution Logic (Oracle V4 Directed - Spot Only)
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
 
62
 
63
  for signal in oracle_approved_signals:
64
  symbol = signal['symbol']
 
65
 
66
+ # Spot Filter: نقبل فقط إشارات الشراء
67
+ if signal.get('action_type') != 'BUY':
68
+ continue
69
+
70
  if symbol in self.open_positions: continue
71
 
72
  # 1. جلب بيانات الدقيقة للقناص
 
73
  ohlcv_task = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
74
  ob_task = self.data_manager.get_order_book_snapshot(symbol)
75
  ohlcv_1m, order_book = await asyncio.gather(ohlcv_task, ob_task)
 
78
  print(f" -> [Sniper Skip] {symbol} (Insufficient 1m data).")
79
  continue
80
 
81
+ # 2. استشارة القناص
82
+ # نمرر له direction='LONG' دائماً لأننا في Spot
 
83
  sniper_result = await self.processor.check_sniper_entry(ohlcv_1m, order_book)
84
 
85
+ sniper_signal = sniper_result.get('signal', 'WAIT')
 
 
86
  sniper_conf = sniper_result.get('confidence_prob', 0.0)
87
 
88
+ # 3. التحقق من التوافق (Must be BUY/LONG)
89
+ if sniper_signal == 'BUY' and sniper_conf >= 0.60:
90
+ print(f" -> 🎯 [Sniper Confirmed] {symbol} | Oracle:BUY + Sniper:BUY")
 
 
 
 
 
 
 
 
 
91
 
92
+ signal['sniper_entry_price'] = sniper_result.get('entry_price', 0)
 
93
  signal['sniper_score'] = sniper_conf
94
  sniper_candidates.append(signal)
 
 
95
 
96
  if not sniper_candidates:
97
  return
 
109
  await self._execute_entry_from_signal(best_signal['symbol'], best_signal)
110
 
111
  # ==============================================================================
112
+ # 🚀 Entry Execution (With Oracle V4 Params)
113
  # ==============================================================================
114
  async def _execute_entry_from_signal(self, symbol, signal_data):
115
  try:
 
124
  current_capital = float(portfolio.get('current_capital_usd', 100.0))
125
  entry_fee_usd = current_capital * self.FEE_RATE
126
 
127
+ # --- [Oracle V4 Params] ---
128
+ # استخدام الأهداف الذكية القادمة من Oracle
129
+ # إذا لم تتوفر (Fallback)، نستخدم حسابات بسيطة
130
+ tp_price = float(signal_data.get('primary_tp', current_price * 1.05))
131
+ sl_price = float(signal_data.get('sl_price', current_price * 0.95))
132
  oracle_strength = float(signal_data.get('strength', 0.5))
133
  oracle_class = signal_data.get('target_class', 'TP2')
 
 
 
 
 
 
 
 
 
 
 
134
 
135
  new_trade = {
136
  'id': trade_id,
137
  'symbol': symbol,
138
  'entry_price': current_price,
139
+ 'direction': 'LONG', # Spot is always LONG
140
  'entry_time': datetime.now().isoformat(),
141
  'status': 'OPEN',
142
  'tp_price': tp_price,
143
  'sl_price': sl_price,
144
  'last_update': datetime.now().isoformat(),
145
+ 'last_oracle_check': datetime.now().isoformat(),
146
+ 'strategy': 'OracleV4_Spot',
147
 
148
+ # تخزين بيانات Oracle
149
  'initial_oracle_strength': oracle_strength,
150
  'initial_oracle_class': oracle_class,
151
  'oracle_tp_map': signal_data.get('tp_map', {}),
152
 
153
  'entry_capital': current_capital,
154
  'entry_fee_usd': entry_fee_usd,
155
+ 'l1_score': float(signal_data.get('enhanced_final_score', 0.0)),
156
+ 'decision_data': {
157
+ 'components': signal_data.get('components', {}),
158
+ 'oracle_conf': signal_data.get('confidence', 0)
159
+ }
160
  }
161
 
162
  self.open_positions[symbol] = new_trade
 
168
 
169
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
170
 
 
171
  if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel()
172
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
173
 
174
+ print(f"✅ [ENTRY] {symbol} @ {current_price} | TP: {tp_price} | SL: {sl_price} | Str: {oracle_strength:.2f}")
175
 
176
  except Exception as e:
177
  print(f"❌ [Entry Error] {symbol}: {e}")
 
181
  # 🛡️ Hybrid Sentry + Strategic Oracle Layer
182
  # ==============================================================================
183
  async def _guardian_loop(self, symbol: str):
184
+ print(f"🛡️ [Sentry] Guarding {symbol} (High/Low Simulation + Oracle)...")
185
+ last_ai_check_time = 0
186
 
187
  while self.running and symbol in self.open_positions:
188
  try:
 
191
  if not trade: break
192
 
193
  # -------------------------------------------------------------
194
+ # 1. محاكاة التلامس (High/Low Wicks Simulation)
195
  # -------------------------------------------------------------
 
196
  ohlcv = await self.data_manager.get_latest_ohlcv(symbol, '1m', limit=2)
197
+ current_ticker_price = await self.data_manager.get_latest_price_async(symbol)
198
+
199
+ if not ohlcv: continue
200
+
201
+ current_candle = ohlcv[-1]
202
+ candle_high = current_candle[2]
203
+ candle_low = current_candle[3]
204
 
205
+ max_price_seen = max(current_ticker_price, candle_high)
206
+ min_price_seen = min(current_ticker_price, candle_low)
207
+
208
+ # Spot Long Logic Check
209
+ if max_price_seen >= trade['tp_price']:
210
+ print(f"🎯 [TP HIT] Price {max_price_seen} touched Target {trade['tp_price']}")
211
+ async with self.execution_lock:
212
+ await self._execute_exit(symbol, trade['tp_price'], "TP_HIT")
213
+ break
214
+
215
+ if min_price_seen <= trade['sl_price']:
216
+ print(f"🛑 [SL HIT] Price {min_price_seen} touched Stop {trade['sl_price']}")
217
+ async with self.execution_lock:
218
+ await self._execute_exit(symbol, trade['sl_price'], "SL_HIT")
219
+ break
220
+
221
+ # -------------------------------------------------------------
222
+ # 2. الحارس الهجين (Hybrid Guardian - AI Check)
223
+ # -------------------------------------------------------------
224
+ if time.time() - last_ai_check_time > 60:
225
+ t1 = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
226
+ t5 = self.data_manager.get_latest_ohlcv(symbol, '5m', 500)
227
+ t15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 500)
228
+ d1, d5, d15 = await asyncio.gather(t1, t5, t15)
229
 
230
+ if d1 and d5 and d15 and len(d1) >= 500:
231
+ decision = self.processor.consult_guardian(d1, d5, d15, trade['entry_price'])
232
+ action = decision.get('action', 'HOLD')
233
+ scores = decision.get('scores', {})
234
+
235
+ if action in ['EXIT_HARD', 'EXIT_SOFT']:
236
+ print(f"🤖 [Guardian] {action}: {decision.get('reason')}")
237
+ async with self.execution_lock:
238
+ await self._execute_exit(symbol, current_ticker_price, f"AI_{action}", ai_scores=scores)
239
  break
240
+ last_ai_check_time = time.time()
241
 
242
+ self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
243
+
 
 
 
 
 
 
 
244
  # -------------------------------------------------------------
245
+ # 3. الطبقة الاستراتيجية (Oracle V4 Re-Evaluation) [NEW]
246
  # -------------------------------------------------------------
247
+ last_oracle_check = datetime.fromisoformat(trade.get('last_oracle_check', datetime.now().isoformat()))
248
+ if (datetime.now() - last_oracle_check).total_seconds() > self.ORACLE_CHECK_INTERVAL:
249
+ # تحديث التوقيت أولاً لمنع التكرار
 
 
 
 
 
250
  self.open_positions[symbol]['last_oracle_check'] = datetime.now().isoformat()
251
+ # استشارة الجنرال
252
+ await self._consult_oracle_strategy_update(symbol, trade)
253
 
254
  except asyncio.CancelledError: break
255
  except Exception as e:
 
258
 
259
  async def _consult_oracle_strategy_update(self, symbol, trade):
260
  """
261
+ Oracle V4 Strategy Update:
262
+ - هل تحول الاتجاه إلى هبوط؟ -> خروج.
263
+ - هل ضعفت القوة؟ -> تقليص الهدف.
264
  """
265
  try:
266
+ # 1. تجميع البيانات
267
+ # نحتاج محاكاة دورة تحليلية مصغرة للحصول على Scores + OHLCV
268
+ # نطلب من DataManager جلب البيانات الأساسية
269
+ tasks = [self.data_manager.get_latest_ohlcv(symbol, tf, limit=100) for tf in ["15m", "1h", "4h"]]
270
+ results = await asyncio.gather(*tasks)
271
+ ohlcv_data = {tf: res for tf, res in zip(["15m", "1h", "4h"], results) if res}
272
+
273
+ if '1h' not in ohlcv_data: return
274
+
275
+ curr_p = await self.data_manager.get_latest_price_async(symbol)
276
+ raw_data = {'symbol': symbol, 'ohlcv': ohlcv_data, 'current_price': curr_p}
277
 
278
+ # 2. تشغيل الطبقة الثانية (Titan/MC) للحصول على Scores محدثة
279
+ l2_analysis = await self.processor.process_compound_signal(raw_data)
280
+ if not l2_analysis: return
281
 
282
+ # 3. استشارة Oracle V4
283
+ oracle_verdict = await self.processor.consult_oracle(l2_analysis)
284
+
285
+ # 4. اتخاذ القرار
286
+ # الحالة أ: Oracle يقول WAIT أو يتوقع هبوط
287
+ # (تذكر: Oracle V4 يعيد WAIT إذا كان التوقع Short في Spot Mode)
288
+ if oracle_verdict.get('action') == 'WAIT' or oracle_verdict.get('direction') == 'SHORT':
289
+ print(f"🚨 [Oracle Command] Outlook turned Bearish for {symbol}. Exiting...")
290
+ await self.force_exit_by_manager(symbol, reason="Oracle_Bearish_Flip")
291
+ return
292
+
293
+ # الحالة ب: ضعف الزخم (Weakness)
294
  current_strength = oracle_verdict.get('strength', 0.5)
295
  initial_strength = trade.get('initial_oracle_strength', 0.5)
 
 
296
 
297
+ if current_strength < (initial_strength * 0.6):
298
+ print(f"⚠️ [Oracle Command] Momentum fading ({initial_strength:.2f}->{current_strength:.2f}). Lowering TP.")
 
 
 
 
 
 
 
 
 
 
 
 
299
 
 
 
300
  tp_map = trade.get('oracle_tp_map', {})
301
  conservative_tp = tp_map.get('TP1')
302
+ current_tp = trade['tp_price']
303
 
304
+ # تأكد أن الهدف الجديد أعلى من السعر الحالي (نحن في صفقة شراء)
305
+ if conservative_tp and conservative_tp > curr_p:
306
+ # تحديث الهدف إذا كان أقرب من الهدف الحالي
307
+ if conservative_tp < current_tp:
 
 
 
 
308
  self.open_positions[symbol]['tp_price'] = conservative_tp
309
  print(f" -> TP updated to {conservative_tp} (TP1)")
310
 
311
+ except Exception as e:
312
+ print(f"⚠️ [Oracle Re-Eval Error] {e}")
313
+
314
+ # ==============================================================================
315
+ # 👻 Ghost Monitor & Learning (Restored)
316
+ # ==============================================================================
317
+ def _launch_post_exit_analysis(self, symbol, exit_price, exit_time, position_size_usd, ai_scores=None, trade_obj=None):
318
+ asyncio.create_task(self._analyze_after_exit_task(symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj))
319
+
320
+ def _update_specific_stat(self, key, is_good, usd_impact):
321
+ if key not in self.ai_stats: return
322
+ self.ai_stats[key]["total"] += 1
323
+ if is_good:
324
+ self.ai_stats[key]["good"] += 1
325
+ self.ai_stats[key]["saved"] += abs(usd_impact)
326
+ else:
327
+ self.ai_stats[key]["missed"] += abs(usd_impact)
328
+
329
+ async def _analyze_after_exit_task(self, symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj):
330
+ await asyncio.sleep(900) # 15 min check
331
+ try:
332
+ curr = await self.data_manager.get_latest_price_async(symbol)
333
+ if curr == 0: return
334
+
335
+ # Spot Logic: الخروج الجيد هو عندما يهبط السعر بعد بيعنا
336
+ # Exit Price > Current Price => Good Exit (Saved money)
337
+ change_pct = (curr - exit_price) / exit_price
338
+ usd_impact = change_pct * position_size_usd
339
+ is_good_exit = change_pct < 0
340
+
341
+ self._update_specific_stat("hybrid", is_good_exit, usd_impact)
342
+ if ai_scores:
343
+ if ai_scores.get('v2', 0) >= 0.60: self._update_specific_stat("v2", is_good_exit, usd_impact)
344
+ if ai_scores.get('v3', 0) >= 0.75: self._update_specific_stat("v3", is_good_exit, usd_impact)
345
+
346
+ record = {"symbol": symbol, "exit_price": exit_price, "price_15m": curr, "usd_impact": usd_impact, "verdict": "SUCCESS" if is_good_exit else "MISS"}
347
+ await self.r2.append_deep_steward_audit(record)
348
+
349
+ if self.learning_hub and trade_obj:
350
+ trade_obj['pnl_percent'] = trade_obj.get('profit_pct', 0.0)
351
+ await self.learning_hub.analyze_trade_and_learn(trade_obj, trade_obj.get('exit_reason', 'UNKNOWN'))
352
 
353
  except Exception as e:
354
+ print(f"⚠️ [Ghost/Learning Error] {e}")
355
 
356
  # ==============================================================================
357
+ # 🔴 Exit Logic (Spot PnL)
358
  # ==============================================================================
359
+ async def _execute_exit(self, symbol, price, reason, ai_scores=None):
 
 
 
360
  if symbol not in self.open_positions: return
361
  try:
362
  trade = self.open_positions.pop(symbol)
363
  entry_price = float(trade['entry_price'])
364
  exit_price = float(price)
365
  entry_capital = float(trade.get('entry_capital', 100.0))
366
+ entry_fee_usd = float(trade.get('entry_fee_usd', 0.0))
367
 
368
+ # Spot PnL Logic
369
+ exit_value_gross = (exit_price / entry_price) * entry_capital
370
+ exit_fee_usd = exit_value_gross * self.FEE_RATE
371
+ net_exit_value = exit_value_gross - exit_fee_usd
372
+ net_pnl_usd = net_exit_value - entry_capital
 
 
 
 
 
373
  net_profit_pct = (net_pnl_usd / entry_capital) * 100
374
 
 
375
  trade.update({
376
+ 'status': 'CLOSED',
377
+ 'exit_price': exit_price,
378
+ 'exit_reason': reason,
379
+ 'profit_pct': net_profit_pct,
380
+ 'net_pnl_usd': net_pnl_usd,
381
+ 'fees_paid_usd': entry_fee_usd + exit_fee_usd
382
  })
383
+
384
+ portfolio = await self.r2.get_portfolio_state_async()
385
+ current_total_cap = float(portfolio.get('current_capital_usd', 100.0))
386
+ new_cap = current_total_cap + net_pnl_usd
387
 
388
+ portfolio['current_capital_usd'] = new_cap
389
+ portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
390
 
391
+ if net_pnl_usd >= 0:
392
+ portfolio['winning_trades'] = portfolio.get('winning_trades', 0) + 1
393
+ portfolio['total_profit_usd'] = portfolio.get('total_profit_usd', 0) + net_pnl_usd
394
+ trade['result'] = 'WIN'
395
+ else:
396
+ portfolio['losing_trades'] = portfolio.get('losing_trades', 0) + 1
397
+ portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0) + abs(net_pnl_usd)
398
+ trade['result'] = 'LOSS'
399
+
400
+ await self.r2.save_portfolio_state_async(portfolio)
401
+ await self.r2.save_open_trades_async(list(self.open_positions.values()))
402
+ await self.r2.append_to_closed_trades_history(trade)
403
 
404
+ print(f"✅ [EXIT] {symbol} | Net PnL: {net_profit_pct:.2f}% (${net_pnl_usd:.2f}) | {reason}")
405
+
406
+ self._launch_post_exit_analysis(symbol, exit_price, trade.get('exit_time'), entry_capital, ai_scores, trade)
407
+
408
+ if symbol in self.sentry_tasks:
409
  self.sentry_tasks[symbol].cancel()
410
  del self.sentry_tasks[symbol]
411
 
412
  except Exception as e:
413
  print(f"❌ [Exit Error] {e}")
414
  traceback.print_exc()
415
+ if symbol not in self.open_positions:
416
+ self.open_positions[symbol] = trade
417
 
418
  async def force_exit_by_manager(self, symbol, reason):
419
  p = await self.data_manager.get_latest_price_async(symbol)
420
  async with self.execution_lock:
421
  await self._execute_exit(symbol, p, reason)
422
+
423
+ async def start_sentry_loops(self):
424
+ for symbol in list(self.open_positions.keys()):
425
+ if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done():
426
+ self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
427
+
428
  async def stop_sentry_loops(self):
429
  self.running = False
430
  for task in self.sentry_tasks.values(): task.cancel()