File size: 33,757 Bytes
6c7250f 00bb5c9 79a9e95 d69dead 28fa18b 394e2c7 164b380 11b4dc5 6b00681 11b4dc5 53cf6c0 28fa18b 53cf6c0 6b00681 53cf6c0 d2775f3 6b00681 c6f72fe 56e3f87 af73eb9 248e033 c6f72fe 79a9e95 c6f72fe dceb75a c6f72fe 87e3669 56e3f87 11b4dc5 6b00681 d69dead 53cf6c0 248e033 56e3f87 6b00681 6c7250f 24a0949 56e3f87 dceb75a 7985470 56e3f87 dceb75a 56e3f87 dceb75a 56e3f87 53cf6c0 b866e29 ea38153 b866e29 164b380 24a0949 d69dead 248e033 24a0949 d69dead 24a0949 56e3f87 d69dead 24a0949 d69dead 24a0949 d69dead 248e033 24a0949 d69dead 56e3f87 24a0949 396f10a 87e3669 24a0949 87e3669 24a0949 87e3669 24a0949 11b4dc5 248e033 11b4dc5 ed04390 ea38153 11b4dc5 ea38153 c6f72fe ea38153 248e033 ea38153 c6f72fe 11b4dc5 ea38153 c6f72fe 7985470 248e033 11b4dc5 7985470 248e033 11b4dc5 c6f72fe ea38153 248e033 ea38153 11b4dc5 eb48a52 248e033 eb48a52 ea38153 56e3f87 ea38153 87e3669 ea38153 87e3669 24a0949 53cf6c0 164b380 11b4dc5 3fd2d9a 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 6c7250f 11b4dc5 24a0949 6714a95 24a0949 6c7250f 79a9e95 164b380 11b4dc5 79a9e95 b44825a 164b380 79a9e95 164b380 79a9e95 6c7250f 79a9e95 164b380 79a9e95 164b380 11b4dc5 164b380 11b4dc5 164b380 11b4dc5 164b380 11b4dc5 3fd2d9a 11b4dc5 3fd2d9a 11b4dc5 164b380 3fd2d9a 6c7250f 11b4dc5 164b380 6c7250f 6714a95 11b4dc5 6c7250f 11b4dc5 164b380 6c7250f 164b380 11b4dc5 7985470 11b4dc5 24a0949 79a9e95 11b4dc5 3fd2d9a 11b4dc5 164b380 11b4dc5 164b380 11b4dc5 6b00681 11b4dc5 3fd2d9a 11b4dc5 164b380 11b4dc5 164b380 11b4dc5 3fd2d9a 164b380 11b4dc5 164b380 3fd2d9a 11b4dc5 164b380 11b4dc5 164b380 b44825a 11b4dc5 7985470 b44825a 11b4dc5 79a9e95 b44825a 11b4dc5 f253a62 b44825a 11b4dc5 b44825a 11b4dc5 3fd2d9a 11b4dc5 3fd2d9a 79a9e95 11b4dc5 f253a62 79a9e95 11b4dc5 b44825a c10d7f8 b44825a 11b4dc5 b44825a 11b4dc5 b44825a f253a62 11b4dc5 f253a62 7985470 11b4dc5 7985470 11b4dc5 164b380 b44825a c10d7f8 b44825a 3fd2d9a 5663c09 3fd2d9a 164b380 5663c09 164b380 24a0949 113d926 dc2c23a 113d926 dc2c23a 113d926 dc2c23a 6714a95 3fd2d9a 6714a95 3fd2d9a dc2c23a 113d926 dc2c23a 3fd2d9a dc2c23a 3fd2d9a dc2c23a 3fd2d9a 113d926 dc2c23a 307f514 3fd2d9a 248e033 3fd2d9a 113d926 dc2c23a 113d926 24a0949 164b380 f16cd30 113d926 6598c39 dc2c23a 3fd2d9a 7f28923 248e033 7f28923 11b4dc5 7f28923 dc2c23a 08e895c dc2c23a 08e895c 164b380 248e033 dc2c23a 11b4dc5 6b00681 dc2c23a 08e895c 7f28923 6b00681 afa0eeb 7aab55a 231c23d 11b4dc5 231c23d 60b8fa4 11b4dc5 08e895c afa0eeb 60b8fa4 11b4dc5 7f28923 08e895c 3fd2d9a 08e895c 7985470 08e895c dc2c23a 6598c39 3fd2d9a 6598c39 11b4dc5 7985470 11b4dc5 116ef05 11b4dc5 231c23d 11b4dc5 56e3f87 20a2029 11b4dc5 20a2029 11b4dc5 20a2029 11b4dc5 20a2029 6c7250f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 |
# data_manager.py (Updated to V7.4 - 1H Momentum Burst Filter)
import os
import asyncio
import httpx
import traceback
import time
from datetime import datetime
import ccxt
import numpy as np
import logging
from typing import List, Dict, Any
import pandas as pd
try:
import pandas_ta as ta
except ImportError:
print("⚠️ مكتبة pandas_ta غير موجودة، فلتر الغربلة المتقدم سيفشل.")
ta = None
from ml_engine.indicators import AdvancedTechnicalAnalyzer
from ml_engine.monte_carlo import MonteCarloAnalyzer
# (V8-MODIFICATION) استيراد المحرك الصحيح (V8)
from ml_engine.patterns import ChartPatternAnalyzer
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
class DataManager:
# (V8-MODIFICATION) قبول r2_service
def __init__(self, contracts_db, whale_monitor, r2_service=None):
self.contracts_db = contracts_db or {}
self.whale_monitor = whale_monitor
self.r2_service = r2_service # (V8-MODIFICATION) الإضافة الجديدة
try:
self.exchange = ccxt.kucoin({
'sandbox': False,
'enableRateLimit': True,
'timeout': 30000,
'verbose': False,
})
print("✅ تم تهيئة اتصال KuCoin بنجاح")
except Exception as e:
print(f"❌ فشل تهيئة اتصال KuCoin: {e}")
self.exchange = None
self.http_client = None
self.market_cache = {}
self.last_market_load = None
self.technical_analyzer = AdvancedTechnicalAnalyzer()
self.monte_carlo_analyzer = MonteCarloAnalyzer()
self.pattern_analyzer = None # (V8-MODIFICATION) سيتم تهيئته في initialize
async def initialize(self):
self.http_client = httpx.AsyncClient(timeout=30.0)
await self._load_markets()
# (V8-MODIFICATION) تهيئة محرك الأنماط V8 (ML-Based)
print(" > [DataManager] تهيئة محرك الأنماط V8 (ML-Based)...")
try:
self.pattern_analyzer = ChartPatternAnalyzer(r2_service=self.r2_service)
await self.pattern_analyzer.initialize() # (تحميل النموذج والمقياس من R2)
except Exception as e:
print(f"❌ [DataManager] فشل تهيئة محرك الأنماط V8: {e}")
# (العودة للوضع الآمن إذا فشل التحميل)
self.pattern_analyzer = ChartPatternAnalyzer(r2_service=None)
# --- (نهاية الإضافة) ---
print("✅ DataManager initialized - V7.4 (1H Momentum Burst Filter)")
async def _load_markets(self):
try:
if not self.exchange:
return
print("🔄 جلب أحدث بيانات الأسواق من KuCoin...")
self.exchange.load_markets()
self.market_cache = self.exchange.markets
self.last_market_load = datetime.now()
print(f"✅ تم تحميل {len(self.market_cache)} سوق من KuCoin")
except Exception as e:
print(f"❌ فشل تحميل بيانات الأسواق: {e}")
async def close(self):
if self.http_client and not self.http_client.is_closed:
await self.http_client.aclose()
print(" ✅ DataManager: http_client closed.")
if self.exchange:
try:
await self.exchange.close()
print(" ✅ DataManager: ccxt.kucoin exchange closed.")
except Exception as e:
print(f" ⚠️ DataManager: Error closing ccxt.kucoin: {e}")
async def get_market_context_async(self):
try:
sentiment_data = await self.get_sentiment_safe_async()
price_data = await self._get_prices_with_fallback()
bitcoin_price = price_data.get('bitcoin')
ethereum_price = price_data.get('ethereum')
market_context = {
'timestamp': datetime.now().isoformat(),
'bitcoin_price_usd': bitcoin_price,
'ethereum_price_usd': ethereum_price,
'fear_and_greed_index': sentiment_data.get('feargreed_value') if sentiment_data else None,
'sentiment_class': sentiment_data.get('feargreed_class') if sentiment_data else 'NEUTRAL',
'market_trend': self._determine_market_trend(bitcoin_price, sentiment_data),
'btc_sentiment': self._get_btc_sentiment(bitcoin_price),
'data_quality': 'HIGH' if bitcoin_price and ethereum_price else 'LOW'
}
return market_context
except Exception as e:
return self._get_minimal_market_context()
async def get_sentiment_safe_async(self):
try:
async with httpx.AsyncClient(timeout=10) as client:
response = await client.get("https://api.alternative.me/fng/")
response.raise_for_status()
data = response.json()
if 'data' not in data or not data['data']:
raise ValueError("بيانات المشاعر غير متوفرة")
latest_data = data['data'][0]
return {
"feargreed_value": int(latest_data['value']),
"feargreed_class": latest_data['value_classification'],
"source": "alternative.me",
"timestamp": datetime.now().isoformat()
}
except Exception as e:
return None
def _determine_market_trend(self, bitcoin_price, sentiment_data):
if bitcoin_price is None:
return "UNKNOWN"
if bitcoin_price > 60000: score = 1
elif bitcoin_price < 55000: score = -1
else: score = 0
if sentiment_data and sentiment_data.get('feargreed_value') is not None:
fear_greed = sentiment_data.get('feargreed_value')
if fear_greed > 60: score += 1
elif fear_greed < 40: score -= 1
if score >= 1: return "bull_market"
elif score <= -1: return "bear_market"
else: return "sideways_market"
def _get_btc_sentiment(self, bitcoin_price):
if bitcoin_price is None: return 'UNKNOWN'
elif bitcoin_price > 60000: return 'BULLISH'
elif bitcoin_price < 55000: return 'BEARISH'
else: return 'NEUTRAL'
async def _get_prices_with_fallback(self):
try:
prices = await self._get_prices_from_kucoin_safe()
if prices.get('bitcoin') and prices.get('ethereum'):
return prices
return await self._get_prices_from_coingecko()
except Exception as e:
return {'bitcoin': None, 'ethereum': None}
async def _get_prices_from_kucoin_safe(self):
if not self.exchange: return {'bitcoin': None, 'ethereum': None}
try:
prices = {'bitcoin': None, 'ethereum': None}
btc_ticker = self.exchange.fetch_ticker('BTC/USDT')
btc_price = float(btc_ticker.get('last', 0)) if btc_ticker.get('last') else None
if btc_price and btc_price > 0: prices['bitcoin'] = btc_price
eth_ticker = self.exchange.fetch_ticker('ETH/USDT')
eth_price = float(eth_ticker.get('last', 0)) if eth_ticker.get('last') else None
if eth_price and eth_price > 0: prices['ethereum'] = eth_price
return prices
except Exception as e:
return {'bitcoin': None, 'ethereum': None}
async def _get_prices_from_coingecko(self):
try:
await asyncio.sleep(0.5)
url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd"
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json'}
async with httpx.AsyncClient(headers=headers) as client:
response = await client.get(url, timeout=10)
if response.status_code == 429:
await asyncio.sleep(2)
response = await client.get(url, timeout=10)
response.raise_for_status()
data = response.json()
btc_price = data.get('bitcoin', {}).get('usd')
eth_price = data.get('ethereum', {}).get('usd')
if btc_price and eth_price:
return {'bitcoin': btc_price, 'ethereum': eth_price}
else:
return {'bitcoin': None, 'ethereum': None}
except Exception as e:
return {'bitcoin': None, 'ethereum': None}
def _get_minimal_market_context(self):
return {
'timestamp': datetime.now().isoformat(),
'data_available': False,
'market_trend': 'UNKNOWN',
'btc_sentiment': 'UNKNOWN',
'data_quality': 'LOW'
}
def _create_dataframe(self, candles: List) -> pd.DataFrame:
"""(V7.1) دالة مساعدة لإنشاء DataFrame لتحليل 1H"""
try:
if not candles: return pd.DataFrame()
df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)
df.sort_index(inplace=True)
return df
except Exception as e:
print(f"❌ خطأ في إنشاء DataFrame لمرشح 1H: {e}")
return pd.DataFrame()
# 🔴 --- START OF CHANGE (V7.4) --- 🔴
# (دالة مساعدة جديدة لتقسيم منطق MC)
def _get_mc_score_for_filter(self, analysis: Dict) -> float:
"""(V7.4) (دالة مساعدة) لحساب درجة مونت كارلو للفلتر"""
mc_distribution = analysis.get('monte_carlo_distribution')
monte_carlo_score = 0
if mc_distribution and mc_distribution.get('error') is None:
prob_gain = mc_distribution.get('probability_of_gain', 0)
var_95_value = mc_distribution.get('risk_metrics', {}).get('VaR_95_value', 0)
current_price = analysis.get('current_price', 1)
if current_price > 0:
normalized_var = var_95_value / current_price
risk_penalty = 1.0
if normalized_var > 0.05: risk_penalty = 0.5
elif normalized_var > 0.03: risk_penalty = 0.8
normalized_prob_score = max(0.0, (prob_gain - 0.5) * 2)
monte_carlo_score = normalized_prob_score * risk_penalty
return monte_carlo_score
def _calculate_1h_filter_score(self, analysis: Dict) -> float:
"""
(محدث V7.4 - فلتر الزخم المتفجر 1H)
"فلتر شمس منتصف الظهر"
يبحث عن:
1. انفجار الحجم (Volume Explosion)
2. قوة الاتجاه (Trend Strength - ADX)
3. المنطقة الآمنة (RSI Safe Zone)
4. (يحتوي على واقي العملات المستقرة V7.2)
"""
try:
# (V7.2) واقي العملات المستقرة (لا تغيير)
ohlcv_candles = analysis.get('ohlcv_1h', {}).get('1h', [])
if not ohlcv_candles or len(ohlcv_candles) < 30: # (تحتاج 30 لـ ADX و Vol MA)
return 0.0
closes_1h = [c[4] for c in ohlcv_candles]
if len(closes_1h) > 20: # (التحقق من العملة المستقرة أولاً)
std_dev = np.std(closes_1h[-20:])
if std_dev < 1e-5:
# print(f" - {analysis.get('symbol', 'N/A')}: تم الاستبعاد (عملة مستقرة)")
return 0.0
# --- (الإضافة الجديدة: حساب المؤشرات المتقدمة للفلتر) ---
if ta is None: # (التحقق من pandas_ta)
return 0.0 # لا يمكن الحساب بدون المكتبة
df = self._create_dataframe(ohlcv_candles) # (إعادة إنشاء DF لحساب ADX/Vol)
if df.empty:
return 0.0
# 1. حساب مؤشرات الزخم المتفجر
volume = df['volume']
vol_ma = ta.sma(volume, length=20)
if vol_ma is None or vol_ma.empty: return 0.0
current_volume = volume.iloc[-1]
avg_volume = vol_ma.iloc[-1]
adx_data = ta.adx(df['high'], df['low'], df['close'], length=14)
current_adx = adx_data['ADX_14'].iloc[-1] if adx_data is not None and not adx_data.empty else 0
# 2. جلب المؤشرات الأساسية (المحسوبة مسبقاً)
indicators = analysis.get('advanced_indicators', {}).get('1h', {})
rsi = indicators.get('rsi', 50)
# 3. جلب درجة مونت كارلو (المحسوبة مسبقاً)
monte_carlo_score = self._get_mc_score_for_filter(analysis)
# 4. جلب درجة الأنماط (المحسوبة مسبقاً)
pattern_confidence = analysis.get('pattern_analysis', {}).get('pattern_confidence', 0)
# --- (منطق الفلترة الجديد) ---
# المعايير الصارمة لـ "شمس منتصف الظهر"
VOL_MULTIPLIER = 1.75 # (يجب أن يكون الحجم الحالي 1.75x المتوسط)
ADX_THRESHOLD = 25.0 # (يجب أن يكون الاتجاه قوياً)
RSI_MIN = 60 # (يجب أن يكون في منطقة صاعدة)
RSI_MAX = 85 # (يجب ألا يكون منهكاً تماماً)
vol_score = 0.0
if avg_volume > 0:
# (تطبيع درجة الحجم: 1.0 إذا كان يساوي أو يفوق المضاعف)
vol_score = min(1.0, max(0.0, (current_volume / avg_volume) / VOL_MULTIPLIER))
# (تطبيع درجة ADX: 0.0 عند 25، و 1.0 عند 40+)
adx_score = min(1.0, max(0.0, (current_adx - ADX_THRESHOLD) / 15.0))
rsi_score = 0.0
if RSI_MIN <= rsi <= RSI_MAX:
rsi_score = 1.0
elif rsi > RSI_MAX: # (عقوبة بسيطة للإرهاق)
rsi_score = 0.5
# (الأوزان الجديدة) - إعطاء الأولوية للزخم والحجم
WEIGHT_VOL = 0.30
WEIGHT_ADX = 0.30
WEIGHT_RSI = 0.15
WEIGHT_MC = 0.15
WEIGHT_PATTERN = 0.10 # (تقليل أهمية النمط أثناء الانفجار)
final_score = (
(vol_score * WEIGHT_VOL) +
(adx_score * WEIGHT_ADX) +
(rsi_score * WEIGHT_RSI) +
(monte_carlo_score * WEIGHT_MC) +
(pattern_confidence * WEIGHT_PATTERN)
)
# (العتبة (Threshold) لا تزال 0.50 كما هي في V7.3)
return min(max(final_score, 0.0), 1.0)
except Exception as e:
# print(f"❌ خطأ في حساب درجة فلتر 1H (V-Burst): {e}")
return 0.0
# 🔴 --- END OF CHANGE --- 🔴
async def layer1_rapid_screening(self) -> List[Dict[str, Any]]:
"""
الطبقة 1: فحص سريع - (محدث بالكامل V7.3)
"""
print("📊 الطبقة 1 (V7.4): بدء الغربلة (الكاشف المتفجر 1H)...")
# الخطوة 1: جلب أفضل 100 عملة حسب الحجم
volume_data = await self._get_volume_data_optimal()
if not volume_data:
volume_data = await self._get_volume_data_direct_api()
if not volume_data:
print("❌ فشل جلب بيانات الأحجام للطبقة 1")
return []
volume_data.sort(key=lambda x: x['dollar_volume'], reverse=True)
top_100_by_volume = volume_data[:100]
print(f"✅ تم تحديد أفضل {len(top_100_by_volume)} عملة. بدء تشغيل الكاشف المتفجر (1H)...")
final_candidates = []
batch_size = 20
for i in range(0, len(top_100_by_volume), batch_size):
batch_symbols_data = top_100_by_volume[i:i + batch_size]
batch_symbols = [s['symbol'] for s in batch_symbols_data]
print(f" 🔄 معالجة دفعة {int(i/batch_size) + 1}/{(len(top_100_by_volume) + batch_size - 1) // batch_size} ({len(batch_symbols)} عملة)...")
# الخطوة 2: جلب بيانات 1H بالتوازي
tasks = [self._fetch_1h_ohlcv_for_screening(symbol) for symbol in batch_symbols]
results_candles = await asyncio.gather(*tasks, return_exceptions=True)
analysis_tasks = []
valid_symbol_data = []
for j, (candles) in enumerate(results_candles):
symbol_data = batch_symbols_data[j]
symbol = symbol_data['symbol']
if isinstance(candles, Exception) or not candles or len(candles) < 50:
continue
ohlcv_1h_only = {'1h': candles}
symbol_data['ohlcv_1h'] = ohlcv_1h_only
symbol_data['current_price'] = candles[-1][4]
analysis_tasks.append(self._run_mini_detector(symbol_data))
valid_symbol_data.append(symbol_data)
if not analysis_tasks:
continue
analysis_results = await asyncio.gather(*analysis_tasks, return_exceptions=True)
for j, (analysis_output) in enumerate(analysis_results):
symbol_data = valid_symbol_data[j]
symbol = symbol_data['symbol']
if isinstance(analysis_output, Exception):
print(f" - {symbol}: فشل الكاشف المصغر ({analysis_output})")
continue
analysis_output['ohlcv_1h'] = symbol_data['ohlcv_1h']
analysis_output['symbol'] = symbol
# (استدعاء الدالة الجديدة V7.4)
filter_score = self._calculate_1h_filter_score(analysis_output)
# (لا تغيير في العتبة، V7.3)
if filter_score >= 0.50:
print(f" ✅ {symbol}: نجح (الدرجة: {filter_score:.2f})")
symbol_data['layer1_score'] = filter_score
symbol_data['reasons_for_candidacy'] = [f'1H_MOMENTUM_BURST']
if 'ohlcv_1h' in symbol_data: del symbol_data['ohlcv_1h']
final_candidates.append(symbol_data)
print(f"🎯 اكتملت الغربلة (V7.4). تم تأهيل {len(final_candidates)} عملة من أصل 100 للطبقة 2.")
print("🏆 المرشحون الناجحون:")
for k, candidate in enumerate(final_candidates[:15]):
score = candidate.get('layer1_score', 0)
volume = candidate.get('dollar_volume', 0)
print(f" {k+1:2d}. {candidate['symbol']}: (الدرجة: {score:.2f}) | ${volume:,.0f}")
return final_candidates
async def _run_mini_detector(self, symbol_data: Dict) -> Dict:
"""(V7.1) يشغل المحللات الأساسية بالتوازي على بيانات 1H فقط"""
ohlcv_1h = symbol_data.get('ohlcv_1h')
current_price = symbol_data.get('current_price')
df = self._create_dataframe(ohlcv_1h.get('1h'))
if df.empty:
raise ValueError("DataFrame فارغ لتحليل 1H")
analysis_dict = {'current_price': current_price}
task_indicators = self.technical_analyzer.calculate_all_indicators(df, '1h')
task_mc = self.monte_carlo_analyzer.generate_1h_price_distribution(ohlcv_1h)
# (V8-MODIFICATION) استخدام الدالة الجديدة
task_pattern = self.pattern_analyzer.detect_chart_patterns(ohlcv_1h)
results = await asyncio.gather(task_mc, task_pattern, return_exceptions=True)
analysis_dict['advanced_indicators'] = {'1h': task_indicators}
if not isinstance(results[0], Exception):
analysis_dict['monte_carlo_distribution'] = results[0]
if not isinstance(results[1], Exception):
analysis_dict['pattern_analysis'] = results[1]
return analysis_dict
async def _fetch_1h_ohlcv_for_screening(self, symbol: str) -> List:
"""(V7.1) جلب 100 شمعة لإطار الساعة (1H) للغربلة السريعة"""
try:
ohlcv_data = self.exchange.fetch_ohlcv(symbol, '1h', limit=100)
if not ohlcv_data or len(ohlcv_data) < 50:
return None
return ohlcv_data
except Exception:
return None
async def _get_volume_data_optimal(self) -> List[Dict[str, Any]]:
try:
if not self.exchange: return []
tickers = self.exchange.fetch_tickers()
volume_data = []
for symbol, ticker in tickers.items():
if not symbol.endswith('/USDT') or not ticker.get('active', True): continue
current_price = ticker.get('last', 0)
quote_volume = ticker.get('quoteVolume', 0)
if current_price is None or current_price <= 0: continue
if quote_volume is not None and quote_volume > 0:
dollar_volume = quote_volume
else:
base_volume = ticker.get('baseVolume', 0)
if base_volume is None: continue
dollar_volume = base_volume * current_price
if dollar_volume is None or dollar_volume < 50000: continue
price_change_24h = ticker.get('percentage', 0) or 0
if price_change_24h is None: price_change_24h = 0
volume_data.append({
'symbol': symbol, 'dollar_volume': dollar_volume,
'current_price': current_price, 'volume_24h': ticker.get('baseVolume', 0) or 0,
'price_change_24h': price_change_24h
})
print(f"✅ تم معالجة {len(volume_data)} عملة في الطريقة المثلى (لجلب الحجم)")
return volume_data
except Exception as e:
print(f"❌ خطأ في جلب بيانات الحجم المثلى: {e}")
return []
async def _get_volume_data_direct_api(self) -> List[Dict[str, Any]]:
try:
url = "https://api.kucoin.com/api/v1/market/allTickers"
async with httpx.AsyncClient(timeout=15) as client:
response = await client.get(url)
response.raise_for_status()
data = response.json()
if data.get('code') != '200000': raise ValueError(f"استجابة API غير متوقعة: {data.get('code')}")
tickers = data['data']['ticker']
volume_data = []
for ticker in tickers:
symbol = ticker['symbol']
if not symbol.endswith('USDT'): continue
formatted_symbol = symbol.replace('-', '/')
try:
vol_value = ticker.get('volValue')
last_price = ticker.get('last')
change_rate = ticker.get('changeRate')
vol = ticker.get('vol')
if vol_value is None or last_price is None or change_rate is None or vol is None: continue
dollar_volume = float(vol_value) if vol_value else 0
current_price = float(last_price) if last_price else 0
price_change = (float(change_rate) * 100) if change_rate else 0
volume_24h = float(vol) if vol else 0
if dollar_volume >= 50000 and current_price > 0:
volume_data.append({
'symbol': formatted_symbol, 'dollar_volume': dollar_volume,
'current_price': current_price, 'volume_24h': volume_24h,
'price_change_24h': price_change
})
except (ValueError, TypeError, KeyError) as e: continue
print(f"✅ تم معالجة {len(volume_data)} عملة في الطريقة المباشرة (لجلب الحجم)")
return volume_data
except Exception as e:
print(f"❌ خطأ في جلب بيانات الحجم المباشر: {e}")
return []
async def stream_ohlcv_data(self, symbols: List[Dict[str, Any]], queue: asyncio.Queue):
"""
(محدث V7.2)
جلب بيانات OHLCV كاملة (6 أطر زمنية) للعملات الناجحة فقط
"""
print(f"📊 بدء تدفق بيانات OHLCV (الكاملة) لـ {len(symbols)} عملة (الناجحين من الغربلة)...")
batch_size = 15
batches = [symbols[i:i + batch_size] for i in range(0, len(symbols), batch_size)]
total_successful = 0
for batch_num, batch in enumerate(batches):
print(f" 🔄 [المنتج] جلب الدفعة {batch_num + 1}/{len(batches)} ({len(batch)} عملة)...")
batch_tasks = []
# (V7.2 FIX)
for symbol_data in batch:
symbol_str = symbol_data['symbol']
task = asyncio.create_task(self._fetch_complete_ohlcv_parallel(symbol_str))
batch_tasks.append(task)
batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
successful_data_for_batch = []
successful_count = 0
for i, result in enumerate(batch_results):
original_symbol_data = batch[i]
symbol_str = original_symbol_data['symbol']
if isinstance(result, Exception):
print(f" ❌ [المنتج] فشل جلب {symbol_str}: {result}")
elif result is not None:
result.update(original_symbol_data)
successful_data_for_batch.append(result)
successful_count += 1
timeframes_count = result.get('successful_timeframes', 0)
print(f" ✅ [المنتج] {symbol_str}: {timeframes_count}/6 أطر زمنية")
else:
print(f" ⚠️ [المنتج] {symbol_str}: بيانات غير كافية، تم التجاهل")
print(f" 📦 [المنتج] اكتملت الدفعة {batch_num + 1}: {successful_count}/{len(batch)} ناجحة")
if successful_data_for_batch:
try:
await queue.put(successful_data_for_batch)
print(f" 📬 [المنتج] تم إرسال {len(successful_data_for_batch)} عملة إلى طابور المعالجة")
total_successful += len(successful_data_for_batch)
except Exception as q_err:
print(f" ❌ [المنتج] فشل إرسال الدفعة للطابور: {q_err}")
if batch_num < len(batches) - 1:
await asyncio.sleep(1)
print(f"✅ [المنتج] اكتمل تدفق بيانات OHLCV (الكاملة). تم إرسال {total_successful} عملة للمعالجة.")
try:
await queue.put(None)
print(" 📬 [المنتج] تم إرسال إشارة الإنهاء (None) إلى الطابور.")
except Exception as q_err:
print(f" ❌ [المنتج] فشل إرسال إشارة الإنهاء (None) للطابور: {q_err}")
async def _fetch_complete_ohlcv_parallel(self, symbol: str) -> Dict[str, Any]:
"""(V7.2) جلب بيانات OHLCV كاملة - يتوقع 'symbol' كنص"""
try:
ohlcv_data = {}
timeframes = [
('5m', 200), ('15m', 200), ('1h', 200),
('4h', 200), ('1d', 200), ('1w', 200),
]
timeframe_tasks = []
for timeframe, limit in timeframes:
task = asyncio.create_task(self._fetch_single_timeframe_improved(symbol, timeframe, limit))
timeframe_tasks.append(task)
timeframe_results = await asyncio.gather(*timeframe_tasks, return_exceptions=True)
successful_timeframes = 0
min_required_timeframes = 2
for i, (timeframe, limit) in enumerate(timeframes):
result = timeframe_results[i]
if isinstance(result, Exception): continue
# (V8-MODIFICATION) زيادة الحد الأدنى من الشموع لاستيعاب المؤشرات
if result and len(result) >= 200: # (كان 10)
ohlcv_data[timeframe] = result
successful_timeframes += 1
# (V8-MODIFICATION) زيادة الحد الأدنى للأطر الزمنية
if successful_timeframes >= 3 and ohlcv_data: # (كان 2)
try:
current_price = await self.get_latest_price_async(symbol)
if current_price is None:
for timeframe_data in ohlcv_data.values():
if timeframe_data and len(timeframe_data) > 0:
last_candle = timeframe_data[-1]
if len(last_candle) >= 5:
current_price = last_candle[4]; break
if current_price is None: return None
result_data = {
'symbol': symbol, 'ohlcv': ohlcv_data, 'raw_ohlcv': ohlcv_data,
'current_price': current_price, 'timestamp': datetime.now().isoformat(),
'candles_count': {tf: len(data) for tf, data in ohlcv_data.items()},
'successful_timeframes': successful_timeframes
}
return result_data
except Exception as price_error: return None
else: return None
except Exception as e: return None
async def _fetch_single_timeframe_improved(self, symbol: str, timeframe: str, limit: int):
"""(V7.2) جلب بيانات إطار زمني واحد - يتوقع 'symbol' كنص"""
max_retries = 3
retry_delay = 2
for attempt in range(max_retries):
try:
ohlcv_data = self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
if ohlcv_data and len(ohlcv_data) > 0:
return ohlcv_data
else:
return []
except Exception as e:
if attempt < max_retries - 1:
await asyncio.sleep(retry_delay * (attempt + 1))
else:
return []
async def get_latest_price_async(self, symbol):
"""(V7.2) جلب السعر الحالي - يتوقع 'symbol' كنص"""
try:
if not self.exchange: return None
if not symbol or '/' not in symbol: return None
ticker = self.exchange.fetch_ticker(symbol)
if not ticker: return None
current_price = ticker.get('last')
if current_price is None: return None
return float(current_price)
except Exception as e: return None
async def get_whale_data_for_symbol(self, symbol):
try:
if self.whale_monitor:
whale_data = await self.whale_monitor.get_symbol_whale_activity(symbol)
return whale_data
else: return None
except Exception as e: return None
async def get_whale_trading_signal(self, symbol, whale_data, market_context):
try:
if self.whale_monitor:
return await self.whale_monitor.generate_whale_trading_signal(symbol, whale_data, market_context)
else:
return {'action': 'HOLD', 'confidence': 0.3, 'reason': 'Whale monitor not available', 'source': 'whale_analysis'}
except Exception as e:
return {'action': 'HOLD', 'confidence': 0.3, 'reason': f'Error: {str(e)}', 'source': 'whale_analysis'}
print("✅ DataManager loaded - V7.4 (1H Momentum Burst Filter)") |