## REQ-038 CHG-04/05: KIS 시세 tr_id 정정 (FHKST01010100)
- kis.py _tr_price(): TTTC8397R/VTTC8397R → FHKST01010100 (실전/모의 공용)
배포 전 실 KIS LIVE 진단에서 구 tr_id가 rt_cd=1 "없는 서비스 코드"로 거부됨을
확인 — stop-loss/take-profit이 LIVE에서 조용히 미발동하는 결함이었음.
FHKST01010100으로 정정 후 get_current_price('005930')=329000(rt_cd=0) 확인.
- test_kis_price.py: AC-7 테스트 정정 (FHKST01010100 공용 검증)
- REQ-038: CHG-04/05 Change Log 추가, Requirement/AC-7/Notes 정정
- BAT-011 implementation.md: LIVE 검증 및 tr_id 정정 이력 추가
- BAT-011 verification.md: AC-7 evidence를 CHG-04 정정 버전으로 갱신
- docs/srs/index.md: CHG-04/05 Change Log 항목 추가
## REL-003: core 자율 안전 매도 release 문서 생성
- docs/releases/rel-003_20260607_core-autonomous-safety-sell.md 신규 생성
(BAT-010 + BAT-011, app-service profile, Status: confirmed)
- docs/releases/index.md: REL-003 행 추가
## dcy-008: 비상 정지/강제 청산(kill-switch) Discovery 초안
- docs/discovery/dcy-008_20260607_emergency-stop-kill-switch.md 신규 생성
GitHub #3 기반; SlotService.sell() 재사용, 당일 전략 제외 day-scoped
FR 6개, NFR 3개, OQ 4개 (트리거 표면/영속 방식/수동 해제 결정 필요)
- docs/discovery/index.md: dcy-008 행 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
3.3 KiB
Python
104 lines
3.3 KiB
Python
"""KIS current-price adapter unit tests (REQ-038 / BAT-011).
|
|
|
|
Network is monkeypatched — these run always (no KIS credentials needed).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from decimal import Decimal
|
|
|
|
import httpx
|
|
|
|
from trex_core.adapters.broker.kis import KISBroker
|
|
from trex_core.adapters.broker.paper import PaperBroker
|
|
|
|
_MOCK_URL = "https://openapivts.koreainvestment.com:29443"
|
|
_LIVE_URL = "https://openapi.koreainvestment.com:9443"
|
|
|
|
|
|
class _FakeResp:
|
|
def __init__(self, payload):
|
|
self._payload = payload
|
|
|
|
def json(self):
|
|
return self._payload
|
|
|
|
|
|
def _mock_broker():
|
|
return KISBroker("12345678-01", app_key="k", app_secret="s", base_url=_MOCK_URL)
|
|
|
|
|
|
def _live_broker():
|
|
return KISBroker("12345678-01", app_key="k", app_secret="s",
|
|
base_url=_LIVE_URL, allow_live=True)
|
|
|
|
|
|
def test_tr_price_is_quotations_tr():
|
|
"""AC-7 (CHG-20260607-04): 주식현재가 시세 uses FHKST01010100, live/mock 공용.
|
|
|
|
The old TTTC8397R/VTTC8397R were rejected LIVE as "없는 서비스 코드".
|
|
"""
|
|
assert _live_broker()._tr_price() == "FHKST01010100"
|
|
assert _mock_broker()._tr_price() == "FHKST01010100"
|
|
|
|
|
|
def test_get_current_price_parses_stck_prpr(monkeypatch):
|
|
"""get_current_price parses output.stck_prpr into a Decimal."""
|
|
captured = {}
|
|
|
|
def fake_get(url, headers=None, params=None, timeout=None):
|
|
captured["url"] = url
|
|
captured["tr_id"] = headers["tr_id"]
|
|
captured["params"] = params
|
|
return _FakeResp({"rt_cd": "0", "output": {"stck_prpr": "49000"}})
|
|
|
|
b = _mock_broker()
|
|
monkeypatch.setattr(b, "_ensure_token", lambda: "tok")
|
|
monkeypatch.setattr(httpx, "get", fake_get)
|
|
price = b.get_current_price("005930")
|
|
assert price == Decimal("49000")
|
|
assert captured["tr_id"] == "FHKST01010100"
|
|
assert captured["params"]["FID_INPUT_ISCD"] == "005930"
|
|
|
|
|
|
def test_get_current_price_network_error_returns_none(monkeypatch):
|
|
"""Network error → None (worker skips the slot, no false trigger)."""
|
|
def boom(*a, **kw):
|
|
raise httpx.ConnectError("down")
|
|
|
|
b = _mock_broker()
|
|
monkeypatch.setattr(b, "_ensure_token", lambda: "tok")
|
|
monkeypatch.setattr(httpx, "get", boom)
|
|
assert b.get_current_price("005930") is None
|
|
|
|
|
|
def test_get_current_price_error_rt_cd_returns_none(monkeypatch):
|
|
"""Non-zero rt_cd → None."""
|
|
def fake_get(*a, **kw):
|
|
return _FakeResp({"rt_cd": "1", "msg1": "error"})
|
|
|
|
b = _mock_broker()
|
|
monkeypatch.setattr(b, "_ensure_token", lambda: "tok")
|
|
monkeypatch.setattr(httpx, "get", fake_get)
|
|
assert b.get_current_price("005930") is None
|
|
|
|
|
|
def test_get_current_price_missing_field_returns_none(monkeypatch):
|
|
"""Missing/blank stck_prpr → None."""
|
|
def fake_get(*a, **kw):
|
|
return _FakeResp({"rt_cd": "0", "output": {"stck_prpr": ""}})
|
|
|
|
b = _mock_broker()
|
|
monkeypatch.setattr(b, "_ensure_token", lambda: "tok")
|
|
monkeypatch.setattr(httpx, "get", fake_get)
|
|
assert b.get_current_price("005930") is None
|
|
|
|
|
|
class TestPaperCurrentPrice:
|
|
def test_unset_returns_none(self):
|
|
assert PaperBroker().get_current_price("005930") is None
|
|
|
|
def test_set_and_get(self):
|
|
b = PaperBroker()
|
|
b.set_current_price("005930", Decimal("51000"))
|
|
assert b.get_current_price("005930") == Decimal("51000")
|