Skip to content

Commit aa95c96

Browse files
authored
Support income layer start amount (#101)
1 parent 528b530 commit aa95c96

6 files changed

Lines changed: 38 additions & 0 deletions

.github/workflows/sync-cloud-run-env.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ jobs:
103103
INCOME_THRESHOLD_USD: ${{ vars.INCOME_THRESHOLD_USD }}
104104
QQQI_INCOME_RATIO: ${{ vars.QQQI_INCOME_RATIO }}
105105
INCOME_LAYER_ENABLED: ${{ vars.INCOME_LAYER_ENABLED }}
106+
INCOME_LAYER_START_USD: ${{ vars.INCOME_LAYER_START_USD }}
106107
INCOME_LAYER_MAX_RATIO: ${{ vars.INCOME_LAYER_MAX_RATIO }}
107108
RUNTIME_TARGET_ENABLED: ${{ vars.RUNTIME_TARGET_ENABLED }}
108109
EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }}
@@ -561,6 +562,7 @@ jobs:
561562
add_optional_env INCOME_THRESHOLD_USD
562563
add_optional_env QQQI_INCOME_RATIO
563564
add_optional_env INCOME_LAYER_ENABLED
565+
add_optional_env INCOME_LAYER_START_USD
564566
add_optional_env INCOME_LAYER_MAX_RATIO
565567
add_optional_env RUNTIME_TARGET_ENABLED
566568
add_optional_env EXECUTION_REPORT_GCS_URI

runtime_config_support.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class PlatformRuntimeSettings:
5454
income_threshold_usd: float | None = None
5555
qqqi_income_ratio: float | None = None
5656
income_layer_enabled: bool | None = None
57+
income_layer_start_usd: float | None = None
5758
income_layer_max_ratio: float | None = None
5859
runtime_execution_window_trading_days: int | None = None
5960
feature_snapshot_path: str | None = None
@@ -169,6 +170,7 @@ def load_platform_runtime_settings(
169170
income_threshold_usd=resolve_optional_float_env(os.environ, "INCOME_THRESHOLD_USD"),
170171
qqqi_income_ratio=_qqqi_income_ratio_env(),
171172
income_layer_enabled=_optional_bool_env("INCOME_LAYER_ENABLED"),
173+
income_layer_start_usd=_optional_non_negative_float_env("INCOME_LAYER_START_USD"),
172174
income_layer_max_ratio=_optional_ratio_env("INCOME_LAYER_MAX_RATIO"),
173175
runtime_execution_window_trading_days=_runtime_execution_window_trading_days_env(
174176
strategy_definition.profile
@@ -301,6 +303,17 @@ def _optional_ratio_env(name: str) -> float | None:
301303
return value
302304

303305

306+
def _optional_non_negative_float_env(name: str) -> float | None:
307+
value = resolve_optional_float_env(os.environ, name)
308+
if value is None:
309+
return None
310+
if not math.isfinite(value):
311+
raise ValueError(f"{name} must be finite, got {value}")
312+
if value < 0:
313+
raise ValueError(f"{name} must be non-negative, got {value}")
314+
return float(value)
315+
316+
304317
def _resolve_non_negative_float_env(name: str, *, default: float) -> float:
305318
value = resolve_optional_float_env(os.environ, name)
306319
if value is None:

strategy_runtime.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,12 @@ def load_runtime_parameters(self) -> dict[str, Any]:
144144
def _build_runtime_overrides(profile: str, runtime_settings: PlatformRuntimeSettings) -> dict[str, Any]:
145145
overrides: dict[str, Any] = {}
146146
income_layer_enabled = getattr(runtime_settings, "income_layer_enabled", None)
147+
income_layer_start_usd = getattr(runtime_settings, "income_layer_start_usd", None)
147148
income_layer_max_ratio = getattr(runtime_settings, "income_layer_max_ratio", None)
148149
if income_layer_enabled is not None:
149150
overrides["income_layer_enabled"] = income_layer_enabled
151+
if income_layer_start_usd is not None:
152+
overrides["income_layer_start_usd"] = income_layer_start_usd
150153
if income_layer_max_ratio is not None:
151154
overrides["income_layer_max_ratio"] = income_layer_max_ratio
152155
if profile == "tqqq_growth_income":

tests/test_runtime_config_support.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,13 @@ def test_reserved_cash_policy_loads_from_env(monkeypatch):
100100
def test_income_layer_overrides_load_from_env(monkeypatch):
101101
monkeypatch.setenv("RUNTIME_TARGET_JSON", _target_json("tqqq_growth_income"))
102102
monkeypatch.setenv("INCOME_LAYER_ENABLED", "false")
103+
monkeypatch.setenv("INCOME_LAYER_START_USD", "250000")
103104
monkeypatch.setenv("INCOME_LAYER_MAX_RATIO", "0.25")
104105

105106
settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
106107

107108
assert settings.income_layer_enabled is False
109+
assert settings.income_layer_start_usd == 250000.0
108110
assert settings.income_layer_max_ratio == 0.25
109111

110112

@@ -116,6 +118,14 @@ def test_invalid_income_layer_max_ratio_is_rejected(monkeypatch):
116118
load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
117119

118120

121+
def test_invalid_income_layer_start_usd_is_rejected(monkeypatch):
122+
monkeypatch.setenv("RUNTIME_TARGET_JSON", _target_json("tqqq_growth_income"))
123+
monkeypatch.setenv("INCOME_LAYER_START_USD", "-1")
124+
125+
with pytest.raises(ValueError, match="INCOME_LAYER_START_USD"):
126+
load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
127+
128+
119129
def test_runtime_target_enabled_loads_from_env(monkeypatch):
120130
monkeypatch.setenv("RUNTIME_TARGET_JSON", _target_json())
121131
monkeypatch.setenv("RUNTIME_TARGET_ENABLED", "false")

tests/test_strategy_runtime.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@ def test_runtime_execution_window_override_ignores_other_profiles():
5252
def test_income_layer_overrides_apply_to_runtime_config():
5353
settings = _runtime_settings(
5454
income_layer_enabled=False,
55+
income_layer_start_usd=250000.0,
5556
income_layer_max_ratio=0.25,
5657
)
5758

5859
assert _build_runtime_overrides("global_etf_rotation", settings) == {
5960
"income_layer_enabled": False,
61+
"income_layer_start_usd": 250000.0,
6062
"income_layer_max_ratio": 0.25,
6163
}

tests/test_sync_cloud_run_env_workflow.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ def test_sync_cloud_run_env_workflow_syncs_strategy_plugin_alert_settings():
3737
assert f"{name}: ${{{{ vars.{name} }}}}" in workflow
3838
assert f"add_optional_env {name}" in workflow
3939

40+
for name in (
41+
"INCOME_LAYER_ENABLED",
42+
"INCOME_LAYER_START_USD",
43+
"INCOME_LAYER_MAX_RATIO",
44+
):
45+
assert f"{name}: ${{{{ vars.{name} }}}}" in workflow
46+
assert f"add_optional_env {name}" in workflow
47+
4048
assert (
4149
"STRATEGY_PLUGIN_ALERT_EMAIL_SENDER_PASSWORD_SECRET_NAME: "
4250
"${{ vars.STRATEGY_PLUGIN_ALERT_EMAIL_SENDER_PASSWORD_SECRET_NAME }}"

0 commit comments

Comments
 (0)