Spaces:
Paused
Paused
Update trade_manager.py
Browse files- trade_manager.py +125 -44
trade_manager.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# trade_manager.py (
|
| 2 |
|
| 3 |
import asyncio
|
| 4 |
import uuid
|
|
@@ -19,6 +19,9 @@ class TradeManager:
|
|
| 19 |
self.sentry_tasks = {}
|
| 20 |
self.running = True
|
| 21 |
|
|
|
|
|
|
|
|
|
|
| 22 |
self.ai_stats = {
|
| 23 |
"hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
|
| 24 |
"v2": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
|
|
@@ -26,7 +29,7 @@ class TradeManager:
|
|
| 26 |
}
|
| 27 |
|
| 28 |
self.execution_lock = asyncio.Lock()
|
| 29 |
-
print(f"🛡️ [TradeManager
|
| 30 |
|
| 31 |
async def initialize_sentry_exchanges(self):
|
| 32 |
print("🛡️ [TradeManager] Syncing state with R2...")
|
|
@@ -60,7 +63,7 @@ class TradeManager:
|
|
| 60 |
if symbol in self.open_positions or symbol in self.watchlist:
|
| 61 |
continue
|
| 62 |
|
| 63 |
-
#
|
| 64 |
ohlcv_task = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
|
| 65 |
ob_task = self.data_manager.get_order_book_snapshot(symbol)
|
| 66 |
ohlcv_1m, order_book = await asyncio.gather(ohlcv_task, ob_task)
|
|
@@ -99,15 +102,25 @@ class TradeManager:
|
|
| 99 |
await self._execute_entry_from_signal(best_signal['symbol'], best_signal)
|
| 100 |
|
| 101 |
# ==============================================================================
|
| 102 |
-
# 🎯 Entry Execution
|
| 103 |
# ==============================================================================
|
| 104 |
async def _execute_entry_from_signal(self, symbol, signal_data):
|
| 105 |
try:
|
| 106 |
trade_id = str(uuid.uuid4())
|
|
|
|
|
|
|
| 107 |
current_price = float(signal_data.get('current_price', 0.0))
|
| 108 |
if current_price <= 0.0:
|
| 109 |
current_price = await self.data_manager.get_latest_price_async(symbol)
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
l2_result = signal_data.get('l2_sniper_result', {})
|
| 112 |
tp_price = float(signal_data.get('tp_price') or 0.0)
|
| 113 |
sl_price = float(signal_data.get('sl_price') or 0.0)
|
|
@@ -129,6 +142,9 @@ class TradeManager:
|
|
| 129 |
'strategy': 'Hybrid_Titan_V1',
|
| 130 |
'l1_score': float(signal_data.get('enhanced_final_score', 0.0)),
|
| 131 |
'l2_sniper_confidence': float(l2_result.get('confidence_prob', 0.0)),
|
|
|
|
|
|
|
|
|
|
| 132 |
'decision_data': {
|
| 133 |
'components': signal_data.get('components', {}),
|
| 134 |
'sniper_analysis': l2_result,
|
|
@@ -140,73 +156,101 @@ class TradeManager:
|
|
| 140 |
self.open_positions[symbol] = new_trade
|
| 141 |
if self.watchlist: self.watchlist.clear()
|
| 142 |
|
| 143 |
-
portfolio = await self.r2.get_portfolio_state_async()
|
| 144 |
if portfolio.get('first_trade_timestamp') is None:
|
| 145 |
portfolio['first_trade_timestamp'] = new_trade['entry_time']
|
| 146 |
await self.r2.save_portfolio_state_async(portfolio)
|
|
|
|
| 147 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 148 |
|
| 149 |
if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel()
|
| 150 |
self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
|
| 151 |
|
| 152 |
-
print(f"✅ [ENTRY] {symbol} @ {current_price} |
|
| 153 |
|
| 154 |
except Exception as e:
|
| 155 |
print(f"❌ [Entry Error] {symbol}: {e}")
|
| 156 |
traceback.print_exc()
|
| 157 |
|
| 158 |
# ==============================================================================
|
| 159 |
-
# 🛡️ Hybrid Sentry (
|
| 160 |
# ==============================================================================
|
| 161 |
async def _guardian_loop(self, symbol: str):
|
| 162 |
-
print(f"🛡️ [Sentry] Guarding {symbol}...")
|
| 163 |
last_ai_check_time = 0
|
| 164 |
|
| 165 |
while self.running and symbol in self.open_positions:
|
| 166 |
try:
|
| 167 |
-
|
|
|
|
|
|
|
| 168 |
trade = self.open_positions.get(symbol)
|
| 169 |
if not trade: break
|
| 170 |
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
#
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
async with self.execution_lock:
|
| 177 |
-
|
|
|
|
| 178 |
break
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
| 180 |
async with self.execution_lock:
|
| 181 |
-
|
|
|
|
| 182 |
break
|
| 183 |
|
| 184 |
-
#
|
|
|
|
|
|
|
| 185 |
if time.time() - last_ai_check_time > 60:
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
t15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 500) # كان 100
|
| 190 |
d1, d5, d15 = await asyncio.gather(t1, t5, t15)
|
| 191 |
|
| 192 |
-
# التحقق من أن البيانات كافية بعد الحسابات
|
| 193 |
if d1 and d5 and d15 and len(d1) >= 500:
|
| 194 |
decision = self.processor.consult_guardian(d1, d5, d15, trade['entry_price'])
|
| 195 |
action = decision.get('action', 'HOLD')
|
| 196 |
scores = decision.get('scores', {})
|
| 197 |
|
| 198 |
-
# طباعة التشخيص (اختياري لملف التداول)
|
| 199 |
-
# v2_val = int(scores.get('v2', 0.0) * 100)
|
| 200 |
-
# v3_val = int(scores.get('v3', 0.0) * 100)
|
| 201 |
-
# print(f" 🕵️ [Sentry check] {symbol} | V2:{v2_val} V3:{v3_val} | {action}")
|
| 202 |
-
|
| 203 |
if action in ['EXIT_HARD', 'EXIT_SOFT']:
|
| 204 |
print(f"🤖 [Guardian] {action}: {decision.get('reason')}")
|
| 205 |
async with self.execution_lock:
|
| 206 |
-
|
|
|
|
| 207 |
break
|
| 208 |
last_ai_check_time = time.time()
|
|
|
|
|
|
|
| 209 |
self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
|
|
|
|
| 210 |
except asyncio.CancelledError: break
|
| 211 |
except Exception as e:
|
| 212 |
print(f"❌ [Sentry Error] {symbol}: {e}")
|
|
@@ -253,41 +297,75 @@ class TradeManager:
|
|
| 253 |
print(f"⚠️ [Ghost/Learning Error] {e}")
|
| 254 |
|
| 255 |
# ==============================================================================
|
| 256 |
-
# 🔴 Exit Logic
|
| 257 |
# ==============================================================================
|
| 258 |
async def _execute_exit(self, symbol, price, reason, ai_scores=None):
|
| 259 |
if symbol not in self.open_positions: return
|
| 260 |
try:
|
| 261 |
trade = self.open_positions.pop(symbol)
|
| 262 |
-
entry = float(trade['entry_price'])
|
| 263 |
-
exit_p = float(price)
|
| 264 |
-
profit_pct = ((exit_p - entry) / entry)
|
| 265 |
|
| 266 |
-
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
portfolio = await self.r2.get_portfolio_state_async()
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
|
|
|
| 272 |
|
| 273 |
portfolio['current_capital_usd'] = new_cap
|
| 274 |
portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
|
| 275 |
-
|
|
|
|
| 276 |
portfolio['winning_trades'] = portfolio.get('winning_trades', 0) + 1
|
| 277 |
-
portfolio['total_profit_usd'] = portfolio.get('total_profit_usd', 0) +
|
| 278 |
trade['result'] = 'WIN'
|
| 279 |
else:
|
| 280 |
portfolio['losing_trades'] = portfolio.get('losing_trades', 0) + 1
|
| 281 |
-
portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0) + abs(
|
| 282 |
trade['result'] = 'LOSS'
|
| 283 |
|
| 284 |
await self.r2.save_portfolio_state_async(portfolio)
|
| 285 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 286 |
await self.r2.append_to_closed_trades_history(trade)
|
| 287 |
|
| 288 |
-
print(f"✅ [EXIT] {symbol} | PnL: {
|
| 289 |
|
| 290 |
-
self._launch_post_exit_analysis(symbol,
|
| 291 |
|
| 292 |
if symbol in self.sentry_tasks:
|
| 293 |
self.sentry_tasks[symbol].cancel()
|
|
@@ -295,7 +373,10 @@ class TradeManager:
|
|
| 295 |
|
| 296 |
except Exception as e:
|
| 297 |
print(f"❌ [Exit Error] {e}")
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
async def force_exit_by_manager(self, symbol, reason):
|
| 301 |
p = await self.data_manager.get_latest_price_async(symbol)
|
|
|
|
| 1 |
+
# trade_manager.py (V24.0 - GEM-Architect: Realistic Sim & Fees)
|
| 2 |
|
| 3 |
import asyncio
|
| 4 |
import uuid
|
|
|
|
| 19 |
self.sentry_tasks = {}
|
| 20 |
self.running = True
|
| 21 |
|
| 22 |
+
# إعدادات الرسوم (0.1% لكل عملية)
|
| 23 |
+
self.FEE_RATE = 0.001
|
| 24 |
+
|
| 25 |
self.ai_stats = {
|
| 26 |
"hybrid": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
|
| 27 |
"v2": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0},
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
self.execution_lock = asyncio.Lock()
|
| 32 |
+
print(f"🛡️ [TradeManager V24.0] Initialized (High/Low Sim + Fees Enabled).")
|
| 33 |
|
| 34 |
async def initialize_sentry_exchanges(self):
|
| 35 |
print("🛡️ [TradeManager] Syncing state with R2...")
|
|
|
|
| 63 |
if symbol in self.open_positions or symbol in self.watchlist:
|
| 64 |
continue
|
| 65 |
|
| 66 |
+
# التحقق من القناص مع بيانات كافية
|
| 67 |
ohlcv_task = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
|
| 68 |
ob_task = self.data_manager.get_order_book_snapshot(symbol)
|
| 69 |
ohlcv_1m, order_book = await asyncio.gather(ohlcv_task, ob_task)
|
|
|
|
| 102 |
await self._execute_entry_from_signal(best_signal['symbol'], best_signal)
|
| 103 |
|
| 104 |
# ==============================================================================
|
| 105 |
+
# 🎯 Entry Execution (With Fee Calculation)
|
| 106 |
# ==============================================================================
|
| 107 |
async def _execute_entry_from_signal(self, symbol, signal_data):
|
| 108 |
try:
|
| 109 |
trade_id = str(uuid.uuid4())
|
| 110 |
+
|
| 111 |
+
# تحديد سعر الدخول
|
| 112 |
current_price = float(signal_data.get('current_price', 0.0))
|
| 113 |
if current_price <= 0.0:
|
| 114 |
current_price = await self.data_manager.get_latest_price_async(symbol)
|
| 115 |
|
| 116 |
+
# جلب حجم المحفظة لحساب الرسوم الأولية
|
| 117 |
+
portfolio = await self.r2.get_portfolio_state_async()
|
| 118 |
+
current_capital = float(portfolio.get('current_capital_usd', 100.0))
|
| 119 |
+
|
| 120 |
+
# حساب الرسوم المتوقعة للدخول
|
| 121 |
+
# ملاحظة: نفترض أننا ندخل بكامل رأس المال المتاح في المحاكاة الحالية
|
| 122 |
+
entry_fee_usd = current_capital * self.FEE_RATE
|
| 123 |
+
|
| 124 |
l2_result = signal_data.get('l2_sniper_result', {})
|
| 125 |
tp_price = float(signal_data.get('tp_price') or 0.0)
|
| 126 |
sl_price = float(signal_data.get('sl_price') or 0.0)
|
|
|
|
| 142 |
'strategy': 'Hybrid_Titan_V1',
|
| 143 |
'l1_score': float(signal_data.get('enhanced_final_score', 0.0)),
|
| 144 |
'l2_sniper_confidence': float(l2_result.get('confidence_prob', 0.0)),
|
| 145 |
+
# تخزين بيانات الرسوم ورأس المال عند الدخول
|
| 146 |
+
'entry_capital': current_capital,
|
| 147 |
+
'entry_fee_usd': entry_fee_usd,
|
| 148 |
'decision_data': {
|
| 149 |
'components': signal_data.get('components', {}),
|
| 150 |
'sniper_analysis': l2_result,
|
|
|
|
| 156 |
self.open_positions[symbol] = new_trade
|
| 157 |
if self.watchlist: self.watchlist.clear()
|
| 158 |
|
|
|
|
| 159 |
if portfolio.get('first_trade_timestamp') is None:
|
| 160 |
portfolio['first_trade_timestamp'] = new_trade['entry_time']
|
| 161 |
await self.r2.save_portfolio_state_async(portfolio)
|
| 162 |
+
|
| 163 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 164 |
|
| 165 |
if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel()
|
| 166 |
self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol))
|
| 167 |
|
| 168 |
+
print(f"✅ [ENTRY] {symbol} @ {current_price} | Cap: ${current_capital:.2f} | Fee: ${entry_fee_usd:.3f}")
|
| 169 |
|
| 170 |
except Exception as e:
|
| 171 |
print(f"❌ [Entry Error] {symbol}: {e}")
|
| 172 |
traceback.print_exc()
|
| 173 |
|
| 174 |
# ==============================================================================
|
| 175 |
+
# 🛡️ Hybrid Sentry (Updated: High/Low Candle Check)
|
| 176 |
# ==============================================================================
|
| 177 |
async def _guardian_loop(self, symbol: str):
|
| 178 |
+
print(f"🛡️ [Sentry] Guarding {symbol} (High/Low Simulation Mode)...")
|
| 179 |
last_ai_check_time = 0
|
| 180 |
|
| 181 |
while self.running and symbol in self.open_positions:
|
| 182 |
try:
|
| 183 |
+
# انتظار قصير للتحديث
|
| 184 |
+
await asyncio.sleep(1)
|
| 185 |
+
|
| 186 |
trade = self.open_positions.get(symbol)
|
| 187 |
if not trade: break
|
| 188 |
|
| 189 |
+
# -------------------------------------------------------------
|
| 190 |
+
# 1. جلب بيانات الشموع للتحقق من الذيول (Wicks)
|
| 191 |
+
# -------------------------------------------------------------
|
| 192 |
+
# نجلب آخر شمعتين لضمان تغطية الشمعة الحالية والمكتملة حديثاً
|
| 193 |
+
ohlcv = await self.data_manager.get_latest_ohlcv(symbol, '1m', limit=2)
|
| 194 |
+
current_ticker_price = await self.data_manager.get_latest_price_async(symbol)
|
| 195 |
+
|
| 196 |
+
if not ohlcv:
|
| 197 |
+
continue
|
| 198 |
+
|
| 199 |
+
# الشمعة الأخيرة (الحالية)
|
| 200 |
+
current_candle = ohlcv[-1]
|
| 201 |
+
# [timestamp, open, high, low, close, volume]
|
| 202 |
+
candle_high = current_candle[2]
|
| 203 |
+
candle_low = current_candle[3]
|
| 204 |
+
|
| 205 |
+
# استخدام السعر الأعلى بين التيكر والشمعة لضمان عدم تفويت أي قمة لحظية
|
| 206 |
+
max_price_seen = max(current_ticker_price, candle_high)
|
| 207 |
+
min_price_seen = min(current_ticker_price, candle_low)
|
| 208 |
+
|
| 209 |
+
# -------------------------------------------------------------
|
| 210 |
+
# 2. التحقق من الأهداف (Simulated Limit Orders)
|
| 211 |
+
# -------------------------------------------------------------
|
| 212 |
+
|
| 213 |
+
# أ) تحقق من الهدف (Take Profit)
|
| 214 |
+
if max_price_seen >= trade['tp_price']:
|
| 215 |
+
print(f"🎯 [TP HIT] Price {max_price_seen} touched Target {trade['tp_price']}")
|
| 216 |
async with self.execution_lock:
|
| 217 |
+
# الخروج بسعر الهدف تماماً كما يفعل الأمر المعلق
|
| 218 |
+
await self._execute_exit(symbol, trade['tp_price'], "TP_HIT_SIMULATED")
|
| 219 |
break
|
| 220 |
+
|
| 221 |
+
# ب) تحقق من وقف الخسارة (Stop Loss)
|
| 222 |
+
if min_price_seen <= trade['sl_price']:
|
| 223 |
+
print(f"🛑 [SL HIT] Price {min_price_seen} touched Stop {trade['sl_price']}")
|
| 224 |
async with self.execution_lock:
|
| 225 |
+
# الخروج بسعر الوقف
|
| 226 |
+
await self._execute_exit(symbol, trade['sl_price'], "SL_HIT_SIMULATED")
|
| 227 |
break
|
| 228 |
|
| 229 |
+
# -------------------------------------------------------------
|
| 230 |
+
# 3. التدقيق الذكي (AI Guardian)
|
| 231 |
+
# -------------------------------------------------------------
|
| 232 |
if time.time() - last_ai_check_time > 60:
|
| 233 |
+
t1 = self.data_manager.get_latest_ohlcv(symbol, '1m', 1000)
|
| 234 |
+
t5 = self.data_manager.get_latest_ohlcv(symbol, '5m', 500)
|
| 235 |
+
t15 = self.data_manager.get_latest_ohlcv(symbol, '15m', 500)
|
|
|
|
| 236 |
d1, d5, d15 = await asyncio.gather(t1, t5, t15)
|
| 237 |
|
|
|
|
| 238 |
if d1 and d5 and d15 and len(d1) >= 500:
|
| 239 |
decision = self.processor.consult_guardian(d1, d5, d15, trade['entry_price'])
|
| 240 |
action = decision.get('action', 'HOLD')
|
| 241 |
scores = decision.get('scores', {})
|
| 242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
if action in ['EXIT_HARD', 'EXIT_SOFT']:
|
| 244 |
print(f"🤖 [Guardian] {action}: {decision.get('reason')}")
|
| 245 |
async with self.execution_lock:
|
| 246 |
+
# في حالة الخروج الذكي، نستخدم السعر الحالي للتنفيذ (Market Order)
|
| 247 |
+
await self._execute_exit(symbol, current_ticker_price, f"AI_{action}", ai_scores=scores)
|
| 248 |
break
|
| 249 |
last_ai_check_time = time.time()
|
| 250 |
+
|
| 251 |
+
# تحديث وقت آخر نشاط للصفقة
|
| 252 |
self.open_positions[symbol]['last_update'] = datetime.now().isoformat()
|
| 253 |
+
|
| 254 |
except asyncio.CancelledError: break
|
| 255 |
except Exception as e:
|
| 256 |
print(f"❌ [Sentry Error] {symbol}: {e}")
|
|
|
|
| 297 |
print(f"⚠️ [Ghost/Learning Error] {e}")
|
| 298 |
|
| 299 |
# ==============================================================================
|
| 300 |
+
# 🔴 Exit Logic (Net PnL with Fees)
|
| 301 |
# ==============================================================================
|
| 302 |
async def _execute_exit(self, symbol, price, reason, ai_scores=None):
|
| 303 |
if symbol not in self.open_positions: return
|
| 304 |
try:
|
| 305 |
trade = self.open_positions.pop(symbol)
|
|
|
|
|
|
|
|
|
|
| 306 |
|
| 307 |
+
entry_price = float(trade['entry_price'])
|
| 308 |
+
exit_price = float(price)
|
| 309 |
+
|
| 310 |
+
# استرجاع رأس المال عند الدخول ورسوم الدخول
|
| 311 |
+
entry_capital = float(trade.get('entry_capital', 100.0)) # Fallback if missing
|
| 312 |
+
entry_fee_usd = float(trade.get('entry_fee_usd', 0.0))
|
| 313 |
+
|
| 314 |
+
# حساب قيمة الخروج قبل خصم رسوم الخروج
|
| 315 |
+
# معادلة التغير: (سعر الخروج / سعر الدخول) * رأس المال
|
| 316 |
+
exit_value_gross = (exit_price / entry_price) * entry_capital
|
| 317 |
+
|
| 318 |
+
# حساب رسوم الخروج
|
| 319 |
+
exit_fee_usd = exit_value_gross * self.FEE_RATE
|
| 320 |
+
|
| 321 |
+
# القيمة الصافية النهائية (Net Value)
|
| 322 |
+
net_exit_value = exit_value_gross - exit_fee_usd
|
| 323 |
+
|
| 324 |
+
# صافي الربح/الخسارة بالدولار (يشمل خصم رسوم الدخول والخروج)
|
| 325 |
+
# Net PnL = القيمة النهائية - (رأس المال الأصلي + رسوم الدخول المدفوعة سابقاً)
|
| 326 |
+
# ملاحظة: رسوم الدخول خُصمت ضمنياً لأننا نستخدم رأس المال الكامل، لذا الحسبة الأدق:
|
| 327 |
+
# الربح الصافي = ما تبقى في اليد (Net Exit) - ما كان في اليد قبل الدخول (Entry Capital)
|
| 328 |
+
net_pnl_usd = net_exit_value - entry_capital
|
| 329 |
+
|
| 330 |
+
# نسبة الربح الصافي
|
| 331 |
+
net_profit_pct = (net_pnl_usd / entry_capital) * 100
|
| 332 |
+
|
| 333 |
+
# تحديث بيانات الصفقة
|
| 334 |
+
trade.update({
|
| 335 |
+
'status': 'CLOSED',
|
| 336 |
+
'exit_price': exit_price,
|
| 337 |
+
'exit_reason': reason,
|
| 338 |
+
'profit_pct': net_profit_pct,
|
| 339 |
+
'net_pnl_usd': net_pnl_usd,
|
| 340 |
+
'fees_paid_usd': entry_fee_usd + exit_fee_usd
|
| 341 |
+
})
|
| 342 |
+
|
| 343 |
+
# تحديث المحفظة
|
| 344 |
portfolio = await self.r2.get_portfolio_state_async()
|
| 345 |
+
current_total_cap = float(portfolio.get('current_capital_usd', 100.0))
|
| 346 |
+
|
| 347 |
+
# رأس المال الجديد = رأس المال القديم + صافي الربح/الخسارة
|
| 348 |
+
new_cap = current_total_cap + net_pnl_usd
|
| 349 |
|
| 350 |
portfolio['current_capital_usd'] = new_cap
|
| 351 |
portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
|
| 352 |
+
|
| 353 |
+
if net_pnl_usd >= 0:
|
| 354 |
portfolio['winning_trades'] = portfolio.get('winning_trades', 0) + 1
|
| 355 |
+
portfolio['total_profit_usd'] = portfolio.get('total_profit_usd', 0) + net_pnl_usd
|
| 356 |
trade['result'] = 'WIN'
|
| 357 |
else:
|
| 358 |
portfolio['losing_trades'] = portfolio.get('losing_trades', 0) + 1
|
| 359 |
+
portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0) + abs(net_pnl_usd)
|
| 360 |
trade['result'] = 'LOSS'
|
| 361 |
|
| 362 |
await self.r2.save_portfolio_state_async(portfolio)
|
| 363 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 364 |
await self.r2.append_to_closed_trades_history(trade)
|
| 365 |
|
| 366 |
+
print(f"✅ [EXIT] {symbol} | Net PnL: {net_profit_pct:.2f}% (${net_pnl_usd:.2f}) | Fees: ${entry_fee_usd+exit_fee_usd:.3f} | Cap: ${new_cap:.2f}")
|
| 367 |
|
| 368 |
+
self._launch_post_exit_analysis(symbol, exit_price, trade.get('exit_time'), entry_capital, ai_scores, trade)
|
| 369 |
|
| 370 |
if symbol in self.sentry_tasks:
|
| 371 |
self.sentry_tasks[symbol].cancel()
|
|
|
|
| 373 |
|
| 374 |
except Exception as e:
|
| 375 |
print(f"❌ [Exit Error] {e}")
|
| 376 |
+
traceback.print_exc()
|
| 377 |
+
# في حالة الخطأ نعيد الصفقة للقائمة لتجنب ضياعها
|
| 378 |
+
if symbol not in self.open_positions:
|
| 379 |
+
self.open_positions[symbol] = trade
|
| 380 |
|
| 381 |
async def force_exit_by_manager(self, symbol, reason):
|
| 382 |
p = await self.data_manager.get_latest_price_async(symbol)
|