Riy777 commited on
Commit
6973033
·
1 Parent(s): 39f71cc

Update ml_engine/titan_engine.py

Browse files
Files changed (1) hide show
  1. ml_engine/titan_engine.py +108 -206
ml_engine/titan_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ml_engine/titan_engine.py
2
- # V1.4 No ambiguous Series truth, fully safe TA, aligned Series
3
 
4
  import os
5
  import joblib
@@ -7,56 +7,7 @@ import numpy as np
7
  import pandas as pd
8
  import pandas_ta as ta
9
  import xgboost as xgb
10
-
11
-
12
- # ===================== Utils =====================
13
-
14
- def _series_like(index, fill=np.nan) -> pd.Series:
15
- return pd.Series(index=index, data=fill, dtype="float64")
16
-
17
- def _as_float_series(obj, index, fill=0.0) -> pd.Series:
18
- """
19
- يحول أي ناتج (Series/DataFrame/ndarray/None) إلى Series float مُحاذاة للفهرس.
20
- لا يستخدم أي تحقق منطقي مبهم.
21
- """
22
- if obj is None:
23
- return _series_like(index, fill)
24
- try:
25
- if isinstance(obj, pd.DataFrame):
26
- # خذ العمود الأول فقط بشكل صريح
27
- s = obj.iloc[:, 0]
28
- elif isinstance(obj, pd.Series):
29
- s = obj
30
- else:
31
- # أي نوع آخر إلى Series
32
- s = pd.Series(obj, index=index)
33
- s = s.astype("float64")
34
- # أعِد المحاذاة أولاً ثم الاستبدال/الملء
35
- s = s.reindex(index)
36
- s = s.replace([np.inf, -np.inf], np.nan).fillna(fill)
37
- return s
38
- except Exception:
39
- return _series_like(index, fill)
40
-
41
- def _safe_df(obj):
42
- """يعيد DataFrame صالح أو None دون أي تقييم منطقي."""
43
- if obj is None:
44
- return None
45
- try:
46
- if isinstance(obj, pd.DataFrame) and obj.shape[0] > 0 and obj.shape[1] > 0:
47
- return obj
48
- return None
49
- except Exception:
50
- return None
51
-
52
- def _safe_divide(num: pd.Series, den: pd.Series, fill=0.0) -> pd.Series:
53
- den = den.copy()
54
- den = den.replace(0, np.nan)
55
- out = num / den
56
- return _as_float_series(out, num.index, fill=fill)
57
-
58
-
59
- # ================= Titan Engine ==================
60
 
61
  class TitanEngine:
62
  def __init__(self, model_dir="ml_models/layer2"):
@@ -67,184 +18,135 @@ class TitanEngine:
67
  self.initialized = False
68
 
69
  async def initialize(self):
70
- print(f"🛡️ [Titan] تهيئة من {self.model_path}", flush=True)
 
71
  try:
72
  if os.path.exists(self.model_path) and os.path.exists(self.features_path):
 
73
  self.model = xgb.Booster()
74
  self.model.load_model(self.model_path)
 
 
75
  self.feature_names = joblib.load(self.features_path)
 
76
  self.initialized = True
77
- print(f"✅ [Titan] جاهز. ميزات: {len(self.feature_names)}", flush=True)
78
  else:
79
- print("❌ [Titan] ملفات النموذج مفقودة!", flush=True)
80
  except Exception as e:
