Riy777 commited on
Commit
3f02cf6
·
verified ·
1 Parent(s): 7bdf8d9

Update trade_manager.py

Browse files
Files changed (1) hide show
  1. trade_manager.py +101 -267
trade_manager.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================
2
- # 🛡️ trade_manager.py (V35.1 - GEM-Architect: Portfolio-Driven & Muzzled Guardians)
3
  # ============================================================
4
 
5
  import asyncio
@@ -9,7 +9,6 @@ import traceback
9
  from datetime import datetime
10
  from typing import List, Dict, Any
11
 
12
- # ✅ استيراد المحفظة الذكية
13
  from smart_portfolio import SmartPortfolio
14
 
15
  class TradeManager:
@@ -17,39 +16,26 @@ class TradeManager:
17
  self.r2 = r2_service
18
  self.data_manager = data_manager
19
  self.processor = processor
20
- self.learning_hub = None # سيتم حقن AdaptiveHub هنا من app.py
21
-
22
- # ✅ تهيئة المحفظة الذكية
23
  self.smart_portfolio = SmartPortfolio(r2_service, data_manager)
24
-
25
  self.open_positions = {}
26
  self.watchlist = {}
27
  self.sentry_tasks = {}
28
  self.running = True
29
-
30
- # متغير لتمرير الرسائل إلى الواجهة (UI Log Bridge)
31
  self.latest_guardian_log = "🛡️ Guardian & Portfolio Systems Online."
32
-
33
- # إعدادات الرسوم (لأغراض الحساب فقط، المحفظة تدير الخصم الفعلي)
34
  self.FEE_RATE = 0.001
35
-
36
- # إعدادات المراجعة الاستراتيجية (Oracle Re-check)
37
- self.ORACLE_CHECK_INTERVAL = 900 # كل 15 دقيقة
38
-
39
- # إحصائيات AI
40
  self.ai_stats = {
41
  "hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
42
  "crash": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
43
  "giveback": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
44
  "stagnation": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0}
45
  }
46
-
47
  self.execution_lock = asyncio.Lock()
48
- print(f"🛡️ [TradeManager V35.1] Portfolio-Driven Engine Online.")
49
 
50
  async def initialize_sentry_exchanges(self):
51
  print("🛡️ [TradeManager] Syncing state & Initializing Portfolio...")
52
- # تهيئة المحفظة الذكية أولاً
53
  await self.smart_portfolio.initialize()
54
  await self.sync_internal_state_with_r2()
55
 
@@ -58,65 +44,40 @@ class TradeManager:
58
  open_trades_list = await self.r2.get_open_trades_async()
59
  self.open_positions = {trade['symbol']: trade for trade in open_trades_list}
60
  print(f" -> [Sync] Recovered {len(self.open_positions)} active trades.")
61
-
62
- # 🔄 مزامنة رأس المال المحجوز مع المحفظة بناءً على الصفقات المستعادة
63
  total_allocated = sum(float(t.get('entry_capital', 0.0)) for t in self.open_positions.values())
64
  self.smart_portfolio.state["allocated_capital_usd"] = total_allocated
65
-
66
  except Exception as e:
67
  print(f"❌ [TradeManager] R2 Sync Failed: {e}")
68
  self.open_positions = {}
69
 
70
- # ==============================================================================
71
- # 🐕 Watchdog: Ensure Guardians are ALWAYS Running
72
- # ==============================================================================
73
  async def ensure_active_guardians(self):
74
- """تأكد من عمل حلقات الحراسة لكل صفقة مفتوحة"""
75
  active_symbols = list(self.open_positions.keys())
76
- if not active_symbols:
77
- return "💤 No active trades."
78
-
79
- restored_count = 0
80
- status_msgs = []
81
-
82
  for symbol in active_symbols:
83
  task = self.sentry_tasks.get(symbol)
84
  is_alive = task and not task.done()
85
-
86
  if not is_alive:
87
  print(f"🚨 [Watchdog] Found DEAD guardian for {symbol}. Resurrecting...")
88
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
89
  restored_count += 1
90
  status_msgs.append(f"♻️ Resurrected {symbol}")
91
- else:
92
- status_msgs.append(f"✅ {symbol} Running")
93
-
94
  if restored_count > 0:
95
  self.latest_guardian_log = f"⚠️ Watchdog restored: {', '.join(status_msgs)}"
96
  return f"⚠️ Watchdog restored {restored_count} guardians."
97
-
98
  return "✅ All guardians active."
99
 
100
- # ==============================================================================
101
- # 🎯 L4 Sniper Execution Logic
102
- # ==============================================================================
103
  async def select_and_execute_best_signal(self, oracle_approved_signals: List[Dict[str, Any]]):
104
- if not self.processor.initialized:
105
- await self.processor.initialize()
106
-
107
  sniper_candidates = []
108
  print(f"\n🔎 [Sniper] Scanning {len(oracle_approved_signals)} candidates...")
109
 
110
  for signal in oracle_approved_signals:
111
  symbol = signal['symbol']
112
-
113
- # Spot Filter
114
- if signal.get('action_type') != 'BUY':
115
- continue
116
-
117
  if symbol in self.open_positions: continue
118
 
119
- # 1. جلب بيانات الدقيقة للقناص
120
  ohlcv_task = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
121
  ob_task = self.data_manager.get_order_book_snapshot(symbol)
122
  ohlcv_1m, order_book = await asyncio.gather(ohlcv_task, ob_task)
@@ -125,18 +86,11 @@ class TradeManager:
125
  print(f" -> ⚠️ [Skip] {symbol}: Insufficient 1m data.")
126
  continue
127
 
