From 60eda5a5ee24395189bc1f7f9af781d061d3c111 Mon Sep 17 00:00:00 2001 From: jongjae Date: Tue, 19 May 2026 08:59:35 +0900 Subject: [PATCH] =?UTF-8?q?[2026-05-19]=20KIS=20API=20TR=5FID=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=E2=80=94=20=EC=99=B8=EA=B5=AD=EC=9D=B8/=EA=B8=B0?= =?UTF-8?q?=EA=B4=80=20=EC=88=98=EA=B8=89=C2=B7=EC=97=85=EC=A2=85=20?= =?UTF-8?q?=EC=A7=80=EC=88=98=20API=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - get_foreign_institution_rank: FHKST04430000(없는 서비스) → FHPTJ04400000(foreign-institution-total) - get_sector_trend: FHKST03010100(일봉 오용) → FHPUP02100000(inquire-index-price) 15개 업종 다중 호출 - 두 함수 모두 실거래 API 테스트 통과 확인 Co-Authored-By: Claude Sonnet 4.6 --- app/execution/kis_client.py | 105 ++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/app/execution/kis_client.py b/app/execution/kis_client.py index b57daba..a1a9e39 100644 --- a/app/execution/kis_client.py +++ b/app/execution/kis_client.py @@ -413,61 +413,86 @@ class KISClient: return result async def get_foreign_institution_rank(self, top_n: int = 30) -> Dict: - """외국인/기관 순매수 상위 (AI 판단용)""" + """외국인/기관 순매수 상위 (AI 판단용) — FHPTJ04400000""" data = await self._request( method = "GET", - path = "/uapi/domestic-stock/v1/quotations/inquire-investor", - tr_id = "FHKST04430000", + path = "/uapi/domestic-stock/v1/quotations/foreign-institution-total", + tr_id = "FHPTJ04400000", params = { - "FID_COND_MRKT_DIV_CODE": "J", - "FID_INPUT_ISCD" : "0000", - "FID_INPUT_DATE_1" : "", - "FID_INPUT_DATE_2" : "", - "FID_PERIOD_DIV_CODE" : "D", + "FID_COND_MRKT_DIV_CODE" : "J", + "FID_COND_SCR_DIV_CODE" : "20171", + "FID_INPUT_ISCD" : "0000", + "FID_DIV_CLS_CODE" : "0", + "FID_BLNG_CLS_CODE" : "0", + "FID_TRGT_CLS_CODE" : "111111111", + "FID_TRGT_EXLS_CLS_CODE" : "000000", + "FID_INPUT_PRICE_1" : "", + "FID_INPUT_PRICE_2" : "", + "FID_VOL_CNT" : "", + "FID_INPUT_DATE_1" : "", + "FID_RANK_SORT_CLS_CODE" : "1", # 순매수량 기준 정렬 + "FID_ETC_CLS_CODE" : "0", } ) foreign = [] institution = [] - for row in data.get("output", [])[:top_n]: - entry = { - "ticker": row.get("mksc_shrn_iscd", ""), - "name" : row.get("hts_kor_isnm", ""), - "amount": int(row.get("frgn_ntby_qty", "0")), - } - foreign.append(entry) - entry2 = { - "ticker": row.get("mksc_shrn_iscd", ""), - "name" : row.get("hts_kor_isnm", ""), - "amount": int(row.get("orgn_ntby_qty", "0")), - } - institution.append(entry2) + for row in data.get("output", []): + ticker = row.get("mksc_shrn_iscd", "") + name = row.get("hts_kor_isnm", "") + try: + frgn_qty = int(row.get("frgn_ntby_qty") or 0) + except (ValueError, TypeError): + frgn_qty = 0 + try: + orgn_qty = int(row.get("orgn_ntby_qty") or 0) + except (ValueError, TypeError): + orgn_qty = 0 + foreign.append({"ticker": ticker, "name": name, "amount": frgn_qty}) + institution.append({"ticker": ticker, "name": name, "amount": orgn_qty}) return { "foreign" : sorted(foreign, key=lambda x: x["amount"], reverse=True)[:top_n], "institution": sorted(institution, key=lambda x: x["amount"], reverse=True)[:top_n], } + # 업종코드 → 업종명 (KOSPI 주요 업종) + _SECTOR_CODES = [ + ("0001", "KOSPI"), + ("0028", "반도체"), + ("0150", "2차전지"), + ("0018", "전기전자"), + ("0011", "의약품"), + ("0010", "화학"), + ("0016", "철강금속"), + ("0017", "기계"), + ("0006", "건설업"), + ("0027", "금융업"), + ("0026", "통신업"), + ("0020", "운수창고"), + ("0021", "유통업"), + ("0009", "음식료품"), + ("0024", "전기가스업"), + ] + async def get_sector_trend(self) -> list: - """업종별 등락률 (AI 판단용)""" - data = await self._request( - method = "GET", - path = "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice", - tr_id = "FHKST03010100", - params = { - "FID_COND_MRKT_DIV_CODE": "U", # 업종 - "FID_INPUT_ISCD" : "0001", - "FID_INPUT_DATE_1" : "", - "FID_INPUT_DATE_2" : "", - "FID_PERIOD_DIV_CODE" : "D", - "FID_ORG_ADJ_PRC" : "0", - } - ) + """업종별 등락률 (AI 판단용) — FHPUP02100000 다중 호출""" result = [] - for row in data.get("output1", []): - result.append({ - "sector" : row.get("hts_kor_isnm", ""), - "change_pct": float(row.get("prdy_ctrt", "0")), - }) + for code, name in self._SECTOR_CODES: + try: + data = await self._request( + method = "GET", + path = "/uapi/domestic-stock/v1/quotations/inquire-index-price", + tr_id = "FHPUP02100000", + params = { + "FID_COND_MRKT_DIV_CODE": "U", + "FID_INPUT_ISCD" : code, + } + ) + o = data.get("output", {}) + change_pct = float(o.get("bstp_nmix_prdy_ctrt") or 0) + result.append({"sector": name, "change_pct": change_pct}) + except Exception as e: + logger.warning(f"업종 지수 조회 실패 [{name}/{code}]: {e}") return result