fix(paper): BAT-039 — paper order seq Redis 영속화 (재시작 멱등 키 충돌 방지, REQ-011/issue #4)
REQ-011 change-req(CHG-20260612-01)→구현→confirm-req-implemented(CHG-20260612-02)
전체 delivery. paper 한정 결정성 버그(프로덕션 무관).
- seq_store.py 신규: SeqStore 포트 + RedisSeqStore(원자 INCR, 재시작 단조·영속)
+ InMemorySeqStore(폴백/테스트). REQ-016 token_cache 패턴 재사용.
- paper.py: optional seq_store 주입. 미주입 시 기존 in-memory 동작 byte-for-byte 유지.
- config.py/daemon.py: TREX_PAPER_REDIS_URL → RedisSeqStore wiring
(key=trex:broker:paper:{instance_key}:order-seq).
- 런타임 Redis 오류는 전파(silent in-memory 폴백 금지 — 버그 재현 방지).
- tests: 재시작 단조성/correlation_id 비충돌/in-memory 폴백/fake redis INCR (5).
434 passed (+5), ruff clean.
REQ-011 Approved→Implemented, BAT-039 release-candidate. issue #4 close.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import threading
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from trex_core.adapters.broker.seq_store import SeqStore
|
||||
from trex_core.application.broker_port import (
|
||||
BalanceItem,
|
||||
FillStatus,
|
||||
@@ -47,12 +48,18 @@ class PaperBroker:
|
||||
fill_after_polls: int = 0,
|
||||
qty_per_poll: Optional[int] = None,
|
||||
reject_symbols: Optional[set[str]] = None,
|
||||
seq_store: Optional[SeqStore] = None,
|
||||
) -> None:
|
||||
self._fill_after = fill_after_polls
|
||||
self._qty_per_poll = qty_per_poll
|
||||
self._reject = reject_symbols or set()
|
||||
self._orders: dict[str, _Order] = {}
|
||||
self._seq = 0
|
||||
# REQ-011 CHG-20260612-01 (issue #4): when injected, order seq is minted
|
||||
# from a persistent monotonic store so it survives daemon restarts and a
|
||||
# post-restart sell does not collide its correlation_id with a prior buy.
|
||||
# Unset (no Redis configured) keeps the byte-for-byte in-memory counter.
|
||||
self._seq_store = seq_store
|
||||
self._lock = threading.Lock()
|
||||
# REQ-038: programmable current price for safety-sell scenario tests.
|
||||
self._prices: dict[str, Decimal] = {}
|
||||
@@ -62,8 +69,12 @@ class PaperBroker:
|
||||
|
||||
def submit_order(self, req: OrderRequest) -> OrderAck:
|
||||
with self._lock:
|
||||
self._seq += 1
|
||||
oid = f"PAPER-{self._seq:06d}"
|
||||
if self._seq_store is not None:
|
||||
seq = self._seq_store.next_seq()
|
||||
else:
|
||||
self._seq += 1
|
||||
seq = self._seq
|
||||
oid = f"PAPER-{seq:06d}"
|
||||
self._orders[oid] = _Order(oid, req)
|
||||
return OrderAck(accepted=True, broker_order_id=oid)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user