Riy777 commited on
Commit
e3acf42
·
verified ·
1 Parent(s): 6f7ea8e

Update trade_manager.py

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