Files
stockautomtion/app/services/account.py

116 lines
4.0 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("account")
_BALANCE_URL = "/uapi/domestic-stock/v1/trading/inquire-balance"
_ORDERABLE_URL = "/uapi/domestic-stock/v1/trading/inquire-psamount"
_BALANCE_TR_IDS = {"real": "TTTC0311R", "vps": "VTTC0311R"}
class AccountService:
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_balance(self) -> dict:
if not self._check_auth():
return {"deposits": [], "stocks": []}
await self._rate_limiter.acquire()
tr_id = _BALANCE_TR_IDS.get(settings.kis.server_mode, "VTTC0311R")
headers = token_manager.get_auth_headers(tr_id)
params = {
"CANO": settings.kis.account_no,
"ACNT_PRDT_CD": settings.kis.account_code,
"AFHR_FLPR_YN": "N",
"OFLN_YN": "N",
"INQR_DVSN": "02",
"UNPR_DVSN": "01",
"FUND_STTL_ICLD_YN": "N",
"CNCD_UNCLD_YN": "N",
}
url = f"{get_base_url()}{_BALANCE_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", data.get("msg1"))
return {"deposits": [], "stocks": []}
stocks = []
for item in data.get("output1", []):
stocks.append({
"stock_code": item.get("pdno", ""),
"stock_name": item.get("hsts_km_name", ""),
"qty": int(item.get("hldg_qty", 0)),
"avg_price": float(item.get("pchs_avg_pric", 0)),
"current_price": int(item.get("prpr", 0)),
"profit": float(item.get("evlu_pfls_amt", 0)),
"profit_rate": float(item.get("pfls_rt", 0)),
"buy_amount": float(item.get("pchs_amt", 0)),
"eval_amount": float(item.get("evlu_amt", 0)),
})
deposits = []
for item in data.get("output2", []):
deposits.append({
"currency": item.get("crcy_cd", "KRW"),
"amount": float(item.get("nmbdy_now_amt", 0)),
"orderable": float(item.get("ord_psbl_amt", 0)),
})
return {"deposits": deposits, "stocks": stocks}
async def get_orderable_amount(self, stock_code: str, price: int) -> dict:
if not self._check_auth():
return {"orderable_amount": 0, "orderable_qty": 0}
await self._rate_limiter.acquire()
headers = token_manager.get_auth_headers("VTTC0830R" if settings.kis.server_mode == "vps" else "TTTC0830R")
params = {
"CANO": settings.kis.account_no,
"ACNT_PRDT_CD": settings.kis.account_code,
"PDNO": stock_code,
"ORD_UNPR": str(price),
"ORD_DVSN": "00",
}
url = f"{get_base_url()}{_ORDERABLE_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 {"orderable_amount": 0, "orderable_qty": 0}
output = data.get("output", {})
return {
"orderable_amount": float(output.get("psmps_numb", 0)),
"orderable_qty": int(output.get("ord_psbl_qty", 0)),
"max_buy_amount": float(output.get("max_buy_psbl_amt", 0)),
}
async def close(self) -> None:
await self._client.aclose()
account_service = AccountService()