Skip to content

Commit 2abc897

Browse files
committed
Expose IBKR profile input classifications
1 parent 7d00230 commit 2abc897

3 files changed

Lines changed: 73 additions & 5 deletions

File tree

scripts/print_strategy_profile_status.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,33 @@
1414
if candidate_str not in sys.path:
1515
sys.path.insert(0, candidate_str)
1616

17-
from strategy_registry import get_platform_profile_status_matrix # noqa: E402
17+
from strategy_registry import IBKR_PLATFORM, get_platform_profile_status_matrix # noqa: E402
18+
from us_equity_strategies.runtime_adapters import describe_platform_runtime_requirements # noqa: E402
19+
20+
21+
def build_status_rows() -> list[dict[str, object]]:
22+
rows = []
23+
for row in get_platform_profile_status_matrix():
24+
rows.append(
25+
{
26+
**row,
27+
**describe_platform_runtime_requirements(
28+
row["canonical_profile"],
29+
platform_id=IBKR_PLATFORM,
30+
),
31+
}
32+
)
33+
return rows
1834

1935

2036
def _print_table(rows: list[dict[str, object]]) -> None:
2137
headers = (
2238
"canonical_profile",
2339
"display_name",
40+
"profile_group",
41+
"input_mode",
42+
"requires_snapshot_artifacts",
43+
"requires_strategy_config_path",
2444
"eligible",
2545
"enabled",
2646
"is_default",
@@ -38,7 +58,7 @@ def _print_table(rows: list[dict[str, object]]) -> None:
3858

3959

4060
def main() -> int:
41-
rows = get_platform_profile_status_matrix()
61+
rows = build_status_rows()
4262
if "--json" in sys.argv:
4363
print(json.dumps(rows, indent=2, sort_keys=True))
4464
return 0

scripts/print_strategy_switch_env_plan.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
resolve_strategy_metadata,
2424
)
2525
from us_equity_strategies import get_strategy_catalog # noqa: E402
26+
from us_equity_strategies.runtime_adapters import describe_platform_runtime_requirements # noqa: E402
2627

2728

2829
def build_switch_plan(profile: str) -> dict[str, object]:
@@ -36,7 +37,12 @@ def build_switch_plan(profile: str) -> dict[str, object]:
3637
definition.profile,
3738
repo_root=ROOT,
3839
)
39-
requires_feature_snapshot = "feature_snapshot" in definition.required_inputs
40+
runtime_requirements = describe_platform_runtime_requirements(
41+
definition.profile,
42+
platform_id=IBKR_PLATFORM,
43+
)
44+
requires_feature_snapshot = bool(runtime_requirements["requires_snapshot_artifacts"])
45+
requires_strategy_config_path = bool(runtime_requirements["requires_strategy_config_path"])
4046

4147
set_env: dict[str, str] = {"STRATEGY_PROFILE": definition.profile}
4248
keep_env = [
@@ -52,7 +58,7 @@ def build_switch_plan(profile: str) -> dict[str, object]:
5258
if requires_feature_snapshot:
5359
set_env["IBKR_FEATURE_SNAPSHOT_PATH"] = "<required>"
5460
set_env["IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH"] = "<required>"
55-
if artifact_paths.bundled_config_path is not None:
61+
if requires_strategy_config_path and artifact_paths.bundled_config_path is not None:
5662
set_env["IBKR_STRATEGY_CONFIG_PATH"] = str(artifact_paths.bundled_config_path)
5763
else:
5864
remove_if_present.append("IBKR_STRATEGY_CONFIG_PATH")
@@ -90,6 +96,7 @@ def build_switch_plan(profile: str) -> dict[str, object]:
9096
"display_name": metadata.display_name,
9197
"eligible": status_row["eligible"],
9298
"enabled": status_row["enabled"],
99+
**runtime_requirements,
93100
"required_inputs": sorted(definition.required_inputs),
94101
"target_mode": definition.target_mode,
95102
"set_env": set_env,
@@ -105,7 +112,11 @@ def _print_plan(plan: dict[str, object]) -> None:
105112
print(f"platform: {plan['platform']}")
106113
print(f"profile: {plan['canonical_profile']} ({plan['display_name']})")
107114
print(f"eligible: {plan['eligible']} enabled: {plan['enabled']}")
115+
print(f"profile_group: {plan['profile_group']}")
108116
print(f"required_inputs: {', '.join(plan['required_inputs'])}")
117+
print(f"input_mode: {plan['input_mode']}")
118+
print(f"requires_snapshot_artifacts: {plan['requires_snapshot_artifacts']}")
119+
print(f"requires_strategy_config_path: {plan['requires_strategy_config_path']}")
109120
print(f"target_mode: {plan['target_mode']}")
110121
print("\nset_env:")
111122
for key, value in plan["set_env"].items():

tests/test_runtime_config_support.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,33 @@ def test_print_strategy_profile_status_json_matches_registry():
251251
text=True,
252252
)
253253

