Skip to content

Commit 987b126

Browse files
authored
Merge pull request #407 from QuantStrategyLab/codex/execution-receipt-adapter
feat: publish bounded LongBridge execution receipts
2 parents a515c9e + 98310c3 commit 987b126

7 files changed

Lines changed: 125 additions & 7 deletions

File tree

.github/workflows/execution-report-heartbeat.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ jobs:
113113

114114
- name: Publish read-only runtime execution evidence
115115
if: matrix.target.label == 'PAPER'
116-
uses: QuantStrategyLab/QuantRuntimeSettings/actions/publish-runtime-execution-evidence@f28bee6a4e28e2aead5fb37bd3213c12dd419e29
116+
uses: QuantStrategyLab/QuantRuntimeSettings/actions/publish-runtime-execution-evidence@813ba38c4adb759e143503c8e6b70715090391cc
117117
with:
118118
source-id: runtime-reports-longbridge
119119
report-platform: longbridge
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Bounded execution-receipt facts derived from LongBridge cycle results."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from quant_platform_kit.common.execution_receipts import (
8+
attach_runtime_execution_receipt,
9+
resolve_execution_receipt_fact,
10+
)
11+
12+
13+
def attach_cycle_execution_receipt(
14+
report: dict[str, Any],
15+
cycle_result: object,
16+
) -> dict[str, Any]:
17+
"""Attach only facts that the LongBridge cycle result explicitly carries.
18+
19+
``action_done`` confirms a local order-submission step, not a broker fill.
20+
A populated ``pending_orders`` list wins over it and produces a
21+
reconciliation requirement instead.
22+
"""
23+
24+
pending_orders = tuple(getattr(cycle_result, "pending_orders", ()) or ())
25+
outcome, confirmation = resolve_execution_receipt_fact(
26+
dry_run=bool(report.get("dry_run")),
27+
submission_attempted=bool(getattr(cycle_result, "action_done", False)),
28+
reconciliation_required=bool(pending_orders),
29+
)
30+
return attach_runtime_execution_receipt(
31+
report,
32+
outcome=outcome,
33+
broker_confirmation=confirmation,
34+
)
35+
36+
37+
def attach_terminal_fallback_execution_receipt(report: dict[str, Any]) -> dict[str, Any]:
38+
"""Attach a conservative fact when the cycle exits before a result exists."""
39+
40+
failed = str(report.get("status") or "").strip().lower() == "error"
41+
outcome, confirmation = resolve_execution_receipt_fact(
42+
dry_run=bool(report.get("dry_run")),
43+
submission_attempted=failed,
44+
failed=failed,
45+
)
46+
return attach_runtime_execution_receipt(
47+
report,
48+
outcome=outcome,
49+
broker_confirmation=confirmation,
50+
)

main.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
import google.auth
1616
import requests
1717
from application.monitor_dispatcher import dispatch_due_monitors, load_monitor_targets
18+
from application.execution_receipt_adapter import (
19+
attach_cycle_execution_receipt,
20+
attach_terminal_fallback_execution_receipt,
21+
)
1822
from application.runtime_broker_adapters import build_runtime_broker_adapters
1923
from application.runtime_composer import build_runtime_composer
2024
from application.rebalance_service import run_strategy as run_rebalance_cycle
@@ -811,6 +815,13 @@ def run_strategy(*, force_run: bool = False, validation_only: bool = False, vali
811815
message="Strategy signal snapshot",
812816
**signal_snapshot,
813817
)
818+
try:
819+
attach_cycle_execution_receipt(report, cycle_result)
820+
except ValueError:
821+
# Test/legacy runtime reports without a release attestation remain
822+
# evidence-missing; receipt metadata must not change a completed
823+
# strategy cycle into a failure.
824+
pass
814825
finalize_runtime_report(
815826
report,
816827
status="ok",
@@ -876,6 +887,13 @@ def run_strategy(*, force_run: bool = False, validation_only: bool = False, vali
876887
)
877888
return False
878889
finally:
890+
if "execution_receipt" not in report:
891+
try:
892+
attach_terminal_fallback_execution_receipt(report)
893+
except ValueError:
894+
# Legacy targets remain evidence-missing; receipt metadata
895+
# cannot affect strategy execution or error handling.
896+
pass
879897
try:
880898
report_path = reporting_adapters.persist_execution_report(report)
881899
print(f"execution_report {report_path}", flush=True)

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ dependencies = [
1818
"google-cloud-storage",
1919
"google-auth",
2020
"longport==3.0.23",
21-
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@ee8d996392f96e8bdf40988bd68ae30bf5911d2d",
21+
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@b567acd52a6afa6caca3e35648d4347a1ce13ab1",
2222
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@4584de176ca27955270471284663a13a2ce7f828",
2323
"hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@c526f2ed869c59f37a494d5fd3a64be299855ec8",
2424
]
@@ -61,5 +61,5 @@ include = [
6161

6262
[tool.uv]
6363
override-dependencies = [
64-
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@ee8d996392f96e8bdf40988bd68ae30bf5911d2d",
64+
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@b567acd52a6afa6caca3e35648d4347a1ce13ab1",
6565
]

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 = "ee8d996392f96e8bdf40988bd68ae30bf5911d2d"
8+
quant_platform_kit = "b567acd52a6afa6caca3e35648d4347a1ce13ab1"
99
us_equity_strategies = "4584de176ca27955270471284663a13a2ce7f828"
1010
hk_equity_strategies = "c526f2ed869c59f37a494d5fd3a64be299855ec8"
1111

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from __future__ import annotations
2+
3+
from types import SimpleNamespace
4+
import unittest
5+
6+
from application.execution_receipt_adapter import attach_cycle_execution_receipt
7+
8+
9+
REVISION = "a" * 40
10+
11+
12+
def _report() -> dict[str, object]:
13+
return {
14+
"platform": "longbridge",
15+
"strategy_profile": "sg_us_equity_rotation",
16+
"dry_run": False,
17+
"runtime_target": {"execution_mode": "paper"},
18+
"runtime_release_receipt": {
19+
"attestation_state": "self_attested",
20+
"strategy_release": {"strategy_revision": REVISION},
21+
},
22+
}
23+
24+
25+
class ExecutionReceiptAdapterTest(unittest.TestCase):
26+
def test_submission_does_not_claim_broker_acknowledgement(self) -> None:
27+
report = _report()
28+
29+
attach_cycle_execution_receipt(report, SimpleNamespace(action_done=True, pending_orders=()))
30+
31+
self.assertEqual(report["execution_receipt"]["outcome"], "submitted")
32+
self.assertEqual(report["execution_receipt"]["broker_confirmation"], "not_observed")
33+
34+
def test_pending_orders_require_reconciliation(self) -> None:
35+
report = _report()
36+
37+
attach_cycle_execution_receipt(
38+
report,
39+
SimpleNamespace(action_done=True, pending_orders=({"symbol": "QQQ"},)),
40+
)
41+
42+
self.assertEqual(report["execution_receipt"]["outcome"], "reconciliation_required")
43+
44+
def test_dry_run_never_claims_submission(self) -> None:
45+
report = _report()
46+
report["dry_run"] = True
47+
48+
attach_cycle_execution_receipt(report, SimpleNamespace(action_done=True, pending_orders=()))
49+
50+
self.assertEqual(report["execution_receipt"]["outcome"], "no_action")

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)