Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ authors = [
dependencies = [
"firstrade==0.0.39",
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@b846c9d777a450e95d23c264853997d671f47dd9",
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@d08492ea4b4055515606ae386e59a31a943a7fec",
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@361338f60900182e3be535cd5fd2be2b9a07b422",
"google-cloud-storage",
"requests",
]
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ flask
gunicorn
firstrade==0.0.39
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@b846c9d777a450e95d23c264853997d671f47dd9
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@22689494e922a8b18349562edcc6389d2faaed8f
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@361338f60900182e3be535cd5fd2be2b9a07b422
google-cloud-storage
requests
pytest
32 changes: 32 additions & 0 deletions runtime_config_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class PlatformRuntimeSettings:
income_layer_enabled: bool | None = None
income_layer_start_usd: float | None = None
income_layer_max_ratio: float | None = None
dca_mode: str | None = None
dca_base_investment_usd: float | None = None
runtime_execution_window_trading_days: int | None = None
feature_snapshot_path: str | None = None
feature_snapshot_manifest_path: str | None = None
Expand Down Expand Up @@ -172,6 +174,8 @@ def load_platform_runtime_settings(
income_layer_enabled=_optional_bool_env("INCOME_LAYER_ENABLED"),
income_layer_start_usd=_optional_non_negative_float_env("INCOME_LAYER_START_USD"),
income_layer_max_ratio=_optional_ratio_env("INCOME_LAYER_MAX_RATIO"),
dca_mode=_optional_dca_mode_env("DCA_MODE"),
dca_base_investment_usd=_optional_positive_float_env("DCA_BASE_INVESTMENT_USD"),
Comment on lines +177 to +178

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add DCA vars to Cloud Run env sync

When DCA settings are configured through this repo's Cloud Run deploy workflow, these new variables won't be propagated: I checked .github/workflows/sync-cloud-run-env.yml, and it only imports GitHub vars that are listed in the job env: block and then passed through add_optional_env; DCA_MODE and DCA_BASE_INVESTMENT_USD are missing from both places. As a result, setting those repository variables for nasdaq_sp500_smart_dca or ibit_smart_dca leaves Cloud Run running with the strategy defaults/old environment after deploy, so the new runtime controls added here do not take effect in the managed deployment path.

Useful? React with 👍 / 👎.

runtime_execution_window_trading_days=_runtime_execution_window_trading_days_env(
strategy_definition.profile
),
Expand Down Expand Up @@ -314,6 +318,34 @@ def _optional_non_negative_float_env(name: str) -> float | None:
return float(value)


def _optional_positive_float_env(name: str) -> float | None:
value = resolve_optional_float_env(os.environ, name)
if value is None:
return None
if not math.isfinite(value):
raise ValueError(f"{name} must be finite, got {value}")
if value <= 0:
raise ValueError(f"{name} must be positive, got {value}")
return float(value)


def _optional_dca_mode_env(name: str) -> str | None:
raw_value = os.getenv(name)
if raw_value is None or str(raw_value).strip() == "":
return None
value = str(raw_value).strip().lower()
aliases = {
"ordinary": "fixed",
"ordinary_dca": "fixed",
"fixed_dca": "fixed",
"smart_dca": "smart",
}
mode = aliases.get(value, value)
if mode not in {"fixed", "smart"}:
raise ValueError(f"{name} must be fixed or smart, got {raw_value!r}")
return mode


def _resolve_non_negative_float_env(name: str, *, default: float) -> float:
value = resolve_optional_float_env(os.environ, name)
if value is None:
Expand Down
9 changes: 9 additions & 0 deletions strategy_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
)

_FEATURE_SNAPSHOT_INPUT = "feature_snapshot"
DCA_PROFILES = frozenset({"nasdaq_sp500_smart_dca", "ibit_smart_dca"})


@dataclass(frozen=True)
Expand Down Expand Up @@ -152,6 +153,14 @@ def _build_runtime_overrides(profile: str, runtime_settings: PlatformRuntimeSett
overrides["income_layer_start_usd"] = income_layer_start_usd
if income_layer_max_ratio is not None:
overrides["income_layer_max_ratio"] = income_layer_max_ratio
if profile in DCA_PROFILES:
dca_mode = getattr(runtime_settings, "dca_mode", None)
dca_base_investment_usd = getattr(runtime_settings, "dca_base_investment_usd", None)
if dca_mode is not None:
overrides["investment_amount_mode"] = "fixed"
overrides["smart_multiplier_enabled"] = dca_mode == "smart"
if dca_base_investment_usd is not None:
overrides["base_investment_usd"] = dca_base_investment_usd
if profile == "tqqq_growth_income":
if runtime_settings.income_threshold_usd is not None:
overrides["income_threshold_usd"] = runtime_settings.income_threshold_usd
Expand Down
20 changes: 20 additions & 0 deletions tests/test_strategy_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,23 @@ def test_income_layer_overrides_apply_to_runtime_config():
"income_layer_start_usd": 250000.0,
"income_layer_max_ratio": 0.25,
}


def test_dca_overrides_apply_to_runtime_config():
settings = _runtime_settings(
strategy_profile="nasdaq_sp500_smart_dca",
dca_mode="smart",
dca_base_investment_usd=500.0,
)

assert _build_runtime_overrides("nasdaq_sp500_smart_dca", settings) == {
"investment_amount_mode": "fixed",
"smart_multiplier_enabled": True,
"base_investment_usd": 500.0,
}


def test_dca_overrides_ignore_non_dca_profiles():
settings = _runtime_settings(dca_mode="smart", dca_base_investment_usd=500.0)

assert _build_runtime_overrides("global_etf_rotation", settings) == {}