Riy777 commited on
Commit
d123da7
·
verified ·
1 Parent(s): 160a8a2

Update ml_engine/processor.py

Browse files
Files changed (1) hide show
  1. ml_engine/processor.py +65 -129
ml_engine/processor.py CHANGED
@@ -1,6 +1,6 @@
1
  # ============================================================
2
  # 🧠 ml_engine/processor.py
3
- # (V70.0 - GEM-Architect: 5-Layer Precision Logic - Restored)
4
  # ============================================================
5
 
6
  import asyncio
@@ -10,9 +10,8 @@ import numpy as np
10
  from typing import Dict, Any, List, Optional
11
 
12
  # Imports
13
- try: from .pattern_engine import PatternEngine # Formerly TitanEngine
14
  except ImportError: PatternEngine = None
15
-
16
  try: from .monte_carlo import MonteCarloEngine
17
  except ImportError: MonteCarloEngine = None
18
  try: from .oracle_engine import OracleEngine
@@ -34,44 +33,27 @@ MODEL_V3_PATH = os.path.join(BASE_DIR, "ml_models", "DeepSteward_V3_Production.j
34
  MODEL_V3_FEAT = os.path.join(BASE_DIR, "ml_models", "DeepSteward_V3_Features.json")
35
 
36
  # ============================================================
37
- # 🎛️ SYSTEM LIMITS (Restored Full Config)
38
  # ============================================================
39
  class SystemLimits:
40
- """
41
- GEM-Architect: Logic Gates Configuration.
42
- """
43
- # --- Layer 2: Pattern Net Gate ---
44
  L2_GATE_PATTERN_NET = 0.40
45
-
46
- # --- Layer 2: Composite Score Weights ---
47
  L2_WEIGHT_PATTERN = 0.50
48
  L2_WEIGHT_ORACLE = 0.30
49
  L2_WEIGHT_MC = 0.20
50
-
51
  L2_MIN_COMPOSITE_SCORE = 50.0
52
 
53
- # --- Layer 3: External Data Impact ---
54
  L3_WHALE_IMPACT_MAX = 15.0
55
  L3_NEWS_IMPACT_MAX = 10.0
56
- L3_MC_ADVANCED_MAX = 10.0
57
-
58
- # --- Layer 4: Sniper ---
59
  L4_ENTRY_THRESHOLD = 0.50
60
  L4_WEIGHT_ML = 0.60
61
  L4_WEIGHT_OB = 0.40
62
  L4_OB_WALL_RATIO = 0.35
63
 
64
- # --- Layer 0: Hydra & Guardian Defaults (Critical Logic) ---
65
  HYDRA_CRASH_THRESH = 0.60
66
  HYDRA_GIVEBACK_THRESH = 0.80
67
  HYDRA_STAGNATION_THRESH = 0.60
68
 
69
- # Legacy Guards
70
- LEGACY_V2_PANIC_THRESH = 0.98
71
- LEGACY_V3_HARD_THRESH = 0.95
72
- LEGACY_V3_SOFT_THRESH = 0.88
73
- LEGACY_V3_ULTRA_THRESH = 0.99
74
-
75
  @classmethod
76
  def to_dict(cls) -> Dict[str, Any]:
77
  return {k: v for k, v in cls.__dict__.items() if not k.startswith('__') and not callable(v)}
@@ -84,15 +66,11 @@ class MLProcessor:
84
  self.data_manager = data_manager
85
  self.initialized = False
86
 
87
- # ✅ Layer 2 Engines
88
  self.pattern_net = PatternEngine(model_dir=MODELS_UNIFIED_DIR) if PatternEngine else None
89
  self.oracle = OracleEngine(model_dir=MODELS_UNIFIED_DIR) if OracleEngine else None
90
  self.mc_analyzer = MonteCarloEngine() if MonteCarloEngine else None
91
-
92
- # ✅ Layer 4 Engine
93
  self.sniper = SniperEngine(models_dir=MODELS_SNIPER_DIR) if SniperEngine else None
94
 
95
- # ✅ Layer 0 (Guardians)
96
  self.guardian_hydra = GuardianHydra(model_dir=MODELS_HYDRA_DIR) if GuardianHydra else None
97
  self.guardian_legacy = None
98
  if HybridDeepSteward:
@@ -102,7 +80,7 @@ class MLProcessor:
102
  v3_features_map_path=MODEL_V3_FEAT
103
  )
