Riy777 commited on
Commit
5efae6d
·
verified ·
1 Parent(s): 2c80c4b

Update ml_engine/data_manager.py

Browse files
Files changed (1) hide show
  1. ml_engine/data_manager.py +52 -200
ml_engine/data_manager.py CHANGED
@@ -1,5 +1,5 @@
1
  # ml_engine/data_manager.py
2
- # (V15.1 - GEM-Architect: Tuned Logic Tree - Marksman Mode)
3
 
4
  import asyncio
5
  import httpx
@@ -10,7 +10,7 @@ import pandas as pd
10
  import numpy as np
11
  from typing import List, Dict, Any
12
 
13
- # إعدادات التسجيل
14
  logging.getLogger("httpx").setLevel(logging.WARNING)
15
  logging.getLogger("httpcore").setLevel(logging.WARNING)
16
  logging.getLogger("ccxt").setLevel(logging.WARNING)
@@ -34,10 +34,10 @@ class DataManager:
34
  self.http_client = None
35
  self.market_cache = {}
36
 
37
- # قوائم الاستبعاد
38
  self.BLACKLIST_TOKENS = [
39
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
40
- 'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
41
  ]
42
 
43
  async def initialize(self):
@@ -45,7 +45,7 @@ class DataManager:
45
  print(" > [DataManager] Starting initialization...")
46
  self.http_client = httpx.AsyncClient(timeout=30.0)
47
  await self._load_markets()
48
- print(f"✅ [DataManager V15.1] Ready (Logic Tree: Tuned/Flexible).")
49
 
50
  async def _load_markets(self):
51
  try:
@@ -74,229 +74,80 @@ class DataManager:
74
  return self.contracts_db
75
 
76
  # ==================================================================
77
- # 🛡️ Layer 1: The Tuned Decision Tree Screening
78
  # ==================================================================
79
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
80
- print(f"🔍 [Layer 1] Initiating Tuned Logic Tree Screening...")
 
 
 
 
81
 
82
- # 1. المرحلة 0: فلتر الكون (مخفف)
83
- initial_candidates = await self._stage0_universe_filter()
84
-
85
- if not initial_candidates:
86
- return []
87
-
88
- # 2. جلب البيانات الفنية
89
- top_liquid_candidates = initial_candidates[:300]
90
- enriched_data = await self._fetch_technical_data_batch(top_liquid_candidates)
91
-
92
- # 3. تطبيق شجرة القرار (Overbought -> Classify)
93
- breakout_list = []
94
- reversal_list = []
95
-
96
- for item in enriched_data:
97
- classification = self._apply_logic_tree(item)
98
-
99
- if classification['type'] == 'BREAKOUT':
100
- item['l1_sort_score'] = classification['score']
101
- breakout_list.append(item)
102
- elif classification['type'] == 'REVERSAL':
103
- item['l1_sort_score'] = classification['score']
104
- reversal_list.append(item)
105
-
106
- print(f" -> [L1 Logic] Found: {len(breakout_list)} Breakouts, {len(reversal_list)} Reversals.")
107
-
108
- # 4. الترتيب والدمج النهائي
109
- breakout_list.sort(key=lambda x: x['l1_sort_score'], reverse=True)
110
- reversal_list.sort(key=lambda x: x['l1_sort_score'], reverse=False)
111
-
112
- final_selection = breakout_list[:80] + reversal_list[:70]
113
-
114
- cleaned_selection = []
115
- for item in final_selection:
116
- cleaned_selection.append({
117
- 'symbol': item['symbol'],
118
- 'quote_volume': item.get('quote_volume', 0),
119
- 'current_price': item.get('current_price', 0),
120
- 'type': item.get('type', 'UNKNOWN'), # نمرر النوع لـ app.py إذا رغب باستخدامه
121
- 'l1_score': item.get('l1_sort_score', 0)
122
- })
123
-
124
- print(f"✅ [Layer 1] Final Selection: {len(cleaned_selection)} candidates passed to models.")
125
- return cleaned_selection
126
-
127
- # ------------------------------------------------------------------
128
- # Stage 0: Universe Filter (RELAXED)
129
- # ------------------------------------------------------------------
130
- async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
131
  try:
 
132
  tickers = await self.exchange.fetch_tickers()
133
  candidates = []
134
 
135
  for symbol, ticker in tickers.items():
 
136
  if not symbol.endswith('/USDT'): continue
