Update ml_engine/indicators.py
Browse files- ml_engine/indicators.py +30 -40
ml_engine/indicators.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# ml_engine/indicators.py
|
| 2 |
import pandas as pd
|
| 3 |
import pandas_ta as ta
|
| 4 |
import numpy as np
|
|
@@ -36,11 +36,9 @@ class AdvancedTechnicalAnalyzer:
|
|
| 36 |
trend = {}
|
| 37 |
|
| 38 |
try:
|
| 39 |
-
# التحقق من وجود البيانات الأساسية
|
| 40 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
|
| 41 |
return {}
|
| 42 |
|
| 43 |
-
# المتوسطات المتحركة
|
| 44 |
if len(dataframe) >= 9:
|
| 45 |
ema_9 = ta.ema(dataframe['close'], length=9)
|
| 46 |
if ema_9 is not None and not ema_9.empty and not pd.isna(ema_9.iloc[-1]):
|
|
@@ -61,12 +59,10 @@ class AdvancedTechnicalAnalyzer:
|
|
| 61 |
if ema_200 is not None and not ema_200.empty and not pd.isna(ema_200.iloc[-1]):
|
| 62 |
trend['ema_200'] = float(ema_200.iloc[-1])
|
| 63 |
|
| 64 |
-
# إيشيموكو
|
| 65 |
if len(dataframe) >= 26:
|
| 66 |
try:
|
| 67 |
ichimoku = ta.ichimoku(dataframe['high'], dataframe['low'], dataframe['close'])
|
| 68 |
if ichimoku is not None and len(ichimoku) > 0:
|
| 69 |
-
# التحقق من أن ichimoku ليس None وأنه يحتوي على بيانات
|
| 70 |
conversion_line = ichimoku[0].get('ITS_9') if ichimoku[0] is not None else None
|
| 71 |
base_line = ichimoku[0].get('IKS_26') if ichimoku[0] is not None else None
|
| 72 |
|
|
@@ -75,9 +71,8 @@ class AdvancedTechnicalAnalyzer:
|
|
| 75 |
if base_line is not None and not base_line.empty and not pd.isna(base_line.iloc[-1]):
|
| 76 |
trend['ichimoku_base'] = float(base_line.iloc[-1])
|
| 77 |
except Exception as ichimoku_error:
|
| 78 |
-
|
| 79 |
|
| 80 |
-
# ADX - قوة الاتجاه
|
| 81 |
if len(dataframe) >= 14:
|
| 82 |
try:
|
| 83 |
adx_result = ta.adx(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
|
|
@@ -86,7 +81,7 @@ class AdvancedTechnicalAnalyzer:
|
|
| 86 |
if adx_value is not None and not adx_value.empty and not pd.isna(adx_value.iloc[-1]):
|
| 87 |
trend['adx'] = float(adx_value.iloc[-1])
|
| 88 |
except Exception as adx_error:
|
| 89 |
-
|
| 90 |
|
| 91 |
except Exception as e:
|
| 92 |
print(f"⚠️ خطأ في حساب مؤشرات الاتجاه: {e}")
|
|
@@ -98,17 +93,14 @@ class AdvancedTechnicalAnalyzer:
|
|
| 98 |
momentum = {}
|
| 99 |
|
| 100 |
try:
|
| 101 |
-
# التحقق من وجود البيانات الأساسية
|
| 102 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
|
| 103 |
return {}
|
| 104 |
|
| 105 |
-
# RSI
|
| 106 |
if len(dataframe) >= 14:
|
| 107 |
rsi = ta.rsi(dataframe['close'], length=14)
|
| 108 |
if rsi is not None and not rsi.empty and not pd.isna(rsi.iloc[-1]):
|
| 109 |
momentum['rsi'] = float(rsi.iloc[-1])
|
| 110 |
|
| 111 |
-
# MACD
|
| 112 |
if len(dataframe) >= 26:
|
| 113 |
macd = ta.macd(dataframe['close'])
|
| 114 |
if macd is not None and not macd.empty:
|
|
@@ -120,7 +112,6 @@ class AdvancedTechnicalAnalyzer:
|
|
| 120 |
if macd_line is not None and not macd_line.empty and not pd.isna(macd_line.iloc[-1]):
|
| 121 |
momentum['macd_line'] = float(macd_line.iloc[-1])
|
| 122 |
|
| 123 |
-
# ستوكاستك RSI
|
| 124 |
if len(dataframe) >= 14:
|
| 125 |
stoch_rsi = ta.stochrsi(dataframe['close'], length=14)
|
| 126 |
if stoch_rsi is not None and not stoch_rsi.empty:
|
|
@@ -128,7 +119,6 @@ class AdvancedTechnicalAnalyzer:
|
|
| 128 |
if stoch_k is not None and not stoch_k.empty and not pd.isna(stoch_k.iloc[-1]):
|
| 129 |
momentum['stoch_rsi_k'] = float(stoch_k.iloc[-1])
|
| 130 |
|
| 131 |
-
# ويليامز %R
|
| 132 |
if len(dataframe) >= 14:
|
| 133 |
williams = ta.willr(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
|
| 134 |
if williams is not None and not williams.empty and not pd.isna(williams.iloc[-1]):
|
|
@@ -144,11 +134,9 @@ class AdvancedTechnicalAnalyzer:
|
|
| 144 |
volatility = {}
|
| 145 |
|
| 146 |
try:
|
| 147 |
-
# التحقق من وجود البيانات الأساسية
|
| 148 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
|
| 149 |
return {}
|
| 150 |
|
| 151 |
-
# بولينجر باندز
|
| 152 |
if len(dataframe) >= 20:
|
| 153 |
bollinger_bands = ta.bbands(dataframe['close'], length=20, std=2)
|
| 154 |
if bollinger_bands is not None and not bollinger_bands.empty:
|
|
@@ -163,7 +151,6 @@ class AdvancedTechnicalAnalyzer:
|
|
| 163 |
if bb_middle is not None and not bb_middle.empty and not pd.isna(bb_middle.iloc[-1]):
|
| 164 |
volatility['bb_middle'] = float(bb_middle.iloc[-1])
|
| 165 |
|
| 166 |
-
# متوسط المدى الحقيقي (ATR)
|
| 167 |
if len(dataframe) >= 14:
|
| 168 |
average_true_range = ta.atr(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
|
| 169 |
if average_true_range is not None and not average_true_range.empty and not pd.isna(average_true_range.iloc[-1]):
|
|
@@ -176,46 +163,53 @@ class AdvancedTechnicalAnalyzer:
|
|
| 176 |
except Exception as e:
|
| 177 |
print(f"⚠️ خطأ في حساب مؤشرات التقلب: {e}")
|
| 178 |
|
| 179 |
-
return {key: value for key, value in
|
| 180 |
|
|
|
|
| 181 |
def _calculate_volume_indicators(self, dataframe, timeframe):
|
| 182 |
"""حساب مؤشرات الحجم"""
|
| 183 |
volume = {}
|
| 184 |
|
| 185 |
try:
|
| 186 |
-
# التحقق من وجود البيانات الأساسية
|
| 187 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns or 'volume' not in dataframe.columns:
|
| 188 |
return {}
|
| 189 |
|
| 190 |
-
# VWAP - إصلاح المشكلة هنا
|
| 191 |
if len(dataframe) >= 1:
|
| 192 |
try:
|
| 193 |
-
# إنشاء نسخة من البيانات مع DatetimeIndex مرتب
|
| 194 |
df_vwap = dataframe.copy()
|
| 195 |
|
| 196 |
-
#
|
| 197 |
if not isinstance(df_vwap.index, pd.DatetimeIndex):
|
| 198 |
if 'timestamp' in df_vwap.columns:
|
| 199 |
df_vwap['timestamp'] = pd.to_datetime(df_vwap['timestamp'], unit='ms')
|
| 200 |
df_vwap.set_index('timestamp', inplace=True)
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
-
# التأكد من أن الفهرس مرتب
|
| 203 |
df_vwap.sort_index(inplace=True)
|
| 204 |
|
| 205 |
-
#
|
|
|
|
|
|
|
|
|
|
| 206 |
volume_weighted_average_price = ta.vwap(
|
| 207 |
-
high=
|
| 208 |
-
low=
|
| 209 |
-
close=
|
| 210 |
-
volume=
|
| 211 |
)
|
| 212 |
|
|
|
|
| 213 |
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]):
|
| 214 |
volume['vwap'] = float(volume_weighted_average_price.iloc[-1])
|
| 215 |
|
| 216 |
except Exception as vwap_error:
|
| 217 |
-
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
| 219 |
if len(dataframe) >= 20:
|
| 220 |
try:
|
| 221 |
typical_price = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
|
|
@@ -223,26 +217,24 @@ class AdvancedTechnicalAnalyzer:
|
|
| 223 |
if not np.isnan(vwap_simple):
|
| 224 |
volume['vwap'] = float(vwap_simple)
|
| 225 |
except Exception as simple_vwap_error:
|
| 226 |
-
|
| 227 |
|
| 228 |
-
#
|
| 229 |
try:
|
| 230 |
on_balance_volume = ta.obv(dataframe['close'], dataframe['volume'])
|
| 231 |
if on_balance_volume is not None and not on_balance_volume.empty and not pd.isna(on_balance_volume.iloc[-1]):
|
| 232 |
volume['obv'] = float(on_balance_volume.iloc[-1])
|
| 233 |
except Exception as obv_error:
|
| 234 |
-
|
| 235 |
|
| 236 |
-
# MFI
|
| 237 |
if len(dataframe) >= 14:
|
| 238 |
try:
|
| 239 |
money_flow_index = ta.mfi(dataframe['high'], dataframe['low'], dataframe['close'], dataframe['volume'], length=14)
|
| 240 |
if money_flow_index is not None and not money_flow_index.empty and not pd.isna(money_flow_index.iloc[-1]):
|
| 241 |
volume['mfi'] = float(money_flow_index.iloc[-1])
|
| 242 |
except Exception as mfi_error:
|
| 243 |
-
|
| 244 |
|
| 245 |
-
# نسبة الحجم
|
| 246 |
if len(dataframe) >= 20:
|
| 247 |
try:
|
| 248 |
volume_avg_20 = float(dataframe['volume'].tail(20).mean())
|
|
@@ -252,29 +244,27 @@ class AdvancedTechnicalAnalyzer:
|
|
| 252 |
if not np.isnan(volume_ratio):
|
| 253 |
volume['volume_ratio'] = volume_ratio
|
| 254 |
except Exception as volume_error:
|
| 255 |
-
|
| 256 |
|
| 257 |
except Exception as e:
|
| 258 |
print(f"⚠️ خطأ في حساب مؤشرات الحجم: {e}")
|
| 259 |
|
| 260 |
return {key: value for key, value in volume.items() if value is not None and not np.isnan(value)}
|
|
|
|
| 261 |
|
| 262 |
def _calculate_cycle_indicators(self, dataframe):
|
| 263 |
"""حساب مؤشرات الدورة"""
|
| 264 |
cycle = {}
|
| 265 |
|
| 266 |
try:
|
| 267 |
-
# التحقق من وجود البيانات الأساسية
|
| 268 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
|
| 269 |
return {}
|
| 270 |
|
| 271 |
-
# هول موفينج افريج
|
| 272 |
if len(dataframe) >= 9:
|
| 273 |
hull_moving_average = ta.hma(dataframe['close'], length=9)
|
| 274 |
if hull_moving_average is not None and not hull_moving_average.empty and not pd.isna(hull_moving_average.iloc[-1]):
|
| 275 |
cycle['hull_ma'] = float(hull_moving_average.iloc[-1])
|
| 276 |
|
| 277 |
-
# سوبرتريند
|
| 278 |
if len(dataframe) >= 10:
|
| 279 |
supertrend = ta.supertrend(dataframe['high'], dataframe['low'], dataframe['close'], length=10, multiplier=3)
|
| 280 |
if supertrend is not None and not supertrend.empty:
|
|
@@ -287,4 +277,4 @@ class AdvancedTechnicalAnalyzer:
|
|
| 287 |
|
| 288 |
return {key: value for key, value in cycle.items() if value is not None and not np.isnan(value)}
|
| 289 |
|
| 290 |
-
print("✅ ML Module: Technical Indicators loaded")
|
|
|
|
| 1 |
+
# ml_engine/indicators.py (V5.1 - Fixed VWAP DatetimeIndex Error)
|
| 2 |
import pandas as pd
|
| 3 |
import pandas_ta as ta
|
| 4 |
import numpy as np
|
|
|
|
| 36 |
trend = {}
|
| 37 |
|
| 38 |
try:
|
|
|
|
| 39 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
|
| 40 |
return {}
|
| 41 |
|
|
|
|
| 42 |
if len(dataframe) >= 9:
|
| 43 |
ema_9 = ta.ema(dataframe['close'], length=9)
|
| 44 |
if ema_9 is not None and not ema_9.empty and not pd.isna(ema_9.iloc[-1]):
|
|
|
|
| 59 |
if ema_200 is not None and not ema_200.empty and not pd.isna(ema_200.iloc[-1]):
|
| 60 |
trend['ema_200'] = float(ema_200.iloc[-1])
|
| 61 |
|
|
|
|
| 62 |
if len(dataframe) >= 26:
|
| 63 |
try:
|
| 64 |
ichimoku = ta.ichimoku(dataframe['high'], dataframe['low'], dataframe['close'])
|
| 65 |
if ichimoku is not None and len(ichimoku) > 0:
|
|
|
|
| 66 |
conversion_line = ichimoku[0].get('ITS_9') if ichimoku[0] is not None else None
|
| 67 |
base_line = ichimoku[0].get('IKS_26') if ichimoku[0] is not None else None
|
| 68 |
|
|
|
|
| 71 |
if base_line is not None and not base_line.empty and not pd.isna(base_line.iloc[-1]):
|
| 72 |
trend['ichimoku_base'] = float(base_line.iloc[-1])
|
| 73 |
except Exception as ichimoku_error:
|
| 74 |
+
pass # (تقليل التشويش)
|
| 75 |
|
|
|
|
| 76 |
if len(dataframe) >= 14:
|
| 77 |
try:
|
| 78 |
adx_result = ta.adx(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
|
|
|
|
| 81 |
if adx_value is not None and not adx_value.empty and not pd.isna(adx_value.iloc[-1]):
|
| 82 |
trend['adx'] = float(adx_value.iloc[-1])
|
| 83 |
except Exception as adx_error:
|
| 84 |
+
pass # (تقليل التشويش)
|
| 85 |
|
| 86 |
except Exception as e:
|
| 87 |
print(f"⚠️ خطأ في حساب مؤشرات الاتجاه: {e}")
|
|
|
|
| 93 |
momentum = {}
|
| 94 |
|
| 95 |
try:
|
|
|
|
| 96 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
|
| 97 |
return {}
|
| 98 |
|
|
|
|
| 99 |
if len(dataframe) >= 14:
|
| 100 |
rsi = ta.rsi(dataframe['close'], length=14)
|
| 101 |
if rsi is not None and not rsi.empty and not pd.isna(rsi.iloc[-1]):
|
| 102 |
momentum['rsi'] = float(rsi.iloc[-1])
|
| 103 |
|
|
|
|
| 104 |
if len(dataframe) >= 26:
|
| 105 |
macd = ta.macd(dataframe['close'])
|
| 106 |
if macd is not None and not macd.empty:
|
|
|
|
| 112 |
if macd_line is not None and not macd_line.empty and not pd.isna(macd_line.iloc[-1]):
|
| 113 |
momentum['macd_line'] = float(macd_line.iloc[-1])
|
| 114 |
|
|
|
|
| 115 |
if len(dataframe) >= 14:
|
| 116 |
stoch_rsi = ta.stochrsi(dataframe['close'], length=14)
|
| 117 |
if stoch_rsi is not None and not stoch_rsi.empty:
|
|
|
|
| 119 |
if stoch_k is not None and not stoch_k.empty and not pd.isna(stoch_k.iloc[-1]):
|
| 120 |
momentum['stoch_rsi_k'] = float(stoch_k.iloc[-1])
|
| 121 |
|
|
|
|
| 122 |
if len(dataframe) >= 14:
|
| 123 |
williams = ta.willr(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
|
| 124 |
if williams is not None and not williams.empty and not pd.isna(williams.iloc[-1]):
|
|
|
|
| 134 |
volatility = {}
|
| 135 |
|
| 136 |
try:
|
|
|
|
| 137 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
|
| 138 |
return {}
|
| 139 |
|
|
|
|
| 140 |
if len(dataframe) >= 20:
|
| 141 |
bollinger_bands = ta.bbands(dataframe['close'], length=20, std=2)
|
| 142 |
if bollinger_bands is not None and not bollinger_bands.empty:
|
|
|
|
| 151 |
if bb_middle is not None and not bb_middle.empty and not pd.isna(bb_middle.iloc[-1]):
|
| 152 |
volatility['bb_middle'] = float(bb_middle.iloc[-1])
|
| 153 |
|
|
|
|
| 154 |
if len(dataframe) >= 14:
|
| 155 |
average_true_range = ta.atr(dataframe['high'], dataframe['low'], dataframe['close'], length=14)
|
| 156 |
if average_true_range is not None and not average_true_range.empty and not pd.isna(average_true_range.iloc[-1]):
|
|
|
|
| 163 |
except Exception as e:
|
| 164 |
print(f"⚠️ خطأ في حساب مؤشرات التقلب: {e}")
|
| 165 |
|
| 166 |
+
return {key: value for key, value in volume.items() if value is not None and not np.isnan(value)}
|
| 167 |
|
| 168 |
+
# 🔴 --- START OF CHANGE (V5.1 - VWAP FIX) --- 🔴
|
| 169 |
def _calculate_volume_indicators(self, dataframe, timeframe):
|
| 170 |
"""حساب مؤشرات الحجم"""
|
| 171 |
volume = {}
|
| 172 |
|
| 173 |
try:
|
|
|
|
| 174 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns or 'volume' not in dataframe.columns:
|
| 175 |
return {}
|
| 176 |
|
|
|
|
| 177 |
if len(dataframe) >= 1:
|
| 178 |
try:
|
|
|
|
| 179 |
df_vwap = dataframe.copy()
|
| 180 |
|
| 181 |
+
# (التحقق من الفهرس وترتيبه - كما كان)
|
| 182 |
if not isinstance(df_vwap.index, pd.DatetimeIndex):
|
| 183 |
if 'timestamp' in df_vwap.columns:
|
| 184 |
df_vwap['timestamp'] = pd.to_datetime(df_vwap['timestamp'], unit='ms')
|
| 185 |
df_vwap.set_index('timestamp', inplace=True)
|
| 186 |
+
else:
|
| 187 |
+
# (إذا لم يكن هناك فهرس زمني ولا عمود، لا يمكن المتابعة)
|
| 188 |
+
raise ValueError("DataFrame needs 'timestamp' column or DatetimeIndex")
|
| 189 |
|
|
|
|
| 190 |
df_vwap.sort_index(inplace=True)
|
| 191 |
|
| 192 |
+
# (الإصلاح: إعادة تعيين الفهرس لتمرير أعمدة Series بسيطة إلى ta.vwap)
|
| 193 |
+
# (هذا يتجنب مشاكل مكتبة pandas_ta مع الفهرس الزمني)
|
| 194 |
+
df_vwap_reset = df_vwap.reset_index()
|
| 195 |
+
|
| 196 |
volume_weighted_average_price = ta.vwap(
|
| 197 |
+
high=df_vwap_reset['high'],
|
| 198 |
+
low=df_vwap_reset['low'],
|
| 199 |
+
close=df_vwap_reset['close'],
|
| 200 |
+
volume=df_vwap_reset['volume']
|
| 201 |
)
|
| 202 |
|
| 203 |
+
# (استخدام القيمة الأخيرة من السلسلة الناتجة)
|
| 204 |
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]):
|
| 205 |
volume['vwap'] = float(volume_weighted_average_price.iloc[-1])
|
| 206 |
|
| 207 |
except Exception as vwap_error:
|
| 208 |
+
# (تقليل التشويش: طباعة فقط إذا كان الخطأ جوهرياً)
|
| 209 |
+
if "Index" not in str(vwap_error):
|
| 210 |
+
print(f"⚠️ خطأ في حساب VWAP لـ {timeframe}: {vwap_error}")
|
| 211 |
+
|
| 212 |
+
# (الكود الاحتياطي يبقى كما هو)
|
| 213 |
if len(dataframe) >= 20:
|
| 214 |
try:
|
| 215 |
typical_price = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
|
|
|
|
| 217 |
if not np.isnan(vwap_simple):
|
| 218 |
volume['vwap'] = float(vwap_simple)
|
| 219 |
except Exception as simple_vwap_error:
|
| 220 |
+
pass
|
| 221 |
|
| 222 |
+
# (باقي مؤشرات الحجم - لا تغيير)
|
| 223 |
try:
|
| 224 |
on_balance_volume = ta.obv(dataframe['close'], dataframe['volume'])
|
| 225 |
if on_balance_volume is not None and not on_balance_volume.empty and not pd.isna(on_balance_volume.iloc[-1]):
|
| 226 |
volume['obv'] = float(on_balance_volume.iloc[-1])
|
| 227 |
except Exception as obv_error:
|
| 228 |
+
pass
|
| 229 |
|
|
|
|
| 230 |
if len(dataframe) >= 14:
|
| 231 |
try:
|
| 232 |
money_flow_index = ta.mfi(dataframe['high'], dataframe['low'], dataframe['close'], dataframe['volume'], length=14)
|
| 233 |
if money_flow_index is not None and not money_flow_index.empty and not pd.isna(money_flow_index.iloc[-1]):
|
| 234 |
volume['mfi'] = float(money_flow_index.iloc[-1])
|
| 235 |
except Exception as mfi_error:
|
| 236 |
+
pass
|
| 237 |
|
|
|
|
| 238 |
if len(dataframe) >= 20:
|
| 239 |
try:
|
| 240 |
volume_avg_20 = float(dataframe['volume'].tail(20).mean())
|
|
|
|
| 244 |
if not np.isnan(volume_ratio):
|
| 245 |
volume['volume_ratio'] = volume_ratio
|
| 246 |
except Exception as volume_error:
|
| 247 |
+
pass
|
| 248 |
|
| 249 |
except Exception as e:
|
| 250 |
print(f"⚠️ خطأ في حساب مؤشرات الحجم: {e}")
|
| 251 |
|
| 252 |
return {key: value for key, value in volume.items() if value is not None and not np.isnan(value)}
|
| 253 |
+
# 🔴 --- END OF CHANGE (V5.1 - VWAP FIX) --- 🔴
|
| 254 |
|
| 255 |
def _calculate_cycle_indicators(self, dataframe):
|
| 256 |
"""حساب مؤشرات الدورة"""
|
| 257 |
cycle = {}
|
| 258 |
|
| 259 |
try:
|
|
|
|
| 260 |
if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
|
| 261 |
return {}
|
| 262 |
|
|
|
|
| 263 |
if len(dataframe) >= 9:
|
| 264 |
hull_moving_average = ta.hma(dataframe['close'], length=9)
|
| 265 |
if hull_moving_average is not None and not hull_moving_average.empty and not pd.isna(hull_moving_average.iloc[-1]):
|
| 266 |
cycle['hull_ma'] = float(hull_moving_average.iloc[-1])
|
| 267 |
|
|
|
|
| 268 |
if len(dataframe) >= 10:
|
| 269 |
supertrend = ta.supertrend(dataframe['high'], dataframe['low'], dataframe['close'], length=10, multiplier=3)
|
| 270 |
if supertrend is not None and not supertrend.empty:
|
|
|
|
| 277 |
|
| 278 |
return {key: value for key, value in cycle.items() if value is not None and not np.isnan(value)}
|
| 279 |
|
| 280 |
+
print("✅ ML Module: Technical Indicators loaded (V5.1 - VWAP Fix)")
|