104
 
105
- print(f"🧠 [Processor V70.0] 5-Layer Architecture Initialized (Full Logic Restored).")
106
 
107
  async def initialize(self):
108
  if self.initialized: return True
@@ -132,28 +110,19 @@ class MLProcessor:
132
  else:
133
  self.guardian_legacy.initialize()
134
 
135
- self.guardian_legacy.configure_thresholds(
136
- v2_panic=SystemLimits.LEGACY_V2_PANIC_THRESH,
137
- v3_hard=SystemLimits.LEGACY_V3_HARD_THRESH,
138
- v3_soft=SystemLimits.LEGACY_V3_SOFT_THRESH,
139
- v3_ultra=SystemLimits.LEGACY_V3_ULTRA_THRESH
140
- )
141
-
142
  self.initialized = True
143
  return True
144
  except Exception as e:
145
  print(f"❌ [Processor] Init Error: {e}")
146
- traceback.print_exc()
147
  return False
148
 
149
  # ============================================================
150
- # 🏢 LAYER 2: Pattern + Oracle + MC (The Filter)
151
  # ============================================================
152
- async def execute_layer2_analysis(self, raw_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
153
  """
154
- Calculates the Composite Score for Layer 2.
155
- Input: Symbol Data from Layer 1.
156
- Output: Enriched Data with Score if passed, else None.
157
  """
158
  if not self.initialized: await self.initialize()
159
 
@@ -161,84 +130,94 @@ class MLProcessor:
161
  ohlcv = raw_data.get('ohlcv')
162
  limits = raw_data.get('dynamic_limits', {})
163
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  try:
165
- # 1. Pattern Net Analysis (The Neural Core)
166
  pattern_res = {'score': 0.0, 'probs': [0,0,0]}
167
  if self.pattern_net:
168
  pattern_res = await asyncio.to_thread(self.pattern_net.predict, ohlcv)
169
 
170
  nn_score = pattern_res.get('score', 0.0)
171
- pattern_probs = pattern_res.get('probs', [0,0,0]) # [Neutral, Loss, Win]
 
 
 
172
 
173
- # 🛑 Hard Gate: Pattern Net
 
 
 
 
 
 
 
 
 
 
 
174
  gate_pattern = limits.get('l2_gate_pattern', SystemLimits.L2_GATE_PATTERN_NET)
175
  if nn_score < gate_pattern:
176
- return None # REJECT immediately
 
177
 
178
- # 2. Oracle Analysis (Strategic Return)
 
 
179
  oracle_input = raw_data.copy()
180
  oracle_input['titan_probs'] = pattern_probs
181
  oracle_input['pattern_probs'] = pattern_probs
182
 
183
  oracle_res = {'oracle_score': 0.0}
184
  if self.oracle:
185
- # Update threshold if needed
186
  thresh = limits.get('l3_oracle_thresh', 0.005)
187
  if hasattr(self.oracle, 'set_threshold'): self.oracle.set_threshold(thresh)
188
  oracle_res = await self.oracle.predict(oracle_input)
189
 
190
- # Normalize Oracle (Exp Return)
191
  oracle_val = max(0.0, min(1.0, oracle_res.get('oracle_score', 0.0) * 100))
192
-
193
- # 3. Monte Carlo (Light Check)
194
- mc_val = 0.5
195
- if self.mc_analyzer and '1h' in ohlcv:
196
- try:
197
- closes = [c[4] for c in ohlcv['1h']]
198
- raw_mc = self.mc_analyzer.run_light_check(closes)
199
- mc_val = 0.5 + (raw_mc * 5.0)
200
- mc_val = max(0.0, min(1.0, mc_val))
201
- except: pass
202
 
203
  # 4. Composite Scoring
204
  composite_score = (
205
  (nn_score * SystemLimits.L2_WEIGHT_PATTERN) +
206
  (oracle_val * SystemLimits.L2_WEIGHT_ORACLE) +
207
  (mc_val * SystemLimits.L2_WEIGHT_MC)
208
- ) * 100 # Scale to 0-100
 
 
209
 
210
  if composite_score < SystemLimits.L2_MIN_COMPOSITE_SCORE:
211
- return None # REJECT Weak Score
 
212
 
213
- # Pass to Layer 3
214
- result = raw_data.copy()
215
- result.update({
216
- 'l2_score': composite_score,
217
- 'pattern_score': nn_score,
218
- 'oracle_score': oracle_res.get('oracle_score', 0.0),
219
- 'mc_score': mc_val,
220
- 'pattern_probs': pattern_probs,
221
- 'titan_score': nn_score * 100, # Legacy compatibility for charts
222
- 'titan_probs': pattern_probs # Legacy compatibility
223
- })
224
  return result
225
 
226
  except Exception as e:
227
  print(f"❌ [Layer 2] Error {symbol}: {e}")
228
- return None
 
229
 
230
- # ============================================================
231
- # 🎯 LAYER 4: Sniper Analysis (The Executor)
232
- # ============================================================
233
  async def execute_layer4_sniper(self, symbol: str, ohlcv_1m: List, order_book: Dict) -> Dict[str, Any]:
234
- """
235
- Runs the Sniper Engine on the Top Candidates.
236
- """
237
  if not self.initialized: await self.initialize()
238
-
239
- if not self.sniper:
240
- return {'signal': 'WAIT', 'confidence_prob': 0.0, 'reason': 'No Sniper'}
241
-
242
  try:
243
  self.sniper.configure_settings(
244
  threshold=SystemLimits.L4_ENTRY_THRESHOLD,
@@ -246,35 +225,25 @@ class MLProcessor:
246
  w_ml=SystemLimits.L4_WEIGHT_ML,
247
  w_ob=SystemLimits.L4_WEIGHT_OB
248
  )
249
-
250
  result = await self.sniper.check_entry_signal_async(ohlcv_1m, order_book, symbol=symbol)
251
  return result
252
  except Exception as e:
253
  return {'signal': 'WAIT', 'confidence_prob': 0.0, 'reason': f"Sniper Error: {e}"}
254
 
255
- # ============================================================
256
- # 🛡️ Guardians (Exit Logic) - RESTORED FULL LOGIC
257
- # ============================================================
258
  def consult_guardians(self, symbol, ohlcv_1m, ohlcv_5m, ohlcv_15m, trade_context, ob_snapshot=None):
259
- """
260
- 💎 GEM-Architect: Full Guardian Logic (Restored)
261
- """
262
  if not self.initialized:
263
  return {'action': 'HOLD', 'reason': 'System not initialized', 'probs': {}, 'scores': {}}
264
 
265
- # 1. Extract Limits
266
  limits = trade_context.get('dynamic_limits', {})
267
  h_crash_thresh = limits.get('hydra_crash', SystemLimits.HYDRA_CRASH_THRESH)
268
  h_giveback_thresh = limits.get('hydra_giveback', SystemLimits.HYDRA_GIVEBACK_THRESH)
269
  h_stag_thresh = limits.get('hydra_stagnation', SystemLimits.HYDRA_STAGNATION_THRESH)
270
 
271
- # Context
272
  entry_price = float(trade_context.get('entry_price', 0.0))
273
  highest_price = trade_context.get('highest_price', entry_price)
274
  max_pnl_pct = ((highest_price - entry_price) / entry_price) * 100 if entry_price > 0 else 0.0
275
  time_in_trade_mins = trade_context.get('time_in_trade_mins', 0.0)
276
 
277
- # A. Hydra Execution
278
  hydra_result = {'action': 'HOLD', 'reason': 'Disabled', 'probs': {}}
279
  if self.guardian_hydra:
280
  try:
@@ -284,7 +253,6 @@ class MLProcessor:
284
  p_giveback = h_probs.get('giveback', 0.0)
285
  p_stagnation = h_probs.get('stagnation', 0.0)
286
 
287
- # Processor-Level Override Logic
288
  if p_crash >= h_crash_thresh:
289
  hydra_result['action'] = 'EXIT_HARD'
290
  hydra_result['reason'] = f"Hydra Crash Risk {p_crash:.2f}"
@@ -294,10 +262,8 @@ class MLProcessor:
294
  elif p_stagnation >= h_stag_thresh and time_in_trade_mins > 90:
295
  hydra_result['action'] = 'EXIT_SOFT'
296
  hydra_result['reason'] = f"Hydra Stagnation {p_stagnation:.2f}"
297
- except Exception as e:
298
- print(f"⚠️ [Processor] Hydra error: {e}")
299
 
300
- # B. Legacy Execution
301
  legacy_result = {'action': 'HOLD', 'reason': 'Disabled', 'scores': {}}
302
  if self.guardian_legacy:
303
  try:
@@ -307,20 +273,14 @@ class MLProcessor:
307
  order_book=ob_snapshot,
308
  volume_30m_usd=vol_30m
309
  )
