Riy777 commited on
Commit
11cbc0d
·
verified ·
1 Parent(s): d11dbf3

Update ml_engine/oracle_engine.py

Browse files
Files changed (1) hide show
  1. ml_engine/oracle_engine.py +197 -238
ml_engine/oracle_engine.py CHANGED
@@ -1,299 +1,258 @@
1
  import os
 
2
  import numpy as np
3
  import pandas as pd
4
  import pandas_ta as ta
5
  import lightgbm as lgb
6
- from scipy.signal import find_peaks
7
  import warnings
8
  from typing import Dict, Any, List, Optional
9
 
10
- # --- [ 0. إعدادات ] ---
11
  warnings.filterwarnings('ignore', category=FutureWarning)
12
 
13
- PIPELINE_SETTINGS = {
14
- 'SWING_PROMINENCE_PCT': 0.02, # 2%
15
- }
16
-
17
- DECISION_CONFIDENCE_THRESHOLD = 0.30
18
- N_STRATEGY_MODELS = 11
19
-
20
- STRATEGY_MAP = {
21
- 0: 'WAIT',
22
- 1: 'SWING_LONG',
23
- 2: 'SCALP_LONG',
24
- 3: 'SWING_SHORT',
25
- 4: 'SCALP_SHORT'
26
- }
27
-
28
- # (الأطر الزمنية التي تم تدريب النماذج عليها)
29
- TIMEBYTES_TO_PROCESS = ['15m', '1h', '4h']
30
-
31
 
32
  class OracleEngine:
33
  def __init__(self, model_dir: str = "ml_models/Unified_Models_V1"):
34
  """
35
- تهيئة "العقل" الاحتمالي (L3 Oracle).
 
36
  """
37
  self.model_dir = model_dir
38
- self.strategy_boosters: List[lgb.Booster] = []
39
- self.quantile_boosters: Dict[str, lgb.Booster] = {}
 
40
 
41
- self.feature_names: List[str] = []
42
  self.initialized = False
43
- # (تحديث الإصدار)
44
- print("🧠 [OracleEngine V2.3] تم الإنشاء (Spot/Long-Only Mode). جاهز للتهيئة.")
45
 
46
  async def initialize(self):
47
- """
48
- تحميل جميع النماذج الـ 15 (11 استراتيجية + 4 أهداف) إلى الذاكرة.
49
- """
50
- if self.initialized:
51
- return True
52
 
53
- print(f"🧠 [OracleEngine V2.3] جاري تحميل 15 نموذجاً من {self.model_dir}...")
54
  try:
55
- # 1. تحميل نماذج "لجنة القرار" (Strategy Ensemble)
56
- for i in range(N_STRATEGY_MODELS):
57
- model_file = os.path.join(self.model_dir, f"lgbm_strategy_fold_{i}.txt")
58
- if not os.path.exists(model_file):
59
- print(f"❌ [Oracle Error] ملف نموذج مفقود: {model_file}")
60
- return False
61
- booster = lgb.Booster(model_file=model_file)
62
- self.strategy_boosters.append(booster)
63
-
64
- print(f" ✅ تم تحميل {len(self.strategy_boosters)} نماذج استراتيجية.")
 
 
 
 
 
 
 
 
65
 
66
- # 2. تحميل نماذج "لجنة الأهداف" (Quantile Models)
67
- quantile_names = ['tp_p20', 'tp_p50', 'tp_p80', 'sl_p80']
68
- for name in quantile_names:
69
- model_file = os.path.join(self.model_dir, f"lgbm_{name}.txt")
70
- if not os.path.exists(model_file):
71
- print(f"❌ [Oracle Error] ملف نموذج مفقود: {model_file}")
72
- return False
73
- booster = lgb.Booster(model_file=model_file)
74
- self.quantile_boosters[name] = booster
75
 
76
- print(f" ✅ تم تحميل {len(self.quantile_boosters)} نماذج أهداف.")
 
77
 
78
- # 3. حفظ قائمة الميزات
79
- self.feature_names = self.strategy_boosters[0].feature_name()
80
  self.initialized = True
81
-
82
- print(f"✅ [OracleEngine V2.3] جاهز. (Threshold: {DECISION_CONFIDENCE_THRESHOLD*100}%)")
83
- print(f" -> سيعمل على الأطر: {TIMEBYTES_TO_PROCESS}")
84
  return True