128
- # 2. استشارة القناص
129
  sniper_result = await self.processor.check_sniper_entry(ohlcv_1m, order_book)
130
-
131
  sniper_signal = sniper_result.get('signal', 'WAIT')
132
  final_conf = sniper_result.get('confidence_prob', 0.0)
133
- reason = sniper_result.get('reason', 'N/A')
134
 
135
- ml_score = sniper_result.get('ml_score', 0.0)
136
- ob_score = sniper_result.get('ob_score', 0.0)
137
-
138
- log_msg = (f" -> 🔭 {symbol:<6} | Decision: {sniper_signal} | Score: {final_conf:.2f} "
139
- f"(ML:{ml_score:.2f} + OB:{ob_score:.2f}) | 📝 {reason}")
140
  print(log_msg)
141
 
142
  if sniper_signal == 'BUY':
@@ -149,92 +103,52 @@ class TradeManager:
149
  print(" -> 📉 No candidates passed the Sniper L4 check.")
150
  return
151
 
152
- # الترتيب حسب قوة الإشارة المجمعة
153
- sniper_candidates.sort(
154
- key=lambda x: (x.get('confidence', 0) + x.get('sniper_score', 0)),
155
- reverse=True
156
- )
157
-
158
  best_signal = sniper_candidates[0]
159
 
160
  async with self.execution_lock:
161
  print(f"🚀 [EXECUTING] Attempting entry for best candidate: {best_signal['symbol']}")
162
  await self._execute_entry_from_signal(best_signal['symbol'], best_signal)
163
 
164
- # ==============================================================================
165
- # 🚀 Entry Execution (Portfolio Integrated)
166
- # ==============================================================================
167
  async def _execute_entry_from_signal(self, symbol, signal_data):
168
  try:
169
- # 1. 💼 طلب الموافقة من المحفظة الذكية
170
- is_approved, plan = await self.smart_portfolio.request_entry_approval(
171
- signal_data,
172
- open_positions_count=len(self.open_positions)
173
- )
174
-
175
  if not is_approved:
176
  print(f"⛔ [Portfolio Rejection] {symbol}: {plan.get('reason')}")
177
  return
178
 
179
- # 2. استلام خطة الدخول المعتمدة
180
  approved_size_usd = plan['approved_size_usd']
181
  approved_tp = plan['approved_tp']
182
  target_label = plan.get('target_label', 'TP2')
183
  system_conf = plan.get('system_confidence', 0.5)
184
  market_mood = plan.get('market_mood', 'N/A')
185
-
186
  trade_id = str(uuid.uuid4())
187
-
188
  current_price = float(signal_data.get('sniper_entry_price', 0.0))
189
- if current_price <= 0.0:
190
- current_price = await self.data_manager.get_latest_price_async(symbol)
191
-
192
  entry_fee_usd = approved_size_usd * self.FEE_RATE
193
 
194
- # Params
195
- sl_price = float(signal_data.get('sl_price', current_price * 0.95))
196
- oracle_strength = float(signal_data.get('strength', 0.5))
197
- oracle_class = signal_data.get('target_class', 'TP2')
198
-
199
- target_class_int = 3
200
- if isinstance(oracle_class, str) and oracle_class.startswith('TP'):
201
- try: target_class_int = int(oracle_class[-1])
202
- except: pass
203
-
204
  new_trade = {
205
- 'id': trade_id,
206
- 'symbol': symbol,
207
- 'entry_price': current_price,
208
- 'direction': 'LONG',
209
- 'entry_time': datetime.now().isoformat(),
210
- 'status': 'OPEN',
211
- 'tp_price': approved_tp,
212
- 'sl_price': sl_price,
213
- 'last_update': datetime.now().isoformat(),
214
- 'last_oracle_check': datetime.now().isoformat(),
215
  'strategy': 'OracleV4_Hydra_Portfolio',
216
-
217
- 'initial_oracle_strength': oracle_strength,
218
- 'initial_oracle_class': oracle_class,
219
  'oracle_tp_map': signal_data.get('tp_map', {}),
220
-
221
- 'entry_capital': approved_size_usd,
222
- 'entry_fee_usd': entry_fee_usd,
223
  'l1_score': float(signal_data.get('enhanced_final_score', 0.0)),
224
- 'target_class_int': target_class_int,
225
  'decision_data': {
226
  'components': signal_data.get('components', {}),
227
  'oracle_conf': signal_data.get('confidence', 0),
228
- 'system_confidence': system_conf,
229
- 'market_mood': market_mood
230
  },
231
  'highest_price': current_price
232
  }
233
 
234
  self.open_positions[symbol] = new_trade
235
  if self.watchlist: self.watchlist.clear()
236
-
237
- # 3. 📝 تسجيل الصفقة في المحفظة (حجز رأس المال)
238
  await self.smart_portfolio.register_new_position(approved_size_usd)
239
 
240
  portfolio_state = await self.r2.get_portfolio_state_async()
@@ -243,8 +157,6 @@ class TradeManager:
243
  await self.r2.save_portfolio_state_async(portfolio_state)
244
 
245
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
246
-
247
- # Start Guardian Loop
248
  if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel()
249
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
250
 
@@ -254,59 +166,45 @@ class TradeManager:
254
  print(f"❌ [Entry Error] {symbol}: {e}")
255
  traceback.print_exc()
256
 
257
- # ==============================================================================
258
- # 🛡️ Dual-Core Guardian Loop (The Active Manager)
259
- # ==============================================================================
260
  async def _guardian_loop(self, symbol: str):
261
  print(f"🛡️ [Dual-Core] STARTING WATCH for {symbol}...")
262
  last_ai_check_time = 0
263
 
264
  while self.running:
