매도(sell) 주문이 현재가가 아닌 매수가(entry_price) 지정가로 나가던 버그(issue #10) 정정. 슬롯의 매수 전용 entry_price 를 side 무관 "현재 pending 주문 지정가" limit_price 로 내부 rename 하고, pending 이탈(→holding/→closed) 시 clear 해 매수 지정가가 매도로 누수되는 경로를 구조적으로 제거. 거버넌스: 전부 change-req (신규 REQ 없음, dcy-012 OQ-3). - REQ-002: Slot entry_price→limit_price rename + clear-on-exit 생애주기 - REQ-006: daily_slot 컬럼 rename 마이그레이션 006 (idempotent) + persistence 매핑 - REQ-013: _dispatch 가 slot.limit_price 단일 필드로 가격/주문유형 결정 - REQ-007: SellIntent/sell payload optional limit_price + _admit_sell 운반 - REQ-036: SlotService.sell optional 가격 → 자율 매도(safety-sell/kill-switch) 시장가 - REQ-003: 컬럼 계약/드리프트 가드 limit_price 반영 - REQ-015: runtime-flows/interface-contract/data-model 정합 매도 정책(OQ-1 하이브리드): 가격 지정→지정가("00"), 미지정→시장가("01"). 자율 매도는 가격 미전달 → 항상 시장가 (손절 미체결 pending_sell 갇힘 방지, issue #9 연계). 검증: 단위 286 passed/32 skipped, 인프라 게이트(PG+RabbitMQ) 318 passed, ruff clean. 신규 test_sell_pricing(recording broker)로 지정가 매수 슬롯의 매도가 매수가 재사용 안 함을 직접 검증. BAT-016 release-candidate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.7 KiB
3.7 KiB
BAT-016 Implementation
- Status: completed
- Batch: BAT-016
구현 요약
- dcy-012 매도 가격 버그(issue #10)를 슬롯
entry_price→limit_pricerename + clear-on-exit 생애주기 + dispatch 단일 필드 결정으로 정정. 전부 change-req(신규 REQ 없음).
변경 내역
REQ-002 — 슬롯 도메인 rename + 생애주기
trex_core/domain/slot/models.py:Slot.entry_price→limit_price(주석: side 무관 pending 주문 지정가, clear-on-exit);EXPECTED_SLOT_FIELDS의 entry_price → limit_price.trex_core/domain/slot/service.py:transit_slot/_apply_transition_fields파라미터entry_price→limit_price.- IDLE reset 분기:
entry_price=None→limit_price=None. - generic 경로:
to_state ∈ {PENDING_BUY, PENDING_SELL}면 set(입력 or 유지), 아니면(HOLDING/CLOSED)new_limit_price=Noneclear. SlotService.sell(..., *, limit_price=None)추가 → transit_slot 에 전달.
REQ-013 — dispatch 단일 필드
trex_core/application/execution.py::_dispatch:limit = int(slot.limit_price) if slot.limit_price else None(side 분기/entry_price 재사용 제거). order_type 은 limit 유무로 "00"/"01".
REQ-007 — sell intent 가격
trex_core/application/intents.py:SellIntent.limit_price: Optional[float] = None;_admit_sell→transit_slot(..., limit_price=intent.limit_price);_admit_buy→limit_price=intent.entry_price(BuyIntent.entry_price 외부 필드 유지).trex_core/application/messaging_handlers.py: sell 핸들러limit_price=_opt_float(body.get("limit_price")); buy payloadentry_price유지;_slot_to_dict외부 키"entry_price"유지하되s.limit_price매핑(NFR-3).
REQ-036 — 자동 매도 시장가
SlotService.selloptionallimit_price(default None) → safety-sell/kill-switch 자율 매도는 가격 미전달 → 시장가.
REQ-006 — 마이그레이션 + persistence
trex_core/adapters/persistence/sql/006_rename_entry_price_to_limit_price.sql(신규): idempotent DO-block rename(컬럼 존재 && 신컬럼 부재 시에만). 순수 rename → 데이터 보존.trex_core/adapters/persistence/postgresql.py:_SLOT_COLUMNS/update_slot SQL+바인딩/_row_to_slot 의 entry_price → limit_price.
REQ-003 — 컬럼 계약 가드
EXPECTED_SLOT_FIELDSrename 으로test_contract.py/test_migration_and_drift.py자동 반영(상수 참조). PG 게이트에서 실 컬럼 limit_price 검증.
REQ-015 — baseline 문서
docs/runtime-flows.md: dispatch 가격 결정(slot.limit_price 단일 필드) + limit_price 생애주기 set/clear. Last Updated → BAT-016.docs/interface-contract.md: sell payload optional limit_price + ORD_DVSN 매핑. Last Updated → BAT-016.docs/data-model.md: 슬롯 limit_price 컬럼 의미/생애주기. Last Updated → BAT-016.
테스트
- 신규
tests/application/test_sell_pricing.py(recording paper broker): 4건 — (a) 지정가 매수+시장가 매도(entry_price 미사용), (b) 지정가 매수+지정가 매도, (c) SlotService.sell 시장가+closed, (d) 시장가 매수 매도 회귀. tests/domain/slot/test_service.py: PENDING_BUY→HOLDING 시limit_price is None단언 추가; 두 →HOLDING 전이의 obsoleteentry_price=인자 제거.
검증 실행
- 단위:
TREX_BROKER=paper .venv/bin/python -m pytest -q→286 passed / 32 skipped(::test_sandbox_token_and_submitdeselect — LIVE.env환경 의존, 본 batch 무관). - 인프라 게이트:
TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q→318 passed(006 rename 마이그레이션 적용 + 컬럼 계약 limit_price + 마이그레이션 idempotency 포함). ruff check trex_core tests→ All checks passed.