Skip to content

Commit 911b2fa

Browse files
Pigbibicodex
andcommitted
refactor: share paper command lifecycle
Co-Authored-By: Codex <noreply@openai.com>
1 parent 12f0bd4 commit 911b2fa

3 files changed

Lines changed: 53 additions & 220 deletions

File tree

application/paper_execution_command_consumer.py

Lines changed: 43 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,20 @@
1616

1717
from quant_platform_kit.common.execution_commands import (
1818
ExecutionCommand,
19-
ExecutionCommandState,
2019
ExecutionCommandStore,
21-
validate_execution_command_release_binding,
2220
)
23-
from quant_platform_kit.common.paper_execution_admission import evaluate_paper_execution_admission
21+
from quant_platform_kit.common.paper_execution_command_consumer import (
22+
PaperExecutionProposal,
23+
PaperExecutionReconciliation,
24+
consume_due_paper_execution_commands as consume_shared_paper_execution_commands,
25+
)
2426
from quant_platform_kit.common.runtime_command_gate import (
25-
RuntimeCommandAction,
2627
RuntimeCommandExposureEffect,
27-
RuntimeCommandGateEnforcement,
28-
RuntimeCommandGatePolicy,
29-
evaluate_runtime_command_gate,
30-
)
31-
from quant_platform_kit.common.strategy_release import (
32-
StrategyReleaseIdentity,
33-
build_strategy_release_identity,
34-
validate_runtime_loaded_receipt,
3528
)
29+
from quant_platform_kit.common.strategy_release import StrategyReleaseIdentity
3630

37-
PAPER_COMMAND_CONSUMER_SCHEMA_VERSION = "longbridge.paper-execution-command-consumer.v1"
3831
PAPER_EXECUTION_INTENT_SCHEMA_VERSION = "longbridge.paper-execution-intent.v1"
3932
_NOTIONAL_TOLERANCE = 0.01
40-
_PAPER_COMMAND_GATE_POLICY = RuntimeCommandGatePolicy(
41-
enforcement=RuntimeCommandGateEnforcement.ENFORCE,
42-
)
4333

4434