310
- except Exception as e:
311
- print(f"⚠️ [Processor] Legacy error: {e}")
312
 
313
- # C. Arbitration (The Brain)
314
- h_probs = hydra_result.get('probs', {})
315
- l_scores = legacy_result.get('scores', {})
316
-
317
  final_action = 'HOLD'
318
  final_reason = f"Safe."
319
 
320
  hydra_act = hydra_result.get('action', 'HOLD')
321
  legacy_act = legacy_result.get('action', 'HOLD')
322
 
323
- # Priority: Hydra > Legacy
324
  if hydra_act in ['EXIT_HARD', 'EXIT_SOFT', 'TIGHTEN_SL', 'TRAIL_SL']:
325
  final_action = hydra_act
326
  final_reason = f"🐲 {hydra_result.get('reason')}"
@@ -331,30 +291,6 @@ class MLProcessor:
331
  return {
332
  'action': final_action,
333
  'reason': final_reason,
334
- 'probs': h_probs,
335
- 'scores': l_scores
336
- }
337
-
338
- # ============================================================
339
- # 🔮 Advanced Utilities (Restored)
340
- # ============================================================
341
- async def run_advanced_monte_carlo(self, symbol, timeframe='1h'):
342
- """Restored for Layer 3 usage if needed"""
343
- if self.mc_analyzer and self.data_manager:
344
- try:
345
- ohlcv = await self.data_manager.get_latest_ohlcv(symbol, timeframe, limit=300)
346
- if ohlcv:
347
- return self.mc_analyzer.run_advanced_simulation([c[4] for c in ohlcv])
348
- except: pass
349
- return 0.0
350
-
351
- async def consult_oracle(self, symbol_data: Dict[str, Any]) -> Dict[str, Any]:
352
- """Legacy wrapper used if needed, pointing to L2 logic"""
353
- if self.oracle:
354
- return await self.oracle.predict(symbol_data)
355
- return {'action': 'WAIT'}
356
-
357
- async def check_sniper_entry(self, ohlcv_1m_data, order_book_data, context_data=None):
358
- """Legacy wrapper pointing to execute_layer4"""
359
- symbol = "UNKNOWN" # Sniper needs symbol mostly for cache
360
- return await self.execute_layer4_sniper(symbol, ohlcv_1m_data, order_book_data)
 
