Files
stockautomtion/app/services/market_data.py

138 lines
4.8 KiB
Python

from __future__ import annotations
import httpx
from app.core.auth import token_manager
from app.core.config import get_base_url, settings
from app.core.logger import setup_logger
from app.core.rate_limiter import RateLimiter
logger = setup_logger("market_data")
_PRICE_URL = "/uapi/domestic-stock/v1/quotations/inquire-price"
_CHART_URL = "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice"
_ORDERBOOK_URL = "/uapi/domestic-stock/v1/quotations/inquire-asking-price"
_FID_INPUT_ISCD = "FID_INPUT_ISCD"
_FID_COND_MRKT_DIV_CODE = "FID_COND_MRKT_DIV_CODE"
class MarketDataService:
def __init__(self) -> None:
self._client = httpx.AsyncClient(timeout=10.0)
self._rate_limiter = RateLimiter(settings.rate_limit.requests_per_second)
def _check_auth(self) -> bool:
if not token_manager.is_authenticated:
logger.debug("API 인증되지 않음 - 시세 조회 불가")
return False
return True
async def get_current_price(self, stock_code: str) -> dict:
if not self._check_auth():
return {}
await self._rate_limiter.acquire()
headers = token_manager.get_auth_headers("FHKST01010100")
params = {_FID_COND_MRKT_DIV_CODE: "J", _FID_INPUT_ISCD: stock_code}
url = f"{get_base_url()}{_PRICE_URL}"
resp = await self._client.get(url, headers=headers, params=params)
resp.raise_for_status()
data = resp.json()
if data.get("rt_cd") != "0":
logger.warning("현재가 조회 실패: %s - %s", stock_code, data.get("msg1"))
return {}
output = data.get("output", {})
return {
"stock_code": stock_code,
"current_price": int(output.get("stck_prpr", 0)),
"change_price": int(output.get("prdy_vrss", 0)),
"change_rate": float(output.get("prdy_ctrt", 0)),
"open_price": int(output.get("stck_oprc", 0)),
"high_price": int(output.get("stck_hgpr", 0)),
"low_price": int(output.get("stck_lwpr", 0)),
"volume": int(output.get("acml_vol", 0)),
"trade_amount": int(output.get("acml_tr_pbmn", 0)),
"stock_name": output.get("hts_kor_isnm", ""),
}
async def get_daily_chart(
self, stock_code: str, start_date: str, end_date: str, count: int = 30
) -> list[dict]:
if not self._check_auth():
return []
await self._rate_limiter.acquire()
headers = token_manager.get_auth_headers("FHKST03010200")
params = {
_FID_COND_MRKT_DIV_CODE: "J",
_FID_INPUT_ISCD: stock_code,
"FID_INPUT_DATE_1": start_date,
"FID_INPUT_DATE_2": end_date,
"FID_PERIOD_DIV_CODE": "D",
"FID_ADJ_PRC": "1",
}
url = f"{get_base_url()}{_CHART_URL}"
resp = await self._client.get(url, headers=headers, params=params)
resp.raise_for_status()
data = resp.json()
if data.get("rt_cd") != "0":
logger.warning("차트 조회 실패: %s - %s", stock_code, data.get("msg1"))
return []
result = []
for item in data.get("output2", [])[:count]:
result.append({
"date": item.get("stck_bsop_date", ""),
"open": int(item.get("stck_oprc", 0)),
"high": int(item.get("stck_hgpr", 0)),
"low": int(item.get("stck_lwpr", 0)),
"close": int(item.get("stck_clpr", 0)),
"volume": int(item.get("acml_vol", 0)),
})
return result
async def get_orderbook(self, stock_code: str) -> dict:
if not self._check_auth():
return {}
await self._rate_limiter.acquire()
headers = token_manager.get_auth_headers("FHKST01010200")
params = {_FID_COND_MRKT_DIV_CODE: "J", _FID_INPUT_ISCD: stock_code}
url = f"{get_base_url()}{_ORDERBOOK_URL}"
resp = await self._client.get(url, headers=headers, params=params)
resp.raise_for_status()
data = resp.json()
if data.get("rt_cd") != "0":
return {}
output = data.get("output", [{}])[0] if data.get("output") else {}
return {
"stock_code": stock_code,
"bid_prices": [
int(output.get(f"phsc_kprc_{i}", 0)) for i in range(1, 6)
],
"ask_prices": [
int(output.get(f"sats_kprc_{i}", 0)) for i in range(1, 6)
],
"bid_volumes": [
int(output.get(f"phsc_vola_{i}", 0)) for i in range(1, 6)
],
"ask_volumes": [
int(output.get(f"sats_ac_vola_{i}", 0)) for i in range(1, 6)
],
}
async def close(self) -> None:
await self._client.aclose()
market_data_service = MarketDataService()