feat(rel-006): dcy-022/023 + BAT-032~034 — 실행 정책·qty 검증·allocated_capital 제거 통합
BAT-032 (dcy-022): timeout_sec/slippage_pct 실행 정책, 미체결 자동 취소, 보호 지정가, `trex-core cancel` 백스톱. 마이그레이션 012. BAT-033: `trex-core enter --qty` 필수 검증 — 미지정 시 RPC 전 즉시 오류. BAT-034 (dcy-023): allocated_capital 죽은 필드 전 경로 제거 (domain·intent· persistence·snapshot·CLI). 마이그레이션 013(비가역 DROP). 외부 발신자 unknown key 무시 수신 의미론 적용. REL-006 confirmed: BAT-026~034 통합 릴리즈, pre-release smoke 증거 포함. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,6 @@ class Slot:
|
||||
# on pending exit (→HOLDING, →INACTIVE) so a buy limit never leaks into a
|
||||
# sell order (issue #10). None => market order at dispatch.
|
||||
limit_price: Optional[float] = None
|
||||
allocated_capital: Optional[float] = None
|
||||
order_id: Optional[str] = None
|
||||
strategy_id: Optional[str] = None
|
||||
broker_order_id: Optional[str] = None # BAT-003 / REQ-011 (trex-orbit lineage)
|
||||
@@ -47,6 +46,11 @@ class Slot:
|
||||
# instance-scoped. retired_at marks a soft-retired / universe-detached slot.
|
||||
instance_key: str = "default"
|
||||
retired_at: Optional[datetime] = None
|
||||
# dcy-022 / REQ-068: execution policy — set on PENDING entry, cleared on exit.
|
||||
# timeout_sec: cancel_order is sent after this many seconds of no fill.
|
||||
# slippage_pct: effective limit = entry_price * (1 ± pct/100); None = no band.
|
||||
timeout_sec: Optional[int] = None
|
||||
slippage_pct: Optional[float] = None
|
||||
|
||||
|
||||
# Canonical column contract for `Slot` (dcy-001 OQ-2 / FR-5).
|
||||
@@ -60,7 +64,6 @@ EXPECTED_SLOT_FIELDS: frozenset[str] = frozenset({
|
||||
"qty",
|
||||
"avg_price",
|
||||
"limit_price",
|
||||
"allocated_capital",
|
||||
"order_id",
|
||||
"strategy_id",
|
||||
"broker_order_id",
|
||||
@@ -73,6 +76,8 @@ EXPECTED_SLOT_FIELDS: frozenset[str] = frozenset({
|
||||
"updated_at",
|
||||
"instance_key", # dcy-020 / REQ-056 (BAT-027)
|
||||
"retired_at", # dcy-020 / REQ-056 (BAT-027)
|
||||
"timeout_sec", # dcy-022 / REQ-068 (BAT-032)
|
||||
"slippage_pct", # dcy-022 / REQ-068 (BAT-032)
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ class SlotService:
|
||||
qty: Optional[float] = None,
|
||||
avg_price: Optional[float] = None,
|
||||
limit_price: Optional[float] = None,
|
||||
allocated_capital: Optional[float] = None,
|
||||
order_id: Optional[str] = None,
|
||||
strategy_id: Optional[str] = None,
|
||||
broker_order_id: Optional[str] = None,
|
||||
@@ -74,6 +73,9 @@ class SlotService:
|
||||
forced_close_at: Optional[datetime] = None,
|
||||
stop_loss_price: Optional[Decimal] = None,
|
||||
take_profit_price: Optional[Decimal] = None,
|
||||
# dcy-022 / REQ-068: execution policy (set on PENDING entry, cleared on exit).
|
||||
timeout_sec: Optional[int] = None,
|
||||
slippage_pct: Optional[float] = None,
|
||||
extra: Optional[dict[str, Any]] = None,
|
||||
) -> Slot:
|
||||
"""Transition slot from one state to another atomically.
|
||||
@@ -112,7 +114,6 @@ class SlotService:
|
||||
qty=qty,
|
||||
avg_price=avg_price,
|
||||
limit_price=limit_price,
|
||||
allocated_capital=allocated_capital,
|
||||
order_id=order_id,
|
||||
strategy_id=strategy_id,
|
||||
broker_order_id=broker_order_id,
|
||||
@@ -121,6 +122,8 @@ class SlotService:
|
||||
forced_close_at=forced_close_at,
|
||||
stop_loss_price=stop_loss_price,
|
||||
take_profit_price=take_profit_price,
|
||||
timeout_sec=timeout_sec,
|
||||
slippage_pct=slippage_pct,
|
||||
)
|
||||
self._repo.update_slot(updated)
|
||||
|
||||
@@ -146,7 +149,6 @@ class SlotService:
|
||||
qty: Optional[float],
|
||||
avg_price: Optional[float],
|
||||
limit_price: Optional[float],
|
||||
allocated_capital: Optional[float],
|
||||
order_id: Optional[str],
|
||||
strategy_id: Optional[str],
|
||||
broker_order_id: Optional[str] = None,
|
||||
@@ -155,6 +157,8 @@ class SlotService:
|
||||
forced_close_at: Optional[datetime] = None,
|
||||
stop_loss_price: Optional[Decimal] = None,
|
||||
take_profit_price: Optional[Decimal] = None,
|
||||
timeout_sec: Optional[int] = None,
|
||||
slippage_pct: Optional[float] = None,
|
||||
) -> Slot:
|
||||
"""Compute the post-transition Slot row.
|
||||
|
||||
@@ -174,7 +178,6 @@ class SlotService:
|
||||
qty=None,
|
||||
avg_price=None,
|
||||
limit_price=None,
|
||||
allocated_capital=None,
|
||||
order_id=None,
|
||||
strategy_id=None,
|
||||
broker_order_id=None,
|
||||
@@ -182,15 +185,22 @@ class SlotService:
|
||||
forced_close_at=None,
|
||||
stop_loss_price=None,
|
||||
take_profit_price=None,
|
||||
timeout_sec=None,
|
||||
slippage_pct=None,
|
||||
updated_at=datetime.now(tz=timezone.utc),
|
||||
)
|
||||
|
||||
# limit_price lifecycle (dcy-012 FR-1): set only on entering a pending
|
||||
# state (the order's limit); cleared on entering HOLDING/ERROR.
|
||||
# execution policy (dcy-022/REQ-068): same lifecycle as limit_price.
|
||||
if to_state in {SlotState.PENDING_BUY, SlotState.PENDING_SELL}:
|
||||
new_limit_price = limit_price if limit_price is not None else slot.limit_price
|
||||
else: # HOLDING / ERROR — pending order limit no longer applies.
|
||||
new_timeout_sec = timeout_sec if timeout_sec is not None else slot.timeout_sec
|
||||
new_slippage_pct = slippage_pct if slippage_pct is not None else slot.slippage_pct
|
||||
else: # HOLDING / ERROR — pending order policy no longer applies.
|
||||
new_limit_price = None
|
||||
new_timeout_sec = None
|
||||
new_slippage_pct = None
|
||||
|
||||
# qty semantics during a pending state = the ORDER quantity (dcy-021 D1/D4).
|
||||
# Entering PENDING_SELL takes the caller qty AS-IS so a full sell
|
||||
@@ -210,9 +220,6 @@ class SlotService:
|
||||
qty=new_qty,
|
||||
avg_price=avg_price if avg_price is not None else slot.avg_price,
|
||||
limit_price=new_limit_price,
|
||||
allocated_capital=(
|
||||
allocated_capital if allocated_capital is not None else slot.allocated_capital
|
||||
),
|
||||
order_id=order_id if order_id is not None else slot.order_id,
|
||||
strategy_id=strategy_id if strategy_id is not None else slot.strategy_id,
|
||||
broker_order_id=(
|
||||
@@ -235,6 +242,8 @@ class SlotService:
|
||||
take_profit_price=(
|
||||
take_profit_price if take_profit_price is not None else slot.take_profit_price
|
||||
),
|
||||
timeout_sec=new_timeout_sec,
|
||||
slippage_pct=new_slippage_pct,
|
||||
updated_at=datetime.now(tz=timezone.utc),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user