265
- if symbol not in self.open_positions:
266
- break
267
-
268
  try:
269
  await asyncio.sleep(1)
270
  trade = self.open_positions.get(symbol)
271
- if not trade:
272
- break
273
 
274
  current_ticker_price = await self.data_manager.get_latest_price_async(symbol)
 
 
275
 
276
- if 'highest_price' not in trade:
277
- trade['highest_price'] = float(trade['entry_price'])
278
-
279
- if current_ticker_price > float(trade['highest_price']):
280
- trade['highest_price'] = current_ticker_price
281
-
282
- # Hard Limits
283
  if current_ticker_price >= trade['tp_price']:
284
- print(f"🎯 [TP HIT] Price {current_ticker_price} hit Target {trade['tp_price']}")
285
- async with self.execution_lock:
286
- await self._execute_exit(symbol, trade['tp_price'], "TP_HIT")
287
  break
288
-
289
  if current_ticker_price <= trade['sl_price']:
290
- print(f"🛑 [SL HIT] Price {current_ticker_price} hit Stop {trade['sl_price']}")
291
- async with self.execution_lock:
292
- await self._execute_exit(symbol, trade['sl_price'], "SL_HIT")
293
  break
294
 
295
- # Dual AI Check (Processor determines Policy)
296
  if time.time() - last_ai_check_time > 60:
297
  t1 = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
298
  t5 = self.data_manager.get_latest_ohlcv(symbol, '5m', 300)
299
  t15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 200)
300
-
301
- # ✅ GEM-Architect Update: Fetch Order Book for Validation
302
  tob = self.data_manager.get_order_book_snapshot(symbol)
303
 
304
- try:
305
- d1, d5, d15, d_ob = await asyncio.gather(t1, t5, t15, tob)
306
- except:
307
- continue
308
 
309
- if d1 and d5 and d15 and len(d1) >= 200:
 
 
 
 
 
310
  context_data = {
311
  'entry_price': trade['entry_price'],
312
  'tp_price': trade['tp_price'],
@@ -314,220 +212,156 @@ class TradeManager:
314
  'entry_time': trade['entry_time'],
315
  'oracle_conf': trade.get('decision_data', {}).get('oracle_conf', 0.8),
316
  'system_conf': trade.get('decision_data', {}).get('system_confidence', 0.8),
317
- 'l2_score': trade.get('l1_score', 0.7),
318
- 'target_class_int': trade.get('target_class_int', 3),
319
  'highest_price': float(trade['highest_price']),
320
- 'time_in_trade_mins': (datetime.now() - datetime.fromisoformat(trade['entry_time'])).total_seconds() / 60
 
321
  }
322
 
323
- # ✅ Pass order book (d_ob) to Processor
324
  decision = self.processor.consult_dual_guardians(symbol, d1, d5, d15, context_data, order_book_snapshot=d_ob)
325
-
326
  action = decision.get('action', 'HOLD')
327
  reason = decision.get('reason', '')
328
  ai_metrics = decision.get('probs') or decision.get('scores') or {}
329
 
330
- log_msg = f"🛡️ {action} | {reason}"
331
- self.latest_guardian_log = log_msg
332
 
333
  if action in ['EXIT_HARD', 'EXIT_SOFT']:
334
  print(f"🐲 [Dual-Core Trigger] {action}: {reason}")
335
  async with self.execution_lock:
336
  await self._execute_exit(symbol, current_ticker_price, f"DualGuard_{action}", ai_scores=ai_metrics)
337
  break
338
-
339
- elif action == 'TIGHTEN_SL':
340
- entry_p = float(trade['entry_price'])
341
- curr_sl = float(trade['sl_price'])
342
- if curr_sl < entry_p:
343
- print(f"🛡️ [Dual-Core] TIGHTEN_SL: Moving Stop to Entry {entry_p}")
344
- self.open_positions[symbol]['sl_price'] = entry_p
345
- self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
346
- await self.r2.save_open_trades_async(list(self.open_positions.values()))
347
-
348
- elif action == 'TRAIL_SL':
349
- entry_p = float(trade['entry_price'])
350
- curr_p = float(current_ticker_price)
351
- if curr_p > entry_p:
352
- potential_sl = entry_p + ((curr_p - entry_p) * 0.5)
353
- curr_sl = float(trade['sl_price'])
354
- if potential_sl > curr_sl:
355
- print(f"🛡️ [Dual-Core] TRAIL_SL: Moving Stop to {potential_sl:.4f}")
356
- self.open_positions[symbol]['sl_price'] = potential_sl
357
- self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
358
- await self.r2.save_open_trades_async(list(self.open_positions.values()))
359
- else:
360
- self.latest_guardian_log = f"📡 Gathering Deep Data (Need 200+ candles)..."
361
 
362
  last_ai_check_time = time.time()
363
  self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
364
 
365
- # 4. Oracle Re-Check
366
- last_oracle_check = datetime.fromisoformat(trade.get('last_oracle_check', datetime.now().isoformat()))
367
- if (datetime.now() - last_oracle_check).total_seconds() > self.ORACLE_CHECK_INTERVAL:
368
  self.open_positions[symbol]['last_oracle_check'] = datetime.now().isoformat()
369
  await self._consult_oracle_strategy_update(symbol, trade)
370
 
371
  except asyncio.CancelledError: break
372
  except Exception as e:
