Riy777 commited on
Commit
088dd38
·
1 Parent(s): e4d750c

Update ml_engine/titan_engine.py

Browse files
Files changed (1) hide show
  1. ml_engine/titan_engine.py +174 -138
ml_engine/titan_engine.py CHANGED
@@ -1,5 +1,5 @@
1
  # ml_engine/titan_engine.py
2
- # (V1.2 - Robust indicators for short daily history)
3
 
4
  import os
5
  import joblib
@@ -8,45 +8,56 @@ import pandas as pd
8
  import pandas_ta as ta
9
  import xgboost as xgb
10
 
11
- def _safe_series_like(ref_index, fill=np.nan):
12
- return pd.Series(index=ref_index, data=fill, dtype="float64")
13
 
14
- def _safe_ema(s: pd.Series, length: int) -> pd.Series:
15
- try:
16
- ema = ta.ema(s, length=length)
17
- if ema is None:
18
- return _safe_series_like(s.index)
19
- return ema
20
- except Exception:
21
- return _safe_series_like(s.index)
22
 
23
- def _safe_adx(h, l, c):
24
- try:
25
- adx = ta.adx(h, l, c)
26
- if adx is None or adx.empty:
27
- return None
28
- return adx
29
- except Exception:
30
- return None
31
 
32
- def _safe_macd(c):
 
 
 
 
 
 
33
  try:
34
- m = ta.macd(c)
35
- if m is None or m.empty:
36
- return None
37
- return m
 
 
 
 
 
 
 
 
 
38
  except Exception:
39
- return None
40
 
41
- def _safe_bbands(c, length=20, std=2.0):
 
 
 
42
  try:
43
- bb = ta.bbands(c, length=length, std=std)
44
- if bb is None or bb.empty:
45
- return None
46
- return bb
47
  except Exception:
48
  return None
49
 
 
 
 
 
 
 
 
 
 
50
  class TitanEngine:
51
  def __init__(self, model_dir="ml_models/layer2"):
52
  self.model_path = os.path.join(model_dir, "Titan_XGB_V1.json")
@@ -69,94 +80,120 @@ class TitanEngine:
69
  except Exception as e:
70
  print(f"❌ [Titan] خطأ أثناء التهيئة: {e}", flush=True)
71
 
72
- def apply_inverted_pyramid(self, df_in, tf):
73
- df = df_in.copy().sort_values('timestamp').reset_index(drop=True)
74
- # فهرس زمني لسهولة عمل pandas_ta
75
- df = df.set_index(pd.DatetimeIndex(pd.to_datetime(df['timestamp'], unit='ms')))
76
- close = df['close']; high = df['high']; low = df['low']; vol = df['volume']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- # مؤشرات عامة آمنة
79
- def safe_fill(s):
80
- return s.replace([np.inf, -np.inf], np.nan).astype("float64")
81
 
82
- try:
83
- if tf in ['5m', '15m']:
84
- df['RSI'] = safe_fill(ta.rsi(close, length=14) or _safe_series_like(close.index)).fillna(50)
85
- macd = _safe_macd(close)
86
- if macd is not None:
87
- df['MACD'] = safe_fill(macd.iloc[:, 0]).fillna(0)
88
- df['MACD_h'] = safe_fill(macd.iloc[:, 1]).fillna(0)
89
- else:
90
- df['MACD'] = _safe_series_like(close.index, 0)
91
- df['MACD_h'] = _safe_series_like(close.index, 0)
92
 
93
- df['CCI'] = safe_fill(ta.cci(high, low, close, length=20) or _safe_series_like(close.index)).fillna(0)
94
 
95
- adx = _safe_adx(high, low, close)
96
- if adx is not None:
97
- df['ADX'] = safe_fill(adx.iloc[:, 0]).fillna(0)
98
- else:
99
- df['ADX'] = _safe_series_like(close.index, 0)
100
 
101
- for p in [9, 21, 50, 200]:
102
- ema = _safe_ema(close, p)
103
- df[f'EMA_{p}_dist'] = safe_fill((close / ema) - 1).fillna(0)
 
 
104
 
