""" Created on 2025-06-19 """ import logging import time from typing import Optional import sys import pandas as pd sys.path.extend(['../..', '.']) import kis_auth as ka # 로깅 설정 logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s') logger = logging.getLogger(__name__) ############################################################################################## # [국내주식] ELW시세 - ELW 종목검색[국내주식-166] ############################################################################################## # 상수 정의 API_URL = "/uapi/elw/v1/quotations/cond-search" def cond_search( fid_cond_mrkt_div_code: str, # 조건시장분류코드 (필수) fid_cond_scr_div_code: str, # 조건화면분류코드 (필수) fid_rank_sort_cls_code: str, # 순위정렬구분코드 (필수) fid_input_cnt_1: str, # 입력수1 (필수) fid_rank_sort_cls_code_2: Optional[str] = "", # 순위정렬구분코드2 (선택) fid_input_cnt_2: Optional[str] = "", # 입력수2 (선택) fid_rank_sort_cls_code_3: Optional[str] = "", # 순위정렬구분코드3 (선택) fid_input_cnt_3: Optional[str] = "", # 입력수3 (선택) fid_trgt_cls_code: Optional[str] = "", # 대상구분코드 (선택) fid_input_iscd: Optional[str] = "", # 입력종목코드 (선택 - 전체 허용) fid_unas_input_iscd: Optional[str] = "", # 기초자산입력종목코드 (선택) fid_mrkt_cls_code: Optional[str] = "", # 시장구분코드 (선택 - 전체 허용) fid_input_date_1: Optional[str] = "", # 입력날짜1 (선택 - 전체 허용) fid_input_date_2: Optional[str] = "", # 입력날짜2 (선택 - 전체 허용) fid_input_iscd_2: Optional[str] = "", # 입력종목코드2 (선택) fid_etc_cls_code: Optional[str] = "", # 기타구분코드 (선택 - 전체 허용) fid_input_rmnn_dynu_1: Optional[str] = "", # 입력잔존일수1 (선택 - 이상값) fid_input_rmnn_dynu_2: Optional[str] = "", # 입력잔존일수2 (선택 - 이하값) fid_prpr_cnt1: Optional[str] = "", # 현재가수1 (선택 - 이상값) fid_prpr_cnt2: Optional[str] = "", # 현재가수2 (선택 - 이하값) fid_rsfl_rate1: Optional[str] = "", # 등락비율1 (선택 - 이상값) fid_rsfl_rate2: Optional[str] = "", # 등락비율2 (선택 - 이하값) fid_vol1: Optional[str] = "", # 거래량1 (선택 - 이상값) fid_vol2: Optional[str] = "", # 거래량2 (선택 - 이하값) fid_aply_rang_prc_1: Optional[str] = "", # 적용범위가격1 (선택) fid_aply_rang_prc_2: Optional[str] = "", # 적용범위가격2 (선택) fid_lvrg_val1: Optional[str] = "", # 레버리지값1 (선택) fid_lvrg_val2: Optional[str] = "", # 레버리지값2 (선택) fid_vol3: Optional[str] = "", # 거래량3 (선택) fid_vol4: Optional[str] = "", # 거래량4 (선택) fid_ints_vltl1: Optional[str] = "", # 내재변동성1 (선택 - 이상값) fid_ints_vltl2: Optional[str] = "", # 내재변동성2 (선택 - 이하값) fid_prmm_val1: Optional[str] = "", # 프리미엄값1 (선택 - 이상값) fid_prmm_val2: Optional[str] = "", # 프리미엄값2 (선택 - 이하값) fid_gear1: Optional[str] = "", # 기어링1 (선택 - 이상값) fid_gear2: Optional[str] = "", # 기어링2 (선택 - 이하값) fid_prls_qryr_rate1: Optional[str] = "", # 손익분기비율1 (선택 - 이상값) fid_prls_qryr_rate2: Optional[str] = "", # 손익분기비율2 (선택 - 이하값) fid_delta1: Optional[str] = "", # 델타1 (선택 - 이상값) fid_delta2: Optional[str] = "", # 델타2 (선택 - 이하값) fid_acpr1: Optional[str] = "", # 행사가1 (선택) fid_acpr2: Optional[str] = "", # 행사가2 (선택) fid_stck_cnvr_rate1: Optional[str] = "", # 주식전환비율1 (선택 - 이상값) fid_stck_cnvr_rate2: Optional[str] = "", # 주식전환비율2 (선택 - 이하값) fid_div_cls_code: Optional[str] = "", # 분류구분코드 (선택) fid_prit1: Optional[str] = "", # 패리티1 (선택 - 이상값) fid_prit2: Optional[str] = "", # 패리티2 (선택 - 이하값) fid_cfp1: Optional[str] = "", # 자본지지점1 (선택 - 이상값) fid_cfp2: Optional[str] = "", # 자본지지점2 (선택 - 이하값) fid_input_nmix_price_1: Optional[str] = "", # 지수가격1 (선택 - 이상값) fid_input_nmix_price_2: Optional[str] = "", # 지수가격2 (선택 - 이하값) fid_egea_val1: Optional[str] = "", # E기어링값1 (선택 - 이상값) fid_egea_val2: Optional[str] = "", # E기어링값2 (선택 - 이하값) fid_input_dvdn_ert: Optional[str] = "", # 배당수익율 (선택 - 이상값) fid_input_hist_vltl: Optional[str] = "", # 역사적변동성 (선택 - 이하값) fid_theta1: Optional[str] = "", # 세타1 (선택 - 이상값) fid_theta2: Optional[str] = "", # 세타2 (선택 - 이하값) tr_cont: str = "", # 연속 거래 여부 dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임 depth: int = 0, # 현재 재귀 깊이 max_depth: int = 10 # 최대 재귀 깊이 ) -> pd.DataFrame: """ ELW 종목검색 API를 호출하여 조건에 맞는 ELW 종목 정보를 조회합니다. 한국투자 HTS(eFriend Plus) > [0291] ELW 종목검색 화면의 기능을 API로 구현한 함수입니다. 다양한 조건을 설정하여 ELW 종목을 검색하고, 한 번의 호출에 최대 100건까지 조회 가능합니다. 연속 조회를 통해 전체 데이터를 수집할 수 있습니다. Args: fid_cond_mrkt_div_code (str): 조건시장분류코드 (ELW의 경우 "W" 입력) fid_cond_scr_div_code (str): 조건화면분류코드 (화면번호 "11510" 입력) fid_rank_sort_cls_code (str): 순위정렬구분코드 - 0: 정렬안함, 1: 종목코드, 2: 현재가, 3: 대비율, 4: 거래량 - 5: 행사가격, 6: 전환비율, 7: 상장일, 8: 만기일, 9: 잔존일수, 10: 레버리지 fid_input_cnt_1 (str): 정렬1기준 (1: 상위, 2: 하위) 나머지 파라미터들: 대부분 선택사항으로 빈 문자열("")로 설정 가능 tr_cont (str): 연속 거래 여부 (초기 조회시 공백, 연속 조회시 "N") dataframe (Optional[pd.DataFrame]): 누적 데이터프레임 (재귀 호출용) depth (int): 현재 재귀 깊이 (재귀 호출 횟수 추적) max_depth (int): 최대 재귀 깊이 (무한 재귀 방지, 기본값: 10) Returns: Optional[pd.DataFrame]: ELW 종목검색 결과 데이터프레임 - 성공시: ELW 종목 정보가 포함된 DataFrame 반환 - 실패시: 빈 DataFrame 반환 - API 오류시: None 반환 Raises: ValueError: 필수 파라미터가 누락되거나 잘못된 값이 입력된 경우 Example: >>> # 기본 ELW 검색 (전체 종목) >>> df = cond_search( ... fid_cond_mrkt_div_code="W", ... fid_cond_scr_div_code="11510", ... fid_rank_sort_cls_code="0", ... fid_input_cnt_1="1", ... fid_rank_sort_cls_code_2="", ... fid_input_cnt_2="", ... fid_rank_sort_cls_code_3="", ... fid_input_cnt_3="", ... fid_trgt_cls_code="", ... fid_input_iscd="", ... fid_unas_input_iscd="", ... fid_mrkt_cls_code="", ... fid_input_date_1="", ... fid_input_date_2="", ... fid_input_iscd_2="", ... fid_etc_cls_code="", ... # 나머지 모든 파라미터는 빈 문자열 ... **{param: "" for param in [ ... "fid_input_rmnn_dynu_1", "fid_input_rmnn_dynu_2", ... "fid_prpr_cnt1", "fid_prpr_cnt2", "fid_rsfl_rate1", "fid_rsfl_rate2", ... "fid_vol1", "fid_vol2", "fid_aply_rang_prc_1", "fid_aply_rang_prc_2", ... "fid_lvrg_val1", "fid_lvrg_val2", "fid_vol3", "fid_vol4", ... "fid_ints_vltl1", "fid_ints_vltl2", "fid_prmm_val1", "fid_prmm_val2", ... "fid_gear1", "fid_gear2", "fid_prls_qryr_rate1", "fid_prls_qryr_rate2", ... "fid_delta1", "fid_delta2", "fid_acpr1", "fid_acpr2", ... "fid_stck_cnvr_rate1", "fid_stck_cnvr_rate2", "fid_div_cls_code", ... "fid_prit1", "fid_prit2", "fid_cfp1", "fid_cfp2", ... "fid_input_nmix_price_1", "fid_input_nmix_price_2", ... "fid_egea_val1", "fid_egea_val2", "fid_input_dvdn_ert", ... "fid_input_hist_vltl", "fid_theta1", "fid_theta2" ... ]} ... ) >>> print(df.head()) """ # 필수 파라미터 검증 (최소한으로 축소) if not fid_cond_mrkt_div_code: logger.error("fid_cond_mrkt_div_code is required. (e.g. 'W')") raise ValueError("fid_cond_mrkt_div_code is required. (e.g. 'W')") if not fid_cond_scr_div_code: logger.error("fid_cond_scr_div_code is required. (e.g. '11510')") raise ValueError("fid_cond_scr_div_code is required. (e.g. '11510')") if fid_rank_sort_cls_code is None: logger.error("fid_rank_sort_cls_code is required. (e.g. '0')") raise ValueError("fid_rank_sort_cls_code is required. (e.g. '0')") if not fid_input_cnt_1: logger.error("fid_input_cnt_1 is required. (e.g. '1')") raise ValueError("fid_input_cnt_1 is required. (e.g. '1')") # 최대 재귀 깊이 체크 (무한 재귀 방지) if depth >= max_depth: logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth) return dataframe if dataframe is not None else pd.DataFrame() # API 호출 정보 설정 tr_id = "FHKEW15100000" # 요청 파라미터 설정 params = { "FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code, "FID_COND_SCR_DIV_CODE": fid_cond_scr_div_code, "FID_RANK_SORT_CLS_CODE": fid_rank_sort_cls_code, "FID_INPUT_CNT_1": fid_input_cnt_1, "FID_RANK_SORT_CLS_CODE_2": fid_rank_sort_cls_code_2, "FID_INPUT_CNT_2": fid_input_cnt_2, "FID_RANK_SORT_CLS_CODE_3": fid_rank_sort_cls_code_3, "FID_INPUT_CNT_3": fid_input_cnt_3, "FID_TRGT_CLS_CODE": fid_trgt_cls_code, "FID_INPUT_ISCD": fid_input_iscd, "FID_UNAS_INPUT_ISCD": fid_unas_input_iscd, "FID_MRKT_CLS_CODE": fid_mrkt_cls_code, "FID_INPUT_DATE_1": fid_input_date_1, "FID_INPUT_DATE_2": fid_input_date_2, "FID_INPUT_ISCD_2": fid_input_iscd_2, "FID_ETC_CLS_CODE": fid_etc_cls_code, "FID_INPUT_RMNN_DYNU_1": fid_input_rmnn_dynu_1, "FID_INPUT_RMNN_DYNU_2": fid_input_rmnn_dynu_2, "FID_PRPR_CNT1": fid_prpr_cnt1, "FID_PRPR_CNT2": fid_prpr_cnt2, "FID_RSFL_RATE1": fid_rsfl_rate1, "FID_RSFL_RATE2": fid_rsfl_rate2, "FID_VOL1": fid_vol1, "FID_VOL2": fid_vol2, "FID_APLY_RANG_PRC_1": fid_aply_rang_prc_1, "FID_APLY_RANG_PRC_2": fid_aply_rang_prc_2, "FID_LVRG_VAL1": fid_lvrg_val1, "FID_LVRG_VAL2": fid_lvrg_val2, "FID_VOL3": fid_vol3, "FID_VOL4": fid_vol4, "FID_INTS_VLTL1": fid_ints_vltl1, "FID_INTS_VLTL2": fid_ints_vltl2, "FID_PRMM_VAL1": fid_prmm_val1, "FID_PRMM_VAL2": fid_prmm_val2, "FID_GEAR1": fid_gear1, "FID_GEAR2": fid_gear2, "FID_PRLS_QRYR_RATE1": fid_prls_qryr_rate1, "FID_PRLS_QRYR_RATE2": fid_prls_qryr_rate2, "FID_DELTA1": fid_delta1, "FID_DELTA2": fid_delta2, "FID_ACPR1": fid_acpr1, "FID_ACPR2": fid_acpr2, "FID_STCK_CNVR_RATE1": fid_stck_cnvr_rate1, "FID_STCK_CNVR_RATE2": fid_stck_cnvr_rate2, "FID_DIV_CLS_CODE": fid_div_cls_code, "FID_PRIT1": fid_prit1, "FID_PRIT2": fid_prit2, "FID_CFP1": fid_cfp1, "FID_CFP2": fid_cfp2, "FID_INPUT_NMIX_PRICE_1": fid_input_nmix_price_1, "FID_INPUT_NMIX_PRICE_2": fid_input_nmix_price_2, "FID_EGEA_VAL1": fid_egea_val1, "FID_EGEA_VAL2": fid_egea_val2, "FID_INPUT_DVDN_ERT": fid_input_dvdn_ert, "FID_INPUT_HIST_VLTL": fid_input_hist_vltl, "FID_THETA1": fid_theta1, "FID_THETA2": fid_theta2, } # API 호출 정보 로그 logger.info("ELW 종목검색 API 호출 시작 (depth: %d)", depth) # API 호출 실행 res = ka._url_fetch(API_URL, tr_id, tr_cont, params) # API 응답 처리 if res.isOK(): # 응답 데이터 처리 if hasattr(res.getBody(), 'output'): output_data = res.getBody().output # 단일 객체인 경우 리스트로 변환 if not isinstance(output_data, list): output_data = [output_data] current_data = pd.DataFrame(output_data) logger.info("API 호출 성공: %d건의 데이터를 수신했습니다.", len(current_data)) else: current_data = pd.DataFrame() logger.warning("API 응답에서 output 데이터를 찾을 수 없습니다.") # 데이터프레임 누적 처리 if dataframe is not None: dataframe = pd.concat([dataframe, current_data], ignore_index=True) else: dataframe = current_data # 연속 조회 여부 확인 tr_cont = res.getHeader().tr_cont # 연속 데이터가 있는 경우 재귀 호출 if tr_cont == "M": logger.info("Calling next page...") ka.smart_sleep() # API 호출 간격 조절 return cond_search( fid_cond_mrkt_div_code, fid_cond_scr_div_code, fid_rank_sort_cls_code, fid_input_cnt_1, fid_rank_sort_cls_code_2, fid_input_cnt_2, fid_rank_sort_cls_code_3, fid_input_cnt_3, fid_trgt_cls_code, fid_input_iscd, fid_unas_input_iscd, fid_mrkt_cls_code, fid_input_date_1, fid_input_date_2, fid_input_iscd_2, fid_etc_cls_code, fid_input_rmnn_dynu_1, fid_input_rmnn_dynu_2, fid_prpr_cnt1, fid_prpr_cnt2, fid_rsfl_rate1, fid_rsfl_rate2, fid_vol1, fid_vol2, fid_aply_rang_prc_1, fid_aply_rang_prc_2, fid_lvrg_val1, fid_lvrg_val2, fid_vol3, fid_vol4, fid_ints_vltl1, fid_ints_vltl2, fid_prmm_val1, fid_prmm_val2, fid_gear1, fid_gear2, fid_prls_qryr_rate1, fid_prls_qryr_rate2, fid_delta1, fid_delta2, fid_acpr1, fid_acpr2, fid_stck_cnvr_rate1, fid_stck_cnvr_rate2, fid_div_cls_code, fid_prit1, fid_prit2, fid_cfp1, fid_cfp2, fid_input_nmix_price_1, fid_input_nmix_price_2, fid_egea_val1, fid_egea_val2, fid_input_dvdn_ert, fid_input_hist_vltl, fid_theta1, fid_theta2, "N", dataframe, depth + 1, max_depth ) else: # 데이터 수집 완료 logger.info("Data fetch complete.") total_records = len(dataframe) if dataframe is not None else 0 logger.info("총 %d건의 ELW 종목 데이터를 수집했습니다.", total_records) return dataframe else: # API 호출 실패 처리 error_code = res.getErrorCode() error_message = res.getErrorMessage() logger.error("API call failed: %s - %s", error_code, error_message) res.printError(API_URL) return pd.DataFrame() # 빈 데이터프레임 반환