81
- print(f"❌ [Titan] خطأ أثناء التهيئة: {e}", flush=True)
82
-
83
- # --------- Features per timeframe, no boolean checks ---------
84
-
85
- def _feat_5m_15m(self, df: pd.DataFrame) -> pd.DataFrame:
86
- idx = pd.DatetimeIndex(pd.to_datetime(df['timestamp'], unit='ms'))
87
- df = df.set_index(idx)
88
-
89
- c = df['close']; h = df['high']; l = df['low']; v = df['volume']
90
-
91
- rsi = ta.rsi(c, length=14)
92
- df['RSI'] = _as_float_series(rsi, df.index, fill=50)
93
-
94
- macd = _safe_df(ta.macd(c))
95
- if macd is not None and macd.shape[1] >= 2:
96
- # عمود 0: خط MACD, عمود 1: الهستوغرام غالباً
97
- df['MACD'] = _as_float_series(macd.iloc[:, 0], df.index, fill=0)
98
- df['MACD_h'] = _as_float_series(macd.iloc[:, 1], df.index, fill=0)
99
- else:
100
- df['MACD'] = _series_like(df.index, 0)
101
- df['MACD_h'] = _series_like(df.index, 0)
102
-
103
- cci = ta.cci(h, l, c, length=20)
104
- df['CCI'] = _as_float_series(cci, df.index, fill=0)
105
-
106
- adx = _safe_df(ta.adx(h, l, c))
107
- if adx is not None:
108
- df['ADX'] = _as_float_series(adx.iloc[:, 0], df.index, fill=0)
109
- else:
110
- df['ADX'] = _series_like(df.index, 0)
111
-
112
- for p in [9, 21, 50, 200]:
113
- ema = _as_float_series(ta.ema(c, length=p), df.index, fill=np.nan)
114
- df[f'EMA_{p}_dist'] = _as_float_series(_safe_divide(c, ema, fill=0) - 1.0, df.index, fill=0)
115
-
116
- bb = _safe_df(ta.bbands(c, length=20, std=2.0))
117
- if bb is not None and bb.shape[1] >= 3:
118
- lower = _as_float_series(bb.iloc[:, 0], df.index, fill=np.nan)
119
- mid = _as_float_series(bb.iloc[:, 1], df.index, fill=np.nan)
120
- upper = _as_float_series(bb.iloc[:, 2], df.index, fill=np.nan)
121
- width = _safe_divide(upper - lower, mid, fill=0)
122
- pos = _safe_divide(c - lower, (upper - lower), fill=0)
123
- df['BB_w'] = _as_float_series(width, df.index, fill=0)
124
- df['BB_p'] = _as_float_series(pos, df.index, fill=0)
125
- else:
126
- df['BB_w'] = _series_like(df.index, 0)
127
- df['BB_p'] = _series_like(df.index, 0)
128
-
129
- vwap = _as_float_series(ta.vwap(h, l, c, v), df.index, fill=np.nan)
130
- df['VWAP_dist'] = _as_float_series(_safe_divide(c, vwap, fill=0) - 1.0, df.index, fill=0)
131
-
132
- return df.reset_index(drop=True)
133
-
134
- def _feat_1h_4h(self, df: pd.DataFrame) -> pd.DataFrame:
135
- idx = pd.DatetimeIndex(pd.to_datetime(df['timestamp'], unit='ms'))
136
- df = df.set_index(idx)
137
-
138
- c = df['close']; h = df['high']; l = df['low']
139
-
140
- rsi = ta.rsi(c, length=14)
141
- df['RSI'] = _as_float_series(rsi, df.index, fill=50)
142
-
143
- macd = _safe_df(ta.macd(c))
144
- if macd is not None and macd.shape[1] >= 2:
145
- df['MACD_h'] = _as_float_series(macd.iloc[:, 1], df.index, fill=0)
146
- else:
147
- df['MACD_h'] = _series_like(df.index, 0)
148
-
149
- ema50 = _as_float_series(ta.ema(c, length=50), df.index, fill=np.nan)
150
- ema200 = _as_float_series(ta.ema(c, length=200), df.index, fill=np.nan)
151
- df['EMA_50_dist'] = _as_float_series(_safe_divide(c, ema50, fill=0) - 1.0, df.index, fill=0)
152
- df['EMA_200_dist'] = _as_float_series(_safe_divide(c, ema200, fill=0) - 1.0, df.index, fill=0)
153
-
154
- atr = _as_float_series(ta.atr(h, l, c, length=14), df.index, fill=0)
155
- df['ATR_pct'] = _as_float_series(_safe_divide(atr, c, fill=0), df.index, fill=0)
156
-
157
- return df.reset_index(drop=True)
158
-
159
- def _feat_1d(self, df: pd.DataFrame) -> pd.DataFrame:
160
- idx = pd.DatetimeIndex(pd.to_datetime(df['timestamp'], unit='ms'))
161
- df = df.set_index(idx)
162
-
163
- c = df['close']; h = df['high']; l = df['low']
164
-
165
- rsi = ta.rsi(c, length=14)
166
- df['RSI'] = _as_float_series(rsi, df.index, fill=50)
167
-
168
- ema200 = _as_float_series(ta.ema(c, length=200), df.index, fill=np.nan)
169
- df['EMA_200_dist'] = _as_float_series(_safe_divide(c, ema200, fill=0) - 1.0, df.index, fill=0)
170
-
171
- adx = _safe_df(ta.adx(h, l, c))
172
- if adx is not None:
173
- adx_main = _as_float_series(adx.iloc[:, 0], df.index, fill=0)
174
- df['Trend_Strong'] = (adx_main > 25).astype(int)
175
- else:
176
- df['Trend_Strong'] = _series_like(df.index, 0)
177
 
