Tradtesting / ml_engine /deep_steward.py
Riy777's picture
Update ml_engine/deep_steward.py
f90c278 verified
raw
history blame
7.42 kB
# ml_engine/deep_steward.py
# (V1.0 - Deep Steward Production Inference Engine)
import os
import numpy as np
import pandas as pd
import xgboost as xgb
import logging
import traceback
import pandas_ta as ta
class DeepSteward:
def __init__(self, model_path="ml_models/DeepSteward_Production_V1.json"):
self.model_path = model_path
self.model = None
self.initialized = False
# ⚙️ إعدادات الحساسية (قابل للتعديل من الخارج)
# أي إشارة خروج بثقة أقل من هذه سيتم تجاهلها
self.EXIT_THRESHOLD = 0.50
# أسماء الميزات بالترتيب (مهم جداً للمطابقة مع التدريب)
self.FEATURE_NAMES = [
'log_ret', 'rel_vol', 'rsi_norm', 'macd_hist', 'roc',
'bb_width', 'bb_pct', 'atr_pct', 'dist_ema50', 'dist_ema200',
'pnl_channel' # القناة 11
]
def initialize(self):
"""تحميل النموذج للذاكرة"""
try:
if not os.path.exists(self.model_path):
print(f"❌ [DeepSteward] Model file not found: {self.model_path}")
return False
self.model = xgb.Booster()
self.model.load_model(self.model_path)
self.initialized = True
print(f"✅ [DeepSteward] V1 Loaded successfully. (Threshold: {self.EXIT_THRESHOLD})")
return True
except Exception as e:
print(f"❌ [DeepSteward] Init Failed: {e}")
return False
def _engineer_features(self, df: pd.DataFrame, current_price: float, entry_price: float) -> np.ndarray:
"""
تحويل الشموع الخام إلى ميزات Titan (مطابقة 100% لكود التدريب)
"""
try:
# نسخة لتجنب التعديل على الأصل
df = df.copy()
# التأكد من وجود بيانات كافية
if len(df) < 200: return None
# 1. Log Returns
df['log_ret'] = np.log(df['close'] / df['close'].shift(1))
# 2. Relative Volume
vol_sma = df['volume'].rolling(window=50).mean()
df['rel_vol'] = (df['volume'] - vol_sma) / (vol_sma + 1e-9)
# 3. RSI (14) - تطبيع
df['rsi'] = ta.rsi(df['close'], length=14)
df['rsi_norm'] = df['rsi'] / 100.0
# 4. MACD (12, 26, 9)
macd = ta.macd(df['close'], fast=12, slow=26, signal=9)
# pandas_ta returns columns like MACD_12_26_9, MACDh_12_26_9, MACDs_12_26_9
# We need the Histogram (MACDh)
hist_col = [c for c in macd.columns if 'MACDh' in c][0]
df['macd_hist'] = macd[hist_col]
# 5. ROC (9)
df['roc'] = ta.roc(df['close'], length=9) / 100.0 # pandas_ta returns percentage
# 6. Bollinger Bands (20, 2)
bb = ta.bbands(df['close'], length=20, std=2)
# Columns: BBL, BBM, BBU, BBB (Bandwidth), BBP (Percent)
# We need Width and Percent
w_col = [c for c in bb.columns if 'BBB' in c][0]
p_col = [c for c in bb.columns if 'BBP' in c][0]
# تطبيع العرض (pandas_ta يعطيه كنسبة مئوية جاهزة أحياناً، لكن لنتأكد)
df['bb_width'] = bb[w_col] / 100.0 # إذا كان كبيراً، نقسمه
df['bb_pct'] = bb[p_col]
# 7. ATR (14)
df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=14)
df['atr_pct'] = df['atr'] / df['close']
# 8. EMA Distances
df['ema50'] = ta.ema(df['close'], length=50)
df['ema200'] = ta.ema(df['close'], length=200)
df['dist_ema50'] = (df['close'] - df['ema50']) / (df['ema50'] + 1e-9)
df['dist_ema200'] = (df['close'] - df['ema200']) / (df['ema200'] + 1e-9)
# 9. PnL Channel (الحالة الحالية)
# نحسب الربح/الخسارة اللحظية
current_pnl = (current_price - entry_price) / entry_price
df['pnl_channel'] = current_pnl
# تنظيف القيم المفقودة
df = df.fillna(0)
# استخراج آخر صف (الوضع الحالي)
latest_row = df.iloc[-1][self.FEATURE_NAMES]
# تحويل لمصفوفة ثنائية الأبعاد (1, 11)
# بما أننا دربنا XGBoost على بيانات مسطحة (Flattened) من 64 شمعة
# هنا نقطة مهمة: هل النموذج الحالي هو XGBoost الذي تدرب على 64 شمعة مسطحة؟
# أم هو نسخة مبسطة؟
# ⚠️ تصحيح هندسي هام:
# نموذج XGBoost تدرب على مصفوفة مسطحة لـ 64 شمعة (Flattened Sequence)
# أي (64 * 11 = 704 ميزة).
# لذلك يجب أن نأخذ آخر 64 شمعة ونسطحها، وليس شمعة واحدة فقط!
window = df.iloc[-64:][self.FEATURE_NAMES].values # (64, 11)
if window.shape[0] < 64:
return None # بيانات غير كافية
# التسطيح (1, 704)
feature_vector = window.reshape(1, -1)
return feature_vector
except Exception as e:
# print(f"⚠️ [DeepSteward] Feature Eng Error: {e}")
return None
def analyze_position(self, ohlcv_5m: list, entry_price: float) -> dict:
"""
تحليل وضع الصفقة الحالي واتخاذ قرار
"""
if not self.initialized or not self.model:
return {'action': 'HOLD', 'confidence': 0.0, 'reason': 'Not Initialized'}
try:
# تحويل القائمة لـ DataFrame
df = pd.DataFrame(ohlcv_5m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
current_price = float(df['close'].iloc[-1])
# هندسة الميزات
features = self._engineer_features(df, current_price, entry_price)
if features is None:
return {'action': 'HOLD', 'confidence': 0.0, 'reason': 'Insufficient Data'}
# التنبؤ
dmatrix = xgb.DMatrix(features)
prob = self.model.predict(dmatrix)[0] # يعيد قيمة بين 0 و 1
# منطق القرار
# النموذج تدرب على: 1 = EXIT (خطر/هروب)، 0 = HOLD (أمان/صعود)
if prob > self.EXIT_THRESHOLD:
return {
'action': 'EXIT',
'confidence': float(prob),
'reason': f'DeepSteward Danger Signal ({prob:.1%})'
}
else:
return {
'action': 'HOLD',
'confidence': float(1 - prob), # ثقة البقاء
'reason': f'DeepSteward Safe ({prob:.1%})'
}
except Exception as e:
print(f"❌ [DeepSteward] Inference Error: {e}")
return {'action': 'HOLD', 'confidence': 0.0, 'reason': f'Error: {e}'}