Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions application/cycle_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
try_record_platform_execution,
)
from application.execution_receipt_adapter import attach_execution_receipt_from_report
from application.execution_kernel_adapter import consult_t2_unknown_new_cycle
from application.portfolio_service import EARN_FORWARD_REASON_CODES
from runtime_logging import RuntimeLogContext, emit_runtime_log
from runtime_support import (
Expand Down Expand Up @@ -231,6 +232,8 @@ def execute_strategy_cycle(
reconcile_pending_funding_submission(runtime)
submission_state = state.get("order_submission", {}).get("state", "RESERVED")
if submission_state == "SUBMISSION_UNKNOWN":
# N13: redundant T2 consult; local raise always wins (no behavior change).
consult_t2_unknown_new_cycle()
raise ExecutionIntegrityError("order_reconciliation_uncertain")
if submission_state == "FILLED_ACCOUNTING_PENDING":
raise ExecutionIntegrityError("filled_order_accounting_unverifiable")
Expand Down
49 changes: 49 additions & 0 deletions application/execution_kernel_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Thin adapter: map Binance SUBMISSION_UNKNOWN deny paths onto QPK execution_kernel T2/T3.

N13: consult-only / redundant fail-closed. Local raise still always wins.
Does not own claims or change allow / reconcile-success paths.
Does not touch FILLED_ACCOUNTING_PENDING (B13-b G3; BN-only).
"""

from __future__ import annotations

from quant_platform_kit.execution_kernel import (
ExecutionGuardDecision,
SubmissionCertainty,
SubmissionRetrySnapshot,
SubmissionUnknownSnapshot,
deny_blind_retry_after_uncertain_transport,
deny_new_cycle_when_submission_unknown,
)


def consult_t2_unknown_new_cycle() -> ExecutionGuardDecision:
"""Consult T2 when cycle entry finds SUBMISSION_UNKNOWN and will raise.

Caller must still raise ExecutionIntegrityError("order_reconciliation_uncertain").
"""
return deny_new_cycle_when_submission_unknown(
SubmissionUnknownSnapshot(
submission_certainty=SubmissionCertainty.UNKNOWN,
requesting_new_cycle_submit=True,
)
)


def consult_t3_deny_blind_retry(
*,
transport_uncertain: bool = True,
reconciled: bool = False,
) -> ExecutionGuardDecision:
"""Consult T3 on paths that already refuse blind retry after uncertain transport.

Caller must still raise OrderReconciliationError("order_reconciliation_uncertain").
Do not call on reconcile-success return paths.
"""
return deny_blind_retry_after_uncertain_transport(
SubmissionRetrySnapshot(
transport_uncertain=bool(transport_uncertain),
reconciled=bool(reconciled),
requesting_blind_retry=True,
)
)
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.1.0"
description = "QuantStrategyLab platform layer for Binance exchange."
requires-python = ">=3.11"
dependencies = [
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@8e8ec51884bf8abb0a7ca699fc2da39fad134f89",
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@155d74a615ccdebf7223787939c2dc7151d75efd",
"crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@7fcca8ee0280b3e66245eb673caadda8b8b3b9a3",
"python-binance",
"pandas",
Expand All @@ -23,7 +23,7 @@ test = [

[tool.uv]
override-dependencies = [
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@8e8ec51884bf8abb0a7ca699fc2da39fad134f89",
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@155d74a615ccdebf7223787939c2dc7151d75efd",
]

[tool.ruff]
Expand Down
2 changes: 1 addition & 1 deletion qsl.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ expires_at = "2026-09-30"
next_action = "keep uv.lock current and maintain QPK/CryptoStrategies pin consistency"

[qsl.requires]
quant_platform_kit = "8e8ec51884bf8abb0a7ca699fc2da39fad134f89"
quant_platform_kit = "155d74a615ccdebf7223787939c2dc7151d75efd"
crypto_strategies = "7fcca8ee0280b3e66245eb673caadda8b8b3b9a3"

[qsl.compat]
Expand Down
7 changes: 7 additions & 0 deletions runtime_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import requests
from quant_platform_kit.common.runtime_reports import build_runtime_report_base
from application.execution_kernel_adapter import consult_t3_deny_blind_retry
from application.execution_receipt_adapter import (
record_order_failure,
record_order_response,
Expand Down Expand Up @@ -1210,6 +1211,8 @@ def runtime_call_client(runtime, report, *, method_name, payload, effect_type,
submission_status = submission_record["state"]
if submission_status == _ORDER_SUBMISSION_UNKNOWN:
if "method_name" in submission_record or is_earn_call:
# N13: redundant T3 consult; local raise always wins (no behavior change).
consult_t3_deny_blind_retry(transport_uncertain=True, reconciled=False)
raise OrderReconciliationError("order_reconciliation_uncertain") from None
current_payload, current_identity_sha256 = _ensure_order_logical_identity(runtime, method_name, payload)
if current_identity_sha256 != submission_record["identity_sha256"]:
Expand Down Expand Up @@ -1300,6 +1303,8 @@ def runtime_call_client(runtime, report, *, method_name, payload, effect_type,
payload={"payload": dict(client_payload), "reason": "order_reconciliation_uncertain", "retries": 0},
executed=False,
)
# N13: redundant T3 consult; local raise always wins (no behavior change).
consult_t3_deny_blind_retry(transport_uncertain=True, reconciled=False)
raise OrderReconciliationError("order_reconciliation_uncertain") from None
if not _is_confirmed_earn_success(method_name, response):
record_side_effect(
Expand All @@ -1310,6 +1315,8 @@ def runtime_call_client(runtime, report, *, method_name, payload, effect_type,
payload={"payload": dict(client_payload), "reason": "order_reconciliation_uncertain", "retries": 0},
executed=False,
)
# N13: redundant T3 consult; local raise always wins (no behavior change).
consult_t3_deny_blind_retry(transport_uncertain=True, reconciled=False)
raise OrderReconciliationError("order_reconciliation_uncertain") from None
record_side_effect(
runtime,
Expand Down
40 changes: 40 additions & 0 deletions tests/test_execution_kernel_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""N13: Binance adapter around QPK execution_kernel T2/T3 (UNKNOWN deny paths)."""

from __future__ import annotations

import unittest

from application.execution_kernel_adapter import (
consult_t2_unknown_new_cycle,
consult_t3_deny_blind_retry,
)


class ExecutionKernelAdapterTests(unittest.TestCase):
def test_t2_unknown_new_cycle_denies(self):
decision = consult_t2_unknown_new_cycle()
self.assertEqual(decision.decision, "deny")
self.assertEqual(decision.reason_code, "submission_uncertain")
self.assertFalse(decision.allowed)

def test_t3_uncertain_unreconciled_blind_retry_denies(self):
decision = consult_t3_deny_blind_retry(
transport_uncertain=True,
reconciled=False,
)
self.assertEqual(decision.decision, "deny")
self.assertEqual(decision.reason_code, "reconcile_required")
self.assertFalse(decision.allowed)

def test_t3_reconciled_allows_kernel(self):
"""If already reconciled, T3 allows; callers must not request blind retry here."""
decision = consult_t3_deny_blind_retry(
transport_uncertain=True,
reconciled=True,
)
self.assertEqual(decision.decision, "allow")
self.assertTrue(decision.allowed)


if __name__ == "__main__":
unittest.main()
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.