137
 
 
138
  base_curr = symbol.split('/')[0]
139
  if any(bad in base_curr for bad in self.BLACKLIST_TOKENS): continue
140
 
141
- # 👇 [Tuning] خفضنا السيولة المطلوبة لمليون واحد فقط
142
- quote_vol = ticker.get('quoteVolume')
143
- if not quote_vol or quote_vol < 1_000_000: continue
144
-
145
  last_price = ticker.get('last')
146
- if not last_price or last_price < 0.0005: continue
 
 
 
 
 
147
 
148
  candidates.append({
149
  'symbol': symbol,
150
  'quote_volume': quote_vol,
151
  'current_price': last_price,
152
- 'change_24h': ticker.get('percentage', 0.0)
153
  })
154
 
 
155
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
156
- return candidates
157
 
158
- except Exception as e:
159
- print(f"❌ [L1 Error] Universe filter failed: {e}")
160
- return []
161
-
162
- # ------------------------------------------------------------------
163
- # Data Fetching Helpers
164
- # ------------------------------------------------------------------
165
- async def _fetch_technical_data_batch(self, candidates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
166
- chunk_size = 10
167
- results = []
168
- for i in range(0, len(candidates), chunk_size):
169
- chunk = candidates[i:i + chunk_size]
170
- chunk_tasks = [self._fetch_single_tech_data(c) for c in chunk]
171
- chunk_results = await asyncio.gather(*chunk_tasks)
172
- results.extend([r for r in chunk_results if r is not None])
173
- await asyncio.sleep(0.1) # تسريع قليل
174
- return results
175
-
176
- async def _fetch_single_tech_data(self, candidate: Dict[str, Any]) -> Any:
177
- symbol = candidate['symbol']
178
- try:
179
- ohlcv_1h = await self.exchange.fetch_ohlcv(symbol, '1h', limit=60)
180
- ohlcv_15m = await self.exchange.fetch_ohlcv(symbol, '15m', limit=60)
181
 
182
- if not ohlcv_1h or len(ohlcv_1h) < 55 or not ohlcv_15m or len(ohlcv_15m) < 55:
183
- return None
184
-
185
- candidate['ohlcv_1h'] = ohlcv_1h
186
- candidate['ohlcv_15m'] = ohlcv_15m
187
- return candidate
188
- except Exception:
189
- return None
190
-
191
- # ------------------------------------------------------------------
192
- # 🧠 The Logic Core: Math & Decision Tree (RELAXED)
193
- # ------------------------------------------------------------------
194
- def _apply_logic_tree(self, data: Dict[str, Any]) -> Dict[str, Any]:
195
- try:
196
- df_1h = self._calc_indicators(data['ohlcv_1h'])
197
- df_15m = self._calc_indicators(data['ohlcv_15m'])
198
- except:
199
- return {'type': 'NONE'}
200
-
201
- curr_1h = df_1h.iloc[-1]
202
- curr_15m = df_15m.iloc[-1]
203
-
204
- # --- Stage 2: Overbought Filter ---
205
- try:
206
- close_4h_ago = df_1h.iloc[-5]['close']
207
- change_4h = ((curr_1h['close'] - close_4h_ago) / close_4h_ago) * 100
208
- except: change_4h = 0.0
209
-
210
- if change_4h > 15.0: return {'type': 'NONE'}
211
- if data.get('change_24h', 0) > 25.0: return {'type': 'NONE'}
212
- if curr_1h['rsi'] > 80: return {'type': 'NONE'} # 👇 [Tuning] سمحنا بـ RSI أعلى قليلاً
213
-
214
- deviation = (curr_1h['close'] - curr_1h['ema20']) / curr_1h['atr'] if curr_1h['atr'] > 0 else 0
215
- if deviation > 2.5: return {'type': 'NONE'} # 👇 [Tuning] سمحنا بإنحراف أكبر قليلاً
216
-
217
- # --- Stage 3: Classification ---
218
-
219
- # === A. Breakout Logic (RELAXED) ===
220
- is_breakout = False
221
- breakout_score = 0.0
222
-
223
- # Trend check (EMA Cross OR Price above both EMAs)
224
- bullish_structure = (curr_1h['ema20'] > curr_1h['ema50']) or (curr_1h['close'] > curr_1h['ema20'])
225
-
226
- if bullish_structure:
227
- # 👇 [Tuning] RSI range expanded
228
- if 40 <= curr_1h['rsi'] <= 70:
229
- # 15m bullish
230
- if curr_15m['close'] >= curr_15m['ema20']:
231
- # Volatility check (Range)
232
- avg_range = (df_15m['high'] - df_15m['low']).rolling(10).mean().iloc[-1]
233
- # 👇 [Tuning] Less strict squeeze check (1.5x avg range allowed)
234
- if (curr_15m['high'] - curr_15m['low']) <= avg_range * 1.5:
235
- vol_ma20 = df_15m['volume'].rolling(20).mean().iloc[-1]
236
- # 👇 [Tuning] Volume Spike lowered to 1.2x
237
- if curr_15m['volume'] >= 1.2 * vol_ma20:
238
- is_breakout = True
239
- breakout_score = curr_15m['volume'] / vol_ma20 if vol_ma20 > 0 else 1.0
240
-
241
- if is_breakout:
242
- data['type'] = 'BREAKOUT'
243
- return {'type': 'BREAKOUT', 'score': breakout_score}
244
-
245
- # === B. Reversal Logic (RELAXED) ===
246
- is_reversal = False
247
- reversal_score = 100.0
248
-
249
- # 👇 [Tuning] RSI threshold increased to 40
250
- if 10 <= curr_1h['rsi'] <= 40:
251
- # 👇 [Tuning] Drop requirement reduced to -3%
252
- if change_4h <= -3.0:
253
- # Rejection check (Any bullish closing in last 3 candles)
254
- last_3 = df_15m.iloc[-3:]
255
- found_rejection = False
256
- for _, row in last_3.iterrows():
257
- # 👇 [Tuning] Simple logic: Green candle OR Close in upper half
258
- rng = row['high'] - row['low']
259
- if rng > 0:
260
- is_green = row['close'] > row['open']
261
- upper_half = row['close'] > (row['low'] + rng * 0.5)
262
- if is_green or upper_half:
263
- found_rejection = True
264
- break
265
 
266
- if found_rejection:
267
- is_reversal = True
268
- reversal_score = curr_1h['rsi']
269
-
270
- if is_reversal:
271
- data['type'] = 'REVERSAL'
272
- return {'type': 'REVERSAL', 'score': reversal_score}
 
 
 
 
 
 
 
 
273
 
274
- return {'type': 'NONE'}
275
-
276
- def _calc_indicators(self, ohlcv_list):
277
- df = pd.DataFrame(ohlcv_list, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
278
-
279
- delta = df['close'].diff()
280
- gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
281
- loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
282
- rs = gain / loss
283
- df['rsi'] = 100 - (100 / (1 + rs))
284
-
285
- df['ema20'] = df['close'].ewm(span=20, adjust=False).mean()
286
- df['ema50'] = df['close'].ewm(span=50, adjust=False).mean()
287
-
288
- high_low = df['high'] - df['low']
289
- high_close = np.abs(df['high'] - df['close'].shift())
290
- low_close = np.abs(df['low'] - df['close'].shift())
291
- ranges = pd.concat([high_low, high_close, low_close], axis=1)
292
- true_range = np.max(ranges, axis=1)
293
- df['atr'] = true_range.rolling(14).mean()
294
-
295
- df.fillna(0, inplace=True)
296
- return df
297
 
298
  # ==================================================================
299
- # 🎯 Public Helpers
300
  # ==================================================================
301
  async def get_latest_price_async(self, symbol: str) -> float:
302
  try:
@@ -306,6 +157,7 @@ class DataManager:
306
 
307
  async def get_latest_ohlcv(self, symbol: str, timeframe: str = '5m', limit: int = 100) -> List[List[float]]:
308
  try:
 
309
  candles = await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
310
  return candles or []
311
  except Exception: return []
 
1
  # ml_engine/data_manager.py
2
+ # (V15.2 - GEM-Architect: Pure Liquidity Edition)
3
 
4
  import asyncio
5
  import httpx
 
10
  import numpy as np
11
  from typing import List, Dict, Any
12
 
13
+ # إعدادات التسجيل - تقليل الضجيج
14
  logging.getLogger("httpx").setLevel(logging.WARNING)
15
  logging.getLogger("httpcore").setLevel(logging.WARNING)
16
  logging.getLogger("ccxt").setLevel(logging.WARNING)
 
34
  self.http_client = None
35
  self.market_cache = {}
36
 
37
+ # قوائم الاستبعاد (العملات المستقرة والرموز الرافعة الخطرة)
38
  self.BLACKLIST_TOKENS = [
39
  'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
40
+ 'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L', '2S', '2L', '5S', '5L'
41
  ]
42
 
43
  async def initialize(self):
 
45
  print(" > [DataManager] Starting initialization...")
46
  self.http_client = httpx.AsyncClient(timeout=30.0)
47
  await self._load_markets()
48
+ print(f"✅ [DataManager V15.2] Ready (Strategy: Top 100 Volume).")
49
 
50
  async def _load_markets(self):
51
  try:
 
74
  return self.contracts_db
75
 
76
  # ==================================================================
77
+ # 🛡️ Layer 1: Rapid Screening (Top 100 Volume Only)
78
  # ==================================================================
79
  async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
80
+ """
81
+ جلب أعلى 100 عملة من حيث حجم التداول (USDT Volume).
82
+ المنطق: السيولة هي الملك. العملات النشطة هي الأفضل للتداول الآلي.
83
+ """
84
+ print(f"🔍 [Layer 1] Screening Top 100 by Volume...")
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  try:
87
+ # 1. جلب كل الأسعار والبيانات اللحظية
88
  tickers = await self.exchange.fetch_tickers()
89
  candidates = []
90
 
91
  for symbol, ticker in tickers.items():
92
+ # تصفية الأزواج غير المرغوبة
93
  if not symbol.endswith('/USDT'): continue
94
 
95
+ # تصفية العملات المحظورة (stablecoins, leveraged tokens)
96
  base_curr = symbol.split('/')[0]
97
  if any(bad in base_curr for bad in self.BLACKLIST_TOKENS): continue
98
 
99
+ # التأكد من صحة البيانات
100
+ quote_vol = ticker.get('quoteVolume') # حجم التداول بالدولار
 
 
101
  last_price = ticker.get('last')
102
+
103
+ if quote_vol is None or last_price is None: continue
104
+ if last_price <= 0: continue
105
+
106
+ # الحد الأدنى للقبول (مليون دولار حجم تداول يومي) لتجنب العملات الميتة تماماً
107
+ if quote_vol < 1_000_000: continue
108
 
109
  candidates.append({
110
  'symbol': symbol,
111
  'quote_volume': quote_vol,
112
  'current_price': last_price,
113
+ 'change_24h': ticker.get('percentage', 0.0)
114
  })
115
 
116
+ # 2. الترتيب التنازلي حسب حجم التداول
117
  candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
 
118
 
119
+ # 3. اختيار أعلى 100 عملة فقط
120
+ top_100 = candidates[:100]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ # 4. تنسيق البيانات للنظام التالي (Layer 2)
123
+ final_selection = []
124
+
125
+ if top_100:
126
+ max_vol = top_100[0]['quote_volume']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ for item in top_100:
129
+ # حساب درجة بسيطة (Score) بناءً على الحجم النسبي
130
+ # أعلى عملة تأخذ 1.0، والباقي نسبة منها
131
+ vol_score = item['quote_volume'] / max_vol if max_vol > 0 else 0.0
132
+
133
+ final_selection.append({
134
+ 'symbol': item['symbol'],
135
+ 'quote_volume': item['quote_volume'],
136
+ 'current_price': item['current_price'],
137
+ 'type': 'TOP_LIQUIDITY', # وسم موحد للجميع
138
+ 'l1_score': vol_score # يستخدم للترتيب الأولي في app.py
139
+ })
140
+
141
+ print(f"✅ [Layer 1] Selected {len(final_selection)} assets based on pure volume.")
142
+ return final_selection
143
 
144
+ except Exception as e:
145
+ print(f"❌ [Layer 1 Error] Screening failed: {e}")
146
+ traceback.print_exc()
147
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
  # ==================================================================
150
+ # 🎯 Public Helpers (Used by Processor & TradeManager)
151
  # ==================================================================
152
  async def get_latest_price_async(self, symbol: str) -> float:
153
  try:
 
157
 
158
  async def get_latest_ohlcv(self, symbol: str, timeframe: str = '5m', limit: int = 100) -> List[List[float]]:
159
  try:
160
+ # دالة مساعدة لجلب الشموع للمحركات الأخرى (Processor/Guardian)
161
  candles = await self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
162
  return candles or []
163
  except Exception: return []