@@ -394,25 +394,6 @@ def _normalize_option_recipe(value: str, *, field_name: str) -> str:
394394 return text
395395
396396
397- def _normalize_ibit_zscore_exit_mode (value : str ) -> str :
398- mode = str (value or "" ).strip ().lower ()
399- aliases = {
400- "off" : "disabled" ,
401- "none" : "disabled" ,
402- "false" : "disabled" ,
403- "0" : "disabled" ,
404- "disable" : "disabled" ,
405- "enabled" : "live" ,
406- "shadow" : "paper" ,
407- "dry_run" : "paper" ,
408- "dry-run" : "paper" ,
409- }
410- mode = aliases .get (mode , mode )
411- if mode not in {"disabled" , "paper" , "live" }:
412- raise ValueError ("ibit_zscore_exit_mode must be disabled, paper, or live" )
413- return mode
414-
415-
416397def _normalize_symbol_text (value : str , * , field_name : str ) -> str :
417398 text = str (value or "" ).strip ().upper ().removesuffix (".US" )
418399 if not text or not re .fullmatch (r"[A-Z0-9.-]{1,12}" , text ):
@@ -573,6 +554,18 @@ def _dca_extra_variables(
573554 return extra_variables
574555
575556
557+ def _effective_dca_mode (
558+ args : argparse .Namespace ,
559+ strategy_profile : str ,
560+ controls : dict [str , Any ] | None = None ,
561+ ) -> str :
562+ if strategy_profile not in DCA_PROFILES :
563+ return ""
564+ controls = dict (controls or {})
565+ raw_mode = args .dca_mode if str (args .dca_mode or "" ).strip () else controls .get (DCA_MODE_CONTROL_FIELD , "" )
566+ return _normalize_dca_mode (raw_mode ) if str (raw_mode or "" ).strip () else "fixed"
567+
568+
576569def _reject_direct_dca_extra_variables (extra_variables : dict [str , Any ]) -> None :
577570 provided = [
578571 variable
@@ -594,7 +587,10 @@ def _reject_direct_ibit_zscore_exit_extra_variables(extra_variables: dict[str, A
594587 ]
595588 if provided :
596589 names = ", " .join (provided )
597- raise ValueError (f"use ibit_zscore_exit_* control fields instead of extra_variables_json for { names } " )
590+ raise ValueError (
591+ "IBIT_ZSCORE_EXIT variables are derived from ibit_smart_dca smart DCA mode; "
592+ f"do not set them directly: { names } "
593+ )
598594
599595
600596def _reject_research_only_extra_variables (extra_variables : dict [str , Any ]) -> None :
@@ -620,81 +616,36 @@ def _ibit_zscore_exit_extra_variables(
620616 args : argparse .Namespace ,
621617 strategy_profile : str ,
622618 plugin_mode : str ,
623- controls : dict [ str , Any ] | None = None ,
619+ dca_mode : str ,
624620) -> dict [str , Any ]:
625- controls = dict (controls or {})
626- cli_mode = str (getattr (args , "ibit_zscore_exit_mode" , "" ) or "" ).strip ()
627- mode_value = cli_mode or controls .get ("ibit_zscore_exit_mode" , "" )
628- has_controls = bool (mode_value ) or any (
629- str (controls .get (field , "" ) or "" ).strip ()
630- for field in IBIT_ZSCORE_EXIT_CONTROL_FIELDS
631- if field != "ibit_zscore_exit_mode"
632- )
633- has_cli_controls = any (
634- str (getattr (args , attr , "" ) or "" ).strip ()
635- for attr in (
636- "ibit_zscore_exit_parking_symbol" ,
637- "ibit_zscore_exit_risk_reduced_exposure" ,
638- "ibit_zscore_exit_risk_off_exposure" ,
639- "ibit_zscore_exit_allow_outside_execution_window" ,
640- )
641- )
642621 is_ibit_profile = strategy_profile == IBIT_ZSCORE_EXIT_STRATEGY_PROFILE
643622 if not is_ibit_profile :
644- if has_controls or has_cli_controls :
645- raise ValueError ("IBIT Z-Score exit settings are only supported for ibit_smart_dca" )
646623 return {variable : "" for variable in IBIT_ZSCORE_EXIT_RUNTIME_VARIABLES }
647624
648- if not mode_value :
649- mode = "disabled" if plugin_mode == "none" else "live"
650- else :
651- mode = _normalize_ibit_zscore_exit_mode (mode_value )
652- if plugin_mode == "none" and mode != "disabled" :
653- raise ValueError ("IBIT Z-Score exit live/paper modes require plugin_mode auto or custom" )
654-
655- parking_symbol = (
656- getattr (args , "ibit_zscore_exit_parking_symbol" , "" )
657- or controls .get ("ibit_zscore_exit_parking_symbol" )
658- or "BOXX"
659- )
660- risk_reduced_exposure = (
661- getattr (args , "ibit_zscore_exit_risk_reduced_exposure" , "" )
662- or controls .get ("ibit_zscore_exit_risk_reduced_exposure" )
663- or "0.50"
664- )
665- risk_off_exposure = (
666- getattr (args , "ibit_zscore_exit_risk_off_exposure" , "" )
667- or controls .get ("ibit_zscore_exit_risk_off_exposure" )
668- or "0.25"
669- )
670- allow_outside_window = (
671- getattr (args , "ibit_zscore_exit_allow_outside_execution_window" , "" )
672- or controls .get ("ibit_zscore_exit_allow_outside_execution_window" )
673- or "true"
674- )
625+ mode = "live" if plugin_mode != "none" and dca_mode == "smart" else "disabled"
675626 return {
676627 IBIT_ZSCORE_EXIT_ENABLED_VARIABLE : "true" if mode != "disabled" else "false" ,
677628 IBIT_ZSCORE_EXIT_MODE_VARIABLE : "paper" if mode == "disabled" else mode ,
678629 IBIT_ZSCORE_EXIT_PARKING_SYMBOL_VARIABLE : _normalize_symbol_text (
679- parking_symbol ,
630+ "BOXX" ,
680631 field_name = "ibit_zscore_exit_parking_symbol" ,
681632 ),
682633 IBIT_ZSCORE_EXIT_RISK_REDUCED_EXPOSURE_VARIABLE : _normalize_ratio_decimal (
683- risk_reduced_exposure ,
634+ "0.50" ,
684635 field_name = "ibit_zscore_exit_risk_reduced_exposure" ,
685636 ),
686637 IBIT_ZSCORE_EXIT_RISK_OFF_EXPOSURE_VARIABLE : _normalize_ratio_decimal (
687- risk_off_exposure ,
638+ "0.25" ,
688639 field_name = "ibit_zscore_exit_risk_off_exposure" ,
689640 ),
690641 IBIT_ZSCORE_EXIT_ALLOW_OUTSIDE_WINDOW_VARIABLE : _normalize_optional_bool_text (
691- allow_outside_window ,
642+ "true" ,
692643 field_name = "ibit_zscore_exit_allow_outside_execution_window" ,
693644 ),
694645 }
695646
696647
697- def _auto_plugin_mounts (strategy_profile : str , artifact_bucket_uri : str ) -> list [dict [str , Any ]]:
648+ def _auto_plugin_mounts (strategy_profile : str , artifact_bucket_uri : str , dca_mode : str = "" ) -> list [dict [str , Any ]]:
698649 prefix = artifact_bucket_uri .rstrip ("/" )
699650 mounts : list [dict [str , Any ]] = []
700651 if strategy_profile in MARKET_REGIME_CONTROL_PROFILES :
@@ -711,7 +662,7 @@ def _auto_plugin_mounts(strategy_profile: str, artifact_bucket_uri: str) -> list
711662 "expected_schema_version" : "market_regime_control.v1" ,
712663 }
713664 )
714- if strategy_profile == IBIT_ZSCORE_EXIT_STRATEGY_PROFILE :
665+ if strategy_profile == IBIT_ZSCORE_EXIT_STRATEGY_PROFILE and dca_mode == "smart" :
715666 mounts .append (
716667 {
717668 "strategy" : strategy_profile ,
@@ -743,12 +694,12 @@ def _custom_plugin_mounts(raw_json: str) -> list[dict[str, Any]]:
743694 return [dict (item ) for item in payload ]
744695
745696
746- def _plugin_mounts (args : argparse .Namespace , strategy_profile : str ) -> list [dict [str , Any ]]:
697+ def _plugin_mounts (args : argparse .Namespace , strategy_profile : str , dca_mode : str = "" ) -> list [dict [str , Any ]]:
747698 mode = str (args .plugin_mode or "auto" ).strip ().lower ()
748699 if mode == "none" :
749700 return []
750701 if mode == "auto" :
751- return _auto_plugin_mounts (strategy_profile , args .artifact_bucket_uri )
702+ return _auto_plugin_mounts (strategy_profile , args .artifact_bucket_uri , dca_mode )
752703 if mode == "custom" :
753704 return _custom_plugin_mounts (args .custom_plugin_mounts_json )
754705 raise ValueError (f"unsupported plugin_mode { args .plugin_mode !r} " )
@@ -935,13 +886,14 @@ def build_switch_target(args: argparse.Namespace) -> dict[str, Any]:
935886 raise ValueError ("variable_scope must be repository or environment" )
936887 github_environment = args .github_environment or _default_github_environment (platform , target_name , variable_scope )
937888 runtime_target = _build_runtime_target (args )
938- mounts = _plugin_mounts (args , runtime_target ["strategy_profile" ])
939- runtime_target ["scheduler" ] = _scheduler_plan_for_strategy (runtime_target ["strategy_profile" ], mounts )
940- mounts_variable = f"{ SUPPORTED_PLATFORMS [platform ]['plugin_mounts_prefix' ]} STRATEGY_PLUGIN_MOUNTS_JSON"
941889 extra_variables = _parse_extra_variables (args .extra_variable , args .extra_variables_json )
942890 cash_only_controls = _extract_cash_only_control_fields (extra_variables )
943891 dca_controls = _extract_dca_control_fields (extra_variables )
944- ibit_zscore_exit_controls = _extract_ibit_zscore_exit_control_fields (extra_variables )
892+ _extract_ibit_zscore_exit_control_fields (extra_variables )
893+ effective_dca_mode = _effective_dca_mode (args , runtime_target ["strategy_profile" ], dca_controls )
894+ mounts = _plugin_mounts (args , runtime_target ["strategy_profile" ], effective_dca_mode )
895+ runtime_target ["scheduler" ] = _scheduler_plan_for_strategy (runtime_target ["strategy_profile" ], mounts )
896+ mounts_variable = f"{ SUPPORTED_PLATFORMS [platform ]['plugin_mounts_prefix' ]} STRATEGY_PLUGIN_MOUNTS_JSON"
945897 if cash_only_controls .get (CASH_ONLY_EXECUTION_CONTROL_FIELD ):
946898 args .cash_only_execution_mode = str (cash_only_controls [CASH_ONLY_EXECUTION_CONTROL_FIELD ]).strip ().lower ()
947899 _reject_direct_dca_extra_variables (extra_variables )
@@ -966,7 +918,7 @@ def build_switch_target(args: argparse.Namespace) -> dict[str, Any]:
966918 args ,
967919 runtime_target ["strategy_profile" ],
968920 str (args .plugin_mode or "auto" ).strip ().lower (),
969- ibit_zscore_exit_controls ,
921+ effective_dca_mode ,
970922 )
971923 )
972924
@@ -1040,11 +992,6 @@ def build_parser() -> argparse.ArgumentParser:
1040992 parser .add_argument ("--option-overlay-mode" , choices = sorted (OPTION_OVERLAY_MODES ), default = "current" )
1041993 parser .add_argument ("--dca-mode" , default = "" )
1042994 parser .add_argument ("--dca-base-investment-usd" , default = "" )
1043- parser .add_argument ("--ibit-zscore-exit-mode" , choices = ("disabled" , "paper" , "live" ), default = "" )
1044- parser .add_argument ("--ibit-zscore-exit-parking-symbol" , default = "" )
1045- parser .add_argument ("--ibit-zscore-exit-risk-reduced-exposure" , default = "" )
1046- parser .add_argument ("--ibit-zscore-exit-risk-off-exposure" , default = "" )
1047- parser .add_argument ("--ibit-zscore-exit-allow-outside-execution-window" , default = "" )
1048995 parser .add_argument ("--existing-service-targets-json-file" , default = "" )
1049996 parser .add_argument ("--no-platform-dry-run-variable" , dest = "set_platform_dry_run_variable" , action = "store_false" )
1050997 parser .set_defaults (set_platform_dry_run_variable = True )
0 commit comments