Update data_manager.py
Browse files- data_manager.py +70 -64
data_manager.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# data_manager.py (Updated to V7.
|
| 2 |
import os
|
| 3 |
import asyncio
|
| 4 |
import httpx
|
|
@@ -11,19 +11,15 @@ import logging
|
|
| 11 |
from typing import List, Dict, Any
|
| 12 |
import pandas as pd
|
| 13 |
|
| 14 |
-
# 🔴 --- START OF CHANGE (V7.1) --- 🔴
|
| 15 |
-
# (استيراد الأدوات اللازمة لـ "الكاشف المصغر")
|
| 16 |
try:
|
| 17 |
import pandas_ta as ta
|
| 18 |
except ImportError:
|
| 19 |
print("⚠️ مكتبة pandas_ta غير موجودة، فلتر الغربلة المتقدم سيفشل.")
|
| 20 |
ta = None
|
| 21 |
|
| 22 |
-
# (استيراد الوحدات الأساسية من محرك ML لتشغيل الكاشف المصغر)
|
| 23 |
from ml_engine.indicators import AdvancedTechnicalAnalyzer
|
| 24 |
from ml_engine.monte_carlo import MonteCarloAnalyzer
|
| 25 |
from ml_engine.patterns import ChartPatternAnalyzer
|
| 26 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 27 |
|
| 28 |
logging.getLogger("httpx").setLevel(logging.WARNING)
|
| 29 |
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
@@ -49,17 +45,14 @@ class DataManager:
|
|
| 49 |
self.market_cache = {}
|
| 50 |
self.last_market_load = None
|
| 51 |
|
| 52 |
-
# 🔴 --- START OF CHANGE (V7.1) --- 🔴
|
| 53 |
-
# (تهيئة أدوات الكاشف المصغر)
|
| 54 |
self.technical_analyzer = AdvancedTechnicalAnalyzer()
|
| 55 |
self.monte_carlo_analyzer = MonteCarloAnalyzer()
|
| 56 |
self.pattern_analyzer = ChartPatternAnalyzer()
|
| 57 |
-
# 🔴 --- END OF CHANGE --- 🔴
|
| 58 |
|
| 59 |
async def initialize(self):
|
| 60 |
self.http_client = httpx.AsyncClient(timeout=30.0)
|
| 61 |
await self._load_markets()
|
| 62 |
-
print("✅ DataManager initialized - V7.
|
| 63 |
|
| 64 |
async def _load_markets(self):
|
| 65 |
try:
|
|
@@ -205,10 +198,10 @@ class DataManager:
|
|
| 205 |
'data_quality': 'LOW'
|
| 206 |
}
|
| 207 |
|
| 208 |
-
# 🔴 --- START OF REFACTOR (V7.
|
| 209 |
|
| 210 |
def _create_dataframe(self, candles: List) -> pd.DataFrame:
|
| 211 |
-
"""(
|
| 212 |
try:
|
| 213 |
if not candles: return pd.DataFrame()
|
| 214 |
df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
|
@@ -223,11 +216,22 @@ class DataManager:
|
|
| 223 |
|
| 224 |
def _calculate_1h_filter_score(self, analysis: Dict) -> float:
|
| 225 |
"""
|
| 226 |
-
(
|
| 227 |
-
|
| 228 |
-
يستخدم (المؤشرات، مونت كارلو، الأنماط) فقط.
|
| 229 |
"""
|
| 230 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
# 1. درجة الأنماط (Pattern Score)
|
| 232 |
pattern_confidence = analysis.get('pattern_analysis', {}).get('pattern_confidence', 0)
|
| 233 |
|
|
@@ -235,7 +239,7 @@ class DataManager:
|
|
| 235 |
mc_distribution = analysis.get('monte_carlo_distribution')
|
| 236 |
monte_carlo_score = 0
|
| 237 |
|
| 238 |
-
if mc_distribution:
|
| 239 |
prob_gain = mc_distribution.get('probability_of_gain', 0)
|
| 240 |
var_95_value = mc_distribution.get('risk_metrics', {}).get('VaR_95_value', 0)
|
| 241 |
current_price = analysis.get('current_price', 1)
|
|
@@ -246,12 +250,10 @@ class DataManager:
|
|
| 246 |
if normalized_var > 0.05: risk_penalty = 0.5
|
| 247 |
elif normalized_var > 0.03: risk_penalty = 0.8
|
| 248 |
|
| 249 |
-
# (تطبيع الاحتمالية: 0.5 -> 0, 1.0 -> 1.0)
|
| 250 |
normalized_prob_score = max(0.0, (prob_gain - 0.5) * 2)
|
| 251 |
monte_carlo_score = normalized_prob_score * risk_penalty
|
| 252 |
|
| 253 |
# 3. درجة المؤشرات (Indicator Score)
|
| 254 |
-
# (تقييم بسيط للمؤشرات الرئيسية لـ 1H)
|
| 255 |
indicator_score = 0
|
| 256 |
indicators = analysis.get('advanced_indicators', {}).get('1h', {})
|
| 257 |
if indicators:
|
|
@@ -268,7 +270,6 @@ class DataManager:
|
|
| 268 |
indicator_score = min(0.4 + (35 - rsi) / 35, 0.8)
|
| 269 |
|
| 270 |
# 4. حساب النتيجة النهائية (بدون استراتيجيات أو حيتان)
|
| 271 |
-
# الأوزان: مونت كارلو (40%)، الأنماط (30%)، المؤشرات (30%)
|
| 272 |
components = []
|
| 273 |
weights = []
|
| 274 |
|
|
@@ -290,14 +291,9 @@ class DataManager:
|
|
| 290 |
|
| 291 |
async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
|
| 292 |
"""
|
| 293 |
-
الطبقة 1: فحص سريع - (محدث بالكامل V7.
|
| 294 |
-
1. جلب أفضل 100 عملة حسب الحجم (24 ساعة).
|
| 295 |
-
2. جلب 100 شمعة (1H) لهذه الـ 100 عملة.
|
| 296 |
-
3. تشغيل "الكاشف المصغر" (مؤشرات، مونت كارلو، أنماط) على 1H.
|
| 297 |
-
4. استبعاد أي عملة درجتها < 0.20.
|
| 298 |
-
5. إرجاع العملات الناجحة فقط للطبقة 2.
|
| 299 |
"""
|
| 300 |
-
print("📊 الطبقة 1 (V7.
|
| 301 |
|
| 302 |
# الخطوة 1: جلب أفضل 100 عملة حسب الحجم
|
| 303 |
volume_data = await self._get_volume_data_optimal()
|
|
@@ -315,7 +311,6 @@ class DataManager:
|
|
| 315 |
|
| 316 |
final_candidates = []
|
| 317 |
|
| 318 |
-
# (استخدام دفعات لمعالجة الـ 100 عملة بكفاءة)
|
| 319 |
batch_size = 20
|
| 320 |
for i in range(0, len(top_100_by_volume), batch_size):
|
| 321 |
batch_symbols_data = top_100_by_volume[i:i + batch_size]
|
|
@@ -327,7 +322,6 @@ class DataManager:
|
|
| 327 |
tasks = [self._fetch_1h_ohlcv_for_screening(symbol) for symbol in batch_symbols]
|
| 328 |
results_candles = await asyncio.gather(*tasks, return_exceptions=True)
|
| 329 |
|
| 330 |
-
# الخطوة 3 و 4: تطبيق الكاشف المصغر
|
| 331 |
analysis_tasks = []
|
| 332 |
valid_symbol_data = []
|
| 333 |
|
|
@@ -340,43 +334,45 @@ class DataManager:
|
|
| 340 |
|
| 341 |
# (إعداد البيانات للمحللات)
|
| 342 |
ohlcv_1h_only = {'1h': candles}
|
| 343 |
-
symbol_data['ohlcv_1h'] = ohlcv_1h_only
|
| 344 |
-
symbol_data['current_price'] = candles[-1][4]
|
| 345 |
analysis_tasks.append(self._run_mini_detector(symbol_data))
|
| 346 |
valid_symbol_data.append(symbol_data)
|
| 347 |
|
| 348 |
if not analysis_tasks:
|
| 349 |
continue
|
| 350 |
|
| 351 |
-
# تشغيل التحليلات (MC, Patterns, Indicators) بالتوازي
|
| 352 |
analysis_results = await asyncio.gather(*analysis_tasks, return_exceptions=True)
|
| 353 |
|
| 354 |
for j, (analysis_output) in enumerate(analysis_results):
|
| 355 |
-
symbol_data = valid_symbol_data[j]
|
| 356 |
symbol = symbol_data['symbol']
|
| 357 |
|
| 358 |
if isinstance(analysis_output, Exception):
|
| 359 |
print(f" - {symbol}: فشل الكاشف المصغر ({analysis_output})")
|
| 360 |
continue
|
| 361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
# الخطوة 4: حساب النتيجة النهائية للفلتر
|
| 363 |
filter_score = self._calculate_1h_filter_score(analysis_output)
|
| 364 |
|
| 365 |
# الخطوة 5: تطبيق العتبة (Threshold)
|
| 366 |
if filter_score >= 0.20:
|
| 367 |
print(f" ✅ {symbol}: نجح (الدرجة: {filter_score:.2f})")
|
| 368 |
-
# (إضافة البيانات الأولية للطبقة 2)
|
| 369 |
symbol_data['layer1_score'] = filter_score
|
| 370 |
symbol_data['reasons_for_candidacy'] = [f'1H_DETECTOR_PASS']
|
| 371 |
|
| 372 |
-
# (تنظيف البيانات قبل إرسالها)
|
| 373 |
if 'ohlcv_1h' in symbol_data: del symbol_data['ohlcv_1h']
|
| 374 |
|
| 375 |
final_candidates.append(symbol_data)
|
| 376 |
# else:
|
|
|
|
| 377 |
# print(f" - {symbol}: فشل (الدرجة: {filter_score:.2f})")
|
| 378 |
|
| 379 |
-
print(f"🎯 اكتملت الغربلة (V7.
|
| 380 |
|
| 381 |
print("🏆 المرشحون الناجحون:")
|
| 382 |
for k, candidate in enumerate(final_candidates[:15]):
|
|
@@ -387,30 +383,24 @@ class DataManager:
|
|
| 387 |
return final_candidates
|
| 388 |
|
| 389 |
async def _run_mini_detector(self, symbol_data: Dict) -> Dict:
|
| 390 |
-
"""(
|
| 391 |
ohlcv_1h = symbol_data.get('ohlcv_1h')
|
| 392 |
current_price = symbol_data.get('current_price')
|
| 393 |
|
| 394 |
-
# (إنشاء DataFrame مرة واحدة)
|
| 395 |
df = self._create_dataframe(ohlcv_1h.get('1h'))
|
| 396 |
if df.empty:
|
| 397 |
raise ValueError("DataFrame فارغ لتحليل 1H")
|
| 398 |
|
| 399 |
analysis_dict = {'current_price': current_price}
|
| 400 |
|
| 401 |
-
# إنشاء المهام
|
| 402 |
-
# (لا حاجة لـ asyncio.create_task لأن هذه الدوال ليست async)
|
| 403 |
# (ملاحظة: جعلنا دوال التحليل async في ملفاتها لتكون متوافقة)
|
| 404 |
-
|
| 405 |
task_indicators = self.technical_analyzer.calculate_all_indicators(df, '1h')
|
| 406 |
task_mc = self.monte_carlo_analyzer.generate_1h_price_distribution(ohlcv_1h)
|
| 407 |
task_pattern = self.pattern_analyzer.detect_chart_patterns(ohlcv_1h)
|
| 408 |
|
| 409 |
-
# (تشغيل المهام التي هي async)
|
| 410 |
results = await asyncio.gather(task_mc, task_pattern, return_exceptions=True)
|
| 411 |
|
| 412 |
-
|
| 413 |
-
analysis_dict['advanced_indicators'] = {'1h': task_indicators} # (هذه دالة متزامنة)
|
| 414 |
|
| 415 |
if not isinstance(results[0], Exception):
|
| 416 |
analysis_dict['monte_carlo_distribution'] = results[0]
|
|
@@ -421,20 +411,16 @@ class DataManager:
|
|
| 421 |
|
| 422 |
|
| 423 |
async def _fetch_1h_ohlcv_for_screening(self, symbol: str) -> List:
|
| 424 |
-
"""(
|
| 425 |
try:
|
| 426 |
-
# 100 شمعة كافية لحساب (RSI 14, EMA 21, MACD 26, BB 20)
|
| 427 |
ohlcv_data = self.exchange.fetch_ohlcv(symbol, '1h', limit=100)
|
| 428 |
|
| 429 |
-
if not ohlcv_data or len(ohlcv_data) < 50:
|
| 430 |
return None
|
| 431 |
return ohlcv_data
|
| 432 |
except Exception:
|
| 433 |
return None
|
| 434 |
|
| 435 |
-
# 🔴 --- END OF REFACTOR (V7.1) --- 🔴
|
| 436 |
-
|
| 437 |
-
|
| 438 |
# (دوال جلب الحجم تبقى كما هي لأنها فعالة)
|
| 439 |
async def _get_volume_data_optimal(self) -> List[Dict[str, Any]]:
|
| 440 |
try:
|
|
@@ -453,8 +439,13 @@ class DataManager:
|
|
| 453 |
if base_volume is None: continue
|
| 454 |
dollar_volume = base_volume * current_price
|
| 455 |
if dollar_volume is None or dollar_volume < 50000: continue
|
| 456 |
-
|
|
|
|
|
|
|
|
|
|
| 457 |
if price_change_24h is None: price_change_24h = 0
|
|
|
|
|
|
|
| 458 |
volume_data.append({
|
| 459 |
'symbol': symbol, 'dollar_volume': dollar_volume,
|
| 460 |
'current_price': current_price, 'volume_24h': ticker.get('baseVolume', 0) or 0,
|
|
@@ -488,7 +479,10 @@ class DataManager:
|
|
| 488 |
if vol_value is None or last_price is None or change_rate is None or vol is None: continue
|
| 489 |
dollar_volume = float(vol_value) if vol_value else 0
|
| 490 |
current_price = float(last_price) if last_price else 0
|
|
|
|
|
|
|
| 491 |
price_change = (float(change_rate) * 100) if change_rate else 0
|
|
|
|
| 492 |
volume_24h = float(vol) if vol else 0
|
| 493 |
if dollar_volume >= 50000 and current_price > 0:
|
| 494 |
volume_data.append({
|
|
@@ -504,8 +498,9 @@ class DataManager:
|
|
| 504 |
return []
|
| 505 |
|
| 506 |
# (دالة تدفق الشموع تبقى كما هي - لا تغيير)
|
| 507 |
-
async def stream_ohlcv_data(self, symbols: List[str], queue: asyncio.Queue):
|
| 508 |
"""
|
|
|
|
| 509 |
جلب بيانات OHLCV كاملة (6 أطر زمنية) للعملات الناجحة فقط
|
| 510 |
"""
|
| 511 |
print(f"📊 بدء تدفق بيانات OHLCV (الكاملة) لـ {len(symbols)} عملة (الناجحين من الغربلة)...")
|
|
@@ -519,28 +514,37 @@ class DataManager:
|
|
| 519 |
print(f" 🔄 [المنتج] جلب الدفعة {batch_num + 1}/{len(batches)} ({len(batch)} عملة)...")
|
| 520 |
|
| 521 |
batch_tasks = []
|
| 522 |
-
for symbol in batch:
|
| 523 |
-
task = asyncio.create_task(self._fetch_complete_ohlcv_parallel(symbol))
|
| 524 |
-
batch_tasks.append(task)
|
| 525 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
|
| 527 |
|
| 528 |
successful_data_for_batch = []
|
| 529 |
successful_count = 0
|
| 530 |
for i, result in enumerate(batch_results):
|
| 531 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
if isinstance(result, Exception):
|
| 533 |
-
print(f" ❌ [المنتج] فشل جلب {
|
| 534 |
elif result is not None:
|
| 535 |
-
# (
|
| 536 |
-
|
| 537 |
-
result.update(original_data) # (دمج الدرجة الأولية مع البيانات الكاملة)
|
| 538 |
successful_data_for_batch.append(result)
|
| 539 |
successful_count += 1
|
| 540 |
timeframes_count = result.get('successful_timeframes', 0)
|
| 541 |
-
print(f" ✅ [المنتج] {
|
| 542 |
else:
|
| 543 |
-
print(f" ⚠️ [المنتج] {
|
| 544 |
|
| 545 |
print(f" 📦 [المنتج] اكتملت الدفعة {batch_num + 1}: {successful_count}/{len(batch)} ناجحة")
|
| 546 |
|
|
@@ -565,7 +569,7 @@ class DataManager:
|
|
| 565 |
|
| 566 |
|
| 567 |
async def _fetch_complete_ohlcv_parallel(self, symbol: str) -> Dict[str, Any]:
|
| 568 |
-
"""جلب بيانات OHLCV كاملة
|
| 569 |
try:
|
| 570 |
ohlcv_data = {}
|
| 571 |
|
|
@@ -576,6 +580,7 @@ class DataManager:
|
|
| 576 |
|
| 577 |
timeframe_tasks = []
|
| 578 |
for timeframe, limit in timeframes:
|
|
|
|
| 579 |
task = asyncio.create_task(self._fetch_single_timeframe_improved(symbol, timeframe, limit))
|
| 580 |
timeframe_tasks.append(task)
|
| 581 |
|
|
@@ -614,11 +619,12 @@ class DataManager:
|
|
| 614 |
except Exception as e: return None
|
| 615 |
|
| 616 |
async def _fetch_single_timeframe_improved(self, symbol: str, timeframe: str, limit: int):
|
| 617 |
-
"""جلب بيانات إطار زمني واحد
|
| 618 |
max_retries = 3
|
| 619 |
retry_delay = 2
|
| 620 |
for attempt in range(max_retries):
|
| 621 |
try:
|
|
|
|
| 622 |
ohlcv_data = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
|
| 623 |
if ohlcv_data and len(ohlcv_data) > 0:
|
| 624 |
return ohlcv_data
|
|
@@ -631,7 +637,7 @@ class DataManager:
|
|
| 631 |
return []
|
| 632 |
|
| 633 |
async def get_latest_price_async(self, symbol):
|
| 634 |
-
"""جلب السعر الحالي
|
| 635 |
try:
|
| 636 |
if not self.exchange: return None
|
| 637 |
if not symbol or '/' not in symbol: return None
|
|
@@ -660,4 +666,4 @@ class DataManager:
|
|
| 660 |
except Exception as e:
|
| 661 |
return {'action': 'HOLD', 'confidence': 0.3, 'reason': f'Error: {str(e)}', 'source': 'whale_analysis'}
|
| 662 |
|
| 663 |
-
print("✅ DataManager loaded - V7.
|
|
|
|
| 1 |
+
# data_manager.py (Updated to V7.2 - Fixed Task Bug & Added Stablecoin Guard)
|
| 2 |
import os
|
| 3 |
import asyncio
|
| 4 |
import httpx
|
|
|
|
| 11 |
from typing import List, Dict, Any
|
| 12 |
import pandas as pd
|
| 13 |
|
|
|
|
|
|
|
| 14 |
try:
|
| 15 |
import pandas_ta as ta
|
| 16 |
except ImportError:
|
| 17 |
print("⚠️ مكتبة pandas_ta غير موجودة، فلتر الغربلة المتقدم سيفشل.")
|
| 18 |
ta = None
|
| 19 |
|
|
|
|
| 20 |
from ml_engine.indicators import AdvancedTechnicalAnalyzer
|
| 21 |
from ml_engine.monte_carlo import MonteCarloAnalyzer
|
| 22 |
from ml_engine.patterns import ChartPatternAnalyzer
|
|
|
|
| 23 |
|
| 24 |
logging.getLogger("httpx").setLevel(logging.WARNING)
|
| 25 |
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
|
|
| 45 |
self.market_cache = {}
|
| 46 |
self.last_market_load = None
|
| 47 |
|
|
|
|
|
|
|
| 48 |
self.technical_analyzer = AdvancedTechnicalAnalyzer()
|
| 49 |
self.monte_carlo_analyzer = MonteCarloAnalyzer()
|
| 50 |
self.pattern_analyzer = ChartPatternAnalyzer()
|
|
|
|
| 51 |
|
| 52 |
async def initialize(self):
|
| 53 |
self.http_client = httpx.AsyncClient(timeout=30.0)
|
| 54 |
await self._load_markets()
|
| 55 |
+
print("✅ DataManager initialized - V7.2 (Stablecoin Guard Active)")
|
| 56 |
|
| 57 |
async def _load_markets(self):
|
| 58 |
try:
|
|
|
|
| 198 |
'data_quality': 'LOW'
|
| 199 |
}
|
| 200 |
|
| 201 |
+
# 🔴 --- START OF REFACTOR (V7.2 - Fixed Bugs) --- 🔴
|
| 202 |
|
| 203 |
def _create_dataframe(self, candles: List) -> pd.DataFrame:
|
| 204 |
+
"""(V7.1) دالة مساعدة لإنشاء DataFrame لتحليل 1H"""
|
| 205 |
try:
|
| 206 |
if not candles: return pd.DataFrame()
|
| 207 |
df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
|
|
|
| 216 |
|
| 217 |
def _calculate_1h_filter_score(self, analysis: Dict) -> float:
|
| 218 |
"""
|
| 219 |
+
(محدث V7.2) - "الكاشف المصغر"
|
| 220 |
+
يحتوي الآن على "واقي العملات المستقرة"
|
|
|
|
| 221 |
"""
|
| 222 |
try:
|
| 223 |
+
# 🔴 --- (FIX V7.2) واقي العملات المستقرة --- 🔴
|
| 224 |
+
# (يتم تشغيله قبل أي حساب آخر)
|
| 225 |
+
if 'ohlcv_1h' in analysis and '1h' in analysis['ohlcv_1h']:
|
| 226 |
+
closes_1h = [c[4] for c in analysis['ohlcv_1h']['1h']]
|
| 227 |
+
if len(closes_1h) > 20:
|
| 228 |
+
# (نستخدم آخر 20 شمعة للتحقق من التقلب)
|
| 229 |
+
std_dev = np.std(closes_1h[-20:])
|
| 230 |
+
if std_dev < 1e-5: # (تقلب شبه صفري)
|
| 231 |
+
print(f" - {analysis.get('symbol', 'N/A')}: تم الاستبعاد (عملة مستقرة)")
|
| 232 |
+
return 0.0 # (إعطاء درجة صفر فوراً)
|
| 233 |
+
# 🔴 --- نهاية الإصلاح --- 🔴
|
| 234 |
+
|
| 235 |
# 1. درجة الأنماط (Pattern Score)
|
| 236 |
pattern_confidence = analysis.get('pattern_analysis', {}).get('pattern_confidence', 0)
|
| 237 |
|
|
|
|
| 239 |
mc_distribution = analysis.get('monte_carlo_distribution')
|
| 240 |
monte_carlo_score = 0
|
| 241 |
|
| 242 |
+
if mc_distribution and mc_distribution.get('error') is None: # (تحقق من عدم وجود خطأ)
|
| 243 |
prob_gain = mc_distribution.get('probability_of_gain', 0)
|
| 244 |
var_95_value = mc_distribution.get('risk_metrics', {}).get('VaR_95_value', 0)
|
| 245 |
current_price = analysis.get('current_price', 1)
|
|
|
|
| 250 |
if normalized_var > 0.05: risk_penalty = 0.5
|
| 251 |
elif normalized_var > 0.03: risk_penalty = 0.8
|
| 252 |
|
|
|
|
| 253 |
normalized_prob_score = max(0.0, (prob_gain - 0.5) * 2)
|
| 254 |
monte_carlo_score = normalized_prob_score * risk_penalty
|
| 255 |
|
| 256 |
# 3. درجة المؤشرات (Indicator Score)
|
|
|
|
| 257 |
indicator_score = 0
|
| 258 |
indicators = analysis.get('advanced_indicators', {}).get('1h', {})
|
| 259 |
if indicators:
|
|
|
|
| 270 |
indicator_score = min(0.4 + (35 - rsi) / 35, 0.8)
|
| 271 |
|
| 272 |
# 4. حساب النتيجة النهائية (بدون استراتيجيات أو حيتان)
|
|
|
|
| 273 |
components = []
|
| 274 |
weights = []
|
| 275 |
|
|
|
|
| 291 |
|
| 292 |
async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
|
| 293 |
"""
|
| 294 |
+
الطبقة 1: فحص سريع - (محدث بالكامل V7.2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
"""
|
| 296 |
+
print("📊 الطبقة 1 (V7.2): بدء الغربلة (الكاشف المصغر 1H)...")
|
| 297 |
|
| 298 |
# الخطوة 1: جلب أفضل 100 عملة حسب الحجم
|
| 299 |
volume_data = await self._get_volume_data_optimal()
|
|
|
|
| 311 |
|
| 312 |
final_candidates = []
|
| 313 |
|
|
|
|
| 314 |
batch_size = 20
|
| 315 |
for i in range(0, len(top_100_by_volume), batch_size):
|
| 316 |
batch_symbols_data = top_100_by_volume[i:i + batch_size]
|
|
|
|
| 322 |
tasks = [self._fetch_1h_ohlcv_for_screening(symbol) for symbol in batch_symbols]
|
| 323 |
results_candles = await asyncio.gather(*tasks, return_exceptions=True)
|
| 324 |
|
|
|
|
| 325 |
analysis_tasks = []
|
| 326 |
valid_symbol_data = []
|
| 327 |
|
|
|
|
| 334 |
|
| 335 |
# (إعداد البيانات للمحللات)
|
| 336 |
ohlcv_1h_only = {'1h': candles}
|
| 337 |
+
symbol_data['ohlcv_1h'] = ohlcv_1h_only
|
| 338 |
+
symbol_data['current_price'] = candles[-1][4]
|
| 339 |
analysis_tasks.append(self._run_mini_detector(symbol_data))
|
| 340 |
valid_symbol_data.append(symbol_data)
|
| 341 |
|
| 342 |
if not analysis_tasks:
|
| 343 |
continue
|
| 344 |
|
|
|
|
| 345 |
analysis_results = await asyncio.gather(*analysis_tasks, return_exceptions=True)
|
| 346 |
|
| 347 |
for j, (analysis_output) in enumerate(analysis_results):
|
| 348 |
+
symbol_data = valid_symbol_data[j]
|
| 349 |
symbol = symbol_data['symbol']
|
| 350 |
|
| 351 |
if isinstance(analysis_output, Exception):
|
| 352 |
print(f" - {symbol}: فشل الكاشف المصغر ({analysis_output})")
|
| 353 |
continue
|
| 354 |
|
| 355 |
+
# (تمرير ohlcv_1h إلى دالة حساب النقاط من أجل واقي العملات المستقرة)
|
| 356 |
+
analysis_output['ohlcv_1h'] = symbol_data['ohlcv_1h']
|
| 357 |
+
analysis_output['symbol'] = symbol
|
| 358 |
+
|
| 359 |
# الخطوة 4: حساب النتيجة النهائية للفلتر
|
| 360 |
filter_score = self._calculate_1h_filter_score(analysis_output)
|
| 361 |
|
| 362 |
# الخطوة 5: تطبيق العتبة (Threshold)
|
| 363 |
if filter_score >= 0.20:
|
| 364 |
print(f" ✅ {symbol}: نجح (الدرجة: {filter_score:.2f})")
|
|
|
|
| 365 |
symbol_data['layer1_score'] = filter_score
|
| 366 |
symbol_data['reasons_for_candidacy'] = [f'1H_DETECTOR_PASS']
|
| 367 |
|
|
|
|
| 368 |
if 'ohlcv_1h' in symbol_data: del symbol_data['ohlcv_1h']
|
| 369 |
|
| 370 |
final_candidates.append(symbol_data)
|
| 371 |
# else:
|
| 372 |
+
# (تم إيقاف طباعة الفشل لتقليل التشويش)
|
| 373 |
# print(f" - {symbol}: فشل (الدرجة: {filter_score:.2f})")
|
| 374 |
|
| 375 |
+
print(f"🎯 اكتملت الغربلة (V7.2). تم تأهيل {len(final_candidates)} عملة من أصل 100 للطبقة 2.")
|
| 376 |
|
| 377 |
print("🏆 المرشحون الناجحون:")
|
| 378 |
for k, candidate in enumerate(final_candidates[:15]):
|
|
|
|
| 383 |
return final_candidates
|
| 384 |
|
| 385 |
async def _run_mini_detector(self, symbol_data: Dict) -> Dict:
|
| 386 |
+
"""(V7.1) يشغل المحللات الأساسية بالتوازي على بيانات 1H فقط"""
|
| 387 |
ohlcv_1h = symbol_data.get('ohlcv_1h')
|
| 388 |
current_price = symbol_data.get('current_price')
|
| 389 |
|
|
|
|
| 390 |
df = self._create_dataframe(ohlcv_1h.get('1h'))
|
| 391 |
if df.empty:
|
| 392 |
raise ValueError("DataFrame فارغ لتحليل 1H")
|
| 393 |
|
| 394 |
analysis_dict = {'current_price': current_price}
|
| 395 |
|
|
|
|
|
|
|
| 396 |
# (ملاحظة: جعلنا دوال التحليل async في ملفاتها لتكون متوافقة)
|
|
|
|
| 397 |
task_indicators = self.technical_analyzer.calculate_all_indicators(df, '1h')
|
| 398 |
task_mc = self.monte_carlo_analyzer.generate_1h_price_distribution(ohlcv_1h)
|
| 399 |
task_pattern = self.pattern_analyzer.detect_chart_patterns(ohlcv_1h)
|
| 400 |
|
|
|
|
| 401 |
results = await asyncio.gather(task_mc, task_pattern, return_exceptions=True)
|
| 402 |
|
| 403 |
+
analysis_dict['advanced_indicators'] = {'1h': task_indicators}
|
|
|
|
| 404 |
|
| 405 |
if not isinstance(results[0], Exception):
|
| 406 |
analysis_dict['monte_carlo_distribution'] = results[0]
|
|
|
|
| 411 |
|
| 412 |
|
| 413 |
async def _fetch_1h_ohlcv_for_screening(self, symbol: str) -> List:
|
| 414 |
+
"""(V7.1) جلب 100 شمعة لإطار الساعة (1H) للغربلة السريعة"""
|
| 415 |
try:
|
|
|
|
| 416 |
ohlcv_data = self.exchange.fetch_ohlcv(symbol, '1h', limit=100)
|
| 417 |
|
| 418 |
+
if not ohlcv_data or len(ohlcv_data) < 50:
|
| 419 |
return None
|
| 420 |
return ohlcv_data
|
| 421 |
except Exception:
|
| 422 |
return None
|
| 423 |
|
|
|
|
|
|
|
|
|
|
| 424 |
# (دوال جلب الحجم تبقى كما هي لأنها فعالة)
|
| 425 |
async def _get_volume_data_optimal(self) -> List[Dict[str, Any]]:
|
| 426 |
try:
|
|
|
|
| 439 |
if base_volume is None: continue
|
| 440 |
dollar_volume = base_volume * current_price
|
| 441 |
if dollar_volume is None or dollar_volume < 50000: continue
|
| 442 |
+
|
| 443 |
+
# 🔴 --- (FIX V7.2) إصلاح نسبة التغير المئوية --- 🔴
|
| 444 |
+
# (ccxt 'percentage' هي النسبة الفعلية، لا تحتاج للضرب في 100)
|
| 445 |
+
price_change_24h = ticker.get('percentage', 0) or 0
|
| 446 |
if price_change_24h is None: price_change_24h = 0
|
| 447 |
+
# 🔴 --- نهاية الإصلاح --- 🔴
|
| 448 |
+
|
| 449 |
volume_data.append({
|
| 450 |
'symbol': symbol, 'dollar_volume': dollar_volume,
|
| 451 |
'current_price': current_price, 'volume_24h': ticker.get('baseVolume', 0) or 0,
|
|
|
|
| 479 |
if vol_value is None or last_price is None or change_rate is None or vol is None: continue
|
| 480 |
dollar_volume = float(vol_value) if vol_value else 0
|
| 481 |
current_price = float(last_price) if last_price else 0
|
| 482 |
+
|
| 483 |
+
# (الإصلاح هنا أيضاً: 'changeRate' هو النسبة العشرية، لذا نضرب في 100)
|
| 484 |
price_change = (float(change_rate) * 100) if change_rate else 0
|
| 485 |
+
|
| 486 |
volume_24h = float(vol) if vol else 0
|
| 487 |
if dollar_volume >= 50000 and current_price > 0:
|
| 488 |
volume_data.append({
|
|
|
|
| 498 |
return []
|
| 499 |
|
| 500 |
# (دالة تدفق الشموع تبقى كما هي - لا تغيير)
|
| 501 |
+
async def stream_ohlcv_data(self, symbols: List[Dict[str, Any]], queue: asyncio.Queue):
|
| 502 |
"""
|
| 503 |
+
(محدث V7.2)
|
| 504 |
جلب بيانات OHLCV كاملة (6 أطر زمنية) للعملات الناجحة فقط
|
| 505 |
"""
|
| 506 |
print(f"📊 بدء تدفق بيانات OHLCV (الكاملة) لـ {len(symbols)} عملة (الناجحين من الغربلة)...")
|
|
|
|
| 514 |
print(f" 🔄 [المنتج] جلب الدفعة {batch_num + 1}/{len(batches)} ({len(batch)} عملة)...")
|
| 515 |
|
| 516 |
batch_tasks = []
|
|
|
|
|
|
|
|
|
|
| 517 |
|
| 518 |
+
# 🔴 --- (FIX V7.2) إصلاح تمرير الوسيط --- 🔴
|
| 519 |
+
for symbol_data in batch:
|
| 520 |
+
symbol_str = symbol_data['symbol'] # (استخراج النص)
|
| 521 |
+
# (تمرير النص فقط إلى الدالة التي تتوقع نصاً)
|
| 522 |
+
task = asyncio.create_task(self._fetch_complete_ohlcv_parallel(symbol_str))
|
| 523 |
+
batch_tasks.append(task)
|
| 524 |
+
# 🔴 --- نهاية الإصلاح --- 🔴
|
| 525 |
+
|
| 526 |
batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
|
| 527 |
|
| 528 |
successful_data_for_batch = []
|
| 529 |
successful_count = 0
|
| 530 |
for i, result in enumerate(batch_results):
|
| 531 |
+
|
| 532 |
+
# 🔴 --- (FIX V7.2) مطابقة النتائج بالبيانات الأصلية --- 🔴
|
| 533 |
+
original_symbol_data = batch[i]
|
| 534 |
+
symbol_str = original_symbol_data['symbol']
|
| 535 |
+
# 🔴 --- نهاية الإصلاح --- 🔴
|
| 536 |
+
|
| 537 |
if isinstance(result, Exception):
|
| 538 |
+
print(f" ❌ [المنتج] فشل جلب {symbol_str}: {result}")
|
| 539 |
elif result is not None:
|
| 540 |
+
# (دمج الدرجة الأولية مع البيانات الكاملة)
|
| 541 |
+
result.update(original_symbol_data)
|
|
|
|
| 542 |
successful_data_for_batch.append(result)
|
| 543 |
successful_count += 1
|
| 544 |
timeframes_count = result.get('successful_timeframes', 0)
|
| 545 |
+
print(f" ✅ [المنتج] {symbol_str}: {timeframes_count}/6 أطر زمنية")
|
| 546 |
else:
|
| 547 |
+
print(f" ⚠️ [المنتج] {symbol_str}: بيانات غير كافية، تم التجاهل")
|
| 548 |
|
| 549 |
print(f" 📦 [المنتج] اكتملت الدفعة {batch_num + 1}: {successful_count}/{len(batch)} ناجحة")
|
| 550 |
|
|
|
|
| 569 |
|
| 570 |
|
| 571 |
async def _fetch_complete_ohlcv_parallel(self, symbol: str) -> Dict[str, Any]:
|
| 572 |
+
"""(V7.2) جلب بيانات OHLCV كاملة - يتوقع 'symbol' كنص"""
|
| 573 |
try:
|
| 574 |
ohlcv_data = {}
|
| 575 |
|
|
|
|
| 580 |
|
| 581 |
timeframe_tasks = []
|
| 582 |
for timeframe, limit in timeframes:
|
| 583 |
+
# (هنا 'symbol' هو نص، وهو صحيح)
|
| 584 |
task = asyncio.create_task(self._fetch_single_timeframe_improved(symbol, timeframe, limit))
|
| 585 |
timeframe_tasks.append(task)
|
| 586 |
|
|
|
|
| 619 |
except Exception as e: return None
|
| 620 |
|
| 621 |
async def _fetch_single_timeframe_improved(self, symbol: str, timeframe: str, limit: int):
|
| 622 |
+
"""(V7.2) جلب بيانات إطار زمني واحد - يتوقع 'symbol' كنص"""
|
| 623 |
max_retries = 3
|
| 624 |
retry_delay = 2
|
| 625 |
for attempt in range(max_retries):
|
| 626 |
try:
|
| 627 |
+
# (هنا 'symbol' هو نص، وهو صحيح)
|
| 628 |
ohlcv_data = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
|
| 629 |
if ohlcv_data and len(ohlcv_data) > 0:
|
| 630 |
return ohlcv_data
|
|
|
|
| 637 |
return []
|
| 638 |
|
| 639 |
async def get_latest_price_async(self, symbol):
|
| 640 |
+
"""(V7.2) جلب السعر الحالي - يتوقع 'symbol' كنص"""
|
| 641 |
try:
|
| 642 |
if not self.exchange: return None
|
| 643 |
if not symbol or '/' not in symbol: return None
|
|
|
|
| 666 |
except Exception as e:
|
| 667 |
return {'action': 'HOLD', 'confidence': 0.3, 'reason': f'Error: {str(e)}', 'source': 'whale_analysis'}
|
| 668 |
|
| 669 |
+
print("✅ DataManager loaded - V7.2 (Stablecoin Guard Active + Task Bug Fixed)")
|