178
  return df.reset_index(drop=True)
179
 
180
- def apply_inverted_pyramid(self, df_in: pd.DataFrame, tf: str) -> pd.DataFrame:
181
- # لا try/except شامل. أي خطأ يُطبع ويُرجع DataFrame كما هو لضمان عدم توقف الباقي.
182
- try:
183
- df_in = df_in.copy().sort_values('timestamp').reset_index(drop=True)
184
- if tf in ('5m', '15m'):
185
- return self._feat_5m_15m(df_in)
186
- if tf in ('1h', '4h'):
187
- return self._feat_1h_4h(df_in)
188
- if tf == '1d':
189
- return self._feat_1d(df_in)
190
- # افتراضي: لا شيء
191
- return df_in
192
- except Exception as e:
193
- print(f"⚠️ [Titan] مؤشر فشل على tf={tf}: {e}", flush=True)
194
- return df_in
195
-
196
- # ---------------- Prediction -------------------
197
-
198
  def predict(self, ohlcv_data: dict) -> dict:
 
 
 
 
199
  if not self.initialized or not self.model:
200
- print("⚠️ [Titan] لم يُهيَّأ. score=0", flush=True)
201
  return {'score': 0.0, 'error': 'Titan not initialized'}
 
202
  try:
 
203
  processed_tfs = {}
204
  for tf, data in ohlcv_data.items():
205
- if data is None:
206
- continue
207
  if isinstance(data, list):
208
- df = pd.DataFrame(data, columns=['timestamp','open','high','low','close','volume'])
209
  else:
210
  df = data.copy()
211
- processed_tfs[tf] = self.apply_inverted_pyramid(df, tf)
 
 
 
212
 
213
- if '5m' not in processed_tfs or processed_tfs['5m'].empty:
214
- print("⚠️ [Titan] Missing 5m base timeframe", flush=True)
215
  return {'score': 0.0, 'error': 'Missing 5m base timeframe'}
216
 
217
- base5 = processed_tfs['5m'].iloc[-1:].copy()
218
- latest_ts = int(base5['timestamp'].iloc[0])
219
- row = base5.add_prefix('5m_').rename(columns={'5m_timestamp': 'timestamp'})
220
-
221
- # دمج آخر صف متاح من كل إطار ≤ latest_ts
222
- for tf, tdf in processed_tfs.items():
223
- if tf == '5m' or tdf is None or tdf.empty:
224
- continue
225
- rel = tdf[tdf['timestamp'] <= latest_ts]
226
- if rel.empty:
227
- continue
228
- last = rel.iloc[-1:]
229
- cols = [c for c in last.columns if c not in ['timestamp','open','high','low','close','volume']]
230
- for c in cols:
231
- row[f"{tf}_{c}"] = last[c].values[0]
232
-
233
- # بناء متجه الميزات بنفس الترتيب
234
- feats = []
235
- for name in self.feature_names:
236
- val = row.get(name, np.nan)
237
- if isinstance(val, pd.Series):
238
- val = val.iloc[0] if len(val) else np.nan
239
- if pd.isna(val):
240
- val = 0.0
241
- feats.append(float(val))
242
-
243
- dtest = xgb.DMatrix([feats], feature_names=self.feature_names)
244
- pred = float(self.model.predict(dtest)[0])
245
- print(f"[DEBUG][Titan] pred={pred:.3f}", flush=True)
246
- return {'score': pred, 'timestamp': latest_ts, 'status': 'OK'}
 
 
 
 
 
 
 
 
247
 