1
  # ============================================================
2
  # 🧠 ml_engine/processor.py
3
+ # (V70.2 - GEM-Architect: Transparent Debugging Mode)
4
  # ============================================================
5
 
6
  import asyncio
 
10
  from typing import Dict, Any, List, Optional
11
 
12
  # Imports
13
+ try: from .pattern_engine import PatternEngine
14
  except ImportError: PatternEngine = None
 
15
  try: from .monte_carlo import MonteCarloEngine
16
  except ImportError: MonteCarloEngine = None
17
  try: from .oracle_engine import OracleEngine
 
33
  MODEL_V3_FEAT = os.path.join(BASE_DIR, "ml_models", "DeepSteward_V3_Features.json")
34
 
35
  # ============================================================
36
+ # 🎛️ SYSTEM LIMITS
37
  # ============================================================
38
  class SystemLimits:
 
 
 
 
39
  L2_GATE_PATTERN_NET = 0.40
 
 
40
  L2_WEIGHT_PATTERN = 0.50
41
  L2_WEIGHT_ORACLE = 0.30
42
  L2_WEIGHT_MC = 0.20
 
43
  L2_MIN_COMPOSITE_SCORE = 50.0
44
 
 
45
  L3_WHALE_IMPACT_MAX = 15.0
46
  L3_NEWS_IMPACT_MAX = 10.0
