Skip to content

Commit 3eb8e8d

Browse files
authored
Add DCA runtime settings (#105)
1 parent d729cc0 commit 3eb8e8d

5 files changed

Lines changed: 63 additions & 2 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ authors = [
1515
dependencies = [
1616
"firstrade==0.0.39",
1717
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@b846c9d777a450e95d23c264853997d671f47dd9",
18-
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@d08492ea4b4055515606ae386e59a31a943a7fec",
18+
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@361338f60900182e3be535cd5fd2be2b9a07b422",
1919
"google-cloud-storage",
2020
"requests",
2121
]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ flask
22
gunicorn
33
firstrade==0.0.39
44
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@b846c9d777a450e95d23c264853997d671f47dd9
5-
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@22689494e922a8b18349562edcc6389d2faaed8f
5+
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@361338f60900182e3be535cd5fd2be2b9a07b422
66
google-cloud-storage
77
requests
88
pytest

runtime_config_support.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class PlatformRuntimeSettings:
5656
income_layer_enabled: bool | None = None
5757
income_layer_start_usd: float | None = None
5858
income_layer_max_ratio: float | None = None
59+
dca_mode: str | None = None
60+
dca_base_investment_usd: float | None = None
5961
runtime_execution_window_trading_days: int | None = None
6062
feature_snapshot_path: str | None = None
6163
feature_snapshot_manifest_path: str | None = None
@@ -172,6 +174,8 @@ def load_platform_runtime_settings(
172174
income_layer_enabled=_optional_bool_env("INCOME_LAYER_ENABLED"),
173175
income_layer_start_usd=_optional_non_negative_float_env("INCOME_LAYER_START_USD"),
174176
income_layer_max_ratio=_optional_ratio_env("INCOME_LAYER_MAX_RATIO"),
177+
dca_mode=_optional_dca_mode_env("DCA_MODE"),
178+
dca_base_investment_usd=_optional_positive_float_env("DCA_BASE_INVESTMENT_USD"),
175179
runtime_execution_window_trading_days=_runtime_execution_window_trading_days_env(
176180
strategy_definition.profile
177181
),
@@ -314,6 +318,34 @@ def _optional_non_negative_float_env(name: str) -> float | None:
314318
return float(value)
315319

316320

321+
def _optional_positive_float_env(name: str) -> float | None:
322+
value = resolve_optional_float_env(os.environ, name)
323+
if value is None:
324+
return None
325+
if not math.isfinite(value):
326+
raise ValueError(f"{name} must be finite, got {value}")
327+
if value <= 0:
328+
raise ValueError(f"{name} must be positive, got {value}")
329+
return float(value)
330+
331+
332+
def _optional_dca_mode_env(name: str) -> str | None:
333+
raw_value = os.getenv(name)
334+
if raw_value is None or str(raw_value).strip() == "":
335+
return None
336+
value = str(raw_value).strip().lower()
337+
aliases = {
338+
"ordinary": "fixed",
339+
"ordinary_dca": "fixed",
340+
"fixed_dca": "fixed",
341+
"smart_dca": "smart",
342+
}
343+
mode = aliases.get(value, value)
344+
if mode not in {"fixed", "smart"}:
345+
raise ValueError(f"{name} must be fixed or smart, got {raw_value!r}")
346+
return mode
347+
348+
317349
def _resolve_non_negative_float_env(name: str, *, default: float) -> float:
318350
value = resolve_optional_float_env(os.environ, name)
319351
if value is None:

strategy_runtime.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
)
2525

2626
_FEATURE_SNAPSHOT_INPUT = "feature_snapshot"
27+
DCA_PROFILES = frozenset({"nasdaq_sp500_smart_dca", "ibit_smart_dca"})
2728

2829

2930
@dataclass(frozen=True)
@@ -152,6 +153,14 @@ def _build_runtime_overrides(profile: str, runtime_settings: PlatformRuntimeSett
152153
overrides["income_layer_start_usd"] = income_layer_start_usd
153154
if income_layer_max_ratio is not None:
154155
overrides["income_layer_max_ratio"] = income_layer_max_ratio
156+
if profile in DCA_PROFILES:
157+
dca_mode = getattr(runtime_settings, "dca_mode", None)
158+
dca_base_investment_usd = getattr(runtime_settings, "dca_base_investment_usd", None)
159+
if dca_mode is not None:
160+
overrides["investment_amount_mode"] = "fixed"
161+
overrides["smart_multiplier_enabled"] = dca_mode == "smart"
162+
if dca_base_investment_usd is not None:
163+
overrides["base_investment_usd"] = dca_base_investment_usd
155164
if profile == "tqqq_growth_income":
156165
if runtime_settings.income_threshold_usd is not None:
157166
overrides["income_threshold_usd"] = runtime_settings.income_threshold_usd

tests/test_strategy_runtime.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,23 @@ def test_income_layer_overrides_apply_to_runtime_config():
6161
"income_layer_start_usd": 250000.0,
6262
"income_layer_max_ratio": 0.25,
6363
}
64+
65+
66+
def test_dca_overrides_apply_to_runtime_config():
67+
settings = _runtime_settings(
68+
strategy_profile="nasdaq_sp500_smart_dca",
69+
dca_mode="smart",
70+
dca_base_investment_usd=500.0,
71+
)
72+
73+
assert _build_runtime_overrides("nasdaq_sp500_smart_dca", settings) == {
74+
"investment_amount_mode": "fixed",
75+
"smart_multiplier_enabled": True,
76+
"base_investment_usd": 500.0,
77+
}
78+
79+
80+
def test_dca_overrides_ignore_non_dca_profiles():
81+
settings = _runtime_settings(dca_mode="smart", dca_base_investment_usd=500.0)
82+
83+
assert _build_runtime_overrides("global_etf_rotation", settings) == {}

0 commit comments

Comments
 (0)