373
- print(f"❌ [Sentry Error] {symbol}: {e}")
374
- traceback.print_exc()
375
- await asyncio.sleep(5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
  async def _consult_oracle_strategy_update(self, symbol, trade):
378
  try:
379
  tasks = [self.data_manager.get_latest_ohlcv(symbol, tf, limit=100) for tf in ["15m", "1h", "4h"]]
380
  results = await asyncio.gather(*tasks)
381
  ohlcv_data = {tf: res for tf, res in zip(["15m", "1h", "4h"], results) if res}
382
-
383
  if '1h' not in ohlcv_data: return
384
-
385
- curr_p = await self.data_manager.get_latest_price_async(symbol)
386
- raw_data = {'symbol': symbol, 'ohlcv': ohlcv_data, 'current_price': curr_p}
387
 
388
- l2_analysis = await self.processor.process_compound_signal(raw_data)
389
- if not l2_analysis: return
390
-
391
- oracle_verdict = await self.processor.consult_oracle(l2_analysis)
392
 
393
- if oracle_verdict.get('action') == 'WAIT' or oracle_verdict.get('direction') == 'SHORT':
394
- print(f"🚨 [Oracle Command] Outlook turned Bearish for {symbol}. Exiting...")
 
395
  await self.force_exit_by_manager(symbol, reason="Oracle_Bearish_Flip")
396
  return
397
 
398
- current_strength = oracle_verdict.get('strength', 0.5)
399
- initial_strength = trade.get('initial_oracle_strength', 0.5)
400
-
401
- if current_strength < (initial_strength * 0.6):
402
- print(f"⚠️ [Oracle Command] Momentum fading. Lowering TP.")
403
  tp_map = trade.get('oracle_tp_map', {})
404
- conservative_tp = tp_map.get('TP1')
405
- current_tp = trade['tp_price']
406
- if conservative_tp and conservative_tp > curr_p and conservative_tp < current_tp:
407
- self.open_positions[symbol]['tp_price'] = conservative_tp
 
408
 
409
- except Exception as e:
410
- print(f"⚠️ [Oracle Re-Eval Error] {e}")
411
-
412
- # ==============================================================================
413
- # 👻 Ghost Monitor & Learning
414
- # ==============================================================================
415
  def _launch_post_exit_analysis(self, symbol, exit_price, exit_time, position_size_usd, ai_scores=None, trade_obj=None):
416
  asyncio.create_task(self._analyze_after_exit_task(symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj))
417
 
418
  def _update_specific_stat(self, key, is_good, usd_impact):
419
  if key not in self.ai_stats: return
420
  self.ai_stats[key]["total"] += 1
421
- if is_good:
422
- self.ai_stats[key]["good"] += 1
423
- self.ai_stats[key]["saved"] += abs(usd_impact)
424
- else:
425
- self.ai_stats[key]["missed"] += abs(usd_impact)
426
 
427
  async def _analyze_after_exit_task(self, symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj):
428
- await asyncio.sleep(900) # 15 min check
429
  try:
430
  curr = await self.data_manager.get_latest_price_async(symbol)
431
  if curr == 0: return
432
-
433
  change_pct = (curr - exit_price) / exit_price
434
  usd_impact = change_pct * position_size_usd
435
  is_good_exit = change_pct < 0
436
-
437
  self._update_specific_stat("hybrid", is_good_exit, usd_impact)
438
-
439
  if ai_scores:
440
- if ai_scores.get('crash', 0) >= 0.60:
441
- self._update_specific_stat("crash", is_good_exit, usd_impact)
442
- if ai_scores.get('giveback', 0) >= 0.70:
443
- self._update_specific_stat("giveback", is_good_exit, usd_impact)
444
- if ai_scores.get('stagnation', 0) >= 0.50:
445
- self._update_specific_stat("stagnation", is_good_exit, usd_impact)
446
-
447
  record = {"symbol": symbol, "exit_price": exit_price, "price_15m": curr, "usd_impact": usd_impact, "verdict": "SUCCESS" if is_good_exit else "MISS"}
448
  await self.r2.append_deep_steward_audit(record)
449
-
450
  if self.learning_hub and trade_obj:
451
  trade_obj['pnl_percent'] = trade_obj.get('profit_pct', 0.0)
452
  await self.learning_hub.register_trade_outcome(trade_obj)
 
453
 
454
- except Exception as e:
455
- print(f"⚠️ [Ghost/Learning Error] {e}")
456
-
457
- # ==============================================================================
458
- # 🔴 Exit Logic (Portfolio Integrated)
459
- # ==============================================================================
460
  async def _execute_exit(self, symbol, price, reason, ai_scores=None):
461
  if symbol not in self.open_positions: return
462
  try:
463
  trade = self.open_positions.pop(symbol)
464
- entry_price = float(trade['entry_price'])
465
- exit_price = float(price)
466
- entry_capital = float(trade.get('entry_capital', 100.0))
467
- entry_fee_usd = float(trade.get('entry_fee_usd', 0.0))
468
 
469
- exit_value_gross = (exit_price / entry_price) * entry_capital
470
- exit_fee_usd = exit_value_gross * self.FEE_RATE
471
- net_exit_value = exit_value_gross - exit_fee_usd
 
472
 
473
- # PnL calculations
474
- net_pnl_usd = net_exit_value - entry_capital
475
- net_profit_pct = (net_pnl_usd / entry_capital) * 100
476
 
477
- total_fees = entry_fee_usd + exit_fee_usd
478
 
479
- # 1. 💼 إبلاغ المحفظة الذكية لتحرير رأس المال وتحديث الرصيد
480
- await self.smart_portfolio.register_closed_position(entry_capital, net_pnl_usd, total_fees)
481
-
482
- trade.update({
483
- 'status': 'CLOSED',
484
- 'exit_price': exit_price,
485
- 'exit_reason': reason,
486
- 'profit_pct': net_profit_pct,
487
- 'net_pnl_usd': net_pnl_usd,
488
- 'fees_paid_usd': total_fees
489
- })
490
-
491
- # 2. 📊 تحديث الإحصائيات (Counters Only) في TradeManager
492
  portfolio = await self.r2.get_portfolio_state_async()
493
  portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
494
- if net_pnl_usd >= 0:
495
  portfolio['winning_trades'] = portfolio.get('winning_trades', 0) + 1
496
- portfolio['total_profit_usd'] = portfolio.get('total_profit_usd', 0) + net_pnl_usd
497
  trade['result'] = 'WIN'
498
  else:
499
  portfolio['losing_trades'] = portfolio.get('losing_trades', 0) + 1
500
- portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0) + abs(net_pnl_usd)
501
  trade['result'] = 'LOSS'