47
+
 
 
48
  L4_ENTRY_THRESHOLD = 0.50
49
  L4_WEIGHT_ML = 0.60
50
  L4_WEIGHT_OB = 0.40
51
  L4_OB_WALL_RATIO = 0.35
52
 
 
53
  HYDRA_CRASH_THRESH = 0.60
54
  HYDRA_GIVEBACK_THRESH = 0.80
55
  HYDRA_STAGNATION_THRESH = 0.60
56
 
 
 
 
 
 
 
57
  @classmethod
58
  def to_dict(cls) -> Dict[str, Any]:
59
  return {k: v for k, v in cls.__dict__.items() if not k.startswith('__') and not callable(v)}
 
66
  self.data_manager = data_manager
67
  self.initialized = False
68
 
 
69
  self.pattern_net = PatternEngine(model_dir=MODELS_UNIFIED_DIR) if PatternEngine else None
70
  self.oracle = OracleEngine(model_dir=MODELS_UNIFIED_DIR) if OracleEngine else None
71
  self.mc_analyzer = MonteCarloEngine() if MonteCarloEngine else None
 
 
72
  self.sniper = SniperEngine(models_dir=MODELS_SNIPER_DIR) if SniperEngine else None
73
 
 
74
  self.guardian_hydra = GuardianHydra(model_dir=MODELS_HYDRA_DIR) if GuardianHydra else None
75
  self.guardian_legacy = None
76
  if HybridDeepSteward:
 
80
  v3_features_map_path=MODEL_V3_FEAT
81
  )
82
 
83
+ print(f"🧠 [Processor V70.2] Transparent Debugging Active.")
84
 
85
  async def initialize(self):
86
  if self.initialized: return True
 
110
  else:
111
  self.guardian_legacy.initialize()
112
 
 
 
 
 
 
 
 
113
  self.initialized = True
114
  return True
115
  except Exception as e:
116
  print(f"❌ [Processor] Init Error: {e}")
 
117
  return False
118
 
119
  # ============================================================
120
+ # 🏢 LAYER 2: Pattern + Oracle + MC (Transparent Logic)
121
  # ============================================================
122
+ async def execute_layer2_analysis(self, raw_data: Dict[str, Any]) -> Dict[str, Any]:
123
  """
124
+ Calculates Scores. Returns result even if rejected for debugging.
125
+ Key 'is_valid': True/False determines acceptance.
 
126
  """
127
  if not self.initialized: await self.initialize()
128
 
 
130
  ohlcv = raw_data.get('ohlcv')
131
  limits = raw_data.get('dynamic_limits', {})
132
 