85
 
86
  except Exception as e:
87
- print(f"❌ [OracleEngine V2.3] فشل فادح أثناء التهيئة: {e}")
88
- self.initialized = False
89
  return False
90
 
91
- # --- [ دوال هندسة الميزات (مطابقة 100% للتدريب) ] ---
 
 
92
 
93
- def _calculate_base_ta(self, df: pd.DataFrame) -> pd.DataFrame:
94
- df.ta.rsi(length=14, append=True)
95
- df.ta.adx(length=14, append=True)
96
- df.ta.macd(fast=12, slow=26, signal=9, append=True)
97
- df.ta.bbands(length=20, std=2, append=True)
98
- df.ta.atr(length=14, append=True)
99
- for length in [9, 21, 50, 100, 200]:
100
- df[f'EMA_{length}'] = ta.ema(df['close'], length=length)
101
- return df
102
-
103
- def _calculate_market_structure(self, df: pd.DataFrame, prominence_pct: float) -> pd.DataFrame:
104
- prominence_value = df['close'].mean() * prominence_pct
105
- high_peaks_idx, _ = find_peaks(df['high'], prominence=prominence_value)
106
- low_peaks_idx, _ = find_peaks(-df['low'], prominence=prominence_value)
107
- df['last_SH_price'] = df.iloc[high_peaks_idx]['high'].reindex(df.index).ffill().bfill()
108
- df['last_SL_price'] = df.iloc[low_peaks_idx]['low'].reindex(df.index).ffill().bfill()
109
- df['BOS_Long'] = np.where(df['close'] > df['last_SH_price'].shift(1), 1, 0)
110
- df['BOS_Short'] = np.where(df['low'] < df['last_SL_price'].shift(1), 1, 0)
111
- return df
112
-
113
- def _calculate_fibonacci_matrix(self, df: pd.DataFrame) -> pd.DataFrame:
114
- wave_range = df['last_SH_price'] - df['last_SL_price']
115
- df['fibo_0.382'] = df['last_SH_price'] - (wave_range * 0.382)
116
- df['fibo_0.500'] = df['last_SH_price'] - (wave_range * 0.500)
117
- df['fibo_0.618'] = df['last_SL_price'] + (wave_range * 0.618)
118
- df['fibo_ext_1.618'] = df['last_SH_price'] + (wave_range * 1.618)
119
- df['dist_to_0.618_pct'] = (df['close'] - df['fibo_0.618']) / (df['close'] + 1e-9)
120
- df['dist_to_1.618_pct'] = (df['fibo_ext_1.618'] - df['close']) / (df['close'] + 1e-9)
121
- df['is_in_golden_pocket'] = np.where(
122
- (df['close'] < df['fibo_0.500']) & (df['close'] > df['fibo_0.618']), 1, 0
123
- )
124
- df.replace([np.inf, -np.inf], np.nan, inplace=True)
125
- return df
126
-
127
- def _calculate_alpha_strategies(self, df: pd.DataFrame) -> pd.DataFrame:
128
- df['volume_zscore'] = (df['volume'] - df['volume'].rolling(50).mean()) / (df['volume'].rolling(50).std() + 1e-9)
129
- df['dist_from_EMA200_pct'] = (df['close'] - df['EMA_200']) / (df['EMA_200'] + 1e-9)
130
 
131
- bbu_col = next((col for col in df.columns if 'BBU_20_2.0' in str(col)), None)
132
- bbl_col = next((col for col in df.columns if 'BBL_20_2.0' in str(col)), None)
133
- bbm_col = next((col for col in df.columns if 'BBM_20_2.0' in str(col)), None)
134
-
135
- if all([bbu_col, bbl_col, bbm_col]):
136
- df['BBW_pct'] = (df[bbu_col] - df[bbl_col]) / (df[bbm_col] + 1e-9)
137
- df['is_squeeze'] = np.where(df['BBW_pct'] < df['BBW_pct'].rolling(100).min(), 1, 0)
138
- else:
139
- df['BBW_pct'] = np.nan
140
- df['is_squeeze'] = 0
141
-
142
- df['is_trending'] = np.where(df['ADX_14'] > 25, 1, 0)
143
- df['ATR_pct'] = (df['ATRr_14'] / df['close']) * 100
144
- return df
145
-
146
- def _create_feature_vector(self, ohlcv_tf_data: List) -> Optional[pd.DataFrame]:
147
- """
148
- تشغيل خط أنابيب الميزات الكامل على بيانات إطار زمني واحد.
149
- """
150
- if ohlcv_tf_data is None or len(ohlcv_tf_data) < 200:
151
- return None
152
 
