Spaces:
Paused
Paused
Update ml_engine/data_manager.py
Browse files- ml_engine/data_manager.py +92 -40
ml_engine/data_manager.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# ============================================================
|
| 2 |
# 📂 ml_engine/data_manager.py
|
| 3 |
-
# (V60.
|
| 4 |
# ============================================================
|
| 5 |
|
| 6 |
import asyncio
|
|
@@ -12,11 +12,13 @@ import pandas as pd
|
|
| 12 |
import numpy as np
|
| 13 |
from typing import List, Dict, Any
|
| 14 |
|
|
|
|
| 15 |
try:
|
| 16 |
from ml_engine.processor import SystemLimits
|
| 17 |
except ImportError:
|
| 18 |
SystemLimits = None
|
| 19 |
|
|
|
|
| 20 |
logging.getLogger("httpx").setLevel(logging.WARNING)
|
| 21 |
logging.getLogger("ccxt").setLevel(logging.WARNING)
|
| 22 |
|
|
@@ -25,7 +27,7 @@ class DataManager:
|
|
| 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 |
self.exchange = ccxt.kucoin({
|
| 31 |
'enableRateLimit': True,
|
|
@@ -36,12 +38,13 @@ class DataManager:
|
|
| 36 |
self.http_client = None
|
| 37 |
self.market_cache = {}
|
| 38 |
|
|
|
|
| 39 |
self.BLACKLIST_TOKENS = [
|
| 40 |
'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
|
| 41 |
'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
|
| 42 |
]
|
| 43 |
|
| 44 |
-
print(f"📦 [DataManager V60.
|
| 45 |
|
| 46 |
async def initialize(self):
|
| 47 |
print(" > [DataManager] Starting initialization...")
|
|
@@ -74,23 +77,23 @@ class DataManager:
|
|
| 74 |
# ==================================================================
|
| 75 |
async def layer1_rapid_screening(self, adaptive_hub_ref=None) -> List[Dict[str, Any]]:
|
| 76 |
self.adaptive_hub_ref = adaptive_hub_ref
|
| 77 |
-
print(f"🔍 [Layer 1] Initiating
|
| 78 |
|
| 79 |
-
# 1. فلتر السيولة الأساسي (
|
| 80 |
initial_candidates = await self._stage0_universe_filter()
|
| 81 |
|
| 82 |
if not initial_candidates:
|
| 83 |
-
print("⚠️ [Layer 1] Stage 0 returned 0 candidates.
|
| 84 |
return []
|
| 85 |
|
| 86 |
-
# 2. جلب البيانات الفنية
|
| 87 |
-
top_candidates = initial_candidates[:120]
|
| 88 |
enriched_data = await self._fetch_technical_data_batch(top_candidates)
|
| 89 |
|
| 90 |
final_list = []
|
| 91 |
|
| 92 |
for item in enriched_data:
|
| 93 |
-
# 3. تطبيق الفلتر (
|
| 94 |
classification = self._apply_strict_logic_tree(item)
|
| 95 |
|
| 96 |
if classification['type'] != 'NONE':
|
|
@@ -99,6 +102,7 @@ class DataManager:
|
|
| 99 |
item['asset_regime'] = regime_info['regime']
|
| 100 |
item['asset_regime_conf'] = regime_info['conf']
|
| 101 |
|
|
|
|
| 102 |
if self.adaptive_hub_ref:
|
| 103 |
dynamic_config = self.adaptive_hub_ref.get_regime_config(regime_info['regime'])
|
| 104 |
item['dynamic_limits'] = dynamic_config
|
|
@@ -114,6 +118,79 @@ class DataManager:
|
|
| 114 |
print(f"✅ [Layer 1] Passed {len(selection)} candidates to Processor.")
|
| 115 |
return selection
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
# ------------------------------------------------------------------
|
| 118 |
# 🧭 The Diagnoser
|
| 119 |
# ------------------------------------------------------------------
|
|
@@ -144,7 +221,7 @@ class DataManager:
|
|
| 144 |
except Exception: return {'regime': 'RANGE', 'conf': 0.0}
|
| 145 |
|
| 146 |
# ------------------------------------------------------------------
|
| 147 |
-
# 🛡️ The Logic Tree (TUNED
|
| 148 |
# ------------------------------------------------------------------
|
| 149 |
def _apply_strict_logic_tree(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
| 150 |
try:
|
|
@@ -162,13 +239,13 @@ class DataManager:
|
|
| 162 |
except: change_4h = 0.0
|
| 163 |
|
| 164 |
# 🔧 TUNED GATES (فتحات التنفس)
|
| 165 |
-
# 1. السماح بحركة 12% في 4 ساعات
|
| 166 |
if change_4h > 12.0: return {'type': 'NONE', 'score': 0}
|
| 167 |
|
| 168 |
-
# 2. السماح بـ RSI حتى 75
|
| 169 |
if curr_1h['rsi'] > 75: return {'type': 'NONE', 'score': 0}
|
| 170 |
|
| 171 |
-
# 3. السماح بانحراف 2.2 ATR
|
| 172 |
dev = (curr_1h['close'] - curr_1h['ema20']) / curr_1h['atr'] if curr_1h['atr'] > 0 else 0
|
| 173 |
if dev > 2.2: return {'type': 'NONE', 'score': 0}
|
| 174 |
|
|
@@ -176,10 +253,10 @@ class DataManager:
|
|
| 176 |
|
| 177 |
# A. Breakout (اختراق آمن)
|
| 178 |
is_bullish = (curr_1h['ema20'] > curr_1h['ema50']) or (curr_1h['close'] > curr_1h['ema20'])
|
| 179 |
-
if is_bullish and (45 <= curr_1h['rsi'] <=
|
| 180 |
vol_ma = df_15m['volume'].rolling(20).mean().iloc[-1]
|
| 181 |
|
| 182 |
-
# 🔧 TUNED:
|
| 183 |
if curr_15m['volume'] >= 1.2 * vol_ma:
|
| 184 |
score = curr_15m['volume'] / vol_ma if vol_ma > 0 else 1.0
|
| 185 |
return {'type': 'BREAKOUT', 'score': score}
|
|
@@ -194,31 +271,6 @@ class DataManager:
|
|
| 194 |
# ------------------------------------------------------------------
|
| 195 |
# Helpers
|
| 196 |
# ------------------------------------------------------------------
|
| 197 |
-
async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
|
| 198 |
-
try:
|
| 199 |
-
tickers = await self.exchange.fetch_tickers()
|
| 200 |
-
candidates = []
|
| 201 |
-
for symbol, ticker in tickers.items():
|
| 202 |
-
if not symbol.endswith('/USDT'): continue
|
| 203 |
-
if any(b in symbol for b in self.BLACKLIST_TOKENS): continue
|
| 204 |
-
|
| 205 |
-
# 🔧 TUNED: خفضنا الحد الأدنى للسيولة لـ 500k
|
| 206 |
-
quote_vol = ticker.get('quoteVolume', 0)
|
| 207 |
-
if quote_vol < 500_000: continue
|
| 208 |
-
|
| 209 |
-
# 🔧 TUNED: السماح بـ 20% تغير يومي (بدلاً من 15%)
|
| 210 |
-
if ticker.get('percentage', 0) > 20.0: continue
|
| 211 |
-
|
| 212 |
-
candidates.append({
|
| 213 |
-
'symbol': symbol,
|
| 214 |
-
'quote_volume': quote_vol,
|
| 215 |
-
'current_price': ticker.get('last'),
|
| 216 |
-
'change_24h': ticker.get('percentage')
|
| 217 |
-
})
|
| 218 |
-
candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
|
| 219 |
-
return candidates
|
| 220 |
-
except: return []
|
| 221 |
-
|
| 222 |
async def _fetch_technical_data_batch(self, candidates):
|
| 223 |
chunk_size = 10; results = []
|
| 224 |
for i in range(0, len(candidates), chunk_size):
|
|
|
|
| 1 |
# ============================================================
|
| 2 |
# 📂 ml_engine/data_manager.py
|
| 3 |
+
# (V60.2 - GEM-Architect: Robust Data Fix & Tuned Shield)
|
| 4 |
# ============================================================
|
| 5 |
|
| 6 |
import asyncio
|
|
|
|
| 12 |
import numpy as np
|
| 13 |
from typing import List, Dict, Any
|
| 14 |
|
| 15 |
+
# محاولة استيراد حدود النظام (اختياري)
|
| 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 |
|
|
|
|
| 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 # مرجع لملف التعلم
|
| 31 |
|
| 32 |
self.exchange = ccxt.kucoin({
|
| 33 |
'enableRateLimit': True,
|
|
|
|
| 38 |
self.http_client = None
|
| 39 |
self.market_cache = {}
|
| 40 |
|
| 41 |
+
# قائمة العملات المحظورة
|
| 42 |
self.BLACKLIST_TOKENS = [
|
| 43 |
'USDT', 'USDC', 'DAI', 'TUSD', 'BUSD', 'FDUSD', 'EUR', 'PAX',
|
| 44 |
'UP', 'DOWN', 'BEAR', 'BULL', '3S', '3L'
|
| 45 |
]
|
| 46 |
|
| 47 |
+
print(f"📦 [DataManager V60.2] Robust Data Fix + Tuned Shield Active.")
|
| 48 |
|
| 49 |
async def initialize(self):
|
| 50 |
print(" > [DataManager] Starting initialization...")
|
|
|
|
| 77 |
# ==================================================================
|
| 78 |
async def layer1_rapid_screening(self, adaptive_hub_ref=None) -> List[Dict[str, Any]]:
|
| 79 |
self.adaptive_hub_ref = adaptive_hub_ref
|
| 80 |
+
print(f"🔍 [Layer 1] Initiating Robust Screening...")
|
| 81 |
|
| 82 |
+
# 1. فلتر السيولة الأساسي (المصحح)
|
| 83 |
initial_candidates = await self._stage0_universe_filter()
|
| 84 |
|
| 85 |
if not initial_candidates:
|
| 86 |
+
print("⚠️ [Layer 1] CRITICAL: Stage 0 returned 0 candidates.")
|
| 87 |
return []
|
| 88 |
|
| 89 |
+
# 2. جلب البيانات الفنية (لأفضل 120 عملة لضمان التنوع)
|
| 90 |
+
top_candidates = initial_candidates[:120]
|
| 91 |
enriched_data = await self._fetch_technical_data_batch(top_candidates)
|
| 92 |
|
| 93 |
final_list = []
|
| 94 |
|
| 95 |
for item in enriched_data:
|
| 96 |
+
# 3. تطبيق الفلتر الفني (Tuned Anti-FOMO)
|
| 97 |
classification = self._apply_strict_logic_tree(item)
|
| 98 |
|
| 99 |
if classification['type'] != 'NONE':
|
|
|
|
| 102 |
item['asset_regime'] = regime_info['regime']
|
| 103 |
item['asset_regime_conf'] = regime_info['conf']
|
| 104 |
|
| 105 |
+
# حقن العتبات الديناميكية
|
| 106 |
if self.adaptive_hub_ref:
|
| 107 |
dynamic_config = self.adaptive_hub_ref.get_regime_config(regime_info['regime'])
|
| 108 |
item['dynamic_limits'] = dynamic_config
|
|
|
|
| 118 |
print(f"✅ [Layer 1] Passed {len(selection)} candidates to Processor.")
|
| 119 |
return selection
|
| 120 |
|
| 121 |
+
# ==================================================================
|
| 122 |
+
# 🔍 Stage 0: Universe Filter (Debugged & Robust)
|
| 123 |
+
# ==================================================================
|
| 124 |
+
async def _stage0_universe_filter(self) -> List[Dict[str, Any]]:
|
| 125 |
+
try:
|
| 126 |
+
print(" 🛡️ [Stage 0] Fetching Tickers...")
|
| 127 |
+
tickers = await self.exchange.fetch_tickers()
|
| 128 |
+
candidates = []
|
| 129 |
+
|
| 130 |
+
# عداد لمعرفة سبب الرفض (للتشخيص)
|
| 131 |
+
reject_stats = {"volume": 0, "change": 0, "blacklist": 0, "bad_data": 0}
|
| 132 |
+
debug_printed = False
|
| 133 |
+
|
| 134 |
+
for symbol, ticker in tickers.items():
|
| 135 |
+
if not symbol.endswith('/USDT'): continue
|
| 136 |
+
|
| 137 |
+
# 1. Blacklist
|
| 138 |
+
base_curr = symbol.split('/')[0]
|
| 139 |
+
if any(bad in base_curr for bad in self.BLACKLIST_TOKENS):
|
| 140 |
+
reject_stats["blacklist"] += 1
|
| 141 |
+
continue
|
| 142 |
+
|
| 143 |
+
# 2. Robust Volume Calculation (الإصلاح الجذري)
|
| 144 |
+
quote_vol = ticker.get('quoteVolume')
|
| 145 |
+
|
| 146 |
+
# إذا ��ان غير موجود أو صفر، نحسبه يدوياً
|
| 147 |
+
if quote_vol is None or float(quote_vol) == 0:
|
| 148 |
+
base_vol = ticker.get('baseVolume')
|
| 149 |
+
last_price = ticker.get('last')
|
| 150 |
+
if base_vol is not None and last_price is not None:
|
| 151 |
+
quote_vol = float(base_vol) * float(last_price)
|
| 152 |
+
else:
|
| 153 |
+
quote_vol = 0.0
|
| 154 |
+
|
| 155 |
+
quote_vol = float(quote_vol)
|
| 156 |
+
|
| 157 |
+
# طباعة فحص لعملة معروفة (BTC) للتأكد من صحة البيانات في السجل
|
| 158 |
+
if "BTC/USDT" in symbol and not debug_printed:
|
| 159 |
+
print(f" 🐛 [DEBUG] BTC Data Check: Vol=${quote_vol:,.2f} | Change={ticker.get('percentage')}%")
|
| 160 |
+
debug_printed = True
|
| 161 |
+
|
| 162 |
+
# فلتر السيولة (500k)
|
| 163 |
+
if quote_vol < 500_000:
|
| 164 |
+
reject_stats["volume"] += 1
|
| 165 |
+
continue
|
| 166 |
+
|
| 167 |
+
# فلتر التغير (20%)
|
| 168 |
+
change_pct = ticker.get('percentage')
|
| 169 |
+
if change_pct is None: change_pct = 0.0
|
| 170 |
+
|
| 171 |
+
if abs(change_pct) > 20.0:
|
| 172 |
+
reject_stats["change"] += 1
|
| 173 |
+
continue
|
| 174 |
+
|
| 175 |
+
candidates.append({
|
| 176 |
+
'symbol': symbol,
|
| 177 |
+
'quote_volume': quote_vol,
|
| 178 |
+
'current_price': ticker.get('last'),
|
| 179 |
+
'change_24h': change_pct
|
| 180 |
+
})
|
| 181 |
+
|
| 182 |
+
# طباعة تقرير الرفض
|
| 183 |
+
print(f" 📊 [Filter Stats] Total Tickers: {len(tickers)} | Passed: {len(candidates)}")
|
| 184 |
+
print(f" ❌ Rejected: Vol < 500k ({reject_stats['volume']}) | Change > 20% ({reject_stats['change']})")
|
| 185 |
+
|
| 186 |
+
candidates.sort(key=lambda x: x['quote_volume'], reverse=True)
|
| 187 |
+
return candidates
|
| 188 |
+
|
| 189 |
+
except Exception as e:
|
| 190 |
+
print(f"❌ [L1 Error] Universe filter failed: {e}")
|
| 191 |
+
traceback.print_exc()
|
| 192 |
+
return []
|
| 193 |
+
|
| 194 |
# ------------------------------------------------------------------
|
| 195 |
# 🧭 The Diagnoser
|
| 196 |
# ------------------------------------------------------------------
|
|
|
|
| 221 |
except Exception: return {'regime': 'RANGE', 'conf': 0.0}
|
| 222 |
|
| 223 |
# ------------------------------------------------------------------
|
| 224 |
+
# 🛡️ The Logic Tree (TUNED)
|
| 225 |
# ------------------------------------------------------------------
|
| 226 |
def _apply_strict_logic_tree(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
| 227 |
try:
|
|
|
|
| 239 |
except: change_4h = 0.0
|
| 240 |
|
| 241 |
# 🔧 TUNED GATES (فتحات التنفس)
|
| 242 |
+
# 1. السماح بحركة 12% في 4 ساعات
|
| 243 |
if change_4h > 12.0: return {'type': 'NONE', 'score': 0}
|
| 244 |
|
| 245 |
+
# 2. السماح بـ RSI حتى 75
|
| 246 |
if curr_1h['rsi'] > 75: return {'type': 'NONE', 'score': 0}
|
| 247 |
|
| 248 |
+
# 3. السماح بانحراف 2.2 ATR
|
| 249 |
dev = (curr_1h['close'] - curr_1h['ema20']) / curr_1h['atr'] if curr_1h['atr'] > 0 else 0
|
| 250 |
if dev > 2.2: return {'type': 'NONE', 'score': 0}
|
| 251 |
|
|
|
|
| 253 |
|
| 254 |
# A. Breakout (اختراق آمن)
|
| 255 |
is_bullish = (curr_1h['ema20'] > curr_1h['ema50']) or (curr_1h['close'] > curr_1h['ema20'])
|
| 256 |
+
if is_bullish and (45 <= curr_1h['rsi'] <= 75):
|
| 257 |
vol_ma = df_15m['volume'].rolling(20).mean().iloc[-1]
|
| 258 |
|
| 259 |
+
# 🔧 TUNED: 1.2x Volume
|
| 260 |
if curr_15m['volume'] >= 1.2 * vol_ma:
|
| 261 |
score = curr_15m['volume'] / vol_ma if vol_ma > 0 else 1.0
|
| 262 |
return {'type': 'BREAKOUT', 'score': score}
|
|
|
|
| 271 |
# ------------------------------------------------------------------
|
| 272 |
# Helpers
|
| 273 |
# ------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
async def _fetch_technical_data_batch(self, candidates):
|
| 275 |
chunk_size = 10; results = []
|
| 276 |
for i in range(0, len(candidates), chunk_size):
|