BE 회신(holdings_intel.py:109-118)에 맞춰 반전 기준을 price<day_open → price<day_high×climax_close_pct(윗꼬리)로 변경. - kis_client.get_quote에 day_high(stck_hgpr) 추가 - monitor._build_ctx가 day_high를 ctx로 전달 - climax_vol_x·climax_close_pct를 monitor-set exit_params에서 읽기 (fallback: TM_CLIMAX_VOL_MULT/0.97) - 테스트 36/36 (climax exit_params 2건 추가) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N83vbXEA8h83GMXQcg8fxD
57 lines
2.2 KiB
Python
57 lines
2.2 KiB
Python
"""KISClient — 토큰 발급/캐시 + quote/daily 파싱 (respx)."""
|
|
import httpx
|
|
import respx
|
|
|
|
from kis_client import KISClient
|
|
|
|
BASE = "https://openapi.koreainvestment.com:9443"
|
|
|
|
|
|
def _client():
|
|
return KISClient("APPKEY", "APPSECRET", "12345678-01", is_virtual=False)
|
|
|
|
|
|
@respx.mock
|
|
async def test_issue_token_cached():
|
|
route = respx.post(f"{BASE}/oauth2/tokenP").mock(
|
|
return_value=httpx.Response(200, json={"access_token": "TKN", "expires_in": 86400}))
|
|
c = _client()
|
|
t1 = await c._issue_token()
|
|
t2 = await c._issue_token()
|
|
assert t1 == "TKN" and t2 == "TKN"
|
|
assert route.call_count == 1 # 캐시 → 1회만 발급
|
|
await c.close()
|
|
|
|
|
|
@respx.mock
|
|
async def test_get_quote_parses():
|
|
respx.post(f"{BASE}/oauth2/tokenP").mock(
|
|
return_value=httpx.Response(200, json={"access_token": "TKN", "expires_in": 86400}))
|
|
respx.get(f"{BASE}/uapi/domestic-stock/v1/quotations/inquire-price").mock(
|
|
return_value=httpx.Response(200, json={"output": {
|
|
"stck_prpr": "71500", "stck_oprc": "71000", "stck_hgpr": "72000",
|
|
"acml_vol": "1234567"}}))
|
|
c = _client()
|
|
q = await c.get_quote("005930")
|
|
assert q["price"] == 71500 and q["day_open"] == 71000 and q["today_volume"] == 1234567
|
|
assert q["day_high"] == 72000
|
|
await c.close()
|
|
|
|
|
|
@respx.mock
|
|
async def test_get_daily_ascending():
|
|
respx.post(f"{BASE}/oauth2/tokenP").mock(
|
|
return_value=httpx.Response(200, json={"access_token": "TKN", "expires_in": 86400}))
|
|
# KIS는 내림차순 반환 → 오름차순으로 뒤집혀야 함
|
|
respx.get(f"{BASE}/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice").mock(
|
|
return_value=httpx.Response(200, json={"output2": [
|
|
{"stck_bsop_date": "20260702", "stck_oprc": "100", "stck_hgpr": "110",
|
|
"stck_lwpr": "90", "stck_clpr": "105", "acml_vol": "5"},
|
|
{"stck_bsop_date": "20260701", "stck_oprc": "95", "stck_hgpr": "102",
|
|
"stck_lwpr": "94", "stck_clpr": "100", "acml_vol": "4"}]}))
|
|
c = _client()
|
|
bars = await c.get_daily_ohlcv("005930", days=250)
|
|
assert bars[0]["datetime"] == "2026-07-01"
|
|
assert bars[-1]["close"] == 105
|
|
await c.close()
|