153
- df = pd.DataFrame(ohlcv_tf_data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
154
- df = df.astype(float)
155
- df['datetime'] = pd.to_datetime(df['timestamp'], unit='ms')
156
- df = df.set_index('datetime')
157
 
158
- # تشغيل خط الأنابيب
159
- df = self._calculate_base_ta(df)
160
- df = self._calculate_market_structure(df, PIPELINE_SETTINGS['SWING_PROMINENCE_PCT'])
161
- df = self._calculate_fibonacci_matrix(df)
162
- df = self._calculate_alpha_strategies(df)
163
-
164
- # ملء أي قيم NaN أولية
165
- df = df.ffill().bfill()
166
-
167
- # أخذ آخر صف فقط
168
- latest_features = df.iloc[-1:]
169
-
170
  try:
171
- feature_vector = latest_features[self.feature_names]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
- # --- [ إصلاح المشكلة: NaN Bug ] ---
174
- feature_vector = feature_vector.fillna(0)
175
- # --- [ نهاية الإصلاح ] ---
176
 
177
- if feature_vector.isnull().values.any():
178
- print("⚠️ [Oracle Warning] Feature vector still contains NaN after fill(0).")
179
- return None
180
- return feature_vector
181
  except Exception as e:
182
- print(f" [Oracle Error] عدم تطابق الميزات: {e}")
183
  return None
184
 
 
 
 
185
 
186
  async def predict(self, symbol_data: Dict[str, Any]) -> Dict[str, Any]:
187
- """
188
- الدالة الرئيسية: تحليل إشارة مرشحة وإرجاع قرار كامل.
189
- """
190
  if not self.initialized:
191
- return {'action': 'WAIT', 'reason': 'Oracle Engine not initialized'}
192
-
193
- ohlcv_data = symbol_data.get('ohlcv')
194
- current_price = symbol_data.get('current_price')
195
- if not ohlcv_data or not current_price:
196
- return {'action': 'WAIT', 'reason': 'Missing OHLCV or price data'}
197
 
198
  try:
199
- all_tf_decisions = []
200
-
201
- # --- [ الخطوة 1: تحليل كل إطار زمني على حدة ] ---
202
- for tf in TIMEBYTES_TO_PROCESS:
203
- feature_vector = self._create_feature_vector(ohlcv_data.get(tf))
204
-
205
- if feature_vector is None:
206
- print(f" -> {symbol_data['symbol']} @ {tf}: Skipped (Insufficient data or NaN)")
207
- continue
208
-
209
- all_probs = [
210
- booster.predict(feature_vector, num_iteration=booster.best_iteration)
211
- for booster in self.strategy_boosters
212
- ]
213
- ensemble_probs = np.mean(all_probs, axis=0)[0]
214
-
215
- # --- [ إصلاح المشكلة: منطق WAIT ] ---
216
- actionable_probs = ensemble_probs.copy()
217
- actionable_probs[0] = 0.0 # (تجاهل WAIT)
218
-
219
- predicted_strategy_idx = np.argmax(actionable_probs)
220
- confidence = actionable_probs[predicted_strategy_idx]
221
- strategy_name = STRATEGY_MAP.get(predicted_strategy_idx, 'WAIT')
222
- # --- [ نهاية الإصلاح ] ---
223
-
224
- all_tf_decisions.append({
225
- 'timeframe': tf,
226
- 'strategy': strategy_name,
227
- 'confidence': float(confidence),
228
- 'feature_vector': feature_vector
229
- })
230
-
231
- if not all_tf_decisions:
232
- return {'action': 'IGNORE', 'reason': 'Feature calculation failed for all TFs'}
233
-
234
- # --- [ الخطوة 2: اختيار القرار الأفضل (أعلى ثقة) ] ---
235
- best_decision = max(all_tf_decisions, key=lambda x: x['confidence'])
236
-
237
- strategy_name = best_decision['strategy']
238
- confidence = best_decision['confidence']
239
- best_tf = best_decision['timeframe']
240
 
241
- # --- [ 🛑 🛑 🛑 التعديل الجديد: فلتر Spot Only ] ---
242
- # إذا كانت الاستراتيجية تحتوي على "SHORT"، يتم تجاهلها فوراً
243
- if "SHORT" in strategy_name:
244
  return {
245
- 'action': 'IGNORE',
246
- 'reason': f"Spot Mode: Ignored {strategy_name} signal (Shorts disabled)",
247
- 'confidence': confidence,
248
- 'strategy': strategy_name
249
  }
250
- # --- [ نهاية التعديل ] ---
251
 
252
- # --- [ الخطوة 3: تطبيق فلتر الثقة ] ---
253
- if confidence < DECISION_CONFIDENCE_THRESHOLD:
254
- return {
255
- 'action': 'IGNORE',
256
- 'reason': f"Best Actionable Signal ({strategy_name} @ {best_tf}) confidence ({confidence:.2f}) is below threshold ({DECISION_CONFIDENCE_THRESHOLD})",
257
- 'confidence': confidence,
258
- 'strategy': strategy_name
259
- }
260
 
261
- # --- [ الخطوة 4: (نجحت الثقة) - تشغيل "لجنة الأهداف" ] ---
262
- winning_feature_vector = best_decision['feature_vector']
263
- preds_quantile = {}
264
- for name, booster in self.quantile_boosters.items():
265
- preds_quantile[name] = booster.predict(winning_feature_vector, num_iteration=booster.best_iteration)[0]
266
-
267
- # --- [ الخطوة 5: تحديد الأهداف النهائية ] ---
268
- tp_pct = preds_quantile['tp_p50'] # (الهدف الواقعي)
269
- sl_pct = preds_quantile['sl_p80'] # (وقف الخسارة الآمن)
 
 
270
 
271
- if tp_pct <= 0 or sl_pct <= 0:
272
- return {'action': 'IGNORE', 'reason': f'Quantile model predicted negative TP/SL ({tp_pct=}, {sl_pct=})'}
273
 
274
- if "LONG" in strategy_name:
275
- tp_price = current_price * (1 + tp_pct)
276
- sl_price = current_price * (1 - sl_pct)
277
- action_type = "BUY"
278
- else:
279
- # (لن نصل إلى هنا لأننا قمنا بتصفية SHORT مسبقاً)
280
- return {'action': 'IGNORE', 'reason': 'Strategy not actionable (Unknown type)'}
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
- # --- [ الخطوة 6: إرجاع القرار الكامل ] ---
283
  return {
284
- 'action': 'WATCH',
285
- 'confidence': confidence,
286
- 'analysis_summary': f"Oracle Consensus @ {best_tf}: {strategy_name} (Conf: {confidence:.2%})",
287
- 'strategy': strategy_name,
288
- 'action_type': action_type,
289
- 'tp_price': float(tp_price),
290
  'sl_price': float(sl_price),
291
- 'quantile_tp_pct': float(tp_pct),
292
- 'quantile_sl_pct': float(sl_pct)
293
  }
294
 
295
  except Exception as e:
296
- print(f"❌ [OracleEngine V2.3] فشل فادح أثناء التنبؤ: {e}")
297
  import traceback
298
  traceback.print_exc()
299
- return {'action': 'WAIT', 'reason': f'Exception: {e}'}
 
1
  import os
2
+ import joblib
3
  import numpy as np
4
  import pandas as pd
5
  import pandas_ta as ta
6
  import lightgbm as lgb
 
7
  import warnings
8
  from typing import Dict, Any, List, Optional
9
 
10
+ # --- [ إعدادات النظام ] ---
11
  warnings.filterwarnings('ignore', category=FutureWarning)
12
 
13
+ # العتبة الذهبية المستخرجة من اختباراتك (عند 0.65 الدقة ~77%)
14
+ CONFIDENCE_THRESHOLD = 0.65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  class OracleEngine:
17
  def __init__(self, model_dir: str = "ml_models/Unified_Models_V1"):
18
  """
19
+ Oracle V3.0: The Strategic Brain
20
+ يدمج نماذج: Direction (Binary), Target (Multiclass), Strength (Regression)
21
  """
22
  self.model_dir = model_dir
23
+ self.model_direction = None
24
+ self.model_target = None
25
+ self.model_strength = None
26
 
27
+ self.feature_cols = []
28
  self.initialized = False
29
+ print("🧠 [Oracle V3] Engine Instance Created.")
 
30
 
31
  async def initialize(self):
32
+ """تحميل النماذج وخريطة الميزات"""
33
+ if self.initialized: return True
 
 
 
34
 
35
+ print(f"🧠 [Oracle V3] Loading artifacts from {self.model_dir}...")
36
  try:
37
+ # 1. تحميل خريطة الميزات (لضمان ترتيب الأعمدة)
38
+ feat_path = os.path.join(self.model_dir, "feature_columns.pkl")
39
+ if not os.path.exists(feat_path):
40
+ print(f"❌ [Oracle] Feature map missing: {feat_path}")
41
+ return False
42
+ self.feature_cols = joblib.load(feat_path)
43
+
44
+ # 2. تحميل النماذج
45
+ # ملاحظة: تأكد من نقل ملفات .txt من Drive إلى مجلد المشروع المحلي
46
+ dir_path = os.path.join(self.model_dir, "lgbm_direction.txt")
47
+ tgt_path = os.path.join(self.model_dir, "lgbm_target_class.txt")
48
+ str_path = os.path.join(self.model_dir, "lgbm_strength.txt")
49
+
50
+ if os.path.exists(dir_path):
51
+ self.model_direction = lgb.Booster(model_file=dir_path)
52
+ else:
53
+ print("❌ [Oracle] Direction Model missing!")
54
+ return False
55
 
56
+ if os.path.exists(tgt_path):
57
+ self.model_target = lgb.Booster(model_file=tgt_path)
 
 
 
 
 
 
 
58
 
59
+ if os.path.exists(str_path):
60
+ self.model_strength = lgb.Booster(model_file=str_path)
61
 
 
 
62
  self.initialized = True
63
+ print(f"✅ [Oracle V3] Ready. Threshold: {CONFIDENCE_THRESHOLD}")
 
 
64
  return True
65
 
66
  except Exception as e:
67
+ print(f"❌ [Oracle] Init Error: {e}")
 
68
  return False
69
 
70
+ # ==========================================================================
71
+ # 🛠️ هندسة الميزات (يجب أن تطابق DataFactory حرفياً)
72
+ # ==========================================================================
73
 
74
+ def _calculate_snapshot_features(self, df, tf_prefix):
75
+ """حساب المؤشرات الفنية المضغوطة"""
76
+ df = df.copy()
77
+ # تحويلات لضمان الدقة
78
+ df['close'] = df['close'].astype(float)
79
+ df['volume'] = df['volume'].astype(float)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
+ # 1. Slope
82
+ df[f'{tf_prefix}_slope'] = ta.slope(df['close'], length=7)
83
+ # 2. RSI
84
+ df[f'{tf_prefix}_rsi'] = ta.rsi(df['close'], length=14)
85
+ # 3. ATR Ratio
86
+ atr = ta.atr(df['high'], df['low'], df['close'], length=14)
87
+ df[f'{tf_prefix}_atr_pct'] = atr / df['close']
88
+ # 4. Volume Z-Score
89
+ vol_mean = df['volume'].rolling(20).mean()
90
+ vol_std = df['volume'].rolling(20).std()
91
+ df[f'{tf_prefix}_vol_z'] = (df['volume'] - vol_mean) / (vol_std + 1e-9)
 
 
 
 
 
 
 
 
 
 
92
 
93
+ # إرجاع الأعمدة فقط (مع ملء الفراغات للحسابات اللحظية)
94
+ cols = [f'{tf_prefix}_slope', f'{tf_prefix}_rsi', f'{tf_prefix}_atr_pct', f'{tf_prefix}_vol_z']
95
+ return df[cols].ffill().bfill()
 
96
 
97
+ def _create_feature_vector(self, ohlcv_data: Dict[str, Any], titan_score: float, mc_score: float, pattern_score: float) -> Optional[pd.DataFrame]:
98
+ """تجميع متجه الميزات ومحاكاة مدخلات الطبقة الثانية"""
 
 
 
 
 
 
 
 
 
 
99
  try:
100
+ # 1. التحقق من البيانات
101
+ raw_1h = ohlcv_data.get('1h')
102
+ if not raw_1h or len(raw_1h) < 30: return None
103
+
104
+ # تحويل البيانات إلى DataFrame
105
+ df_1h = pd.DataFrame(raw_1h, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
106
+ df_15m = pd.DataFrame(ohlcv_data.get('15m', []), columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
107
+ df_4h = pd.DataFrame(ohlcv_data.get('4h', []), columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
108
+
109
+ # 2. حساب الميزات الفنية (Snapshot)
110
+ # نأخذ آخر صف فقط (State الحالية)
111
+ feats_1h = self._calculate_snapshot_features(df_1h, "1h").iloc[-1:].reset_index(drop=True)
112
+
113
+ if len(df_15m) > 20:
114
+ feats_15m = self._calculate_snapshot_features(df_15m, "15m").iloc[-1:].reset_index(drop=True)
115
+ else:
116
+ feats_15m = pd.DataFrame(np.zeros((1, 4)), columns=[f'15m_{c}' for c in ['slope', 'rsi', 'atr_pct', 'vol_z']])
117
+
118
+ if len(df_4h) > 20:
119
+ feats_4h = self._calculate_snapshot_features(df_4h, "4h").iloc[-1:].reset_index(drop=True)
120
+ else:
121
+ feats_4h = pd.DataFrame(np.zeros((1, 4)), columns=[f'4h_{c}' for c in ['slope', 'rsi', 'atr_pct', 'vol_z']])
122
+
123
+ # 3. التجميع (Vector Assembly)
124
+ vector = pd.concat([feats_1h, feats_15m, feats_4h], axis=1)
125
+
126
+ # 4. حقن درجات الطبقة الثانية (Injection)
127
+ # هنا نربط الدرجات الحقيقية بالأعمدة التي تدرب عليها النموذج (sim_...)
128
+ vector['sim_titan_score'] = float(titan_score)
129
+ vector['sim_mc_score'] = float(mc_score)
130
+ vector['sim_pattern_score'] = float(pattern_score)
131
+
132
+ # 5. محاذاة الأعمدة (Column Alignment)
133
+ # يجب أن نرسل للنموذج نفس الأعمدة بنفس الترتيب
134
+ final_vector = pd.DataFrame(columns=self.feature_cols)
135
+ for col in self.feature_cols:
136
+ if col in vector.columns:
137
+ final_vector.at[0, col] = vector[col].iloc[0]
138
+ else:
139
+ final_vector.at[0, col] = 0.0 # Missing features default to 0
140
 
141
+ return final_vector.astype(float)
 
 
142
 
 
 
 
 
143
  except Exception as e:
144
+ print(f"⚠️ [Oracle] Vector build failed: {e}")
145
  return None
146
 
147
+ # ==========================================================================
148
+ # 🔮 التنبؤ (Inference Logic)
149
+ # ==========================================================================
150
 
151
  async def predict(self, symbol_data: Dict[str, Any]) -> Dict[str, Any]:
152
+ """تحليل الفرصة وإصدار التوصية"""
 
 
153
  if not self.initialized:
154
+ return {'action': 'WAIT', 'reason': 'Not initialized'}
 
 
 
 
 
155
 
156
  try:
157
+ # استلام البيانات
158
+ ohlcv = symbol_data.get('ohlcv')
159
+ current_price = symbol_data.get('current_price', 0.0)
160
+
161
+ # الدرجات القادمة من L2
162
+ titan = symbol_data.get('titan_score', 0.5)
163
+ mc = symbol_data.get('mc_score', 0.5)
164
+ patt = symbol_data.get('patterns_score', 0.5)
165
+
166
+ # 1. بناء المتجه
167
+ features = self._create_feature_vector(ohlcv, titan, mc, patt)
168
+ if features is None:
169
+ return {'action': 'WAIT', 'reason': 'Features failed'}
170
+
171
+ # 2. التنبؤ بالاتجاه (Direction)
172
+ # النموذج يعيد احتمالية لكل كلاس. بما أننا دربنا 1=Long, 2=Short
173
+ # LightGBM (Multi-class) يعيد array: [Prob_Wait(محذوف عملياً), Prob_Long, Prob_Short]
174
+ # أو إذا كان Binary (0=Long, 1=Short) يعيد احتمالية Short.
175
+ # *تذكير*: في كود التدريب الأخير استخدمنا: y - 1. إذن: 0=Long, 1=Short.
176
+
177
+ dir_probs = self.model_direction.predict(features)[0] # Array [Prob_Long, Prob_Short]
178
+
179
+ # التعامل مع نوع المخرجات (حسب نسخة LightGBM)
180
+ if isinstance(dir_probs, (np.ndarray, list)):
181
+ prob_long = dir_probs[0]
182
+ prob_short = dir_probs[1]
183
+ else:
184
+ # Binary objective case
185
+ prob_short = dir_probs
186
+ prob_long = 1.0 - dir_probs
187
+
188
+ # تحديد الاتجاه الأقوى
189
+ if prob_long > prob_short:
190
+ direction = "LONG"
191
+ confidence = prob_long
192
+ else:
193
+ direction = "SHORT"
194
+ confidence = prob_short
 
 
 
195
 
196
+ # 3. البوابة المنطقية (Threshold Check)
197
+ if confidence < CONFIDENCE_THRESHOLD:
 
198
  return {
199
+ 'action': 'WAIT',
200
+ 'reason': f'Low Confidence ({confidence:.2f} < {CONFIDENCE_THRESHOLD})',
201
+ 'direction': direction
 
202
  }
 
203
 
204
+ # 4. التنبؤ بالأهداف والقوة (فقط للفرص المؤكدة)
 
 
 
 
 
 
 
205
 
206
+ # Strength (Regression: 0.0 - 1.0)
207
+ strength = 0.5
208
+ if self.model_strength:
209
+ strength = float(self.model_strength.predict(features)[0])
210
+ strength = max(0.0, min(1.0, strength)) # Clip
211
+
212
+ # Target Class (0=TP1, 1=TP2, 2=TP3, 3=TP4)
213
+ tp_class_idx = 1 # Default TP2
214
+ if self.model_target:
215
+ tgt_probs = self.model_target.predict(features)[0]
216
+ tp_class_idx = np.argmax(tgt_probs)
217
 
218
+ tp_labels = ['TP1', 'TP2', 'TP3', 'TP4']
219
+ target_profile = tp_labels[tp_class_idx]
220
 
221
+ # 5. حساب المستويات السعرية (ATR-Based)
222
+ # نحتاج قيمة ATR الحالية
223
+ atr_pct_val = features['1h_atr_pct'].iloc[0]
224
+ atr_abs = atr_pct_val * current_price
225
+
226
+ dir_mult = 1 if direction == "LONG" else -1
227
+
228
+ # خريطة الأهداف
229
+ tp_map = {
230
+ 'TP1': current_price + (dir_mult * 0.8 * atr_abs),
231
+ 'TP2': current_price + (dir_mult * 1.5 * atr_abs),
232
+ 'TP3': current_price + (dir_mult * 2.5 * atr_abs),
233
+ 'TP4': current_price + (dir_mult * 4.0 * atr_abs),
234
+ }
235
+
236
+ # الهدف الأساسي الموصى به
237
+ primary_tp = tp_map[target_profile]
238
+
239
+ # الوقف (Stop Loss) - دائماً 1.2 ATR كبداية
240
+ sl_price = current_price - (dir_mult * 1.2 * atr_abs)
241
 
 
242
  return {
243
+ 'action': 'WATCH', # إشارة صالحة للمتابعة
244
+ 'action_type': 'BUY' if direction == "LONG" else 'SELL',
245
+ 'confidence': float(confidence),
246
+ 'strength': float(strength),
247
+ 'target_class': target_profile,
248
+ 'primary_tp': float(primary_tp),
249
  'sl_price': float(sl_price),
250
+ 'tp_map': tp_map,
251
+ 'analysis_summary': f"{direction} (Conf: {confidence:.0%}) | Strength: {strength:.2f} | Aiming: {target_profile}"
252
  }
253
 
254
  except Exception as e:
255
+ print(f"❌ [Oracle] Prediction Error: {e}")
256
  import traceback
257
  traceback.print_exc()
258
+ return {'action': 'WAIT', 'reason': 'Error'}