Riy777 commited on
Commit
ba7da31
Β·
verified Β·
1 Parent(s): c6ab7a4

Update ml_engine/guardian_hydra.py

Browse files
Files changed (1) hide show
  1. ml_engine/guardian_hydra.py +140 -127
ml_engine/guardian_hydra.py CHANGED
@@ -1,211 +1,224 @@
1
- # ml_engine/guardian_hydra.py
2
- # (V2.0 - GEM-Architect: Loader Hardening & Input Fix)
3
-
4
  import os
5
  import joblib
6
  import numpy as np
7
  import pandas as pd
8
  import pandas_ta as ta
9
  import xgboost as xgb
 
10
  import traceback
11
-
12
- # βœ… FORCE SKLEARN COMPATIBILITY GLOBALLY
13
- # This prevents 'XGBClassifier' object has no attribute '_estimator_type' error
14
- xgb.XGBClassifier._estimator_type = "classifier"
15
 
16
  class GuardianHydra:
17
  """
18
- GuardianHydra V2.0
19
- - Robust Model Loader (JSON/Booster Fallback).
20
- - Prevents 0.00 scores by ensuring valid input vectors.
21
  """
22
  def __init__(self, model_dir):
23
  self.model_dir = model_dir
24
  self.initialized = False
25
  self.models = {}
26
  self.feature_cols = []
27
- self.verbose = True
 
 
 
 
 
 
 
28
  self.ATR_PERIOD = 14
 
 
 
 
 
 
29
 
30
  def initialize(self):
31
- if self.verbose: print(f"🐲 [Hydra] Initializing from: {self.model_dir}")
32
 
33
  if not os.path.exists(self.model_dir):
34
- print(f"❌ [Hydra] Path missing: {self.model_dir}")
35
  return False
36
 
37
  try:
38
- # 1. Load Features List
39
  feat_path = os.path.join(self.model_dir, "hydra_features_list.pkl")
40
  if not os.path.exists(feat_path):
41
- print(f"❌ Feature list missing: {feat_path}")
42
  return False
43
  self.feature_cols = joblib.load(feat_path)
 
44
 
45
- # 2. Load Models (Head Models)
46
  heads = ['crash', 'giveback', 'stagnation']
47
  for h in heads:
48
- # We look for the RAW JSON models (standard XGBoost)
49
- # If you only have .pkl, we can adapt, but usually raw json is preferred for speed
50
  model_path = os.path.join(self.model_dir, f"hydra_head_{h}_raw.json")
51
- if not os.path.exists(model_path):
52
- # Try finding PKL if JSON missing
53
- model_path = os.path.join(self.model_dir, f"hydra_head_{h}_calib.pkl")
54
- if not os.path.exists(model_path): continue
55
-
56
- try:
57
- if model_path.endswith('.json'):
58
- # Load as Classifier Wrapper first
59
- clf = xgb.XGBClassifier()
60
- clf._estimator_type = "classifier"
61
- clf.load_model(model_path)
62
- self.models[h] = {'model': clf, 'type': 'sklearn'}
63
- else:
64
- # Load PKL (likely a Pipeline or Isotonic)
65
- # Warning: This might be slow if it's a full pipeline
66
- mod = joblib.load(model_path)
67
- self.models[h] = {'model': mod, 'type': 'pkl'}
68
-
69
- except Exception as e:
70
- # Fallback to Booster if wrapper fails
71
- if self.verbose: print(f"⚠️ {h}: JSON Load failed, trying Booster. ({e})")
72
- try:
73
- bst = xgb.Booster()
74
- bst.load_model(model_path)
75
- self.models[h] = {'model': bst, 'type': 'booster'}
76
- except:
77
- print(f"❌ Failed to load {h} completely.")
78
 
79
  self.initialized = True
80
- print(f"βœ… [Hydra] Ready ({len(self.models)} heads).")
81
  return True
82
 
83
  except Exception as e:
84
- print(f"❌ [Hydra Init] {e}")
 
85
  return False
86
 
87
  def _engineer_features(self, ohlcv_1m, ohlcv_5m, ohlcv_15m, trade_context):
88
  try:
