feat(signal_v2-phase3b): kis_client.get_daily_ohlcv (60 daily bars)
TR_ID FHKST03010100 (수정주가 일봉). KIS returns descending; client reverses to ascending and trims to last N days. 1 new test, 34 total. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
@@ -153,3 +153,41 @@ class KISClient:
|
|||||||
"current_price": current_price,
|
"current_price": current_price,
|
||||||
"as_of": datetime.now(KST).isoformat(),
|
"as_of": datetime.now(KST).isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def get_daily_ohlcv(self, ticker: str, days: int = 60) -> list[dict]:
|
||||||
|
"""KRX 일봉 OHLCV (TR_ID FHKST03010100).
|
||||||
|
|
||||||
|
Returns: [{"datetime", "open", "high", "low", "close", "volume"}, ...]
|
||||||
|
시간 오름차순.
|
||||||
|
"""
|
||||||
|
path = "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice"
|
||||||
|
today = datetime.now(KST).strftime("%Y%m%d")
|
||||||
|
start_date = (datetime.now(KST) - timedelta(days=days * 2)).strftime("%Y%m%d")
|
||||||
|
params = {
|
||||||
|
"FID_COND_MRKT_DIV_CODE": "J",
|
||||||
|
"FID_INPUT_ISCD": ticker,
|
||||||
|
"FID_INPUT_DATE_1": start_date,
|
||||||
|
"FID_INPUT_DATE_2": today,
|
||||||
|
"FID_PERIOD_DIV_CODE": "D",
|
||||||
|
"FID_ORG_ADJ_PRC": "1",
|
||||||
|
}
|
||||||
|
raw = await self._request_with_retry(
|
||||||
|
"GET", path, tr_id="FHKST03010100", params=params,
|
||||||
|
)
|
||||||
|
output2 = raw.get("output2", [])
|
||||||
|
bars = []
|
||||||
|
for row in output2:
|
||||||
|
try:
|
||||||
|
date = row["stck_bsop_date"]
|
||||||
|
bars.append({
|
||||||
|
"datetime": f"{date[:4]}-{date[4:6]}-{date[6:]}",
|
||||||
|
"open": int(row["stck_oprc"]),
|
||||||
|
"high": int(row["stck_hgpr"]),
|
||||||
|
"low": int(row["stck_lwpr"]),
|
||||||
|
"close": int(row["stck_clpr"]),
|
||||||
|
"volume": int(row["acml_vol"]),
|
||||||
|
})
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
continue
|
||||||
|
bars.reverse()
|
||||||
|
return bars[-days:]
|
||||||
|
|||||||
@@ -126,3 +126,36 @@ async def test_get_asking_price_computes_bid_ratio(kis_client_factory):
|
|||||||
assert "as_of" in data
|
assert "as_of" in data
|
||||||
finally:
|
finally:
|
||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
async def test_get_daily_ohlcv_returns_60_bars(kis_client_factory):
|
||||||
|
"""KIS daily endpoint returns 60 ascending bars after parsing."""
|
||||||
|
# Build 60 KIS-format daily bars (descending dates as KIS does)
|
||||||
|
sample_output2 = []
|
||||||
|
for i in range(60):
|
||||||
|
# Generate a fake date 60 days ago, descending
|
||||||
|
day = 60 - i
|
||||||
|
sample_output2.append({
|
||||||
|
"stck_bsop_date": f"2026{(((day-1)//30)+1):02d}{(((day-1)%30)+1):02d}",
|
||||||
|
"stck_oprc": "78000", "stck_hgpr": "78500",
|
||||||
|
"stck_lwpr": "77800", "stck_clpr": str(78000 + i),
|
||||||
|
"acml_vol": "12345",
|
||||||
|
})
|
||||||
|
|
||||||
|
respx.get(
|
||||||
|
"https://openapivts.koreainvestment.com:29443/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice"
|
||||||
|
).mock(return_value=httpx.Response(200, json={"output2": sample_output2}))
|
||||||
|
|
||||||
|
client = kis_client_factory()
|
||||||
|
try:
|
||||||
|
bars = await client.get_daily_ohlcv("005930", days=60)
|
||||||
|
# KIS returns descending; client reverses to ascending
|
||||||
|
assert len(bars) == 60
|
||||||
|
# Ascending order: first item has smaller datetime than last
|
||||||
|
assert bars[0]["datetime"] < bars[-1]["datetime"]
|
||||||
|
assert isinstance(bars[0]["open"], int)
|
||||||
|
assert isinstance(bars[0]["close"], int)
|
||||||
|
assert "datetime" in bars[0]
|
||||||
|
finally:
|
||||||
|
await client.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user