Spaces:
Paused
Paused
Update trade_manager.py
Browse files- trade_manager.py +55 -79
trade_manager.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
-
# trade_manager.py (V22.
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import uuid
|
|
|
|
| 5 |
import traceback
|
| 6 |
from datetime import datetime
|
| 7 |
from typing import List, Dict, Any
|
|
@@ -26,22 +27,18 @@ class TradeManager:
|
|
| 26 |
self.sentry_tasks = {}
|
| 27 |
self.running = True
|
| 28 |
|
| 29 |
-
# [ 🧠 IQ Stats ] مخزن الإحصائيات
|
| 30 |
-
# Hybrid: كل الصفقات التي أغلقها الذكاء الاصطناعي
|
| 31 |
-
# V2: إحصائيات خاصة عندما كان V2 مرتفعاً
|
| 32 |
-
# V3: إحصائيات خاصة عندما كان V3 مرتفعاً
|
| 33 |
self.ai_stats = {
|
| 34 |
"hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
|
| 35 |
"v2": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
|
| 36 |
"v3": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0}
|
| 37 |
}
|
| 38 |
|
| 39 |
-
# للإبقاء على التوافق مع الكود القديم الذي قد يطلب ds_stats مباشرة
|
| 40 |
self.ds_stats = self.ai_stats["hybrid"]
|
| 41 |
|
| 42 |
self.execution_lock = asyncio.Lock()
|
| 43 |
|
| 44 |
-
print(f"🛡️ [TradeManager V22.
|
| 45 |
|
| 46 |
async def initialize_sentry_exchanges(self):
|
| 47 |
print("🛡️ [TradeManager] Initializing and syncing state with R2...")
|
|
@@ -195,14 +192,12 @@ class TradeManager:
|
|
| 195 |
self.watchlist.clear()
|
| 196 |
|
| 197 |
# ============================================================
|
| 198 |
-
# 🕒 تسجيل وقت أول صفقة
|
| 199 |
# ============================================================
|
| 200 |
portfolio = await self.r2.get_portfolio_state_async()
|
| 201 |
if portfolio.get('first_trade_timestamp') is None:
|
| 202 |
portfolio['first_trade_timestamp'] = new_trade['entry_time']
|
| 203 |
await self.r2.save_portfolio_state_async(portfolio)
|
| 204 |
-
print(f"🕒 [System Info] تم تسجيل توقيت أول صفقة في النظام: {new_trade['entry_time']}")
|
| 205 |
-
# ============================================================
|
| 206 |
|
| 207 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 208 |
|
|
@@ -212,7 +207,6 @@ class TradeManager:
|
|
| 212 |
self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
|
| 213 |
|
| 214 |
print(f"✅ [ENTRY EXECUTED] {symbol} | Price: {current_price:.8f} | TP: {tp_price:.8f} | SL: {sl_price:.8f}")
|
| 215 |
-
print(f" -> L2 Score: {new_trade['l1_score']:.2f}, L4 Conf: {new_trade['l2_sniper_confidence']:.2f}")
|
| 216 |
|
| 217 |
except Exception as e:
|
| 218 |
print(f"❌ [TradeManager] فشل فادح أثناء تنفيذ الدخول لـ {symbol}: {e}")
|
|
@@ -266,15 +260,18 @@ class TradeManager:
|
|
| 266 |
await self.select_and_execute_best_signal(signals_from_watchlist)
|
| 267 |
|
| 268 |
# ==============================================================================
|
| 269 |
-
# 🛡️ دوال حارس الخروج (Hybrid Sentry)
|
| 270 |
# ==============================================================================
|
| 271 |
async def _guardian_loop(self, symbol: str):
|
| 272 |
-
print(f"🛡️ [Sentry Activated] بدء الحراسة لـ {symbol} (
|
|
|
|
|
|
|
| 273 |
|
| 274 |
while self.running and symbol in self.open_positions:
|
| 275 |
try:
|
| 276 |
-
#
|
| 277 |
-
|
|
|
|
| 278 |
|
| 279 |
trade = self.open_positions.get(symbol)
|
| 280 |
if not trade: break
|
|
@@ -282,71 +279,66 @@ class TradeManager:
|
|
| 282 |
current_price = await self.data_manager.get_latest_price_async(symbol)
|
| 283 |
if current_price == 0.0: continue
|
| 284 |
|
| 285 |
-
#
|
| 286 |
if current_price >= trade['tp_price']:
|
| 287 |
-
print(f"✅ [Sentry
|
| 288 |
async with self.execution_lock:
|
| 289 |
await self._execute_exit(symbol, current_price, "TP_HIT_HARD")
|
| 290 |
break
|
| 291 |
|
| 292 |
if current_price <= trade['sl_price']:
|
| 293 |
-
print(f"🛑 [Sentry
|
| 294 |
async with self.execution_lock:
|
| 295 |
await self._execute_exit(symbol, current_price, "SL_HIT_HARD")
|
| 296 |
break
|
| 297 |
|
| 298 |
-
#
|
| 299 |
-
if
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
d1, d5, d15 = await asyncio.gather(task1, task5, task15)
|
| 306 |
-
|
| 307 |
-
# التأكد من وجود بيانات كافية (V2 يحتاج 64 شمعة، المؤشرات تحتاج 200)
|
| 308 |
-
if d1 and d5 and d15 and len(d1) >= 200:
|
| 309 |
-
# استشارة الحارس الهجين
|
| 310 |
-
decision = self.deep_steward.analyze_position(d1, d5, d15, trade['entry_price'])
|
| 311 |
-
action = decision.get('action', 'HOLD')
|
| 312 |
-
scores = decision.get('scores', {}) # درجات V2 و V3 للتدقيق
|
| 313 |
|
| 314 |
-
|
| 315 |
-
print(f"🤖 [Hybrid Guardian] 🚨 خروج طارئ! {decision['reason']}")
|
| 316 |
-
async with self.execution_lock:
|
| 317 |
-
await self._execute_exit(symbol, current_price, "AI_HARD_EXIT", ai_scores=scores)
|
| 318 |
-
break
|
| 319 |
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
|
| 334 |
except asyncio.CancelledError:
|
| 335 |
print(f"🛡️ [Sentry STOP] تم إيقاف الحارس لـ {symbol}.")
|
| 336 |
break
|
| 337 |
except Exception as e:
|
| 338 |
print(f"❌ [Sentry ERROR] فشل مؤقت لـ {symbol}: {e}")
|
| 339 |
-
await asyncio.sleep(
|
| 340 |
|
| 341 |
# ==============================================================================
|
| 342 |
-
# 👻 المراقب الشبحي (Ghost Monitor)
|
| 343 |
# ==============================================================================
|
| 344 |
def _launch_post_exit_analysis(self, symbol, exit_price, exit_time, position_size_usd, ai_scores=None):
|
| 345 |
-
"""يطلق مهمة خلفية لمراقبة السعر بعد الخروج وحساب الأثر بالدولار"""
|
| 346 |
asyncio.create_task(self._analyze_after_exit_task(symbol, exit_price, exit_time, position_size_usd, ai_scores))
|
| 347 |
|
| 348 |
def _update_specific_stat(self, key, is_good, usd_impact):
|
| 349 |
-
"""دالة مساعدة لتحديث إحصائيات محفظة معينة (Hybrid, V2, V3)"""
|
| 350 |
if key not in self.ai_stats: return
|
| 351 |
self.ai_stats[key]["total"] += 1
|
| 352 |
if is_good:
|
|
@@ -356,41 +348,36 @@ class TradeManager:
|
|
| 356 |
self.ai_stats[key]["missed"] += abs(usd_impact)
|
| 357 |
|
| 358 |
async def _analyze_after_exit_task(self, symbol, exit_price, exit_time, position_size_usd, ai_scores):
|
| 359 |
-
# ⏳ ننتظر 15 دقيقة (900 ثانية)
|
| 360 |
await asyncio.sleep(900)
|
| 361 |
|
| 362 |
try:
|
| 363 |
current_price = await self.data_manager.get_latest_price_async(symbol)
|
| 364 |
if current_price == 0: return
|
| 365 |
|
| 366 |
-
# هل كان الخروج صحيحاً؟
|
| 367 |
change_pct = (current_price - exit_price) / exit_price
|
| 368 |
usd_impact = change_pct * position_size_usd
|
| 369 |
-
is_good_exit = change_pct < 0
|
| 370 |
|
| 371 |
-
# 1. تحديث
|
| 372 |
self._update_specific_stat("hybrid", is_good_exit, usd_impact)
|
| 373 |
|
| 374 |
-
# 2. ت
|
| 375 |
if ai_scores:
|
| 376 |
v2_score = ai_scores.get('v2', 0.0)
|
| 377 |
v3_score = ai_scores.get('v3', 0.0)
|
| 378 |
|
| 379 |
-
|
| 380 |
-
if v2_score >= 0.60:
|
| 381 |
self._update_specific_stat("v2", is_good_exit, usd_impact)
|
| 382 |
|
| 383 |
-
|
| 384 |
-
if v3_score >= 0.75:
|
| 385 |
self._update_specific_stat("v3", is_good_exit, usd_impact)
|
| 386 |
|
| 387 |
-
# إعداد السجل لـ R2
|
| 388 |
audit_record = {
|
| 389 |
"symbol": symbol,
|
| 390 |
"exit_time": exit_time,
|
| 391 |
"exit_price": exit_price,
|
| 392 |
-
"price_15m_later": current_price,
|
| 393 |
-
"change_15m_pct": change_pct * 100,
|
| 394 |
"usd_impact": usd_impact,
|
| 395 |
"verdict": "SUCCESS (Saved Loss)" if is_good_exit else "MISS (Lost Profit)",
|
| 396 |
"ai_scores": ai_scores if ai_scores else {},
|
|
@@ -401,7 +388,7 @@ class TradeManager:
|
|
| 401 |
print(f"👻 [Ghost Monitor] {symbol}: {audit_record['verdict']} | Impact: ${usd_impact:.2f}")
|
| 402 |
|
| 403 |
except Exception as e:
|
| 404 |
-
print(f"⚠️ [Ghost Error]
|
| 405 |
|
| 406 |
# ==============================================================================
|
| 407 |
# 🔴 دالة الخروج المحاسبية (Accounting Exit Logic)
|
|
@@ -412,31 +399,25 @@ class TradeManager:
|
|
| 412 |
return
|
| 413 |
|
| 414 |
try:
|
| 415 |
-
# 1. استخراج بيانات الصفقة
|
| 416 |
trade = self.open_positions.pop(symbol)
|
| 417 |
-
|
| 418 |
entry_price = float(trade['entry_price'])
|
| 419 |
exit_price = float(price)
|
| 420 |
|
| 421 |
-
# 2. حساب نسبة الربح
|
| 422 |
raw_profit_pct = (exit_price - entry_price) / entry_price
|
| 423 |
profit_pct_display = raw_profit_pct * 100
|
| 424 |
|
| 425 |
-
# 3. تحديث بيانات الصفقة
|
| 426 |
trade['status'] = 'CLOSED'
|
| 427 |
trade['exit_price'] = exit_price
|
| 428 |
trade['exit_time'] = datetime.now().isoformat()
|
| 429 |
trade['exit_reason'] = reason
|
| 430 |
trade['profit_pct'] = profit_pct_display
|
| 431 |
|
| 432 |
-
# 4. 💰 [المحاسبة] تحديث المحفظة (محفظة واحدة فقط)
|
| 433 |
portfolio = await self.r2.get_portfolio_state_async()
|
| 434 |
current_capital = float(portfolio.get('current_capital_usd', 100.0))
|
| 435 |
|
| 436 |
pnl_usd = current_capital * raw_profit_pct
|
| 437 |
new_capital = current_capital + pnl_usd
|
| 438 |
|
| 439 |
-
# تحديث الإحصائيات التراكمية
|
| 440 |
portfolio['current_capital_usd'] = new_capital
|
| 441 |
portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
|
| 442 |
|
|
@@ -449,25 +430,21 @@ class TradeManager:
|
|
| 449 |
portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0.0) + abs(pnl_usd)
|
| 450 |
trade['result'] = 'LOSS'
|
| 451 |
|
| 452 |
-
# تحديث نسبة الفوز
|
| 453 |
total_t = portfolio['total_trades']
|
| 454 |
if total_t > 0:
|
| 455 |
portfolio['win_rate'] = (portfolio['winning_trades'] / total_t) * 100
|
| 456 |
|
| 457 |
trade['pnl_usd'] = pnl_usd
|
| 458 |
|
| 459 |
-
# 5. حفظ البيانات في R2
|
| 460 |
await self.r2.save_portfolio_state_async(portfolio)
|
| 461 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 462 |
await self.r2.append_to_closed_trades_history(trade)
|
| 463 |
|
| 464 |
print(f"✅ [EXIT EXECUTED] {symbol} | Reason: {reason} | PnL: {profit_pct_display:.2f}% (${pnl_usd:.2f}) | New Cap: ${new_capital:.2f}")
|
| 465 |
|
| 466 |
-
# 6. [ 🧠 ] إطلاق المراقب الشبحي إذا كان الخروج بسبب الذكاء الاصطناعي
|
| 467 |
if "AI_" in reason:
|
| 468 |
self._launch_post_exit_analysis(symbol, exit_price, trade['exit_time'], current_capital, ai_scores)
|
| 469 |
|
| 470 |
-
# 7. تنظيف مهام الحارس
|
| 471 |
if symbol in self.sentry_tasks:
|
| 472 |
self.sentry_tasks[symbol].cancel()
|
| 473 |
del self.sentry_tasks[symbol]
|
|
@@ -478,7 +455,6 @@ class TradeManager:
|
|
| 478 |
if symbol not in self.open_positions:
|
| 479 |
self.open_positions[symbol] = trade
|
| 480 |
|
| 481 |
-
# [جديد] دالة للخروج القسري من واجهة المستخدم
|
| 482 |
async def force_exit_by_manager(self, symbol, reason):
|
| 483 |
print(f"⚠️ [Manager Force Exit] طلب خروج إداري لـ {symbol} بسبب: {reason}")
|
| 484 |
current_price = await self.data_manager.get_latest_price_async(symbol)
|
|
|
|
| 1 |
+
# trade_manager.py (V22.5 - GEM-Architect: Real-Time Hard Limits Fix)
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import uuid
|
| 5 |
+
import time
|
| 6 |
import traceback
|
| 7 |
from datetime import datetime
|
| 8 |
from typing import List, Dict, Any
|
|
|
|
| 27 |
self.sentry_tasks = {}
|
| 28 |
self.running = True
|
| 29 |
|
| 30 |
+
# [ 🧠 IQ Stats ] مخزن الإحصائيات
|
|
|
|
|
|
|
|
|
|
| 31 |
self.ai_stats = {
|
| 32 |
"hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
|
| 33 |
"v2": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
|
| 34 |
"v3": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0}
|
| 35 |
}
|
| 36 |
|
|
|
|
| 37 |
self.ds_stats = self.ai_stats["hybrid"]
|
| 38 |
|
| 39 |
self.execution_lock = asyncio.Lock()
|
| 40 |
|
| 41 |
+
print(f"🛡️ [TradeManager V22.5] Initialized. Real-Time Hard Limits Enabled.")
|
| 42 |
|
| 43 |
async def initialize_sentry_exchanges(self):
|
| 44 |
print("🛡️ [TradeManager] Initializing and syncing state with R2...")
|
|
|
|
| 192 |
self.watchlist.clear()
|
| 193 |
|
| 194 |
# ============================================================
|
| 195 |
+
# 🕒 تسجيل وقت أول صفقة
|
| 196 |
# ============================================================
|
| 197 |
portfolio = await self.r2.get_portfolio_state_async()
|
| 198 |
if portfolio.get('first_trade_timestamp') is None:
|
| 199 |
portfolio['first_trade_timestamp'] = new_trade['entry_time']
|
| 200 |
await self.r2.save_portfolio_state_async(portfolio)
|
|
|
|
|
|
|
| 201 |
|
| 202 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 203 |
|
|
|
|
| 207 |
self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
|
| 208 |
|
| 209 |
print(f"✅ [ENTRY EXECUTED] {symbol} | Price: {current_price:.8f} | TP: {tp_price:.8f} | SL: {sl_price:.8f}")
|
|
|
|
| 210 |
|
| 211 |
except Exception as e:
|
| 212 |
print(f"❌ [TradeManager] فشل فادح أثناء تنفيذ الدخول لـ {symbol}: {e}")
|
|
|
|
| 260 |
await self.select_and_execute_best_signal(signals_from_watchlist)
|
| 261 |
|
| 262 |
# ==============================================================================
|
| 263 |
+
# 🛡️ دوال حارس الخروج (Hybrid Sentry) - [CRITICAL FIX: Real-Time Checks]
|
| 264 |
# ==============================================================================
|
| 265 |
async def _guardian_loop(self, symbol: str):
|
| 266 |
+
print(f"🛡️ [Sentry Activated] بدء الحراسة لـ {symbol} (وضع الاستجابة الفورية).")
|
| 267 |
+
|
| 268 |
+
last_ai_check_time = 0
|
| 269 |
|
| 270 |
while self.running and symbol in self.open_positions:
|
| 271 |
try:
|
| 272 |
+
# ⚡ [FAST LANE] المسار السريع: فحص الحدود الصارمة كل 2 ثانية
|
| 273 |
+
# هذا يضمن عدم تفويت الهدف إذا تحرك السعر بسرعة
|
| 274 |
+
await asyncio.sleep(2)
|
| 275 |
|
| 276 |
trade = self.open_positions.get(symbol)
|
| 277 |
if not trade: break
|
|
|
|
| 279 |
current_price = await self.data_manager.get_latest_price_async(symbol)
|
| 280 |
if current_price == 0.0: continue
|
| 281 |
|
| 282 |
+
# 1. فحص الحدود الصارمة (Hard Limits) - أولوية قصوى
|
| 283 |
if current_price >= trade['tp_price']:
|
| 284 |
+
print(f"✅ [Sentry FAST] {symbol} (Target Hit) at {current_price:.8f}")
|
| 285 |
async with self.execution_lock:
|
| 286 |
await self._execute_exit(symbol, current_price, "TP_HIT_HARD")
|
| 287 |
break
|
| 288 |
|
| 289 |
if current_price <= trade['sl_price']:
|
| 290 |
+
print(f"🛑 [Sentry FAST] {symbol} (Stop Loss Hit) at {current_price:.8f}")
|
| 291 |
async with self.execution_lock:
|
| 292 |
await self._execute_exit(symbol, current_price, "SL_HIT_HARD")
|
| 293 |
break
|
| 294 |
|
| 295 |
+
# 🐌 [SLOW LANE] المسار البطيء: فحص الذكاء الاصطناعي كل 60 ثانية
|
| 296 |
+
if time.time() - last_ai_check_time > 60:
|
| 297 |
+
if self.deep_steward and self.deep_steward.initialized:
|
| 298 |
+
# جلب البيانات اللازمة (تم تثبيت الحد على 300)
|
| 299 |
+
task1 = self.data_manager.get_latest_ohlcv(symbol, '1m', 300)
|
| 300 |
+
task5 = self.data_manager.get_latest_ohlcv(symbol, '5m', 200)
|
| 301 |
+
task15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 100)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
+
d1, d5, d15 = await asyncio.gather(task1, task5, task15)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
|
| 305 |
+
if d1 and d5 and d15 and len(d1) >= 200:
|
| 306 |
+
decision = self.deep_steward.analyze_position(d1, d5, d15, trade['entry_price'])
|
| 307 |
+
action = decision.get('action', 'HOLD')
|
| 308 |
+
scores = decision.get('scores', {})
|
| 309 |
+
|
| 310 |
+
if action == 'EXIT_HARD':
|
| 311 |
+
print(f"🤖 [Hybrid Guardian] 🚨 خروج طارئ! {decision['reason']}")
|
| 312 |
+
async with self.execution_lock:
|
| 313 |
+
await self._execute_exit(symbol, current_price, "AI_HARD_EXIT", ai_scores=scores)
|
| 314 |
+
break
|
| 315 |
+
|
| 316 |
+
elif action == 'EXIT_SOFT':
|
| 317 |
+
print(f"🤖 [Hybrid Guardian] ⚠️ تحذير خروج! {decision['reason']}")
|
| 318 |
+
async with self.execution_lock:
|
| 319 |
+
await self._execute_exit(symbol, current_price, "AI_SOFT_EXIT", ai_scores=scores)
|
| 320 |
+
break
|
| 321 |
+
else:
|
| 322 |
+
# لو البيانات ناقصة، ننتظر الد��رة القادمة
|
| 323 |
+
pass
|
| 324 |
+
|
| 325 |
+
last_ai_check_time = time.time()
|
| 326 |
+
self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
|
| 327 |
|
| 328 |
except asyncio.CancelledError:
|
| 329 |
print(f"🛡️ [Sentry STOP] تم إيقاف الحارس لـ {symbol}.")
|
| 330 |
break
|
| 331 |
except Exception as e:
|
| 332 |
print(f"❌ [Sentry ERROR] فشل مؤقت لـ {symbol}: {e}")
|
| 333 |
+
await asyncio.sleep(5) # انتظار قصير عند الخطأ
|
| 334 |
|
| 335 |
# ==============================================================================
|
| 336 |
+
# 👻 المراقب الشبحي (Ghost Monitor)
|
| 337 |
# ==============================================================================
|
| 338 |
def _launch_post_exit_analysis(self, symbol, exit_price, exit_time, position_size_usd, ai_scores=None):
|
|
|
|
| 339 |
asyncio.create_task(self._analyze_after_exit_task(symbol, exit_price, exit_time, position_size_usd, ai_scores))
|
| 340 |
|
| 341 |
def _update_specific_stat(self, key, is_good, usd_impact):
|
|
|
|
| 342 |
if key not in self.ai_stats: return
|
| 343 |
self.ai_stats[key]["total"] += 1
|
| 344 |
if is_good:
|
|
|
|
| 348 |
self.ai_stats[key]["missed"] += abs(usd_impact)
|
| 349 |
|
| 350 |
async def _analyze_after_exit_task(self, symbol, exit_price, exit_time, position_size_usd, ai_scores):
|
|
|
|
| 351 |
await asyncio.sleep(900)
|
| 352 |
|
| 353 |
try:
|
| 354 |
current_price = await self.data_manager.get_latest_price_async(symbol)
|
| 355 |
if current_price == 0: return
|
| 356 |
|
|
|
|
| 357 |
change_pct = (current_price - exit_price) / exit_price
|
| 358 |
usd_impact = change_pct * position_size_usd
|
| 359 |
+
is_good_exit = change_pct < 0
|
| 360 |
|
| 361 |
+
# 1. تحديث الهجين
|
| 362 |
self._update_specific_stat("hybrid", is_good_exit, usd_impact)
|
| 363 |
|
| 364 |
+
# 2. تحديث المكونات
|
| 365 |
if ai_scores:
|
| 366 |
v2_score = ai_scores.get('v2', 0.0)
|
| 367 |
v3_score = ai_scores.get('v3', 0.0)
|
| 368 |
|
| 369 |
+
if v2_score >= 0.60: # V2 ساهم
|
|
|
|
| 370 |
self._update_specific_stat("v2", is_good_exit, usd_impact)
|
| 371 |
|
| 372 |
+
if v3_score >= 0.75: # V3 ساهم
|
|
|
|
| 373 |
self._update_specific_stat("v3", is_good_exit, usd_impact)
|
| 374 |
|
|
|
|
| 375 |
audit_record = {
|
| 376 |
"symbol": symbol,
|
| 377 |
"exit_time": exit_time,
|
| 378 |
"exit_price": exit_price,
|
| 379 |
+
"price_15m_later": current_price,
|
| 380 |
+
"change_15m_pct": change_pct * 100,
|
| 381 |
"usd_impact": usd_impact,
|
| 382 |
"verdict": "SUCCESS (Saved Loss)" if is_good_exit else "MISS (Lost Profit)",
|
| 383 |
"ai_scores": ai_scores if ai_scores else {},
|
|
|
|
| 388 |
print(f"👻 [Ghost Monitor] {symbol}: {audit_record['verdict']} | Impact: ${usd_impact:.2f}")
|
| 389 |
|
| 390 |
except Exception as e:
|
| 391 |
+
print(f"⚠️ [Ghost Error] {symbol}: {e}")
|
| 392 |
|
| 393 |
# ==============================================================================
|
| 394 |
# 🔴 دالة الخروج المحاسبية (Accounting Exit Logic)
|
|
|
|
| 399 |
return
|
| 400 |
|
| 401 |
try:
|
|
|
|
| 402 |
trade = self.open_positions.pop(symbol)
|
|
|
|
| 403 |
entry_price = float(trade['entry_price'])
|
| 404 |
exit_price = float(price)
|
| 405 |
|
|
|
|
| 406 |
raw_profit_pct = (exit_price - entry_price) / entry_price
|
| 407 |
profit_pct_display = raw_profit_pct * 100
|
| 408 |
|
|
|
|
| 409 |
trade['status'] = 'CLOSED'
|
| 410 |
trade['exit_price'] = exit_price
|
| 411 |
trade['exit_time'] = datetime.now().isoformat()
|
| 412 |
trade['exit_reason'] = reason
|
| 413 |
trade['profit_pct'] = profit_pct_display
|
| 414 |
|
|
|
|
| 415 |
portfolio = await self.r2.get_portfolio_state_async()
|
| 416 |
current_capital = float(portfolio.get('current_capital_usd', 100.0))
|
| 417 |
|
| 418 |
pnl_usd = current_capital * raw_profit_pct
|
| 419 |
new_capital = current_capital + pnl_usd
|
| 420 |
|
|
|
|
| 421 |
portfolio['current_capital_usd'] = new_capital
|
| 422 |
portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
|
| 423 |
|
|
|
|
| 430 |
portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0.0) + abs(pnl_usd)
|
| 431 |
trade['result'] = 'LOSS'
|
| 432 |
|
|
|
|
| 433 |
total_t = portfolio['total_trades']
|
| 434 |
if total_t > 0:
|
| 435 |
portfolio['win_rate'] = (portfolio['winning_trades'] / total_t) * 100
|
| 436 |
|
| 437 |
trade['pnl_usd'] = pnl_usd
|
| 438 |
|
|
|
|
| 439 |
await self.r2.save_portfolio_state_async(portfolio)
|
| 440 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 441 |
await self.r2.append_to_closed_trades_history(trade)
|
| 442 |
|
| 443 |
print(f"✅ [EXIT EXECUTED] {symbol} | Reason: {reason} | PnL: {profit_pct_display:.2f}% (${pnl_usd:.2f}) | New Cap: ${new_capital:.2f}")
|
| 444 |
|
|
|
|
| 445 |
if "AI_" in reason:
|
| 446 |
self._launch_post_exit_analysis(symbol, exit_price, trade['exit_time'], current_capital, ai_scores)
|
| 447 |
|
|
|
|
| 448 |
if symbol in self.sentry_tasks:
|
| 449 |
self.sentry_tasks[symbol].cancel()
|
| 450 |
del self.sentry_tasks[symbol]
|
|
|
|
| 455 |
if symbol not in self.open_positions:
|
| 456 |
self.open_positions[symbol] = trade
|
| 457 |
|
|
|
|
| 458 |
async def force_exit_by_manager(self, symbol, reason):
|
| 459 |
print(f"⚠️ [Manager Force Exit] طلب خروج إداري لـ {symbol} بسبب: {reason}")
|
| 460 |
current_price = await self.data_manager.get_latest_price_async(symbol)
|