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