Skip to content

Commit 93f8fdf

Browse files
Pigbibicodex
andcommitted
fix: require explicit IBKR broker paper environment
Co-Authored-By: Codex <noreply@openai.com>
1 parent d7d20d3 commit 93f8fdf

8 files changed

Lines changed: 46 additions & 14 deletions

File tree

application/paper_execution_admission.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
RuntimeCommandGatePolicy,
3030
evaluate_runtime_command_gate,
3131
)
32+
from quant_platform_kit.common.runtime_target import RuntimeExecutionEnvironment
3233

3334

3435
PAPER_EXECUTION_ADMISSION_SCHEMA_VERSION = "ibkr.paper_execution_admission.v1"
@@ -44,17 +45,26 @@ def resolve_paper_execution_admission_enabled(
4445
env_reader,
4546
dry_run_only: bool,
4647
execution_mode: object,
48+
execution_environment: object | None = None,
4749
) -> bool:
48-
"""Resolve the opt-in flag and reject every non-PAPER configuration."""
50+
"""Resolve the opt-in flag and require an explicit broker PAPER target."""
4951

5052
raw_value = str(env_reader("IBKR_PAPER_EXECUTION_ADMISSION_ENABLED", "") or "").strip().lower()
5153
enabled = raw_value in {"1", "true", "t", "yes", "y", "on"}
5254
if not enabled:
5355
return False
5456
normalized_mode = str(execution_mode or "").strip().lower().replace("-", "_")
55-
if dry_run_only or normalized_mode != "paper":
57+
normalized_environment = str(
58+
getattr(execution_environment, "value", execution_environment) or ""
59+
).strip().lower()
60+
if (
61+
dry_run_only
62+
or normalized_mode != "paper"
63+
or normalized_environment != RuntimeExecutionEnvironment.PAPER.value
64+
):
5665
raise RuntimeError(
57-
"IBKR_PAPER_EXECUTION_ADMISSION_ENABLED is only supported for ordinary execution_mode=paper"
66+
"IBKR_PAPER_EXECUTION_ADMISSION_ENABLED requires "
67+
"dry_run_only=false, execution_mode=paper, and execution_environment=paper"
5868
)
5969
return True
6070

application/runtime_composer.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ def build_rebalance_config(self, *, extra_notification_lines=(), cash_only_execu
193193
env_reader=self.env_reader,
194194
dry_run_only=self.dry_run_only,
195195
execution_mode=execution_mode,
196+
execution_environment=(
197+
self.runtime_target.execution_environment
198+
if self.runtime_target is not None
199+
else None
200+
),
196201
),
197202
runtime_release_receipt=build_runtime_loaded_receipt(
198203
strategy_release=(

docs/ibkr_paper_execution_admission.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# IBKR PAPER execution admission
22

33
`IBKR_PAPER_EXECUTION_ADMISSION_ENABLED` is an opt-in guard for the ordinary
4-
IBKR `execution_mode=paper` rebalance path. Its default is disabled. It does
5-
not apply to `IBKR_PAPER_LIQUIDATE_ONLY`, dry-run previews, live execution,
4+
IBKR `execution_mode=paper` rebalance path. Its default is disabled. It
5+
requires the shared runtime target to explicitly declare
6+
`execution_environment=paper` with `dry_run_only=false`. It does not apply to
7+
`IBKR_PAPER_LIQUIDATE_ONLY`, local dry-run previews, live execution,
68
deployment workflows, or schedulers.
79

810
When explicitly enabled, the strategy/control-plane producer must place a QPK
@@ -22,5 +24,5 @@ reconciled exposure facts, and enforced runtime-gate receipts are persisted in
2224
the normal reconciliation record under `paper_execution_admission`.
2325

2426
Do not enable the flag until an upstream producer can supply this immutable
25-
command contract. Enabling it for a non-PAPER or dry-run target fails at
26-
startup rather than silently weakening the guard.
27+
command contract. Enabling it for a non-PAPER, local dry-run, or ambiguous
28+
runtime target fails at startup rather than silently weakening the guard.

main.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,11 @@ def build_broker_adapters(*, dry_run_only_override: bool | None = None):
623623
env_reader=os.getenv,
624624
dry_run_only=effective_dry_run_only,
625625
execution_mode=effective_execution_mode,
626+
execution_environment=(
627+
RUNTIME_SETTINGS.runtime_target.execution_environment
628+
if RUNTIME_SETTINGS.runtime_target is not None
629+
else None
630+
),
626631
),
627632
runtime_release_receipt=build_runtime_loaded_receipt(
628633
strategy_release=expected_strategy_release,

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ dependencies = [
2121
"google-cloud-secret-manager",
2222
"google-cloud-storage",
2323
"yfinance",
24-
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@3fa3b7588eb220e77b3d3851ce9f192046d47afd",
24+
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@0507eedd44b8590ef232008a2b6d1469fec0b910",
2525
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@64574bdd0e213c6938c5144773d7e827ddef4ef2",
2626
"hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@7e9abf9b0f92647f04b4978ff0771b3bd4f75ae3",
2727
]
@@ -64,5 +64,5 @@ include = [
6464

6565
[tool.uv]
6666
override-dependencies = [
67-
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@3fa3b7588eb220e77b3d3851ce9f192046d47afd",
67+
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@0507eedd44b8590ef232008a2b6d1469fec0b910",
6868
]

qsl.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ upgrade_ring = "ring_d"
55
allow_legacy = false
66

77
[qsl.requires]
8-
quant_platform_kit = "3fa3b7588eb220e77b3d3851ce9f192046d47afd"
8+
quant_platform_kit = "0507eedd44b8590ef232008a2b6d1469fec0b910"
99
us_equity_strategies = "64574bdd0e213c6938c5144773d7e827ddef4ef2"
1010
hk_equity_strategies = "7e9abf9b0f92647f04b4978ff0771b3bd4f75ae3"
1111

tests/test_paper_execution_admission.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
build_paper_risk_admission_receipt,
1515
)
1616
from quant_platform_kit.common.strategy_release import build_runtime_loaded_receipt
17+
from quant_platform_kit.common.runtime_target import RuntimeExecutionEnvironment
1718

1819

1920
def _release_identity() -> dict[str, str]:
@@ -86,12 +87,21 @@ def test_paper_admission_is_opt_in_and_rejects_non_paper_enablement():
8687
env_reader=lambda _name, _default: "true",
8788
dry_run_only=False,
8889
execution_mode="paper",
90+
execution_environment=RuntimeExecutionEnvironment.PAPER,
8991
)
90-
with pytest.raises(RuntimeError, match="execution_mode=paper"):
92+
with pytest.raises(RuntimeError, match="execution_environment=paper"):
9193
resolve_paper_execution_admission_enabled(
9294
env_reader=lambda _name, _default: "true",
9395
dry_run_only=False,
9496
execution_mode="live",
97+
execution_environment=RuntimeExecutionEnvironment.LIVE,
98+
)
99+
with pytest.raises(RuntimeError, match="execution_environment=paper"):
100+
resolve_paper_execution_admission_enabled(
101+
env_reader=lambda _name, _default: "true",
102+
dry_run_only=True,
103+
execution_mode="dry_run",
104+
execution_environment=RuntimeExecutionEnvironment.DRY_RUN,
95105
)
96106

97107

uv.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)