Spaces:
Running
Running
Update trade_manager.py
Browse files- trade_manager.py +28 -60
trade_manager.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# trade_manager.py (Updated to V6.
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import time
|
|
@@ -8,15 +8,12 @@ from datetime import datetime, timedelta
|
|
| 8 |
from typing import Dict, Any, List
|
| 9 |
from collections import deque, defaultdict
|
| 10 |
|
| 11 |
-
# 🔴 --- START OF CHANGE (V6.7) --- 🔴
|
| 12 |
-
# (إضافة pandas لتحليل مؤشرات 1-دقيقة)
|
| 13 |
import pandas as pd
|
| 14 |
try:
|
| 15 |
import pandas_ta as ta
|
| 16 |
except ImportError:
|
| 17 |
print("⚠️ مكتبة pandas_ta غير موجودة، مؤشرات الحارس (Sentry 1m) ستفشل.")
|
| 18 |
ta = None
|
| 19 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 20 |
|
| 21 |
try:
|
| 22 |
import ccxt.async_support as ccxtasync
|
|
@@ -47,16 +44,11 @@ class TacticalData:
|
|
| 47 |
self.last_kucoin_trade_id = None
|
| 48 |
self.last_confirmation_trade_ids = defaultdict(lambda: None)
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
# (إضافة: بيانات ومؤشرات 1-دقيقة)
|
| 53 |
-
self.ohlcv_1m = deque(maxlen=100) # (لتخزين 100 شمعة 1-دقيقة)
|
| 54 |
-
self.indicators_1m = {} # (لتخزين EMA 9, EMA 21, MACD Hist)
|
| 55 |
self.last_1m_candle_timestamp = None
|
| 56 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 57 |
|
| 58 |
def add_trade(self, trade):
|
| 59 |
-
"""إضافة صفقة KuCoin (الأساسية)"""
|
| 60 |
trade_id = trade.get('id')
|
| 61 |
if trade_id and trade_id == self.last_kucoin_trade_id:
|
| 62 |
return
|
|
@@ -77,7 +69,6 @@ class TacticalData:
|
|
| 77 |
except Exception: pass
|
| 78 |
|
| 79 |
def add_confirmation_trade(self, exchange_id: str, trade: Dict):
|
| 80 |
-
"""(جديد) إضافة صفقة تأكيد (Bybit, OKX, etc.)"""
|
| 81 |
trade_id = trade.get('id')
|
| 82 |
if trade_id and trade_id == self.last_confirmation_trade_ids[exchange_id]:
|
| 83 |
return
|
|
@@ -103,43 +94,34 @@ class TacticalData:
|
|
| 103 |
return {"bids_depth": bids_depth, "asks_depth": asks_depth}
|
| 104 |
except Exception: return {"bids_depth": 0, "asks_depth": 0}
|
| 105 |
|
| 106 |
-
# 🔴 --- START OF CHANGE (V6.7) --- 🔴
|
| 107 |
-
# (حذف دالة get_1m_rsi التقريبية)
|
| 108 |
-
|
| 109 |
def add_1m_ohlcv(self, ohlcv_data: List):
|
| 110 |
-
"""(جديد V6.7) إضافة شموع 1-دقيقة وحساب المؤشرات"""
|
| 111 |
if not ohlcv_data:
|
| 112 |
return
|
| 113 |
|
| 114 |
-
# (إضافة الشموع الجديدة فقط)
|
| 115 |
new_candles_added = False
|
| 116 |
for candle in ohlcv_data:
|
| 117 |
timestamp = candle[0]
|
| 118 |
if timestamp and timestamp != self.last_1m_candle_timestamp:
|
| 119 |
if self.ohlcv_1m and timestamp < self.ohlcv_1m[-1][0]:
|
| 120 |
-
continue
|
| 121 |
|
| 122 |
self.ohlcv_1m.append(candle)
|
| 123 |
self.last_1m_candle_timestamp = timestamp
|
| 124 |
new_candles_added = True
|
| 125 |
|
| 126 |
-
|
| 127 |
-
if new_candles_added and len(self.ohlcv_1m) >= 26: # (26 كافية لـ EMA 21 و MACD)
|
| 128 |
self._analyze_1m_indicators()
|
| 129 |
|
| 130 |
def _analyze_1m_indicators(self):
|
| 131 |
-
"""(جديد V6.7) حساب مؤشرات 1-دقيقة الحقيقية"""
|
| 132 |
if ta is None or len(self.ohlcv_1m) < 26:
|
| 133 |
self.indicators_1m = {}
|
| 134 |
return
|
| 135 |
|
| 136 |
try:
|
| 137 |
-
# (تحويل deque إلى DataFrame للمعالجة)
|
| 138 |
df = pd.DataFrame(list(self.ohlcv_1m), columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
| 139 |
df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
|
| 140 |
close = df['close']
|
| 141 |
|
| 142 |
-
# (حساب المؤشرات المطلوبة)
|
| 143 |
ema_9 = ta.ema(close, length=9)
|
| 144 |
ema_21 = ta.ema(close, length=21)
|
| 145 |
macd_data = ta.macd(close, fast=12, slow=26, signal=9)
|
|
@@ -157,29 +139,25 @@ class TacticalData:
|
|
| 157 |
self.indicators_1m = {}
|
| 158 |
|
| 159 |
except Exception as e:
|
| 160 |
-
# print(f"⚠️ [Sentry] خطأ في حساب مؤشرات 1m لـ {self.symbol}: {e}")
|
| 161 |
self.indicators_1m = {}
|
| 162 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 163 |
|
| 164 |
def get_tactical_snapshot(self):
|
| 165 |
-
"""(محدث V6.7) لإرجاع مؤشرات 1-دقيقة الحقيقية"""
|
| 166 |
agg_cvd = sum(self.confirmation_cvd.values())
|
| 167 |
|
| 168 |
-
# 🔴 --- (تغيير V6.7) --- 🔴
|
| 169 |
return {
|
| 170 |
"cvd_kucoin": self.cvd,
|
| 171 |
"cvd_confirmation_sources": dict(self.confirmation_cvd),
|
| 172 |
"cvd_confirmation_aggregate": agg_cvd,
|
| 173 |
"large_trades_count_5m": len([t for t in self.large_trades if t.get('timestamp') and (time.time() - t['timestamp']/1000) < 300]),
|
| 174 |
-
|
| 175 |
-
"indicators_1m": self.indicators_1m, # (إضافة المؤشرات الجديدة)
|
| 176 |
"ob_analysis": self.analyze_order_book()
|
| 177 |
}
|
| 178 |
-
# 🔴 --- نهاية التغيير --- 🔴
|
| 179 |
|
| 180 |
|
| 181 |
class TradeManager:
|
| 182 |
-
|
|
|
|
|
|
|
| 183 |
if not CCXT_ASYNC_AVAILABLE:
|
| 184 |
raise RuntimeError("مكتبة 'ccxt.async_support' غير متاحة.")
|
| 185 |
|
|
@@ -187,6 +165,12 @@ class TradeManager:
|
|
| 187 |
self.learning_hub = learning_hub
|
| 188 |
self.data_manager = data_manager
|
| 189 |
self.state_manager = state_manager
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
self.is_running = False
|
| 191 |
self.sentry_watchlist = {}
|
| 192 |
self.sentry_tasks = {}
|
|
@@ -354,8 +338,6 @@ class TradeManager:
|
|
| 354 |
await asyncio.sleep(10)
|
| 355 |
continue
|
| 356 |
|
| 357 |
-
# 🔴 --- START OF CHANGE (V6.7) --- 🔴
|
| 358 |
-
# (جلب 3 أنواع بيانات بالتوازي)
|
| 359 |
tasks = {
|
| 360 |
'ob': asyncio.create_task(self.kucoin_rest.fetch_order_book(symbol, limit=20)),
|
| 361 |
'trades': asyncio.create_task(self.kucoin_rest.fetch_trades(symbol, since=int((time.time() - 60) * 1000), limit=50)),
|
|
@@ -365,23 +347,19 @@ class TradeManager:
|
|
| 365 |
await asyncio.wait(tasks.values(), return_when=asyncio.ALL_COMPLETED)
|
| 366 |
|
| 367 |
if symbol not in self.tactical_data_cache:
|
| 368 |
-
continue
|
| 369 |
|
| 370 |
-
# 1. معالجة دفتر الطلبات
|
| 371 |
if not tasks['ob'].exception():
|
| 372 |
self.tactical_data_cache[symbol].set_order_book(tasks['ob'].result())
|
| 373 |
|
| 374 |
-
# 2. معالجة آخر الصفقات
|
| 375 |
if not tasks['trades'].exception():
|
| 376 |
trades = tasks['trades'].result()
|
| 377 |
trades.sort(key=lambda x: x['timestamp'])
|
| 378 |
for trade in trades:
|
| 379 |
self.tactical_data_cache[symbol].add_trade(trade)
|
| 380 |
|
| 381 |
-
# 3. معالجة شموع 1-دقيقة
|
| 382 |
if not tasks['ohlcv_1m'].exception():
|
| 383 |
self.tactical_data_cache[symbol].add_1m_ohlcv(tasks['ohlcv_1m'].result())
|
| 384 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 385 |
|
| 386 |
await asyncio.sleep(self.polling_interval)
|
| 387 |
|
|
@@ -472,8 +450,6 @@ class TradeManager:
|
|
| 472 |
is_still_on_watchlist = symbol in self.sentry_watchlist
|
| 473 |
|
| 474 |
if is_still_on_watchlist:
|
| 475 |
-
# 🔴 --- (تغيير V6.7) --- 🔴
|
| 476 |
-
# (snapshot يحتوي الآن على مؤشرات 1-دقيقة الحقيقية)
|
| 477 |
trigger = self._check_entry_trigger(symbol, strategy_hint, snapshot)
|
| 478 |
if trigger:
|
| 479 |
print(f"✅ [Sentry] زناد دخول تكتيكي لـ {symbol} (استراتيجية: {strategy_hint})")
|
|
@@ -489,11 +465,9 @@ class TradeManager:
|
|
| 489 |
raise
|
| 490 |
except Exception as e: print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}"); traceback.print_exc()
|
| 491 |
|
| 492 |
-
# 🔴 --- START OF CHANGE (V6.7) --- 🔴
|
| 493 |
def _check_entry_trigger(self, symbol: str, strategy_hint: str, data: Dict) -> bool:
|
| 494 |
"""(محدث V6.7) زناد ثلاثي: CVD + دفتر الطلبات + مؤشرات 1-دقيقة"""
|
| 495 |
|
| 496 |
-
# --- جلب البيانات ---
|
| 497 |
cvd_kucoin = data.get('cvd_kucoin', 0)
|
| 498 |
|
| 499 |
ob_analysis = data.get('ob_analysis', {})
|
|
@@ -504,38 +478,24 @@ class TradeManager:
|
|
| 504 |
ema_9_1m = indicators_1m.get('ema_9')
|
| 505 |
ema_21_1m = indicators_1m.get('ema_21')
|
| 506 |
|
| 507 |
-
# (للاستراتيجيات الأخرى)
|
| 508 |
large_trades = data.get('large_trades_count_5m', 0)
|
| 509 |
|
| 510 |
-
# --- منطق الزناد ---
|
| 511 |
if strategy_hint in ['breakout_momentum', 'trend_following']:
|
| 512 |
|
| 513 |
-
# (الشرط 0: يجب أن تتوفر بيانات 1-دقيقة)
|
| 514 |
if ema_9_1m is None or ema_21_1m is None:
|
| 515 |
return False
|
| 516 |
|
| 517 |
-
# (الشرط 1: زخم الصفقات - CVD)
|
| 518 |
cvd_check = (cvd_kucoin > 0)
|
| 519 |
-
|
| 520 |
-
# (الشرط 2: زخم دفتر الطلبات - OB Depth)
|
| 521 |
ob_check = (bids_depth > asks_depth)
|
| 522 |
-
|
| 523 |
-
# (الشرط 3: زخم الاتجاه - 1m EMAs)
|
| 524 |
ema_check = (ema_9_1m > ema_21_1m)
|
| 525 |
|
| 526 |
if cvd_check and ob_check and ema_check:
|
| 527 |
print(f" [Trigger] {symbol} (Momentum): CVD+, OB+, 1m_EMA+. الدخول!")
|
| 528 |
return True
|
| 529 |
-
# (إذا فشل، لا نطبع شيئاً لتقليل التشويش)
|
| 530 |
|
| 531 |
elif strategy_hint == 'mean_reversion':
|
| 532 |
-
# (
|
| 533 |
-
|
| 534 |
-
# rsi = data.get('rsi_1m_approx', 50) # (محذوف)
|
| 535 |
-
# if (rsi < 35):
|
| 536 |
-
# print(f" [Trigger] {symbol} Reversion: RSI={rsi:.1f}")
|
| 537 |
-
# return True
|
| 538 |
-
pass # (يبقى معطلاً حتى نضيف مؤشرات انعكاس 1-دقيقة)
|
| 539 |
|
| 540 |
elif strategy_hint == 'volume_spike':
|
| 541 |
if (large_trades > 0):
|
|
@@ -543,7 +503,6 @@ class TradeManager:
|
|
| 543 |
return True
|
| 544 |
|
| 545 |
return False
|
| 546 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 547 |
|
| 548 |
def _check_exit_trigger(self, trade: Dict, data: Dict, tactical_data: TacticalData) -> str:
|
| 549 |
"""(محدث V6.6) يراقب وقف الخسارة وجني الأرباح باستخدام (Bid) و (Last Trade Price)"""
|
|
@@ -763,6 +722,15 @@ class TradeManager:
|
|
| 763 |
print(f"🧠 [LearningHub] تشغيل التعلم (Reflector+Stats) لـ {symbol}...")
|
| 764 |
await self.learning_hub.analyze_trade_and_learn(trade_to_close, reason)
|
| 765 |
else: print("⚠️ [Sentry] LearningHub غير متاح، تم تخطي التعلم.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 766 |
print(f"✅ [Executor] تم إغلاق الصفقة (الوهمية) {symbol} - السبب: {reason} - PnL: {pnl_percent:+.2f}%")
|
| 767 |
return True
|
| 768 |
except Exception as e: print(f"❌ [Executor] فشل فادح أثناء إغلاق الصفقة (الوهمية) {symbol}: {e}"); traceback.print_exc(); raise
|
|
@@ -839,4 +807,4 @@ class TradeManager:
|
|
| 839 |
except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
|
| 840 |
|
| 841 |
|
| 842 |
-
print(f"✅ Trade Manager loaded - V6.
|
|
|
|
| 1 |
+
# trade_manager.py (Updated to V6.8 - Added Post-Close Cycle Trigger)
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import time
|
|
|
|
| 8 |
from typing import Dict, Any, List
|
| 9 |
from collections import deque, defaultdict
|
| 10 |
|
|
|
|
|
|
|
| 11 |
import pandas as pd
|
| 12 |
try:
|
| 13 |
import pandas_ta as ta
|
| 14 |
except ImportError:
|
| 15 |
print("⚠️ مكتبة pandas_ta غير موجودة، مؤشرات الحارس (Sentry 1m) ستفشل.")
|
| 16 |
ta = None
|
|
|
|
| 17 |
|
| 18 |
try:
|
| 19 |
import ccxt.async_support as ccxtasync
|
|
|
|
| 44 |
self.last_kucoin_trade_id = None
|
| 45 |
self.last_confirmation_trade_ids = defaultdict(lambda: None)
|
| 46 |
|
| 47 |
+
self.ohlcv_1m = deque(maxlen=100)
|
| 48 |
+
self.indicators_1m = {}
|
|
|
|
|
|
|
|
|
|
| 49 |
self.last_1m_candle_timestamp = None
|
|
|
|
| 50 |
|
| 51 |
def add_trade(self, trade):
|
|
|
|
| 52 |
trade_id = trade.get('id')
|
| 53 |
if trade_id and trade_id == self.last_kucoin_trade_id:
|
| 54 |
return
|
|
|
|
| 69 |
except Exception: pass
|
| 70 |
|
| 71 |
def add_confirmation_trade(self, exchange_id: str, trade: Dict):
|
|
|
|
| 72 |
trade_id = trade.get('id')
|
| 73 |
if trade_id and trade_id == self.last_confirmation_trade_ids[exchange_id]:
|
| 74 |
return
|
|
|
|
| 94 |
return {"bids_depth": bids_depth, "asks_depth": asks_depth}
|
| 95 |
except Exception: return {"bids_depth": 0, "asks_depth": 0}
|
| 96 |
|
|
|
|
|
|
|
|
|
|
| 97 |
def add_1m_ohlcv(self, ohlcv_data: List):
|
|
|
|
| 98 |
if not ohlcv_data:
|
| 99 |
return
|
| 100 |
|
|
|
|
| 101 |
new_candles_added = False
|
| 102 |
for candle in ohlcv_data:
|
| 103 |
timestamp = candle[0]
|
| 104 |
if timestamp and timestamp != self.last_1m_candle_timestamp:
|
| 105 |
if self.ohlcv_1m and timestamp < self.ohlcv_1m[-1][0]:
|
| 106 |
+
continue
|
| 107 |
|
| 108 |
self.ohlcv_1m.append(candle)
|
| 109 |
self.last_1m_candle_timestamp = timestamp
|
| 110 |
new_candles_added = True
|
| 111 |
|
| 112 |
+
if new_candles_added and len(self.ohlcv_1m) >= 26:
|
|
|
|
| 113 |
self._analyze_1m_indicators()
|
| 114 |
|
| 115 |
def _analyze_1m_indicators(self):
|
|
|
|
| 116 |
if ta is None or len(self.ohlcv_1m) < 26:
|
| 117 |
self.indicators_1m = {}
|
| 118 |
return
|
| 119 |
|
| 120 |
try:
|
|
|
|
| 121 |
df = pd.DataFrame(list(self.ohlcv_1m), columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
| 122 |
df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
|
| 123 |
close = df['close']
|
| 124 |
|
|
|
|
| 125 |
ema_9 = ta.ema(close, length=9)
|
| 126 |
ema_21 = ta.ema(close, length=21)
|
| 127 |
macd_data = ta.macd(close, fast=12, slow=26, signal=9)
|
|
|
|
| 139 |
self.indicators_1m = {}
|
| 140 |
|
| 141 |
except Exception as e:
|
|
|
|
| 142 |
self.indicators_1m = {}
|
|
|
|
| 143 |
|
| 144 |
def get_tactical_snapshot(self):
|
|
|
|
| 145 |
agg_cvd = sum(self.confirmation_cvd.values())
|
| 146 |
|
|
|
|
| 147 |
return {
|
| 148 |
"cvd_kucoin": self.cvd,
|
| 149 |
"cvd_confirmation_sources": dict(self.confirmation_cvd),
|
| 150 |
"cvd_confirmation_aggregate": agg_cvd,
|
| 151 |
"large_trades_count_5m": len([t for t in self.large_trades if t.get('timestamp') and (time.time() - t['timestamp']/1000) < 300]),
|
| 152 |
+
"indicators_1m": self.indicators_1m,
|
|
|
|
| 153 |
"ob_analysis": self.analyze_order_book()
|
| 154 |
}
|
|
|
|
| 155 |
|
| 156 |
|
| 157 |
class TradeManager:
|
| 158 |
+
# 🔴 --- START OF CHANGE (V6.8) --- 🔴
|
| 159 |
+
def __init__(self, r2_service, learning_hub=None, data_manager=None, state_manager=None, callback_on_close=None):
|
| 160 |
+
# 🔴 --- END OF CHANGE --- 🔴
|
| 161 |
if not CCXT_ASYNC_AVAILABLE:
|
| 162 |
raise RuntimeError("مكتبة 'ccxt.async_support' غير متاحة.")
|
| 163 |
|
|
|
|
| 165 |
self.learning_hub = learning_hub
|
| 166 |
self.data_manager = data_manager
|
| 167 |
self.state_manager = state_manager
|
| 168 |
+
|
| 169 |
+
# 🔴 --- START OF CHANGE (V6.8) --- 🔴
|
| 170 |
+
# (حفظ دالة رد النداء)
|
| 171 |
+
self.callback_on_close = callback_on_close
|
| 172 |
+
# 🔴 --- END OF CHANGE --- 🔴
|
| 173 |
+
|
| 174 |
self.is_running = False
|
| 175 |
self.sentry_watchlist = {}
|
| 176 |
self.sentry_tasks = {}
|
|
|
|
| 338 |
await asyncio.sleep(10)
|
| 339 |
continue
|
| 340 |
|
|
|
|
|
|
|
| 341 |
tasks = {
|
| 342 |
'ob': asyncio.create_task(self.kucoin_rest.fetch_order_book(symbol, limit=20)),
|
| 343 |
'trades': asyncio.create_task(self.kucoin_rest.fetch_trades(symbol, since=int((time.time() - 60) * 1000), limit=50)),
|
|
|
|
| 347 |
await asyncio.wait(tasks.values(), return_when=asyncio.ALL_COMPLETED)
|
| 348 |
|
| 349 |
if symbol not in self.tactical_data_cache:
|
| 350 |
+
continue
|
| 351 |
|
|
|
|
| 352 |
if not tasks['ob'].exception():
|
| 353 |
self.tactical_data_cache[symbol].set_order_book(tasks['ob'].result())
|
| 354 |
|
|
|
|
| 355 |
if not tasks['trades'].exception():
|
| 356 |
trades = tasks['trades'].result()
|
| 357 |
trades.sort(key=lambda x: x['timestamp'])
|
| 358 |
for trade in trades:
|
| 359 |
self.tactical_data_cache[symbol].add_trade(trade)
|
| 360 |
|
|
|
|
| 361 |
if not tasks['ohlcv_1m'].exception():
|
| 362 |
self.tactical_data_cache[symbol].add_1m_ohlcv(tasks['ohlcv_1m'].result())
|
|
|
|
| 363 |
|
| 364 |
await asyncio.sleep(self.polling_interval)
|
| 365 |
|
|
|
|
| 450 |
is_still_on_watchlist = symbol in self.sentry_watchlist
|
| 451 |
|
| 452 |
if is_still_on_watchlist:
|
|
|
|
|
|
|
| 453 |
trigger = self._check_entry_trigger(symbol, strategy_hint, snapshot)
|
| 454 |
if trigger:
|
| 455 |
print(f"✅ [Sentry] زناد دخول تكتيكي لـ {symbol} (استراتيجية: {strategy_hint})")
|
|
|
|
| 465 |
raise
|
| 466 |
except Exception as e: print(f"❌ [Sentry] خطأ في حلقة التحليل التكتيكي لـ {symbol}: {e}"); traceback.print_exc()
|
| 467 |
|
|
|
|
| 468 |
def _check_entry_trigger(self, symbol: str, strategy_hint: str, data: Dict) -> bool:
|
| 469 |
"""(محدث V6.7) زناد ثلاثي: CVD + دفتر الطلبات + مؤشرات 1-دقيقة"""
|
| 470 |
|
|
|
|
| 471 |
cvd_kucoin = data.get('cvd_kucoin', 0)
|
| 472 |
|
| 473 |
ob_analysis = data.get('ob_analysis', {})
|
|
|
|
| 478 |
ema_9_1m = indicators_1m.get('ema_9')
|
| 479 |
ema_21_1m = indicators_1m.get('ema_21')
|
| 480 |
|
|
|
|
| 481 |
large_trades = data.get('large_trades_count_5m', 0)
|
| 482 |
|
|
|
|
| 483 |
if strategy_hint in ['breakout_momentum', 'trend_following']:
|
| 484 |
|
|
|
|
| 485 |
if ema_9_1m is None or ema_21_1m is None:
|
| 486 |
return False
|
| 487 |
|
|
|
|
| 488 |
cvd_check = (cvd_kucoin > 0)
|
|
|
|
|
|
|
| 489 |
ob_check = (bids_depth > asks_depth)
|
|
|
|
|
|
|
| 490 |
ema_check = (ema_9_1m > ema_21_1m)
|
| 491 |
|
| 492 |
if cvd_check and ob_check and ema_check:
|
| 493 |
print(f" [Trigger] {symbol} (Momentum): CVD+, OB+, 1m_EMA+. الدخول!")
|
| 494 |
return True
|
|
|
|
| 495 |
|
| 496 |
elif strategy_hint == 'mean_reversion':
|
| 497 |
+
# (ملاحظة: هذا الزناد معطل مؤقتاً لعدم وجود مؤشرات انعكاس 1-دقيقة)
|
| 498 |
+
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
|
| 500 |
elif strategy_hint == 'volume_spike':
|
| 501 |
if (large_trades > 0):
|
|
|
|
| 503 |
return True
|
| 504 |
|
| 505 |
return False
|
|
|
|
| 506 |
|
| 507 |
def _check_exit_trigger(self, trade: Dict, data: Dict, tactical_data: TacticalData) -> str:
|
| 508 |
"""(محدث V6.6) يراقب وقف الخسارة وجني الأرباح باستخدام (Bid) و (Last Trade Price)"""
|
|
|
|
| 722 |
print(f"🧠 [LearningHub] تشغيل التعلم (Reflector+Stats) لـ {symbol}...")
|
| 723 |
await self.learning_hub.analyze_trade_and_learn(trade_to_close, reason)
|
| 724 |
else: print("⚠️ [Sentry] LearningHub غير متاح، تم تخطي التعلم.")
|
| 725 |
+
|
| 726 |
+
# 🔴 --- START OF CHANGE (V6.8) --- 🔴
|
| 727 |
+
# (تشغيل الدورة الجديدة بعد الإغلاق)
|
| 728 |
+
if self.callback_on_close:
|
| 729 |
+
print("🔄 [Executor] Trade closed. Scheduling immediate Explorer cycle...")
|
| 730 |
+
# (استخدام create_task لتجنب الجمود (Deadlock))
|
| 731 |
+
asyncio.create_task(self.callback_on_close())
|
| 732 |
+
# 🔴 --- END OF CHANGE --- 🔴
|
| 733 |
+
|
| 734 |
print(f"✅ [Executor] تم إغلاق الصفقة (الوهمية) {symbol} - السبب: {reason} - PnL: {pnl_percent:+.2f}%")
|
| 735 |
return True
|
| 736 |
except Exception as e: print(f"❌ [Executor] فشل فادح أثناء إغلاق الصفقة (الوهمية) {symbol}: {e}"); traceback.print_exc(); raise
|
|
|
|
| 807 |
except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
|
| 808 |
|
| 809 |
|
| 810 |
+
print(f"✅ Trade Manager loaded - V6.8 (1M Trigger + Post-Close Cycle) (ccxt.async_support: {CCXT_ASYNC_AVAILABLE})")
|