248
  except Exception as e:
249
- import traceback; traceback.print_exc()
 
 
250
  return {'score': 0.0, 'error': str(e)}
 
1
  # ml_engine/titan_engine.py
2
+ # (V1.0 - Titan Inference Engine)
3
 
4
  import os
5
  import joblib
 
7
  import pandas as pd
8
  import pandas_ta as ta
9
  import xgboost as xgb
10
+ import json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  class TitanEngine:
13
  def __init__(self, model_dir="ml_models/layer2"):
 
18
  self.initialized = False
19
 
20
  async def initialize(self):
21
+ """تحميل النموذج وقائمة الميزات من القرص"""
22
+ print(f"🛡️ [Titan] جاري تهيئة المحرك من {self.model_path}...")
23
  try:
24
  if os.path.exists(self.model_path) and os.path.exists(self.features_path):
25
+ # تحميل نموذج XGBoost
26
  self.model = xgb.Booster()
27
  self.model.load_model(self.model_path)
28
+
29
+ # تحميل قائمة الميزات لضمان الترتيب الصحيح
30
  self.feature_names = joblib.load(self.features_path)
31
+
32
  self.initialized = True
33
+ print(f"✅ [Titan] تم التحميل بنجاح. جاهز بـ {len(self.feature_names)} ميزة.")
34
  else:
35
+ print(f"❌ [Titan] ملفات النموذج مفقودة!")
36
  except Exception as e:
37
+ print(f"❌ [Titan] خطأ فادح أثناء التهيئة: {e}")
38
+
39
+ def apply_inverted_pyramid(self, df, tf):
40
+ """نفس منطق هندسة الميزات المستخدم في التدريب تماماً"""
41
+ df = df.copy().sort_values('timestamp').reset_index(drop=True)
42
+ # تعيين الفهرس للسهولة في pandas_ta
43
+ df = df.set_index(pd.DatetimeIndex(pd.to_datetime(df['timestamp'], unit='ms')))
44
+
45
+ # --- المستوى 1: دقيق (5m, 15m) ---
46
+ if tf in ['5m', '15m']:
47
+ df['RSI'] = ta.rsi(df['close'], length=14)
48
+ df['MACD'] = ta.macd(df['close']).iloc[:, 0]
49
+ df['MACD_h'] = ta.macd(df['close']).iloc[:, 1]
50
+ df['CCI'] = ta.cci(df['high'], df['low'], df['close'], length=20)
51
+ df['ADX'] = ta.adx(df['high'], df['low'], df['close'], length=14).iloc[:, 0]
52
+ for p in [9, 21, 50, 200]:
53
+ ema = ta.ema(df['close'], length=p)
54
+ df[f'EMA_{p}_dist'] = (df['close'] / ema) - 1
55
+ bb = ta.bbands(df['close'], length=20, std=2.0)
56
+ df['BB_w'] = (bb.iloc[:, 2] - bb.iloc[:, 0]) / bb.iloc[:, 1]
57
+ df['BB_p'] = (df['close'] - bb.iloc[:, 0]) / (bb.iloc[:, 2] - bb.iloc[:, 0])
58
+ df['MFI'] = ta.mfi(df['high'], df['low'], df['close'], df['volume'], length=14)
59
+ vwap = ta.vwap(df['high'], df['low'], df['close'], df['volume'])
60
+ df['VWAP_dist'] = (df['close'] / vwap) - 1
61
+
62
+ # --- المستوى 2: تكتيكي (1h, 4h) ---
63
+ elif tf in ['1h', '4h']:
64
+ df['RSI'] = ta.rsi(df['close'], length=14)
65
+ df['MACD_h'] = ta.macd(df['close']).iloc[:, 1]
66
+ df['EMA_50_dist'] = (df['close'] / ta.ema(df['close'], length=50)) - 1
67
+ df['EMA_200_dist'] = (df['close'] / ta.ema(df['close'], length=200)) - 1
68
+ df['ATR_pct'] = ta.atr(df['high'], df['low'], df['close'], length=14) / df['close']
69
+
70
+ # --- المستوى 3: استراتيجي (1d) ---
71
+ elif tf == '1d':
72
+ df['RSI'] = ta.rsi(df['close'], length=14)
73
+ df['EMA_200_dist'] = (df['close'] / ta.ema(df['close'], length=200)) - 1
74
+ adx = ta.adx(df['high'], df['low'], df['close'])
75
+ if adx is not None and not adx.empty:
76
+ df['Trend_Strong'] = np.where(adx.iloc[:, 0] > 25, 1, 0)
77
+ else:
78
+ df['Trend_Strong'] = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  return df.reset_index(drop=True)
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  def predict(self, ohlcv_data: dict) -> dict:
83
+ """
84
+ استقبال البيانات الخام (Dictionary of DataFrames/Lists)،
85
+ تجهيزها، ثم استدعاء النموذج للتنبؤ.
86
+ """
87
  if not self.initialized or not self.model:
 
