# req-038: stop-loss·take-profit KIS 가격 polling + 트리거 - Status: Implemented - Type: Integration - Priority: High - Owner: project maintainers ## Intent 워치독이 만료된 슬롯에 한해 KIS API(`TTTC8397R` 실전 / `VTTC8397R` 모의)로 현재가를 주기적으로 polling하고, `현재가 <= stop_loss_price`(손절) 또는 `현재가 >= take_profit_price`(익절) 조건 충족 시 자동 매도를 제출한다. 전략 모듈이 정상 활성 중인 슬롯에는 개입하지 않는다(dcy-007 FR-2, NFR-3, A-3). ## Requirement - 데몬은 `TREX_STOP_LOSS_POLL_INTERVAL(초)` 주기로, `stop_loss_price` 또는 `take_profit_price`가 설정되고 **워치독이 만료된** `holding` 슬롯들의 현재가를 KIS 주식현재가 시세 API(`/uapi/domestic-stock/v1/quotations/inquire-price`, tr_id `FHKST01010100`, 실전/모의 공용)로 polling해야 한다. (CHG-20260607-04: 시세 tr_id 정정 — 구 `TTTC8397R`/`VTTC8397R`는 실 KIS 에서 "없는 서비스 코드".) - 현재가 평가 기준값은 `stck_prpr`(직전 체결가) 필드를 사용한다. - `현재가 <= stop_loss_price` 조건 충족 시 손절 매도를 제출해야 한다. - `현재가 >= take_profit_price` 조건 충족 시 익절 매도를 제출해야 한다. - 두 조건은 동일 polling 루프에서 평가한다. - 워치독 활성(만료되지 않은) 슬롯과 임계값 미설정(`None`) 슬롯은 polling 대상이 아니다. - KIS API 호출 상한은 `워치독 만료 대상 슬롯 수 × (3600 / TREX_STOP_LOSS_POLL_INTERVAL)`로 산정한다. 이 상한이 KIS 쿼터를 초과할 경우 polling 간격 조정 AC를 이 REQ의 Notes에 명시하고 REQ 단계에서 상한 경고를 구현한다. - 자동 매도 제출은 `SlotService.sell()`(REQ-036)을 사용한다. ## Acceptance Criteria 1. Given `holding`, 워치독 만료, `stop_loss_price=Decimal("50000")`, `take_profit_price=None`인 슬롯 S가 있고 KIS polling 결과 `stck_prpr=49000`이면, When 가격 체크가 수행되면, Then S에 대해 `SlotService.sell()`이 호출된다(손절 트리거). 2. Given `holding`, 워치독 만료, `stop_loss_price=None`, `take_profit_price=Decimal("80000")`인 슬롯 S가 있고 KIS polling 결과 `stck_prpr=81000`이면, When 가격 체크가 수행되면, Then S에 대해 `sell()`이 호출된다(익절 트리거). 3. Given `holding`, 워치독 만료, `stop_loss_price=Decimal("50000")`, `take_profit_price=Decimal("80000")`인 슬롯 S가 있고 `stck_prpr=65000`이면, When 가격 체크가 수행되면, Then `sell()`이 호출되지 않는다(두 조건 모두 미충족). 4. Given `holding`, 워치독 **활성**인 슬롯 S가 있을 때, When 가격 polling이 수행되면, Then S에 대한 KIS API 호출이 발생하지 않는다(워치독 보호). 5. Given `stop_loss_price=None`이고 `take_profit_price=None`인 슬롯 S가 있을 때, When polling이 수행되면, Then S는 polling 대상에서 제외된다. 6. Given 이미 `pending_sell` 상태인 슬롯 S에 조건이 충족될 때, When 가격 체크가 수행되면, Then 중복 `sell()` 호출이 발생하지 않는다(REQ-036 idempotency). 7. Given `broker=kis` 일 때, When 가격 polling을 수행하면, Then KIS 주식현재가 시세 endpoint(`/uapi/domestic-stock/v1/quotations/inquire-price`)를 tr_id `FHKST01010100`(실전/모의 공용)로 호출하고 `output.stck_prpr` 를 파싱한다. 실 KIS LIVE 호출이 `rt_cd=0` + `stck_prpr` 를 반환함을 확인한다(구 `TTTC8397R` 는 `rt_cd=1` "없는 서비스 코드" 로 거부됨). 8. Given `TREX_STOP_LOSS_POLL_INTERVAL=30`이고 워치독 만료 대상 슬롯이 N개일 때, When 정상 동작 중이면, Then 1시간당 KIS API 호출 수가 `N × 120`회를 넘지 않는다(NFR-3 쿼터 산정). ## Impacted Area - Docs: - `docs/runtime-flows.md` — stop-loss·take-profit 가격 체크 흐름 추가 (REQ-042 참조) - `docs/interface-contract.md` — 현재가 polling 어댑터 계약 명시 (REQ-042 참조) - Modules: - 신규 `trex_core/adapters/broker/kis_price.py` 또는 `kis.py` 확장 — `TTTC8397R`/`VTTC8397R` 현재가 polling, `stck_prpr` 파싱 - `trex_core/runtime/daemon.py` — stop-loss·take-profit 가격 체크를 safety-sell worker에 통합 - `trex_core/domain/slot/service.py` — stop-loss·take-profit 조건 평가 메서드(또는 worker 내 인라인) - Tests: - KIS 현재가 polling 어댑터 단위 테스트 — `stck_prpr` 파싱 확인, 실전/모의 분기 확인 - 가격 체크 단위 테스트(mock polling) — 손절/익절/미충족/워치독 보호 시나리오 ## Notes - KIS 현재가 polling 어댑터는 기존 `trex_core/adapters/broker/kis.py`에 `get_current_price(symbol)` 메서드로 추가하거나, 별도 모듈로 분리한다. batch planning에서 결정한다. - paper broker 모의 환경에서는 stub 구현(`set_current_price`/`get_current_price`)으로 현재가를 반환한다(KIS 시세 호출 없음). 실 KIS(`broker=kis`) 는 `FHKST01010100` 시세 tr 사용. - KIS 쿼터 초과 방지: 워치독 만료 슬롯 수가 임계값을 넘으면 polling 간격 자동 조정 또는 경고 로그를 구현한다. 구체적인 쿼터 수치는 KIS API 문서 기준으로 batch 설계에서 확정한다. - `TREX_STOP_LOSS_POLL_INTERVAL` env var는 REQ-041 참조. ## Change Log ### CHG-20260607-01 - Date: 2026-06-07 - Author: Justin Ko #### Change Summary - dcy-007 confirmed → REQ-038 초안 생성 #### Intent - stop-loss·take-profit KIS 가격 polling + 트리거(FR-2)를 독립 Integration REQ로 정규화 #### Acceptance Criteria Delta - Added: AC-1~8 전체 #### Impacted Area Delta - Docs: runtime-flows.md, interface-contract.md (REQ-042에서 처리) - Modules: kis 현재가 polling 어댑터(신규/확장), daemon.py, service.py - Tests: polling 어댑터 단위 + 가격 체크 시나리오 단위 (mock) #### Delivery Trace Delta - Batches: none (미할당) - Releases: none #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current - Follow-up Action: suggest-batch-reqs 후 batch 배정 ### CHG-20260607-02 - Date: 2026-06-07 - Author: Justin Ko #### Change Summary - Proposed → Approved (confirm-req) #### Intent - 승인 게이트 통과: 필수 필드·AC·Impacted Area·Change Log 완비 확인. dcy-007(confirmed) 범위 및 결정 사항과 모순 없음. #### Acceptance Criteria Delta - (변경 없음) #### Impacted Area Delta - (변경 없음) #### Delivery Trace Delta - Batches: none (미할당) - Releases: none #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current - Follow-up Action: suggest-batch-reqs 후 batch 배정 ### CHG-20260607-03 - Date: 2026-06-07 - Author: Justin Ko #### Change Summary - Status Approved → Implemented (confirm-req-implemented, 근거 batch BAT-011 release-candidate). #### Intent - BAT-011 verification.md 의 AC↔evidence(KIS `get_current_price` TTTC8397R/VTTC8397R·`stck_prpr` 파싱 + 손절/익절/미충족/워치독 보호/쿼터 산정/가격 미수신 skip)로 AC-1~8 충족 → 구현 완료 동기화. #### Acceptance Criteria Delta - (변경 없음) #### Impacted Area Delta - (변경 없음) #### Delivery Trace Delta - Batches: BAT-011 (release-candidate) - Releases: none #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current (Implemented) - Follow-up Action: draft-release 로 BAT-011 배포 편성. ### CHG-20260607-04 - Date: 2026-06-07 - Author: Justin Ko #### Change Summary - 현재가 시세 tr_id 정정: `TTTC8397R`(실전)/`VTTC8397R`(모의) → `FHKST01010100`(주식현재가 시세, 실전/모의 공용). Status Implemented → Approved (AC-7 evidence 무효). #### Intent - 배포 전 실 KIS LIVE 진단(읽기 전용 시세 호출)에서 구 tr_id 가 `rt_cd=1` "없는 서비스 코드 입니다"(OPSQ0002) 로 거부됨을 확인. 이 경우 `get_current_price` 가 항상 None 을 반환 → stop-loss/take-profit 가 LIVE 에서 **조용히 미발동**. 표준 시세 tr `FHKST01010100` 는 `rt_cd=0` + `stck_prpr=329000`(005930) 정상 수신. mock/샌드박스로는 잡히지 않는 결함이라 LIVE 검증으로 포착. #### Acceptance Criteria Delta - Updated: - AC-7: 시세 endpoint/tr_id 를 `FHKST01010100`(실전/모의 공용)로 정정. 실 KIS LIVE `rt_cd=0` + `stck_prpr` 수신 검증 추가. - Added: - 없음 - Removed: - 없음 (TTTC8397R/VTTC8397R 분기 폐기) #### Impacted Area Delta - Docs: - 없음 (REQ 본문 + 후속 baseline 은 verification 단계 갱신) - Modules: - `trex_core/adapters/broker/kis.py` (`_tr_price` → `FHKST01010100`, live/mock 분기 제거) - Tests: - `tests/adapters/broker/test_kis_price.py` (AC-7 tr_id 단언 정정) #### Delivery Trace Delta - Batches: BAT-011 (release-candidate — AC-7 재구현/재검증) - Releases: REL-003 (confirmed — 본 정정 반영 후 재확인 필요) #### Revalidation Impact - Reverification Needed: yes (AC-7 한정; AC-1~6/8 은 paper/단위 evidence 유효) - Existing Implementation Invalidated: yes (AC-7 — 구 tr_id 코드/테스트) - Status Recommendation: Implemented → Approved (AC-7 재검증 전까지) - Follow-up Action: kis.py 수정 + LIVE 시세 재검증 + 단위 정정 → confirm-req-implemented 로 Implemented 재전환 → REL-003 재확인. #### Validation Plan - 실 KIS LIVE `get_current_price('005930')` → Decimal(약 329000) 반환 확인(읽기 전용). - `test_kis_price.py` AC-7: `_tr_price()` == `FHKST01010100` 단언 + `stck_prpr` 파싱. - 단위 전체 회귀 + ruff. ### CHG-20260607-05 - Date: 2026-06-07 - Author: Justin Ko #### Change Summary - Status Approved → Implemented (confirm-req-implemented, CHG-04 정정 재검증 완료). #### Intent - CHG-04 의 tr_id 정정(`FHKST01010100`)을 구현·재검증: `kis.py _tr_price()` 수정, `test_kis_price.py` AC-7 정정(7 passed), 단위 전체 236 passed/ruff clean, **실 KIS LIVE `get_current_price('005930')=Decimal('329000')`(rt_cd=0)** 확인. AC-7 evidence 복구 → 전체 AC 충족. #### Acceptance Criteria Delta - (변경 없음) #### Impacted Area Delta - (변경 없음) #### Delivery Trace Delta - Batches: BAT-011 (release-candidate) - Releases: REL-003 (confirmed — 정정 반영 후 재확인) #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current (Implemented) - Follow-up Action: REL-003 재확인 후 배포. ### CHG-20260608-01 - Date: 2026-06-08 - Author: Justin Ko #### Change Summary - (change-req, dcy-017/issue #12) SL/TP 발동 가시화: `stop_loss` 발동=WARN, `take_profit` 발동=INFO(`_log_fire`) + 카운터(`fire:stop_loss`/`fire:take_profit`). 구조화 필드(slot_id/portfolio_id/reason/phase/instance_key). 판정·KIS polling 동작 불변(관측만). BAT-021 재구현·재검증 완료 → Implemented 유지. #### Intent - 손절/익절 발동을 무성에서 가시화(OQ-2: stop_loss=WARN 주의사건, take_profit=INFO). #### Acceptance Criteria Delta - Added: - stop_loss(WARN)/take_profit(INFO) 발동 로그 + 카운터. #### Impacted Area Delta - Modules: `trex_core/application/safety_sell.py`(check_prices 발동 가시화) - Tests: tests/application/test_safety_sell_visibility.py::test_take_profit_fire_logs_info #### Delivery Trace Delta - Batches: BAT-021 - Releases: none #### Revalidation Impact - Reverification Needed: yes (BAT-021 에서 완료) - Existing Implementation Invalidated: no (관측만 추가) - Status Recommendation: Implemented (change-req → BAT-021 재검증) - Follow-up Action: confirm-req-implemented(BAT-021). #### Validation Plan - BAT-021: take_profit 발동 INFO + 카운터 단위.