89
- if not ohlcv_1m or len(ohlcv_1m) < 20: return None
 
 
 
 
90
  df_1m = pd.DataFrame(ohlcv_1m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
 
 
91
  last_close = df_1m['close'].iloc[-1]
 
 
92
 
93
- # Indicators
94
- df_1m['atr'] = ta.atr(df_1m['high'], df_1m['low'], df_1m['close'], length=self.ATR_PERIOD).fillna(0)
95
- df_1m['rsi'] = ta.rsi(df_1m['close'], length=14).fillna(50)
96
 
 
 
 
 
 
97
  last_rsi = df_1m['rsi'].iloc[-1]
98
  last_atr = df_1m['atr'].iloc[-1]
99
 
 
 
 
 
100
  bb = ta.bbands(df_1m['close'], length=20, std=2)
101
- bb_width = bb.iloc[-1, 2] if bb is not None else 0.0
 
 
 
 
102
 
103
- vol_ma = df_1m['volume'].rolling(50).mean().iloc[-1]
104
- rel_vol = df_1m['volume'].iloc[-1] / (vol_ma + 1e-9)
105
 
106
- rsi_5m = 50.0; rsi_15m = 50.0
107
- if ohlcv_5m and len(ohlcv_5m)>14:
108
- d5 = pd.DataFrame(ohlcv_5m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
109
- rsi_5m = ta.rsi(d5['close'], 14).iloc[-1]
110
- if ohlcv_15m and len(ohlcv_15m)>14:
111
- d15 = pd.DataFrame(ohlcv_15m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
112
- rsi_15m = ta.rsi(d15['close'], 14).iloc[-1]
 
 
 
 
113
 
114
  # Trade Context
115
- entry_price = float(trade_context.get('entry_price', last_close))
116
- if entry_price <= 0: entry_price = last_close
117
 
118
  atr_val = last_atr if last_atr > 0 else (entry_price * 0.01)
119
  sl_dist_unit = 1.5 * atr_val
120
  pnl_amt = last_close - entry_price
 
 
 
121
 
122
- highest = float(trade_context.get('highest_price', entry_price))
 
123
 
124
- # Feature Dictionary (Must match training features)
 
 
 
125
  feat_dict = {
126
- 'rsi_1m': last_rsi,
127
- 'rsi_5m': rsi_5m,
128
  'rsi_15m': rsi_15m,
129
- 'bb_width': bb_width,
130
- 'rel_vol': rel_vol,
131
- 'dist_ema20_1h': 0.0, # Placeholder if missing
132
- 'atr_pct': atr_val / (last_close + 1e-9),
133
- 'norm_pnl_r': pnl_amt / (sl_dist_unit + 1e-9),
134
- 'max_pnl_r': (highest - entry_price) / (sl_dist_unit + 1e-9),
135
- 'dist_tp_atr': 0.0,
136
  'dist_sl_atr': 0.0,
137
- 'time_in_trade': float(trade_context.get('time_in_trade_mins', 0)),
138
- 'entry_type': 0.0,
139
- 'oracle_conf': 0.8,
140
- 'l2_score': 0.7,
141
  'target_class': 3.0
142
  }
143
 
144
  vector = pd.DataFrame([feat_dict])
145
 
146
- # Ensure all columns exist and order is correct
147
  for col in self.feature_cols:
148
- if col not in vector.columns: vector[col] = 0.0
 
 
 
 
 
 
 
149
 
150
  return vector[self.feature_cols].astype(float)
151
 
152
- except Exception:
 
 
 
153
  return None
154
 
155
  def analyze_position(self, symbol, ohlcv_1m, ohlcv_5m, ohlcv_15m, trade_data):
156
- if not self.initialized: return {'action': 'HOLD', 'reason': 'Not Init'}
 
157
 
158
  try:
159
  features = self._engineer_features(ohlcv_1m, ohlcv_5m, ohlcv_15m, trade_data)
160
- if features is None: return {'action': 'HOLD', 'reason': 'Feat Fail'}
161
 
 
 
 
 
162
  probs = {}
163
- for h, cont in self.models.items():
164
- m = cont['model']
165
- m_type = cont['type']
166
  try:
167
- if m_type == 'sklearn':
168
- # Sklearn wrapper: predict_proba return [[prob_0, prob_1]]
169
- probs[h] = float(m.predict_proba(features)[0][1])
170
- elif m_type == 'pkl':
171
- # PKL Pipeline: usually predict_proba or decision_function
172
- # Try predict_proba first
173
- if hasattr(m, 'predict_proba'):
174
- res = m.predict_proba(features)
175
- probs[h] = float(res[0][1] if len(res.shape)>1 else res[0])
176
- else:
177
- probs[h] = float(m.predict(features)[0])
178
- else:
179
- # Raw Booster: predict returns probability directly
180
- probs[h] = float(m.predict(xgb.DMatrix(features))[0])
181
- except:
182
  probs[h] = 0.0
183
 
184
- # Logic
185
- crash = probs.get('crash', 0.0)
186
- giveback = probs.get('giveback', 0.0)
187
- stag = probs.get('stagnation', 0.0)
188
-
189
- action = 'HOLD'
190
- reason = 'Safe'
191
-
192
- if crash >= 0.60:
193
- action = 'EXIT_HARD'
194
- reason = f'Crash Risk ({crash:.2f})'
195
- elif giveback >= 0.75:
196
- action = 'EXIT_SOFT'
197
- reason = f'Giveback ({giveback:.2f})'
198
- elif stag >= 0.70:
199
- action = 'EXIT_SOFT'
200
- reason = f'Stagnation ({stag:.2f})'
201
-
202
- return {
203
- 'action': action,
204
- 'reason': reason,
205
- 'probs': probs,
206
- # Mapping specifically for UI:
207
- 'scores': {'v2': crash, 'v3': giveback}
208
- }
209
 
210
  except Exception as e:
211
- return {'action': 'HOLD', 'reason': 'Error', 'probs': {}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import joblib
3
  import numpy as np
4
  import pandas as pd
5
  import pandas_ta as ta
6
  import xgboost as xgb
7
+ from collections import deque, defaultdict
8
  import traceback
9
+ import sys
 
 
 
10
 
11
  class GuardianHydra:
12
  """
13
+ GuardianHydra V1.4 (Configurable Verbosity)
14
+ - Added set_silent_mode() to suppress logs during Backtesting.
 
15
  """
16
  def __init__(self, model_dir):
17
  self.model_dir = model_dir
18
  self.initialized = False
19
  self.models = {}
20
  self.feature_cols = []
21
+ self.verbose = True # βœ… Default to True for Live System
22
+
23
+ self.smoothing_buffer = defaultdict(lambda: {
24
+ 'crash': deque(maxlen=3),
25
+ 'giveback': deque(maxlen=3),
26
+ 'stagnation': deque(maxlen=3)
27
+ })
28
+
29
  self.ATR_PERIOD = 14
30
+ # βœ… Silent check
31
+ if self.verbose: print("🐲 [Hydra X-RAY] Instance Created. Waiting for data...")
32
+
33
+ def set_silent_mode(self, silent=True):
34
+ """ βœ… Control Logging Output (True = No Logs, False = X-RAY Logs) """
35
+ self.verbose = not silent
36
 
37
  def initialize(self):
38
+ if self.verbose: print(f"🐲 [Hydra X-RAY] Loading from: {self.model_dir}")
39
 
40
  if not os.path.exists(self.model_dir):
41
+ if self.verbose: print(f"❌ [FATAL] Directory missing: {self.model_dir}")
42
  return False
43
 
44
  try:
45
+ # 1. Load Features
46
  feat_path = os.path.join(self.model_dir, "hydra_features_list.pkl")
47
  if not os.path.exists(feat_path):
48
+ if self.verbose: print(f"❌ Feature list missing: {feat_path}")
49
  return False
50
  self.feature_cols = joblib.load(feat_path)
51
+ if self.verbose: print(f"βœ… Features List Loaded ({len(self.feature_cols)} items)")
52
 
53
+ # 2. Load Models (RAW)
54
  heads = ['crash', 'giveback', 'stagnation']
55
  for h in heads:
 
 
56
  model_path = os.path.join(self.model_dir, f"hydra_head_{h}_raw.json")
57
+ if not os.path.exists(model_path):
58
+ if self.verbose: print(f"❌ Model missing: {model_path}")
59
+ return False
60
+
61
+ clf = xgb.XGBClassifier()
62
+ clf.load_model(model_path)
63
+ self.models[h] = clf
64
+ if self.verbose: print(f"βœ… Loaded Head: {h}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  self.initialized = True
67
+ if self.verbose: print(f"βœ… [Hydra X-RAY] System Ready.")
68
  return True
69
 
70
  except Exception as e:
71
+ if self.verbose: print(f"οΏ½οΏ½οΏ½ [Hydra Init Error] {e}")
72
+ traceback.print_exc()
73
  return False
74
 
75
  def _engineer_features(self, ohlcv_1m, ohlcv_5m, ohlcv_15m, trade_context):
76
  try:
77
+ # 1. Check Raw Data Inputs
78
+ if not ohlcv_1m or len(ohlcv_1m) < 1:
79
+ if self.verbose: print("⚠️ [X-RAY] 1m Data is EMPTY!")
80
+ return None
81
+
82
  df_1m = pd.DataFrame(ohlcv_1m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
83
+
84
+ # [DIAGNOSTIC 1] Print Input Sample
85
  last_close = df_1m['close'].iloc[-1]
86
+ if self.verbose:
87
+ print(f"πŸ” [Input Check] Last Close: {last_close} | Candles: {len(df_1m)}")
88
 
89
+ if len(df_1m) < 50:
90
+ if self.verbose: print(f"⚠️ [X-RAY] Not enough history: {len(df_1m)} < 50")
91
+ return None
92
 
93
+ # 2. Indicator Calculation
94
+ df_1m['atr'] = ta.atr(df_1m['high'], df_1m['low'], df_1m['close'], length=self.ATR_PERIOD)
95
+ df_1m['rsi'] = ta.rsi(df_1m['close'], length=14)
96
+
97
+ # [DIAGNOSTIC 2] Check Indicators
98
  last_rsi = df_1m['rsi'].iloc[-1]
99
  last_atr = df_1m['atr'].iloc[-1]
100
 
101
+ if (pd.isna(last_rsi) or pd.isna(last_atr)) and self.verbose:
102
+ print(f"⚠️ [X-RAY] Indicators are NaN! RSI: {last_rsi}, ATR: {last_atr}")
103
+
104
+ # ... rest of calculations ...
105
  bb = ta.bbands(df_1m['close'], length=20, std=2)
106
+ if bb is not None:
107
+ w_col = [c for c in bb.columns if 'BBB' in c]
108
+ df_1m['bb_width'] = bb[w_col[0]] if w_col else 0.0
109
+ else:
110
+ df_1m['bb_width'] = 0.0
111
 
112
+ vol_ma = df_1m['volume'].rolling(50).mean()
113
+ df_1m['rel_vol'] = df_1m['volume'] / (vol_ma + 1e-9)
114
 
115
+ # HTF Mocking if missing
116
+ df_5m = pd.DataFrame(ohlcv_5m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) if ohlcv_5m else pd.DataFrame()
117
+ rsi_5m = ta.rsi(df_5m['close'], length=14).iloc[-1] if len(df_5m) > 14 else 50
118
+
119
+ df_15m = pd.DataFrame(ohlcv_15m, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) if ohlcv_15m else pd.DataFrame()
120
+ rsi_15m = ta.rsi(df_15m['close'], length=14).iloc[-1] if len(df_15m) > 14 else 50
121
+
122
+ dist_ema20_1h = 0.0
123
+ if len(df_15m) > 4:
124
+ ema20_1h_approx = df_15m['close'].ewm(span=80, adjust=False).mean().iloc[-1]
125
+ dist_ema20_1h = (last_close - ema20_1h_approx) / last_close
126
 
127
  # Trade Context
128
+ entry_price = float(trade_context.get('entry_price', 0.0))
129
+ if entry_price == 0: entry_price = last_close # Fallback
130
 
131
  atr_val = last_atr if last_atr > 0 else (entry_price * 0.01)
132
  sl_dist_unit = 1.5 * atr_val
133
  pnl_amt = last_close - entry_price
134
+ norm_pnl_r = pnl_amt / sl_dist_unit
135
+
136
+ duration_mins = trade_context.get('time_in_trade_mins', 10)
137
 
138
+ highest_price = float(trade_context.get('highest_price', entry_price))
139
+ if highest_price < entry_price: highest_price = entry_price
140
 
141
+ max_pnl_amt = highest_price - entry_price
142
+ max_pnl_r = max_pnl_amt / sl_dist_unit if sl_dist_unit > 0 else 0.0
143
+
144
+ # Assemble Vector
145
  feat_dict = {
146
+ 'rsi_1m': last_rsi,
147
+ 'rsi_5m': rsi_5m,
148
  'rsi_15m': rsi_15m,
149
+ 'bb_width': df_1m['bb_width'].iloc[-1],
150
+ 'rel_vol': df_1m['rel_vol'].iloc[-1],
151
+ 'dist_ema20_1h': dist_ema20_1h,
152
+ 'atr_pct': atr_val / last_close,
153
+ 'norm_pnl_r': norm_pnl_r,
154
+ 'max_pnl_r': max_pnl_r,
155
+ 'dist_tp_atr': 0.0,
156
  'dist_sl_atr': 0.0,
157
+ 'time_in_trade': float(duration_mins),
158
+ 'entry_type': 0.0,
159
+ 'oracle_conf': 0.8,
160
+ 'l2_score': 0.7,
161
  'target_class': 3.0
162
  }
163
 
164
  vector = pd.DataFrame([feat_dict])
165
 
 
166
  for col in self.feature_cols:
167
+ if col not in vector.columns:
168
+ vector[col] = 0.0
169
+
170
+ if vector.isnull().values.any():
171
+ if self.verbose:
172
+ print("⚠️ [X-RAY] Final Vector contains NaNs! Model will fail or output 0.")
173
+ print(vector.iloc[0].to_dict())
174
+ vector = vector.fillna(0)
175
 
176
  return vector[self.feature_cols].astype(float)
177
 
178
+ except Exception as e:
179
+ if self.verbose:
180
+ print(f"❌ [X-RAY] Feature Error: {e}")
181
+ traceback.print_exc()
182
  return None
183
 
184
  def analyze_position(self, symbol, ohlcv_1m, ohlcv_5m, ohlcv_15m, trade_data):
185
+ if not self.initialized:
186
+ return {'action': 'HOLD', 'reason': 'Not Init'}
187
 
188
  try:
189
  features = self._engineer_features(ohlcv_1m, ohlcv_5m, ohlcv_15m, trade_data)
 
190
 
191
+ if features is None:
192
+ if self.verbose: print(f"🚫 [X-RAY] {symbol}: Feature Engineering Failed.")
193
+ return {'action': 'HOLD', 'reason': 'Feat Fail'}
194
+
195
  probs = {}
196
+ if self.verbose: print(f"πŸ”¬ [X-RAY] Predicting for {symbol}...")
197
+
198
+ for h in ['crash', 'giveback', 'stagnation']:
199
  try:
200
+ full_pred = self.models[h].predict_proba(features)
201
+ raw_prob = full_pred[0][1]
202
+ probs[h] = raw_prob
203
+
204
+ if raw_prob > 0.0 and self.verbose:
205
+ print(f" πŸ”₯ {h.upper()} Non-Zero Prob: {raw_prob:.4f}")
206
+
207
+ except Exception as e:
208
+ if self.verbose: print(f" ❌ Error predicting {h}: {e}")
 
 
 
 
 
 
209
  probs[h] = 0.0
210
 
211
+ return self._pkg('HOLD', 0.0, "X-RAY DIAGNOSTIC", probs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
  except Exception as e:
214
+ if self.verbose: print(f"❌ [X-RAY] Analyze Error: {e}")
215
+ return {'action': 'HOLD', 'reason': 'Error'}
216
+
217
+ def _pkg(self, action, conf, reason, probs):
218
+ return {
219
+ 'action': action,
220
+ 'confidence': float(conf),
221
+ 'reason': reason,
222
+ 'probs': {k: float(v) for k, v in probs.items()},
223
+ 'scores': {'v2': probs.get('crash',0), 'v3': probs.get('giveback',0)}
224
+ }