88
  return {'score': 0.0, 'error': 'Titan not initialized'}
89
+
90
  try:
91
+ # 1. تجهيز البيانات لكل إطار
92
  processed_tfs = {}
93
  for tf, data in ohlcv_data.items():
94
+ if not data: continue
95
+ # تحويل القوائم إلى DataFrame إذا لزم الأمر
96
  if isinstance(data, list):
97
+ df = pd.DataFrame(data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
98
  else:
99
  df = data.copy()
100
+
101
+ # تطبيق المؤشرات حسب الإطار
102
+ df = self.apply_inverted_pyramid(df, tf)
103
+ processed_tfs[tf] = df
104
 
105
+ # 2. الدمج (Alignment) للحصول على آخر لقطة (Latest Snapshot)
106
+ if '5m' not in processed_tfs:
107
  return {'score': 0.0, 'error': 'Missing 5m base timeframe'}
108
 
109
+ # نأخذ آخر صف فقط من الـ 5m كأساس
110
+ latest_5m = processed_tfs['5m'].iloc[-1:].copy()
111
+ latest_ts = latest_5m['timestamp'].iloc[0]
112
+
113
+ base_row = latest_5m.add_prefix('5m_').rename(columns={'5m_timestamp': 'timestamp'})
114
+
115
+ # دمج باقي الأطر (نأخذ آخر شمعة أغلقت قبل أو مع شمعة الـ 5m الحالية)
116
+ for tf, df in processed_tfs.items():
117
+ if tf == '5m': continue
118
+ # العثور على الشمعة المناسبة زمنياً
119
+ relevant_row = df[df['timestamp'] <= latest_ts].iloc[-1:].copy()
120
+ if relevant_row.empty: continue
121
+
122
+ # تجهيز الأعمدة للدمج
123
+ cols = [c for c in relevant_row.columns if c not in ['timestamp','open','high','low','close','volume']]
124
+ for col in cols:
125
+ base_row[f"{tf}_{col}"] = relevant_row[col].values[0]
126
+
127
+ # 3. تجهيز شعاع الإدخال (Feature Vector)
128
+ # التأكد من وجود كل الميزات المطلوبة بالترتيب الصحيح
129
+ input_data = []
130
+ for feat in self.feature_names:
131
+ val = base_row.get(feat, np.nan)
132
+ # إذا كانت القيمة مصفوفة أو سلسلة بانداز، نأخذ القيمة الأولى
133
+ if isinstance(val, (pd.Series, np.ndarray)):
134
+ val = val.iloc[0] if len(val) > 0 else np.nan
135
+ input_data.append(val)
136
+
137
+ # 4. التنبؤ
138
+ # تحويل إلى DMatrix (تنسيق XGBoost السريع)
139
+ dtest = xgb.DMatrix([input_data], feature_names=self.feature_names)
140
+ prediction = self.model.predict(dtest)[0] # إرجاع الاحتمالية الأولى
141
+
142
+ return {
143
+ 'score': float(prediction),
144
+ 'timestamp': int(latest_ts),
145
+ 'status': 'OK'
146
+ }
147
 
148
  except Exception as e:
149
+ # print(f"⚠️ [Titan Error] {e}")
150
+ import traceback
151
+ traceback.print_exc()
152
  return {'score': 0.0, 'error': str(e)}