|
64 | 64 | "ibkr": "IBKR_CASH_ONLY_EXECUTION", |
65 | 65 | "firstrade": "FIRSTRADE_CASH_ONLY_EXECUTION", |
66 | 66 | } |
| 67 | +PLATFORM_FEATURE_SNAPSHOT_VARIABLES = { |
| 68 | + "schwab": ("SCHWAB_FEATURE_SNAPSHOT_PATH", "SCHWAB_FEATURE_SNAPSHOT_MANIFEST_PATH"), |
| 69 | + "longbridge": ( |
| 70 | + "LONGBRIDGE_FEATURE_SNAPSHOT_PATH", |
| 71 | + "LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH", |
| 72 | + ), |
| 73 | + "ibkr": ("IBKR_FEATURE_SNAPSHOT_PATH", "IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH"), |
| 74 | + "firstrade": ( |
| 75 | + "FIRSTRADE_FEATURE_SNAPSHOT_PATH", |
| 76 | + "FIRSTRADE_FEATURE_SNAPSHOT_MANIFEST_PATH", |
| 77 | + ), |
| 78 | +} |
67 | 79 | INCOME_LAYER_VARIABLES = ( |
68 | 80 | "INCOME_LAYER_ENABLED", |
69 | 81 | "INCOME_LAYER_START_USD", |
@@ -747,6 +759,57 @@ def _market_plan_for_strategy(strategy_profile: str) -> dict[str, str]: |
747 | 759 | return market |
748 | 760 |
|
749 | 761 |
|
| 762 | +def _feature_snapshot_extra_variables( |
| 763 | + platform: str, |
| 764 | + strategy_profile: str, |
| 765 | + extra_variables: dict[str, Any], |
| 766 | +) -> dict[str, str]: |
| 767 | + variable_names = PLATFORM_FEATURE_SNAPSHOT_VARIABLES.get(platform) |
| 768 | + if not variable_names: |
| 769 | + return {} |
| 770 | + snapshot_variable, manifest_variable = variable_names |
| 771 | + |
| 772 | + config = _load_platform_config() |
| 773 | + strategies = config.get("strategies") |
| 774 | + strategy = strategies.get(strategy_profile) if isinstance(strategies, dict) else None |
| 775 | + if not isinstance(strategy, dict): |
| 776 | + raise ValueError(f"strategy {strategy_profile!r} is missing from the runtime artifact catalog") |
| 777 | + runtime_artifacts = strategy.get("runtime_artifacts") or {} |
| 778 | + if not isinstance(runtime_artifacts, dict): |
| 779 | + raise ValueError(f"strategy {strategy_profile!r} runtime_artifacts must be an object") |
| 780 | + feature_snapshot = runtime_artifacts.get("feature_snapshot") or {} |
| 781 | + if not isinstance(feature_snapshot, dict): |
| 782 | + raise ValueError( |
| 783 | + f"strategy {strategy_profile!r} runtime_artifacts.feature_snapshot must be an object" |
| 784 | + ) |
| 785 | + |
| 786 | + explicit = snapshot_variable in extra_variables or manifest_variable in extra_variables |
| 787 | + if explicit: |
| 788 | + snapshot_path = extra_variables.get(snapshot_variable) |
| 789 | + manifest_path = extra_variables.get(manifest_variable) |
| 790 | + else: |
| 791 | + snapshot_path = feature_snapshot.get("path") |
| 792 | + manifest_path = feature_snapshot.get("manifest_path") |
| 793 | + snapshot_path = snapshot_path.strip() if isinstance(snapshot_path, str) else "" |
| 794 | + manifest_path = manifest_path.strip() if isinstance(manifest_path, str) else "" |
| 795 | + if explicit and not feature_snapshot and (snapshot_path or manifest_path): |
| 796 | + raise ValueError( |
| 797 | + f"strategy {strategy_profile!r} does not accept feature snapshot artifacts" |
| 798 | + ) |
| 799 | + if bool(snapshot_path) != bool(manifest_path): |
| 800 | + raise ValueError( |
| 801 | + f"strategy {strategy_profile!r} feature snapshot path and manifest path must be configured together" |
| 802 | + ) |
| 803 | + if feature_snapshot.get("required") is True and not (snapshot_path and manifest_path): |
| 804 | + raise ValueError( |
| 805 | + f"strategy {strategy_profile!r} requires feature snapshot path and manifest path" |
| 806 | + ) |
| 807 | + return { |
| 808 | + snapshot_variable: snapshot_path, |
| 809 | + manifest_variable: manifest_path, |
| 810 | + } |
| 811 | + |
| 812 | + |
750 | 813 | def _build_runtime_target(args: argparse.Namespace) -> dict[str, Any]: |
751 | 814 | platform = _normalize_platform(args.platform) |
752 | 815 | target_name = _normalize_target_name(args.target_name) |
@@ -903,6 +966,13 @@ def build_switch_target(args: argparse.Namespace) -> dict[str, Any]: |
903 | 966 | _reject_direct_dca_extra_variables(extra_variables) |
904 | 967 | _reject_direct_ibit_zscore_exit_extra_variables(extra_variables) |
905 | 968 | _reject_research_only_extra_variables(extra_variables) |
| 969 | + extra_variables.update( |
| 970 | + _feature_snapshot_extra_variables( |
| 971 | + platform, |
| 972 | + runtime_target["strategy_profile"], |
| 973 | + extra_variables, |
| 974 | + ) |
| 975 | + ) |
906 | 976 |
|
907 | 977 | if args.set_platform_dry_run_variable: |
908 | 978 | extra_variables[PLATFORM_DRY_RUN_VARIABLES[platform]] = env_string(runtime_target["dry_run_only"]) |
|
0 commit comments