502
 
503
  await self.r2.save_portfolio_state_async(portfolio)
504
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
505
  await self.r2.append_to_closed_trades_history(trade)
506
 
507
- print(f"✅ [EXIT] {symbol} | Net PnL: {net_profit_pct:.2f}% (${net_pnl_usd:.2f}) | {reason}")
508
-
509
  self._launch_post_exit_analysis(symbol, exit_price, trade.get('exit_time'), entry_capital, ai_scores, trade)
510
-
511
  self.latest_guardian_log = f"✅ Closed {symbol} ({reason})"
512
-
513
- if symbol in self.sentry_tasks:
514
- self.sentry_tasks[symbol].cancel()
515
- del self.sentry_tasks[symbol]
516
 
517
  except Exception as e:
518
- print(f"❌ [Exit Error] {e}")
519
- traceback.print_exc()
520
- if symbol not in self.open_positions:
521
- self.open_positions[symbol] = trade
522
 
523
  async def force_exit_by_manager(self, symbol, reason):
524
  p = await self.data_manager.get_latest_price_async(symbol)
525
- async with self.execution_lock:
526
- await self._execute_exit(symbol, p, reason)
527
-
528
- async def start_sentry_loops(self):
529
- await self.ensure_active_guardians()
530
 
 
531
  async def stop_sentry_loops(self):
532
  self.running = False
533
  for task in self.sentry_tasks.values(): task.cancel()
 
1
  # ============================================================
2
+ # 🛡️ trade_manager.py (V35.2 - GEM-Architect: Volume Calculation)
3
  # ============================================================
4
 
5
  import asyncio
 
9
  from datetime import datetime
10
  from typing import List, Dict, Any
11
 
 
12
  from smart_portfolio import SmartPortfolio
13
 
14
  class TradeManager:
 
16
  self.r2 = r2_service
17
  self.data_manager = data_manager
18
  self.processor = processor
19
+ self.learning_hub = None
 
 
20
  self.smart_portfolio = SmartPortfolio(r2_service, data_manager)
 
21
  self.open_positions = {}
22
  self.watchlist = {}
23
  self.sentry_tasks = {}
24
  self.running = True
 
 
25
  self.latest_guardian_log = "🛡️ Guardian & Portfolio Systems Online."
 
 
26
  self.FEE_RATE = 0.001
27
+ self.ORACLE_CHECK_INTERVAL = 900
 
 
 
 
28
  self.ai_stats = {
29
  "hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
30
  "crash": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
31
  "giveback": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
32
  "stagnation": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0}
33
  }
 
34
  self.execution_lock = asyncio.Lock()
35
+ print(f"🛡️ [TradeManager V35.2] 30m-Depth Aware Engine Online.")
36
 
37
  async def initialize_sentry_exchanges(self):
38
  print("🛡️ [TradeManager] Syncing state & Initializing Portfolio...")
 
39
  await self.smart_portfolio.initialize()
40
  await self.sync_internal_state_with_r2()
41
 
 
44
  open_trades_list = await self.r2.get_open_trades_async()
45
  self.open_positions = {trade['symbol']: trade for trade in open_trades_list}
46
  print(f" -> [Sync] Recovered {len(self.open_positions)} active trades.")
 
 
47
  total_allocated = sum(float(t.get('entry_capital', 0.0)) for t in self.open_positions.values())
48
  self.smart_portfolio.state["allocated_capital_usd"] = total_allocated
 
49
  except Exception as e:
50
  print(f"❌ [TradeManager] R2 Sync Failed: {e}")
51
  self.open_positions = {}
52
 
 
 
 
53
  async def ensure_active_guardians(self):
 
54
  active_symbols = list(self.open_positions.keys())
55
+ if not active_symbols: return "💤 No active trades."
56
+ restored_count = 0; status_msgs = []
 
 
 
 
57
  for symbol in active_symbols:
58
  task = self.sentry_tasks.get(symbol)
59
  is_alive = task and not task.done()
 
60
  if not is_alive:
61
  print(f"🚨 [Watchdog] Found DEAD guardian for {symbol}. Resurrecting...")
62
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
63
  restored_count += 1
64
  status_msgs.append(f"♻️ Resurrected {symbol}")
65
+ else: status_msgs.append(f"✅ {symbol} Running")
 
 
66
  if restored_count > 0:
67
  self.latest_guardian_log = f"⚠️ Watchdog restored: {', '.join(status_msgs)}"
68
  return f"⚠️ Watchdog restored {restored_count} guardians."
 
69
  return "✅ All guardians active."
70
 
 
 
 
71
  async def select_and_execute_best_signal(self, oracle_approved_signals: List[Dict[str, Any]]):
72
+ if not self.processor.initialized: await self.processor.initialize()
 
 
73
  sniper_candidates = []
74
  print(f"\n🔎 [Sniper] Scanning {len(oracle_approved_signals)} candidates...")
75
 
76
  for signal in oracle_approved_signals:
77
  symbol = signal['symbol']