133
+ # Default Result Structure (Rejected by default)
134
+ result = raw_data.copy()
135
+ result.update({
136
+ 'is_valid': False,
137
+ 'reason': 'Unknown',
138
+ 'l2_score': 0.0,
139
+ 'pattern_score': 0.0,
140
+ 'oracle_score': 0.0,
141
+ 'mc_score': 0.0,
142
+ 'pattern_probs': [0, 0, 0]
143
+ })
144
+
145
  try:
146
+ # 1. Pattern Net Analysis
147
  pattern_res = {'score': 0.0, 'probs': [0,0,0]}
148
  if self.pattern_net:
149
  pattern_res = await asyncio.to_thread(self.pattern_net.predict, ohlcv)
150
 
151
  nn_score = pattern_res.get('score', 0.0)
152
+ pattern_probs = pattern_res.get('probs', [0,0,0])
153
+
154
+ result['pattern_score'] = nn_score
155
+ result['pattern_probs'] = pattern_probs
156
 
157
+ # 2. Monte Carlo (Run always for logging)
158
+ mc_val = 0.5
159
+ if self.mc_analyzer and '1h' in ohlcv:
160
+ try:
161
+ closes = [c[4] for c in ohlcv['1h']]
162
+ raw_mc = self.mc_analyzer.run_light_check(closes)
163
+ mc_val = 0.5 + (raw_mc * 5.0)
164
+ mc_val = max(0.0, min(1.0, mc_val))
165
+ except: pass
166
+ result['mc_score'] = mc_val
167
+
168
+ # 🛑 Hard Gate Check
169
  gate_pattern = limits.get('l2_gate_pattern', SystemLimits.L2_GATE_PATTERN_NET)
170
  if nn_score < gate_pattern:
171
+ result['reason'] = f"Pattern Score {nn_score:.2f} < {gate_pattern}"
172
+ return result # Return REJECTED result with scores
173
 
174
+ # 3. Oracle Analysis (Only if passed gate to save resources, or run for debug?)
175
+ # Let's run Oracle only if close to gate or passed, to be efficient.
176
+ # But for full transparency requested, we run it.
177
  oracle_input = raw_data.copy()
178
  oracle_input['titan_probs'] = pattern_probs
179
  oracle_input['pattern_probs'] = pattern_probs
180
 
181
  oracle_res = {'oracle_score': 0.0}
182
  if self.oracle:
 
183
  thresh = limits.get('l3_oracle_thresh', 0.005)
184
  if hasattr(self.oracle, 'set_threshold'): self.oracle.set_threshold(thresh)
185
  oracle_res = await self.oracle.predict(oracle_input)
186
 
 
187
  oracle_val = max(0.0, min(1.0, oracle_res.get('oracle_score', 0.0) * 100))
188
+ result['oracle_score'] = oracle_res.get('oracle_score', 0.0)
 
 
 
 
 
 
 
 
 
189
 
190
  # 4. Composite Scoring
191
  composite_score = (
192
  (nn_score * SystemLimits.L2_WEIGHT_PATTERN) +
193
  (oracle_val * SystemLimits.L2_WEIGHT_ORACLE) +
194
  (mc_val * SystemLimits.L2_WEIGHT_MC)
195
+ ) * 100
196
+
197
+ result['l2_score'] = composite_score
198
 
199
  if composite_score < SystemLimits.L2_MIN_COMPOSITE_SCORE:
200
+ result['reason'] = f"Composite {composite_score:.1f} < {SystemLimits.L2_MIN_COMPOSITE_SCORE}"
201
+ return result
202
 
203
+ # PASSED
204
+ result['is_valid'] = True
205
+ result['reason'] = 'PASSED'
206
+ # Legacy keys for charts
207
+ result['titan_score'] = nn_score * 100
208
+ result['titan_probs'] = pattern_probs
209
+
 
 
 
 
210
  return result
211
 
212
  except Exception as e:
213
  print(f"❌ [Layer 2] Error {symbol}: {e}")
214
+ result['reason'] = f"Error: {str(e)}"
215
+ return result
216
 
