Skip to content

Commit 7b6e3ce

Browse files
Pigbibicursoragent
andauthored
Add shared cash-only execution env resolver for platform overrides. (#113)
Prefer {PLATFORM}_CASH_ONLY_EXECUTION over legacy CASH_ONLY_EXECUTION with safe default true. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d18fe32 commit 7b6e3ce

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

src/quant_platform_kit/common/runtime_config.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,38 @@ def resolve_bool_value(raw_value: str | None) -> bool:
3939
return str(raw_value or "").strip().lower() in {"1", "true", "yes", "y", "on"}
4040

4141

42+
def resolve_optional_bool_env(
43+
env: Mapping[str, str | None],
44+
name: str,
45+
) -> bool | None:
46+
raw_value = env.get(name)
47+
if raw_value is None or str(raw_value).strip() == "":
48+
return None
49+
return resolve_bool_value(raw_value)
50+
51+
52+
def resolve_cash_only_execution_env(
53+
env: Mapping[str, str | None],
54+
*,
55+
platform_env_prefix: str | None = None,
56+
legacy_name: str = "CASH_ONLY_EXECUTION",
57+
default: bool = True,
58+
) -> bool:
59+
"""Resolve cash-only execution with platform-specific override precedence."""
60+
candidates: list[str] = []
61+
prefix = str(platform_env_prefix or "").strip().upper()
62+
if prefix:
63+
candidates.append(f"{prefix}_CASH_ONLY_EXECUTION")
64+
legacy = str(legacy_name or "").strip()
65+
if legacy:
66+
candidates.append(legacy)
67+
for name in candidates:
68+
value = resolve_optional_bool_env(env, name)
69+
if value is not None:
70+
return value
71+
return default
72+
73+
4274
def resolve_dry_run_env(
4375
env: Mapping[str, str | None],
4476
name: str,

tests/test_runtime_config.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
from quant_platform_kit.common.runtime_config import (
88
first_non_empty,
99
resolve_bool_value,
10+
resolve_cash_only_execution_env,
1011
resolve_dry_run_env,
1112
resolve_float_env,
13+
resolve_optional_bool_env,
1214
resolve_optional_float_env,
1315
resolve_quantity_step_env,
1416
resolve_strategy_config_path,
@@ -200,6 +202,28 @@ def test_resolve_strategy_runtime_path_settings_prefers_env_over_derived_paths(s
200202
self.assertEqual(settings.strategy_config_source, "env")
201203
self.assertIsNone(settings.reconciliation_output_path)
202204

205+
def test_resolve_cash_only_execution_env_prefers_platform_override(self) -> None:
206+
env = {
207+
"CASH_ONLY_EXECUTION": "false",
208+
"IBKR_CASH_ONLY_EXECUTION": "true",
209+
"SCHWAB_CASH_ONLY_EXECUTION": "false",
210+
}
211+
self.assertTrue(
212+
resolve_cash_only_execution_env(env, platform_env_prefix="IBKR")
213+
)
214+
self.assertFalse(
215+
resolve_cash_only_execution_env(env, platform_env_prefix="SCHWAB")
216+
)
217+
self.assertFalse(resolve_cash_only_execution_env(env))
218+
self.assertTrue(
219+
resolve_cash_only_execution_env({}, platform_env_prefix="IBKR")
220+
)
221+
222+
def test_resolve_optional_bool_env_treats_blank_as_unset(self) -> None:
223+
self.assertIsNone(resolve_optional_bool_env({"FLAG": ""}, "FLAG"))
224+
self.assertIsNone(resolve_optional_bool_env({}, "FLAG"))
225+
self.assertFalse(resolve_optional_bool_env({"FLAG": "false"}, "FLAG"))
226+
203227

204228
if __name__ == "__main__":
205229
unittest.main()

0 commit comments

Comments
 (0)