Riy777 commited on
Commit
eced413
·
1 Parent(s): 3634c88

Update ml_engine/indicators.py

Browse files
Files changed (1) hide show
  1. ml_engine/indicators.py +121 -223
ml_engine/indicators.py CHANGED
@@ -1,4 +1,4 @@
1
- # ml_engine/indicators.py (V10.1 - SyntaxError FIX & Super-Brain)
2
  import pandas as pd
3
  import pandas_ta as ta
4
  import numpy as np
@@ -8,7 +8,6 @@ try:
8
  from hurst import compute_Hc
9
  HURST_AVAILABLE = True
10
  except ImportError:
11
- # 🔴 --- (V10.1 - تم إصلاح الخطأ النحوي هنا) --- 🔴
12
  print("⚠️ مكتبة 'hurst' غير موجودة. ميزة 'مفتاح النظام' ستكون معطلة.")
13
  print(" -> قم بتثبيتها: pip install hurst")
14
  HURST_AVAILABLE = False
@@ -25,11 +24,10 @@ class AdvancedTechnicalAnalyzer:
25
  'cycle': ['hull_ma', 'supertrend', 'zigzag', 'fisher_transform']
26
  }
27
 
28
- # 🔴 --- (V10.0 - العقل الفائق) --- 🔴
29
  def calculate_v9_smart_features(self, dataframe: pd.DataFrame) -> Dict[str, float]:
30
  """
31
- (محدث V10.0) - (العقل الحسابي لنموذج V9.8)
32
- حساب جميع الميزات المتقدمة (بما في ذلك Hurst, CMF, PPO, VROC)
33
  """
34
  if dataframe.empty or dataframe is None or len(dataframe) < 100:
35
  return {}
@@ -62,10 +60,11 @@ class AdvancedTechnicalAnalyzer:
62
  # --- 3. ميزات "الميل" (Slope) ---
63
  ema_14 = ta.ema(close, length=14).iloc[-1]
64
  if ema_14 and ema_50: features['slope_14_50'] = (ema_14 - ema_50) / 14
65
- if adx_data is not None:
66
  adx_series = adx_data['ADX_14']
67
- adx_ema_5 = ta.ema(adx_series, length=5).iloc[-1]; adx_ema_15 = ta.ema(adx_series, length=15).iloc[-1]
68
- if adx_ema_5 and adx_ema_15: features['adx_slope'] = (adx_ema_5 - adx_ema_15) / 5
 
69
 
70
  # --- 4. ميزات "الحجم" (Volume) و "السيولة" ---
71
  vol_ma_50 = volume.tail(50).mean(); vol_std_50 = volume.tail(50).std()
@@ -73,66 +72,69 @@ class AdvancedTechnicalAnalyzer:
73
  vwap = ta.vwap(high, low, close, volume).iloc[-1]
74
  if vwap and vwap > 0: features['vwap_gap'] = (current_price - vwap) / vwap
75
  cmf = ta.cmf(high, low, close, volume, length=20)
76
- if cmf is not None: features['cmf_20'] = cmf.iloc[-1]
77
  vroc = ta.roc(volume, length=12)
78
- if vroc is not None: features['vroc_12'] = vroc.iloc[-1]
79
- if obv_series is not None:
80
  obv_ema_10 = ta.ema(obv_series, length=10).iloc[-1]; obv_ema_30 = ta.ema(obv_series, length=30).iloc[-1]
81
  if obv_ema_10 and obv_ema_30: features['obv_slope'] = (obv_ema_10 - obv_ema_30) / 10
82
 
83
  # --- 5. ميزات "تجميعية" (Aggregative) ---
84
- if rsi_series is not None:
85
  features['rsi_14'] = rsi_series.iloc[-1]; features['rsi_mean_10'] = rsi_series.tail(10).mean(); features['rsi_std_10'] = rsi_series.tail(10).std()
86
- if mfi_series is not None:
87
  features['mfi_14'] = mfi_series.iloc[-1]; features['mfi_mean_10'] = mfi_series.tail(10).mean()
88
- if adx_data is not None: features['adx_14'] = adx_data['ADX_14'].iloc[-1]
 
 
89
 
90
- # --- 6. ميزات "التقلب" (Volatility) ---
91
- if atr_series is not None:
 
92
  atr_val = atr_series.iloc[-1]
93
  if atr_val and current_price > 0: features['atr_percent'] = (atr_val / current_price) * 100
94
  vol_of_vol_series = ta.atr(atr_series, length=10) # (Vol-of-Vol)
95
- if vol_of_vol_series is not None: features['vol_of_vol'] = vol_of_vol_series.iloc[-1]
 
96
  last_return = close.pct_change().iloc[-1]
97
  if atr_val and atr_val > 0:
98
  features['atr_normalized_return'] = last_return / atr_val
