@@ -58,6 +58,12 @@ class PlatformRuntimeSettings:
5858 income_layer_max_ratio : float | None = None
5959 dca_mode : str | None = None
6060 dca_base_investment_usd : float | None = None
61+ ibit_zscore_exit_enabled : bool | None = None
62+ ibit_zscore_exit_mode : str | None = None
63+ ibit_zscore_exit_parking_symbol : str | None = None
64+ ibit_zscore_exit_risk_reduced_exposure : float | None = None
65+ ibit_zscore_exit_risk_off_exposure : float | None = None
66+ ibit_zscore_exit_allow_outside_execution_window : bool | None = None
6167 runtime_execution_window_trading_days : int | None = None
6268 market_signal_handoff_index_uri : str | None = None
6369 market_signal_handoff_manifest_uri : str | None = None
@@ -186,6 +192,16 @@ def load_platform_runtime_settings(
186192 income_layer_max_ratio = _optional_ratio_env ("INCOME_LAYER_MAX_RATIO" ),
187193 dca_mode = _optional_dca_mode_env ("DCA_MODE" ),
188194 dca_base_investment_usd = _optional_positive_float_env ("DCA_BASE_INVESTMENT_USD" ),
195+ ibit_zscore_exit_enabled = _optional_bool_env ("IBIT_ZSCORE_EXIT_ENABLED" ),
196+ ibit_zscore_exit_mode = _optional_ibit_zscore_exit_mode_env ("IBIT_ZSCORE_EXIT_MODE" ),
197+ ibit_zscore_exit_parking_symbol = _optional_symbol_env ("IBIT_ZSCORE_EXIT_PARKING_SYMBOL" ),
198+ ibit_zscore_exit_risk_reduced_exposure = _optional_ratio_env (
199+ "IBIT_ZSCORE_EXIT_RISK_REDUCED_EXPOSURE"
200+ ),
201+ ibit_zscore_exit_risk_off_exposure = _optional_ratio_env ("IBIT_ZSCORE_EXIT_RISK_OFF_EXPOSURE" ),
202+ ibit_zscore_exit_allow_outside_execution_window = _optional_bool_env (
203+ "IBIT_ZSCORE_EXIT_ALLOW_OUTSIDE_EXECUTION_WINDOW"
204+ ),
189205 runtime_execution_window_trading_days = _runtime_execution_window_trading_days_env (
190206 strategy_definition .profile
191207 ),
@@ -407,6 +423,37 @@ def _optional_dca_mode_env(name: str) -> str | None:
407423 return mode
408424
409425
426+ def _optional_ibit_zscore_exit_mode_env (name : str ) -> str | None :
427+ raw_value = os .getenv (name )
428+ if raw_value is None or str (raw_value ).strip () == "" :
429+ return None
430+ value = str (raw_value ).strip ().lower ()
431+ aliases = {
432+ "off" : "disabled" ,
433+ "none" : "disabled" ,
434+ "false" : "disabled" ,
435+ "disable" : "disabled" ,
436+ "enabled" : "live" ,
437+ "shadow" : "paper" ,
438+ "dry_run" : "paper" ,
439+ "dry-run" : "paper" ,
440+ }
441+ mode = aliases .get (value , value )
442+ if mode not in {"disabled" , "paper" , "live" }:
443+ raise ValueError (f"{ name } must be disabled, paper, or live, got { raw_value !r} " )
444+ return mode
445+
446+
447+ def _optional_symbol_env (name : str ) -> str | None :
448+ raw_value = os .getenv (name )
449+ if raw_value is None or str (raw_value ).strip () == "" :
450+ return None
451+ value = str (raw_value ).strip ().upper ()
452+ if len (value ) > 16 or not value .replace ("." , "" ).replace ("-" , "" ).isalnum ():
453+ raise ValueError (f"{ name } must be a symbol" )
454+ return value
455+
456+
410457def _resolve_non_negative_float_env (name : str , * , default : float ) -> float :
411458 value = resolve_optional_float_env (os .environ , name )
412459 if value is None :
0 commit comments