Riy777 commited on
Commit
c27f49c
·
verified ·
1 Parent(s): e0ee6fd

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +190 -237
ml_engine/data_manager.py CHANGED
@@ -1,6 +1,6 @@
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
- # (V15.2 - GEM-Architect: Anti-FOMO Shield - Strict Marksman Mode)
4
  # ============================================================
5
 
6
  import asyncio
@@ -12,22 +12,23 @@ import pandas as pd
12
  import numpy as np
13
  from typing import List, Dict, Any
14
 
 
 
 
 
 
 
15
  # إعدادات التسجيل
16
  logging.getLogger("httpx").setLevel(logging.WARNING)
17
- logging.getLogger("httpcore").setLevel(logging.WARNING)
18
  logging.getLogger("ccxt").setLevel(logging.WARNING)
19
 
20
  class DataManager:
21
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
22
- # ==================================================================
23
- # ⚙️ إعدادات التحكم
24
- # ==================================================================
25
  self.contracts_db = contracts_db or {}
26
  self.whale_monitor = whale_monitor
27
  self.r2_service = r2_service
28
- self.adaptive_hub_ref = None # مرجع لملف التعلم
29
 
30
- # إعداد المنصة (KuCoin)
31
  self.exchange = ccxt.kucoin({
32
  'enableRateLimit': True,
33
  'timeout': 60000,
@@ -37,31 +38,25 @@ class DataManager:
37
  self.http_client = None
38
  self.market_cache = {}
39
 
40
- # قوائم الاستبعاد (العملات المستقرة والعملات ذات الرافعة)
41
  self.BLACKLIST_TOKENS = [
42
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
43
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
44
  ]
45
 
46
- print(f"📦 [DataManager V15.2] Restored 'Anti-FOMO' Logic Shield.")
47
 
48
  async def initialize(self):
49
- """تهيئة مدير البيانات والاتصالات"""
50
  print(" > [DataManager] Starting initialization...")
51
  self.http_client = httpx.AsyncClient(timeout=30.0)
52
  await self._load_markets()
53
- # تحميل العقود إذا وجدت
54
  await self.load_contracts_from_r2()
55
- print(f"✅ [DataManager] Ready. Logic: STRICT/Anti-FOMO.")
56
 
57
  async def _load_markets(self):
58
  try:
59
  if self.exchange:
60
  await self.exchange.load_markets()
61
  self.market_cache = self.exchange.markets
62
- except Exception as e:
63
- print(f"❌ [DataManager] Market load failed: {e}")
64
- traceback.print_exc()
65
 
66
  async def close(self):
67
  if self.http_client: await self.http_client.aclose()
@@ -71,283 +66,241 @@ class DataManager:
71
  if not self.r2_service: return
72
  try:
73
  self.contracts_db = await self.r2_service.load_contracts_db_async()
74
- except Exception:
75
- self.contracts_db = {}
76
 
77
  def get_contracts_db(self) -> Dict[str, Any]:
78
  return self.contracts_db
79
-
80
  # ==================================================================
81
- # 🛡️ Layer 1: The Strict Logic Tree Screening (Core Logic)
82
  # ==================================================================
83
  async def layer1_rapid_screening(self, adaptive_hub_ref=None) -> List[Dict[str, Any]]:
84
  """
85
- يقوم بمسح السوق وتقسيم الفرص إلى نوعين:
86
- 1. Breakout: اختراق آمن (RSI < 70).
87
- 2. Reversal: ارتداد من القاع (RSI < 40).
88
- أي شيء آخر يتم استبعاده فوراً.
89
  """
90
  self.adaptive_hub_ref = adaptive_hub_ref
91
- print(f"🔍 [Layer 1] Initiating STRICT Logic Tree Screening...")
92
 
93
- # 1. المرحلة 0: فلتر الكون (السيولة الأساسية)
94
  initial_candidates = await self._stage0_universe_filter()
95
-
96
- if not initial_candidates:
97
- return []
98
 
99
- # 2. جلب البيانات الفنية لأفضل العملات سيولة (Top 100 لتقليل الضغط)
100
- top_liquid_candidates = initial_candidates[:100]
101
- enriched_data = await self._fetch_technical_data_batch(top_liquid_candidates)
102
 
103
- # 3. تطبيق شجرة القرار الصارمة (Anti-FOMO)
104
- breakout_list = []
105
- reversal_list = []
106
 
107
  for item in enriched_data:
108
- classification = self._apply_logic_tree(item)
 
109
 
110
- # حقن التكوين الديناميكي (إذا وجد Hub)
111
- if self.adaptive_hub_ref:
112
- regime = "BULL" if classification['type'] == 'BREAKOUT' else "RANGE"
113
- item['dynamic_limits'] = self.adaptive_hub_ref.get_regime_config(regime)
114
-
115
- if classification['type'] == 'BREAKOUT':
116
- item['l1_sort_score'] = classification['score']
117
- item['strategy_tag'] = 'Safe_Breakout'
118
- breakout_list.append(item)
119
- elif classification['type'] == 'REVERSAL':
 
 
 
 
120
  item['l1_sort_score'] = classification['score']
121
- item['strategy_tag'] = 'Dip_Sniper'
122
- reversal_list.append(item)
123
-
124
- print(f" -> [L1 Logic] Found: {len(breakout_list)} Breakouts, {len(reversal_list)} Reversals.")
125
 
126
- # 4. الترتيب والدمج النهائي
127
- # الاختراق: نرتب بالأعلى سكور (حجم تداول نسبي)
128
- breakout_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
129
- # الارتداد: نرتب بالأعلى سكور (كلما كان الـ RSI أقل كان السكور أعلى في منطقنا)
130
- reversal_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
131
-
132
- # نختار صفوة الصفوة
133
- final_selection = breakout_list[:25] + reversal_list[:25]
134
 
135
- # تنظيف البيانات للإرجاع
136
- # نحتفظ بالبيانات الفنية لأن المعالج سيحتاجها
137
- print(f"✅ [Layer 1] Final Selection: {len(final_selection)} candidates (Anti-FOMO Active).")
138
- return final_selection
139
 
140
  # ------------------------------------------------------------------
141
- # Stage 0: Universe Filter (Basic Liquidity)
142
  # ------------------------------------------------------------------
143
- async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
 
 
 
 
144
  try:
145
- tickers = await self.exchange.fetch_tickers()
146
- candidates = []
 
147
 
148
- for symbol, ticker in tickers.items():
149
- if not symbol.endswith('/USDT'): continue
150
-
151
- base_curr = symbol.split('/')[0]
152
- if any(bad in base_curr for bad in self.BLACKLIST_TOKENS): continue
153
-
154
- # 👇 الحد الأدنى للسيولة (1 مليون دولار لضمان التنفيذ السريع)
155
- quote_vol = ticker.get('quoteVolume')
156
- if not quote_vol or quote_vol < 1_000_000: continue
157
-
158
- last_price = ticker.get('last')
159
- if not last_price or last_price < 0.0005: continue
160
-
161
- # 👇 فلتر أولي: استبعاد العملات التي انفجرت بجنون (+15% فأكثر يتم تجاهلها مبدئياً)
162
- change_24h = ticker.get('percentage', 0.0)
163
- if change_24h > 15.0: continue
164
-
165
- candidates.append({
166
- 'symbol': symbol,
167
- 'quote_volume': quote_vol,
168
- 'current_price': last_price,
169
- 'change_24h': change_24h
170
- })
171
 
172
- # الترتيب حسب السيولة
173
- candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
174
- return candidates
 
 
175
 
176
- except Exception as e:
177
- print(f"❌ [L1 Error] Universe filter failed: {e}")
178
- return []
179
-
180
- # ------------------------------------------------------------------
181
- # Data Fetching Helpers (Batch Processing)
182
- # ------------------------------------------------------------------
183
- async def _fetch_technical_data_batch(self, candidates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
184
- chunk_size = 10
185
- results = []
186
- for i in range(0, len(candidates), chunk_size):
187
- chunk = candidates[i:i + chunk_size]
188
- chunk_tasks = [self._fetch_single_tech_data(c) for c in chunk]
189
- chunk_results = await asyncio.gather(*chunk_tasks)
190
- results.extend([r for r in chunk_results if r is not None])
191
- # تأخير بسيط جداً لتجنب حظر API
192
- await asyncio.sleep(0.05)
193
- return results
194
-
195
- async def _fetch_single_tech_data(self, candidate: Dict[str, Any]) -> Any:
196
- symbol = candidate['symbol']
197
- try:
198
- # نحتاج 1H للاتجاه العام و 15M للدخول الدقيق
199
- ohlcv_1h = await self.exchange.fetch_ohlcv(symbol, '1h', limit=60)
200
- ohlcv_15m = await self.exchange.fetch_ohlcv(symbol, '15m', limit=60)
201
 
202
- if not ohlcv_1h or len(ohlcv_1h) < 55 or not ohlcv_15m or len(ohlcv_15m) < 55:
203
- return None
204
 
205
- # تخزين البيانات الخام لاستخدامها لاحقاً في المعالج
206
- candidate['ohlcv'] = {
207
- '1h': ohlcv_1h,
208
- '15m': ohlcv_15m,
209
- # يمكن إضافة 5m لاحقاً عند الحاجة للدقة القصوى
210
- }
 
 
 
 
 
 
 
 
 
 
 
211
 
212
- # نسخ للاستخدام الداخلي في الفلتر
213
- candidate['ohlcv_1h_raw'] = ohlcv_1h
214
- candidate['ohlcv_15m_raw'] = ohlcv_15m
215
- return candidate
216
  except Exception:
217
- return None
218
 
219
  # ------------------------------------------------------------------
220
- # 🧠 The Logic Core: Math & Decision Tree (STRICT ANTI-FOMO MODE)
221
  # ------------------------------------------------------------------
222
- def _apply_logic_tree(self, data: Dict[str, Any]) -> Dict[str, Any]:
223
  try:
 
224
  df_1h = self._calc_indicators(data['ohlcv_1h_raw'])
225
  df_15m = self._calc_indicators(data['ohlcv_15m_raw'])
226
- except:
227
- return {'type': 'NONE'}
 
 
 
228
 
229
  curr_1h = df_1h.iloc[-1]
230
  curr_15m = df_15m.iloc[-1]
231
 
232
- # --- Stage 2: Overbought Filter (STRICT MODE 🛡️) ---
233
  try:
234
  close_4h_ago = df_1h.iloc[-5]['close']
235
  change_4h = ((curr_1h['close'] - close_4h_ago) / close_4h_ago) * 100
236
  except: change_4h = 0.0
237
 
238
- # 1. Anti-Pump: إذا صعدت أكثر من 8% في 4 ساعات، اتركها
239
- if change_4h > 8.0: return {'type': 'NONE'}
240
-
241
- # 2. Anti-FOMO: إذا صعدت أكثر من 12% في اليوم، اتركها
242
- if data.get('change_24h', 0) > 12.0: return {'type': 'NONE'}
243
-
244
- # 3. RSI Ceiling: ممنوع الدخول إذا RSI فوق 70 (منطقة تشبع)
245
- if curr_1h['rsi'] > 70: return {'type': 'NONE'}
246
 
247
- # 4. Mean Reversion Risk: السعر بعيد جداً عن المتوسط
248
- deviation = (curr_1h['close'] - curr_1h['ema20']) / curr_1h['atr'] if curr_1h['atr'] > 0 else 0
249
- if deviation > 1.8: return {'type': 'NONE'}
250
-
251
- # --- Stage 3: Classification ---
252
-
253
- # === A. Breakout Logic (Safe & Early) ===
254
- is_breakout = False
255
- breakout_score = 0.0
256
 
257
- # شروط الترند الصاعد الصحي
258
- bullish_structure = (curr_1h['ema20'] > curr_1h['ema50']) or (curr_1h['close'] > curr_1h['ema20'])
259
-
260
- if bullish_structure:
261
- # RSI يجب أن يكون لديه مساحة للصعود (بين 45 و 68)
262
- if 45 <= curr_1h['rsi'] <= 68:
263
- if curr_15m['close'] >= curr_15m['ema20']:
264
- # Volatility Squeeze: السعر يتحرك في نطاق ضيق
265
- avg_range = (df_15m['high'] - df_15m['low']).rolling(10).mean().iloc[-1]
266
- if (curr_15m['high'] - curr_15m['low']) <= avg_range * 1.8:
267
- vol_ma20 = df_15m['volume'].rolling(20).mean().iloc[-1]
268
-
269
- # Volume Confirmation: حجم تداول أعلى من المتوسط بـ 1.5 مرة
270
- if curr_15m['volume'] >= 1.5 * vol_ma20:
271
- is_breakout = True
272
- breakout_score = curr_15m['volume'] / vol_ma20 if vol_ma20 > 0 else 1.0
273
 
274
- if is_breakout:
275
- return {'type': 'BREAKOUT', 'score': breakout_score}
 
 
 
 
276
 
277
- # === B. Reversal Logic (Dip Buy / Oversold) ===
278
- is_reversal = False
279
- reversal_score = 100.0
280
-
281
- # شراء التشبع البيعي فقط (RSI بين 20 و 40)
282
- if 20 <= curr_1h['rsi'] <= 40:
283
- # السعر هبط مؤخراً
284
- if change_4h <= -2.0:
285
- # البحث عن شمعة انعكاسية (مطرقة Hammer أو ابتلاعية Engulfing) في آخر 3 شموع 15m
286
- last_3 = df_15m.iloc[-3:]
287
- found_rejection = False
288
- for _, row in last_3.iterrows():
289
- rng = row['high'] - row['low']
290
- if rng > 0:
291
- is_green = row['close'] > row['open']
292
- # شكل المطرقة: الذيل السفلي طويل
293
- hammer_shape = (min(row['open'], row['close']) - row['low']) > (rng * 0.6)
294
- if is_green or hammer_shape:
295
- found_rejection = True
296
- break
297
 
298
- if found_rejection:
299
- is_reversal = True
300
- # كلما قل الـ RSI زادت احتمالية الارتداد (سكور أعلى)
301
- reversal_score = (100 - curr_1h['rsi'])
 
 
 
 
 
302
 
303
- if is_reversal:
304
- return {'type': 'REVERSAL', 'score': reversal_score}
 
 
 
 
 
 
 
305
 
306
- return {'type': 'NONE'}
 
 
 
 
 
 
 
 
 
 
307
 
308
- def _calc_indicators(self, ohlcv_list):
309
- # حسابات يدوية سريعة باستخدام Pandas (بدون مكتبات خارجية لتقليل التبعيات في الفلتر)
310
- df = pd.DataFrame(ohlcv_list, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
311
-
312
  # RSI
313
- delta = df['close'].diff()
314
- gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
315
- loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
316
- rs = gain / loss
317
- df['rsi'] = 100 - (100 / (1 + rs))
318
-
319
  # EMAs
320
- df['ema20'] = df['close'].ewm(span=20, adjust=False).mean()
321
- df['ema50'] = df['close'].ewm(span=50, adjust=False).mean()
322
-
323
  # ATR
324
- high_low = df['high'] - df['low']
325
- high_close = np.abs(df['high'] - df['close'].shift())
326
- low_close = np.abs(df['low'] - df['close'].shift())
327
- ranges = pd.concat([high_low, high_close, low_close], axis=1)
328
- true_range = np.max(ranges, axis=1)
329
- df['atr'] = true_range.rolling(14).mean()
330
 
331
- df.fillna(0, inplace=True)
332
- return df
333
-
334
- # ==================================================================
335
- # 🎯 Public Helpers (Standard Interface)
336
- # ==================================================================
337
- async def get_latest_price_async(self, symbol: str) -> float:
338
- try:
339
- ticker = await self.exchange.fetch_ticker(symbol)
340
- return float(ticker['last'])
341
- except Exception: return 0.0
342
-
343
- async def get_latest_ohlcv(self, symbol: str, timeframe: str = '5m', limit: int = 100) -> List[List[float]]:
344
- try:
345
- candles = await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
346
- return candles or []
347
- except Exception: return []
348
 
349
- async def get_order_book_snapshot(self, symbol: str, limit: int = 20) -> Dict[str, Any]:
350
- try:
351
- ob = await self.exchange.fetch_order_book(symbol, limit)
352
- return ob
353
- except Exception: return {}
 
1
  # ============================================================
2
  # 📂 ml_engine/data_manager.py
3
+ # (V60.0 - GEM-Architect: Anti-FOMO Shield + Neural Injection)
4
  # ============================================================
5
 
6
  import asyncio
 
12
  import numpy as np
13
  from typing import List, Dict, Any
14
 
15
+ # Keep SystemLimits import for fallbacks if needed
16
+ try:
17
+ from ml_engine.processor import SystemLimits
18
+ except ImportError:
19
+ SystemLimits = None
20
+
21
  # إعدادات التسجيل
22
  logging.getLogger("httpx").setLevel(logging.WARNING)
 
23
  logging.getLogger("ccxt").setLevel(logging.WARNING)
24
 
25
  class DataManager:
26
  def __init__(self, contracts_db, whale_monitor, r2_service=None):
 
 
 
27
  self.contracts_db = contracts_db or {}
28
  self.whale_monitor = whale_monitor
29
  self.r2_service = r2_service
30
+ self.adaptive_hub_ref = None # 🧠 المرجع لملف التعلم (Adaptive Hub)
31
 
 
32
  self.exchange = ccxt.kucoin({
33
  'enableRateLimit': True,
34
  'timeout': 60000,
 
38
  self.http_client = None
39
  self.market_cache = {}
40
 
 
41
  self.BLACKLIST_TOKENS = [
42
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
43
  'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
44
  ]
45
 
46
+ print(f"📦 [DataManager V60.0] Anti-FOMO Shield + Context Injector Active.")
47
 
48
  async def initialize(self):
 
49
  print(" > [DataManager] Starting initialization...")
50
  self.http_client = httpx.AsyncClient(timeout=30.0)
51
  await self._load_markets()
 
52
  await self.load_contracts_from_r2()
 
53
 
54
  async def _load_markets(self):
55
  try:
56
  if self.exchange:
57
  await self.exchange.load_markets()
58
  self.market_cache = self.exchange.markets
59
+ except Exception: pass
 
 
60
 
61
  async def close(self):
62
  if self.http_client: await self.http_client.aclose()
 
66
  if not self.r2_service: return
67
  try:
68
  self.contracts_db = await self.r2_service.load_contracts_db_async()
69
+ except Exception: self.contracts_db = {}
 
70
 
71
  def get_contracts_db(self) -> Dict[str, Any]:
72
  return self.contracts_db
73
+
74
  # ==================================================================
75
+ # 🧠 Layer 1: Screening + Diagnosis + Injection
76
  # ==================================================================
77
  async def layer1_rapid_screening(self, adaptive_hub_ref=None) -> List[Dict[str, Any]]:
78
  """
79
+ 1. Filters using Strict Anti-FOMO Logic.
80
+ 2. Diagnoses Market Regime (Bull/Bear/Range) for survivors.
81
+ 3. Injects Dynamic Thresholds from AdaptiveHub.
 
82
  """
83
  self.adaptive_hub_ref = adaptive_hub_ref
84
+ print(f"🔍 [Layer 1] Initiating STRICT Screening & Context Injection...")
85
 
86
+ # 1. فلتر السيولة الأساسي
87
  initial_candidates = await self._stage0_universe_filter()
88
+ if not initial_candidates: return []
 
 
89
 
90
+ # 2. جلب البيانات الفنية
91
+ top_candidates = initial_candidates[:100]
92
+ enriched_data = await self._fetch_technical_data_batch(top_candidates)
93
 
94
+ final_list = []
 
 
95
 
96
  for item in enriched_data:
97
+ # 3. تطبيق الفلتر الصارم (Anti-FOMO)
98
+ classification = self._apply_strict_logic_tree(item)
99
 
100
+ if classification['type'] != 'NONE':
101
+ # ✅ 4. التشخيص والحقن (The Injection Step)
102
+ # نحدد حالة العملة (صاعدة/هابطة/ميتة) لتحديد العتبات المناسبة
103
+ regime_info = self._diagnose_asset_regime(item)
104
+ item['asset_regime'] = regime_info['regime']
105
+ item['asset_regime_conf'] = regime_info['conf']
106
+
107
+ # حقن العتبات الخاصة من AdaptiveHub
108
+ if self.adaptive_hub_ref:
109
+ # يجلب الإعدادات الخاصة بهذه الحالة (مثلاً: BULL يحتاج عتبات أقل)
110
+ dynamic_config = self.adaptive_hub_ref.get_regime_config(regime_info['regime'])
111
+ item['dynamic_limits'] = dynamic_config
112
+
113
+ # حفظ النتيجة
114
  item['l1_sort_score'] = classification['score']
115
+ item['strategy_tag'] = classification['type']
116
+ final_list.append(item)
 
 
117
 
118
+ # 5. الترتيب النهائي
119
+ # نفضل الاختراقات الآمنة (Breakout) والارتدادات القوية
120
+ final_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
 
 
 
 
 
121
 
122
+ selection = final_list[:50]
123
+ print(f"✅ [Layer 1] Injected Context into {len(selection)} Candidates.")
124
+ return selection
 
125
 
126
  # ------------------------------------------------------------------
127
+ # 🧭 The Diagnoser (يحدد حالة العملة لحقن العتبات)
128
  # ------------------------------------------------------------------
129
+ def _diagnose_asset_regime(self, item: Dict[str, Any]) -> Dict[str, Any]:
130
+ """
131
+ تحليل حالة العملة بناءً على البيانات المحسوبة مسبقاً (1H).
132
+ يحدد هل هي BULL, BEAR, RANGE, أم DEAD.
133
+ """
134
  try:
135
+ # نستخدم بيانات الـ 1 ساعة المحسوبة في _apply_strict_logic_tree
136
+ # البيانات موجودة في item['df_1h'] (سنقوم بحفظها هناك)
137
+ if 'df_1h' not in item: return {'regime': 'RANGE', 'conf': 0.0}
138
 
139
+ df = item['df_1h']
140
+ curr = df.iloc[-1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ price = curr['close']
143
+ ema20 = curr['ema20']
144
+ ema50 = curr['ema50']
145
+ rsi = curr['rsi']
146
+ atr = curr['atr']
147
 
148
+ # حساب نسبة الـ ATR (لقياس النشاط)
149
+ atr_pct = (atr / price) * 100 if price > 0 else 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
+ regime = "RANGE"
152
+ conf = 0.5
153
 
154
+ # 1. DEAD Check (خمول تام)
155
+ # حركة ضعيفة جداً وسيولة منخفضة
156
+ if atr_pct < 0.5:
157
+ return {'regime': 'DEAD', 'conf': 0.9}
158
+
159
+ # 2. BULL Check (ترند صاعد قوي)
160
+ # السعر فوق المتوسطات، والمتوسطات مرتبة، والزخم إيجابي
161
+ if price > ema20 and ema20 > ema50 and rsi > 50:
162
+ regime = "BULL"
163
+ conf = 0.8 if rsi > 55 else 0.6
164
+
165
+ # 3. BEAR Check (ترند هابط)
166
+ elif price < ema20 and ema20 < ema50 and rsi < 50:
167
+ regime = "BEAR"
168
+ conf = 0.8 if rsi < 45 else 0.6
169
+
170
+ return {'regime': regime, 'conf': conf}
171
 
 
 
 
 
172
  except Exception:
173
+ return {'regime': 'RANGE', 'conf': 0.0}
174
 
175
  # ------------------------------------------------------------------
176
+ # 🛡️ The Strict Logic Tree (Anti-FOMO)
177
  # ------------------------------------------------------------------
178
+ def _apply_strict_logic_tree(self, data: Dict[str, Any]) -> Dict[str, Any]:
179
  try:
180
+ # حساب المؤشرات
181
  df_1h = self._calc_indicators(data['ohlcv_1h_raw'])
182
  df_15m = self._calc_indicators(data['ohlcv_15m_raw'])
183
+
184
+ # حفظ الداتافريم للتشخيص لاحقاً
185
+ data['df_1h'] = df_1h
186
+
187
+ except: return {'type': 'NONE', 'score': 0}
188
 
189
  curr_1h = df_1h.iloc[-1]
190
  curr_15m = df_15m.iloc[-1]
191
 
192
+ # --- فلاتر الأمان (Safety Gates) ---
193
  try:
194
  close_4h_ago = df_1h.iloc[-5]['close']
195
  change_4h = ((curr_1h['close'] - close_4h_ago) / close_4h_ago) * 100
196
  except: change_4h = 0.0
197
 
198
+ # 1. لا تطارد المضخات (+8% في 4 ساعات ممنوع)
199
+ if change_4h > 8.0: return {'type': 'NONE', 'score': 0}
200
+ # 2. لا تشتري في قمة التشبع (RSI > 70 ممنوع)
201
+ if curr_1h['rsi'] > 70: return {'type': 'NONE', 'score': 0}
202
+ # 3. لا تشتري بعيداً عن المتوسط (Mean Reversion Risk)
203
+ dev = (curr_1h['close'] - curr_1h['ema20']) / curr_1h['atr'] if curr_1h['atr'] > 0 else 0
204
+ if dev > 1.8: return {'type': 'NONE', 'score': 0}
 
205
 
206
+ # --- التصنيف ---
 
 
 
 
 
 
 
 
207
 
208
+ # A. Breakout (اختراق آمن)
209
+ # هيكلية صاعدة + تجميع (Squeeze) + فوليوم
210
+ is_bullish = (curr_1h['ema20'] > curr_1h['ema50']) or (curr_1h['close'] > curr_1h['ema20'])
211
+ if is_bullish and (45 <= curr_1h['rsi'] <= 68):
212
+ vol_ma = df_15m['volume'].rolling(20).mean().iloc[-1]
213
+ if curr_15m['volume'] >= 1.5 * vol_ma: # شرط الفوليوم
214
+ # سكور الاختراق يعتمد على قوة الفوليوم النسبي
215
+ score = curr_15m['volume'] / vol_ma if vol_ma > 0 else 1.0
216
+ return {'type': 'BREAKOUT', 'score': score}
 
 
 
 
 
 
 
217
 
218
+ # B. Reversal (صيد القاع)
219
+ # تشبع بيعي + شمعة عاكسة
220
+ if 20 <= curr_1h['rsi'] <= 40 and change_4h <= -2.0:
221
+ # سكور الارتداد يعتمد على مدى انخفاض الـ RSI (كلما قل كان أفضل للارتداد)
222
+ score = (100 - curr_1h['rsi'])
223
+ return {'type': 'REVERSAL', 'score': score}
224
 
225
+ return {'type': 'NONE', 'score': 0}
226
+
227
+ # ------------------------------------------------------------------
228
+ # Helpers
229
+ # ------------------------------------------------------------------
230
+ async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
231
+ try:
232
+ tickers = await self.exchange.fetch_tickers()
233
+ candidates = []
234
+ for symbol, ticker in tickers.items():
235
+ if not symbol.endswith('/USDT'): continue
236
+ if any(b in symbol for b in self.BLACKLIST_TOKENS): continue
237
+
238
+ # السيولة
239
+ quote_vol = ticker.get('quoteVolume', 0)
240
+ if quote_vol < 1_000_000: continue
241
+
242
+ # حماية من العملات المنفجرة (+15% يومي ممنوع)
243
+ if ticker.get('percentage', 0) > 15.0: continue
 
244
 
245
+ candidates.append({
246
+ 'symbol': symbol,
247
+ 'quote_volume': quote_vol,
248
+ 'current_price': ticker.get('last'),
249
+ 'change_24h': ticker.get('percentage')
250
+ })
251
+ candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
252
+ return candidates
253
+ except: return []
254
 
255
+ async def _fetch_technical_data_batch(self, candidates):
256
+ chunk_size = 10; results = []
257
+ for i in range(0, len(candidates), chunk_size):
258
+ chunk = candidates[i:i+chunk_size]
259
+ tasks = [self._fetch_single(c) for c in chunk]
260
+ res = await asyncio.gather(*tasks)
261
+ results.extend([r for r in res if r])
262
+ await asyncio.sleep(0.05)
263
+ return results
264
 
265
+ async def _fetch_single(self, c):
266
+ try:
267
+ # نحتاج 1H و 15M للتشخيص والفلتر
268
+ h1 = await self.exchange.fetch_ohlcv(c['symbol'], '1h', limit=60)
269
+ m15 = await self.exchange.fetch_ohlcv(c['symbol'], '15m', limit=60)
270
+ if not h1 or not m15: return None
271
+ c['ohlcv'] = {'1h': h1, '15m': m15} # للمعالج لاحقاً
272
+ c['ohlcv_1h_raw'] = h1 # للفلتر الداخلي
273
+ c['ohlcv_15m_raw'] = m15
274
+ return c
275
+ except: return None
276
 
277
+ def _calc_indicators(self, ohlcv):
278
+ df = pd.DataFrame(ohlcv, columns=['ts', 'o', 'h', 'l', 'c', 'v'])
 
 
279
  # RSI
280
+ delta = df['c'].diff()
281
+ gain = (delta.where(delta>0, 0)).rolling(14).mean()
282
+ loss = (-delta.where(delta<0, 0)).rolling(14).mean()
283
+ rs = gain/loss
284
+ df['rsi'] = 100 - (100/(1+rs))
 
285
  # EMAs
286
+ df['ema20'] = df['c'].ewm(span=20).mean()
287
+ df['ema50'] = df['c'].ewm(span=50).mean()
 
288
  # ATR
289
+ tr = np.maximum(df['h']-df['l'], np.maximum(abs(df['h']-df['c'].shift()), abs(df['l']-df['c'].shift())))
290
+ df['atr'] = tr.rolling(14).mean()
 
 
 
 
291
 
292
+ # Renaming for consistency
293
+ df.rename(columns={'o':'open', 'h':'high', 'l':'low', 'c':'close', 'v':'volume'}, inplace=True)
294
+ return df.fillna(0)
295
+
296
+ async def get_latest_price_async(self, symbol):
297
+ try: return float((await self.exchange.fetch_ticker(symbol))['last'])
298
+ except: return 0.0
299
+
300
+ async def get_latest_ohlcv(self, symbol, timeframe='5m', limit=100):
301
+ try: return await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
302
+ except: return []
 
 
 
 
 
 
303
 
304
+ async def get_order_book_snapshot(self, symbol, limit=20):
305
+ try: return await self.exchange.fetch_order_book(symbol, limit)
306
+ except: return {}