99
-
100
- # --- 7. (جديد V9.8) ميزات النظام (Regime Features) ---
 
 
101
  if HURST_AVAILABLE:
102
- hurst_series = close.tail(100).to_numpy()
103
- H, c, data = compute_Hc(hurst_series, kind='price', simplified=True)
104
- features['hurst'] = H
 
 
 
105
  else:
106
  features['hurst'] = 0.5 # (محايد إذا لم يتم تثبيت المكتبة)
107
 
108
  ppo_data = ta.ppo(close, fast=12, slow=26, signal=9)
109
- if ppo_data is not None:
110
  features['ppo_hist'] = ppo_data['PPOh_12_26_9'].iloc[-1]
111
  features['ppo_line'] = ppo_data['PPO_12_26_9'].iloc[-1]
112
 
113
  except Exception as e:
114
  # print(f"⚠️ خطأ في حساب ميزات V9.8 الذكية: {e}");
115
- pass # (نتجاهل الأخطاء في التداول الحي ونعيد ميزات جزئية)
116
 
117
  final_features = {};
118
  for key, value in features.items():
119
  if value is not None and np.isfinite(value): final_features[key] = float(value)
120
  else: final_features[key] = 0.0
121
  return final_features
122
- # 🔴 --- END OF UPDATED FUNCTION (V10.0) --- 🔴
123
-
124
 
125
  # -----------------------------------------------------------------
126
  # --- (الدوال القديمة تبقى كما هي للاستخدامات الأخرى مثل Sentry 1m) ---
127
  # -----------------------------------------------------------------
128
 
129
  def calculate_all_indicators(self, dataframe, timeframe):
130
- """حساب جميع المؤشرات الفنية للإطار الزمني المحدد"""
131
- if dataframe.empty or dataframe is None:
132
- return {}
133
-
134
  indicators = {}
135
-
136
  try:
137
  indicators.update(self._calculate_trend_indicators(dataframe))
138
  indicators.update(self._calculate_momentum_indicators(dataframe))
@@ -141,250 +143,146 @@ class AdvancedTechnicalAnalyzer:
141
  indicators.update(self._calculate_cycle_indicators(dataframe))
142
  except Exception as e:
143
  print(f"⚠️ خطأ في حساب المؤشرات لـ {timeframe}: {e}")
144
-
145
  return indicators
146
 
147
  def _calculate_trend_indicators(self, dataframe):
148
- """حساب مؤشرات الاتجاه"""
149
- trend = {}
150
-
151
  try:
152
- if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
153
- return {}
154
-
155
  if len(dataframe) >= 9:
156
- ema_9 = ta.ema(dataframe['close'], length=9)
157
- if ema_9 is not None and not ema_9.empty and not pd.isna(ema_9.iloc[-1]):
158
- trend['ema_9'] = float(ema_9.iloc[-1])
159
-
160
  if len(dataframe) >= 21:
161
- ema_21 = ta.ema(dataframe['close'], length=21)
162
- if ema_21 is not None and not ema_21.empty and not pd.isna(ema_21.iloc[-1]):
163
- trend['ema_21'] = float(ema_21.iloc[-1])
164
-
165
  if len(dataframe) >= 50:
166
- ema_50 = ta.ema(dataframe['close'], length=50)
167
- if ema_50 is not None and not ema_50.empty and not pd.isna(ema_50.iloc[-1]):
168
- trend['ema_50'] = float(ema_50.iloc[-1])
169
-
170
  if len(dataframe) >= 200:
171
- ema_200 = ta.ema(dataframe['close'], length=200)
172
- if ema_200 is not None and not ema_200.empty and not pd.isna(ema_200.iloc[-1]):
173
- trend['ema_200'] = float(ema_200.iloc[-1])
174
-
175
  if len(dataframe) >= 26:
176
  try:
177
- ichimoku = ta.ichimoku(dataframe['high'], dataframe['low'], dataframe['close'])
178
  if ichimoku is not None and len(ichimoku) > 0:
179
- conversion_line = ichimoku[0].get('ITS_9') if ichimoku[0] is not None else None
180
- base_line = ichimoku[0].get('IKS_26') if ichimoku[0] is not None else None
181
-
182
- if conversion_line is not None and not conversion_line.empty and not pd.isna(conversion_line.iloc[-1]):
183
- trend['ichimoku_conversion'] = float(conversion_line.iloc[-1])
184
- if base_line is not None and not base_line.empty and not pd.isna(base_line.iloc[-1]):
185
- trend['ichimoku_base'] = float(base_line.iloc[-1])
186
- except Exception as ichimoku_error:
187
- pass
188
-
189
  if len(dataframe) >= 14:
190
  try:
