106 lines
3.7 KiB
Python
106 lines
3.7 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)
|
|
|
|
async def get_balance(self) -> dict:
|
|
await self._rate_limiter.acquire()
|
|
token = await token_manager.get_access_token()
|
|
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:
|
|
await self._rate_limiter.acquire()
|
|
token = await token_manager.get_access_token()
|
|
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()
|