105
- bb = _safe_bbands(close, length=20, std=2.0)
106
- if bb is not None:
107
- mid = bb.iloc[:, 1]
108
- df['BB_w'] = safe_fill((bb.iloc[:, 2] - bb.iloc[:, 0]) / (mid.replace(0, np.nan))).fillna(0)
109
- df['BB_p'] = safe_fill((close - bb.iloc[:, 0]) / ((bb.iloc[:, 2] - bb.iloc[:, 0]).replace(0, np.nan))).fillna(0)
110
- else:
111
- df['BB_w'] = _safe_series_like(close.index, 0)
112
- df['BB_p'] = _safe_series_like(close.index, 0)
113
-
114
- try:
115
- vwap = ta.vwap(high, low, close, vol)
116
- if vwap is None:
117
- vwap = _safe_series_like(close.index)
118
- except Exception:
119
- vwap = _safe_series_like(close.index)
120
- df['VWAP_dist'] = safe_fill((close / vwap) - 1).fillna(0)
121
-
122
- elif tf in ['1h', '4h']:
123
- df['RSI'] = safe_fill(ta.rsi(close, length=14) or _safe_series_like(close.index)).fillna(50)
124
- macd = _safe_macd(close)
125
- if macd is not None:
126
- df['MACD_h'] = safe_fill(macd.iloc[:, 1]).fillna(0)
127
- else:
128
- df['MACD_h'] = _safe_series_like(close.index, 0)
129
-
130
- ema50 = _safe_ema(close, 50)
131
- ema200 = _safe_ema(close, 200)
132
- df['EMA_50_dist'] = safe_fill((close / ema50) - 1).fillna(0)
133
- df['EMA_200_dist'] = safe_fill((close / ema200) - 1).fillna(0)
134
-
135
- try:
136
- atr = ta.atr(high, low, close, length=14)
137
- if atr is None:
138
- atr = _safe_series_like(close.index, 0)
139
- except Exception:
140
- atr = _safe_series_like(close.index, 0)
141
- df['ATR_pct'] = safe_fill(atr / close.replace(0, np.nan)).fillna(0)
142
-
143
- elif tf == '1d':
144
- df['RSI'] = safe_fill(ta.rsi(close, length=14) or _safe_series_like(close.index)).fillna(50)
145
- ema200 = _safe_ema(close, 200) # قد يكون كله NaN عند 60 شمعة، وهذا مقبول
146
- df['EMA_200_dist'] = safe_fill((close / ema200) - 1).fillna(0)
147
-
148
- adx = _safe_adx(high, low, close)
149
- if adx is not None:
150
- adx_main = safe_fill(adx.iloc[:, 0]).fillna(0)
151
- df['Trend_Strong'] = (adx_main > 25).astype(int)
152
- else:
153
- df['Trend_Strong'] = 0
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  except Exception as e:
156
  print(f"⚠️ [Titan] مؤشر فشل على tf={tf}: {e}", flush=True)
 
157
 
158
- # ارجع إلى شكل طبيعي
159
- return df.reset_index(drop=True)
160
 
161
  def predict(self, ohlcv_data: dict) -> dict:
162
  if not self.initialized or not self.model:
@@ -165,50 +202,49 @@ class TitanEngine:
165
  try:
166
  processed_tfs = {}
167
  for tf, data in ohlcv_data.items():
168
- if not data:
169
  continue
170
  if isinstance(data, list):
171
- df = pd.DataFrame(data, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
172
  else:
173
  df = data.copy()
174
- df = self.apply_inverted_pyramid(df, tf)
175
- processed_tfs[tf] = df
176
 
177
- if '5m' not in processed_tfs:
178
  print("⚠️ [Titan] Missing 5m base timeframe", flush=True)
179
  return {'score': 0.0, 'error': 'Missing 5m base timeframe'}
180
 
181
- latest_5m = processed_tfs['5m'].iloc[-1:].copy()
182
- latest_ts = latest_5m['timestamp'].iloc[0]
183
- base_row = latest_5m.add_prefix('5m_').rename(columns={'5m_timestamp': 'timestamp'})
184
 
185
- for tf, df in processed_tfs.items():
186
- if tf == '5m':
 
187
  continue
188
- relevant_row = df[df['timestamp'] <= latest_ts].iloc[-1:].copy()
189
- if relevant_row.empty:
190
  continue
191
- cols = [c for c in relevant_row.columns if c not in ['timestamp','open','high','low','close','volume']]
192
- for col in cols:
193
- base_row[f"{tf}_{col}"] = relevant_row[col].values[0]
194
-
195
- # ترتيب الميزات
196
- input_data = []
197
- for feat in self.feature_names:
198
- val = base_row.get(feat, np.nan)
199
- if isinstance(val, (pd.Series, np.ndarray)):
200
- val = val.iloc[0] if len(val) > 0 else np.nan
 
201
  if pd.isna(val):
202
  val = 0.0
203
- input_data.append(float(val))
204
-
205
- dtest = xgb.DMatrix([input_data], feature_names=self.feature_names)
206
- prediction = float(self.model.predict(dtest)[0])
207
 
208
- print(f"[DEBUG][Titan] pred={prediction:.3f}", flush=True)
209
- return {'score': prediction, 'timestamp': int(latest_ts), 'status': 'OK'}
 
 
210
 
211
  except Exception as e:
212
- import traceback
213
- traceback.print_exc()
214
  return {'score': 0.0, 'error': str(e)}
 
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
 
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"):
63
  self.model_path = os.path.join(model_dir, "Titan_XGB_V1.json")
 
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:
 
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)}