Update trade_manager.py
Browse files- trade_manager.py +74 -36
trade_manager.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# trade_manager.py (Updated to V7.
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import time
|
|
@@ -17,7 +17,11 @@ except ImportError:
|
|
| 17 |
|
| 18 |
try:
|
| 19 |
import ccxt.async_support as ccxtasync
|
|
|
|
|
|
|
|
|
|
| 20 |
CCXT_ASYNC_AVAILABLE = True
|
|
|
|
| 21 |
except ImportError:
|
| 22 |
print("❌❌❌ خطأ فادح: فشل استيراد 'ccxt.async_support'. ❌❌❌")
|
| 23 |
CCXT_ASYNC_AVAILABLE = False
|
|
@@ -52,12 +56,10 @@ class TacticalData:
|
|
| 52 |
self.indicators_1m = {}
|
| 53 |
self.last_1m_candle_timestamp = None
|
| 54 |
|
| 55 |
-
# 🔴 --- START OF CHANGE (V7.5) --- 🔴
|
| 56 |
# (إضافة مؤشرات 1h لـ ATR Trailing)
|
| 57 |
self.ohlcv_1h = deque(maxlen=100)
|
| 58 |
self.indicators_1h = {}
|
| 59 |
self.last_1h_candle_timestamp = None
|
| 60 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 61 |
|
| 62 |
self.ohlcv_5m = deque(maxlen=100)
|
| 63 |
self.last_5m_candle_timestamp = None
|
|
@@ -147,8 +149,6 @@ class TacticalData:
|
|
| 147 |
if new_candles_added and len(self.ohlcv_1m) >= 26:
|
| 148 |
self._analyze_1m_indicators()
|
| 149 |
|
| 150 |
-
# 🔴 --- START OF CHANGE (V7.5) --- 🔴
|
| 151 |
-
# (دالة جديدة لحساب مؤشرات 1h لـ ATR)
|
| 152 |
def add_1h_ohlcv(self, ohlcv_data: List):
|
| 153 |
"""(جديد V7.5) إضافة شموع 1-ساعة (لـ ATR Trailing Stop)"""
|
| 154 |
if not ohlcv_data:
|
|
@@ -167,7 +167,6 @@ class TacticalData:
|
|
| 167 |
|
| 168 |
if new_candles_added and len(self.ohlcv_1h) >= 20: # (ATR يحتاج 14)
|
| 169 |
self._analyze_1h_indicators()
|
| 170 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 171 |
|
| 172 |
def _analyze_1m_indicators(self):
|
| 173 |
"""حساب مؤشرات 1-دقيقة الحقيقية (للدخول)"""
|
|
@@ -209,7 +208,6 @@ class TacticalData:
|
|
| 209 |
except Exception as e:
|
| 210 |
self.indicators_1m = {}
|
| 211 |
|
| 212 |
-
# 🔴 --- START OF CHANGE (V7.5) --- 🔴
|
| 213 |
def _analyze_1h_indicators(self):
|
| 214 |
"""(جديد V7.5) حساب مؤشرات 1-ساعة (فقط ATR حالياً)"""
|
| 215 |
if ta is None or len(self.ohlcv_1h) < 15: # (ATR يحتاج 14)
|
|
@@ -231,7 +229,6 @@ class TacticalData:
|
|
| 231 |
|
| 232 |
except Exception as e:
|
| 233 |
self.indicators_1h = {}
|
| 234 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 235 |
|
| 236 |
def add_5m_ohlcv(self, ohlcv_data: List):
|
| 237 |
"""(جديد V7.0) إضافة شموع 5-دقائق (لحماية الأرباح)"""
|
|
@@ -292,6 +289,14 @@ class TradeManager:
|
|
| 292 |
self.polling_interval = 1.5
|
| 293 |
self.confirmation_polling_interval = 3.0
|
| 294 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
self.sentry_technical_analyzer = AdvancedTechnicalAnalyzer()
|
| 296 |
|
| 297 |
if self.data_manager and self.data_manager.pattern_analyzer:
|
|
@@ -311,7 +316,11 @@ class TradeManager:
|
|
| 311 |
print("🔄 [Sentry] تهيئة منصات التداول (KuCoin REST ومنصات التأكيد)...")
|
| 312 |
|
| 313 |
print(" [Sentry] تهيئة KuCoin للبيانات العامة (وضع المحاكاة).")
|
| 314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
await self.kucoin_rest.load_markets()
|
| 316 |
print("✅ [Sentry] منصة REST الأساسية (KuCoin) جاهزة (بيانات عامة فقط).")
|
| 317 |
|
|
@@ -321,7 +330,8 @@ class TradeManager:
|
|
| 321 |
print(f" [Sentry] تهيئة منصات التأكيد (Confirmation Exchanges): {', '.join(confirmation_exchange_ids)}")
|
| 322 |
for ex_id in confirmation_exchange_ids:
|
| 323 |
try:
|
| 324 |
-
|
|
|
|
| 325 |
await exchange.load_markets()
|
| 326 |
self.confirmation_exchanges[ex_id] = exchange
|
| 327 |
print(f" ✅ [Sentry] منصة التأكيد {ex_id} جاهزة (REST).")
|
|
@@ -466,8 +476,15 @@ class TradeManager:
|
|
| 466 |
if symbol not in self.tactical_data_cache:
|
| 467 |
self.tactical_data_cache[symbol] = TacticalData(symbol)
|
| 468 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
tasks_to_gather = [
|
| 470 |
-
|
|
|
|
| 471 |
self._poll_confirmation_data(symbol),
|
| 472 |
self._run_tactical_analysis_loop(symbol, strategy_hint)
|
| 473 |
]
|
|
@@ -486,8 +503,13 @@ class TradeManager:
|
|
| 486 |
if symbol in self.tactical_data_cache:
|
| 487 |
del self.tactical_data_cache[symbol]
|
| 488 |
|
| 489 |
-
|
| 490 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
while self.is_running:
|
| 492 |
try:
|
| 493 |
if not self.kucoin_rest:
|
|
@@ -500,7 +522,6 @@ class TradeManager:
|
|
| 500 |
'trades': asyncio.create_task(self.kucoin_rest.fetch_trades(symbol, since=int((time.time() - 60) * 1000), limit=50)),
|
| 501 |
'ohlcv_1m': asyncio.create_task(self.kucoin_rest.fetch_ohlcv(symbol, '1m', limit=50)),
|
| 502 |
'ohlcv_5m': asyncio.create_task(self.kucoin_rest.fetch_ohlcv(symbol, '5m', limit=50)),
|
| 503 |
-
# (إضافة 1h لـ ATR Trailing Stop)
|
| 504 |
'ohlcv_1h': asyncio.create_task(self.kucoin_rest.fetch_ohlcv(symbol, '1h', limit=50))
|
| 505 |
}
|
| 506 |
|
|
@@ -509,14 +530,20 @@ class TradeManager:
|
|
| 509 |
if symbol not in self.tactical_data_cache:
|
| 510 |
continue
|
| 511 |
|
|
|
|
| 512 |
if not tasks['ob'].exception():
|
| 513 |
self.tactical_data_cache[symbol].set_order_book(tasks['ob'].result())
|
|
|
|
|
|
|
|
|
|
| 514 |
|
| 515 |
if not tasks['trades'].exception():
|
| 516 |
trades = tasks['trades'].result()
|
| 517 |
trades.sort(key=lambda x: x['timestamp'])
|
| 518 |
for trade in trades:
|
| 519 |
self.tactical_data_cache[symbol].add_trade(trade)
|
|
|
|
|
|
|
| 520 |
|
| 521 |
if not tasks['ohlcv_1m'].exception():
|
| 522 |
self.tactical_data_cache[symbol].add_1m_ohlcv(tasks['ohlcv_1m'].result())
|
|
@@ -524,21 +551,34 @@ class TradeManager:
|
|
| 524 |
if not tasks['ohlcv_5m'].exception():
|
| 525 |
self.tactical_data_cache[symbol].add_5m_ohlcv(tasks['ohlcv_5m'].result())
|
| 526 |
|
| 527 |
-
# 🔴 --- START OF CHANGE (V7.5) --- 🔴
|
| 528 |
if not tasks['ohlcv_1h'].exception():
|
| 529 |
self.tactical_data_cache[symbol].add_1h_ohlcv(tasks['ohlcv_1h'].result())
|
| 530 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 531 |
|
| 532 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
|
| 534 |
-
except ccxtasync.RateLimitExceeded as e:
|
| 535 |
-
print(f"⏳ [Sentry Polling] {symbol} KuCoin Rate Limit Exceeded: {e}. زيادة فترة الانتظار...")
|
| 536 |
-
await asyncio.sleep(10)
|
| 537 |
except asyncio.CancelledError:
|
| 538 |
raise
|
|
|
|
| 539 |
except Exception as e:
|
|
|
|
| 540 |
print(f"⚠️ [Sentry Polling] خ��أ في {symbol} KuCoin data polling: {e}")
|
| 541 |
-
|
|
|
|
|
|
|
|
|
|
| 542 |
|
| 543 |
async def _poll_confirmation_data(self, symbol):
|
| 544 |
if not self.confirmation_exchanges:
|
|
@@ -574,15 +614,22 @@ class TradeManager:
|
|
| 574 |
for trade in trades:
|
| 575 |
self.tactical_data_cache[symbol].add_confirmation_trade(ex_id, trade)
|
| 576 |
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
except asyncio.CancelledError:
|
| 581 |
raise
|
| 582 |
except Exception as e:
|
|
|
|
| 583 |
pass
|
| 584 |
|
| 585 |
-
# 🔴 --- START OF CHANGE (V7.5) --- 🔴
|
| 586 |
async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
|
| 587 |
"""(محدث V7.5) (دماغ الحارس) يشغل التحليل التكتيكي + ATR Trailing"""
|
| 588 |
while self.is_running:
|
|
@@ -677,7 +724,6 @@ class TradeManager:
|
|
| 677 |
except asyncio.CancelledError:
|
| 678 |
raise
|
| 679 |
except Exception as e: print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}"); traceback.print_exc()
|
| 680 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 681 |
|
| 682 |
|
| 683 |
def _check_entry_trigger(self, symbol: str, strategy_hint: str, data: Dict, all_weights: Dict) -> bool:
|
|
@@ -753,18 +799,15 @@ class TradeManager:
|
|
| 753 |
default=None
|
| 754 |
)
|
| 755 |
|
| 756 |
-
# 🔴 --- START OF CHANGE (V7.5) --- 🔴
|
| 757 |
# (التحقق فقط إذا كانت القيمة أكبر من صفر)
|
| 758 |
if hard_stop and hard_stop > 0 and current_price_for_sl and current_price_for_sl <= hard_stop:
|
| 759 |
return f"Strategic Stop Loss hit: {current_price_for_sl} <= {hard_stop}"
|
| 760 |
|
| 761 |
if take_profit and take_profit > 0 and current_price_for_tp and current_price_for_tp >= take_profit:
|
| 762 |
return f"Strategic Take Profit hit: {current_price_for_tp} >= {take_profit}"
|
| 763 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 764 |
|
| 765 |
return None
|
| 766 |
|
| 767 |
-
# 🔴 --- START OF CHANGE (V7.5) --- 🔴
|
| 768 |
async def _check_atr_trailing_stop(self, trade: Dict, snapshot: Dict, current_price: float) -> str:
|
| 769 |
"""(جديد V7.5) يحسب ويحدث وقف الخسارة المتحرك ATR"""
|
| 770 |
|
|
@@ -787,7 +830,7 @@ class TradeManager:
|
|
| 787 |
# (السعر الحالي - (قيمة ATR * المضاعف))
|
| 788 |
new_trailing_stop = current_price - (atr_1h * atr_multiplier)
|
| 789 |
|
| 790 |
-
# 4. جلب وقف
|
| 791 |
current_dynamic_sl = trade.get('dynamic_stop_loss', 0)
|
| 792 |
|
| 793 |
# 5. منطق التحديث (لا تخفض الوقف أبداً)
|
|
@@ -809,7 +852,6 @@ class TradeManager:
|
|
| 809 |
except Exception as e:
|
| 810 |
print(f"❌ [Sentry ATR] {trade['symbol']}: Error calculating ATR stop: {e}")
|
| 811 |
return None
|
| 812 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 813 |
|
| 814 |
|
| 815 |
def _create_dataframe_5m(self, candles: List) -> pd.DataFrame:
|
|
@@ -1132,7 +1174,6 @@ class TradeManager:
|
|
| 1132 |
finally:
|
| 1133 |
if self.r2_service.lock_acquired: self.r2_service.release_lock()
|
| 1134 |
|
| 1135 |
-
# 🔴 --- START OF CHANGE (V7.5) --- 🔴
|
| 1136 |
async def update_trade_strategy(self, trade_to_update, re_analysis_decision):
|
| 1137 |
"""
|
| 1138 |
(محدث V7.5)
|
|
@@ -1177,9 +1218,7 @@ class TradeManager:
|
|
| 1177 |
except Exception as e:
|
| 1178 |
print(f"❌ (Explorer) فشل تحديث استراتيجية {symbol}: {e}");
|
| 1179 |
raise
|
| 1180 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 1181 |
|
| 1182 |
-
# 🔴 --- START OF CHANGE (V7.5) --- 🔴
|
| 1183 |
async def _update_trade_dynamic_sl_in_r2(self, trade_id: str, new_dynamic_sl: float):
|
| 1184 |
"""(جديد V7.5) دالة مساعدة لتحديث الوقف الديناميكي فقط في R2"""
|
| 1185 |
try:
|
|
@@ -1198,7 +1237,6 @@ class TradeManager:
|
|
| 1198 |
|
| 1199 |
except Exception as e:
|
| 1200 |
print(f"❌ [Sentry ATR] فشل حفظ الوقف المتحرك الجديد في R2: {e}")
|
| 1201 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 1202 |
|
| 1203 |
async def _archive_closed_trade(self, closed_trade):
|
| 1204 |
try:
|
|
@@ -1237,4 +1275,4 @@ class TradeManager:
|
|
| 1237 |
except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
|
| 1238 |
|
| 1239 |
|
| 1240 |
-
print(f"✅ Trade Manager loaded - V7.
|
|
|
|
| 1 |
+
# trade_manager.py (Updated to V7.6 - Exponential Backoff for Timeouts)
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import time
|
|
|
|
| 17 |
|
| 18 |
try:
|
| 19 |
import ccxt.async_support as ccxtasync
|
| 20 |
+
# 🔴 --- START OF CHANGE (V7.6) --- 🔴
|
| 21 |
+
# (نحتاج لاستيراد الأخطاء للتعامل معها)
|
| 22 |
+
from ccxt.base.errors import RequestTimeout, RateLimitExceeded
|
| 23 |
CCXT_ASYNC_AVAILABLE = True
|
| 24 |
+
# 🔴 --- END OF CHANGE --- 🔴
|
| 25 |
except ImportError:
|
| 26 |
print("❌❌❌ خطأ فادح: فشل استيراد 'ccxt.async_support'. ❌❌❌")
|
| 27 |
CCXT_ASYNC_AVAILABLE = False
|
|
|
|
| 56 |
self.indicators_1m = {}
|
| 57 |
self.last_1m_candle_timestamp = None
|
| 58 |
|
|
|
|
| 59 |
# (إضافة مؤشرات 1h لـ ATR Trailing)
|
| 60 |
self.ohlcv_1h = deque(maxlen=100)
|
| 61 |
self.indicators_1h = {}
|
| 62 |
self.last_1h_candle_timestamp = None
|
|
|
|
| 63 |
|
| 64 |
self.ohlcv_5m = deque(maxlen=100)
|
| 65 |
self.last_5m_candle_timestamp = None
|
|
|
|
| 149 |
if new_candles_added and len(self.ohlcv_1m) >= 26:
|
| 150 |
self._analyze_1m_indicators()
|
| 151 |
|
|
|
|
|
|
|
| 152 |
def add_1h_ohlcv(self, ohlcv_data: List):
|
| 153 |
"""(جديد V7.5) إضافة شموع 1-ساعة (لـ ATR Trailing Stop)"""
|
| 154 |
if not ohlcv_data:
|
|
|
|
| 167 |
|
| 168 |
if new_candles_added and len(self.ohlcv_1h) >= 20: # (ATR يحتاج 14)
|
| 169 |
self._analyze_1h_indicators()
|
|
|
|
| 170 |
|
| 171 |
def _analyze_1m_indicators(self):
|
| 172 |
"""حساب مؤشرات 1-دقيقة الحقيقية (للدخول)"""
|
|
|
|
| 208 |
except Exception as e:
|
| 209 |
self.indicators_1m = {}
|
| 210 |
|
|
|
|
| 211 |
def _analyze_1h_indicators(self):
|
| 212 |
"""(جديد V7.5) حساب مؤشرات 1-ساعة (فقط ATR حالياً)"""
|
| 213 |
if ta is None or len(self.ohlcv_1h) < 15: # (ATR يحتاج 14)
|
|
|
|
| 229 |
|
| 230 |
except Exception as e:
|
| 231 |
self.indicators_1h = {}
|
|
|
|
| 232 |
|
| 233 |
def add_5m_ohlcv(self, ohlcv_data: List):
|
| 234 |
"""(جديد V7.0) إضافة شموع 5-دقائق (لحماية الأرباح)"""
|
|
|
|
| 289 |
self.polling_interval = 1.5
|
| 290 |
self.confirmation_polling_interval = 3.0
|
| 291 |
|
| 292 |
+
# 🔴 --- START OF CHANGE (V7.6) --- 🔴
|
| 293 |
+
# (زيادة المهلة الافتراضية للاتصال العام)
|
| 294 |
+
self.kucoin_rest_config = {
|
| 295 |
+
'enableRateLimit': True,
|
| 296 |
+
'timeout': 60000, # (زيادة المهلة إلى 60 ثانية)
|
| 297 |
+
}
|
| 298 |
+
# 🔴 --- END OF CHANGE --- 🔴
|
| 299 |
+
|
| 300 |
self.sentry_technical_analyzer = AdvancedTechnicalAnalyzer()
|
| 301 |
|
| 302 |
if self.data_manager and self.data_manager.pattern_analyzer:
|
|
|
|
| 316 |
print("🔄 [Sentry] تهيئة منصات التداول (KuCoin REST ومنصات التأكيد)...")
|
| 317 |
|
| 318 |
print(" [Sentry] تهيئة KuCoin للبيانات العامة (وضع المحاكاة).")
|
| 319 |
+
# 🔴 --- START OF CHANGE (V7.6) --- 🔴
|
| 320 |
+
# (استخدام الإعدادات الجديدة مع المهلة الممددة)
|
| 321 |
+
self.kucoin_rest = ccxtasync.kucoin(self.kucoin_rest_config)
|
| 322 |
+
# 🔴 --- END OF CHANGE --- 🔴
|
| 323 |
+
|
| 324 |
await self.kucoin_rest.load_markets()
|
| 325 |
print("✅ [Sentry] منصة REST الأساسية (KuCoin) جاهزة (بيانات عامة فقط).")
|
| 326 |
|
|
|
|
| 330 |
print(f" [Sentry] تهيئة منصات التأكيد (Confirmation Exchanges): {', '.join(confirmation_exchange_ids)}")
|
| 331 |
for ex_id in confirmation_exchange_ids:
|
| 332 |
try:
|
| 333 |
+
# (تطبيق نفس المهلة الممددة على منصات التأكيد أيضاً)
|
| 334 |
+
exchange = getattr(ccxtasync, ex_id)(self.kucoin_rest_config)
|
| 335 |
await exchange.load_markets()
|
| 336 |
self.confirmation_exchanges[ex_id] = exchange
|
| 337 |
print(f" ✅ [Sentry] منصة التأكيد {ex_id} جاهزة (REST).")
|
|
|
|
| 476 |
if symbol not in self.tactical_data_cache:
|
| 477 |
self.tactical_data_cache[symbol] = TacticalData(symbol)
|
| 478 |
|
| 479 |
+
# 🔴 --- START OF CHANGE (V7.6) --- 🔴
|
| 480 |
+
# (إضافة متغيرات الترا��ع الأسي الخاصة بهذه المهمة)
|
| 481 |
+
kucoin_backoff_delay = 5.0 # (البدء بـ 5 ثوانٍ)
|
| 482 |
+
MAX_BACKOFF = 60.0 # (الحد الأقصى 60 ثانية)
|
| 483 |
+
# 🔴 --- END OF CHANGE --- 🔴
|
| 484 |
+
|
| 485 |
tasks_to_gather = [
|
| 486 |
+
# (تمرير متغيرات التراجع)
|
| 487 |
+
self._poll_kucoin_data(symbol, kucoin_backoff_delay, MAX_BACKOFF),
|
| 488 |
self._poll_confirmation_data(symbol),
|
| 489 |
self._run_tactical_analysis_loop(symbol, strategy_hint)
|
| 490 |
]
|
|
|
|
| 503 |
if symbol in self.tactical_data_cache:
|
| 504 |
del self.tactical_data_cache[symbol]
|
| 505 |
|
| 506 |
+
# 🔴 --- START OF CHANGE (V7.6) --- 🔴
|
| 507 |
+
# (تعديل الدالة لتقبل متغيرات التراجع)
|
| 508 |
+
async def _poll_kucoin_data(self, symbol, kucoin_backoff_delay, MAX_BACKOFF):
|
| 509 |
+
"""(محدث V7.6) حلقة Polling لبيانات KuCoin (مع التراجع الأسي)"""
|
| 510 |
+
|
| 511 |
+
base_polling_interval = self.polling_interval # (الفترة العادية)
|
| 512 |
+
|
| 513 |
while self.is_running:
|
| 514 |
try:
|
| 515 |
if not self.kucoin_rest:
|
|
|
|
| 522 |
'trades': asyncio.create_task(self.kucoin_rest.fetch_trades(symbol, since=int((time.time() - 60) * 1000), limit=50)),
|
| 523 |
'ohlcv_1m': asyncio.create_task(self.kucoin_rest.fetch_ohlcv(symbol, '1m', limit=50)),
|
| 524 |
'ohlcv_5m': asyncio.create_task(self.kucoin_rest.fetch_ohlcv(symbol, '5m', limit=50)),
|
|
|
|
| 525 |
'ohlcv_1h': asyncio.create_task(self.kucoin_rest.fetch_ohlcv(symbol, '1h', limit=50))
|
| 526 |
}
|
| 527 |
|
|
|
|
| 530 |
if symbol not in self.tactical_data_cache:
|
| 531 |
continue
|
| 532 |
|
| 533 |
+
# (التحقق من الأخطاء بشكل فردي لتجنب انهيار الحلقة)
|
| 534 |
if not tasks['ob'].exception():
|
| 535 |
self.tactical_data_cache[symbol].set_order_book(tasks['ob'].result())
|
| 536 |
+
else:
|
| 537 |
+
# (رمي الخطأ ليتم التقاطه أدناه)
|
| 538 |
+
raise tasks['ob'].exception()
|
| 539 |
|
| 540 |
if not tasks['trades'].exception():
|
| 541 |
trades = tasks['trades'].result()
|
| 542 |
trades.sort(key=lambda x: x['timestamp'])
|
| 543 |
for trade in trades:
|
| 544 |
self.tactical_data_cache[symbol].add_trade(trade)
|
| 545 |
+
else:
|
| 546 |
+
raise tasks['trades'].exception()
|
| 547 |
|
| 548 |
if not tasks['ohlcv_1m'].exception():
|
| 549 |
self.tactical_data_cache[symbol].add_1m_ohlcv(tasks['ohlcv_1m'].result())
|
|
|
|
| 551 |
if not tasks['ohlcv_5m'].exception():
|
| 552 |
self.tactical_data_cache[symbol].add_5m_ohlcv(tasks['ohlcv_5m'].result())
|
| 553 |
|
|
|
|
| 554 |
if not tasks['ohlcv_1h'].exception():
|
| 555 |
self.tactical_data_cache[symbol].add_1h_ohlcv(tasks['ohlcv_1h'].result())
|
|
|
|
| 556 |
|
| 557 |
+
# (نجاح! إعادة تعيين التراجع والانتظار العادي)
|
| 558 |
+
kucoin_backoff_delay = base_polling_interval # (إعادة تعيين)
|
| 559 |
+
await asyncio.sleep(base_polling_interval)
|
| 560 |
+
|
| 561 |
+
except RateLimitExceeded as e:
|
| 562 |
+
print(f"⏳ [Sentry Polling] {symbol} KuCoin Rate Limit Exceeded: {e}. زيادة فترة الانتظار (30 ثانية)...")
|
| 563 |
+
await asyncio.sleep(30)
|
| 564 |
+
# (لا نزيد التراجع الأسي هنا، ننتظر فقط)
|
| 565 |
+
|
| 566 |
+
except RequestTimeout as e:
|
| 567 |
+
print(f"⏳ [Sentry Polling] {symbol} KuCoin Request Timeout: {e}. التراجع الأسي (الانتظار {kucoin_backoff_delay:.1f} ثانية)...")
|
| 568 |
+
await asyncio.sleep(kucoin_backoff_delay)
|
| 569 |
+
# (مضاعفة فترة الانتظار للمرة القادمة، بحد أقصى)
|
| 570 |
+
kucoin_backoff_delay = min(kucoin_backoff_delay * 2, MAX_BACKOFF)
|
| 571 |
|
|
|
|
|
|
|
|
|
|
| 572 |
except asyncio.CancelledError:
|
| 573 |
raise
|
| 574 |
+
|
| 575 |
except Exception as e:
|
| 576 |
+
# (التقاط أخطاء أخرى (مثل 404 Not Found أو أخطاء التحليل))
|
| 577 |
print(f"⚠️ [Sentry Polling] خ��أ في {symbol} KuCoin data polling: {e}")
|
| 578 |
+
# (إعادة تعيين التراجع عند الأخطاء العامة)
|
| 579 |
+
kucoin_backoff_delay = base_polling_interval
|
| 580 |
+
await asyncio.sleep(10) # (انتظار أطول من 5 ثوانٍ)
|
| 581 |
+
# 🔴 --- END OF CHANGE --- 🔴
|
| 582 |
|
| 583 |
async def _poll_confirmation_data(self, symbol):
|
| 584 |
if not self.confirmation_exchanges:
|
|
|
|
| 614 |
for trade in trades:
|
| 615 |
self.tactical_data_cache[symbol].add_confirmation_trade(ex_id, trade)
|
| 616 |
|
| 617 |
+
# 🔴 --- START OF CHANGE (V7.6) --- 🔴
|
| 618 |
+
# (التعامل مع الأخطاء الشائعة هنا أيضاً)
|
| 619 |
+
except RateLimitExceeded:
|
| 620 |
+
print(f"⏳ [Sentry Conf] {ex_id} Rate Limit لـ {symbol}. الانتظار (30 ثانية)...")
|
| 621 |
+
await asyncio.sleep(30)
|
| 622 |
+
except RequestTimeout:
|
| 623 |
+
print(f"⏳ [Sentry Conf] {ex_id} Timeout لـ {symbol}. الانتظار (15 ثانية)...")
|
| 624 |
+
await asyncio.sleep(15)
|
| 625 |
+
# 🔴 --- END OF CHANGE --- 🔴
|
| 626 |
+
|
| 627 |
except asyncio.CancelledError:
|
| 628 |
raise
|
| 629 |
except Exception as e:
|
| 630 |
+
# (نتجاهل الأخطاء بصمت هنا حتى لا نوقف الحلقة الرئيسية)
|
| 631 |
pass
|
| 632 |
|
|
|
|
| 633 |
async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
|
| 634 |
"""(محدث V7.5) (دماغ الحارس) يشغل التحليل التكتيكي + ATR Trailing"""
|
| 635 |
while self.is_running:
|
|
|
|
| 724 |
except asyncio.CancelledError:
|
| 725 |
raise
|
| 726 |
except Exception as e: print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}"); traceback.print_exc()
|
|
|
|
| 727 |
|
| 728 |
|
| 729 |
def _check_entry_trigger(self, symbol: str, strategy_hint: str, data: Dict, all_weights: Dict) -> bool:
|
|
|
|
| 799 |
default=None
|
| 800 |
)
|
| 801 |
|
|
|
|
| 802 |
# (التحقق فقط إذا كانت القيمة أكبر من صفر)
|
| 803 |
if hard_stop and hard_stop > 0 and current_price_for_sl and current_price_for_sl <= hard_stop:
|
| 804 |
return f"Strategic Stop Loss hit: {current_price_for_sl} <= {hard_stop}"
|
| 805 |
|
| 806 |
if take_profit and take_profit > 0 and current_price_for_tp and current_price_for_tp >= take_profit:
|
| 807 |
return f"Strategic Take Profit hit: {current_price_for_tp} >= {take_profit}"
|
|
|
|
| 808 |
|
| 809 |
return None
|
| 810 |
|
|
|
|
| 811 |
async def _check_atr_trailing_stop(self, trade: Dict, snapshot: Dict, current_price: float) -> str:
|
| 812 |
"""(جديد V7.5) يحسب ويحدث وقف الخسارة المتحرك ATR"""
|
| 813 |
|
|
|
|
| 830 |
# (السعر الحالي - (قيمة ATR * المضاعف))
|
| 831 |
new_trailing_stop = current_price - (atr_1h * atr_multiplier)
|
| 832 |
|
| 833 |
+
# 4. جلب وقف الخsارة الحالي (الديناميكي)
|
| 834 |
current_dynamic_sl = trade.get('dynamic_stop_loss', 0)
|
| 835 |
|
| 836 |
# 5. منطق التحديث (لا تخفض الوقف أبداً)
|
|
|
|
| 852 |
except Exception as e:
|
| 853 |
print(f"❌ [Sentry ATR] {trade['symbol']}: Error calculating ATR stop: {e}")
|
| 854 |
return None
|
|
|
|
| 855 |
|
| 856 |
|
| 857 |
def _create_dataframe_5m(self, candles: List) -> pd.DataFrame:
|
|
|
|
| 1174 |
finally:
|
| 1175 |
if self.r2_service.lock_acquired: self.r2_service.release_lock()
|
| 1176 |
|
|
|
|
| 1177 |
async def update_trade_strategy(self, trade_to_update, re_analysis_decision):
|
| 1178 |
"""
|
| 1179 |
(محدث V7.5)
|
|
|
|
| 1218 |
except Exception as e:
|
| 1219 |
print(f"❌ (Explorer) فشل تحديث استراتيجية {symbol}: {e}");
|
| 1220 |
raise
|
|
|
|
| 1221 |
|
|
|
|
| 1222 |
async def _update_trade_dynamic_sl_in_r2(self, trade_id: str, new_dynamic_sl: float):
|
| 1223 |
"""(جديد V7.5) دالة مساعدة لتحديث الوقف الديناميكي فقط في R2"""
|
| 1224 |
try:
|
|
|
|
| 1237 |
|
| 1238 |
except Exception as e:
|
| 1239 |
print(f"❌ [Sentry ATR] فشل حفظ الوقف المتحرك الجديد في R2: {e}")
|
|
|
|
| 1240 |
|
| 1241 |
async def _archive_closed_trade(self, closed_trade):
|
| 1242 |
try:
|
|
|
|
| 1275 |
except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
|
| 1276 |
|
| 1277 |
|
| 1278 |
+
print(f"✅ Trade Manager loaded - V7.6 (Exponential Backoff) (ccxt.async_support: {CCXT_ASYNC_AVAILABLE})")
|