Spaces:
Sleeping
Sleeping
Update ml_engine/hybrid_guardian.py
Browse files- ml_engine/hybrid_guardian.py +62 -51
ml_engine/hybrid_guardian.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# ml_engine/hybrid_guardian.py
|
| 2 |
-
# (
|
| 3 |
|
| 4 |
import os
|
| 5 |
import json
|
|
@@ -25,6 +25,7 @@ class HybridDeepSteward:
|
|
| 25 |
self.model_v3 = None
|
| 26 |
self.v3_feature_names = []
|
| 27 |
self.initialized = False
|
|
|
|
| 28 |
|
| 29 |
# Legacy V2 Features
|
| 30 |
self.FEATS_1M = ['log_ret_1m', 'rsi_1m', 'fib_pos_1m', 'volatility_1m']
|
|
@@ -39,7 +40,7 @@ class HybridDeepSteward:
|
|
| 39 |
|
| 40 |
def initialize(self):
|
| 41 |
try:
|
| 42 |
-
print(f"🕸️ [Hybrid] Initializing Precision Guardians...")
|
| 43 |
if os.path.exists(self.v2_path):
|
| 44 |
self.model_v2 = xgb.Booster()
|
| 45 |
self.model_v2.load_model(self.v2_path)
|
|
@@ -51,7 +52,7 @@ class HybridDeepSteward:
|
|
| 51 |
if os.path.exists(self.v3_features_path):
|
| 52 |
with open(self.v3_features_path, 'r') as f:
|
| 53 |
self.v3_feature_names = json.load(f)
|
| 54 |
-
print(f" -> Loaded V3 Map: {len(self.v3_feature_names)} features.")
|
| 55 |
|
| 56 |
self.initialized = True
|
| 57 |
return True
|
|
@@ -75,7 +76,7 @@ class HybridDeepSteward:
|
|
| 75 |
except: return pd.DataFrame()
|
| 76 |
|
| 77 |
# ==========================================================================
|
| 78 |
-
# 📐 V2 LEGACY FEATURE ENGINEERING
|
| 79 |
# ==========================================================================
|
| 80 |
def _calc_legacy_row(self, df, suffix):
|
| 81 |
if len(df) < 15: return pd.Series()
|
|
@@ -130,15 +131,20 @@ class HybridDeepSteward:
|
|
| 130 |
"""Calculates Monte Carlo like stats on historical returns"""
|
| 131 |
if len(returns_series) < 10: return 0, 0, 0, 0, 0
|
| 132 |
r = returns_series.dropna()
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
def _enrich_dataframe(self, df):
|
| 141 |
-
if len(df) <
|
| 142 |
c = df['close']
|
| 143 |
|
| 144 |
# 1. Basic Indicators
|
|
@@ -146,53 +152,56 @@ class HybridDeepSteward:
|
|
| 146 |
df['rsi_slope'] = self._calc_slope(df['rsi'])
|
| 147 |
|
| 148 |
# 2. MACD
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
| 155 |
|
| 156 |
# 3. ADX & Trend
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
-
# Trend Net Force: (Close - EMA50) normalized
|
| 166 |
ema50 = ta.ema(c, 50).fillna(c)
|
| 167 |
-
df['trend_net_force'] = (c - ema50) / c
|
| 168 |
|
| 169 |
# 4. EMAs
|
| 170 |
df['ema_20'] = ta.ema(c, 20).fillna(c)
|
| 171 |
df['ema_50'] = ema50
|
| 172 |
df['ema_200'] = ta.ema(c, 200).fillna(c)
|
| 173 |
|
| 174 |
-
df['dist_ema20'] = (c - df['ema_20']) / c
|
| 175 |
-
df['dist_ema50'] = (c - df['ema_50']) / c
|
| 176 |
-
df['dist_ema200'] = (c - df['ema_200']) / c
|
| 177 |
df['slope_ema50'] = self._calc_slope(df['ema_50'], 5)
|
| 178 |
|
| 179 |
# 5. Volatility & Volume
|
| 180 |
df['atr'] = ta.atr(df['high'], df['low'], c, 14).fillna(0)
|
| 181 |
-
df['atr_rel'] = df['atr'] / c
|
| 182 |
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
| 187 |
|
| 188 |
-
# 6. Returns
|
| 189 |
df['log_ret'] = np.log(c / c.shift(1)).fillna(0)
|
| 190 |
|
| 191 |
-
# Rolling MC Stats (Window 50)
|
| 192 |
-
rolling_rets = df['log_ret'].rolling(50)
|
| 193 |
-
# Note: Rolling apply is slow, using simplified approach for performance
|
| 194 |
-
# We calculate MC stats ONLY for the last row in _engineer_v3
|
| 195 |
-
|
| 196 |
return df
|
| 197 |
|
| 198 |
def _engineer_v3_dataframe(self, ohlcv_1m, ohlcv_5m, ohlcv_15m):
|
|
@@ -201,7 +210,7 @@ class HybridDeepSteward:
|
|
| 201 |
df5 = self._prepare_df(ohlcv_5m)
|
| 202 |
df15 = self._prepare_df(ohlcv_15m)
|
| 203 |
|
| 204 |
-
if len(df1) < 50: return None
|
| 205 |
|
| 206 |
# --- Enrich Dataframes ---
|
| 207 |
df1 = self._enrich_dataframe(df1)
|
|
@@ -213,15 +222,15 @@ class HybridDeepSteward:
|
|
| 213 |
r15 = df15.iloc[-1] if len(df15) > 0 else r1
|
| 214 |
|
| 215 |
# --- MC Stats (On 1m History) ---
|
| 216 |
-
#
|
| 217 |
mc_skew, mc_kurt, mc_prob_gain, mc_var_95, mc_shock = self._calc_mc_stats(df1['log_ret'].tail(50))
|
| 218 |
|
| 219 |
# --- MC Stats (On 5m History) ---
|
| 220 |
-
mc_prob_gain_5m = (df5['log_ret'].tail(20) > 0).mean() if len(df5) >
|
| 221 |
-
mc_shock_5m = (df5['log_ret'].min() - df5['log_ret'].mean()) / df5['log_ret'].std() if len(df5) > 20 else 0
|
| 222 |
|
| 223 |
# --- MC Stats (On 15m History) ---
|
| 224 |
-
mc_prob_gain_15m = (df15['log_ret'].tail(20) > 0).mean() if len(df15) >
|
| 225 |
|
| 226 |
# --- Construct Feature Map ---
|
| 227 |
feats = {}
|
|
@@ -255,10 +264,11 @@ class HybridDeepSteward:
|
|
| 255 |
feats['mc_prob_gain_15m'] = float(mc_prob_gain_15m)
|
| 256 |
feats['dist_ema200_15m'] = float(r15.get('dist_ema200', 0.0))
|
| 257 |
|
| 258 |
-
# 4. Numbered Placeholders (
|
| 259 |
-
#
|
| 260 |
-
|
| 261 |
-
|
|
|
|
| 262 |
|
| 263 |
# --- Final Alignment ---
|
| 264 |
df_aligned = pd.DataFrame(columns=self.v3_feature_names)
|
|
@@ -290,13 +300,13 @@ class HybridDeepSteward:
|
|
| 290 |
if not self.initialized: return {'action': 'HOLD', 'scores': scores}
|
| 291 |
|
| 292 |
try:
|
| 293 |
-
# V2
|
| 294 |
vec_v2 = self._engineer_legacy_v2_vector(ohlcv_1m, ohlcv_5m, ohlcv_15m)
|
| 295 |
if vec_v2 is not None and self.model_v2:
|
| 296 |
pred_v2 = self.model_v2.predict(xgb.DMatrix(vec_v2))
|
| 297 |
scores['v2'] = float(pred_v2[0][2]) if len(pred_v2.shape)>1 else float(pred_v2[2]) if len(pred_v2)>1 else float(pred_v2[0])
|
| 298 |
|
| 299 |
-
# V3 (
|
| 300 |
if self.model_v3 and self.v3_feature_names:
|
| 301 |
df_v3 = self._engineer_v3_dataframe(ohlcv_1m, ohlcv_5m, ohlcv_15m)
|
| 302 |
if df_v3 is not None:
|
|
@@ -320,4 +330,5 @@ class HybridDeepSteward:
|
|
| 320 |
return {'action': 'HOLD', 'reason': f'Monitor (V2:{v2:.2f} V3:{v3:.2f})', 'scores': scores}
|
| 321 |
|
| 322 |
except Exception as e:
|
|
|
|
| 323 |
return {'action': 'HOLD', 'reason': 'Error', 'scores': scores}
|
|
|
|
| 1 |
# ml_engine/hybrid_guardian.py
|
| 2 |
+
# (V80.0 - GEM-Architect: Full V3 Feature Parity)
|
| 3 |
|
| 4 |
import os
|
| 5 |
import json
|
|
|
|
| 25 |
self.model_v3 = None
|
| 26 |
self.v3_feature_names = []
|
| 27 |
self.initialized = False
|
| 28 |
+
self.verbose = True
|
| 29 |
|
| 30 |
# Legacy V2 Features
|
| 31 |
self.FEATS_1M = ['log_ret_1m', 'rsi_1m', 'fib_pos_1m', 'volatility_1m']
|
|
|
|
| 40 |
|
| 41 |
def initialize(self):
|
| 42 |
try:
|
| 43 |
+
if self.verbose: print(f"🕸️ [Hybrid] Initializing Precision Guardians...")
|
| 44 |
if os.path.exists(self.v2_path):
|
| 45 |
self.model_v2 = xgb.Booster()
|
| 46 |
self.model_v2.load_model(self.v2_path)
|
|
|
|
| 52 |
if os.path.exists(self.v3_features_path):
|
| 53 |
with open(self.v3_features_path, 'r') as f:
|
| 54 |
self.v3_feature_names = json.load(f)
|
| 55 |
+
if self.verbose: print(f" -> Loaded V3 Map: {len(self.v3_feature_names)} features.")
|
| 56 |
|
| 57 |
self.initialized = True
|
| 58 |
return True
|
|
|
|
| 76 |
except: return pd.DataFrame()
|
| 77 |
|
| 78 |
# ==========================================================================
|
| 79 |
+
# 📐 V2 LEGACY FEATURE ENGINEERING (Keep working logic)
|
| 80 |
# ==========================================================================
|
| 81 |
def _calc_legacy_row(self, df, suffix):
|
| 82 |
if len(df) < 15: return pd.Series()
|
|
|
|
| 131 |
"""Calculates Monte Carlo like stats on historical returns"""
|
| 132 |
if len(returns_series) < 10: return 0, 0, 0, 0, 0
|
| 133 |
r = returns_series.dropna()
|
| 134 |
+
if len(r) < 5: return 0, 0, 0, 0, 0
|
| 135 |
+
|
| 136 |
+
try:
|
| 137 |
+
skew_val = skew(r)
|
| 138 |
+
kurt_val = kurtosis(r)
|
| 139 |
+
prob_gain = (r > 0).mean()
|
| 140 |
+
var_95 = np.percentile(r, 5) # 5th percentile as VaR proxy
|
| 141 |
+
shock = (r.min() - r.mean()) / (r.std() + 1e-9)
|
| 142 |
+
return skew_val, kurt_val, prob_gain, var_95, shock
|
| 143 |
+
except:
|
| 144 |
+
return 0, 0, 0, 0, 0
|
| 145 |
|
| 146 |
def _enrich_dataframe(self, df):
|
| 147 |
+
if len(df) < 30: return df
|
| 148 |
c = df['close']
|
| 149 |
|
| 150 |
# 1. Basic Indicators
|
|
|
|
| 152 |
df['rsi_slope'] = self._calc_slope(df['rsi'])
|
| 153 |
|
| 154 |
# 2. MACD
|
| 155 |
+
try:
|
| 156 |
+
macd = ta.macd(c)
|
| 157 |
+
if macd is not None and not macd.empty:
|
| 158 |
+
df['macd_h'] = macd.iloc[:, 1].fillna(0) # Histogram is usually 2nd column
|
| 159 |
+
df['macd_h_slope'] = self._calc_slope(df['macd_h'])
|
| 160 |
+
else:
|
| 161 |
+
df['macd_h'] = 0.0; df['macd_h_slope'] = 0.0
|
| 162 |
+
except:
|
| 163 |
+
df['macd_h'] = 0.0; df['macd_h_slope'] = 0.0
|
| 164 |
|
| 165 |
# 3. ADX & Trend
|
| 166 |
+
try:
|
| 167 |
+
adx = ta.adx(df['high'], df['low'], c)
|
| 168 |
+
if adx is not None and not adx.empty:
|
| 169 |
+
df['adx'] = adx.iloc[:, 0].fillna(0)
|
| 170 |
+
df['dmp'] = adx.iloc[:, 1].fillna(0)
|
| 171 |
+
df['dmn'] = adx.iloc[:, 2].fillna(0)
|
| 172 |
+
else:
|
| 173 |
+
df['adx'] = 0.0; df['dmp'] = 0.0; df['dmn'] = 0.0
|
| 174 |
+
except:
|
| 175 |
+
df['adx'] = 0.0; df['dmp'] = 0.0; df['dmn'] = 0.0
|
| 176 |
|
| 177 |
+
# Trend Net Force: (Close - EMA50) normalized
|
| 178 |
ema50 = ta.ema(c, 50).fillna(c)
|
| 179 |
+
df['trend_net_force'] = (c - ema50) / (c + 1e-9)
|
| 180 |
|
| 181 |
# 4. EMAs
|
| 182 |
df['ema_20'] = ta.ema(c, 20).fillna(c)
|
| 183 |
df['ema_50'] = ema50
|
| 184 |
df['ema_200'] = ta.ema(c, 200).fillna(c)
|
| 185 |
|
| 186 |
+
df['dist_ema20'] = (c - df['ema_20']) / (c + 1e-9)
|
| 187 |
+
df['dist_ema50'] = (c - df['ema_50']) / (c + 1e-9)
|
| 188 |
+
df['dist_ema200'] = (c - df['ema_200']) / (c + 1e-9)
|
| 189 |
df['slope_ema50'] = self._calc_slope(df['ema_50'], 5)
|
| 190 |
|
| 191 |
# 5. Volatility & Volume
|
| 192 |
df['atr'] = ta.atr(df['high'], df['low'], c, 14).fillna(0)
|
| 193 |
+
df['atr_rel'] = df['atr'] / (c + 1e-9)
|
| 194 |
|
| 195 |
+
try:
|
| 196 |
+
df['obv'] = ta.obv(c, df['volume']).fillna(0)
|
| 197 |
+
df['obv_slope'] = self._calc_slope(df['obv'], 5)
|
| 198 |
+
df['cmf'] = ta.cmf(df['high'], df['low'], c, df['volume'], 20).fillna(0)
|
| 199 |
+
except:
|
| 200 |
+
df['obv'] = 0.0; df['obv_slope'] = 0.0; df['cmf'] = 0.0
|
| 201 |
|
| 202 |
+
# 6. Returns
|
| 203 |
df['log_ret'] = np.log(c / c.shift(1)).fillna(0)
|
| 204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
return df
|
| 206 |
|
| 207 |
def _engineer_v3_dataframe(self, ohlcv_1m, ohlcv_5m, ohlcv_15m):
|
|
|
|
| 210 |
df5 = self._prepare_df(ohlcv_5m)
|
| 211 |
df15 = self._prepare_df(ohlcv_15m)
|
| 212 |
|
| 213 |
+
if len(df1) < 50: return None # V3 Needs more data
|
| 214 |
|
| 215 |
# --- Enrich Dataframes ---
|
| 216 |
df1 = self._enrich_dataframe(df1)
|
|
|
|
| 222 |
r15 = df15.iloc[-1] if len(df15) > 0 else r1
|
| 223 |
|
| 224 |
# --- MC Stats (On 1m History) ---
|
| 225 |
+
# Calculates on the last 50 candles
|
| 226 |
mc_skew, mc_kurt, mc_prob_gain, mc_var_95, mc_shock = self._calc_mc_stats(df1['log_ret'].tail(50))
|
| 227 |
|
| 228 |
# --- MC Stats (On 5m History) ---
|
| 229 |
+
mc_prob_gain_5m = (df5['log_ret'].tail(20) > 0).mean() if len(df5) > 20 else 0
|
| 230 |
+
mc_shock_5m = (df5['log_ret'].min() - df5['log_ret'].mean()) / (df5['log_ret'].std()+1e-9) if len(df5) > 20 else 0
|
| 231 |
|
| 232 |
# --- MC Stats (On 15m History) ---
|
| 233 |
+
mc_prob_gain_15m = (df15['log_ret'].tail(20) > 0).mean() if len(df15) > 20 else 0
|
| 234 |
|
| 235 |
# --- Construct Feature Map ---
|
| 236 |
feats = {}
|
|
|
|
| 264 |
feats['mc_prob_gain_15m'] = float(mc_prob_gain_15m)
|
| 265 |
feats['dist_ema200_15m'] = float(r15.get('dist_ema200', 0.0))
|
| 266 |
|
| 267 |
+
# 4. Numbered Placeholders (Placeholders in JSON)
|
| 268 |
+
# We initialize them to 0.0 to prevent crash if model uses embeddings
|
| 269 |
+
for i in range(0, 20):
|
| 270 |
+
key = str(i)
|
| 271 |
+
if key in self.v3_feature_names: feats[key] = 0.0
|
| 272 |
|
| 273 |
# --- Final Alignment ---
|
| 274 |
df_aligned = pd.DataFrame(columns=self.v3_feature_names)
|
|
|
|
| 300 |
if not self.initialized: return {'action': 'HOLD', 'scores': scores}
|
| 301 |
|
| 302 |
try:
|
| 303 |
+
# V2 (Legacy)
|
| 304 |
vec_v2 = self._engineer_legacy_v2_vector(ohlcv_1m, ohlcv_5m, ohlcv_15m)
|
| 305 |
if vec_v2 is not None and self.model_v2:
|
| 306 |
pred_v2 = self.model_v2.predict(xgb.DMatrix(vec_v2))
|
| 307 |
scores['v2'] = float(pred_v2[0][2]) if len(pred_v2.shape)>1 else float(pred_v2[2]) if len(pred_v2)>1 else float(pred_v2[0])
|
| 308 |
|
| 309 |
+
# V3 (Precision)
|
| 310 |
if self.model_v3 and self.v3_feature_names:
|
| 311 |
df_v3 = self._engineer_v3_dataframe(ohlcv_1m, ohlcv_5m, ohlcv_15m)
|
| 312 |
if df_v3 is not None:
|
|
|
|
| 330 |
return {'action': 'HOLD', 'reason': f'Monitor (V2:{v2:.2f} V3:{v3:.2f})', 'scores': scores}
|
| 331 |
|
| 332 |
except Exception as e:
|
| 333 |
+
# traceback.print_exc()
|
| 334 |
return {'action': 'HOLD', 'reason': 'Error', 'scores': scores}
|