4535
def _normalized_symbol(value: object) -> str:
@@ -184,53 +174,34 @@ def _build_reconciled_order_proposals(
184174
return tuple(proposals), tuple(dict.fromkeys(findings))
185175

186176

187-
def _append_or_raise(
188-
store: ExecutionCommandStore,
177+
def _reconcile_command(
189178
command: ExecutionCommand,
190179
*,
191-
next_state: ExecutionCommandState,
192-
expected_previous_state: ExecutionCommandState,
193-
details: Mapping[str, object],
194-
) -> None:
195-
event = store.append_event(
180+
portfolio: Any,
181+
market_data_port: Any,
182+
) -> PaperExecutionReconciliation:
183+
"""Adapt LongBridge's value-target evidence to the shared paper contract."""
184+
185+
proposals, integrity_findings = _build_reconciled_order_proposals(
196186
command,
197-
next_state=next_state,
198-
expected_previous_state=expected_previous_state,
199-
details=details,
187+
portfolio=portfolio,
188+
market_data_port=market_data_port,
189+
)
190+
return PaperExecutionReconciliation(
191+
proposals=tuple(
192+
PaperExecutionProposal(
193+
symbol=str(proposal["symbol"]),
194+
exposure_effect=str(proposal["exposure_effect"]),
195+
details={
196+
key: value
197+
for key, value in proposal.items()
198+
if key not in {"symbol", "exposure_effect"}
199+
},
200+
)
201+
for proposal in proposals
202+
),
203+
integrity_findings=integrity_findings,
200204
)
201-
if event is None:
202-
raise RuntimeError(f"failed to persist paper command event {next_state.value}")
203-
204-
205-
def _attempt_reconciliation_required(
206-
store: ExecutionCommandStore,
207-
command: ExecutionCommand,
208-
*,
209-
error: Exception,
210-
) -> None:
211-
try:
212-
state = store.current_state(command)
213-
if state not in {
214-
ExecutionCommandState.CLAIMED,
215-
ExecutionCommandState.SUBMITTED,
216-
ExecutionCommandState.ACCEPTED,
217-
ExecutionCommandState.PARTIALLY_FILLED,
218-
}:
219-
return
220-
store.append_event(
221-
command,
222-
next_state=ExecutionCommandState.RECONCILIATION_REQUIRED,
223-
expected_previous_state=state,
224-
details={
225-
"paper_simulation": True,
226-
"reason": "consumer_exception_requires_manual_reconciliation",
227-
"error_type": type(error).__name__,
228-
},
229-
)
230-
except Exception:
231-
# The original error is already captured by the caller's result. Do
232-
# not risk masking it with a second storage failure.
233-
return
234205

235206

236207
def consume_due_paper_execution_commands(
@@ -243,164 +214,17 @@ def consume_due_paper_execution_commands(
243214
runtime_release_receipt: Mapping[str, Any] | None,
244215
expected_strategy_release: StrategyReleaseIdentity | Mapping[str, object] | None,
245216
) -> dict[str, object]:
246-
"""Claim and simulate due paper commands; never submit a broker order."""
247-
if store is None or (not store.cloud_prefix_uri and not store.local_dir):
248-
raise RuntimeError("paper durable execution command store is required")
249-
try:
250-
expected_release = build_strategy_release_identity(expected_strategy_release)
251-
except ValueError:
252-
return {
253-
"schema_version": PAPER_COMMAND_CONSUMER_SCHEMA_VERSION,
254-
"status": "blocked",
255-
"reason": "release_identity_invalid",
256-
"commands": [],
257-
}
258-
release_preflight = validate_runtime_loaded_receipt(
259-
runtime_release_receipt,
260-
expected_strategy_release=expected_release,
261-
)
262-
if not release_preflight.is_valid:
263-
return {
264-
"schema_version": PAPER_COMMAND_CONSUMER_SCHEMA_VERSION,
265-
"status": "blocked",
266-
"reason": release_preflight.findings[0],
267-
"commands": [],
268-
}
269-
270-
as_of_date = str(as_of_session)[:10]
271-
commands: list[dict[str, object]] = []
272-
for command in store.list_due(as_of_date):
273-
if store.current_state(command) is not ExecutionCommandState.QUEUED:
274-
continue
275-
claim = store.claim_due(command, as_of_date=as_of_date, claimant=claimant)
276-
if claim is None:
277-
continue
278-
try:
279-
admission = evaluate_paper_execution_admission(
280-
command=command,
281-
expected_strategy_release=expected_release,
282-
)
283-
integrity_findings = list(admission.integrity_findings)
284-
integrity_findings.extend(
285-
validate_execution_command_release_binding(
286-
command,
287-
expected_strategy_release=expected_release,
288-
).findings
289-
)
290-
if command.execution_mode != "paper":
291-
integrity_findings.append("durable_event_history_invalid")
292-
proposals, reconciliation_findings = _build_reconciled_order_proposals(
293-
command,
294-
portfolio=portfolio,
295-
market_data_port=market_data_port,
296-
)
297-
integrity_findings.extend(reconciliation_findings)
298-
integrity_findings = list(dict.fromkeys(integrity_findings))
299-
receipts: list[dict[str, object]] = []
300-
for proposal in proposals:
301-
decision = evaluate_runtime_command_gate(
302-
action=RuntimeCommandAction.SUBMIT,
303-
exposure_effect=proposal["exposure_effect"],
304-
command=command,
305-
command_state=ExecutionCommandState.CLAIMED,
306-
as_of_session=as_of_date,
307-
runtime_release_receipt=runtime_release_receipt,
308-
expected_strategy_release=expected_release,
309-
integrity_findings=integrity_findings,
310-
policy=_PAPER_COMMAND_GATE_POLICY,
311-
)
312-
receipts.append(decision.to_receipt())
313-
314-
# A no-op command still has to pass the command-level release and
315-
# timing checks before it can be closed as paper-filled.
316-
if not proposals:
317-
decision = evaluate_runtime_command_gate(
318-
action=RuntimeCommandAction.SUBMIT,
319-
exposure_effect=RuntimeCommandExposureEffect.NEUTRAL,
320-
command=command,
321-
command_state=ExecutionCommandState.CLAIMED,
322-
as_of_session=as_of_date,
323-
runtime_release_receipt=runtime_release_receipt,
324-
expected_strategy_release=expected_release,
325-
integrity_findings=integrity_findings,
326-
policy=_PAPER_COMMAND_GATE_POLICY,
327-
)
328-
receipts.append(decision.to_receipt())
329-
330-
details = {
331-
"paper_simulation": True,
332-
"claimant": claimant,
333-
"paper_execution_admission": {
334-
"disposition": admission.disposition.value,
335-
"receipt_sha256": admission.receipt_sha256,
336-
},
337-
"integrity_findings": integrity_findings,
338-
"proposals": list(proposals),
339-
"runtime_command_gate_receipts": receipts,
340-
}
341-
if any(not bool(receipt["policy_allows"]) for receipt in receipts):
342-
_append_or_raise(
343-
store,
344-
command,
345-
next_state=ExecutionCommandState.REJECTED,
346-
expected_previous_state=ExecutionCommandState.CLAIMED,
347-
details={
348-
**details,
349-
"reason": "paper_command_gate_would_block",
350-
},
351-
)
352-
commands.append(
353-
{
354-
"command_id": command.command_id,
355-
"status": ExecutionCommandState.REJECTED.value,
356-
"proposals_count": len(proposals),
357-
"would_block": True,
358-
}
359-
)
360-
continue
361-
362-
_append_or_raise(
363-
store,
364-
command,
365-
next_state=ExecutionCommandState.SUBMITTED,
366-
expected_previous_state=ExecutionCommandState.CLAIMED,
367-
details=details,
368-
)
369-
_append_or_raise(
370-
store,
371-
command,
372-
next_state=ExecutionCommandState.ACCEPTED,
373-
expected_previous_state=ExecutionCommandState.SUBMITTED,
374-
details={"paper_simulation": True, "proposals_count": len(proposals)},
375-
)
376-
_append_or_raise(
377-
store,
378-
command,
379-
next_state=ExecutionCommandState.FILLED,
380-
expected_previous_state=ExecutionCommandState.ACCEPTED,
381-
details={"paper_simulation": True, "simulated_fill_count": len(proposals)},
382-
)
383-
commands.append(
384-
{
385-
"command_id": command.command_id,
386-
"status": ExecutionCommandState.FILLED.value,
387-
"proposals_count": len(proposals),
388-
"would_block": False,
389-
}
390-
)
391-
except Exception as exc:
392-
_attempt_reconciliation_required(store, command, error=exc)
393-
commands.append(
394-
{
395-
"command_id": command.command_id,
396-
"status": ExecutionCommandState.RECONCILIATION_REQUIRED.value,
397-
"error_type": type(exc).__name__,
398-
}
399-
)
217+
"""Claim and simulate due paper commands through the shared lifecycle."""
400218

401-
return {
402-
"schema_version": PAPER_COMMAND_CONSUMER_SCHEMA_VERSION,
403-
"status": "ok",
404-
"as_of_session": as_of_date,
405-
"commands": commands,
406-
}
219+
return consume_shared_paper_execution_commands(
220+
store=store,
221+
as_of_session=as_of_session,
222+
claimant=claimant,
223+
reconcile_command=lambda command: _reconcile_command(
224+
command,
225+
portfolio=portfolio,
226+
market_data_port=market_data_port,
227+
),
228+
runtime_release_receipt=runtime_release_receipt,
229+
expected_strategy_release=expected_strategy_release,
230+
)

docs/paper_execution_command_consumer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# LongBridge 纸面命令消费者
22

3-
这个消费者用于验证延迟执行命令的最后一道风险检查。它只读取 LongBridge 的账户快照和行情,写入纸面命令审计记录;它不会构造执行端口,也不会调用下单 API。
3+
这个消费者用于验证延迟执行命令的最后一道风险检查。它只读取 LongBridge 的账户快照和行情,并把平台特有的价值目标转换为共享的纸面提案;命令认领、风险准入、运行时命令门和状态链由 `quant_platform_kit.common.paper_execution_command_consumer` 统一处理。它不会构造执行端口,也不会调用下单 API。
44

55
## 处理流程
66

tests/test_paper_execution_command_consumer.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ def test_paper_consumer_simulates_reconciled_orders_and_never_calls_an_execution
139139
]
140140
proposals = events[1].details["proposals"]
141141
assert [proposal["exposure_effect"] for proposal in proposals] == ["increases", "reduces"]
142+
assert proposals[0]["details"] == {
143+
"side": "buy",
144+
"quantity": 10.0,
145+
"reference_price": 10.0,
146+
"current_value": 0.0,
147+
"target_value": 100.0,
148+
"target_notional_delta": 100.0,
149+
"current_quantity": 0.0,
150+
}
142151
receipts = events[1].details["runtime_command_gate_receipts"]
143152
assert {receipt["enforcement"] for receipt in receipts} == {"enforce"}
144153
assert all(receipt["broker_write_allowed"] is True for receipt in receipts)

0 commit comments

Comments
 (0)