Update trade_manager.py
Browse files- trade_manager.py +63 -26
trade_manager.py
CHANGED
|
@@ -18,6 +18,18 @@ class TradeManager:
|
|
| 18 |
|
| 19 |
async def open_trade(self, symbol, decision, current_price):
|
| 20 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
portfolio_state = await self.r2_service.get_portfolio_state_async()
|
| 22 |
available_capital = portfolio_state.get("current_capital_usd", 0)
|
| 23 |
|
|
@@ -43,7 +55,7 @@ class TradeManager:
|
|
| 43 |
"status": "OPEN",
|
| 44 |
"stop_loss": decision.get("stop_loss"),
|
| 45 |
"take_profit": decision.get("take_profit"),
|
| 46 |
-
"trade_type":
|
| 47 |
"position_size_usd": available_capital,
|
| 48 |
"expected_target_minutes": expected_target_minutes,
|
| 49 |
"expected_target_time": expected_target_time,
|
|
@@ -65,11 +77,11 @@ class TradeManager:
|
|
| 65 |
"symbol": symbol,
|
| 66 |
"position_size": available_capital,
|
| 67 |
"expected_minutes": expected_target_minutes,
|
| 68 |
-
"trade_type":
|
| 69 |
"strategy": strategy
|
| 70 |
})
|
| 71 |
|
| 72 |
-
print(f"✅ تم فتح صفقة جديدة لـ {symbol} باستراتيجية {strategy}")
|
| 73 |
return new_trade
|
| 74 |
|
| 75 |
except Exception as e:
|
|
@@ -91,7 +103,8 @@ class TradeManager:
|
|
| 91 |
|
| 92 |
entry_price = trade_to_close['entry_price']
|
| 93 |
position_size = trade_to_close['position_size_usd']
|
| 94 |
-
|
|
|
|
| 95 |
strategy = trade_to_close.get('strategy', 'unknown')
|
| 96 |
|
| 97 |
pnl = 0.0
|
|
@@ -99,12 +112,13 @@ class TradeManager:
|
|
| 99 |
|
| 100 |
if entry_price and entry_price > 0 and close_price and close_price > 0:
|
| 101 |
try:
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
| 108 |
|
| 109 |
except (TypeError, ZeroDivisionError) as calc_error:
|
| 110 |
pnl = 0.0
|
|
@@ -153,7 +167,7 @@ class TradeManager:
|
|
| 153 |
"new_capital": new_capital,
|
| 154 |
"strategy": strategy,
|
| 155 |
"position_size": position_size,
|
| 156 |
-
"trade_type": trade_type,
|
| 157 |
"reason": reason
|
| 158 |
})
|
| 159 |
|
|
@@ -193,6 +207,7 @@ class TradeManager:
|
|
| 193 |
trade_to_update['strategy'] = original_strategy
|
| 194 |
trade_to_update['decision_data'] = re_analysis_decision
|
| 195 |
trade_to_update['is_monitored'] = True
|
|
|
|
| 196 |
|
| 197 |
open_trades = await self.r2_service.get_open_trades_async()
|
| 198 |
for i, trade in enumerate(open_trades):
|
|
@@ -304,7 +319,6 @@ class TradeManager:
|
|
| 304 |
|
| 305 |
# الحصول على السعر الحالي مع مهلة زمنية
|
| 306 |
try:
|
| 307 |
-
# ✅ الإصلاح: استخدام await بشكل صحيح مع دالة get_latest_price_async
|
| 308 |
current_price = await asyncio.wait_for(
|
| 309 |
self.data_manager.get_latest_price_async(symbol),
|
| 310 |
timeout=10
|
|
@@ -324,43 +338,55 @@ class TradeManager:
|
|
| 324 |
entry_price = trade['entry_price']
|
| 325 |
stop_loss = trade.get('stop_loss')
|
| 326 |
take_profit = trade.get('take_profit')
|
|
|
|
|
|
|
| 327 |
should_close, close_reason = False, ""
|
| 328 |
|
| 329 |
-
# التحقق من شروط الإغلاق
|
| 330 |
if stop_loss and current_price <= stop_loss:
|
| 331 |
-
should_close, close_reason = True, f"وصول وقف
|
| 332 |
elif take_profit and current_price >= take_profit:
|
| 333 |
-
should_close, close_reason = True, f"وصول جني
|
| 334 |
|
| 335 |
-
# تحديث وقف الخسارة الديناميكي
|
| 336 |
if not should_close and current_price > entry_price:
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
|
| 341 |
# إغلاق الصفقة إذا لزم الأمر
|
| 342 |
if should_close:
|
| 343 |
if self.r2_service.acquire_lock():
|
| 344 |
try:
|
| 345 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
except Exception as close_error:
|
| 347 |
print(f"❌ فشل الإغلاق التلقائي لـ {symbol}: {close_error}")
|
| 348 |
finally:
|
| 349 |
self.r2_service.release_lock()
|
| 350 |
|
| 351 |
-
break
|
| 352 |
|
| 353 |
# إعادة تعيين عداد الأخطاء عند النجاح
|
| 354 |
if symbol in self.monitoring_errors:
|
| 355 |
self.monitoring_errors[symbol] = 0
|
| 356 |
|
| 357 |
# التحقق من وقت المراقبة الطويل
|
| 358 |
-
monitoring_duration = time.time() - start_time
|
| 359 |
if monitoring_duration > max_monitoring_time:
|
| 360 |
-
print(f"🕒 انتهى وقت مراقبة الصفقة {symbol}")
|
|
|
|
| 361 |
break
|
| 362 |
|
| 363 |
-
await asyncio.sleep(15)
|
| 364 |
|
| 365 |
except Exception as error:
|
| 366 |
error_count = self._increment_monitoring_error(symbol)
|
|
@@ -374,11 +400,22 @@ class TradeManager:
|
|
| 374 |
"error_count": error_count,
|
| 375 |
"error": str(error)
|
| 376 |
})
|
| 377 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
|
| 379 |
-
await asyncio.sleep(30)
|
| 380 |
|
| 381 |
print(f"🛑 توقيف مراقبة الصفقة: {symbol}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
def _increment_monitoring_error(self, symbol):
|
| 384 |
if symbol not in self.monitoring_errors:
|
|
|
|
| 18 |
|
| 19 |
async def open_trade(self, symbol, decision, current_price):
|
| 20 |
try:
|
| 21 |
+
# ✅ الإصلاح: التحقق من نوع الصفقة ومنع صفقات SHORT
|
| 22 |
+
trade_type = decision.get("trade_type", "LONG") # الافتراضي هو LONG
|
| 23 |
+
if trade_type == "SHORT":
|
| 24 |
+
print(f"⚠️ تم رفض فتح صفقة SHORT لـ {symbol}. النظام مصمم لـ SPOT فقط.")
|
| 25 |
+
await self.r2_service.save_system_logs_async({
|
| 26 |
+
"trade_open_rejected": True,
|
| 27 |
+
"reason": "SHORT trade not allowed for SPOT system",
|
| 28 |
+
"symbol": symbol,
|
| 29 |
+
"llm_decision": decision
|
| 30 |
+
})
|
| 31 |
+
return None # لا تفتح الصفقة
|
| 32 |
+
|
| 33 |
portfolio_state = await self.r2_service.get_portfolio_state_async()
|
| 34 |
available_capital = portfolio_state.get("current_capital_usd", 0)
|
| 35 |
|
|
|
|
| 55 |
"status": "OPEN",
|
| 56 |
"stop_loss": decision.get("stop_loss"),
|
| 57 |
"take_profit": decision.get("take_profit"),
|
| 58 |
+
"trade_type": "LONG", # ✅ الإصلاح: فرض LONG دائماً لـ SPOT
|
| 59 |
"position_size_usd": available_capital,
|
| 60 |
"expected_target_minutes": expected_target_minutes,
|
| 61 |
"expected_target_time": expected_target_time,
|
|
|
|
| 77 |
"symbol": symbol,
|
| 78 |
"position_size": available_capital,
|
| 79 |
"expected_minutes": expected_target_minutes,
|
| 80 |
+
"trade_type": "LONG", # تم فرض LONG
|
| 81 |
"strategy": strategy
|
| 82 |
})
|
| 83 |
|
| 84 |
+
print(f"✅ تم فتح صفقة جديدة (LONG) لـ {symbol} باستراتيجية {strategy}")
|
| 85 |
return new_trade
|
| 86 |
|
| 87 |
except Exception as e:
|
|
|
|
| 103 |
|
| 104 |
entry_price = trade_to_close['entry_price']
|
| 105 |
position_size = trade_to_close['position_size_usd']
|
| 106 |
+
# ✅ الإصلاح: بما أن النظام SPOT فقط، نفترض دائماً LONG لحساب PnL
|
| 107 |
+
trade_type = "LONG" # trade_to_close.get('trade_type', 'LONG')
|
| 108 |
strategy = trade_to_close.get('strategy', 'unknown')
|
| 109 |
|
| 110 |
pnl = 0.0
|
|
|
|
| 112 |
|
| 113 |
if entry_price and entry_price > 0 and close_price and close_price > 0:
|
| 114 |
try:
|
| 115 |
+
# ✅ الإصلاح: استخدام معادلة LONG فقط
|
| 116 |
+
# if trade_type == 'LONG':
|
| 117 |
+
pnl_percent = ((close_price - entry_price) / entry_price) * 100
|
| 118 |
+
pnl = position_size * (pnl_percent / 100)
|
| 119 |
+
# elif trade_type == 'SHORT': # <-- إزالة هذا القسم
|
| 120 |
+
# pnl_percent = ((entry_price - close_price) / entry_price) * 100
|
| 121 |
+
# pnl = position_size * (pnl_percent / 100)
|
| 122 |
|
| 123 |
except (TypeError, ZeroDivisionError) as calc_error:
|
| 124 |
pnl = 0.0
|
|
|
|
| 167 |
"new_capital": new_capital,
|
| 168 |
"strategy": strategy,
|
| 169 |
"position_size": position_size,
|
| 170 |
+
"trade_type": trade_type, # سيظل LONG دائماً
|
| 171 |
"reason": reason
|
| 172 |
})
|
| 173 |
|
|
|
|
| 207 |
trade_to_update['strategy'] = original_strategy
|
| 208 |
trade_to_update['decision_data'] = re_analysis_decision
|
| 209 |
trade_to_update['is_monitored'] = True
|
| 210 |
+
trade_to_update['trade_type'] = "LONG" # ✅ الإصلاح: التأكد من بقائها LONG
|
| 211 |
|
| 212 |
open_trades = await self.r2_service.get_open_trades_async()
|
| 213 |
for i, trade in enumerate(open_trades):
|
|
|
|
| 319 |
|
| 320 |
# الحصول على السعر الحالي مع مهلة زمنية
|
| 321 |
try:
|
|
|
|
| 322 |
current_price = await asyncio.wait_for(
|
| 323 |
self.data_manager.get_latest_price_async(symbol),
|
| 324 |
timeout=10
|
|
|
|
| 338 |
entry_price = trade['entry_price']
|
| 339 |
stop_loss = trade.get('stop_loss')
|
| 340 |
take_profit = trade.get('take_profit')
|
| 341 |
+
# ✅ الإصلاح: نفترض LONG دائماً عند التحقق من الإغلاق لـ SPOT
|
| 342 |
+
trade_type = "LONG"
|
| 343 |
should_close, close_reason = False, ""
|
| 344 |
|
| 345 |
+
# التحقق من شروط الإغلاق (مع افتراض LONG)
|
| 346 |
if stop_loss and current_price <= stop_loss:
|
| 347 |
+
should_close, close_reason = True, f"وصول وقف الخسارة (LONG): {current_price} <= {stop_loss}"
|
| 348 |
elif take_profit and current_price >= take_profit:
|
| 349 |
+
should_close, close_reason = True, f"وصول جني الأرباح (LONG): {current_price} >= {take_profit}"
|
| 350 |
|
| 351 |
+
# تحديث وقف الخسارة الديناميكي (Trailing Stop Loss) لصفقات LONG
|
| 352 |
if not should_close and current_price > entry_price:
|
| 353 |
+
# مثال بسيط: تحديد وقف خسارة عند 2% تحت السعر الحالي إذا كان أعلى من وقف الخسارة الأصلي
|
| 354 |
+
potential_new_stop = current_price * 0.98
|
| 355 |
+
current_stop_loss = trade.get('stop_loss', 0) or 0 # التعامل مع None
|
| 356 |
+
if potential_new_stop > current_stop_loss:
|
| 357 |
+
trade['stop_loss'] = potential_new_stop
|
| 358 |
+
print(f"📈 {symbol}: تم تحديث وقف الخسارة الديناميكي إلى {potential_new_stop:.6f}")
|
| 359 |
+
# ملاحظة: هذا التحديث محلي للمهمة، قد تحتاج لحفظه في R2 إذا أردت استمراره
|
| 360 |
|
| 361 |
# إغلاق الصفقة إذا لزم الأمر
|
| 362 |
if should_close:
|
| 363 |
if self.r2_service.acquire_lock():
|
| 364 |
try:
|
| 365 |
+
# قبل الإغلاق، تأكد من جلب أحدث نسخة من الصفقة من R2
|
| 366 |
+
latest_trade_data = await self.r2_service.get_trade_by_symbol_async(symbol)
|
| 367 |
+
if latest_trade_data and latest_trade_data['status'] == 'OPEN':
|
| 368 |
+
await self.immediate_close_trade(symbol, current_price, close_reason)
|
| 369 |
+
else:
|
| 370 |
+
print(f"⚠️ الصفقة {symbol} لم تعد مفتوحة أو غير موجودة، تم إلغاء الإغلاق.")
|
| 371 |
except Exception as close_error:
|
| 372 |
print(f"❌ فشل الإغلاق التلقائي لـ {symbol}: {close_error}")
|
| 373 |
finally:
|
| 374 |
self.r2_service.release_lock()
|
| 375 |
|
| 376 |
+
break # الخروج من حلقة المراقبة بعد محاولة الإغلاق
|
| 377 |
|
| 378 |
# إعادة تعيين عداد الأخطاء عند النجاح
|
| 379 |
if symbol in self.monitoring_errors:
|
| 380 |
self.monitoring_errors[symbol] = 0
|
| 381 |
|
| 382 |
# التحقق من وقت المراقبة الطويل
|
| 383 |
+
monitoring_duration = time.time() - self.monitoring_tasks[symbol]['start_time'] # استخدام وقت البدء الأصلي
|
| 384 |
if monitoring_duration > max_monitoring_time:
|
| 385 |
+
print(f"🕒 انتهى وقت مراقبة الصفقة {symbol} ({monitoring_duration:.0f}s > {max_monitoring_time}s)")
|
| 386 |
+
# يمكنك إضافة منطق هنا لإغلاق الصفقة إذا طالت مدتها، أو تركها لإعادة التحليل
|
| 387 |
break
|
| 388 |
|
| 389 |
+
await asyncio.sleep(15) # انتظار 15 ثانية بين كل فحص
|
| 390 |
|
| 391 |
except Exception as error:
|
| 392 |
error_count = self._increment_monitoring_error(symbol)
|
|
|
|
| 400 |
"error_count": error_count,
|
| 401 |
"error": str(error)
|
| 402 |
})
|
| 403 |
+
# اختياري: محاولة إغلاق الصفقة كإجراء أخير
|
| 404 |
+
# current_price_fallback = await self.data_manager.get_latest_price_async(symbol)
|
| 405 |
+
# if current_price_fallback and self.r2_service.acquire_lock():
|
| 406 |
+
# try: await self.immediate_close_trade(symbol, current_price_fallback, "Forced close due to monitoring errors")
|
| 407 |
+
# finally: self.r2_service.release_lock()
|
| 408 |
+
break # الخروج من الحلقة بسبب الأخطاء
|
| 409 |
|
| 410 |
+
await asyncio.sleep(30) # انتظار أطول بعد الخطأ
|
| 411 |
|
| 412 |
print(f"🛑 توقيف مراقبة الصفقة: {symbol}")
|
| 413 |
+
# التأكد من إزالة المهمة عند الخروج من الحلقة
|
| 414 |
+
if symbol in self.monitoring_tasks:
|
| 415 |
+
del self.monitoring_tasks[symbol]
|
| 416 |
+
if symbol in self.monitoring_errors:
|
| 417 |
+
del self.monitoring_errors[symbol]
|
| 418 |
+
|
| 419 |
|
| 420 |
def _increment_monitoring_error(self, symbol):
|
| 421 |
if symbol not in self.monitoring_errors:
|