diff --git a/application/execution_receipt_adapter.py b/application/execution_receipt_adapter.py index b0a9696..4368573 100644 --- a/application/execution_receipt_adapter.py +++ b/application/execution_receipt_adapter.py @@ -11,6 +11,12 @@ ) +# Only promote these explicit cycle reasons. Broad no-ops stay ``no_action`` +# so existing digests and projections keep digest-compatible outcomes. +_NO_SIGNAL_REASON_HEADS = frozenset({"no_signal"}) +_NO_REBALANCE_REASON_HEADS = frozenset({"no_rebalance", "target_diff_below_threshold"}) + + def attach_cycle_execution_receipt( report: dict[str, Any], cycle_result: object, @@ -39,6 +45,11 @@ def attach_cycle_execution_receipt( reconciliation_required=reconciliation_required, risk_blocked=risk_blocked, ) + if outcome == "no_action" and not bool(report.get("dry_run")): + explicit = _explicit_non_action_outcome(execution) + if explicit is not None: + outcome = explicit + confirmation = "not_applicable" return attach_runtime_execution_receipt( report, outcome=outcome, @@ -64,3 +75,17 @@ def attach_terminal_fallback_execution_receipt(report: dict[str, Any]) -> dict[s def _as_mapping(value: object) -> Mapping[str, Any]: return value if isinstance(value, Mapping) else {} + + +def _explicit_non_action_outcome(execution: Mapping[str, Any]) -> str | None: + """Map only explicit cycle reasons to no_signal / no_rebalance.""" + + reason = str(execution.get("no_op_reason") or "").strip().lower() + if not reason: + return None + head = reason.split(":", 1)[0].strip() + if head in _NO_SIGNAL_REASON_HEADS: + return "no_signal" + if head in _NO_REBALANCE_REASON_HEADS: + return "no_rebalance" + return None diff --git a/pyproject.toml b/pyproject.toml index d4a8337..aa264a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "google-cloud-storage", "google-auth", "numpy", - "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@1826204e2927f4812a69348dd9c3d5235265be88", + "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@f982aea79476cadd54d074f7c0447c1111658968", "us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@4a3943883cd6b5bbfe32a559e56a91b40a81b7ce", ] @@ -61,5 +61,5 @@ include = [ [tool.uv] override-dependencies = [ - "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@1826204e2927f4812a69348dd9c3d5235265be88", + "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@f982aea79476cadd54d074f7c0447c1111658968", ] diff --git a/qsl.toml b/qsl.toml index 5cf67dd..f7c8cba 100644 --- a/qsl.toml +++ b/qsl.toml @@ -5,7 +5,7 @@ upgrade_ring = "ring_d" allow_legacy = false [qsl.requires] -quant_platform_kit = "1826204e2927f4812a69348dd9c3d5235265be88" +quant_platform_kit = "f982aea79476cadd54d074f7c0447c1111658968" us_equity_strategies = "4a3943883cd6b5bbfe32a559e56a91b40a81b7ce" [qsl.compat] diff --git a/tests/test_execution_receipt_adapter.py b/tests/test_execution_receipt_adapter.py index 15518f3..aaa158a 100644 --- a/tests/test_execution_receipt_adapter.py +++ b/tests/test_execution_receipt_adapter.py @@ -82,3 +82,58 @@ def test_strategy_risk_rejection_is_persisted_as_risk_blocked(self) -> None: self.assertEqual(report["execution_receipt"]["outcome"], "risk_blocked") self.assertEqual(report["execution_receipt"]["broker_confirmation"], "not_applicable") + + def test_explicit_no_signal_reason_is_no_signal(self) -> None: + report = _report() + + attach_cycle_execution_receipt( + report, + SimpleNamespace(execution={"no_op_reason": "no_signal"}, submitted_orders=()), + ) + + self.assertEqual(report["execution_receipt"]["outcome"], "no_signal") + self.assertEqual(report["execution_receipt"]["broker_confirmation"], "not_applicable") + + def test_explicit_no_rebalance_reason_is_no_rebalance(self) -> None: + report = _report() + + attach_cycle_execution_receipt( + report, + SimpleNamespace( + execution={ + "execution_status": "no_op", + "no_op_reason": "target_diff_below_threshold", + }, + submitted_orders=(), + ), + ) + + self.assertEqual(report["execution_receipt"]["outcome"], "no_rebalance") + self.assertEqual(report["execution_receipt"]["broker_confirmation"], "not_applicable") + + def test_ambiguous_no_op_reason_stays_no_action(self) -> None: + report = _report() + + attach_cycle_execution_receipt( + report, + SimpleNamespace( + execution={"execution_status": "no_op", "no_op_reason": "market_closed"}, + submitted_orders=(), + ), + ) + + self.assertEqual(report["execution_receipt"]["outcome"], "no_action") + + def test_dry_run_keeps_no_action_even_with_explicit_reason(self) -> None: + report = _report() + report["dry_run"] = True + + attach_cycle_execution_receipt( + report, + SimpleNamespace( + execution={"no_op_reason": "no_signal"}, + submitted_orders=(), + ), + ) + + self.assertEqual(report["execution_receipt"]["outcome"], "no_action") diff --git a/uv.lock b/uv.lock index 218e818..16c4b60 100644 --- a/uv.lock +++ b/uv.lock @@ -17,7 +17,7 @@ resolution-markers = [ ] [manifest] -overrides = [{ name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=1826204e2927f4812a69348dd9c3d5235265be88" }] +overrides = [{ name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=f982aea79476cadd54d074f7c0447c1111658968" }] [[package]] name = "anyio" @@ -186,7 +186,7 @@ requires-dist = [ { name = "pytest", marker = "extra == 'test'" }, { name = "pytest-cov", marker = "extra == 'test'" }, { name = "pytz" }, - { name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=1826204e2927f4812a69348dd9c3d5235265be88" }, + { name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=f982aea79476cadd54d074f7c0447c1111658968" }, { name = "requests" }, { name = "ruff", marker = "extra == 'test'" }, { name = "schwab-py" }, @@ -1320,7 +1320,7 @@ wheels = [ [[package]] name = "quant-platform-kit" version = "1.0.0" -source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=1826204e2927f4812a69348dd9c3d5235265be88#1826204e2927f4812a69348dd9c3d5235265be88" } +source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=f982aea79476cadd54d074f7c0447c1111658968#f982aea79476cadd54d074f7c0447c1111658968" } [[package]] name = "requests"