191
- adx_result = ta.adx(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
192
  if adx_result is not None and not adx_result.empty:
193
- adx_value = adx_result.get('ADX_14')
194
- if adx_value is not None and not adx_value.empty and not pd.isna(adx_value.iloc[-1]):
195
- trend['adx'] = float(adx_value.iloc[-1])
196
- except Exception as adx_error:
197
- pass
198
-
199
- except Exception as e:
200
- # print(f"⚠️ خطأ في حساب مؤشرات الاتجاه: {e}")
201
- pass
202
-
203
- return {key: value for key, value in trend.items() if value is not None and not np.isnan(value)}
204
 
205
  def _calculate_momentum_indicators(self, dataframe):
206
- """حساب مؤشرات الزخم"""
207
- momentum = {}
208
-
209
  try:
210
- if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
211
- return {}
212
-
213
  if len(dataframe) >= 14:
214
- rsi = ta.rsi(dataframe['close'], length=14)
215
- if rsi is not None and not rsi.empty and not pd.isna(rsi.iloc[-1]):
216
- momentum['rsi'] = float(rsi.iloc[-1])
217
-
218
  if len(dataframe) >= 26:
219
- macd = ta.macd(dataframe['close'])
220
  if macd is not None and not macd.empty:
221
- macd_hist = macd.get('MACDh_12_26_9')
222
- macd_line = macd.get('MACD_12_26_9')
223
-
224
- if macd_hist is not None and not macd_hist.empty and not pd.isna(macd_hist.iloc[-1]):
225
- momentum['macd_hist'] = float(macd_hist.iloc[-1])
226
- if macd_line is not None and not macd_line.empty and not pd.isna(macd_line.iloc[-1]):
227
- momentum['macd_line'] = float(macd_line.iloc[-1])
228
-
229
  if len(dataframe) >= 14:
230
- stoch_rsi = ta.stochrsi(dataframe['close'], length=14)
231
  if stoch_rsi is not None and not stoch_rsi.empty:
232
- stoch_k = stoch_rsi.get('STOCHRSIk_14_14_3_3')
233
- if stoch_k is not None and not stoch_k.empty and not pd.isna(stoch_k.iloc[-1]):
234
- momentum['stoch_rsi_k'] = float(stoch_k.iloc[-1])
235
-
236
  if len(dataframe) >= 14:
237
- williams = ta.willr(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
238
- if williams is not None and not williams.empty and not pd.isna(williams.iloc[-1]):
239
- momentum['williams_r'] = float(williams.iloc[-1])
240
-
241
- except Exception as e:
242
- # print(f"⚠️ خطأ في حساب مؤشرات الزخم: {e}")
243
- pass
244
-
245
- return {key: value for key, value in momentum.items() if value is not None and not np.isnan(value)}
246
 
247
  def _calculate_volatility_indicators(self, dataframe):
248
- """حساب مؤشرات التقلب"""
249
- volatility = {}
250
-
251
  try:
252
- if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
253
- return {}
254
-
255
  if len(dataframe) >= 20:
256
- bollinger_bands = ta.bbands(dataframe['close'], length=20, std=2)
257
  if bollinger_bands is not None and not bollinger_bands.empty:
258
- bb_lower = bollinger_bands.get('BBL_20_2.0')
259
- bb_upper = bollinger_bands.get('BBU_20_2.0')
260
- bb_middle = bollinger_bands.get('BBM_20_2.0')
261
-
262
- if bb_lower is not None and not bb_lower.empty and not pd.isna(bb_lower.iloc[-1]):
263
- volatility['bb_lower'] = float(bb_lower.iloc[-1])
264
- if bb_upper is not None and not bb_upper.empty and not pd.isna(bb_upper.iloc[-1]):
265
- volatility['bb_upper'] = float(bb_upper.iloc[-1])
266
- if bb_middle is not None and not bb_middle.empty and not pd.isna(bb_middle.iloc[-1]):
267
- volatility['bb_middle'] = float(bb_middle.iloc[-1])
268
-
269
  if len(dataframe) >= 14:
270
- average_true_range = ta.atr(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
271
  if average_true_range is not None and not average_true_range.empty and not pd.isna(average_true_range.iloc[-1]):
272
- atr_value = float(average_true_range.iloc[-1])
273
- volatility['atr'] = atr_value
274
- current_close = dataframe['close'].iloc[-1] if not dataframe['close'].empty else 0
275
- if atr_value and current_close > 0:
276
- volatility['atr_percent'] = (atr_value / current_price) * 100
277
-
278
- except Exception as e:
279
- # print(f"⚠️ خطأ في حساب مؤشرات التقلب: {e}")
280
- pass
281
-
282
- return {key: value for key, value in volatility.items() if value is not None and not np.isnan(value)}
283
 
284
  def _calculate_volume_indicators(self, dataframe, timeframe):
285
- """حساب مؤشرات الحجم"""
286
- volume = {}
287
-
288
  try:
289
- if dataframe is None or dataframe.empty or 'close' not in dataframe.columns or 'volume' not in dataframe.columns:
290
- return {}
291
-
292
  if len(dataframe) >= 1:
293
  try:
294
- df_vwap = dataframe.copy()
295
-
296
  if not isinstance(df_vwap.index, pd.DatetimeIndex):
297
  if 'timestamp' in df_vwap.columns:
298
- df_vwap['timestamp'] = pd.to_datetime(df_vwap['timestamp'], unit='ms')
299
- df_vwap.set_index('timestamp', inplace=True)
300
  elif not df_vwap.index.is_numeric():
301
- # (محاولة تحويل الفهرس إذا كان هو التايم ستامب)
302
- df_vwap.index = pd.to_datetime(df_vwap.index, unit='ms')
303
  else:
304
- raise ValueError("DataFrame needs 'timestamp' column or DatetimeIndex")
305
-
306
- df_vwap.sort_index(inplace=True)
307
-
308
- volume_weighted_average_price = ta.vwap(
309
- high=df_vwap['high'],
310
- low=df_vwap['low'],
311
- close=df_vwap['close'],
312
- volume=df_vwap['volume']
313
- )
314
-
315
- if volume_weighted_average_price is not None and not volume_weighted_average_price.empty and not pd.isna(volume_weighted_average_price.iloc[-1]):
316
- volume['vwap'] = float(volume_weighted_average_price.iloc[-1])
317
-
318
  except Exception as vwap_error:
319
- if "VWAP requires an ordered DatetimeIndex" not in str(vwap_error) and "Index" not in str(vwap_error):
320
- # print(f"⚠️ خطأ في حساب VWAP لـ {timeframe}: {vwap_error}")
321
- pass
322
-
323
  if len(dataframe) >= 20:
324
  try:
325
- typical_price = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
326
- vwap_simple = (typical_price * dataframe['volume']).sum() / dataframe['volume'].sum()
327
- if not np.isnan(vwap_simple):
328
- volume['vwap'] = float(vwap_simple)
329
- except Exception as simple_vwap_error:
330
- pass
331
-
332
  try:
333
- on_balance_volume = ta.obv(dataframe['close'], dataframe['volume'])
334
- if on_balance_volume is not None and not on_balance_volume.empty and not pd.isna(on_balance_volume.iloc[-1]):
335
- volume['obv'] = float(on_balance_volume.iloc[-1])
336
- except Exception as obv_error:
337
- pass
338
-
339
  if len(dataframe) >= 14:
340
  try:
341
- money_flow_index = ta.mfi(dataframe['high'], dataframe['low'], dataframe['close'], dataframe['volume'], length=14)
342
- if money_flow_index is not None and not money_flow_index.empty and not pd.isna(money_flow_index.iloc[-1]):
343
- volume['mfi'] = float(money_flow_index.iloc[-1])
344
- except Exception as mfi_error:
345
- pass
346
-
347
  if len(dataframe) >= 20:
348
  try:
349
- volume_avg_20 = float(dataframe['volume'].tail(20).mean())
350
- current_volume = float(dataframe['volume'].iloc[-1]) if not dataframe['volume'].empty else 0
351
  if volume_avg_20 and volume_avg_20 > 0 and current_volume > 0:
352
- volume_ratio = current_volume / volume_avg_20
353
- if not np.isnan(volume_ratio):
354
- volume['volume_ratio'] = volume_ratio
355
- except Exception as volume_error:
356
- pass
357
-
358
- except Exception as e:
359
- # print(f"⚠️ خطأ في حساب مؤشرات الحجم: {e}")
360
- pass
361
-
362
- return {key: value for key, value in volume.items() if value is not None and not np.isnan(value)}
363
 
364
  def _calculate_cycle_indicators(self, dataframe):
365
- """حساب مؤشرات الدورة"""
366
- cycle = {}
367
-
368
  try:
369
- if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
370
- return {}
371
-
372
  if len(dataframe) >= 9:
373
- hull_moving_average = ta.hma(dataframe['close'], length=9)
374
- if hull_moving_average is not None and not hull_moving_average.empty and not pd.isna(hull_moving_average.iloc[-1]):
375
- cycle['hull_ma'] = float(hull_moving_average.iloc[-1])
376
-
377
  if len(dataframe) >= 10:
378
- supertrend = ta.supertrend(dataframe['high'], dataframe['low'], dataframe['close'], length=10, multiplier=3)
379
  if supertrend is not None and not supertrend.empty:
380
- supertrend_value = supertrend.get('SUPERT_10_3.0')
381
- if supertrend_value is not None and not supertrend_value.empty and not pd.isna(supertrend_value.iloc[-1]):
382
- cycle['supertrend'] = float(supertrend_value.iloc[-1])
383
-
384
- except Exception as e:
385
- # print(f"⚠️ خطأ في حساب مؤشرات الدورة: {e}")
386
- pass
387
-
388
- return {key: value for key, value in cycle.items() if value is not None and not np.isnan(value)}
389
 
390
- print("✅ ML Module: Technical Indicators loaded (V10.1 - SyntaxError FIX)")
 
1
+ # ml_engine/indicators.py (V10.2 - Bug FIX)
2
  import pandas as pd
3
  import pandas_ta as ta
4
  import numpy as np
 
8
  from hurst import compute_Hc
9
  HURST_AVAILABLE = True
10
  except ImportError:
 
11
  print("⚠️ مكتبة 'hurst' غير موجودة. ميزة 'مفتاح النظام' ستكون معطلة.")
12
  print(" -> قم بتثبيتها: pip install hurst")
13
  HURST_AVAILABLE = False
 
24
  'cycle': ['hull_ma', 'supertrend', 'zigzag', 'fisher_transform']
25
  }
26
 
27
+ # 🔴 --- (V10.2 - تم إصلاح الأخطاء هنا) --- 🔴
28
  def calculate_v9_smart_features(self, dataframe: pd.DataFrame) -> Dict[str, float]:
29
  """
30
+ (محدث V10.2) - (إصلاح أخطاء 'hurst' و 'atr_normalized_return')
 
31
  """
32
  if dataframe.empty or dataframe is None or len(dataframe) < 100:
33
  return {}
 
60
  # --- 3. ميزات "الميل" (Slope) ---
61
  ema_14 = ta.ema(close, length=14).iloc[-1]
62
  if ema_14 and ema_50: features['slope_14_50'] = (ema_14 - ema_50) / 14
63
+ if adx_data is not None and not adx_data.empty:
64
  adx_series = adx_data['ADX_14']
65
+ if adx_series is not None and not adx_series.empty:
66
+ adx_ema_5 = ta.ema(adx_series, length=5).iloc[-1]; adx_ema_15 = ta.ema(adx_series, length=15).iloc[-1]
67
+ if adx_ema_5 and adx_ema_15: features['adx_slope'] = (adx_ema_5 - adx_ema_15) / 5
68
 
69
  # --- 4. ميزات "الحجم" (Volume) و "السيولة" ---
70
  vol_ma_50 = volume.tail(50).mean(); vol_std_50 = volume.tail(50).std()
 
72
  vwap = ta.vwap(high, low, close, volume).iloc[-1]
73
  if vwap and vwap > 0: features['vwap_gap'] = (current_price - vwap) / vwap
74
  cmf = ta.cmf(high, low, close, volume, length=20)
75
+ if cmf is not None and not cmf.empty: features['cmf_20'] = cmf.iloc[-1]
76
  vroc = ta.roc(volume, length=12)
77
+ if vroc is not None and not vroc.empty: features['vroc_12'] = vroc.iloc[-1]
78
+ if obv_series is not None and not obv_series.empty:
79
  obv_ema_10 = ta.ema(obv_series, length=10).iloc[-1]; obv_ema_30 = ta.ema(obv_series, length=30).iloc[-1]
80
  if obv_ema_10 and obv_ema_30: features['obv_slope'] = (obv_ema_10 - obv_ema_30) / 10
81
 
82
  # --- 5. ميزات "تجميعية" (Aggregative) ---
83
+ if rsi_series is not None and not rsi_series.empty:
84
  features['rsi_14'] = rsi_series.iloc[-1]; features['rsi_mean_10'] = rsi_series.tail(10).mean(); features['rsi_std_10'] = rsi_series.tail(10).std()
85
+ if mfi_series is not None and not mfi_series.empty:
86
  features['mfi_14'] = mfi_series.iloc[-1]; features['mfi_mean_10'] = mfi_series.tail(10).mean()
87
+ if adx_data is not None and not adx_data.empty:
88
+ adx_val = adx_data['ADX_14'].iloc[-1]
89
+ if adx_val is not None: features['adx_14'] = adx_val
90
 
91
+ # 🔴 --- (V10.2 - إصلاح 'atr_val') --- 🔴
92
+ atr_val = None # (تعريف المتغير أولاً)
93
+ if atr_series is not None and not atr_series.empty:
94
  atr_val = atr_series.iloc[-1]
95
  if atr_val and current_price > 0: features['atr_percent'] = (atr_val / current_price) * 100
96
  vol_of_vol_series = ta.atr(atr_series, length=10) # (Vol-of-Vol)
97
+ if vol_of_vol_series is not None and not vol_of_vol_series.empty: features['vol_of_vol'] = vol_of_vol_series.iloc[-1]
98
+
99
  last_return = close.pct_change().iloc[-1]
100
  if atr_val and atr_val > 0:
101
  features['atr_normalized_return'] = last_return / atr_val
102
+ else:
103
+ features['atr_normalized_return'] = 0.0 # (قيمة افتراضية إذا فشل ATR)
104
+
105
+ # 🔴 --- (V10.2 - إصلاح 'hurst') --- 🔴
106
  if HURST_AVAILABLE:
107
+ try:
108
+ hurst_series = close.tail(100).to_numpy()
109
+ H, c, data = compute_Hc(hurst_series, kind='price', simplified=True)
110
+ features['hurst'] = H
111
+ except Exception:
112
+ features['hurst'] = 0.5 # (محايد إذا فشلت المكتبة)
113
  else:
114
  features['hurst'] = 0.5 # (محايد إذا لم يتم تثبيت المكتبة)
115
 
116
  ppo_data = ta.ppo(close, fast=12, slow=26, signal=9)
117
+ if ppo_data is not None and not ppo_data.empty:
118
  features['ppo_hist'] = ppo_data['PPOh_12_26_9'].iloc[-1]
119
  features['ppo_line'] = ppo_data['PPO_12_26_9'].iloc[-1]
120
 
121
  except Exception as e:
122
  # print(f"⚠️ خطأ في حساب ميزات V9.8 الذكية: {e}");
123
+ pass
124
 
125
  final_features = {};
126
  for key, value in features.items():
127
  if value is not None and np.isfinite(value): final_features[key] = float(value)
128
  else: final_features[key] = 0.0
129
  return final_features
 
 
130
 
131
  # -----------------------------------------------------------------
132
  # --- (الدوال القديمة تبقى كما هي للاستخدامات الأخرى مثل Sentry 1m) ---
133
  # -----------------------------------------------------------------
134
 
135
  def calculate_all_indicators(self, dataframe, timeframe):
136
+ if dataframe.empty or dataframe is None: return {}
 
 
 
137
  indicators = {}
 
138
  try:
139
  indicators.update(self._calculate_trend_indicators(dataframe))
140
  indicators.update(self._calculate_momentum_indicators(dataframe))
 
143
  indicators.update(self._calculate_cycle_indicators(dataframe))
144
  except Exception as e:
145
  print(f"⚠️ خطأ في حساب المؤشرات لـ {timeframe}: {e}")
 
146
  return indicators
147
 
148
  def _calculate_trend_indicators(self, dataframe):
149
+ trend = {};
 
 
150
  try:
151
+ if dataframe is None or dataframe.empty or 'close' not in dataframe.columns: return {};
 
 
152
  if len(dataframe) >= 9:
153
+ ema_9 = ta.ema(dataframe['close'], length=9);
154
+ if ema_9 is not None and not ema_9.empty and not pd.isna(ema_9.iloc[-1]): trend['ema_9'] = float(ema_9.iloc[-1]);
 
 
155
  if len(dataframe) >= 21:
156
+ ema_21 = ta.ema(dataframe['close'], length=21);
157
+ if ema_21 is not None and not ema_21.empty and not pd.isna(ema_21.iloc[-1]): trend['ema_21'] = float(ema_21.iloc[-1]);
 
 
158
  if len(dataframe) >= 50:
159
+ ema_50 = ta.ema(dataframe['close'], length=50);
160
+ if ema_50 is not None and not ema_50.empty and not pd.isna(ema_50.iloc[-1]): trend['ema_50'] = float(ema_50.iloc[-1]);
 
 
161
  if len(dataframe) >= 200:
162
+ ema_200 = ta.ema(dataframe['close'], length=200);
163
+ if ema_200 is not None and not ema_200.empty and not pd.isna(ema_200.iloc[-1]): trend['ema_200'] = float(ema_200.iloc[-1]);
 
 
164
  if len(dataframe) >= 26:
165
  try:
166
+ ichimoku = ta.ichimoku(dataframe['high'], dataframe['low'], dataframe['close']);
167
  if ichimoku is not None and len(ichimoku) > 0:
168
+ conversion_line = ichimoku[0].get('ITS_9') if ichimoku[0] is not None else None;
169
+ base_line = ichimoku[0].get('IKS_26') if ichimoku[0] is not None else None;
170
+ if conversion_line is not None and not conversion_line.empty and not pd.isna(conversion_line.iloc[-1]): trend['ichimoku_conversion'] = float(conversion_line.iloc[-1]);
171
+ if base_line is not None and not base_line.empty and not pd.isna(base_line.iloc[-1]): trend['ichimoku_base'] = float(base_line.iloc[-1]);
172
+ except Exception as ichimoku_error: pass;
 
 
 
 
 
173
  if len(dataframe) >= 14:
174
  try:
175
+ adx_result = ta.adx(dataframe['high'], dataframe['low'], dataframe['close'], length=14);
176
  if adx_result is not None and not adx_result.empty:
177
+ adx_value = adx_result.get('ADX_14');
178
+ if adx_value is not None and not adx_value.empty and not pd.isna(adx_value.iloc[-1]): trend['adx'] = float(adx_value.iloc[-1]);
179
+ except Exception as adx_error: pass;
180
+ except Exception as e: pass;
181
+ return {key: value for key, value in trend.items() if value is not None and not np.isnan(value)};
 
 
 
 
 
 
182
 
183
  def _calculate_momentum_indicators(self, dataframe):
184
+ momentum = {};
 
 
185
  try:
186
+ if dataframe is None or dataframe.empty or 'close' not in dataframe.columns: return {};
 
 
187
  if len(dataframe) >= 14:
188
+ rsi = ta.rsi(dataframe['close'], length=14);
189
+ if rsi is not None and not rsi.empty and not pd.isna(rsi.iloc[-1]): momentum['rsi'] = float(rsi.iloc[-1]);
 
 
190
  if len(dataframe) >= 26:
191
+ macd = ta.macd(dataframe['close']);
192
  if macd is not None and not macd.empty:
193
+ macd_hist = macd.get('MACDh_12_26_9');
194
+ macd_line = macd.get('MACD_12_26_9');
195
+ if macd_hist is not None and not macd_hist.empty and not pd.isna(macd_hist.iloc[-1]): momentum['macd_hist'] = float(macd_hist.iloc[-1]);
196
+ if macd_line is not None and not macd_line.empty and not pd.isna(macd_line.iloc[-1]): momentum['macd_line'] = float(macd_line.iloc[-1]);
 
 
 
 
197
  if len(dataframe) >= 14:
198
+ stoch_rsi = ta.stochrsi(dataframe['close'], length=14);
199
  if stoch_rsi is not None and not stoch_rsi.empty:
200
+ stoch_k = stoch_rsi.get('STOCHRSIk_14_14_3_3');
201
+ if stoch_k is not None and not stoch_k.empty and not pd.isna(stoch_k.iloc[-1]): momentum['stoch_rsi_k'] = float(stoch_k.iloc[-1]);
 
 
202
  if len(dataframe) >= 14:
203
+ williams = ta.willr(dataframe['high'], dataframe['low'], dataframe['close'], length=14);
204
+ if williams is not None and not williams.empty and not pd.isna(williams.iloc[-1]): momentum['williams_r'] = float(williams.iloc[-1]);
205
+ except Exception as e: pass;
206
+ return {key: value for key, value in momentum.items() if value is not None and not np.isnan(value)};
 
 
 
 
 
207
 
208
  def _calculate_volatility_indicators(self, dataframe):
209
+ volatility = {};
 
 
210
  try:
211
+ if dataframe is None or dataframe.empty or 'close' not in dataframe.columns: return {};
 
 
212
  if len(dataframe) >= 20:
213
+ bollinger_bands = ta.bbands(dataframe['close'], length=20, std=2);
214
  if bollinger_bands is not None and not bollinger_bands.empty:
215
+ bb_lower = bollinger_bands.get('BBL_20_2.0'); bb_upper = bollinger_bands.get('BBU_20_2.0'); bb_middle = bollinger_bands.get('BBM_20_2.0');
216
+ if bb_lower is not None and not bb_lower.empty and not pd.isna(bb_lower.iloc[-1]): volatility['bb_lower'] = float(bb_lower.iloc[-1]);
217
+ if bb_upper is not None and not bb_upper.empty and not pd.isna(bb_upper.iloc[-1]): volatility['bb_upper'] = float(bb_upper.iloc[-1]);
218
+ if bb_middle is not None and not bb_middle.empty and not pd.isna(bb_middle.iloc[-1]): volatility['bb_middle'] = float(bb_middle.iloc[-1]);
 
 
 
 
 
 
 
219
  if len(dataframe) >= 14:
220
+ average_true_range = ta.atr(dataframe['high'], dataframe['low'], dataframe['close'], length=14);
221
  if average_true_range is not None and not average_true_range.empty and not pd.isna(average_true_range.iloc[-1]):
222
+ atr_value = float(average_true_range.iloc[-1]); volatility['atr'] = atr_value;
223
+ current_close = dataframe['close'].iloc[-1] if not dataframe['close'].empty else 0;
224
+ if atr_value and current_close > 0: volatility['atr_percent'] = (atr_value / current_close) * 100;
225
+ except Exception as e: pass;
226
+ return {key: value for key, value in volatility.items() if value is not None and not np.isnan(value)};
 
 
 
 
 
 
227
 
228
  def _calculate_volume_indicators(self, dataframe, timeframe):
229
+ volume = {};
 
 
230
  try:
231
+ if dataframe is None or dataframe.empty or 'close' not in dataframe.columns or 'volume' not in dataframe.columns: return {};
 
 
232
  if len(dataframe) >= 1:
233
  try:
234
+ df_vwap = dataframe.copy();
 
235
  if not isinstance(df_vwap.index, pd.DatetimeIndex):
236
  if 'timestamp' in df_vwap.columns:
237
+ df_vwap['timestamp'] = pd.to_datetime(df_vwap['timestamp'], unit='ms'); df_vwap.set_index('timestamp', inplace=True);
 
238
  elif not df_vwap.index.is_numeric():
239
+ df_vwap.index = pd.to_datetime(df_vwap.index, unit='ms');
 
240
  else:
241
+ raise ValueError("DataFrame needs 'timestamp' column or DatetimeIndex");
242
+ df_vwap.sort_index(inplace=True);
243
+ volume_weighted_average_price = ta.vwap(high=df_vwap['high'], low=df_vwap['low'], close=df_vwap['close'], volume=df_vwap['volume']);
244
+ if volume_weighted_average_price is not None and not volume_weighted_average_price.empty and not pd.isna(volume_weighted_average_price.iloc[-1]): volume['vwap'] = float(volume_weighted_average_price.iloc[-1]);
 
 
 
 
 
 
 
 
 
 
245
  except Exception as vwap_error:
246
+ if "VWAP requires an ordered DatetimeIndex" not in str(vwap_error) and "Index" not in str(vwap_error): pass;
 
 
 
247
  if len(dataframe) >= 20:
248
  try:
249
+ typical_price = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3;
250
+ vwap_simple = (typical_price * dataframe['volume']).sum() / dataframe['volume'].sum();
251
+ if not np.isnan(vwap_simple): volume['vwap'] = float(vwap_simple);
252
+ except Exception as simple_vwap_error: pass;
 
 
 
253
  try:
254
+ on_balance_volume = ta.obv(dataframe['close'], dataframe['volume']);
255
+ if on_balance_volume is not None and not on_balance_volume.empty and not pd.isna(on_balance_volume.iloc[-1]): volume['obv'] = float(on_balance_volume.iloc[-1]);
256
+ except Exception as obv_error: pass;
 
 
 
257
  if len(dataframe) >= 14:
258
  try:
259
+ money_flow_index = ta.mfi(dataframe['high'], dataframe['low'], dataframe['close'], dataframe['volume'], length=14);
260
+ if money_flow_index is not None and not money_flow_index.empty and not pd.isna(money_flow_index.iloc[-1]): volume['mfi'] = float(money_flow_index.iloc[-1]);
261
+ except Exception as mfi_error: pass;
 
 
 
262
  if len(dataframe) >= 20:
263
  try:
264
+ volume_avg_20 = float(dataframe['volume'].tail(20).mean());
265
+ current_volume = float(dataframe['volume'].iloc[-1]) if not dataframe['volume'].empty else 0;
266
  if volume_avg_20 and volume_avg_20 > 0 and current_volume > 0:
267
+ volume_ratio = current_volume / volume_avg_20;
268
+ if not np.isnan(volume_ratio): volume['volume_ratio'] = volume_ratio;
269
+ except Exception as volume_error: pass;
270
+ except Exception as e: pass;
271
+ return {key: value for key, value in volume.items() if value is not None and not np.isnan(value)};
 
 
 
 
 
 
272
 
273
  def _calculate_cycle_indicators(self, dataframe):
274
+ cycle = {};
 
 
275
  try:
276
+ if dataframe is None or dataframe.empty or 'close' not in dataframe.columns: return {};
 
 
277
  if len(dataframe) >= 9:
278
+ hull_moving_average = ta.hma(dataframe['close'], length=9);
279
+ if hull_moving_average is not None and not hull_moving_average.empty and not pd.isna(hull_moving_average.iloc[-1]): cycle['hull_ma'] = float(hull_moving_average.iloc[-1]);
 
 
280
  if len(dataframe) >= 10:
281
+ supertrend = ta.supertrend(dataframe['high'], dataframe['low'], dataframe['close'], length=10, multiplier=3);
282
  if supertrend is not None and not supertrend.empty:
283
+ supertrend_value = supertrend.get('SUPERT_10_3.0');
284
+ if supertrend_value is not None and not supertrend_value.empty and not pd.isna(supertrend_value.iloc[-1]): cycle['supertrend'] = float(supertrend_value.iloc[-1]);
285
+ except Exception as e: pass;
286
+ return {key: value for key, value in cycle.items() if value is not None and not np.isnan(value)};
 
 
 
 
 
287
 
288
+ print("✅ ML Module: Technical Indicators loaded (V10.2 - Bug FIX)")