Update trade_manager.py
Browse files- trade_manager.py +20 -26
trade_manager.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# trade_manager.py (Updated to V5.
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import time
|
|
@@ -133,9 +133,7 @@ class TradeManager:
|
|
| 133 |
self.sentry_tasks = {}
|
| 134 |
self.tactical_data_cache = {}
|
| 135 |
|
| 136 |
-
|
| 137 |
-
self.sentry_lock = asyncio.Lock() # إضافة قفل لحماية Watchlist
|
| 138 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 139 |
|
| 140 |
self.kucoin_rest = None # (المنصة الأساسية)
|
| 141 |
self.confirmation_exchanges = {} # (المنصات الثانوية للتأكيد)
|
|
@@ -189,8 +187,7 @@ class TradeManager:
|
|
| 189 |
for ex in self.confirmation_exchanges.values(): await ex.close()
|
| 190 |
raise
|
| 191 |
|
| 192 |
-
|
| 193 |
-
async def start_sentry_and_monitoring_loops(self):
|
| 194 |
"""الحلقة الرئيسية للحارس (Sentry) ومراقب الخروج (Exit Monitor)."""
|
| 195 |
self.is_running = True
|
| 196 |
print(f"✅ [Sentry] بدء حلقات المراقبة التكتيكية (Layer 2 - API Polling)...")
|
|
@@ -236,7 +233,6 @@ class TradeManager:
|
|
| 236 |
|
| 237 |
except Exception as error:
|
| 238 |
print(f"❌ [Sentry] خطأ في الحلقة الرئيسية: {error}"); traceback.print_exc(); await asyncio.sleep(60)
|
| 239 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 240 |
|
| 241 |
async def stop_sentry_loops(self):
|
| 242 |
"""(محدث) إيقاف جميع مهام المراقبة وإغلاق جميع اتصالات REST"""
|
|
@@ -259,13 +255,11 @@ class TradeManager:
|
|
| 259 |
print("✅ [Sentry] تم إغلاق اتصالات التداول (REST).")
|
| 260 |
except Exception as e: print(f"⚠️ [Sentry] خطأ أثناء إغلاق الاتصالات: {e}")
|
| 261 |
|
| 262 |
-
|
| 263 |
-
async def update_sentry_watchlist(self, candidates: List[Dict]):
|
| 264 |
"""تحديث قائمة المراقبة التي يستخدمها الحارس (Sentry)."""
|
| 265 |
async with self.sentry_lock: # (استخدام القفل قبل الكتابة)
|
| 266 |
self.sentry_watchlist = {c['symbol']: c for c in candidates}
|
| 267 |
print(f"ℹ️ [Sentry] تم تحديث Watchlist. عدد المرشحين: {len(self.sentry_watchlist)}")
|
| 268 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 269 |
|
| 270 |
def get_sentry_status(self):
|
| 271 |
"""(محدث) لواجهة برمجة التطبيقات /system-status"""
|
|
@@ -399,7 +393,7 @@ class TradeManager:
|
|
| 399 |
|
| 400 |
|
| 401 |
async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
|
| 402 |
-
"""(محدث) (دماغ الحارس) يشغل التحليل التكتيكي كل ثانية."""
|
| 403 |
while self.is_running:
|
| 404 |
await asyncio.sleep(1) # (التحليل السريع كل ثانية)
|
| 405 |
try:
|
|
@@ -419,9 +413,7 @@ class TradeManager:
|
|
| 419 |
current_price = tactical_data.order_book['bids'][0][0] if tactical_data.order_book and tactical_data.order_book.get('bids') and len(tactical_data.order_book['bids']) > 0 else None
|
| 420 |
if current_price: await self.immediate_close_trade(symbol, current_price, f"Tactical Exit: {exit_reason}")
|
| 421 |
else:
|
| 422 |
-
# (منطق الدخول - محدث)
|
| 423 |
-
# (التحقق من قائمة المراقبة يتم الآن في الحلقة الرئيسية)
|
| 424 |
-
# (لكننا نحتاج إلى التحقق مرة أخرى هنا لضمان عدم حدوث سباق حالات)
|
| 425 |
async with self.sentry_lock:
|
| 426 |
is_still_on_watchlist = symbol in self.sentry_watchlist
|
| 427 |
|
|
@@ -442,8 +434,9 @@ class TradeManager:
|
|
| 442 |
raise # تمرير الإلغاء
|
| 443 |
except Exception as e: print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}"); traceback.print_exc()
|
| 444 |
|
|
|
|
| 445 |
def _check_entry_trigger(self, symbol: str, strategy_hint: str, data: Dict) -> bool:
|
| 446 |
-
"""(محدث V5.
|
| 447 |
|
| 448 |
rsi = data.get('rsi_1m_approx', 50)
|
| 449 |
cvd_kucoin = data.get('cvd_kucoin', 0)
|
|
@@ -452,9 +445,10 @@ class TradeManager:
|
|
| 452 |
cvd_conf_agg = data.get('cvd_confirmation_aggregate', 0)
|
| 453 |
cvd_conf_sources_count = len(data.get('cvd_confirmation_sources', {}))
|
| 454 |
|
| 455 |
-
# (تم حذف شرط CVD العام من هنا)
|
| 456 |
|
| 457 |
-
|
|
|
|
| 458 |
# استراتيجية الزخم *تتطلب* تدفقاً إيجابياً
|
| 459 |
if cvd_kucoin <= 0:
|
| 460 |
return False # (لا يوجد زخم إذا كان CVD سلبياً)
|
|
@@ -463,18 +457,16 @@ class TradeManager:
|
|
| 463 |
if (rsi > 55):
|
| 464 |
# (الفيتو المخفf: نوقف فقط إذا كانت المنصات الأخرى تبيع ضدنا بقوة)
|
| 465 |
if cvd_conf_sources_count > 0 and cvd_conf_agg < (cvd_kucoin * -0.5):
|
| 466 |
-
print(f" [Trigger Hold] {symbol} Breakout: KuCoin CVD ({cvd_kucoin:.0f}) إيجابي، لكن منصات التأكيد ({cvd_conf_agg:.0f}) تبيع بقوة.")
|
| 467 |
return False
|
| 468 |
|
| 469 |
-
print(f" [Trigger] {symbol} Breakout: K_CVD={cvd_kucoin:.0f}, C_CVD_Agg={cvd_conf_agg:.0f}, RSI={rsi:.1f}");
|
| 470 |
return True
|
| 471 |
|
| 472 |
elif strategy_hint == 'mean_reversion':
|
| 473 |
# استراتيجية الانعكاس *لا* تتطلب CVD إيجابي
|
| 474 |
# (الشروط المخففة: RSI < 35)
|
| 475 |
if (rsi < 35):
|
| 476 |
-
# (اختياري: يمكننا إضافة شرط أن يكون CVD قد بدأ في التحسن،
|
| 477 |
-
# ولكن في الوقت الحالي، RSI < 35 هو زناد كافٍ)
|
| 478 |
print(f" [Trigger] {symbol} Reversion: RSI={rsi:.1f}, K_CVD={cvd_kucoin:.0f} (CVD check ignored)")
|
| 479 |
return True
|
| 480 |
|
|
@@ -486,15 +478,18 @@ class TradeManager:
|
|
| 486 |
return True
|
| 487 |
|
| 488 |
return False
|
| 489 |
-
# 🔴 --- END OF LOGIC
|
| 490 |
|
| 491 |
def _check_exit_trigger(self, trade: Dict, data: Dict) -> str: # (لا تغيير هنا)
|
| 492 |
"""يحدد ما إذا كان يجب الخروج من صفقة مفتوحة تكتيكياً."""
|
| 493 |
rsi = data.get('rsi_1m_approx', 50); cvd_kucoin = data.get('cvd_kucoin', 0); symbol = trade['symbol']
|
| 494 |
-
|
| 495 |
-
|
|
|
|
|
|
|
| 496 |
if trade.get('strategy') == 'mean_reversion':
|
| 497 |
if rsi > 75: return "Mean Reversion Target Hit (RSI > 75)"
|
|
|
|
| 498 |
if symbol in self.tactical_data_cache and self.tactical_data_cache[symbol].order_book:
|
| 499 |
ob = self.tactical_data_cache[symbol].order_book
|
| 500 |
if ob and ob.get('bids') and len(ob['bids']) > 0: # (تحقق إضافي)
|
|
@@ -627,7 +622,6 @@ class TradeManager:
|
|
| 627 |
await self.r2_service.save_open_trades_async(trades_to_keep)
|
| 628 |
|
| 629 |
# (لا حاجة لإزالة المهمة يدوياً، الحلقة الرئيسية ستفعل ذلك)
|
| 630 |
-
# if symbol in self.sentry_tasks: print(f"ℹ️ [Sentry] الصفقة {symbol} أغلقت، ستتوقف المراقبة.")
|
| 631 |
|
| 632 |
await self.r2_service.save_system_logs_async({
|
| 633 |
"trade_closed": True, "symbol": symbol, "pnl_usd": pnl, "pnl_percent": pnl_percent,
|
|
@@ -715,4 +709,4 @@ class TradeManager:
|
|
| 715 |
except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
|
| 716 |
|
| 717 |
|
| 718 |
-
print(f"✅ Trade Manager loaded - V5.
|
|
|
|
| 1 |
+
# trade_manager.py (Updated to V5.9 - Added trend_following to Triggers)
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import time
|
|
|
|
| 133 |
self.sentry_tasks = {}
|
| 134 |
self.tactical_data_cache = {}
|
| 135 |
|
| 136 |
+
self.sentry_lock = asyncio.Lock() # (إصلاح V5.8)
|
|
|
|
|
|
|
| 137 |
|
| 138 |
self.kucoin_rest = None # (المنصة الأساسية)
|
| 139 |
self.confirmation_exchanges = {} # (المنصات الثانوية للتأكيد)
|
|
|
|
| 187 |
for ex in self.confirmation_exchanges.values(): await ex.close()
|
| 188 |
raise
|
| 189 |
|
| 190 |
+
async def start_sentry_and_monitoring_loops(self): # (محدث V5.8)
|
|
|
|
| 191 |
"""الحلقة الرئيسية للحارس (Sentry) ومراقب الخروج (Exit Monitor)."""
|
| 192 |
self.is_running = True
|
| 193 |
print(f"✅ [Sentry] بدء حلقات المراقبة التكتيكية (Layer 2 - API Polling)...")
|
|
|
|
| 233 |
|
| 234 |
except Exception as error:
|
| 235 |
print(f"❌ [Sentry] خطأ في الحلقة الرئيسية: {error}"); traceback.print_exc(); await asyncio.sleep(60)
|
|
|
|
| 236 |
|
| 237 |
async def stop_sentry_loops(self):
|
| 238 |
"""(محدث) إيقاف جميع مهام المراقبة وإغلاق جميع اتصالات REST"""
|
|
|
|
| 255 |
print("✅ [Sentry] تم إغلاق اتصالات التداول (REST).")
|
| 256 |
except Exception as e: print(f"⚠️ [Sentry] خطأ أثناء إغلاق الاتصالات: {e}")
|
| 257 |
|
| 258 |
+
async def update_sentry_watchlist(self, candidates: List[Dict]): # (محدث V5.8)
|
|
|
|
| 259 |
"""تحديث قائمة المراقبة التي يستخدمها الحارس (Sentry)."""
|
| 260 |
async with self.sentry_lock: # (استخدام القفل قبل الكتابة)
|
| 261 |
self.sentry_watchlist = {c['symbol']: c for c in candidates}
|
| 262 |
print(f"ℹ️ [Sentry] تم تحديث Watchlist. عدد المرشحين: {len(self.sentry_watchlist)}")
|
|
|
|
| 263 |
|
| 264 |
def get_sentry_status(self):
|
| 265 |
"""(محدث) لواجهة برمجة التطبيقات /system-status"""
|
|
|
|
| 393 |
|
| 394 |
|
| 395 |
async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
|
| 396 |
+
"""(محدث V5.8) (دماغ الحارس) يشغل التحليل التكتيكي كل ثانية."""
|
| 397 |
while self.is_running:
|
| 398 |
await asyncio.sleep(1) # (التحليل السريع كل ثانية)
|
| 399 |
try:
|
|
|
|
| 413 |
current_price = tactical_data.order_book['bids'][0][0] if tactical_data.order_book and tactical_data.order_book.get('bids') and len(tactical_data.order_book['bids']) > 0 else None
|
| 414 |
if current_price: await self.immediate_close_trade(symbol, current_price, f"Tactical Exit: {exit_reason}")
|
| 415 |
else:
|
| 416 |
+
# (منطق الدخول - محدث V5.8)
|
|
|
|
|
|
|
| 417 |
async with self.sentry_lock:
|
| 418 |
is_still_on_watchlist = symbol in self.sentry_watchlist
|
| 419 |
|
|
|
|
| 434 |
raise # تمرير الإلغاء
|
| 435 |
except Exception as e: print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}"); traceback.print_exc()
|
| 436 |
|
| 437 |
+
# 🔴 --- START OF CHANGE (LOGIC FIXED V5.9) --- 🔴
|
| 438 |
def _check_entry_trigger(self, symbol: str, strategy_hint: str, data: Dict) -> bool:
|
| 439 |
+
"""(محدث V5.9) إضافة trend_following إلى منطق الزناد."""
|
| 440 |
|
| 441 |
rsi = data.get('rsi_1m_approx', 50)
|
| 442 |
cvd_kucoin = data.get('cvd_kucoin', 0)
|
|
|
|
| 445 |
cvd_conf_agg = data.get('cvd_confirmation_aggregate', 0)
|
| 446 |
cvd_conf_sources_count = len(data.get('cvd_confirmation_sources', {}))
|
| 447 |
|
| 448 |
+
# (تم حذف شرط CVD العام من هنا - إصلاح V5.7)
|
| 449 |
|
| 450 |
+
# (إصلاح V5.9: دمج trend_following مع breakout_momentum)
|
| 451 |
+
if strategy_hint in ['breakout_momentum', 'trend_following']:
|
| 452 |
# استراتيجية الزخم *تتطلب* تدفقاً إيجابياً
|
| 453 |
if cvd_kucoin <= 0:
|
| 454 |
return False # (لا يوجد زخم إذا كان CVD سلبياً)
|
|
|
|
| 457 |
if (rsi > 55):
|
| 458 |
# (الفيتو المخفf: نوقف فقط إذا كانت المنصات الأخرى تبيع ضدنا بقوة)
|
| 459 |
if cvd_conf_sources_count > 0 and cvd_conf_agg < (cvd_kucoin * -0.5):
|
| 460 |
+
print(f" [Trigger Hold] {symbol} Breakout/Trend: KuCoin CVD ({cvd_kucoin:.0f}) إيجابي، لكن منصات التأكيد ({cvd_conf_agg:.0f}) تبيع بقوة.")
|
| 461 |
return False
|
| 462 |
|
| 463 |
+
print(f" [Trigger] {symbol} Breakout/Trend: K_CVD={cvd_kucoin:.0f}, C_CVD_Agg={cvd_conf_agg:.0f}, RSI={rsi:.1f}");
|
| 464 |
return True
|
| 465 |
|
| 466 |
elif strategy_hint == 'mean_reversion':
|
| 467 |
# استراتيجية الانعكاس *لا* تتطلب CVD إيجابي
|
| 468 |
# (الشروط المخففة: RSI < 35)
|
| 469 |
if (rsi < 35):
|
|
|
|
|
|
|
| 470 |
print(f" [Trigger] {symbol} Reversion: RSI={rsi:.1f}, K_CVD={cvd_kucoin:.0f} (CVD check ignored)")
|
| 471 |
return True
|
| 472 |
|
|
|
|
| 478 |
return True
|
| 479 |
|
| 480 |
return False
|
| 481 |
+
# 🔴 --- END OF CHANGE (LOGIC FIXED V5.9) --- 🔴
|
| 482 |
|
| 483 |
def _check_exit_trigger(self, trade: Dict, data: Dict) -> str: # (لا تغيير هنا)
|
| 484 |
"""يحدد ما إذا كان يجب الخروج من صفقة مفتوحة تكتيكياً."""
|
| 485 |
rsi = data.get('rsi_1m_approx', 50); cvd_kucoin = data.get('cvd_kucoin', 0); symbol = trade['symbol']
|
| 486 |
+
|
| 487 |
+
# (تحديث منطق الخروج ليشمل trend_following)
|
| 488 |
+
if trade.get('strategy') in ['breakout_momentum', 'trend_following']:
|
| 489 |
+
if rsi < 40 and cvd_kucoin < 0: return "Momentum/Trend reversal (RSI < 40 + CVD Negative)"
|
| 490 |
if trade.get('strategy') == 'mean_reversion':
|
| 491 |
if rsi > 75: return "Mean Reversion Target Hit (RSI > 75)"
|
| 492 |
+
|
| 493 |
if symbol in self.tactical_data_cache and self.tactical_data_cache[symbol].order_book:
|
| 494 |
ob = self.tactical_data_cache[symbol].order_book
|
| 495 |
if ob and ob.get('bids') and len(ob['bids']) > 0: # (تحقق إضافي)
|
|
|
|
| 622 |
await self.r2_service.save_open_trades_async(trades_to_keep)
|
| 623 |
|
| 624 |
# (لا حاجة لإزالة المهمة يدوياً، الحلقة الرئيسية ستفعل ذلك)
|
|
|
|
| 625 |
|
| 626 |
await self.r2_service.save_system_logs_async({
|
| 627 |
"trade_closed": True, "symbol": symbol, "pnl_usd": pnl, "pnl_percent": pnl_percent,
|
|
|
|
| 709 |
except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
|
| 710 |
|
| 711 |
|
| 712 |
+
print(f"✅ Trade Manager loaded - V5.9 (Fixed trend_following Trigger) (ccxt.async_support: {CCXT_ASYNC_AVAILABLE})")
|