78
+ if signal.get('action_type') != 'BUY': continue
 
 
 
 
79
  if symbol in self.open_positions: continue
80
 
 
81
  ohlcv_task = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
82
  ob_task = self.data_manager.get_order_book_snapshot(symbol)
83
  ohlcv_1m, order_book = await asyncio.gather(ohlcv_task, ob_task)
 
86
  print(f" -> ⚠️ [Skip] {symbol}: Insufficient 1m data.")
87
  continue
88
 
 
89
  sniper_result = await self.processor.check_sniper_entry(ohlcv_1m, order_book)
 
90
  sniper_signal = sniper_result.get('signal', 'WAIT')
91
  final_conf = sniper_result.get('confidence_prob', 0.0)
 
92
 
93
+ log_msg = (f" -> 🔭 {symbol:<6} | Decision: {sniper_signal} | Score: {final_conf:.2f} | 📝 {sniper_result.get('reason','N/A')}")
 
 
 
 
94
  print(log_msg)
95
 
96
  if sniper_signal == 'BUY':
 
103
  print(" -> 📉 No candidates passed the Sniper L4 check.")
104
  return
105
 
106
+ sniper_candidates.sort(key=lambda x: (x.get('confidence', 0) + x.get('sniper_score', 0)), reverse=True)
 
 
 
 
 
107
  best_signal = sniper_candidates[0]
108
 
109
  async with self.execution_lock:
110
  print(f"🚀 [EXECUTING] Attempting entry for best candidate: {best_signal['symbol']}")
111
  await self._execute_entry_from_signal(best_signal['symbol'], best_signal)
112
 
 
 
 
113
  async def _execute_entry_from_signal(self, symbol, signal_data):
114
  try:
115
+ is_approved, plan = await self.smart_portfolio.request_entry_approval(signal_data, len(self.open_positions))
 
 
 
 
 
116
  if not is_approved:
117
  print(f"⛔ [Portfolio Rejection] {symbol}: {plan.get('reason')}")
118
  return
119
 
 
120
  approved_size_usd = plan['approved_size_usd']
121
  approved_tp = plan['approved_tp']
122
  target_label = plan.get('target_label', 'TP2')
123
  system_conf = plan.get('system_confidence', 0.5)
124
  market_mood = plan.get('market_mood', 'N/A')
 
125
  trade_id = str(uuid.uuid4())
 
126
  current_price = float(signal_data.get('sniper_entry_price', 0.0))
127
+ if current_price <= 0.0: current_price = await self.data_manager.get_latest_price_async(symbol)
 
 
128
  entry_fee_usd = approved_size_usd * self.FEE_RATE
129
 
 
 
 
 
 
 
 
 
 
 
130
  new_trade = {
131
+ 'id': trade_id, 'symbol': symbol, 'entry_price': current_price, 'direction': 'LONG',
132
+ 'entry_time': datetime.now().isoformat(), 'status': 'OPEN',
133
+ 'tp_price': approved_tp, 'sl_price': float(signal_data.get('sl_price', current_price * 0.95)),
134
+ 'last_update': datetime.now().isoformat(), 'last_oracle_check': datetime.now().isoformat(),
 
 
 
 
 
 
135
  'strategy': 'OracleV4_Hydra_Portfolio',
136
+ 'initial_oracle_strength': float(signal_data.get('strength', 0.5)),
137
+ 'initial_oracle_class': signal_data.get('target_class', 'TP2'),
 
138
  'oracle_tp_map': signal_data.get('tp_map', {}),
139
+ 'entry_capital': approved_size_usd, 'entry_fee_usd': entry_fee_usd,
 
 
140
  'l1_score': float(signal_data.get('enhanced_final_score', 0.0)),
141
+ 'target_class_int': 3,
142
  'decision_data': {
143
  'components': signal_data.get('components', {}),
144
  'oracle_conf': signal_data.get('confidence', 0),
145
+ 'system_confidence': system_conf, 'market_mood': market_mood
 
146
  },
147
  'highest_price': current_price
148
  }
149
 
150
  self.open_positions[symbol] = new_trade
151
  if self.watchlist: self.watchlist.clear()
 
 
152
  await self.smart_portfolio.register_new_position(approved_size_usd)
153
 
154
  portfolio_state = await self.r2.get_portfolio_state_async()
 
157
  await self.r2.save_portfolio_state_async(portfolio_state)
158
 
159
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
 
 
160
  if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel()
161
  self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
162
 
 
166
  print(f"❌ [Entry Error] {symbol}: {e}")
167
  traceback.print_exc()
168
 
 
 
 
169
  async def _guardian_loop(self, symbol: str):
170
  print(f"🛡️ [Dual-Core] STARTING WATCH for {symbol}...")
171
  last_ai_check_time = 0
172
 
173
  while self.running:
174
+ if symbol not in self.open_positions: break
 
 
175
  try:
176
  await asyncio.sleep(1)
177
  trade = self.open_positions.get(symbol)
178
+ if not trade: break
 
179
 
180
  current_ticker_price = await self.data_manager.get_latest_price_async(symbol)
181
+ if 'highest_price' not in trade: trade['highest_price'] = float(trade['entry_price'])
182
+ if current_ticker_price > float(trade['highest_price']): trade['highest_price'] = current_ticker_price
183
 
 
 
 
 
 
 
 
184
  if current_ticker_price >= trade['tp_price']:
185
+ print(f"🎯 [TP HIT] {symbol} @ {current_ticker_price}")
186
+ async with self.execution_lock: await self._execute_exit(symbol, trade['tp_price'], "TP_HIT")
 
187
  break
 
188
  if current_ticker_price <= trade['sl_price']:
189
+ print(f"🛑 [SL HIT] {symbol} @ {current_ticker_price}")
190
+ async with self.execution_lock: await self._execute_exit(symbol, trade['sl_price'], "SL_HIT")
 
191
  break
192
 
 
193
  if time.time() - last_ai_check_time > 60:
194
  t1 = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
195
  t5 = self.data_manager.get_latest_ohlcv(symbol, '5m', 300)
196
  t15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 200)
 
 
197
  tob = self.data_manager.get_order_book_snapshot(symbol)
198
 
199
+ try: d1, d5, d15, d_ob = await asyncio.gather(t1, t5, t15, tob)
200
+ except: continue
 
 
201
 
202
+ if d1 and d5 and d15 and len(d5) >= 6:
203
+ # ✅ Calculate 30m Volume (Sum of last 6 candles of 5m)
204
+ # candle format: [ts, o, h, l, c, vol]
205
+ last_6_5m = d5[-6:]
206
+ vol_30m_sum = sum([float(c[5]) * float(c[4]) for c in last_6_5m]) # Approx Volume in USD
207
+
208
  context_data = {
209
  'entry_price': trade['entry_price'],
210
  'tp_price': trade['tp_price'],
 
212
  'entry_time': trade['entry_time'],
213
  'oracle_conf': trade.get('decision_data', {}).get('oracle_conf', 0.8),
214
  'system_conf': trade.get('decision_data', {}).get('system_confidence', 0.8),
 
 
215
  'highest_price': float(trade['highest_price']),
216
+ 'time_in_trade_mins': (datetime.now() - datetime.fromisoformat(trade['entry_time'])).total_seconds() / 60,
217
+ 'volume_30m_usd': vol_30m_sum # ✅ Pass volume info
218
  }
219
 
 
220
  decision = self.processor.consult_dual_guardians(symbol, d1, d5, d15, context_data, order_book_snapshot=d_ob)
 
221
  action = decision.get('action', 'HOLD')
222
  reason = decision.get('reason', '')
223
  ai_metrics = decision.get('probs') or decision.get('scores') or {}
224
 
225
+ self.latest_guardian_log = f"🛡️ {action} | {reason}"
 
226
 
227
  if action in ['EXIT_HARD', 'EXIT_SOFT']:
228
  print(f"🐲 [Dual-Core Trigger] {action}: {reason}")
229
  async with self.execution_lock:
230
  await self._execute_exit(symbol, current_ticker_price, f"DualGuard_{action}", ai_scores=ai_metrics)
231
  break
232
+ elif action in ['TIGHTEN_SL', 'TRAIL_SL']:
233
+ await self._handle_sl_update(symbol, action, trade, current_ticker_price)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
  last_ai_check_time = time.time()
236
  self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
237
 
238
+ # Oracle Re-Check
239
+ last_oracle = datetime.fromisoformat(trade.get('last_oracle_check', datetime.now().isoformat()))
240
+ if (datetime.now() - last_oracle).total_seconds() > self.ORACLE_CHECK_INTERVAL:
241
  self.open_positions[symbol]['last_oracle_check'] = datetime.now().isoformat()
242
  await self._consult_oracle_strategy_update(symbol, trade)
243
 
244
  except asyncio.CancelledError: break
245
  except Exception as e:
246
+ print(f"❌ [Sentry Error] {symbol}: {e}"); traceback.print_exc(); await asyncio.sleep(5)
247
+
248
+ async def _handle_sl_update(self, symbol, action, trade, current_price):
249
+ if action == 'TIGHTEN_SL':
250
+ entry_p = float(trade['entry_price'])
251
+ if float(trade['sl_price']) < entry_p:
252
+ print(f"🛡️ [Dual-Core] TIGHTEN_SL -> Entry {entry_p}")
253
+ self.open_positions[symbol]['sl_price'] = entry_p
254
+ await self.r2.save_open_trades_async(list(self.open_positions.values()))
255
+
256
+ elif action == 'TRAIL_SL':
257
+ entry_p = float(trade['entry_price'])
258
+ if current_price > entry_p:
259
+ potential_sl = entry_p + ((current_price - entry_p) * 0.5)
260
+ if potential_sl > float(trade['sl_price']):
261
+ print(f"🛡️ [Dual-Core] TRAIL_SL -> {potential_sl:.4f}")
262
+ self.open_positions[symbol]['sl_price'] = potential_sl
263
+ await self.r2.save_open_trades_async(list(self.open_positions.values()))
264
 
265
  async def _consult_oracle_strategy_update(self, symbol, trade):
266
  try:
267
  tasks = [self.data_manager.get_latest_ohlcv(symbol, tf, limit=100) for tf in ["15m", "1h", "4h"]]
268
  results = await asyncio.gather(*tasks)
269
  ohlcv_data = {tf: res for tf, res in zip(["15m", "1h", "4h"], results) if res}
 
270
  if '1h' not in ohlcv_data: return
 
 
 
271
 
272
+ curr_p = await self.data_manager.get_latest_price_async(symbol)
273
+ l2 = await self.processor.process_compound_signal({'symbol': symbol, 'ohlcv': ohlcv_data, 'current_price': curr_p})
274
+ if not l2: return
 
275
 
276
+ oracle = await self.processor.consult_oracle(l2)
277
+ if oracle.get('action') == 'WAIT' or oracle.get('direction') == 'SHORT':
278
+ print(f"🚨 [Oracle] Outlook Bearish. Exiting {symbol}...")
279
  await self.force_exit_by_manager(symbol, reason="Oracle_Bearish_Flip")
280
  return
281
 
282
+ if oracle.get('strength', 0.5) < (trade.get('initial_oracle_strength', 0.5) * 0.6):
 
 
 
 
283
  tp_map = trade.get('oracle_tp_map', {})
