ls kiwoom 구독 히스토리 모두 적재

This commit is contained in:
Your Name
2026-07-30 20:23:37 +09:00
parent 67eab24603
commit 7050f788c5
18 changed files with 1379 additions and 120 deletions

View File

@@ -107,26 +107,24 @@ def load_ls_creds(*, use_mock: bool) -> tuple[str, str]:
db.close()
def fetch_access_token(app_key: str, app_secret: str, timeout: float = 15.0) -> str:
url = f"{LS_REST_BASE}/oauth2/token"
resp = requests.post(
url,
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={
"grant_type": "client_credentials",
"appkey": app_key,
"appsecretkey": app_secret,
"scope": "oob",
},
def fetch_access_token(
app_key: str,
app_secret: str,
timeout: float = 15.0,
*,
force: bool = False,
reason: str = "",
) -> str:
"""LS ``/oauth2/token`` — expires_in 캐시 공유 (강제 연타 발급 금지)."""
from kis_trader.network.ls_token import fetch_ls_access_token
return fetch_ls_access_token(
app_key,
app_secret,
timeout=timeout,
force=force,
reason=reason or "ls_condition",
)
if resp.status_code >= 400:
raise RuntimeError(f"token HTTP {resp.status_code}: {resp.text[:400]}")
body = resp.json()
token = body.get("access_token") or body.get("accesstoken")
if not token:
raise RuntimeError(f"no access_token: {body}")
return str(token)
def _rest_headers(token: str, tr_cd: str, *, tr_cont: str = "N", tr_cont_key: str = "") -> dict:
@@ -172,6 +170,22 @@ def call_item_search(
)
if resp.status_code >= 400:
logger.error("REST %s fail: %s", tr_cd, text[:500])
try:
from kis_trader.network.ls_token import is_ls_auth_error
if is_ls_auth_error(
rsp_cd=data.get("rsp_cd") if isinstance(data, dict) else None,
rsp_msg=data.get("rsp_msg") if isinstance(data, dict) else None,
http_status=resp.status_code,
text=text,
):
raise RuntimeError(
f"LS {tr_cd} auth "
f"{(data or {}).get('rsp_cd')}: {(data or {}).get('rsp_msg')}"
)
except RuntimeError:
raise
except Exception:
pass
return data, rh
@@ -222,6 +236,26 @@ def t1866_list_conditions(
data.get("rsp_msg"),
json.dumps(data, ensure_ascii=False)[:400],
)
# 인증 오류는 빈 목록으로 넘기지 않음 — rematch 가 토큰 갱신하도록 raise
try:
from kis_trader.network.ls_token import is_ls_auth_error
except Exception:
is_ls_auth_error = None # type: ignore
if is_ls_auth_error is not None and is_ls_auth_error(
rsp_cd=data.get("rsp_cd"),
rsp_msg=data.get("rsp_msg"),
http_status=resp.status_code,
text=resp.text,
):
raise RuntimeError(
f"LS t1866 auth {data.get('rsp_cd')}: {data.get('rsp_msg')}"
)
if resp.status_code >= 400:
# GW 라우팅 등 — 빈 목록으로 '조건 소실' 오판 금지
raise RuntimeError(
f"LS t1866 HTTP={resp.status_code} "
f"rsp_cd={data.get('rsp_cd')} rsp_msg={data.get('rsp_msg')}"
)
rows = data.get("t1866OutBlock1") or []
if isinstance(rows, dict):
rows = [rows]