Spaces:
Paused
Paused
File size: 7,053 Bytes
6259a29 4829c8c 6259a29 1c1eb92 6259a29 1c1eb92 6259a29 1c1eb92 6259a29 1c1eb92 6259a29 5cc48c7 6259a29 1c1eb92 6259a29 1c1eb92 6259a29 5cc48c7 6259a29 1c1eb92 6259a29 1c1eb92 6259a29 4829c8c 6259a29 5cc48c7 6259a29 5cc48c7 6259a29 4829c8c 6259a29 4829c8c 6259a29 4829c8c 6259a29 1c1eb92 6259a29 5cc48c7 4829c8c 6259a29 1c1eb92 6259a29 1c1eb92 6259a29 1c1eb92 4829c8c 6259a29 4829c8c 6259a29 1c1eb92 6259a29 | 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 | # ml_engine/guard_engine.py
# (V1.4 - GEM-Architect Fix: Full Visibility, Async Wrapper & Column Fix)
import os
import joblib
import numpy as np
import pandas as pd
import pandas_ta as ta
import xgboost as xgb
import logging
import traceback
import asyncio
# إعداد تسجيل بسيط للأخطاء
logger = logging.getLogger("GuardEngine")
class GuardEngine:
def __init__(self, models_dir="ml_models/guard_v2"):
self.models_dir = models_dir
self.exit_model = None
self.exit_features = None
self.initialized = False
# إعدادات الحارس (V2 Settings)
self.EXIT_THRESHOLD = 0.80 # العتبة الذهبية
async def initialize(self):
"""تحميل نموذج الحماية وقائمة ميزاته"""
if self.initialized: return
try:
print(f"🛡️ [GuardEngine] Loading V2 models from {self.models_dir}...")
model_path = os.path.join(self.models_dir, "Guard_Exit_V2.json")
feat_path = os.path.join(self.models_dir, "Guard_Exit_V2_features.pkl")
if os.path.exists(model_path) and os.path.exists(feat_path):
self.exit_model = xgb.Booster()
self.exit_model.load_model(model_path)
self.exit_features = joblib.load(feat_path)
self.initialized = True
print(f"✅ [GuardEngine] Exit Guard V2 Loaded! (Threshold: {self.EXIT_THRESHOLD})")
else:
print(f"❌ [GuardEngine] CRITICAL: Model files missing in {self.models_dir}")
except Exception as e:
print(f"❌ [GuardEngine] Initialization failed: {e}")
traceback.print_exc()
def _engineer_features(self, df_raw):
"""مصنع الميزات الحي (مع إصلاح الأعمدة المفقودة)"""
df = df_raw.copy()
# 1. ميزات تيتان (Titan Features)
df['RSI_14'] = ta.rsi(df['close'], length=14)
df['MFI_14'] = ta.mfi(df['high'], df['low'], df['close'], df['volume'], length=14)
macd = ta.macd(df['close'])
if macd is not None:
df['MACD'] = macd.iloc[:, 0]
df['MACD_Hist'] = macd.iloc[:, 1]
df['ADX_14'] = ta.adx(df['high'], df['low'], df['close'], length=14).iloc[:, 0]
for p in [9, 21, 50, 200]:
ema = ta.ema(df['close'], length=p)
df[f'EMA_{p}'] = ema
df[f'Dist_EMA_{p}'] = (df['close'] / ema) - 1
df['ATR_14'] = ta.atr(df['high'], df['low'], df['close'], length=14)
df['ATR_Pct'] = df['ATR_14'] / df['close']
# --- [ FIX START: Bollinger Bands Robustness ] ---
# حساب البولنجر باند
bb = ta.bbands(df['close'], length=20, std=2)
if bb is not None:
# 1. الحسابات المشتقة
df['BB_Width'] = (bb.iloc[:, 2] - bb.iloc[:, 0]) / bb.iloc[:, 1]
df['BB_Pos'] = (df['close'] - bb.iloc[:, 0]) / (bb.iloc[:, 2] - bb.iloc[:, 0])
# 2. دمج الأعمدة الخام (لأن النموذج يطلبها)
df = pd.concat([df, bb], axis=1)
# 3. التحوط ضد التسميات الغريبة (Double Suffix Fix)
# النموذج يطلب 'BBU_20_2.0_2.0' بدلاً من 'BBU_20_2.0'
# نقوم بنسخ الأعمدة بالأسماء المتوقعة لتجنب الانهيار
if 'BBU_20_2.0' in df.columns:
df['BBU_20_2.0_2.0'] = df['BBU_20_2.0']
df['BBL_20_2.0_2.0'] = df['BBL_20_2.0']
df['BBM_20_2.0_2.0'] = df['BBM_20_2.0']
# --- [ FIX END ] ---
# 2. ميزات الأنماط (Pattern Features)
for i in range(1, 13):
df[f'Close_R_{i}'] = df['close'].shift(i) / df['close']
df[f'Vol_R_{i}'] = df['volume'].shift(i) / (df['volume'] + 1e-9)
df['Body_Size'] = abs(df['close'] - df['open']) / df['open']
df['Upper_Wick'] = (df['high'] - np.maximum(df['close'], df['open'])) / df['open']
df['Lower_Wick'] = (np.minimum(df['close'], df['open']) - df['low']) / df['open']
return df
def check_exit_signal(self, ohlcv_5m):
"""
الفحص الحي (Synchronous)
"""
if not self.initialized or not self.exit_model:
return {'action': 'HOLD', 'confidence': 0.0, 'reason': 'Guard not ready'}
try:
if len(ohlcv_5m) < 200:
return {'action': 'HOLD', 'confidence': 0.0, 'reason': f'Not enough data ({len(ohlcv_5m)})'}
# [Log Stamp] طباعة للتأكد من ان الحارس يعمل
# print(f"🛡️ [Guard Internal] Processing {len(ohlcv_5m)} candles...")
df = pd.DataFrame(ohlcv_5m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df = self._engineer_features(df)
latest_row = df.iloc[-1:].copy()
# التأكد من وجود جميع الميزات المطلوبة
# يتم ملء أي ميزة مفقودة بـ 0 لتجنب الانهيار التام، لكن السجل سيظهر تحذيراً إذا تكرر
try:
dmatrix = xgb.DMatrix(latest_row[self.exit_features])
except KeyError as e:
# محاولة أخيرة: طباعة الأعمدة المفقودة بدقة للمعالجة
missing = list(set(self.exit_features) - set(latest_row.columns))
print(f"⚠️ [Guard Fix Attempt] Missing cols: {missing}. Filling with 0.")
for col in missing:
latest_row[col] = 0.0
dmatrix = xgb.DMatrix(latest_row[self.exit_features])
prob = self.exit_model.predict(dmatrix)[0]
# [Log Stamp] طباعة النتيجة
# print(f"🛡️ [Guard Internal] Result: {prob:.4f} (Threshold: {self.EXIT_THRESHOLD})")
if prob >= self.EXIT_THRESHOLD:
return {
'action': 'EXIT_NOW',
'confidence': float(prob),
'reason': f'Guard V2 Exit Signal (Conf: {prob:.2f} >= {self.EXIT_THRESHOLD})'
}
else:
return {
'action': 'HOLD',
'confidence': float(prob),
'reason': f'Guard V2 Secure (Conf: {prob:.2f})'
}
except Exception as e:
print(f"❌ [Guard Internal Error] {e}")
return {'action': 'ERROR', 'confidence': 0.0, 'reason': str(e)}
async def check_exit_signal_async(self, ohlcv_5m):
"""
Wrapper غير متزامن يحل مشكلة التجمد والـ AttributeError.
"""
return await asyncio.to_thread(self.check_exit_signal, ohlcv_5m) |