File size: 17,161 Bytes
20905a4 701d42d 7ed0e45 701d42d 7ed0e45 701d42d 7ed0e45 701d42d 7ed0e45 701d42d 3e520df 07eb3a2 92c2517 1cd9326 7ed0e45 1cd9326 701d42d 7ed0e45 701d42d 1cd9326 701d42d 7ed0e45 5da2abc 701d42d 5da2abc f597794 701d42d 7ed0e45 3e520df 1cd9326 701d42d 7ed0e45 1cd9326 20905a4 7ed0e45 701d42d 1cd9326 701d42d 20905a4 701d42d 7ed0e45 92c2517 701d42d 7ed0e45 1cd9326 701d42d 7ed0e45 701d42d 1cd9326 701d42d 7ed0e45 701d42d 1cd9326 20905a4 701d42d 20905a4 701d42d 5da2abc 20905a4 3f74f78 032193e 5da2abc 7ed0e45 608f038 5da2abc f597794 032193e 92c2517 3f74f78 701d42d 3f74f78 1cd9326 3f74f78 20905a4 3f74f78 92c2517 1cd9326 701d42d 1cd9326 701d42d 032193e 3f74f78 032193e 701d42d 7ed0e45 1cd9326 608f038 1cd9326 701d42d 032193e 701d42d 1cd9326 701d42d f597794 032193e 701d42d 1cd9326 f597794 032193e 701d42d 7ed0e45 701d42d 1cd9326 701d42d 7ed0e45 1cd9326 7ed0e45 1cd9326 701d42d 7ed0e45 1cd9326 7ed0e45 5da2abc 7ed0e45 1cd9326 92c2517 7ed0e45 20905a4 701d42d 07eb3a2 7ed0e45 07eb3a2 f597794 07eb3a2 f597794 07eb3a2 7ed0e45 f597794 07eb3a2 701d42d 7ed0e45 1cd9326 701d42d 7ed0e45 701d42d 7ed0e45 1cd9326 20905a4 |
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 |
# ml_engine/processor.py (Updated to V6.6 - Statistical News Score)
import pandas as pd
import numpy as np
from datetime import datetime
import asyncio
import json
import re
# (Import components from the same folder)
from .indicators import AdvancedTechnicalAnalyzer
from .monte_carlo import MonteCarloAnalyzer
from .patterns import ChartPatternAnalyzer
from .strategies import MultiStrategyEngine
class MLProcessor:
def __init__(self, market_context, data_manager, learning_hub): # (Changed from learning_engine)
self.market_context = market_context
self.data_manager = data_manager
self.learning_hub = learning_hub # (Changed from learning_engine)
self.technical_analyzer = AdvancedTechnicalAnalyzer()
# (Pass the hub to the strategy engine)
self.strategy_engine = MultiStrategyEngine(data_manager, learning_hub)
self.monte_carlo_analyzer = MonteCarloAnalyzer()
# 🔴 --- START OF CHANGE (V6.4) --- 🔴
# (استخدام محرك V8 الرئيسي المحمل مسبقاً من DataManager)
# (هذا يحل مشكلة "النموذج/المقياس غير محمل")
if self.data_manager and self.data_manager.pattern_analyzer:
self.pattern_analyzer = self.data_manager.pattern_analyzer
print("✅ [MLProcessor V6.4] تم ربط محرك الأنماط V8 (الرئيسي).")
else:
print("⚠️ [MLProcessor V6.4] DataManager أو محرك V8 غير متاح. العودة إلى الوضع الآمن.")
self.pattern_analyzer = ChartPatternAnalyzer(r2_service=None)
# 🔴 --- END OF CHANGE --- 🔴
self.whale_data_semaphore = asyncio.Semaphore(2)
async def process_and_score_symbol_enhanced(self, raw_data, preloaded_whale_data: dict = None):
"""
(Unchanged logic, but now self.strategy_engine uses the learning_hub)
"""
try:
if not raw_data or not raw_data.get('ohlcv'):
return None
symbol = raw_data['symbol']
base_analysis = await self.process_and_score_symbol(raw_data)
if not base_analysis:
return None
try:
# Calculate Indicators
advanced_indicators = {}
ohlcv_available = raw_data.get('ohlcv', {})
for timeframe, candles in ohlcv_available.items():
if candles and len(candles) >= 20:
dataframe = self._create_dataframe(candles)
indicators = self.technical_analyzer.calculate_all_indicators(dataframe, timeframe)
advanced_indicators[timeframe] = indicators
base_analysis['advanced_indicators'] = advanced_indicators
# Monte Carlo (Phase 1)
monte_carlo_results = await self.monte_carlo_analyzer.generate_1h_price_distribution(ohlcv_available)
if monte_carlo_results:
base_analysis['monte_carlo_distribution'] = monte_carlo_results
base_analysis['monte_carlo_probability'] = monte_carlo_results.get('probability_of_gain', 0)
base_analysis['monte_carlo_details'] = self.monte_carlo_analyzer.simulation_results
else:
base_analysis['monte_carlo_distribution'] = None
base_analysis['monte_carlo_probability'] = 0
base_analysis['monte_carlo_details'] = self.monte_carlo_analyzer.simulation_results
# Pattern Analysis
# (This call will now use the *correctly loaded* V8 engine)
pattern_analysis = await self.pattern_analyzer.detect_chart_patterns(ohlcv_available)
base_analysis['pattern_analysis'] = pattern_analysis
# Whale Data
if preloaded_whale_data:
base_analysis['whale_data'] = preloaded_whale_data.get(symbol, {'data_available': False, 'reason': 'Not preloaded'})
else:
base_analysis['whale_data'] = {'data_available': False, 'reason': 'Preloading disabled'}
# 🔴 (This call now uses the Learning Hub via strategy_engine)
strategy_scores, base_scores = await self.strategy_engine.evaluate_all_strategies(base_analysis, self.market_context)
base_analysis['strategy_scores'] = strategy_scores
base_analysis['base_strategy_scores'] = base_scores
if base_scores:
best_strategy = max(base_scores.items(), key=lambda x: x[1])
best_strategy_name = best_strategy[0]
best_strategy_score = best_strategy[1]
base_analysis['recommended_strategy'] = best_strategy_name
base_analysis['strategy_confidence'] = best_strategy_score
base_analysis['target_strategy'] = best_strategy_name if best_strategy_score > 0.3 else 'GENERIC'
# 🔴 (V6.6) استدعاء الدالة المحدثة (بدون درجة الأخبار هنا، لأنها تضاف لاحقاً)
enhanced_score = self._calculate_enhanced_final_score(base_analysis)
base_analysis['enhanced_final_score'] = enhanced_score
return base_analysis
except Exception as strategy_error:
print(f"❌ Error in advanced analysis for {symbol}: {strategy_error}")
return base_analysis
except Exception as error:
print(f"❌ Fatal error in enhanced processing for {raw_data.get('symbol', 'unknown')}: {error}")
return None
def _create_dataframe(self, candles):
# (This function remains unchanged)
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"❌ Error creating DataFrame: {e}")
return pd.DataFrame()
# 🔴 --- START OF CHANGE (V6.6 - STATISTICAL NEWS SCORE) --- 🔴
def _calculate_enhanced_final_score(self, analysis):
"""(محدث V6.6) استخدام درجة الأخبار الإحصائية (الربح/الخسارة الفعلي) بدلاً من VADER الخام."""
try:
base_score = analysis.get('final_score', 0)
pattern_confidence = analysis.get('pattern_analysis', {}).get('pattern_confidence', 0)
strategy_confidence = analysis.get('strategy_confidence', 0)
# (جديد V6.6) جلب درجة الأخبار الإحصائية (الربح/الخسارة الفعلي)
# (هذه القيمة ستكون 0.0 لمعظم العمليات، إلا عند إعادة حساب Top 10)
# (القيمة هي PnL%، مثلاً: 1.1% أو -0.5%)
statistical_pnl = analysis.get('statistical_news_pnl', 0.0)
# (تطبيع PnL% إلى درجة من 0 إلى 1)
# نفترض أن أقصى تأثير هو -3% إلى +3%
clamped_pnl = max(min(statistical_pnl, 3.0), -3.0)
# (-3% -> 0.0)
# (0% -> 0.5)
# (+3% -> 1.0)
normalized_news_score = (clamped_pnl + 3.0) / 6.0
# --- 1. حساب درجة مونت كارلو (كما في V6.2) ---
mc_distribution = analysis.get('monte_carlo_distribution')
monte_carlo_score = 0
if mc_distribution:
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
else:
monte_carlo_score = 0
# --- 2. حساب درجة الحيتان (كما في V6.2) ---
whale_confidence = 0
whale_data = analysis.get('whale_data')
if whale_data and whale_data.get('data_available'):
signal = whale_data.get('trading_signal', {})
if signal.get('action') != 'HOLD' and signal.get('confidence', 0) >= 0.5:
whale_confidence = signal.get('confidence', 0)
# --- 3. حساب النتيجة الموزونة الأولية (محدث V6.5 بالأوزان الجديدة) ---
components = []
weights = []
# (الأوزان الجديدة التي تدمج الأخبار بـ 0.10)
if base_score > 0: components.append(base_score); weights.append(0.15)
if monte_carlo_score > 0: components.append(monte_carlo_score); weights.append(0.20)
if pattern_confidence > 0: components.append(pattern_confidence); weights.append(0.20)
if strategy_confidence > 0: components.append(strategy_confidence); weights.append(0.15)
if whale_confidence > 0: components.append(whale_confidence); weights.append(0.20)
# (V6.6) إضافة درجة الأخبار الإحصائية المطبعة (Normalized) بالوزن 0.10
# (ستكون 0.5 إذا كانت الدرجة الافتراضية 0، وهو ما يعني "محايد" بوزن 0.10)
components.append(normalized_news_score); weights.append(0.10)
if not components: return 0
total_weight = sum(weights)
if total_weight == 0: return 0
enhanced_score = sum(comp * weight for comp, weight in zip(components, weights)) / total_weight
# --- 4. ( V6.3) تطبيق 'عامل جزاء الإرهاق' ---
exhaustion_penalty_factor = 1.0
# (جلب البيانات المطلوبة من القاموس)
price_change_24h = analysis.get('price_change_24h', 0)
rsi_1d = analysis.get('advanced_indicators', {}).get('1d', {}).get('rsi', 50)
rsi_4h = analysis.get('advanced_indicators', {}).get('4h', {}).get('rsi', 50)
if price_change_24h > 60 and (rsi_1d > 80 or rsi_4h > 80):
exhaustion_penalty_factor = 0.4 # (عقوبة قاسية - 60%)
elif price_change_24h > 40 and (rsi_1d > 75 or rsi_4h > 75):
exhaustion_penalty_factor = 0.6 # (عقوبة متوسطة - 40%)
if exhaustion_penalty_factor < 1.0:
# (اختياري: للطباعة التشخيصية)
# print(f" ⚠️ [Processor] {analysis.get('symbol')} Exhaustion Penalty Applied! Score {enhanced_score:.2f} * {exhaustion_penalty_factor} (24h: {price_change_24h:+.1f}%, RSI 1D: {rsi_1d:.1f})")
pass
final_penalized_score = enhanced_score * exhaustion_penalty_factor
return min(max(final_penalized_score, 0.0), 1.0)
except Exception as e:
print(f"❌ Error calculating enhanced score: {e}")
return analysis.get('final_score', 0)
# 🔴 --- END OF CHANGE --- 🔴
async def process_and_score_symbol(self, raw_data):
"""(محدث V6.3) إضافة price_change_24h إلى القاموس"""
try:
symbol = raw_data['symbol']
ohlcv_data = raw_data.get('ohlcv')
if not ohlcv_data: return None
current_price = raw_data.get('current_price', 0)
layer1_score = raw_data.get('layer1_score', 0)
reasons = raw_data.get('reasons_for_candidacy', [])
final_score = layer1_score
successful_timeframes = raw_data.get('successful_timeframes', 0)
# 🔴 --- START OF CHANGE (V6.3) --- 🔴
price_change_24h = raw_data.get('price_change_24h', 0)
# 🔴 --- END OF CHANGE --- 🔴
return {
'symbol': symbol, 'current_price': current_price, 'final_score': final_score,
'enhanced_final_score': final_score, 'reasons_for_candidacy': reasons,
'layer1_score': layer1_score, 'ohlcv': ohlcv_data,
'successful_timeframes': successful_timeframes,
'price_change_24h': price_change_24h # (إضافة الحقل هنا)
}
except Exception as error:
print(f"❌ Error in basic symbol processing {raw_data.get('symbol', 'unknown')}: {error}")
return None
def filter_top_candidates(self, candidates, number_of_candidates=10):
# (This function remains unchanged)
valid_candidates = [c for c in candidates if c is not None and isinstance(c, dict)]
if not valid_candidates: print("❌ No valid candidates to filter"); return []
sorted_candidates = sorted(valid_candidates, key=lambda c: c.get('enhanced_final_score', 0), reverse=True)
top_candidates = sorted_candidates[:number_of_candidates]
print(f"🎖️ Top {len(top_candidates)} Candidates:")
for i, c in enumerate(top_candidates):
score = c.get('enhanced_final_score', 0); strategy = c.get('recommended_strategy', 'GENERIC'); mc_dist = c.get('monte_carlo_distribution'); pattern = c.get('pattern_analysis', {}).get('pattern_detected', 'no_pattern'); symbol = c.get('symbol', 'UNKNOWN'); timeframes = c.get('successful_timeframes', 0)
print(f" {i+1}. {symbol}: 📊 {score:.3f} | TFs: {timeframes}/6")
if mc_dist:
mc_pi_90 = mc_dist.get('prediction_interval_90', [0,0]); mc_var = mc_dist.get('risk_metrics', {}).get('VaR_95_value', 0)
print(f" 🎯 MonteCarlo: 90% PI [{mc_pi_90[0]:.4f} - {mc_pi_90[1]:.4f}] | VaR: ${mc_var:.4f}")
print(f" 🎯 Strategy: {strategy} | Pattern: {pattern}")
whale_data = c.get('whale_data')
if whale_data and whale_data.get('data_available'):
signal = whale_data.get('trading_signal', {}); print(f" 🐋 Whale: {signal.get('action', 'HOLD')} (Conf: {signal.get('confidence', 0):.2f})")
# (V6.6) طباعة درجة الأخبار الإحصائية إن وجدت
if 'statistical_news_pnl' in c:
print(f" 📰 News (Learned PnL): {c['statistical_news_pnl']:.2f}%")
return top_candidates
async def process_multiple_symbols_parallel(self, symbols_data_list, preloaded_whale_data: dict, max_concurrent=5):
# (This function remains unchanged)
semaphore = asyncio.Semaphore(max_concurrent)
tasks_results = []
async def process_symbol_with_semaphore(symbol_data):
async with semaphore:
try:
return await self.process_and_score_symbol_enhanced(symbol_data, preloaded_whale_data)
except Exception as e:
return e
try:
batch_tasks = [asyncio.create_task(process_symbol_with_semaphore(sd)) for sd in symbols_data_list]
batch_results = await asyncio.gather(*batch_tasks, return_exceptions=False)
successful_results = []
for result in batch_results:
if isinstance(result, Exception): raise result
if isinstance(result, dict): successful_results.append(result)
return successful_results
except Exception as error:
raise error
def safe_json_parse(json_string):
# (This function remains unchanged)
if not json_string: return None
try:
return json.loads(json_string)
except json.JSONDecodeError:
try:
s = str(json_string).replace("'", '"'); s = re.sub(r'\\"', '"', s); s = re.sub(r'[\n\t]', ' ', s); s = re.sub(r'(?<!")(\b\w+\b)(?=\s*:)', r'"\1"', s); s = re.sub(r':\s*(\btrue\b|\bfalse\b|\bnull\b)(?=[,\s}])', r': \1', s); s = re.sub(r',\s*([}\]])', r'\1', s)
return json.loads(s)
except json.JSONDecodeError: return None
print("✅ ML Processor loaded - V6.6 (Statistical News Score)") |