diff --git a/application/signal_snapshot.py b/application/signal_snapshot.py index a7979c3..8e3d8e5 100644 --- a/application/signal_snapshot.py +++ b/application/signal_snapshot.py @@ -43,6 +43,72 @@ "blend_gate_volatility_delever_dynamic_cap", "blend_gate_volatility_delever_metric", "blend_gate_volatility_delever_triggered", + "blend_gate_volatility_delever_retention_ratio", + "blend_gate_volatility_delever_redirect_symbol", + "blend_gate_volatility_delever_removed_ratio", + "dual_drive_volatility_delever_enabled", + "dual_drive_volatility_delever_window", + "dual_drive_volatility_delever_threshold_mode", + "dual_drive_volatility_delever_threshold", + "dual_drive_volatility_delever_exit_threshold", + "dual_drive_volatility_delever_dynamic_threshold", + "dual_drive_volatility_delever_dynamic_sample_count", + "dual_drive_volatility_delever_dynamic_lookback", + "dual_drive_volatility_delever_dynamic_percentile", + "dual_drive_volatility_delever_dynamic_min_periods", + "dual_drive_volatility_delever_dynamic_floor", + "dual_drive_volatility_delever_dynamic_cap", + "dual_drive_volatility_delever_metric", + "dual_drive_volatility_delever_triggered", + "dual_drive_volatility_delever_entry_triggered", + "dual_drive_volatility_delever_hysteresis_triggered", + "dual_drive_volatility_delever_trigger_reason", + "dual_drive_volatility_delever_applied", + "dual_drive_volatility_delever_vetoed", + "dual_drive_volatility_delever_veto_reason", + "dual_drive_volatility_delever_taco_veto_enabled", + "dual_drive_volatility_delever_taco_rebound_context_active", + "dual_drive_volatility_delever_true_crisis_active", + "dual_drive_volatility_delever_redirect_symbol", + "dual_drive_volatility_delever_removed_value", + "dual_drive_macro_risk_governor_enabled", + "dual_drive_macro_risk_governor_found", + "dual_drive_macro_risk_governor_route", + "dual_drive_macro_risk_governor_active", + "dual_drive_macro_risk_governor_applied", + "dual_drive_macro_risk_governor_leverage_scalar", + "dual_drive_macro_risk_governor_risk_asset_scalar", + "dual_drive_macro_risk_governor_removed_value", + "dual_drive_macro_risk_governor_redirected_to_unlevered", + "dual_drive_crisis_defense_enabled", + "dual_drive_crisis_defense_triggered", + "dual_drive_crisis_defense_applied", + "dual_drive_crisis_defense_destination", + "dual_drive_crisis_defense_removed_value", + "market_regime_control_enabled", + "market_regime_control_found", + "market_regime_control_source", + "market_regime_control_schema_version", + "market_regime_control_route", + "market_regime_control_route_source", + "market_regime_control_active", + "market_regime_control_applied", + "market_regime_control_route_allowed", + "market_regime_control_risk_scalar", + "market_regime_control_risk_budget_scalar", + "market_regime_control_leverage_scalar", + "market_regime_control_risk_asset_scalar", + "market_regime_control_taco_allowed", + "market_regime_control_local_delever_veto_allowed", + "market_regime_control_crisis_defense_required", + "market_regime_control_blocked_actions", + "market_regime_control_vetoes", + "market_regime_control_reason_codes", + "market_regime_control_removed_weight", + "market_regime_control_removed_ratio", + "market_regime_control_redirected_to_unlevered_ratio", + "market_regime_control_safe_haven", + "market_regime_control_risk_symbols", ) diff --git a/decision_mapper.py b/decision_mapper.py index d80ff46..c085c53 100644 --- a/decision_mapper.py +++ b/decision_mapper.py @@ -52,7 +52,26 @@ "dual_drive_volatility_delever_trigger_reason", "dual_drive_volatility_delever_applied", "dual_drive_volatility_delever_vetoed", + "dual_drive_volatility_delever_veto_reason", + "dual_drive_volatility_delever_taco_veto_enabled", + "dual_drive_volatility_delever_taco_rebound_context_active", + "dual_drive_volatility_delever_true_crisis_active", "dual_drive_volatility_delever_redirect_symbol", + "dual_drive_volatility_delever_removed_value", + "dual_drive_macro_risk_governor_enabled", + "dual_drive_macro_risk_governor_found", + "dual_drive_macro_risk_governor_route", + "dual_drive_macro_risk_governor_active", + "dual_drive_macro_risk_governor_applied", + "dual_drive_macro_risk_governor_leverage_scalar", + "dual_drive_macro_risk_governor_risk_asset_scalar", + "dual_drive_macro_risk_governor_removed_value", + "dual_drive_macro_risk_governor_redirected_to_unlevered", + "dual_drive_crisis_defense_enabled", + "dual_drive_crisis_defense_triggered", + "dual_drive_crisis_defense_applied", + "dual_drive_crisis_defense_destination", + "dual_drive_crisis_defense_removed_value", ) _SOXL_RISK_CONTROL_EXECUTION_FIELDS = ( "blend_gate_volatility_delever_enabled", @@ -73,6 +92,32 @@ "blend_gate_volatility_delever_redirect_symbol", "blend_gate_volatility_delever_removed_ratio", ) +_MARKET_REGIME_CONTROL_EXECUTION_FIELDS = ( + "market_regime_control_enabled", + "market_regime_control_found", + "market_regime_control_source", + "market_regime_control_schema_version", + "market_regime_control_route", + "market_regime_control_route_source", + "market_regime_control_active", + "market_regime_control_applied", + "market_regime_control_route_allowed", + "market_regime_control_risk_scalar", + "market_regime_control_risk_budget_scalar", + "market_regime_control_leverage_scalar", + "market_regime_control_risk_asset_scalar", + "market_regime_control_taco_allowed", + "market_regime_control_local_delever_veto_allowed", + "market_regime_control_crisis_defense_required", + "market_regime_control_blocked_actions", + "market_regime_control_vetoes", + "market_regime_control_reason_codes", + "market_regime_control_removed_weight", + "market_regime_control_removed_ratio", + "market_regime_control_redirected_to_unlevered_ratio", + "market_regime_control_safe_haven", + "market_regime_control_risk_symbols", +) def _build_portfolio_inputs( @@ -195,6 +240,25 @@ def _attach_tqqq_risk_control_execution_fields( execution[field] = value +def _attach_market_regime_control_execution_fields( + plan: dict[str, Any], + *, + decision: StrategyDecision, + runtime_metadata: Mapping[str, Any] | None, +) -> None: + execution = plan.get("execution") + if not isinstance(execution, dict): + return + diagnostics = {**dict(runtime_metadata or {}), **dict(decision.diagnostics)} + annotations = diagnostics.get("execution_annotations") + if isinstance(annotations, Mapping): + diagnostics = {**diagnostics, **dict(annotations)} + for field in _MARKET_REGIME_CONTROL_EXECUTION_FIELDS: + value = diagnostics.get(field) + if value not in (None, ""): + execution[field] = value + + def _attach_soxl_risk_control_execution_fields( plan: dict[str, Any], *, @@ -645,6 +709,11 @@ def map_strategy_decision_to_plan( decision=normalized_decision, runtime_metadata=runtime_metadata, ) + _attach_market_regime_control_execution_fields( + plan, + decision=normalized_decision, + runtime_metadata=runtime_metadata, + ) _attach_tqqq_risk_control_execution_fields( plan, decision=normalized_decision, diff --git a/tests/test_decision_mapper.py b/tests/test_decision_mapper.py index ba321fe..a56f05d 100644 --- a/tests/test_decision_mapper.py +++ b/tests/test_decision_mapper.py @@ -144,7 +144,32 @@ def test_maps_hybrid_decision_from_snapshot_source(self): "dual_drive_volatility_delever_dynamic_floor": 0.24, "dual_drive_volatility_delever_dynamic_cap": 0.36, "dual_drive_volatility_delever_trigger_reason": "entry_threshold", + "dual_drive_volatility_delever_veto_reason": "taco_rebound_context", + "dual_drive_volatility_delever_taco_veto_enabled": True, + "dual_drive_volatility_delever_taco_rebound_context_active": False, + "dual_drive_volatility_delever_true_crisis_active": False, "dual_drive_volatility_delever_redirect_symbol": "QQQ", + "dual_drive_volatility_delever_removed_value": 4500.0, + "dual_drive_macro_risk_governor_enabled": True, + "dual_drive_macro_risk_governor_found": True, + "dual_drive_macro_risk_governor_route": "risk_reduced", + "dual_drive_macro_risk_governor_active": True, + "dual_drive_macro_risk_governor_applied": True, + "dual_drive_macro_risk_governor_leverage_scalar": 0.5, + "dual_drive_macro_risk_governor_risk_asset_scalar": 0.75, + "dual_drive_macro_risk_governor_removed_value": 2500.0, + "dual_drive_macro_risk_governor_redirected_to_unlevered": 1500.0, + "dual_drive_crisis_defense_enabled": True, + "dual_drive_crisis_defense_triggered": True, + "dual_drive_crisis_defense_applied": False, + "dual_drive_crisis_defense_destination": "BOXX", + "dual_drive_crisis_defense_removed_value": 0.0, + "market_regime_control_enabled": True, + "market_regime_control_found": True, + "market_regime_control_route": "risk_reduced", + "market_regime_control_active": True, + "market_regime_control_risk_budget_scalar": 0.5, + "market_regime_control_reason_codes": ("macro:vix_crisis_level",), } }, ) @@ -186,7 +211,32 @@ def test_maps_hybrid_decision_from_snapshot_source(self): self.assertEqual(plan["execution"]["dual_drive_volatility_delever_dynamic_floor"], 0.24) self.assertEqual(plan["execution"]["dual_drive_volatility_delever_dynamic_cap"], 0.36) self.assertEqual(plan["execution"]["dual_drive_volatility_delever_trigger_reason"], "entry_threshold") + self.assertEqual(plan["execution"]["dual_drive_volatility_delever_veto_reason"], "taco_rebound_context") + self.assertIs(plan["execution"]["dual_drive_volatility_delever_taco_veto_enabled"], True) + self.assertIs(plan["execution"]["dual_drive_volatility_delever_taco_rebound_context_active"], False) + self.assertIs(plan["execution"]["dual_drive_volatility_delever_true_crisis_active"], False) self.assertEqual(plan["execution"]["dual_drive_volatility_delever_redirect_symbol"], "QQQ") + self.assertEqual(plan["execution"]["dual_drive_volatility_delever_removed_value"], 4500.0) + self.assertIs(plan["execution"]["dual_drive_macro_risk_governor_enabled"], True) + self.assertIs(plan["execution"]["dual_drive_macro_risk_governor_found"], True) + self.assertEqual(plan["execution"]["dual_drive_macro_risk_governor_route"], "risk_reduced") + self.assertIs(plan["execution"]["dual_drive_macro_risk_governor_active"], True) + self.assertIs(plan["execution"]["dual_drive_macro_risk_governor_applied"], True) + self.assertEqual(plan["execution"]["dual_drive_macro_risk_governor_leverage_scalar"], 0.5) + self.assertEqual(plan["execution"]["dual_drive_macro_risk_governor_risk_asset_scalar"], 0.75) + self.assertEqual(plan["execution"]["dual_drive_macro_risk_governor_removed_value"], 2500.0) + self.assertEqual(plan["execution"]["dual_drive_macro_risk_governor_redirected_to_unlevered"], 1500.0) + self.assertIs(plan["execution"]["dual_drive_crisis_defense_enabled"], True) + self.assertIs(plan["execution"]["dual_drive_crisis_defense_triggered"], True) + self.assertIs(plan["execution"]["dual_drive_crisis_defense_applied"], False) + self.assertEqual(plan["execution"]["dual_drive_crisis_defense_destination"], "BOXX") + self.assertEqual(plan["execution"]["dual_drive_crisis_defense_removed_value"], 0.0) + self.assertIs(plan["execution"]["market_regime_control_enabled"], True) + self.assertIs(plan["execution"]["market_regime_control_found"], True) + self.assertEqual(plan["execution"]["market_regime_control_route"], "risk_reduced") + self.assertIs(plan["execution"]["market_regime_control_active"], True) + self.assertEqual(plan["execution"]["market_regime_control_risk_budget_scalar"], 0.5) + self.assertEqual(plan["execution"]["market_regime_control_reason_codes"], ("macro:vix_crisis_level",)) self.assertEqual(plan["portfolio"]["market_values"]["TQQQ"], 5000.0) def test_translates_weight_decision_for_snapshot_strategy(self): diff --git a/tests/test_signal_snapshot.py b/tests/test_signal_snapshot.py index 4a839a8..b4dbf12 100644 --- a/tests/test_signal_snapshot.py +++ b/tests/test_signal_snapshot.py @@ -123,6 +123,45 @@ def test_includes_soxl_dynamic_volatility_fields(self): snapshot["indicators"]["blend_gate_volatility_delever_triggered"], True ) + def test_includes_tqqq_extended_risk_control_fields(self): + snapshot = build_signal_snapshot( + platform="longbridge", + strategy_profile="tqqq_growth_income", + execution={ + "dual_drive_volatility_delever_threshold_mode": "rolling_percentile", + "dual_drive_volatility_delever_threshold": 0.28, + "dual_drive_volatility_delever_exit_threshold": 0.24, + "dual_drive_volatility_delever_dynamic_threshold": 0.30, + "dual_drive_volatility_delever_dynamic_sample_count": 252, + "dual_drive_volatility_delever_dynamic_percentile": 0.90, + "dual_drive_volatility_delever_metric": 0.312, + "dual_drive_volatility_delever_applied": True, + "dual_drive_volatility_delever_veto_reason": "taco_rebound_context", + "dual_drive_volatility_delever_taco_veto_enabled": True, + "dual_drive_volatility_delever_removed_value": 4500.0, + "dual_drive_macro_risk_governor_applied": True, + "dual_drive_macro_risk_governor_route": "risk_reduced", + "dual_drive_crisis_defense_destination": "BOXX", + "market_regime_control_route": "risk_reduced", + "market_regime_control_reason_codes": ("macro:vix_crisis_level",), + "dual_drive_volatility_delever_redirect_symbol": "QQQM", + }, + ) + + indicators = snapshot["indicators"] + self.assertEqual(indicators["dual_drive_volatility_delever_threshold_mode"], "rolling_percentile") + self.assertEqual(indicators["dual_drive_volatility_delever_dynamic_threshold"], 0.30) + self.assertIs(indicators["dual_drive_volatility_delever_applied"], True) + self.assertEqual(indicators["dual_drive_volatility_delever_veto_reason"], "taco_rebound_context") + self.assertIs(indicators["dual_drive_volatility_delever_taco_veto_enabled"], True) + self.assertEqual(indicators["dual_drive_volatility_delever_removed_value"], 4500.0) + self.assertIs(indicators["dual_drive_macro_risk_governor_applied"], True) + self.assertEqual(indicators["dual_drive_macro_risk_governor_route"], "risk_reduced") + self.assertEqual(indicators["dual_drive_crisis_defense_destination"], "BOXX") + self.assertEqual(indicators["market_regime_control_route"], "risk_reduced") + self.assertEqual(indicators["market_regime_control_reason_codes"], ["macro:vix_crisis_level"]) + self.assertEqual(indicators["dual_drive_volatility_delever_redirect_symbol"], "QQQM") + if __name__ == "__main__": unittest.main()