217
+ # ... (Keep execute_layer4_sniper and consult_guardians exactly as is) ...
 
 
218
  async def execute_layer4_sniper(self, symbol: str, ohlcv_1m: List, order_book: Dict) -> Dict[str, Any]:
 
 
 
219
  if not self.initialized: await self.initialize()
220
+ if not self.sniper: return {'signal': 'WAIT', 'confidence_prob': 0.0, 'reason': 'No Sniper'}
 
 
 
221
  try:
222
  self.sniper.configure_settings(
223
  threshold=SystemLimits.L4_ENTRY_THRESHOLD,
 
225
  w_ml=SystemLimits.L4_WEIGHT_ML,
226
  w_ob=SystemLimits.L4_WEIGHT_OB
227
  )
 
228
  result = await self.sniper.check_entry_signal_async(ohlcv_1m, order_book, symbol=symbol)
229
  return result
230
  except Exception as e:
231
  return {'signal': 'WAIT', 'confidence_prob': 0.0, 'reason': f"Sniper Error: {e}"}
232
 
 
 
 
233
  def consult_guardians(self, symbol, ohlcv_1m, ohlcv_5m, ohlcv_15m, trade_context, ob_snapshot=None):
 
 
 
234
  if not self.initialized:
235
  return {'action': 'HOLD', 'reason': 'System not initialized', 'probs': {}, 'scores': {}}
236
 
 
237
  limits = trade_context.get('dynamic_limits', {})
238
  h_crash_thresh = limits.get('hydra_crash', SystemLimits.HYDRA_CRASH_THRESH)
239
  h_giveback_thresh = limits.get('hydra_giveback', SystemLimits.HYDRA_GIVEBACK_THRESH)
240
  h_stag_thresh = limits.get('hydra_stagnation', SystemLimits.HYDRA_STAGNATION_THRESH)
241
 
 
242
  entry_price = float(trade_context.get('entry_price', 0.0))
243
  highest_price = trade_context.get('highest_price', entry_price)
244
  max_pnl_pct = ((highest_price - entry_price) / entry_price) * 100 if entry_price > 0 else 0.0
245
  time_in_trade_mins = trade_context.get('time_in_trade_mins', 0.0)
246
 
 
247
  hydra_result = {'action': 'HOLD', 'reason': 'Disabled', 'probs': {}}
248
  if self.guardian_hydra:
249
  try:
 
253
  p_giveback = h_probs.get('giveback', 0.0)
254
  p_stagnation = h_probs.get('stagnation', 0.0)
255
 
 
256
  if p_crash >= h_crash_thresh:
257
  hydra_result['action'] = 'EXIT_HARD'
258
  hydra_result['reason'] = f"Hydra Crash Risk {p_crash:.2f}"
 
262
  elif p_stagnation >= h_stag_thresh and time_in_trade_mins > 90:
263
  hydra_result['action'] = 'EXIT_SOFT'
264
  hydra_result['reason'] = f"Hydra Stagnation {p_stagnation:.2f}"
265
+ except Exception: pass
 
266
 
 
267
  legacy_result = {'action': 'HOLD', 'reason': 'Disabled', 'scores': {}}
268
  if self.guardian_legacy:
269
  try:
 
273
  order_book=ob_snapshot,
274
  volume_30m_usd=vol_30m
275
  )
276
+ except Exception: pass
 
277
 
 
 
 
 
278
  final_action = 'HOLD'
279
  final_reason = f"Safe."
280
 
281
  hydra_act = hydra_result.get('action', 'HOLD')
282
  legacy_act = legacy_result.get('action', 'HOLD')
283
 
 
284
  if hydra_act in ['EXIT_HARD', 'EXIT_SOFT', 'TIGHTEN_SL', 'TRAIL_SL']:
285
  final_action = hydra_act
286
  final_reason = f"🐲 {hydra_result.get('reason')}"
 
291
  return {
292
  'action': final_action,
293
  'reason': final_reason,
294
+ 'probs': hydra_result.get('probs', {}),
295
+ 'scores': legacy_result.get('scores', {})
296
+ }