284
+ cons_tp = tp_map.get('TP1')
285
+ if cons_tp and cons_tp > curr_p and cons_tp < trade['tp_price']:
286
+ print(f"⚠️ [Oracle] Weakening. Lowering TP to {cons_tp}")
287
+ self.open_positions[symbol]['tp_price'] = cons_tp
288
+ except Exception: pass
289
 
 
 
 
 
 
 
290
  def _launch_post_exit_analysis(self, symbol, exit_price, exit_time, position_size_usd, ai_scores=None, trade_obj=None):
291
  asyncio.create_task(self._analyze_after_exit_task(symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj))
292
 
293
  def _update_specific_stat(self, key, is_good, usd_impact):
294
  if key not in self.ai_stats: return
295
  self.ai_stats[key]["total"] += 1
296
+ if is_good: self.ai_stats[key]["good"] += 1; self.ai_stats[key]["saved"] += abs(usd_impact)
297
+ else: self.ai_stats[key]["missed"] += abs(usd_impact)
 
 
 
298
 
299
  async def _analyze_after_exit_task(self, symbol, exit_price, exit_time, position_size_usd, ai_scores, trade_obj):
300
+ await asyncio.sleep(900)
301
  try:
302
  curr = await self.data_manager.get_latest_price_async(symbol)
303
  if curr == 0: return
 
304
  change_pct = (curr - exit_price) / exit_price
305
  usd_impact = change_pct * position_size_usd
306
  is_good_exit = change_pct < 0
 
307
  self._update_specific_stat("hybrid", is_good_exit, usd_impact)
 
308
  if ai_scores:
309
+ if ai_scores.get('crash', 0) >= 0.60: self._update_specific_stat("crash", is_good_exit, usd_impact)
310
+ if ai_scores.get('giveback', 0) >= 0.70: self._update_specific_stat("giveback", is_good_exit, usd_impact)
311
+ if ai_scores.get('stagnation', 0) >= 0.50: self._update_specific_stat("stagnation", is_good_exit, usd_impact)
312
+
 
 
 
313
  record = {"symbol": symbol, "exit_price": exit_price, "price_15m": curr, "usd_impact": usd_impact, "verdict": "SUCCESS" if is_good_exit else "MISS"}
314
  await self.r2.append_deep_steward_audit(record)
 
315
  if self.learning_hub and trade_obj:
316
  trade_obj['pnl_percent'] = trade_obj.get('profit_pct', 0.0)
317
  await self.learning_hub.register_trade_outcome(trade_obj)
318
+ except Exception: pass
319
 
 
 
 
 
 
 
320
  async def _execute_exit(self, symbol, price, reason, ai_scores=None):
321
  if symbol not in self.open_positions: return
322
  try:
323
  trade = self.open_positions.pop(symbol)
324
+ entry_price = float(trade['entry_price']); exit_price = float(price)
325
+ entry_capital = float(trade.get('entry_capital', 100.0)); entry_fee = float(trade.get('entry_fee_usd', 0.0))
 
 
326
 
327
+ exit_val = (exit_price / entry_price) * entry_capital
328
+ exit_fee = exit_val * self.FEE_RATE
329
+ net_pnl = (exit_val - exit_fee) - entry_capital
330
+ total_fees = entry_fee + exit_fee
331
 
332
+ await self.smart_portfolio.register_closed_position(entry_capital, net_pnl, total_fees)
 
 
333
 
334
+ trade.update({'status': 'CLOSED', 'exit_price': exit_price, 'exit_reason': reason, 'profit_pct': (net_pnl/entry_capital)*100, 'net_pnl_usd': net_pnl, 'fees_paid_usd': total_fees})
335
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  portfolio = await self.r2.get_portfolio_state_async()
337
  portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
338
+ if net_pnl >= 0:
339
  portfolio['winning_trades'] = portfolio.get('winning_trades', 0) + 1
340
+ portfolio['total_profit_usd'] = portfolio.get('total_profit_usd', 0) + net_pnl
341
  trade['result'] = 'WIN'
342
  else:
343
  portfolio['losing_trades'] = portfolio.get('losing_trades', 0) + 1
344
+ portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0) + abs(net_pnl)
345
  trade['result'] = 'LOSS'
346
 
347
  await self.r2.save_portfolio_state_async(portfolio)
348
  await self.r2.save_open_trades_async(list(self.open_positions.values()))
349
  await self.r2.append_to_closed_trades_history(trade)
350
 
351
+ print(f"✅ [EXIT] {symbol} | Net PnL: {(net_pnl/entry_capital)*100:.2f}% (${net_pnl:.2f}) | {reason}")
 
352
  self._launch_post_exit_analysis(symbol, exit_price, trade.get('exit_time'), entry_capital, ai_scores, trade)
 
353
  self.latest_guardian_log = f"✅ Closed {symbol} ({reason})"
354
+ if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel(); del self.sentry_tasks[symbol]
 
 
 
355
 
356
  except Exception as e:
357
+ print(f"❌ [Exit Error] {e}"); traceback.print_exc()
358
+ if symbol not in self.open_positions: self.open_positions[symbol] = trade
 
 
359
 
360
  async def force_exit_by_manager(self, symbol, reason):
361
  p = await self.data_manager.get_latest_price_async(symbol)
362
+ async with self.execution_lock: await self._execute_exit(symbol, p, reason)
 
 
 
 
363
 
364
+ async def start_sentry_loops(self): await self.ensure_active_guardians()
365
  async def stop_sentry_loops(self):
366
  self.running = False
367
  for task in self.sentry_tasks.values(): task.cancel()