254-
assert json.loads(result.stdout) == get_platform_profile_status_matrix()
254+
rows = json.loads(result.stdout)
255+
assert [
256+
{
257+
key: row[key]
258+
for key in (
259+
"canonical_profile",
260+
"display_name",
261+
"domain",
262+
"eligible",
263+
"enabled",
264+
"is_default",
265+
"is_rollback",
266+
"platform",
267+
)
268+
}
269+
for row in rows
270+
] == get_platform_profile_status_matrix()
271+
by_profile = {row["canonical_profile"]: row for row in rows}
272+
assert by_profile["global_etf_rotation"]["profile_group"] == "direct_runtime_inputs"
273+
assert by_profile["global_etf_rotation"]["input_mode"] == "market_history"
274+
assert by_profile["global_etf_rotation"]["requires_snapshot_artifacts"] is False
275+
assert by_profile["global_etf_rotation"]["requires_strategy_config_path"] is False
276+
assert by_profile["qqq_tech_enhancement"]["profile_group"] == "snapshot_backed"
277+
assert by_profile["qqq_tech_enhancement"]["input_mode"] == "feature_snapshot"
278+
assert by_profile["qqq_tech_enhancement"]["requires_snapshot_artifacts"] is True
279+
assert by_profile["qqq_tech_enhancement"]["requires_strategy_config_path"] is True
280+
assert by_profile["russell_1000_multi_factor_defensive"]["requires_strategy_config_path"] is False
255281

256282

257283
def test_print_strategy_profile_status_table_contains_expected_headers():
@@ -264,6 +290,9 @@ def test_print_strategy_profile_status_table_contains_expected_headers():
264290

265291
assert "canonical_profile" in result.stdout
266292
assert "display_name" in result.stdout
293+
assert "profile_group" in result.stdout
294+
assert "input_mode" in result.stdout
295+
assert "requires_snapshot_artifacts" in result.stdout
267296
assert "global_etf_rotation" in result.stdout
268297
assert "QQQ Tech Enhancement" in result.stdout
269298
assert "TQQQ Growth Income" in result.stdout
@@ -282,6 +311,10 @@ def test_print_strategy_switch_env_plan_for_tqqq_growth_income():
282311
assert plan["canonical_profile"] == "tqqq_growth_income"
283312
assert plan["eligible"] is True
284313
assert plan["enabled"] is True
314+
assert plan["profile_group"] == "direct_runtime_inputs"
315+
assert plan["input_mode"] == "benchmark_history+portfolio_snapshot"
316+
assert plan["requires_snapshot_artifacts"] is False
317+
assert plan["requires_strategy_config_path"] is False
285318
assert plan["set_env"]["STRATEGY_PROFILE"] == "tqqq_growth_income"
286319
assert "ACCOUNT_GROUP" in plan["keep_env"]
287320
assert "IBKR_FEATURE_SNAPSHOT_PATH" in plan["remove_if_present"]
@@ -297,6 +330,10 @@ def test_print_strategy_switch_env_plan_for_feature_snapshot_profile():
297330

298331
plan = json.loads(result.stdout)
299332
assert plan["canonical_profile"] == "qqq_tech_enhancement"
333+
assert plan["profile_group"] == "snapshot_backed"
334+
assert plan["input_mode"] == "feature_snapshot"
335+
assert plan["requires_snapshot_artifacts"] is True
336+
assert plan["requires_strategy_config_path"] is True
300337
assert plan["set_env"]["IBKR_FEATURE_SNAPSHOT_PATH"] == "<required>"
301338
assert plan["set_env"]["IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH"] == "<required>"
302339
assert plan["set_env"]["IBKR_STRATEGY_CONFIG_PATH"].endswith(

0 commit comments

Comments
 (0)