dcy-007(core 자율 안전 매도) 전체 delivery. BAT-010 기반 인프라가 미커밋 상태였고 BAT-011 트리거 레이어와 공유 파일에서 섞여 있어 한 커밋으로 묶음. BAT-010 (REQ-034/035/036/041, release-candidate): - WatchdogStore(application/watchdog.py) + snapshot RPC 워치독 갱신 - Slot stop_loss_price/take_profit_price 필드 + 004_add_price_thresholds.sql - SlotService.sell() 자율 매도 내부 경로 + idempotency - SafetySellConfig env 5종 파싱 BAT-011 (REQ-037/038/039/040/042, release-candidate): - SafetySellService(application/safety_sell.py): forced_close 만기 / stop-loss· take-profit KIS polling / 장 마감 강제 청산 + trex-safety-sell 7번째 스레드 - BrokerPort.get_current_price + KIS inquire-price(TTTC8397R/VTTC8397R, stck_prpr) + paper stub - SlotService.update_thresholds() + intent RPC update_thresholds 액션 (부분 갱신/null 해제, 워치독 last_seen_at 리셋) - baseline 3문서 정합(interface-contract/runtime-flows/project-structure) 거버넌스: REQ-034~042 Implemented 동기화, BAT-010/011 release-candidate, dcy-007 discovery 포함. 검증: 신규 38 테스트 green, 단위 236 passed/23 skipped, 인프라 게이트(PG+RabbitMQ) 259 passed(마이그레이션 001~004 round-trip 포함), ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.8 KiB
5.8 KiB
BAT-011 Design
- Status: confirmed
- Batch: BAT-011
Architecture Summary
BAT-011은 BAT-010 기반 위에 자동 매도를 실제로 발동하는 트리거 레이어를 추가한다.
trex-safety-sellworker 스레드 (runtime/daemon.py): 기존 6 스레드(execution/ledger/intent-rpc/snapshot-rpc/pause-rpc/snapshot-scheduler)에 7번째로 추가._safety_sell_loop()가 단일 통합 루프에서 forced_close_at 만기(REQ-037), stop-loss·take-profit 가격 체크(REQ-038), 장 마감 청산(REQ-039)을 순차 평가한다.- KIS 현재가 polling 어댑터 (
adapters/broker/kis.py확장):get_current_price(symbol) -> Decimal—TTTC8397R(실전)/VTTC8397R(모의) 시세 endpoint 호출,stck_prpr(직전 체결가) 파싱. paper 환경 stub. update_thresholdsintent RPC (application/messaging_handlers.py+domain/slot/service.py):make_intent_rpc_handler에 action 라우팅 추가,SlotService.update_thresholds()로 부분 갱신 + 워치독last_seen_at리셋.- baseline 문서 정합 (REQ-042): 구현 완료 후 3문서 갱신.
Changed Areas
- Docs:
docs/interface-contract.md—update_thresholdsaction 계약 + intent payloadstop_loss_price/take_profit_price선택 필드 + snapshot RPC 워치독 갱신 신호 명시 (REQ-040, REQ-042)docs/runtime-flows.md— 워치독 갱신·forced_close 체크·가격 체크·장 마감 청산 흐름 추가 +:114"A-5 미구현" 주석 갱신 (REQ-042)docs/project-structure.md—trex-safety-sell스레드 + env 5종 + KIS 현재가 polling/watchdog 모듈 반영 (REQ-042)
- Modules:
trex_core/runtime/daemon.py—trex-safety-sellworker 등록 +_safety_sell_loop()구현(forced_close/가격/장 마감 통합)trex_core/adapters/broker/kis.py—get_current_price(symbol)추가(TTTC8397R/VTTC8397R,stck_prpr)trex_core/application/broker_port.py— (필요 시)get_current_price포트 시그니처 추가; paper 더블 stub 구현trex_core/adapters/broker/paper.py—get_current_pricestub(고정/주입 가능 현재가) — 테스트·모의용trex_core/domain/slot/service.py—SlotService.update_thresholds(slot_id, stop_loss_price, take_profit_price, forced_close_at)추가 + 트리거 조건 평가 헬퍼(또는 worker 인라인)trex_core/application/messaging_handlers.py—make_intent_rpc_handler에action: "update_thresholds"라우팅 + holding-only 응답
- Interfaces:
Broker.get_current_price(symbol: str) -> Decimal | None(신규 포트 메서드)SlotService.update_thresholds(slot_id, stop_loss_price=..., take_profit_price=..., forced_close_at=...) -> str(부분 갱신, sentinel로 "미지정" vs "null 해제" 구분)- intent RPC
action: "update_thresholds"payload/response 계약 (REQ-040)
Key Decisions (초안 — 구현 전 확정 대상)
- 단일 통합 worker 루프 vs. 분리 스레드: REQ-037 Notes 권고에 따라
trex-safety-sell단일 스레드 통합 루프를 기본안으로 한다. 가격 polling 주기(TREX_STOP_LOSS_POLL_INTERVAL, 기본 30s)와 forced_close 주기(TREX_FORCED_CLOSE_CHECK_INTERVAL, 기본 60s)가 다르므로, 루프 base tick을 짧은 쪽(또는 GCD)으로 두고 각 체크에 last-run 타임스탬프로 sub-scheduling한다. (확정 필요) - KIS 현재가 어댑터 배치:
kis.py에get_current_price추가(별도kis_price.py분리 대신) — 토큰/세션/_account재사용 이점. paper는paper.pystub. (확정 필요) - WatchdogStore read 동시성: BAT-010 Key Decision 3 계약 승계 — GIL 보장 단일 dict, lock 없음. worker는
is_expired()/in_grace_period()read만 수행. - 장 마감 1회 발동 가드: in-memory
last_liquidation_date: date | None. 재시작 시 초기화(REQ-039 Notes). 같은 거래일 재발동 방지. 발동 시각 도래 판정은now(KST).time() >= liquidation_time and last_liquidation_date != today. update_thresholds부분 갱신 sentinel: payload에 키가 없으면 "유지", 키가null이면 "해제(NULL)"._UNSETsentinel로 두 경우를 구분(REQ-040 AC-4/AC-5).- 루프 예외 격리: tick 단위 try/except로 polling/sell 예외가 worker 스레드를 죽이지 않게 한다. 예외는 로그 후 다음 tick 지속(REQ-024 degrade 정책 정합).
Edge Cases
- 가격 polling 중 KIS 거부/타임아웃: 해당 슬롯 skip + 경고 로그, 다음 tick 재시도(R-4).
- forced_close와 stop-loss가 동시 충족: 둘 다
sell()호출하나 REQ-036 idempotency로 1회만 전이. - 장 마감 청산 시각이 forced_close 체크 사이에 도래: 통합 루프 tick에서 장 마감 평가가 우선 실행되도록 순서 고정.
update_thresholds로forced_close_at만 갱신: 나머지 필드 유지(sentinel),last_seen_at리셋(워치독 타이머 재시작, REQ-034).- non-holding 슬롯에
update_thresholds: 필드 불변 +{status: ignored, reason: "not_holding"}(REQ-040 AC-6). - 쿼터 상한 초과: 경고 로그만(본 batch 범위) — 동적 간격 조정은 out of scope.
Architecture Impact
- runtime-flows: safety-sell worker 4종 흐름(워치독 갱신은 snapshot RPC 측) 추가 — REQ-042에서 문서화.
- project-structure: 7번째 스레드
trex-safety-sell+ KIS 현재가 polling 어댑터 메서드 — REQ-042에서 문서화. - broker_port 계약 확장(
get_current_price)이 paper/kis 양 더블에 반영되어야 한다.
Reference Doc Update Plan
interface-contract.md:update_thresholdsaction + price 선택 필드 + snapshot RPC 워치독 신호 (REQ-042 AC-1/AC-2)runtime-flows.md: 4종 safety-sell 흐름 +:114A-5 주석 갱신 (REQ-042 AC-3/AC-4)project-structure.md:trex-safety-sell스레드 + env 5종 + 신규 모듈 (REQ-042 AC-5)