5815 lines
236 KiB
Python
5815 lines
236 KiB
Python
import logging
|
|
import time
|
|
import sys
|
|
from typing import Optional, Tuple
|
|
|
|
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__)
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 지정가주문번호조회 [해외주식-071]
|
|
##############################################################################################
|
|
|
|
def algo_ordno(
|
|
cano: str, # [필수] 종합계좌번호
|
|
acnt_prdt_cd: str, # [필수] 계좌상품코드 (ex. 01)
|
|
trad_dt: str, # [필수] 거래일자
|
|
FK200: str = "", # 연속조회검색조건200
|
|
NK200: str = "", # 연속조회키200
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> pd.DataFrame:
|
|
"""
|
|
TWAP, VWAP 주문에 대한 주문번호를 조회하는 API
|
|
|
|
Args:
|
|
cano (str): [필수] 종합계좌번호
|
|
acnt_prdt_cd (str): [필수] 계좌상품코드 (ex. 01)
|
|
trad_dt (str): [필수] 거래일자
|
|
FK200 (str): 연속조회검색조건200
|
|
NK200 (str): 연속조회키200
|
|
tr_cont (str): 연속거래여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
pd.DataFrame: 해외주식 지정가주문번호 데이터
|
|
|
|
Example:
|
|
>>> df = algo_ordno(cano=trenv.my_acct, acnt_prdt_cd=trenv.my_prod, trad_dt="20250619")
|
|
>>> print(df)
|
|
"""
|
|
|
|
if cano == "":
|
|
raise ValueError("cano is required")
|
|
|
|
if acnt_prdt_cd == "":
|
|
raise ValueError("acnt_prdt_cd is required (e.g. '01')")
|
|
|
|
if trad_dt == "":
|
|
raise ValueError("trad_dt is required")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe is None:
|
|
return pd.DataFrame()
|
|
else:
|
|
return dataframe
|
|
|
|
tr_id = "TTTS6058R" # 해외주식 지정가주문번호조회
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/algo-ordno"
|
|
|
|
params = {
|
|
"CANO": cano, # 종합계좌번호
|
|
"ACNT_PRDT_CD": acnt_prdt_cd, # 계좌상품코드
|
|
"TRAD_DT": trad_dt, # 거래일자
|
|
"CTX_AREA_FK200": FK200, # 연속조회검색조건200
|
|
"CTX_AREA_NK200": NK200 # 연속조회키200
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
current_data = pd.DataFrame(res.getBody().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
|
|
FK200 = res.getBody().ctx_area_fk200
|
|
NK200 = res.getBody().ctx_area_nk200
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return algo_ordno(
|
|
cano, acnt_prdt_cd, trad_dt, FK200, NK200, "N", dataframe, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 미국주간주문 [v1_해외주식-026]
|
|
##############################################################################################
|
|
|
|
def daytime_order(
|
|
order_dv: str, # 주문구분 buy(매수) / sell(매도)
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
pdno: str, # 상품번호
|
|
ord_qty: str, # 주문수량
|
|
ovrs_ord_unpr: str, # 해외주문단가
|
|
ctac_tlno: str, # 연락전화번호
|
|
mgco_aptm_odno: str, # 운용사지정주문번호
|
|
ord_svr_dvsn_cd: str, # 주문서버구분코드
|
|
ord_dvsn: str, # 주문구분
|
|
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 미국주간주문[v1_해외주식-026]
|
|
해외주식 미국주간주문 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
order_dv (str): 주문구분 buy(매수) / sell(매도)
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
ovrs_excg_cd (str): NASD:나스닥 / NYSE:뉴욕 / AMEX:아멕스
|
|
pdno (str): 종목코드
|
|
ord_qty (str): 해외거래소 별 최소 주문수량 및 주문단위 확인 필요
|
|
ovrs_ord_unpr (str): 소수점 포함, 1주당 가격 * 시장가의 경우 1주당 가격을 공란으로 비우지 않음 "0"으로 입력
|
|
ctac_tlno (str): " "
|
|
mgco_aptm_odno (str): " "
|
|
ord_svr_dvsn_cd (str): "0"
|
|
ord_dvsn (str): [미국 매수/매도 주문] 00 : 지정가 * 주간거래는 지정가만 가능
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 미국주간주문 데이터
|
|
|
|
Example:
|
|
>>> df = daytime_order(
|
|
... order_dv="buy",
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... ovrs_excg_cd="NASD",
|
|
... pdno="AAPL",
|
|
... ord_qty="10",
|
|
... ovrs_ord_unpr="150.00",
|
|
... ctac_tlno="01012345678",
|
|
... mgco_aptm_odno="",
|
|
... ord_svr_dvsn_cd="0",
|
|
... ord_dvsn="00"
|
|
... )
|
|
>>> print(df)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '12345678')")
|
|
raise ValueError("cano is required. (e.g. '12345678')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not ovrs_excg_cd:
|
|
logger.error("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
raise ValueError("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
if not pdno:
|
|
logger.error("pdno is required. (e.g. 'AAPL')")
|
|
raise ValueError("pdno is required. (e.g. 'AAPL')")
|
|
if not ord_qty:
|
|
logger.error("ord_qty is required. (e.g. '10')")
|
|
raise ValueError("ord_qty is required. (e.g. '10')")
|
|
if not ovrs_ord_unpr:
|
|
logger.error("ovrs_ord_unpr is required. (e.g. '150.00')")
|
|
raise ValueError("ovrs_ord_unpr is required. (e.g. '150.00')")
|
|
if not ord_svr_dvsn_cd:
|
|
logger.error("ord_svr_dvsn_cd is required. (e.g. '0')")
|
|
raise ValueError("ord_svr_dvsn_cd is required. (e.g. '0')")
|
|
if not ord_dvsn:
|
|
logger.error("ord_dvsn is required. (e.g. '00')")
|
|
raise ValueError("ord_dvsn is required. (e.g. '00')")
|
|
|
|
if order_dv == "buy":
|
|
tr_id = "TTTS6036U"
|
|
elif order_dv == "sell":
|
|
tr_id = "TTTS6037U"
|
|
else:
|
|
logger.error("Invalid order_dv. (e.g. 'buy' or 'sell')")
|
|
raise ValueError("Invalid order_dv. (e.g. 'buy' or 'sell')")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/daytime-order"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"PDNO": pdno,
|
|
"ORD_QTY": ord_qty,
|
|
"OVRS_ORD_UNPR": ovrs_ord_unpr,
|
|
"CTAC_TLNO": ctac_tlno,
|
|
"MGCO_APTM_ODNO": mgco_aptm_odno,
|
|
"ORD_SVR_DVSN_CD": ord_svr_dvsn_cd,
|
|
"ORD_DVSN": ord_dvsn,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url,
|
|
ptr_id=tr_id,
|
|
tr_cont="",
|
|
params=params,
|
|
postFlag=True)
|
|
|
|
if res.isOK():
|
|
if hasattr(res.getBody(), 'output'):
|
|
output_data = res.getBody().output
|
|
if not isinstance(output_data, list):
|
|
output_data = [output_data]
|
|
dataframe = pd.DataFrame(output_data)
|
|
else:
|
|
dataframe = pd.DataFrame()
|
|
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 미국주간정정취소 [v1_해외주식-027]
|
|
##############################################################################################
|
|
|
|
def daytime_order_rvsecncl(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
pdno: str, # 상품번호
|
|
orgn_odno: str, # 원주문번호
|
|
rvse_cncl_dvsn_cd: str, # 정정취소구분코드
|
|
ord_qty: str, # 주문수량
|
|
ovrs_ord_unpr: str, # 해외주문단가
|
|
ctac_tlno: str, # 연락전화번호
|
|
mgco_aptm_odno: str, # 운용사지정주문번호
|
|
ord_svr_dvsn_cd: str, # 주문서버구분코드
|
|
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 미국주간정정취소[v1_해외주식-027]
|
|
해외주식 미국주간정정취소 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
ovrs_excg_cd (str): NASD:나스닥 / NYSE:뉴욕 / AMEX:아멕스
|
|
pdno (str): 종목코드
|
|
orgn_odno (str): 정정 또는 취소할 원주문번호
|
|
rvse_cncl_dvsn_cd (str): 01 : 정정, 02 : 취소
|
|
ord_qty (str): 주문수량
|
|
ovrs_ord_unpr (str): 소수점 포함, 1주당 가격
|
|
ctac_tlno (str): 연락전화번호
|
|
mgco_aptm_odno (str): 운용사지정주문번호
|
|
ord_svr_dvsn_cd (str): 주문서버구분코드
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 미국주간정정취소 데이터
|
|
|
|
Example:
|
|
>>> df = daytime_order_rvsecncl(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... ovrs_excg_cd="NASD",
|
|
... pdno="AAPL",
|
|
... orgn_odno="1234567890",
|
|
... rvse_cncl_dvsn_cd="01",
|
|
... ord_qty="100",
|
|
... ovrs_ord_unpr="150.00",
|
|
... ctac_tlno="01012345678",
|
|
... mgco_aptm_odno="000000000001",
|
|
... ord_svr_dvsn_cd="0"
|
|
... )
|
|
>>> print(df)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '12345678')")
|
|
raise ValueError("cano is required. (e.g. '12345678')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not ovrs_excg_cd:
|
|
logger.error("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
raise ValueError("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
if not pdno:
|
|
logger.error("pdno is required. (e.g. 'AAPL')")
|
|
raise ValueError("pdno is required. (e.g. 'AAPL')")
|
|
if not orgn_odno:
|
|
logger.error("orgn_odno is required. (e.g. '1234567890')")
|
|
raise ValueError("orgn_odno is required. (e.g. '1234567890')")
|
|
if rvse_cncl_dvsn_cd not in ["01", "02"]:
|
|
logger.error("rvse_cncl_dvsn_cd is required. (e.g. '01' or '02')")
|
|
raise ValueError("rvse_cncl_dvsn_cd is required. (e.g. '01' or '02')")
|
|
if not ord_qty:
|
|
logger.error("ord_qty is required. (e.g. '100')")
|
|
raise ValueError("ord_qty is required. (e.g. '100')")
|
|
if not ovrs_ord_unpr:
|
|
logger.error("ovrs_ord_unpr is required. (e.g. '150.00')")
|
|
raise ValueError("ovrs_ord_unpr is required. (e.g. '150.00')")
|
|
if not ord_svr_dvsn_cd:
|
|
logger.error("ord_svr_dvsn_cd is required. (e.g. '0')")
|
|
raise ValueError("ord_svr_dvsn_cd is required. (e.g. '0')")
|
|
|
|
tr_id = "TTTS6038U"
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/daytime-order-rvsecncl"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"PDNO": pdno,
|
|
"ORGN_ODNO": orgn_odno,
|
|
"RVSE_CNCL_DVSN_CD": rvse_cncl_dvsn_cd,
|
|
"ORD_QTY": ord_qty,
|
|
"OVRS_ORD_UNPR": ovrs_ord_unpr,
|
|
"CTAC_TLNO": ctac_tlno,
|
|
"MGCO_APTM_ODNO": mgco_aptm_odno,
|
|
"ORD_SVR_DVSN_CD": ord_svr_dvsn_cd,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url,
|
|
ptr_id=tr_id,
|
|
tr_cont="",
|
|
params=params,
|
|
postFlag=True
|
|
)
|
|
|
|
if res.isOK():
|
|
if hasattr(res.getBody(), 'output'):
|
|
output_data = res.getBody().output
|
|
if not isinstance(output_data, list):
|
|
output_data = [output_data]
|
|
dataframe = pd.DataFrame(output_data)
|
|
else:
|
|
dataframe = pd.DataFrame()
|
|
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 - 해외증거금 통화별조회 [해외주식-035]
|
|
##############################################################################################
|
|
|
|
def foreign_margin(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
tr_cont: str = "", # 연속 거래 여부
|
|
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
|
depth: int = 0, # 현재 재귀 깊이
|
|
max_depth: int = 10 # 최대 재귀 깊이 (기본값: 10)
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외증거금 통화별조회[해외주식-035]
|
|
해외증거금 통화별조회 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 종합계좌번호 (필수)
|
|
acnt_prdt_cd (str): 계좌상품코드 (필수)
|
|
tr_cont (str): 연속 거래 여부 (기본값: "")
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외증거금 통화별조회 데이터
|
|
|
|
Example:
|
|
>>> df = foreign_margin("12345678", "01")
|
|
>>> print(df)
|
|
"""
|
|
# 필수 파라미터 검증
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '12345678')")
|
|
raise ValueError("cano is required. (e.g. '12345678')")
|
|
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
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()
|
|
|
|
tr_id = "TTTC2101R"
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/foreign-margin"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
}
|
|
|
|
# API 호출
|
|
res = ka._url_fetch(api_url=api_url, ptr_id=tr_id, tr_cont=tr_cont, params=params)
|
|
|
|
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)
|
|
else:
|
|
current_data = pd.DataFrame()
|
|
|
|
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 in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return foreign_margin(
|
|
cano,
|
|
acnt_prdt_cd,
|
|
"N", dataframe, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 지정가체결내역조회 [해외주식-070]
|
|
##############################################################################################
|
|
|
|
def inquire_algo_ccnl(
|
|
cano: str, # [필수] 계좌번호
|
|
acnt_prdt_cd: str, # [필수] 계좌상품코드 (ex. 01)
|
|
ord_dt: str = "", # 주문일자
|
|
ord_gno_brno: str = "", # 주문채번지점번호
|
|
odno: str = "", # 주문번호 (ex. 지정가주문번호 TTTC6058R에서 조회된 주문번호 입력)
|
|
ttlz_icld_yn: str = "", # 집계포함여부
|
|
NK200: str = "", # 연속조회키200
|
|
FK200: str = "", # 연속조회조건200
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
|
dataframe3: Optional[pd.DataFrame] = None, # 누적 데이터프레임3
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
해외주식 TWAP, VWAP 주문에 대한 체결내역 조회 API로 지정가 주문번호조회 API를 수행 후 조회해야합니다
|
|
|
|
Args:
|
|
cano (str): [필수] 계좌번호
|
|
acnt_prdt_cd (str): [필수] 계좌상품코드 (ex. 01)
|
|
ord_dt (str): 주문일자
|
|
ord_gno_brno (str): 주문채번지점번호
|
|
odno (str): 주문번호 (ex. 지정가주문번호 TTTC6058R에서 조회된 주문번호 입력)
|
|
ttlz_icld_yn (str): 집계포함여부
|
|
NK200 (str): 연속조회키200
|
|
FK200 (str): 연속조회조건200
|
|
tr_cont (str): 연속거래여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
dataframe3 (Optional[pd.DataFrame]): 누적 데이터프레임3
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: (output, output3) 체결내역 데이터
|
|
|
|
Example:
|
|
>>> result, result3 = inquire_algo_ccnl(cano=trenv.my_acct, acnt_prdt_cd=trenv.my_prod)
|
|
>>> print(result)
|
|
>>> print(result3)
|
|
"""
|
|
|
|
if cano == "":
|
|
raise ValueError("cano is required")
|
|
|
|
if acnt_prdt_cd == "":
|
|
raise ValueError("acnt_prdt_cd is required")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe is None:
|
|
dataframe = pd.DataFrame()
|
|
if dataframe3 is None:
|
|
dataframe3 = pd.DataFrame()
|
|
return dataframe, dataframe3
|
|
|
|
tr_id = "TTTS6059R" # 해외주식 지정가체결내역조회
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/inquire-algo-ccnl"
|
|
|
|
params = {
|
|
"CANO": cano, # 계좌번호
|
|
"ACNT_PRDT_CD": acnt_prdt_cd, # 계좌상품코드
|
|
"ORD_DT": ord_dt, # 주문일자
|
|
"ORD_GNO_BRNO": ord_gno_brno, # 주문채번지점번호
|
|
"ODNO": odno, # 주문번호
|
|
"TTLZ_ICLD_YN": ttlz_icld_yn, # 집계포함여부
|
|
"CTX_AREA_NK200": NK200, # 연속조회키200
|
|
"CTX_AREA_FK200": FK200 # 연속조회조건200
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
current_data = pd.DataFrame(res.getBody().output)
|
|
current_data3 = pd.DataFrame(res.getBody().output3)
|
|
|
|
if dataframe is not None:
|
|
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
|
else:
|
|
dataframe = current_data
|
|
|
|
if dataframe3 is not None:
|
|
dataframe3 = pd.concat([dataframe3, current_data3], ignore_index=True)
|
|
else:
|
|
dataframe3 = current_data3
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
NK200 = res.getBody().ctx_area_nk200
|
|
FK200 = res.getBody().ctx_area_fk200
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return inquire_algo_ccnl(
|
|
cano, acnt_prdt_cd, ord_dt, ord_gno_brno, odno, ttlz_icld_yn,
|
|
NK200, FK200, "N", dataframe, dataframe3, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe, dataframe3
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 잔고 [v1_해외주식-006]
|
|
##############################################################################################
|
|
|
|
def inquire_balance(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
tr_crcy_cd: str, # 거래통화코드
|
|
FK200: str = "", # 연속조회검색조건200
|
|
NK200: str = "", # 연속조회키200
|
|
env_dv: str = "real", # 실전모의구분
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 잔고[v1_해외주식-006]
|
|
해외주식 잔고 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
ovrs_excg_cd (str): [모의] NASD : 나스닥 NYSE : 뉴욕 AMEX : 아멕스 [실전] NASD : 미국전체 NAS : 나스닥 NYSE : 뉴욕 AMEX : 아멕스 [모의/실전 공통] SEHK : 홍콩 SHAA : 중국상해 SZAA : 중국심천 TKSE : 일본 HASE : 베트남 하노이 VNSE : 베트남 호치민
|
|
tr_crcy_cd (str): USD : 미국달러 HKD : 홍콩달러 CNY : 중국위안화 JPY : 일본엔화 VND : 베트남동
|
|
FK200 (str): 공란 : 최초 조회시 이전 조회 Output CTX_AREA_FK200값 : 다음페이지 조회시(2번째부터)
|
|
NK200 (str): 공란 : 최초 조회시 이전 조회 Output CTX_AREA_NK200값 : 다음페이지 조회시(2번째부터)
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외주식 잔고 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = inquire_balance(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... ovrs_excg_cd="NASD",
|
|
... tr_crcy_cd="USD",
|
|
... FK200="",
|
|
... NK200=""
|
|
... )
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '810XXXXX')")
|
|
raise ValueError("cano is required. (e.g. '810XXXXX')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not ovrs_excg_cd:
|
|
logger.error("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
raise ValueError("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
if not tr_crcy_cd:
|
|
logger.error("tr_crcy_cd is required. (e.g. 'USD')")
|
|
raise ValueError("tr_crcy_cd is required. (e.g. 'USD')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
|
|
# TR ID 설정 (모의투자 지원 로직)
|
|
if env_dv == "real":
|
|
tr_id = "TTTS3012R" # 실전투자용 TR ID
|
|
elif env_dv == "demo":
|
|
tr_id = "VTTS3012R" # 모의투자용 TR ID
|
|
else:
|
|
raise ValueError("env_dv can only be 'real' or 'demo'")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/inquire-balance"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"TR_CRCY_CD": tr_crcy_cd,
|
|
"CTX_AREA_FK200": FK200,
|
|
"CTX_AREA_NK200": NK200,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url, ptr_id=tr_id, tr_cont=tr_cont, params=params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
# output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
tr_cont, FK200, NK200 = res.getHeader().tr_cont, res.getBody().ctx_area_fk200, res.getBody().ctx_area_nk200
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_balance(
|
|
cano,
|
|
acnt_prdt_cd,
|
|
ovrs_excg_cd,
|
|
tr_crcy_cd,
|
|
FK200,
|
|
NK200,
|
|
env_dv,
|
|
dataframe1,
|
|
dataframe2,
|
|
"N",
|
|
depth + 1,
|
|
max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 주문체결내역 [v1_해외주식-007]
|
|
##############################################################################################
|
|
|
|
def inquire_ccnl(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
pdno: str, # 상품번호
|
|
ord_strt_dt: str, # 주문시작일자
|
|
ord_end_dt: str, # 주문종료일자
|
|
sll_buy_dvsn: str, # 매도매수구분
|
|
ccld_nccs_dvsn: str, # 체결미체결구분
|
|
sort_sqn: str, # 정렬순서
|
|
ord_dt: str, # 주문일자
|
|
ord_gno_brno: str, # 주문채번지점번호
|
|
odno: str, # 주문번호
|
|
ovrs_excg_cd: str = "", # 해외거래소코드
|
|
NK200: str = "", # 연속조회키200
|
|
FK200: str = "", # 연속조회검색조건200
|
|
env_dv: str = "real", # 실전모의구분
|
|
tr_cont: str = "",
|
|
dataframe: Optional[pd.DataFrame] = None,
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 주문체결내역[v1_해외주식-007]
|
|
해외주식 주문체결내역 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
pdno (str): 전종목일 경우 "%" 입력 ※ 모의투자계좌의 경우 ""(전체 조회)만 가능
|
|
ord_strt_dt (str): YYYYMMDD 형식 (현지시각 기준)
|
|
ord_end_dt (str): YYYYMMDD 형식 (현지시각 기준)
|
|
sll_buy_dvsn (str): 00 : 전체 01 : 매도 02 : 매수 ※ 모의투자계좌의 경우 "00"(전체 조회)만 가능
|
|
ccld_nccs_dvsn (str): 00 : 전체 01 : 체결 02 : 미체결 ※ 모의투자계좌의 경우 "00"(전체 조회)만 가능
|
|
ovrs_excg_cd (str): 전종목일 경우 "%" 입력 NASD : 미국시장 전체(나스닥, 뉴욕, 아멕스) NYSE : 뉴욕 AMEX : 아멕스 SEHK : 홍콩 SHAA : 중국상해 SZAA : 중국심천 TKSE : 일본 HASE : 베트남 하노이 VNSE : 베트남 호치민 ※ 모의투자계좌의 경우 ""(전체 조회)만 가능
|
|
sort_sqn (str): DS : 정순 AS : 역순 ※ 모의투자계좌의 경우 정렬순서 사용불가(Default : DS(정순))
|
|
ord_dt (str): "" (Null 값 설정)
|
|
ord_gno_brno (str): "" (Null 값 설정)
|
|
odno (str): "" (Null 값 설정) ※ 주문번호로 검색 불가능합니다. 반드시 ""(Null 값 설정) 바랍니다.
|
|
NK200 (str): 공란 : 최초 조회시 이전 조회 Output CTX_AREA_NK200값 : 다음페이지 조회시(2번째부터)
|
|
FK200 (str): 공란 : 최초 조회시 이전 조회 Output CTX_AREA_FK200값 : 다음페이지 조회시(2번째부터)
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
tr_cont (str): 연속 거래 여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 주문체결내역 데이터
|
|
|
|
Example:
|
|
>>> df = inquire_ccnl(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... pdno="%",
|
|
... ord_strt_dt="20211027",
|
|
... ord_end_dt="20211027",
|
|
... sll_buy_dvsn="00",
|
|
... ccld_nccs_dvsn="00",
|
|
... ovrs_excg_cd="%%",
|
|
... sort_sqn="DS",
|
|
... ord_dt="",
|
|
... ord_gno_brno="02111",
|
|
... odno="",
|
|
... NK200="",
|
|
... FK200=""
|
|
... )
|
|
>>> print(df)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '810XXXXX')")
|
|
raise ValueError("cano is required. (e.g. '810XXXXX')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not ord_strt_dt:
|
|
logger.error("ord_strt_dt is required. (e.g. '20211027')")
|
|
raise ValueError("ord_strt_dt is required. (e.g. '20211027')")
|
|
if not ord_end_dt:
|
|
logger.error("ord_end_dt is required. (e.g. '20211027')")
|
|
raise ValueError("ord_end_dt is required. (e.g. '20211027')")
|
|
if not sll_buy_dvsn:
|
|
logger.error("sll_buy_dvsn is required. (e.g. '00')")
|
|
raise ValueError("sll_buy_dvsn is required. (e.g. '00')")
|
|
if not ccld_nccs_dvsn:
|
|
logger.error("ccld_nccs_dvsn is required. (e.g. '00')")
|
|
raise ValueError("ccld_nccs_dvsn is required. (e.g. '00')")
|
|
if not sort_sqn:
|
|
logger.error("sort_sqn is required. (e.g. 'DS')")
|
|
raise ValueError("sort_sqn is required. (e.g. 'DS')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
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()
|
|
|
|
# TR ID 설정 (모의투자 지원 로직)
|
|
if env_dv == "real":
|
|
tr_id = "TTTS3035R" # 실전투자용 TR ID
|
|
elif env_dv == "demo":
|
|
tr_id = "VTTS3035R" # 모의투자용 TR ID
|
|
else:
|
|
raise ValueError("env_dv can only be 'real' or 'demo'")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/inquire-ccnl"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"PDNO": pdno,
|
|
"ORD_STRT_DT": ord_strt_dt,
|
|
"ORD_END_DT": ord_end_dt,
|
|
"SLL_BUY_DVSN": sll_buy_dvsn,
|
|
"CCLD_NCCS_DVSN": ccld_nccs_dvsn,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"SORT_SQN": sort_sqn,
|
|
"ORD_DT": ord_dt,
|
|
"ORD_GNO_BRNO": ord_gno_brno,
|
|
"ODNO": odno,
|
|
"CTX_AREA_NK200": NK200,
|
|
"CTX_AREA_FK200": FK200,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url, ptr_id=tr_id, tr_cont=tr_cont, params=params)
|
|
|
|
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)
|
|
else:
|
|
current_data = pd.DataFrame()
|
|
|
|
if dataframe is not None:
|
|
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
|
else:
|
|
dataframe = current_data
|
|
|
|
tr_cont, NK200, FK200 = res.getHeader().tr_cont, res.getBody().ctx_area_nk200, res.getBody().ctx_area_fk200
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_ccnl(
|
|
cano=cano,
|
|
acnt_prdt_cd=acnt_prdt_cd,
|
|
pdno=pdno,
|
|
ord_strt_dt=ord_strt_dt,
|
|
ord_end_dt=ord_end_dt,
|
|
sll_buy_dvsn=sll_buy_dvsn,
|
|
ccld_nccs_dvsn=ccld_nccs_dvsn,
|
|
ovrs_excg_cd=ovrs_excg_cd,
|
|
sort_sqn=sort_sqn,
|
|
ord_dt=ord_dt,
|
|
ord_gno_brno=ord_gno_brno,
|
|
odno=odno,
|
|
NK200=NK200,
|
|
FK200=FK200,
|
|
env_dv=env_dv,
|
|
tr_cont="N",
|
|
dataframe=dataframe,
|
|
depth=depth + 1,
|
|
max_depth=max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 미체결내역 [v1_해외주식-005]
|
|
##############################################################################################
|
|
|
|
def inquire_nccs(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
sort_sqn: str, # 정렬순서
|
|
FK200: str, # 연속조회검색조건200
|
|
NK200: str, # 연속조회키200
|
|
env_dv: str = "real", # 실전모의구분
|
|
tr_cont: str = "",
|
|
dataframe: Optional[pd.DataFrame] = None,
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 미체결내역[v1_해외주식-005]
|
|
해외주식 미체결내역 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
ovrs_excg_cd (str): NASD : 나스닥 NYSE : 뉴욕 AMEX : 아멕스 SEHK : 홍콩 SHAA : 중국상해 SZAA : 중국심천 TKSE : 일본 HASE : 베트남 하노이 VNSE : 베트남 호치민 * NASD 인 경우만 미국전체로 조회되며 나머지 거래소 코드는 해당 거래소만 조회됨 * 공백 입력 시 다음조회가 불가능하므로, 반드시 거래소코드 입력해야 함
|
|
sort_sqn (str): DS : 정순 그외 : 역순 [header tr_id: TTTS3018R] ""(공란)
|
|
FK200 (str): 공란 : 최초 조회시 이전 조회 Output CTX_AREA_FK200값 : 다음페이지 조회시(2번째부터)
|
|
NK200 (str): 공란 : 최초 조회시 이전 조회 Output CTX_AREA_NK200값 : 다음페이지 조회시(2번째부터)
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
tr_cont (str): 연속 거래 여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 미체결내역 데이터
|
|
|
|
Example:
|
|
>>> df = inquire_nccs(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... ovrs_excg_cd="NYSE",
|
|
... sort_sqn="DS",
|
|
... FK200="",
|
|
... NK200=""
|
|
... )
|
|
>>> print(df)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '810XXXXX')")
|
|
raise ValueError("cano is required. (e.g. '810XXXXX')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not ovrs_excg_cd:
|
|
logger.error("ovrs_excg_cd is required. (e.g. 'NYSE')")
|
|
raise ValueError("ovrs_excg_cd is required. (e.g. 'NYSE')")
|
|
if not sort_sqn:
|
|
logger.error("sort_sqn is required. (e.g. 'DS')")
|
|
raise ValueError("sort_sqn is required. (e.g. 'DS')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
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()
|
|
|
|
tr_id = "TTTS3018R"
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/inquire-nccs"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"SORT_SQN": sort_sqn,
|
|
"CTX_AREA_FK200": FK200,
|
|
"CTX_AREA_NK200": NK200,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url, ptr_id=tr_id, tr_cont=tr_cont, params=params)
|
|
|
|
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)
|
|
else:
|
|
current_data = pd.DataFrame()
|
|
|
|
if dataframe is not None:
|
|
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
|
else:
|
|
dataframe = current_data
|
|
|
|
tr_cont, NK200, FK200 = res.getHeader().tr_cont, res.getBody().ctx_area_nk200, res.getBody().ctx_area_fk200
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_nccs(
|
|
cano=cano,
|
|
acnt_prdt_cd=acnt_prdt_cd,
|
|
ovrs_excg_cd=ovrs_excg_cd,
|
|
sort_sqn=sort_sqn,
|
|
FK200=FK200,
|
|
NK200=NK200,
|
|
env_dv=env_dv,
|
|
tr_cont="N",
|
|
dataframe=dataframe,
|
|
depth=depth + 1,
|
|
max_depth=max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
# 이미 수집된 데이터가 있으면 그것을 반환, 없으면 빈 DataFrame 반환
|
|
if dataframe is not None and not dataframe.empty:
|
|
logger.info("Returning already collected data due to API error.")
|
|
return dataframe
|
|
else:
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 결제기준잔고 [해외주식-064]
|
|
##############################################################################################
|
|
|
|
def inquire_paymt_stdr_balance(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
bass_dt: str, # 기준일자
|
|
wcrc_frcr_dvsn_cd: str, # 원화외화구분코드
|
|
inqr_dvsn_cd: str, # 조회구분코드
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
dataframe3: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output3)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 결제기준잔고[해외주식-064]
|
|
해외주식 결제기준잔고 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 종합계좌번호
|
|
acnt_prdt_cd (str): 계좌상품코드
|
|
bass_dt (str): 기준일자
|
|
wcrc_frcr_dvsn_cd (str): 원화외화구분코드 (01: 원화기준, 02: 외화기준)
|
|
inqr_dvsn_cd (str): 조회구분코드 (00: 전체, 01: 일반, 02: 미니스탁)
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
dataframe3 (Optional[pd.DataFrame]): 누적 데이터프레임 (output3)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: 해외주식 결제기준잔고 데이터
|
|
|
|
Example:
|
|
>>> df1, df2, df3 = inquire_paymt_stdr_balance(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... bass_dt="20230630",
|
|
... wcrc_frcr_dvsn_cd="01",
|
|
... inqr_dvsn_cd="00"
|
|
... )
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '12345678')")
|
|
raise ValueError("cano is required. (e.g. '12345678')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not bass_dt:
|
|
logger.error("bass_dt is required. (e.g. '20230630')")
|
|
raise ValueError("bass_dt is required. (e.g. '20230630')")
|
|
if not wcrc_frcr_dvsn_cd:
|
|
logger.error("wcrc_frcr_dvsn_cd is required. (e.g. '01')")
|
|
raise ValueError("wcrc_frcr_dvsn_cd is required. (e.g. '01')")
|
|
if not inqr_dvsn_cd:
|
|
logger.error("inqr_dvsn_cd is required. (e.g. '00')")
|
|
raise ValueError("inqr_dvsn_cd is required. (e.g. '00')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame(), dataframe3 if dataframe3 is not None else pd.DataFrame()
|
|
|
|
tr_id = "CTRP6010R"
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/inquire-paymt-stdr-balance"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"BASS_DT": bass_dt,
|
|
"WCRC_FRCR_DVSN_CD": wcrc_frcr_dvsn_cd,
|
|
"INQR_DVSN_CD": inqr_dvsn_cd,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url, ptr_id=tr_id, tr_cont=tr_cont, params=params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
# output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
# output3 처리
|
|
if hasattr(res.getBody(), 'output3'):
|
|
output_data = res.getBody().output3
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data3 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data3 = pd.DataFrame([output_data])
|
|
|
|
if dataframe3 is not None:
|
|
dataframe3 = pd.concat([dataframe3, current_data3], ignore_index=True)
|
|
else:
|
|
dataframe3 = current_data3
|
|
else:
|
|
if dataframe3 is None:
|
|
dataframe3 = pd.DataFrame()
|
|
else:
|
|
if dataframe3 is None:
|
|
dataframe3 = pd.DataFrame()
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_paymt_stdr_balance(
|
|
cano=cano,
|
|
acnt_prdt_cd=acnt_prdt_cd,
|
|
bass_dt=bass_dt,
|
|
wcrc_frcr_dvsn_cd=wcrc_frcr_dvsn_cd,
|
|
inqr_dvsn_cd=inqr_dvsn_cd,
|
|
dataframe1=dataframe1,
|
|
dataframe2=dataframe2,
|
|
dataframe3=dataframe3,
|
|
tr_cont="N",
|
|
depth=depth + 1,
|
|
max_depth=max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2, dataframe3
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
# 이미 수집된 데이터가 있으면 그것을 반환, 없으면 빈 DataFrame 반환
|
|
if dataframe1 is not None and not dataframe1.empty:
|
|
logger.info("Returning already collected data due to API error.")
|
|
return dataframe1, dataframe2 if dataframe2 is not None else pd.DataFrame(), dataframe3 if dataframe3 is not None else pd.DataFrame()
|
|
else:
|
|
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 기간손익 [v1_해외주식-032]
|
|
##############################################################################################
|
|
|
|
def inquire_period_profit(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
natn_cd: str, # 국가코드
|
|
crcy_cd: str, # 통화코드
|
|
pdno: str, # 상품번호
|
|
inqr_strt_dt: str, # 조회시작일자
|
|
inqr_end_dt: str, # 조회종료일자
|
|
wcrc_frcr_dvsn_cd: str, # 원화외화구분코드
|
|
FK200: str, # 연속조회검색조건200
|
|
NK200: str, # 연속조회키200
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 기간손익[v1_해외주식-032]
|
|
해외주식 기간손익 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
ovrs_excg_cd (str): 공란 : 전체, NASD : 미국, SEHK : 홍콩, SHAA : 중국, TKSE : 일본, HASE : 베트남
|
|
natn_cd (str): 공란(Default)
|
|
crcy_cd (str): 공란 : 전체 USD : 미국달러, HKD : 홍콩달러, CNY : 중국위안화, JPY : 일본엔화, VND : 베트남동
|
|
pdno (str): 공란 : 전체
|
|
inqr_strt_dt (str): YYYYMMDD
|
|
inqr_end_dt (str): YYYYMMDD
|
|
wcrc_frcr_dvsn_cd (str): 01 : 외화, 02 : 원화
|
|
FK200 (str): 연속조회검색조건200
|
|
NK200 (str): 연속조회키200
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외주식 기간손익 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = inquire_period_profit(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... ovrs_excg_cd="NASD",
|
|
... natn_cd="",
|
|
... crcy_cd="USD",
|
|
... pdno="",
|
|
... inqr_strt_dt="20230101",
|
|
... inqr_end_dt="20231231",
|
|
... wcrc_frcr_dvsn_cd="01",
|
|
... FK200="",
|
|
... NK200=""
|
|
... )
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '12345678')")
|
|
raise ValueError("cano is required. (e.g. '12345678')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not ovrs_excg_cd:
|
|
logger.error("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
raise ValueError("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
if not crcy_cd:
|
|
logger.error("crcy_cd is required. (e.g. 'USD')")
|
|
raise ValueError("crcy_cd is required. (e.g. 'USD')")
|
|
if not inqr_strt_dt:
|
|
logger.error("inqr_strt_dt is required. (e.g. '20230101')")
|
|
raise ValueError("inqr_strt_dt is required. (e.g. '20230101')")
|
|
if not inqr_end_dt:
|
|
logger.error("inqr_end_dt is required. (e.g. '20231231')")
|
|
raise ValueError("inqr_end_dt is required. (e.g. '20231231')")
|
|
if not wcrc_frcr_dvsn_cd:
|
|
logger.error("wcrc_frcr_dvsn_cd is required. (e.g. '01')")
|
|
raise ValueError("wcrc_frcr_dvsn_cd is required. (e.g. '01')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
|
|
tr_id = "TTTS3039R"
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/inquire-period-profit"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"NATN_CD": natn_cd,
|
|
"CRCY_CD": crcy_cd,
|
|
"PDNO": pdno,
|
|
"INQR_STRT_DT": inqr_strt_dt,
|
|
"INQR_END_DT": inqr_end_dt,
|
|
"WCRC_FRCR_DVSN_CD": wcrc_frcr_dvsn_cd,
|
|
"CTX_AREA_FK200": FK200,
|
|
"CTX_AREA_NK200": NK200,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url, ptr_id=tr_id, tr_cont=tr_cont, params=params)
|
|
|
|
if res.isOK():
|
|
# Output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
# Output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
|
|
tr_cont, NK200, FK200 = res.getHeader().tr_cont, res.getBody().ctx_area_nk200, res.getBody().ctx_area_fk200
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_period_profit(
|
|
cano=cano,
|
|
acnt_prdt_cd=acnt_prdt_cd,
|
|
ovrs_excg_cd=ovrs_excg_cd,
|
|
natn_cd=natn_cd,
|
|
crcy_cd=crcy_cd,
|
|
pdno=pdno,
|
|
inqr_strt_dt=inqr_strt_dt,
|
|
inqr_end_dt=inqr_end_dt,
|
|
wcrc_frcr_dvsn_cd=wcrc_frcr_dvsn_cd,
|
|
FK200=FK200,
|
|
NK200=NK200,
|
|
dataframe1=dataframe1,
|
|
dataframe2=dataframe2,
|
|
tr_cont="N",
|
|
depth=depth + 1,
|
|
max_depth=max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
# 이미 수집된 데이터가 있으면 그것을 반환, 없으면 빈 DataFrame 반환
|
|
if dataframe1 is not None and not dataframe1.empty:
|
|
logger.info("Returning already collected data due to API error.")
|
|
return dataframe1, dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
else:
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 일별거래내역 [해외주식-063]
|
|
##############################################################################################
|
|
|
|
def inquire_period_trans(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
erlm_strt_dt: str, # 등록시작일자
|
|
erlm_end_dt: str, # 등록종료일자
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
pdno: str, # 상품번호
|
|
sll_buy_dvsn_cd: str, # 매도매수구분코드
|
|
loan_dvsn_cd: str, # 대출구분코드
|
|
FK100: str, # 연속조회검색조건100
|
|
NK100: str, # 연속조회키100
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 일별거래내역[해외주식-063]
|
|
해외주식 일별거래내역 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 종합계좌번호
|
|
acnt_prdt_cd (str): 계좌상품코드
|
|
erlm_strt_dt (str): 등록시작일자 (예: 20240420)
|
|
erlm_end_dt (str): 등록종료일자 (예: 20240520)
|
|
ovrs_excg_cd (str): 해외거래소코드
|
|
pdno (str): 상품번호
|
|
sll_buy_dvsn_cd (str): 매도매수구분코드 (00: 전체, 01: 매도, 02: 매수)
|
|
loan_dvsn_cd (str): 대출구분코드
|
|
FK100 (str): 연속조회검색조건100
|
|
NK100 (str): 연속조회키100
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외주식 일별거래내역 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = inquire_period_trans(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... erlm_strt_dt="20240420",
|
|
... erlm_end_dt="20240520",
|
|
... ovrs_excg_cd="NAS",
|
|
... pdno="",
|
|
... sll_buy_dvsn_cd="00",
|
|
... loan_dvsn_cd="",
|
|
... FK100="",
|
|
... NK100=""
|
|
... )
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '12345678')")
|
|
raise ValueError("cano is required. (e.g. '12345678')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not erlm_strt_dt:
|
|
logger.error("erlm_strt_dt is required. (e.g. '20240420')")
|
|
raise ValueError("erlm_strt_dt is required. (e.g. '20240420')")
|
|
if not erlm_end_dt:
|
|
logger.error("erlm_end_dt is required. (e.g. '20240520')")
|
|
raise ValueError("erlm_end_dt is required. (e.g. '20240520')")
|
|
if not ovrs_excg_cd:
|
|
logger.error("ovrs_excg_cd is required. (e.g. 'NAS')")
|
|
raise ValueError("ovrs_excg_cd is required. (e.g. 'NAS')")
|
|
if not sll_buy_dvsn_cd:
|
|
logger.error("sll_buy_dvsn_cd is required. (e.g. '00')")
|
|
raise ValueError("sll_buy_dvsn_cd is required. (e.g. '00')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
|
|
tr_id = "CTOS4001R"
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/inquire-period-trans"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"ERLM_STRT_DT": erlm_strt_dt,
|
|
"ERLM_END_DT": erlm_end_dt,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"PDNO": pdno,
|
|
"SLL_BUY_DVSN_CD": sll_buy_dvsn_cd,
|
|
"LOAN_DVSN_CD": loan_dvsn_cd,
|
|
"CTX_AREA_FK100": FK100,
|
|
"CTX_AREA_NK100": NK100,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url, ptr_id=tr_id, tr_cont=tr_cont, params=params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
# output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
|
|
tr_cont, NK100, FK100 = res.getHeader().tr_cont, res.getBody().ctx_area_nk100, res.getBody().ctx_area_fk100
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_period_trans(
|
|
cano=cano,
|
|
acnt_prdt_cd=acnt_prdt_cd,
|
|
erlm_strt_dt=erlm_strt_dt,
|
|
erlm_end_dt=erlm_end_dt,
|
|
ovrs_excg_cd=ovrs_excg_cd,
|
|
pdno=pdno,
|
|
sll_buy_dvsn_cd=sll_buy_dvsn_cd,
|
|
loan_dvsn_cd=loan_dvsn_cd,
|
|
FK100=FK100,
|
|
NK100=NK100,
|
|
dataframe1=dataframe1,
|
|
dataframe2=dataframe2,
|
|
tr_cont="N",
|
|
depth=depth + 1,
|
|
max_depth=max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
# 이미 수집된 데이터가 있으면 그것을 반환, 없으면 빈 DataFrame 반환
|
|
if dataframe1 is not None and not dataframe1.empty:
|
|
logger.info("Returning already collected data due to API error.")
|
|
return dataframe1, dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
else:
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 체결기준현재잔고 [v1_해외주식-008]
|
|
##############################################################################################
|
|
|
|
def inquire_present_balance(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
wcrc_frcr_dvsn_cd: str, # 원화외화구분코드
|
|
natn_cd: str, # 국가코드
|
|
tr_mket_cd: str, # 거래시장코드
|
|
inqr_dvsn_cd: str, # 조회구분코드
|
|
env_dv: str = "real", # 실전모의구분
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
dataframe3: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output3)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 체결기준현재잔고[v1_해외주식-008]
|
|
해외주식 체결기준현재잔고 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
wcrc_frcr_dvsn_cd (str): 01 : 원화 02 : 외화
|
|
natn_cd (str): 000 전체 840 미국 344 홍콩 156 중국 392 일본 704 베트남
|
|
tr_mket_cd (str): [Request body NATN_CD 000 설정] 00 : 전체 [Request body NATN_CD 840 설정] 00 : 전체 01 : 나스닥(NASD) 02 : 뉴욕거래소(NYSE) 03 : 미국(PINK SHEETS) 04 : 미국(OTCBB) 05 : 아멕스(AMEX) [Request body NATN_CD 156 설정] 00 : 전체 01 : 상해B 02 : 심천B 03 : 상해A 04 : 심천A [Request body NATN_CD 392 설정] 01 : 일본 [Request body NATN_CD 704 설정] 01 : 하노이거래 02 : 호치민거래소 [Request body NATN_CD 344 설정] 01 : 홍콩 02 : 홍콩CNY 03 : 홍콩USD
|
|
inqr_dvsn_cd (str): 00 : 전체 01 : 일반해외주식 02 : 미니스탁
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
dataframe3 (Optional[pd.DataFrame]): 누적 데이터프레임 (output3)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: 해외주식 체결기준현재잔고 데이터
|
|
|
|
Example:
|
|
>>> df1, df2, df3 = inquire_present_balance(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... wcrc_frcr_dvsn_cd="01",
|
|
... natn_cd="000",
|
|
... tr_mket_cd="00",
|
|
... inqr_dvsn_cd="00"
|
|
... )
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
>>> print(df3)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '810XXXXX')")
|
|
raise ValueError("cano is required. (e.g. '810XXXXX')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not wcrc_frcr_dvsn_cd:
|
|
logger.error("wcrc_frcr_dvsn_cd is required. (e.g. '01')")
|
|
raise ValueError("wcrc_frcr_dvsn_cd is required. (e.g. '01')")
|
|
if not natn_cd:
|
|
logger.error("natn_cd is required. (e.g. '000')")
|
|
raise ValueError("natn_cd is required. (e.g. '000')")
|
|
if not tr_mket_cd:
|
|
logger.error("tr_mket_cd is required. (e.g. '00')")
|
|
raise ValueError("tr_mket_cd is required. (e.g. '00')")
|
|
if not inqr_dvsn_cd:
|
|
logger.error("inqr_dvsn_cd is required. (e.g. '00')")
|
|
raise ValueError("inqr_dvsn_cd is required. (e.g. '00')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame(), dataframe3 if dataframe3 is not None else pd.DataFrame()
|
|
|
|
# TR ID 설정 (모의투자 지원 로직)
|
|
if env_dv == "real":
|
|
tr_id = "CTRP6504R" # 실전투자용 TR ID
|
|
elif env_dv == "demo":
|
|
tr_id = "VTRP6504R" # 모의투자용 TR ID
|
|
else:
|
|
raise ValueError("env_dv can only be 'real' or 'demo'")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/inquire-present-balance"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"WCRC_FRCR_DVSN_CD": wcrc_frcr_dvsn_cd,
|
|
"NATN_CD": natn_cd,
|
|
"TR_MKET_CD": tr_mket_cd,
|
|
"INQR_DVSN_CD": inqr_dvsn_cd,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url, ptr_id=tr_id, tr_cont=tr_cont, params=params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
|
|
# output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
|
|
# output3 처리
|
|
if hasattr(res.getBody(), 'output3'):
|
|
output_data = res.getBody().output3
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data3 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data3 = pd.DataFrame([output_data])
|
|
|
|
if dataframe3 is not None:
|
|
dataframe3 = pd.concat([dataframe3, current_data3], ignore_index=True)
|
|
else:
|
|
dataframe3 = current_data3
|
|
else:
|
|
if dataframe3 is None:
|
|
dataframe3 = pd.DataFrame()
|
|
else:
|
|
if dataframe3 is None:
|
|
dataframe3 = pd.DataFrame()
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_present_balance(
|
|
cano=cano,
|
|
acnt_prdt_cd=acnt_prdt_cd,
|
|
wcrc_frcr_dvsn_cd=wcrc_frcr_dvsn_cd,
|
|
natn_cd=natn_cd,
|
|
tr_mket_cd=tr_mket_cd,
|
|
inqr_dvsn_cd=inqr_dvsn_cd,
|
|
env_dv=env_dv,
|
|
dataframe1=dataframe1,
|
|
dataframe2=dataframe2,
|
|
dataframe3=dataframe3,
|
|
tr_cont="N",
|
|
depth=depth + 1,
|
|
max_depth=max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2, dataframe3
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
# 이미 수집된 데이터가 있으면 그것을 반환, 없으면 빈 DataFrame 반환
|
|
if dataframe1 is not None and not dataframe1.empty:
|
|
logger.info("Returning already collected data due to API error.")
|
|
return dataframe1, dataframe2 if dataframe2 is not None else pd.DataFrame(), dataframe3 if dataframe3 is not None else pd.DataFrame()
|
|
else:
|
|
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 매수가능금액조회 [v1_해외주식-014]
|
|
##############################################################################################
|
|
|
|
def inquire_psamount(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
ovrs_ord_unpr: str, # 해외주문단가
|
|
item_cd: str, # 종목코드
|
|
env_dv: str = "real", # 실전모의구분
|
|
tr_cont: str = "",
|
|
dataframe: Optional[pd.DataFrame] = None,
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 매수가능금액조회[v1_해외주식-014]
|
|
해외주식 매수가능금액조회 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
ovrs_excg_cd (str): NASD : 나스닥 / NYSE : 뉴욕 / AMEX : 아멕스 SEHK : 홍콩 / SHAA : 중국상해 / SZAA : 중국심천 TKSE : 일본 / HASE : 하노이거래소 / VNSE : 호치민거래소
|
|
ovrs_ord_unpr (str): 해외주문단가 (23.8) 정수부분 23자리, 소수부분 8자리
|
|
item_cd (str): 종목코드
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
tr_cont (str): 연속 거래 여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 매수가능금액조회 데이터
|
|
|
|
Example:
|
|
>>> df = inquire_psamount(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... ovrs_excg_cd="NASD",
|
|
... ovrs_ord_unpr="1.4",
|
|
... item_cd="QQQ"
|
|
... )
|
|
>>> print(df)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '81019777')")
|
|
raise ValueError("cano is required. (e.g. '81019777')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not ovrs_excg_cd:
|
|
logger.error("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
raise ValueError("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
if not ovrs_ord_unpr:
|
|
logger.error("ovrs_ord_unpr is required. (e.g. '1.4')")
|
|
raise ValueError("ovrs_ord_unpr is required. (e.g. '1.4')")
|
|
if not item_cd:
|
|
logger.error("item_cd is required. (e.g. 'QQQ')")
|
|
raise ValueError("item_cd is required. (e.g. 'QQQ')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
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()
|
|
|
|
# TR ID 설정 (모의투자 지원 로직)
|
|
if env_dv == "real":
|
|
tr_id = "TTTS3007R" # 실전투자용 TR ID
|
|
elif env_dv == "demo":
|
|
tr_id = "VTTS3007R" # 모의투자용 TR ID
|
|
else:
|
|
raise ValueError("env_dv can only be 'real' or 'demo'")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/inquire-psamount"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"OVRS_ORD_UNPR": ovrs_ord_unpr,
|
|
"ITEM_CD": item_cd,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url, ptr_id=tr_id, tr_cont=tr_cont, params=params)
|
|
|
|
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)
|
|
else:
|
|
current_data = pd.DataFrame()
|
|
|
|
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()
|
|
return inquire_psamount(
|
|
cano=cano,
|
|
acnt_prdt_cd=acnt_prdt_cd,
|
|
ovrs_excg_cd=ovrs_excg_cd,
|
|
ovrs_ord_unpr=ovrs_ord_unpr,
|
|
item_cd=item_cd,
|
|
env_dv=env_dv,
|
|
tr_cont="N",
|
|
dataframe=dataframe,
|
|
depth=depth + 1,
|
|
max_depth=max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 시가총액순위[해외주식-047]
|
|
##############################################################################################
|
|
|
|
def market_cap(
|
|
excd: str, # 거래소명
|
|
vol_rang: str, # 거래량조건
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
auth: str = "", # 사용자권한정보
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 output1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 output2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
해외주식 시가총액순위 조회 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
keyb (str): NEXT KEY BUFF (ex. "")
|
|
auth (str): 사용자권한정보 (ex. "")
|
|
tr_cont (str): 연속거래여부 (ex. "")
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 output1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 output2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 시가총액순위 데이터 (output1, output2)
|
|
|
|
Example:
|
|
>>> df1, df2 = market_cap(excd="SZS", vol_rang="1")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError(
|
|
"excd is required (e.g. 'NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError(
|
|
"vol_rang is required (e.g. '0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None or dataframe2 is None:
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
else:
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76350100" # 해외주식 시가총액순위
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/market-cap"
|
|
|
|
params = {
|
|
"EXCD": excd, # 거래소명
|
|
"VOL_RANG": vol_rang, # 거래량조건
|
|
"KEYB": keyb, # NEXT KEY BUFF
|
|
"AUTH": auth, # 사용자권한정보
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
current_data1 = pd.DataFrame(res.getBody().output1, index=[0])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return market_cap(
|
|
excd, vol_rang, keyb, auth, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 신고/신저가[해외주식-042]
|
|
##############################################################################################
|
|
|
|
def new_highlow(
|
|
excd: str, # [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
mixn: str, # [필수] N분전콤보값 (ex. 0:1분전, 1:2분전, 2:3분전, 3:5분전, 4:10분전, 5:15분전, 6:20분전, 7:30분전, 8:60분전, 9:120분전)
|
|
vol_rang: str, # [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
gubn: str, # [필수] 신고/신저 구분 (ex. 0:신저,1:신고)
|
|
gubn2: str, # [필수] 일시돌파/돌파 구분 (ex. 0:일시돌파0, 1:돌파유지1)
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
auth: str = "", # 사용자권한정보
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 output1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 output2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 시세분석 > 해외주식 신고/신저가[해외주식-042]
|
|
해외주식 신고/신저가 정보를 조회하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
mixn (str): [필수] N분전콤보값 (ex. 0:1분전, 1:2분전, 2:3분전, 3:5분전, 4:10분전, 5:15분전, 6:20분전, 7:30분전, 8:60분전, 9:120분전)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
gubn (str): [필수] 신고/신저 구분 (ex. 0:신저,1:신고)
|
|
gubn2 (str): [필수] 일시돌파/돌파 구분 (ex. 0:일시돌파0, 1:돌파유지1)
|
|
keyb (str): NEXT KEY BUFF
|
|
auth (str): 사용자권한정보
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 output1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 output2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: (output1 데이터, output2 데이터)
|
|
|
|
Example:
|
|
>>> output1, output2 = new_highlow(excd="AMS", mixn="0", vol_rang="0", gubn="1", gubn2="1")
|
|
>>> print(output1)
|
|
>>> print(output2)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError("excd is required (e.g. 'NYS')")
|
|
|
|
if mixn == "":
|
|
raise ValueError("mixn is required (e.g. '0')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError("vol_rang is required (e.g. '0')")
|
|
|
|
if gubn == "":
|
|
raise ValueError("gubn is required (e.g. '1')")
|
|
|
|
if gubn2 == "":
|
|
raise ValueError("gubn2 is required (e.g. '1')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76300000" # 해외주식 신고/신저가
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/new-highlow"
|
|
|
|
params = {
|
|
"EXCD": excd,
|
|
"MIXN": mixn,
|
|
"VOL_RANG": vol_rang,
|
|
"GUBN": gubn,
|
|
"GUBN2": gubn2,
|
|
"KEYB": keyb,
|
|
"AUTH": auth
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리 (object 타입)
|
|
current_data1 = pd.DataFrame([res.getBody().output1])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리 (array 타입)
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return new_highlow(
|
|
excd, mixn, vol_rang, gubn, gubn2, keyb, auth, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 주문 [v1_해외주식-001]
|
|
##############################################################################################
|
|
|
|
def order(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
pdno: str, # 상품번호
|
|
ord_qty: str, # 주문수량
|
|
ovrs_ord_unpr: str, # 해외주문단가
|
|
ord_dv: str, # 주문구분 (buy: 매수, sell: 매도)
|
|
ctac_tlno: str, # 연락전화번호
|
|
mgco_aptm_odno: str, # 운용사지정주문번호
|
|
ord_svr_dvsn_cd: str, # 주문서버구분코드
|
|
ord_dvsn: str, # 주문구분
|
|
env_dv: str = "real", # 실전모의구분
|
|
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 주문[v1_해외주식-001]
|
|
해외주식 주문 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
ovrs_excg_cd (str): NASD : 나스닥 NYSE : 뉴욕 AMEX : 아멕스 SEHK : 홍콩 SHAA : 중국상해 SZAA : 중국심천 TKSE : 일본 HASE : 베트남 하노이 VNSE : 베트남 호치민
|
|
pdno (str): 종목코드
|
|
ord_qty (str): 주문수량 (해외거래소 별 최소 주문수량 및 주문단위 확인 필요)
|
|
ovrs_ord_unpr (str): 1주당 가격 * 시장가의 경우 1주당 가격을 공란으로 비우지 않음 "0"으로 입력
|
|
ord_dv (str): 주문구분 (buy: 매수, sell: 매도)
|
|
ctac_tlno (str):
|
|
mgco_aptm_odno (str):
|
|
ord_svr_dvsn_cd (str): "0"(Default)
|
|
ord_dvsn (str): [Header tr_id TTTT1002U(미국 매수 주문)] 00 : 지정가 32 : LOO(장개시지정가) 34 : LOC(장마감지정가) * 모의투자 VTTT1002U(미국 매수 주문)로는 00:지정가만 가능 [Header tr_id TTTT1006U(미국 매도 주문)] 00 : 지정가 31 : MOO(장개시시장가) 32 : LOO(장개시지정가) 33 : MOC(장마감시장가) 34 : LOC(장마감지정가) * 모의투자 VTTT1006U(미국 매도 주문)로는 00:지정가만 가능 [Header tr_id TTTS1001U(홍콩 매도 주문)] 00 : 지정가 50 : 단주지정가 * 모의투자 VTTS1001U(홍콩 매도 주문)로는 00:지정가만 가능 [그외 tr_id] 제거
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 주문 데이터
|
|
|
|
Example:
|
|
>>> df = order(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... ovrs_excg_cd="NASD",
|
|
... pdno="AAPL",
|
|
... ord_qty="1",
|
|
... ovrs_ord_unpr="145.00",
|
|
... ord_dv="buy",
|
|
... ctac_tlno="",
|
|
... mgco_aptm_odno="",
|
|
... ord_svr_dvsn_cd="0",
|
|
... ord_dvsn="00",
|
|
... env_dv="real"
|
|
... )
|
|
>>> print(df)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '810XXXXX')")
|
|
raise ValueError("cano is required. (e.g. '810XXXXX')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not ovrs_excg_cd:
|
|
logger.error("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
raise ValueError("ovrs_excg_cd is required. (e.g. 'NASD')")
|
|
if not pdno:
|
|
logger.error("pdno is required. (e.g. 'AAPL')")
|
|
raise ValueError("pdno is required. (e.g. 'AAPL')")
|
|
if not ord_qty:
|
|
logger.error("ord_qty is required. (e.g. '1')")
|
|
raise ValueError("ord_qty is required. (e.g. '1')")
|
|
if not ovrs_ord_unpr:
|
|
logger.error("ovrs_ord_unpr is required. (e.g. '145.00')")
|
|
raise ValueError("ovrs_ord_unpr is required. (e.g. '145.00')")
|
|
if not ord_dv:
|
|
logger.error("ord_dv is required. (e.g. 'buy' or 'sell')")
|
|
raise ValueError("ord_dv is required. (e.g. 'buy' or 'sell')")
|
|
if not ord_svr_dvsn_cd:
|
|
logger.error("ord_svr_dvsn_cd is required. (e.g. '0')")
|
|
raise ValueError("ord_svr_dvsn_cd is required. (e.g. '0')")
|
|
if not ord_dvsn:
|
|
logger.error("ord_dvsn is required. (e.g. '00')")
|
|
raise ValueError("ord_dvsn is required. (e.g. '00')")
|
|
|
|
# TR ID 설정 (매수/매도 및 거래소별)
|
|
if ord_dv == "buy":
|
|
if ovrs_excg_cd in ("NASD", "NYSE", "AMEX"):
|
|
tr_id = "TTTT1002U" # 미국 매수 주문 [모의투자] VTTT1002U
|
|
elif ovrs_excg_cd == "SEHK":
|
|
tr_id = "TTTS1002U" # 홍콩 매수 주문 [모의투자] VTTS1002U
|
|
elif ovrs_excg_cd == "SHAA":
|
|
tr_id = "TTTS0202U" # 중국상해 매수 주문 [모의투자] VTTS0202U
|
|
elif ovrs_excg_cd == "SZAA":
|
|
tr_id = "TTTS0305U" # 중국심천 매수 주문 [모의투자] VTTS0305U
|
|
elif ovrs_excg_cd == "TKSE":
|
|
tr_id = "TTTS0308U" # 일본 매수 주문 [모의투자] VTTS0308U
|
|
elif ovrs_excg_cd in ("HASE", "VNSE"):
|
|
tr_id = "TTTS0311U" # 베트남(하노이,호치민) 매수 주문 [모의투자] VTTS0311U
|
|
else:
|
|
logger.error(
|
|
"ovrs_excg_cd is required. (e.g. 'NASD', 'NYSE', 'AMEX', 'SEHK', 'SHAA', 'SZAA', 'TKSE', 'HASE', 'VNSE')")
|
|
raise ValueError(
|
|
"ovrs_excg_cd is required. (e.g. 'NASD', 'NYSE', 'AMEX', 'SEHK', 'SHAA', 'SZAA', 'TKSE', 'HASE', 'VNSE')")
|
|
sll_type = ""
|
|
elif ord_dv == "sell":
|
|
if ovrs_excg_cd in ("NASD", "NYSE", "AMEX"):
|
|
tr_id = "TTTT1006U" # 미국 매도 주문 [모의투자] VTTT1006U
|
|
elif ovrs_excg_cd == "SEHK":
|
|
tr_id = "TTTS1001U" # 홍콩 매도 주문 [모의투자] VTTS1001U
|
|
elif ovrs_excg_cd == "SHAA":
|
|
tr_id = "TTTS1005U" # 중국상해 매도 주문 [모의투자] VTTS1005U
|
|
elif ovrs_excg_cd == "SZAA":
|
|
tr_id = "TTTS0304U" # 중국심천 매도 주문 [모의투자] VTTS0304U
|
|
elif ovrs_excg_cd == "TKSE":
|
|
tr_id = "TTTS0307U" # 일본 매도 주문 [모의투자] VTTS0307U
|
|
elif ovrs_excg_cd in ("HASE", "VNSE"):
|
|
tr_id = "TTTS0310U" # 베트남(하노이,호치민) 매도 주문 [모의투자] VTTS0310U
|
|
else:
|
|
logger.error(
|
|
"ovrs_excg_cd is required. (e.g. 'NASD', 'NYSE', 'AMEX', 'SEHK', 'SHAA', 'SZAA', 'TKSE', 'HASE', 'VNSE')")
|
|
raise ValueError(
|
|
"ovrs_excg_cd is required. (e.g. 'NASD', 'NYSE', 'AMEX', 'SEHK', 'SHAA', 'SZAA', 'TKSE', 'HASE', 'VNSE')")
|
|
sll_type = "00"
|
|
else:
|
|
logger.error("ord_dv is required. (e.g. 'buy' or 'sell')")
|
|
raise ValueError("ord_dv is required. (e.g. 'buy' or 'sell')")
|
|
|
|
# 모의투자인 경우 TR ID 앞에 V 붙이기
|
|
if env_dv == "demo":
|
|
tr_id = "V" + tr_id[1:]
|
|
elif env_dv != "real":
|
|
logger.error("env_dv can only be 'real' or 'demo'")
|
|
raise ValueError("env_dv can only be 'real' or 'demo'")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/order"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"PDNO": pdno,
|
|
"ORD_QTY": ord_qty,
|
|
"OVRS_ORD_UNPR": ovrs_ord_unpr,
|
|
"CTAC_TLNO": ctac_tlno,
|
|
"MGCO_APTM_ODNO": mgco_aptm_odno,
|
|
"SLL_TYPE": sll_type,
|
|
"ORD_SVR_DVSN_CD": ord_svr_dvsn_cd,
|
|
"ORD_DVSN": ord_dvsn,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url,
|
|
ptr_id=tr_id,
|
|
tr_cont="",
|
|
params=params,
|
|
postFlag=True
|
|
)
|
|
|
|
if res.isOK():
|
|
if hasattr(res.getBody(), 'output'):
|
|
output_data = res.getBody().output
|
|
if not isinstance(output_data, list):
|
|
output_data = [output_data]
|
|
dataframe = pd.DataFrame(output_data)
|
|
else:
|
|
dataframe = pd.DataFrame()
|
|
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 예약주문접수[v1_해외주식-002]
|
|
##############################################################################################
|
|
|
|
def order_resv(
|
|
env_dv: str, # [필수] 실전모의구분 (ex. real:실전, demo:모의)
|
|
ord_dv: str, # [필수] 매도매수구분 (ex. usBuy:미국매수, usSell:미국매도, asia:아시아)
|
|
cano: str, # [필수] 종합계좌번호 (ex. 12345678)
|
|
acnt_prdt_cd: str, # [필수] 계좌상품코드 (ex. 01)
|
|
pdno: str, # [필수] 상품번호
|
|
ovrs_excg_cd: str,
|
|
# [필수] 해외거래소코드 (ex. NASD:나스닥, NYSE:뉴욕, AMEX:아멕스, SEHK:홍콩, SHAA:상해, SZAA:심천, TKSE:일본, HASE:하노이, VNSE:호치민)
|
|
ft_ord_qty: str, # [필수] FT주문수량
|
|
ft_ord_unpr3: str, # [필수] FT주문단가3
|
|
sll_buy_dvsn_cd: Optional[str] = "", # 매도매수구분코드 (ex. 아시아인경우만 사용, 01:매도,02:매수)
|
|
rvse_cncl_dvsn_cd: Optional[str] = "", # 정정취소구분코드 (ex. 아시아인경우만 사용, 00:매도/매수)
|
|
prdt_type_cd: Optional[str] = "", # 상품유형코드 (ex. 아시아인경우만 사용)
|
|
ord_svr_dvsn_cd: Optional[str] = "", # 주문서버구분코드 (ex. 0)
|
|
rsvn_ord_rcit_dt: Optional[str] = "", # 예약주문접수일자 (ex. 아시아인경우만 사용)
|
|
ord_dvsn: Optional[str] = "", # 주문구분 (ex. 미국 매수/매도인 경우만 사용)
|
|
ovrs_rsvn_odno: Optional[str] = "", # 해외예약주문번호 (ex. 아이사인 경우만 사용)
|
|
algo_ord_tmd_dvsn_cd: Optional[str] = "" # 알고리즘주문시간구분코드 (ex. TWAP, VWAP 주문에서만 사용, 02로 고정)
|
|
) -> pd.DataFrame:
|
|
"""
|
|
미국거래소 운영시간 외 미국주식을 예약 매매하기 위한 API입니다.
|
|
|
|
* 해외주식 서비스 신청 후 이용 가능합니다. (아래 링크 3번 해외증권 거래신청 참고)
|
|
https://securities.koreainvestment.com/main/bond/research/_static/TF03ca010001.jsp
|
|
|
|
※ POST API의 경우 BODY값의 key값들을 대문자로 작성하셔야 합니다.
|
|
(EX. "CANO" : "12345678", "ACNT_PRDT_CD": "01",...)
|
|
|
|
* 아래 각 국가의 시장별 예약주문 접수 가능 시간을 확인하시길 바랍니다.
|
|
|
|
미국 예약주문 접수시간
|
|
1) 10:00 ~ 23:20 / 10:00 ~ 22:20 (서머타임 시)
|
|
2) 주문제한 : 16:30 ~ 16:45 경까지 (사유 : 시스템 정산작업시간)
|
|
3) 23:30 정규장으로 주문 전송 (서머타임 시 22:30 정규장 주문 전송)
|
|
4) 미국 거래소 운영시간(한국시간 기준) : 23:30 ~ 06:00 (썸머타임 적용 시 22:30 ~ 05:00)
|
|
|
|
홍콩 예약주문 접수시간
|
|
1) 09:00 ~ 10:20 접수, 10:30 주문전송
|
|
2) 10:40 ~ 13:50 접수, 14:00 주문전송
|
|
|
|
중국 예약주문 접수시간
|
|
1) 09:00 ~ 10:20 접수, 10:30 주문전송
|
|
2) 10:40 ~ 13:50 접수, 14:00 주문전송
|
|
|
|
일본 예약주문 접수시간
|
|
1) 09:10 ~ 12:20 까지 접수, 12:30 주문전송
|
|
|
|
베트남 예약주문 접수시간
|
|
1) 09:00 ~ 11:00 까지 접수, 11:15 주문전송
|
|
2) 11:20 ~ 14:50 까지 접수, 15:00 주문전송
|
|
|
|
* 예약주문 유의사항
|
|
1) 예약주문 유효기간 : 당일
|
|
- 미국장 마감 후, 미체결주문은 자동취소
|
|
- 미국휴장 시, 익 영업일로 이전
|
|
(미국예약주문화면에서 취소 가능)
|
|
2) 증거금 및 잔고보유 : 체크 안함
|
|
3) 주문전송 불가사유
|
|
- 매수증거금 부족: 수수료 포함 매수금액부족, 환전, 시세이용료 출금, 인출에 의한 증거금 부족
|
|
- 기타 매수증거금 부족, 매도가능수량 부족, 주권변경 등 권리발생으로 인한 주문불가사유 발생
|
|
4) 지정가주문만 가능
|
|
* 단 미국 예약매도주문(TTTT3016U)의 경우, MOO(장개시시장가)로 주문 접수 가능
|
|
|
|
Args:
|
|
env_dv (str): [필수] 실전모의구분 (ex. real:실전, demo:모의)
|
|
ord_dv (str): [필수] 매도매수구분 (ex. usBuy:미국매수, usSell:미국매도, asia:아시아)
|
|
cano (str): [필수] 종합계좌번호 (ex. 12345678)
|
|
acnt_prdt_cd (str): [필수] 계좌상품코드 (ex. 01)
|
|
pdno (str): [필수] 상품번호
|
|
ovrs_excg_cd (str): [필수] 해외거래소코드 (ex. NASD:나스닥, NYSE:뉴욕, AMEX:아멕스, SEHK:홍콩, SHAA:상해, SZAA:심천, TKSE:일본, HASE:하노이, VNSE:호치민)
|
|
ft_ord_qty (str): [필수] FT주문수량
|
|
ft_ord_unpr3 (str): [필수] FT주문단가3
|
|
sll_buy_dvsn_cd (Optional[str]): 매도매수구분코드 (ex. 아시아인경우만 사용, 01:매도,02:매수)
|
|
rvse_cncl_dvsn_cd (Optional[str]): 정정취소구분코드 (ex. 아시아인경우만 사용, 00:매도/매수)
|
|
prdt_type_cd (Optional[str]): 상품유형코드 (ex. 아시아인경우만 사용)
|
|
ord_svr_dvsn_cd (Optional[str]): 주문서버구분코드 (ex. 0)
|
|
rsvn_ord_rcit_dt (Optional[str]): 예약주문접수일자 (ex. 아시아인경우만 사용)
|
|
ord_dvsn (Optional[str]): 주문구분 (ex. 미국 매수/매도인 경우만 사용)
|
|
ovrs_rsvn_odno (Optional[str]): 해외예약주문번호 (ex. 아이사인 경우만 사용)
|
|
algo_ord_tmd_dvsn_cd (Optional[str]): 알고리즘주문시간구분코드 (ex. TWAP, VWAP 주문에서만 사용, 02로 고정)
|
|
|
|
Returns:
|
|
pd.DataFrame: 해외주식 예약주문접수 결과 데이터
|
|
|
|
Example:
|
|
>>> df = order_resv(env_dv="real", ord_dv="usBuy", cano=trenv.my_acct, acnt_prdt_cd=trenv.my_prod, pdno="TSLA", ovrs_excg_cd="NASD", ft_ord_qty="1", ft_ord_unpr3="900")
|
|
>>> print(df)
|
|
"""
|
|
|
|
if env_dv == "":
|
|
raise ValueError("env_dv is required (e.g. 'real' or 'demo')")
|
|
|
|
if ord_dv == "":
|
|
raise ValueError("ord_dv is required (e.g. 'usBuy', 'usSell', 'asia')")
|
|
|
|
if cano == "":
|
|
raise ValueError("cano is required (e.g. '12345678')")
|
|
|
|
if acnt_prdt_cd == "":
|
|
raise ValueError("acnt_prdt_cd is required (e.g. '01')")
|
|
|
|
if pdno == "":
|
|
raise ValueError("pdno is required")
|
|
|
|
if ovrs_excg_cd == "":
|
|
raise ValueError(
|
|
"ovrs_excg_cd is required (e.g. 'NASD', 'NYSE', 'AMEX', 'SEHK', 'SHAA', 'SZAA', 'TKSE', 'HASE', 'VNSE')")
|
|
|
|
if ft_ord_qty == "":
|
|
raise ValueError("ft_ord_qty is required")
|
|
|
|
if ft_ord_unpr3 == "":
|
|
raise ValueError("ft_ord_unpr3 is required")
|
|
|
|
# tr_id 설정
|
|
if env_dv == "real":
|
|
if ord_dv == "usBuy":
|
|
tr_id = "TTTT3014U"
|
|
elif ord_dv == "usSell":
|
|
tr_id = "TTTT3016U"
|
|
elif ord_dv == "asia":
|
|
tr_id = "TTTS3013U"
|
|
else:
|
|
raise ValueError("ord_dv can only be 'usBuy', 'usSell' or 'asia'")
|
|
elif env_dv == "demo":
|
|
if ord_dv == "usBuy":
|
|
tr_id = "VTTT3014U"
|
|
elif ord_dv == "usSell":
|
|
tr_id = "VTTT3016U"
|
|
elif ord_dv == "asia":
|
|
tr_id = "VTTS3013U"
|
|
else:
|
|
raise ValueError("ord_dv can only be 'usBuy', 'usSell' or 'asia'")
|
|
else:
|
|
raise ValueError("env_dv is required (e.g. 'real' or 'demo')")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/order-resv"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"PDNO": pdno,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"FT_ORD_QTY": ft_ord_qty,
|
|
"FT_ORD_UNPR3": ft_ord_unpr3
|
|
}
|
|
|
|
# 옵션 파라미터 추가
|
|
if sll_buy_dvsn_cd:
|
|
params["SLL_BUY_DVSN_CD"] = sll_buy_dvsn_cd
|
|
if rvse_cncl_dvsn_cd:
|
|
params["RVSE_CNCL_DVSN_CD"] = rvse_cncl_dvsn_cd
|
|
if prdt_type_cd:
|
|
params["PRDT_TYPE_CD"] = prdt_type_cd
|
|
if ord_svr_dvsn_cd:
|
|
params["ORD_SVR_DVSN_CD"] = ord_svr_dvsn_cd
|
|
if rsvn_ord_rcit_dt:
|
|
params["RSVN_ORD_RCIT_DT"] = rsvn_ord_rcit_dt
|
|
if ord_dvsn:
|
|
params["ORD_DVSN"] = ord_dvsn
|
|
if ovrs_rsvn_odno:
|
|
params["OVRS_RSVN_ODNO"] = ovrs_rsvn_odno
|
|
if algo_ord_tmd_dvsn_cd:
|
|
params["ALGO_ORD_TMD_DVSN_CD"] = algo_ord_tmd_dvsn_cd
|
|
|
|
res = ka._url_fetch(api_url, tr_id, "", params, postFlag=True)
|
|
|
|
if res.isOK():
|
|
current_data = pd.DataFrame(res.getBody().output, index=[0])
|
|
logging.info("Data fetch complete.")
|
|
return current_data
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 예약주문접수취소[v1_해외주식-004]
|
|
##############################################################################################
|
|
|
|
def order_resv_ccnl(
|
|
env_dv: str, # [필수] 실전모의구분 (ex. real:실전, demo:모의)
|
|
nat_dv: str, # [필수] 국가구분 (ex. us:미국)
|
|
cano: str, # [필수] 종합계좌번호 (ex. 12345678)
|
|
acnt_prdt_cd: str, # [필수] 계좌상품코드 (ex. 01)
|
|
rsvn_ord_rcit_dt: str, # [필수] 해외주문접수일자
|
|
ovrs_rsvn_odno: str # [필수] 해외예약주문번호 (ex. 해외주식_예약주문접수 API Output ODNO(주문번호) 참고)
|
|
) -> pd.DataFrame:
|
|
"""
|
|
접수된 미국주식 예약주문을 취소하기 위한 API입니다.
|
|
(해외주식 예약주문접수 시 Return 받은 ODNO를 참고하여 API를 호출하세요.)
|
|
|
|
* 해외주식 서비스 신청 후 이용 가능합니다. (아래 링크 3번 해외증권 거래신청 참고)
|
|
https://securities.koreainvestment.com/main/bond/research/_static/TF03ca010001.jsp
|
|
|
|
※ POST API의 경우 BODY값의 key값들을 대문자로 작성하셔야 합니다.
|
|
(EX. "CANO" : "12345678", "ACNT_PRDT_CD": "01",...)
|
|
|
|
Args:
|
|
env_dv (str): [필수] 실전모의구분 (ex. real:실전, demo:모의)
|
|
nat_dv (str): [필수] 국가구분 (ex. us:미국)
|
|
cano (str): [필수] 종합계좌번호 (ex. 12345678)
|
|
acnt_prdt_cd (str): [필수] 계좌상품코드 (ex. 01)
|
|
rsvn_ord_rcit_dt (str): [필수] 해외주문접수일자
|
|
ovrs_rsvn_odno (str): [필수] 해외예약주문번호 (ex. 해외주식_예약주문접수 API Output ODNO(주문번호) 참고)
|
|
|
|
Returns:
|
|
pd.DataFrame: 해외주식 예약주문접수취소 결과 데이터
|
|
|
|
Example:
|
|
>>> df = order_resv_ccnl(env_dv="real", nat_dv="us", cano=trenv.my_acct, acnt_prdt_cd=trenv.my_prod, rsvn_ord_rcit_dt="20220810", ovrs_rsvn_odno="0030008244")
|
|
>>> print(df)
|
|
"""
|
|
|
|
if env_dv == "":
|
|
raise ValueError("env_dv is required (e.g. 'real' or 'demo')")
|
|
|
|
if nat_dv == "":
|
|
raise ValueError("nat_dv is required (e.g. 'us')")
|
|
|
|
if cano == "":
|
|
raise ValueError("cano is required (e.g. '12345678')")
|
|
|
|
if acnt_prdt_cd == "":
|
|
raise ValueError("acnt_prdt_cd is required (e.g. '01')")
|
|
|
|
if rsvn_ord_rcit_dt == "":
|
|
raise ValueError("rsvn_ord_rcit_dt is required")
|
|
|
|
if ovrs_rsvn_odno == "":
|
|
raise ValueError("ovrs_rsvn_odno is required")
|
|
|
|
# tr_id 설정
|
|
if env_dv == "real":
|
|
if nat_dv == "us":
|
|
tr_id = "TTTT3017U"
|
|
else:
|
|
raise ValueError("nat_dv can only be 'us'")
|
|
elif env_dv == "demo":
|
|
if nat_dv == "us":
|
|
tr_id = "VTTT3017U"
|
|
else:
|
|
raise ValueError("nat_dv can only be 'us'")
|
|
else:
|
|
raise ValueError("env_dv is required (e.g. 'real' or 'demo')")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/order-resv-ccnl"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"RSVN_ORD_RCIT_DT": rsvn_ord_rcit_dt,
|
|
"OVRS_RSVN_ODNO": ovrs_rsvn_odno
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, "", params, postFlag=True)
|
|
|
|
if res.isOK():
|
|
# output은 object 자료형이므로 DataFrame으로 변환
|
|
current_data = pd.DataFrame([res.getBody().output])
|
|
logging.info("Data fetch complete.")
|
|
return current_data
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 예약주문조회[v1_해외주식-013]
|
|
##############################################################################################
|
|
|
|
def order_resv_list(
|
|
nat_dv: str, # 국가구분코드
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
inqr_strt_dt: str, # 조회시작일자
|
|
inqr_end_dt: str, # 조회종료일자
|
|
inqr_dvsn_cd: str, # 조회구분코드
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
prdt_type_cd: str = "", # 상품유형코드
|
|
FK200: str = "", # 연속조회검색조건200
|
|
NK200: str = "", # 연속조회키200
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> pd.DataFrame:
|
|
"""
|
|
해외주식 예약주문 조회 API입니다.
|
|
※ 모의투자는 사용 불가합니다.
|
|
|
|
* 해외주식 서비스 신청 후 이용 가능합니다. (아래 링크 3번 해외증권 거래신청 참고)
|
|
https://securities.koreainvestment.com/main/bond/research/_static/TF03ca010001.jsp
|
|
|
|
Args:
|
|
nat_dv (str): [필수] 국가구분코드 (ex. us:미국, asia:아시아)
|
|
cano (str): [필수] 종합계좌번호 (ex. 12345678)
|
|
acnt_prdt_cd (str): [필수] 계좌상품코드 (ex. 01)
|
|
inqr_strt_dt (str): [필수] 조회시작일자 (ex. 20250101)
|
|
inqr_end_dt (str): [필수] 조회종료일자 (ex. 20251231)
|
|
inqr_dvsn_cd (str): [필수] 조회구분코드 (ex. 00:전체, 01:일반해외주식, 02:미니스탁)
|
|
ovrs_excg_cd (str): [필수] 해외거래소코드 (ex. NASD:나스닥, NYSE:뉴욕, AMEX:아멕스, SEHK:홍콩, SHAA:상해, SZAA:심천, TKSE:일본, HASE:하노이, VNSE:호치민)
|
|
prdt_type_cd (str): 상품유형코드
|
|
FK200 (str): 연속조회검색조건200
|
|
NK200 (str): 연속조회키200
|
|
tr_cont (str): 연속거래여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
pd.DataFrame: 해외주식 예약주문조회 데이터
|
|
|
|
Example:
|
|
>>> df = order_resv_list(nat_dv="us", cano=trenv.my_acct, acnt_prdt_cd=trenv.my_prod, inqr_strt_dt="20250101", inqr_end_dt="20251231", inqr_dvsn_cd="00", ovrs_excg_cd="NASD")
|
|
>>> print(df)
|
|
"""
|
|
|
|
if nat_dv == "":
|
|
raise ValueError("nat_dv is required (e.g. 'us' or 'asia')")
|
|
|
|
if cano == "":
|
|
raise ValueError("cano is required (e.g. '12345678')")
|
|
|
|
if acnt_prdt_cd == "":
|
|
raise ValueError("acnt_prdt_cd is required (e.g. '01')")
|
|
|
|
if inqr_strt_dt == "":
|
|
raise ValueError("inqr_strt_dt is required (e.g. '20250101')")
|
|
|
|
if inqr_end_dt == "":
|
|
raise ValueError("inqr_end_dt is required (e.g. '20251231')")
|
|
|
|
if inqr_dvsn_cd == "":
|
|
raise ValueError("inqr_dvsn_cd is required (e.g. '00')")
|
|
|
|
if ovrs_excg_cd == "":
|
|
raise ValueError("ovrs_excg_cd is required (e.g. 'NASD')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe is None:
|
|
return pd.DataFrame()
|
|
else:
|
|
return dataframe
|
|
|
|
# tr_id 설정
|
|
if nat_dv == "us":
|
|
tr_id = "TTTT3039R"
|
|
elif nat_dv == "asia":
|
|
tr_id = "TTTS3014R"
|
|
else:
|
|
raise ValueError("nat_dv can only be 'us' or 'asia'")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/order-resv-list"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"INQR_STRT_DT": inqr_strt_dt,
|
|
"INQR_END_DT": inqr_end_dt,
|
|
"INQR_DVSN_CD": inqr_dvsn_cd,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"PRDT_TYPE_CD": prdt_type_cd,
|
|
"CTX_AREA_FK200": FK200,
|
|
"CTX_AREA_NK200": NK200
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
current_data = pd.DataFrame(res.getBody().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
|
|
FK200 = res.getBody().ctx_area_fk200
|
|
NK200 = res.getBody().ctx_area_nk200
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return order_resv_list(
|
|
nat_dv, cano, acnt_prdt_cd, inqr_strt_dt, inqr_end_dt,
|
|
inqr_dvsn_cd, ovrs_excg_cd, prdt_type_cd, FK200, NK200,
|
|
"N", dataframe, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
################################################################################
|
|
# [해외주식] 주문/계좌 > 해외주식 정정취소주문[v1_해외주식-003]
|
|
################################################################################
|
|
|
|
def order_rvsecncl(
|
|
cano: str, # 종합계좌번호
|
|
acnt_prdt_cd: str, # 계좌상품코드
|
|
ovrs_excg_cd: str, # 해외거래소코드
|
|
pdno: str, # 상품번호
|
|
orgn_odno: str, # 원주문번호
|
|
rvse_cncl_dvsn_cd: str, # 정정취소구분코드
|
|
ord_qty: str, # 주문수량
|
|
ovrs_ord_unpr: str, # 해외주문단가
|
|
mgco_aptm_odno: str, # 운용사지정주문번호
|
|
ord_svr_dvsn_cd: str, # 주문서버구분코드
|
|
env_dv: str = "real", # 실전모의구분
|
|
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 주문/계좌
|
|
해외주식 정정취소주문[v1_해외주식-003]
|
|
해외주식 정정취소주문 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
cano (str): 계좌번호 체계(8-2)의 앞 8자리
|
|
acnt_prdt_cd (str): 계좌번호 체계(8-2)의 뒤 2자리
|
|
ovrs_excg_cd (str): NASD : 나스닥 NYSE : 뉴욕 AMEX : 아멕스 SEHK : 홍콩 SHAA : 중국상해 SZAA : 중국심천 TKSE : 일본 HASE : 베트남 하노이 VNSE : 베트남 호치민
|
|
pdno (str): 상품번호
|
|
orgn_odno (str): 정정 또는 취소할 원주문번호 (해외주식_주문 API ouput ODNO or 해외주식 미체결내역 API output ODNO 참고)
|
|
rvse_cncl_dvsn_cd (str): 01 : 정정 02 : 취소
|
|
ord_qty (str): 주문수량
|
|
ovrs_ord_unpr (str): 취소주문 시, "0" 입력
|
|
mgco_aptm_odno (str): 운용사지정주문번호
|
|
ord_svr_dvsn_cd (str): "0"(Default)
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 정정취소주문 데이터
|
|
|
|
Example:
|
|
>>> df = order_rvsecncl(
|
|
... cano=trenv.my_acct,
|
|
... acnt_prdt_cd=trenv.my_prod,
|
|
... ovrs_excg_cd="NYSE",
|
|
... pdno="BA",
|
|
... orgn_odno="30135009",
|
|
... rvse_cncl_dvsn_cd="01",
|
|
... ord_qty="1",
|
|
... ovrs_ord_unpr="226.00",
|
|
... mgco_aptm_odno="",
|
|
... ord_svr_dvsn_cd="0",
|
|
... env_dv="real"
|
|
... )
|
|
>>> print(df)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not cano:
|
|
logger.error("cano is required. (e.g. '810XXXXX')")
|
|
raise ValueError("cano is required. (e.g. '810XXXXX')")
|
|
if not acnt_prdt_cd:
|
|
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
|
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
|
if not ovrs_excg_cd:
|
|
logger.error("ovrs_excg_cd is required. (e.g. 'NYSE')")
|
|
raise ValueError("ovrs_excg_cd is required. (e.g. 'NYSE')")
|
|
if not pdno:
|
|
logger.error("pdno is required. (e.g. 'BA')")
|
|
raise ValueError("pdno is required. (e.g. 'BA')")
|
|
if not orgn_odno:
|
|
logger.error("orgn_odno is required. (e.g. '30135009')")
|
|
raise ValueError("orgn_odno is required. (e.g. '30135009')")
|
|
if not rvse_cncl_dvsn_cd:
|
|
logger.error("rvse_cncl_dvsn_cd is required. (e.g. '01')")
|
|
raise ValueError("rvse_cncl_dvsn_cd is required. (e.g. '01')")
|
|
if not ord_qty:
|
|
logger.error("ord_qty is required. (e.g. '1')")
|
|
raise ValueError("ord_qty is required. (e.g. '1')")
|
|
if not ovrs_ord_unpr:
|
|
logger.error("ovrs_ord_unpr is required. (e.g. '226.00')")
|
|
raise ValueError("ovrs_ord_unpr is required. (e.g. '226.00')")
|
|
|
|
# TR ID 설정 (모의투자 지원 로직)
|
|
if env_dv == "real":
|
|
tr_id = "TTTT1004U" # 실전투자용 TR ID
|
|
elif env_dv == "demo":
|
|
tr_id = "VTTT1004U" # 모의투자용 TR ID
|
|
else:
|
|
raise ValueError("env_dv can only be 'real' or 'demo'")
|
|
|
|
api_url = "/uapi/overseas-stock/v1/trading/order-rvsecncl"
|
|
|
|
params = {
|
|
"CANO": cano,
|
|
"ACNT_PRDT_CD": acnt_prdt_cd,
|
|
"OVRS_EXCG_CD": ovrs_excg_cd,
|
|
"PDNO": pdno,
|
|
"ORGN_ODNO": orgn_odno,
|
|
"RVSE_CNCL_DVSN_CD": rvse_cncl_dvsn_cd,
|
|
"ORD_QTY": ord_qty,
|
|
"OVRS_ORD_UNPR": ovrs_ord_unpr,
|
|
"MGCO_APTM_ODNO": mgco_aptm_odno,
|
|
"ORD_SVR_DVSN_CD": ord_svr_dvsn_cd,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url=api_url,
|
|
ptr_id=tr_id,
|
|
tr_cont="",
|
|
params=params,
|
|
postFlag=True)
|
|
|
|
if res.isOK():
|
|
if hasattr(res.getBody(), 'output'):
|
|
output_data = res.getBody().output
|
|
if not isinstance(output_data, list):
|
|
output_data = [output_data]
|
|
dataframe = pd.DataFrame(output_data)
|
|
else:
|
|
dataframe = pd.DataFrame()
|
|
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 가격급등락[해외주식-038]
|
|
##############################################################################################
|
|
|
|
def price_fluct(
|
|
excd: str, # [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
gubn: str, # [필수] 급등/급락구분 (ex. 0:급락, 1:급등)
|
|
mixn: str, # [필수] N분전콤보값 (ex. 0:1분전, 1:2분전, 2:3분전, 3:5분전, 4:10분전, 5:15분전, 6:20분전, 7:30분전, 8:60분전, 9:120분전)
|
|
vol_rang: str, # [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
auth: str = "", # 사용자권한정보
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 시세분석 > 해외주식 가격급등락[해외주식-038]
|
|
해외주식 가격급등락 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
gubn (str): [필수] 급등/급락구분 (ex. 0:급락, 1:급등)
|
|
mixn (str): [필수] N분전콤보값 (ex. 0:1분전, 1:2분전, 2:3분전, 3:5분전, 4:10분전, 5:15분전, 6:20분전, 7:30분전, 8:60분전, 9:120분전)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
keyb (str): NEXT KEY BUFF
|
|
auth (str): 사용자권한정보
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외주식 가격급등락 데이터 (output1, output2)
|
|
|
|
Example:
|
|
>>> df1, df2 = price_fluct(excd="NAS", gubn="0", mixn="0", vol_rang="0")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError("excd is required (e.g. 'NAS')")
|
|
|
|
if gubn == "":
|
|
raise ValueError("gubn is required (e.g. '0' or '1')")
|
|
|
|
if mixn == "":
|
|
raise ValueError("mixn is required (e.g. '0')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError("vol_rang is required (e.g. '0')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76260000" # 해외주식 가격급등락
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/price-fluct"
|
|
|
|
params = {
|
|
"EXCD": excd,
|
|
"GUBN": gubn,
|
|
"MIXN": mixn,
|
|
"VOL_RANG": vol_rang,
|
|
"KEYB": keyb,
|
|
"AUTH": auth
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
current_data1 = pd.DataFrame(res.getBody().output1, index=[0])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return price_fluct(
|
|
excd, gubn, mixn, vol_rang, keyb, auth, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 거래증가율순위[해외주식-045]
|
|
##############################################################################################
|
|
|
|
def trade_growth(
|
|
excd: str, # [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
nday: str, # [필수] N일자값 (ex. 0:당일, 1:2일, 2:3일, 3:5일, 4:10일, 5:20일전, 6:30일, 7:60일, 8:120일, 9:1년)
|
|
vol_rang: str, # [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
auth: str = "", # 사용자권한정보
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세 > 해외주식 거래증가율순위[해외주식-045]
|
|
해외주식 거래증가율순위 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
nday (str): [필수] N일자값 (ex. 0:당일, 1:2일, 2:3일, 3:5일, 4:10일, 5:20일전, 6:30일, 7:60일, 8:120일, 9:1년)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
auth (str): 사용자권한정보
|
|
keyb (str): NEXT KEY BUFF
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: (output1, output2) 데이터프레임 튜플
|
|
|
|
Example:
|
|
>>> df1, df2 = trade_growth(excd="NAS", nday="0", vol_rang="0")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError("excd is required (e.g. 'NYS')")
|
|
|
|
if nday == "":
|
|
raise ValueError("nday is required (e.g. '0')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError("vol_rang is required (e.g. '0')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76330000" # 해외주식 거래증가율순위
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/trade-growth"
|
|
|
|
params = {
|
|
"EXCD": excd,
|
|
"NDAY": nday,
|
|
"VOL_RANG": vol_rang,
|
|
"AUTH": auth,
|
|
"KEYB": keyb
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리 (object)
|
|
current_data1 = pd.DataFrame([res.getBody().output1])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리 (array)
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return trade_growth(
|
|
excd, nday, vol_rang, auth, keyb, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 거래대금순위[해외주식-044]
|
|
##############################################################################################
|
|
|
|
def trade_pbmn(
|
|
excd: str, # [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
nday: str, # [필수] N일자값 (ex. 0:당일, 1:2일, 2:3일, 3:5일, 4:10일, 5:20일전, 6:30일, 7:60일, 8:120일, 9:1년)
|
|
vol_rang: str, # [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
auth: str = "", # 사용자권한정보
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
prc1: str = "", # 현재가 필터범위 시작
|
|
prc2: str = "", # 현재가 필터범위 끝
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
해외주식 거래대금순위 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
nday (str): [필수] N일자값 (ex. 0:당일, 1:2일, 2:3일, 3:5일, 4:10일, 5:20일전, 6:30일, 7:60일, 8:120일, 9:1년)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
auth (str): 사용자권한정보
|
|
keyb (str): NEXT KEY BUFF
|
|
prc1 (str): 현재가 필터범위 시작
|
|
prc2 (str): 현재가 필터범위 끝
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 거래대금순위 데이터 (output1, output2)
|
|
|
|
Example:
|
|
>>> df1, df2 = trade_pbmn(excd="NAS", nday="0", vol_rang="0")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError(
|
|
"excd is required (e.g. 'NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄')")
|
|
|
|
if nday == "":
|
|
raise ValueError("nday is required (e.g. '0:당일, 1:2일, 2:3일, 3:5일, 4:10일, 5:20일전, 6:30일, 7:60일, 8:120일, 9:1년')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError(
|
|
"vol_rang is required (e.g. '0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None or dataframe2 is None:
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
else:
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76320010" # 해외주식 거래대금순위
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/trade-pbmn"
|
|
|
|
params = {
|
|
"EXCD": excd, # 거래소명
|
|
"NDAY": nday, # N일자값
|
|
"VOL_RANG": vol_rang, # 거래량조건
|
|
"AUTH": auth, # 사용자권한정보
|
|
"KEYB": keyb, # NEXT KEY BUFF
|
|
"PRC1": prc1, # 현재가 필터범위 시작
|
|
"PRC2": prc2, # 현재가 필터범위 끝
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리 (object 타입)
|
|
current_data1 = pd.DataFrame([res.getBody().output1])
|
|
|
|
# output2 처리 (array 타입)
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
keyb = res.getBody().keyb if hasattr(res.getBody(), 'keyb') else ""
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return trade_pbmn(
|
|
excd, nday, vol_rang, auth, keyb, prc1, prc2, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 거래회전율순위[해외주식-046]
|
|
##############################################################################################
|
|
|
|
def trade_turnover(
|
|
excd: str, # 거래소명
|
|
nday: str, # N분전콤보값
|
|
vol_rang: str, # 거래량조건
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
auth: str = "", # 사용자권한정보
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
depth: int = 0, # 내부 재귀 깊이 (자동 관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 시세분석 > 해외주식 거래회전율순위[해외주식-046]
|
|
해외주식 거래회전율순위 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
nday (str): [필수] N분전콤보값 (ex. 0:당일, 1:2일전, 2:3일전, 3:5일전, 4:10일전, 5:20일전, 6:30일전, 7:60일전, 8:120일전, 9:1년전)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
keyb (str): NEXT KEY BUFF
|
|
auth (str): 사용자권한정보
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: (output1, output2) 해외주식 거래회전율순위 데이터
|
|
|
|
Example:
|
|
>>> result1, result2 = trade_turnover(excd="SHS", nday="0", vol_rang="0")
|
|
>>> print(result1)
|
|
>>> print(result2)
|
|
"""
|
|
|
|
# 필수 파라미터 검증
|
|
if excd == "":
|
|
raise ValueError(
|
|
"excd is required (e.g. 'NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄')")
|
|
|
|
if nday == "":
|
|
raise ValueError(
|
|
"nday is required (e.g. '0:당일, 1:2일전, 2:3일전, 3:5일전, 4:10일전, 5:20일전, 6:30일전, 7:60일전, 8:120일전, 9:1년전')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError(
|
|
"vol_rang is required (e.g. '0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상')")
|
|
|
|
# 재귀 깊이 제한 확인
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76340000" # 해외주식 거래회전율순위
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/trade-turnover"
|
|
|
|
params = {
|
|
"EXCD": excd, # 거래소명
|
|
"NDAY": nday, # N분전콤보값
|
|
"VOL_RANG": vol_rang, # 거래량조건
|
|
"KEYB": keyb, # NEXT KEY BUFF
|
|
"AUTH": auth # 사용자권한정보
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
current_data1 = pd.DataFrame([res.getBody().output1])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return trade_turnover(
|
|
excd, nday, vol_rang, keyb, auth, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 거래량순위[해외주식-043]
|
|
##############################################################################################
|
|
|
|
def trade_vol(
|
|
excd: str, # 거래소명
|
|
nday: str, # N분전콤보값
|
|
vol_rang: str, # 거래량조건
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
auth: str = "", # 사용자권한정보
|
|
prc1: str = "", # 가격 필터 시작
|
|
prc2: str = "", # 가격 필터 종료
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 시세분석 > 해외주식 거래량순위[해외주식-043]
|
|
해외주식 거래량순위 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
nday (str): [필수] N분전콤보값 (ex. 0:당일, 1:2일전, 2:3일전, 3:5일전, 4:10일전, 5:20일전, 6:30일전, 7:60일전, 8:120일전, 9:1년전)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
keyb (str): NEXT KEY BUFF (ex. "")
|
|
auth (str): 사용자권한정보 (ex. "")
|
|
prc1 (str): 가격 필터 시작 (ex. "")
|
|
prc2 (str): 가격 필터 종료 (ex. "")
|
|
tr_cont (str): 연속거래여부 (ex. "")
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외주식 거래량순위 데이터 (output1, output2)
|
|
|
|
Example:
|
|
>>> df1, df2 = trade_vol(excd="NYS", nday="0", vol_rang="0")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
|
|
# 필수 파라미터 검증
|
|
if excd == "":
|
|
raise ValueError(
|
|
"excd is required (e.g. 'NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄')")
|
|
|
|
if nday == "":
|
|
raise ValueError(
|
|
"nday is required (e.g. '0:당일, 1:2일전, 2:3일전, 3:5일전, 4:10일전, 5:20일전, 6:30일전, 7:60일전, 8:120일전, 9:1년전')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError(
|
|
"vol_rang is required (e.g. '0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76310010" # 해외주식 거래량순위
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/trade-vol"
|
|
|
|
params = {
|
|
"EXCD": excd,
|
|
"NDAY": nday,
|
|
"VOL_RANG": vol_rang,
|
|
"KEYB": keyb,
|
|
"AUTH": auth,
|
|
"PRC1": prc1,
|
|
"PRC2": prc2
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
current_data1 = pd.DataFrame(res.getBody().output1, index=[0])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return trade_vol(
|
|
excd, nday, vol_rang, keyb, auth, prc1, prc2, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 상승률/하락률[해외주식-041]
|
|
##############################################################################################
|
|
|
|
def updown_rate(
|
|
excd: str, # [필수] 거래소명
|
|
nday: str, # [필수] N일자값
|
|
gubn: str, # [필수] 상승율/하락율 구분
|
|
vol_rang: str, # [필수] 거래량조건
|
|
auth: str = "", # 사용자권한정보
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
해외주식 상승률/하락률 순위를 조회합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
nday (str): [필수] N일자값 (ex. 0:당일, 1:2일, 2:3일, 3:5일, 4:10일, 5:20일전, 6:30일, 7:60일, 8:120일, 9:1년)
|
|
gubn (str): [필수] 상승율/하락율 구분 (ex. 0:하락율, 1:상승율)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
auth (str): 사용자권한정보
|
|
keyb (str): NEXT KEY BUFF
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 상승률/하락률 순위 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = updown_rate(excd="NYS", nday="0", gubn="1", vol_rang="0")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
|
|
# 필수 파라미터 검증
|
|
if excd == "":
|
|
raise ValueError(
|
|
"excd is required (e.g. 'NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄')")
|
|
|
|
if nday == "":
|
|
raise ValueError("nday is required (e.g. '0:당일, 1:2일, 2:3일, 3:5일, 4:10일, 5:20일전, 6:30일, 7:60일, 8:120일, 9:1년')")
|
|
|
|
if gubn == "":
|
|
raise ValueError("gubn is required (e.g. '0:하락율, 1:상승율')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError(
|
|
"vol_rang is required (e.g. '0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None and dataframe2 is None:
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
else:
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
|
|
tr_id = "HHDFS76290000"
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/updown-rate"
|
|
|
|
params = {
|
|
"EXCD": excd,
|
|
"NDAY": nday,
|
|
"GUBN": gubn,
|
|
"VOL_RANG": vol_rang,
|
|
"AUTH": auth,
|
|
"KEYB": keyb
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
current_data1 = pd.DataFrame([res.getBody().output1])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return updown_rate(
|
|
excd, nday, gubn, vol_rang, auth, keyb, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 매수체결강도상위[해외주식-040]
|
|
##############################################################################################
|
|
|
|
def volume_power(
|
|
excd: str, # [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
nday: str, # [필수] N일자값 (ex. 0:당일, 1:2일, 2:3일, 3:5일, 4:10일, 5:20일전, 6:30일, 7:60일, 8:120일, 9:1년)
|
|
vol_rang: str, # [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
auth: str = "", # 사용자권한정보
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # output1 누적 데이터프레임
|
|
dataframe2: Optional[pd.DataFrame] = None, # output2 누적 데이터프레임
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 시세분석 > 해외주식 매수체결강도상위[해외주식-040]
|
|
|
|
해외주식 매수 체결강도 상위 종목을 조회합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
nday (str): [필수] N일자값 (ex. 0:당일, 1:2일, 2:3일, 3:5일, 4:10일, 5:20일전, 6:30일, 7:60일, 8:120일, 9:1년)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
auth (str): 사용자권한정보
|
|
keyb (str): NEXT KEY BUFF
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): output1 누적 데이터프레임
|
|
dataframe2 (Optional[pd.DataFrame]): output2 누적 데이터프레임
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: (output1 데이터, output2 데이터)
|
|
|
|
Example:
|
|
>>> df1, df2 = volume_power(excd="HKS", nday="0", vol_rang="0")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError("excd is required (e.g. 'HKS')")
|
|
|
|
if nday == "":
|
|
raise ValueError("nday is required (e.g. '0')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError("vol_rang is required (e.g. '0')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76280000"
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/volume-power"
|
|
|
|
params = {
|
|
"EXCD": excd,
|
|
"NDAY": nday,
|
|
"VOL_RANG": vol_rang,
|
|
"AUTH": auth,
|
|
"KEYB": keyb
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리 (object)
|
|
current_data1 = pd.DataFrame([res.getBody().output1])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리 (array)
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return volume_power(
|
|
excd, nday, vol_rang, auth, keyb, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 거래량급증[해외주식-039]
|
|
##############################################################################################
|
|
|
|
def volume_surge(
|
|
excd: str, # [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
mixn: str, # [필수] N분전콤보값 (ex. 0:1분전, 1:2분전, 2:3분전, 3:5분전, 4:10분전, 5:15분전, 6:20분전, 7:30분전, 8:60분전, 9:120분전)
|
|
vol_rang: str, # [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
auth: str = "", # 사용자권한정보
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 시세분석 > 해외주식 거래량급증[해외주식-039]
|
|
해외주식 거래량급증 정보를 조회합니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
mixn (str): [필수] N분전콤보값 (ex. 0:1분전, 1:2분전, 2:3분전, 3:5분전, 4:10분전, 5:15분전, 6:20분전, 7:30분전, 8:60분전, 9:120분전)
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
keyb (str): NEXT KEY BUFF
|
|
auth (str): 사용자권한정보
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: (output1 데이터, output2 데이터)
|
|
|
|
Example:
|
|
>>> df1, df2 = volume_surge(excd="NYS", mixn="0", vol_rang="0")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError("excd is required (e.g. 'NYS')")
|
|
|
|
if mixn == "":
|
|
raise ValueError("mixn is required (e.g. '0')")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError("vol_rang is required (e.g. '0')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76270000" # 해외주식 거래량급증
|
|
|
|
api_url = "/uapi/overseas-stock/v1/ranking/volume-surge"
|
|
|
|
params = {
|
|
"EXCD": excd, # 거래소명
|
|
"MIXN": mixn, # N분전콤보값
|
|
"VOL_RANG": vol_rang, # 거래량조건
|
|
"KEYB": keyb, # NEXT KEY BUFF
|
|
"AUTH": auth # 사용자권한정보
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
current_data1 = pd.DataFrame(res.getBody().output1, index=[0])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return volume_surge(
|
|
excd, mixn, vol_rang, keyb, auth, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외속보(제목) [해외주식-055]
|
|
##############################################################################################
|
|
|
|
def brknews_title(
|
|
fid_news_ofer_entp_code: str, # [필수] 뉴스제공업체코드 (ex. 0:전체조회)
|
|
fid_cond_scr_div_code: str, # [필수] 조건화면분류코드 (ex. 11801)
|
|
fid_cond_mrkt_cls_code: str = "", # 조건시장구분코드
|
|
fid_input_iscd: str = "", # 입력종목코드
|
|
fid_titl_cntt: str = "", # 제목내용
|
|
fid_input_date_1: str = "", # 입력날짜1
|
|
fid_input_hour_1: str = "", # 입력시간1
|
|
fid_rank_sort_cls_code: str = "", # 순위정렬구분코드
|
|
fid_input_srno: str = "" # 입력일련번호
|
|
) -> pd.DataFrame:
|
|
"""
|
|
해외속보(제목) API입니다.
|
|
한국투자 HTS(eFriend Plus) > [7704] 해외속보 화면 의 기능을 API로 개발한 사항으로, 해당 화면을 참고하시면 기능을 이해하기 쉽습니다.
|
|
|
|
최대 100건까지 조회 가능합니다.
|
|
|
|
Args:
|
|
fid_news_ofer_entp_code (str): [필수] 뉴스제공업체코드 (ex. 0:전체조회)
|
|
fid_cond_scr_div_code (str): [필수] 조건화면분류코드 (ex. 11801)
|
|
fid_cond_mrkt_cls_code (str): 조건시장구분코드
|
|
fid_input_iscd (str): 입력종목코드
|
|
fid_titl_cntt (str): 제목내용
|
|
fid_input_date_1 (str): 입력날짜1
|
|
fid_input_hour_1 (str): 입력시간1
|
|
fid_rank_sort_cls_code (str): 순위정렬구분코드
|
|
fid_input_srno (str): 입력일련번호
|
|
|
|
Returns:
|
|
pd.DataFrame: 해외속보(제목) 데이터
|
|
|
|
Example:
|
|
>>> df = brknews_title("0", "11801")
|
|
>>> print(df)
|
|
"""
|
|
|
|
if fid_news_ofer_entp_code == "":
|
|
raise ValueError("fid_news_ofer_entp_code is required (e.g. '0')")
|
|
|
|
if fid_cond_scr_div_code == "":
|
|
raise ValueError("fid_cond_scr_div_code is required (e.g. '11801')")
|
|
|
|
tr_id = "FHKST01011801"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/brknews-title"
|
|
|
|
params = {
|
|
"FID_NEWS_OFER_ENTP_CODE": fid_news_ofer_entp_code,
|
|
"FID_COND_SCR_DIV_CODE": fid_cond_scr_div_code,
|
|
"FID_COND_MRKT_CLS_CODE": fid_cond_mrkt_cls_code,
|
|
"FID_INPUT_ISCD": fid_input_iscd,
|
|
"FID_TITL_CNTT": fid_titl_cntt,
|
|
"FID_INPUT_DATE_1": fid_input_date_1,
|
|
"FID_INPUT_HOUR_1": fid_input_hour_1,
|
|
"FID_RANK_SORT_CLS_CODE": fid_rank_sort_cls_code,
|
|
"FID_INPUT_SRNO": fid_input_srno
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, "", params)
|
|
|
|
if res.isOK():
|
|
return pd.DataFrame(res.getBody().output)
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 당사 해외주식담보대출 가능 종목 [해외주식-051]
|
|
##############################################################################################
|
|
|
|
def colable_by_company(
|
|
pdno: str, # 상품번호
|
|
natn_cd: str, # 국가코드
|
|
inqr_sqn_dvsn: str, # 조회순서구분
|
|
prdt_type_cd: str = "", # 상품유형코드
|
|
inqr_strt_dt: str = "", # 조회시작일자
|
|
inqr_end_dt: str = "", # 조회종료일자
|
|
inqr_dvsn: str = "", # 조회구분
|
|
rt_dvsn_cd: str = "", # 비율구분코드
|
|
rt: str = "", # 비율
|
|
loan_psbl_yn: str = "", # 대출가능여부
|
|
FK100: str = "", # 연속조회검색조건100
|
|
NK100: str = "", # 연속조회키100
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
당사 해외주식담보대출 가능 종목 API입니다.
|
|
한국투자 HTS(eFriend Plus) > [0497] 당사 해외주식담보대출 가능 종목 화면 의 기능을 API로 개발한 사항으로, 해당 화면을 참고하시면 기능을 이해하기 쉽습니다.
|
|
|
|
한 번의 호출에 20건까지 조회가 가능하며 다음조회가 불가하기에, PDNO에 데이터 확인하고자 하는 종목코드를 입력하여 단건조회용으로 사용하시기 바랍니다.
|
|
|
|
Args:
|
|
pdno (str): [필수] 상품번호 (ex. AMD)
|
|
natn_cd (str): [필수] 국가코드 (ex. 840:미국,344:홍콩,156:중국)
|
|
inqr_sqn_dvsn (str): [필수] 조회순서구분 (ex. 01:이름순,02:코드순)
|
|
prdt_type_cd (str): 상품유형코드
|
|
inqr_strt_dt (str): 조회시작일자
|
|
inqr_end_dt (str): 조회종료일자
|
|
inqr_dvsn (str): 조회구분
|
|
rt_dvsn_cd (str): 비율구분코드
|
|
rt (str): 비율
|
|
loan_psbl_yn (str): 대출가능여부
|
|
FK100 (str): 연속조회검색조건100
|
|
NK100 (str): 연속조회키100
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: (output1 데이터, output2 데이터)
|
|
|
|
Example:
|
|
>>> df1, df2 = colable_by_company(pdno="AMD", natn_cd="840", inqr_sqn_dvsn="01")
|
|
>>> print(df1) # output1 데이터
|
|
>>> print(df2) # output2 데이터
|
|
"""
|
|
|
|
# 필수 파라미터 검증
|
|
if pdno == "":
|
|
raise ValueError("pdno is required (e.g. 'AMD')")
|
|
|
|
if natn_cd == "":
|
|
raise ValueError("natn_cd is required (e.g. '840:미국,344:홍콩,156:중국')")
|
|
|
|
if inqr_sqn_dvsn == "":
|
|
raise ValueError("inqr_sqn_dvsn is required (e.g. '01:이름순,02:코드순')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "CTLN4050R"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/colable-by-company"
|
|
|
|
params = {
|
|
"PDNO": pdno,
|
|
"NATN_CD": natn_cd,
|
|
"INQR_SQN_DVSN": inqr_sqn_dvsn,
|
|
"PRDT_TYPE_CD": prdt_type_cd,
|
|
"INQR_STRT_DT": inqr_strt_dt,
|
|
"INQR_END_DT": inqr_end_dt,
|
|
"INQR_DVSN": inqr_dvsn,
|
|
"RT_DVSN_CD": rt_dvsn_cd,
|
|
"RT": rt,
|
|
"LOAN_PSBL_YN": loan_psbl_yn,
|
|
"CTX_AREA_FK100": FK100,
|
|
"CTX_AREA_NK100": NK100
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리 (array)
|
|
current_data1 = pd.DataFrame(res.getBody().output1)
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리 (object)
|
|
current_data2 = pd.DataFrame([res.getBody().output2])
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
FK100 = res.getBody().ctx_area_fk100
|
|
NK100 = res.getBody().ctx_area_nk100
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return colable_by_company(
|
|
pdno, natn_cd, inqr_sqn_dvsn, prdt_type_cd, inqr_strt_dt, inqr_end_dt,
|
|
inqr_dvsn, rt_dvsn_cd, rt, loan_psbl_yn, FK100, NK100, "N",
|
|
dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외결제일자조회[해외주식-017]
|
|
##############################################################################################
|
|
|
|
def countries_holiday(
|
|
trad_dt: str, # 기준일자
|
|
NK: str = "", # 연속조회키
|
|
FK: str = "", # 연속조회검색조건
|
|
tr_cont: str = "", # 연속 거래 여부
|
|
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
|
depth: int = 0, # 현재 재귀 깊이
|
|
max_depth: int = 10 # 최대 재귀 깊이
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외결제일자조회[해외주식-017]
|
|
해외결제일자조회 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
trad_dt (str): 기준일자(YYYYMMDD)
|
|
ctx_area_nk (str): 공백으로 입력
|
|
ctx_area_fk (str): 공백으로 입력
|
|
tr_cont (str): 연속 거래 여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외결제일자조회 데이터
|
|
|
|
Example:
|
|
>>> df = countries_holiday("20250131", "", "")
|
|
>>> print(df)
|
|
"""
|
|
# 필수 파라미터 검증
|
|
if not trad_dt:
|
|
logger.error("trad_dt is required. (e.g. '20250131')")
|
|
raise ValueError("trad_dt is required. (e.g. '20250131')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
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()
|
|
|
|
tr_id = "CTOS5011R"
|
|
|
|
api_url = "/uapi/overseas-stock/v1/quotations/countries-holiday"
|
|
|
|
params = {
|
|
"TRAD_DT": trad_dt,
|
|
"CTX_AREA_NK": NK,
|
|
"CTX_AREA_FK": FK,
|
|
}
|
|
|
|
# API 호출
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
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)
|
|
else:
|
|
current_data = pd.DataFrame()
|
|
|
|
# 데이터프레임 병합
|
|
if dataframe is not None:
|
|
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
|
else:
|
|
dataframe = current_data
|
|
|
|
# 연속 거래 여부 확인
|
|
tr_cont = res.getHeader().tr_cont
|
|
NK = res.getBody().ctx_area_nk
|
|
FK = res.getBody().ctx_area_fk
|
|
|
|
if tr_cont == "M":
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return countries_holiday(
|
|
trad_dt,
|
|
NK,
|
|
FK,
|
|
"N", dataframe, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외주식 기간별시세[v1_해외주식-010]
|
|
##############################################################################################
|
|
|
|
def dailyprice(
|
|
auth: str, # 사용자권한정보
|
|
excd: str, # 거래소코드
|
|
symb: str, # 종목코드
|
|
gubn: str, # 일/주/월구분
|
|
bymd: str, # 조회기준일자
|
|
modp: str, # 수정주가반영여부
|
|
env_dv: str = "real", # 실전모의구분
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외주식 기간별시세[v1_해외주식-010]
|
|
해외주식 기간별시세 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
auth (str): 사용자권한정보 (예: "")
|
|
excd (str): 거래소코드 (예: "NAS")
|
|
symb (str): 종목코드 (예: "TSLA")
|
|
gubn (str): 일/주/월구분 (예: "0")
|
|
bymd (str): 조회기준일자(YYYYMMDD) (예: "20230101")
|
|
modp (str): 수정주가반영여부 (예: "0")
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외주식 기간별시세 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = dailyprice("auth_token", "NAS", "TSLA", "0", "20230101", "0", "")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
# 로깅 설정
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 필수 파라미터 검증
|
|
if not excd:
|
|
logger.error("excd is required. (e.g. 'NAS')")
|
|
raise ValueError("excd is required. (e.g. 'NAS')")
|
|
if not symb:
|
|
logger.error("symb is required. (e.g. 'TSLA')")
|
|
raise ValueError("symb is required. (e.g. 'TSLA')")
|
|
if not gubn:
|
|
logger.error("gubn is required. (e.g. '0')")
|
|
raise ValueError("gubn is required. (e.g. '0')")
|
|
if not modp:
|
|
logger.error("modp is required. (e.g. '0')")
|
|
raise ValueError("modp is required. (e.g. '0')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
|
|
# TR ID 설정 (모의투자 지원 로직)
|
|
if env_dv == "real" or env_dv == "demo":
|
|
tr_id = "HHDFS76240000" # 실전/모의투자 공통 TR ID
|
|
else:
|
|
logger.error("env_dv can only be 'real' or 'demo'")
|
|
raise ValueError("env_dv can only be 'real' or 'demo'")
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/dailyprice"
|
|
|
|
params = {
|
|
"AUTH": auth,
|
|
"EXCD": excd,
|
|
"SYMB": symb,
|
|
"GUBN": gubn,
|
|
"BYMD": bymd,
|
|
"MODP": modp,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
# output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return dailyprice(
|
|
auth,
|
|
excd,
|
|
symb,
|
|
gubn,
|
|
bymd,
|
|
modp,
|
|
env_dv,
|
|
dataframe1,
|
|
dataframe2,
|
|
"N",
|
|
depth + 1,
|
|
max_depth
|
|
)
|
|
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외주식 업종별코드조회[해외주식-049]
|
|
##############################################################################################
|
|
|
|
def industry_price(
|
|
excd: str, # [필수] 거래소명
|
|
auth: str = "", # 사용자권한정보
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
해외주식 업종별코드조회 API입니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
auth (str): 사용자권한정보
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: (output1, output2) 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = industry_price(excd="NAS")
|
|
>>> print(df1, df2)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError("excd is required (e.g. 'NYS', 'NAS', 'AMS', 'HKS', 'SHS', 'SZS', 'HSX', 'HNX', 'TSE')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76370100"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/industry-price"
|
|
|
|
params = {
|
|
"EXCD": excd,
|
|
"AUTH": auth
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리 (object 타입)
|
|
current_data1 = pd.DataFrame([res.getBody().output1])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 처리 (array 타입)
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return industry_price(
|
|
excd, auth, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외주식 업종별시세[해외주식-048]
|
|
##############################################################################################
|
|
|
|
def industry_theme(
|
|
excd: str, # [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
icod: str, # [필수] 업종코드
|
|
vol_rang: str, # [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
auth: str = "", # 사용자권한정보
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임1
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임2
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
해외주식 업종별시세 API입니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
icod (str): [필수] 업종코드
|
|
vol_rang (str): [필수] 거래량조건 (ex. 0:전체, 1:1백주이상, 2:1천주이상, 3:1만주이상, 4:10만주이상, 5:100만주이상, 6:1000만주이상)
|
|
auth (str): 사용자권한정보
|
|
keyb (str): NEXT KEY BUFF
|
|
tr_cont (str): 연속거래여부
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임1
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임2
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: (output1 데이터, output2 데이터)
|
|
|
|
Example:
|
|
>>> df1, df2 = industry_theme(excd="NAS", icod="010", vol_rang="0")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError("excd is required (e.g. 'NAS')")
|
|
|
|
if icod == "":
|
|
raise ValueError("icod is required")
|
|
|
|
if vol_rang == "":
|
|
raise ValueError("vol_rang is required (e.g. '0')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
return dataframe1, dataframe2
|
|
|
|
tr_id = "HHDFS76370000"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/industry-theme"
|
|
|
|
params = {
|
|
"EXCD": excd,
|
|
"ICOD": icod,
|
|
"VOL_RANG": vol_rang,
|
|
"AUTH": auth,
|
|
"KEYB": keyb
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 데이터 처리
|
|
current_data1 = pd.DataFrame(res.getBody().output1, index=[0])
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
|
|
# output2 데이터 처리
|
|
current_data2 = pd.DataFrame(res.getBody().output2)
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return industry_theme(
|
|
excd, icod, vol_rang, auth, keyb, "N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외주식 현재가 1호가[해외주식-033]
|
|
##############################################################################################
|
|
|
|
def inquire_asking_price(
|
|
auth: str, # 사용자권한정보
|
|
excd: str, # 거래소코드
|
|
symb: str, # 종목코드
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
dataframe3: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output3)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외주식 현재가 1호가[해외주식-033]
|
|
해외주식 현재가 1호가 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
auth (str): 사용자권한정보
|
|
excd (str): 거래소코드 (예: NYS, NAS, AMS, 등)
|
|
symb (str): 종목코드 (예: TSLA)
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
dataframe3 (Optional[pd.DataFrame]): 누적 데이터프레임 (output3)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: 해외주식 현재가 1호가 데이터
|
|
|
|
Example:
|
|
>>> df1, df2, df3 = inquire_asking_price(auth="your_auth_token", excd="NAS", symb="TSLA")
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
>>> print(df3)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not excd:
|
|
logger.error("excd is required. (e.g. 'NAS')")
|
|
raise ValueError("excd is required. (e.g. 'NAS')")
|
|
if not symb:
|
|
logger.error("symb is required. (e.g. 'TSLA')")
|
|
raise ValueError("symb is required. (e.g. 'TSLA')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame(), dataframe3 if dataframe3 is not None else pd.DataFrame()
|
|
|
|
tr_id = "HHDFS76200100"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/inquire-asking-price"
|
|
|
|
params = {
|
|
"AUTH": auth,
|
|
"EXCD": excd,
|
|
"SYMB": symb,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
|
|
# output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
|
|
# output3 처리
|
|
if hasattr(res.getBody(), 'output3'):
|
|
output_data = res.getBody().output3
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data3 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data3 = pd.DataFrame([output_data])
|
|
|
|
if dataframe3 is not None:
|
|
dataframe3 = pd.concat([dataframe3, current_data3], ignore_index=True)
|
|
else:
|
|
dataframe3 = current_data3
|
|
else:
|
|
if dataframe3 is None:
|
|
dataframe3 = pd.DataFrame()
|
|
else:
|
|
if dataframe3 is None:
|
|
dataframe3 = pd.DataFrame()
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_asking_price(
|
|
auth,
|
|
excd,
|
|
symb,
|
|
"N", dataframe1, dataframe2, dataframe3, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2, dataframe3
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외주식 체결추이[해외주식-037]
|
|
##############################################################################################
|
|
|
|
def quot_inquire_ccnl(
|
|
excd: str, # [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
tday: str, # [필수] 당일전일구분 (ex. 0:전일, 1:당일)
|
|
symb: str, # [필수] 종목코드 (ex. 해외종목코드)
|
|
auth: str = "", # 사용자권한정보
|
|
keyb: str = "", # NEXT KEY BUFF
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> pd.DataFrame:
|
|
"""
|
|
해외주식 체결추이 API입니다.
|
|
|
|
Args:
|
|
excd (str): [필수] 거래소명 (ex. NYS:뉴욕, NAS:나스닥, AMS:아멕스, HKS:홍콩, SHS:상해, SZS:심천, HSX:호치민, HNX:하노이, TSE:도쿄)
|
|
tday (str): [필수] 당일전일구분 (ex. 0:전일, 1:당일)
|
|
symb (str): [필수] 종목코드 (ex. 해외종목코드)
|
|
auth (str): 사용자권한정보
|
|
keyb (str): NEXT KEY BUFF
|
|
tr_cont (str): 연속거래여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
pd.DataFrame: 해외주식 체결추이 데이터
|
|
|
|
Example:
|
|
>>> df = quot_inquire_ccnl(excd="NAS", tday="0", symb="TSLA")
|
|
>>> print(df)
|
|
"""
|
|
|
|
if excd == "":
|
|
raise ValueError("excd is required (e.g. 'NAS')")
|
|
|
|
if tday == "":
|
|
raise ValueError("tday is required (e.g. '0' or '1')")
|
|
|
|
if symb == "":
|
|
raise ValueError("symb is required (e.g. 'TSLA')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe is None:
|
|
return pd.DataFrame()
|
|
else:
|
|
return dataframe
|
|
|
|
tr_id = "HHDFS76200300"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/inquire-ccnl"
|
|
|
|
params = {
|
|
"EXCD": excd,
|
|
"TDAY": tday,
|
|
"SYMB": symb,
|
|
"AUTH": auth,
|
|
"KEYB": keyb
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
current_data = pd.DataFrame(res.getBody().output1)
|
|
|
|
if dataframe is not None:
|
|
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
|
else:
|
|
dataframe = current_data
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
keyb = res.getBody().keyb if hasattr(res.getBody(), 'keyb') else ""
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return quot_inquire_ccnl(
|
|
excd, tday, symb, auth, keyb, "N", dataframe, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외주식 종목_지수_환율기간별시세(일_주_월_년)[v1_해외주식-012]
|
|
##############################################################################################
|
|
|
|
def inquire_daily_chartprice(
|
|
fid_cond_mrkt_div_code: str, # FID 조건 시장 분류 코드
|
|
fid_input_iscd: str, # FID 입력 종목코드
|
|
fid_input_date_1: str, # FID 입력 날짜1
|
|
fid_input_date_2: str, # FID 입력 날짜2
|
|
fid_period_div_code: str, # FID 기간 분류 코드
|
|
env_dv: str = "real", # 실전모의구분
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외주식 종목_지수_환율기간별시세(일_주_월_년)[v1_해외주식-012]
|
|
해외주식 종목_지수_환율기간별시세(일_주_월_년) API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
fid_cond_mrkt_div_code (str): N: 해외지수, X 환율, I: 국채, S:금선물
|
|
fid_input_iscd (str): 종목코드 ※ 해외주식 마스터 코드 참조 (포럼 > FAQ > 종목정보 다운로드(해외) > 해외지수) ※ 해당 API로 미국주식 조회 시, 다우30, 나스닥100, S&P500 종목만 조회 가능합니다. 더 많은 미국주식 종목 시세를 이용할 시에는, 해외주식기간별시세 API 사용 부탁드립니다.
|
|
fid_input_date_1 (str): 시작일자(YYYYMMDD)
|
|
fid_input_date_2 (str): 종료일자(YYYYMMDD)
|
|
fid_period_div_code (str): D:일, W:주, M:월, Y:년
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외주식 종목_지수_환율기간별시세(일_주_월_년) 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = inquire_daily_chartprice(
|
|
... fid_cond_mrkt_div_code="N",
|
|
... fid_input_iscd=".DJI",
|
|
... fid_input_date_1="20220401",
|
|
... fid_input_date_2="20220613",
|
|
... fid_period_div_code="D",
|
|
... env_dv="real"
|
|
... )
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not fid_cond_mrkt_div_code:
|
|
logger.error("fid_cond_mrkt_div_code is required. (e.g. 'N')")
|
|
raise ValueError("fid_cond_mrkt_div_code is required. (e.g. 'N')")
|
|
if not fid_input_iscd:
|
|
logger.error("fid_input_iscd is required. (e.g. '.DJI')")
|
|
raise ValueError("fid_input_iscd is required. (e.g. '.DJI')")
|
|
if not fid_input_date_1:
|
|
logger.error("fid_input_date_1 is required. (e.g. '20220401')")
|
|
raise ValueError("fid_input_date_1 is required. (e.g. '20220401')")
|
|
if not fid_input_date_2:
|
|
logger.error("fid_input_date_2 is required. (e.g. '20220613')")
|
|
raise ValueError("fid_input_date_2 is required. (e.g. '20220613')")
|
|
if not fid_period_div_code:
|
|
logger.error("fid_period_div_code is required. (e.g. 'D')")
|
|
raise ValueError("fid_period_div_code is required. (e.g. 'D')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
|
|
# TR ID 설정 (모의투자 지원 로직)
|
|
if env_dv == "real" or env_dv == "demo":
|
|
tr_id = "FHKST03030100" # 실전투자용 TR ID
|
|
else:
|
|
raise ValueError("env_dv can only be 'real' or 'demo'")
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/inquire-daily-chartprice"
|
|
|
|
params = {
|
|
"FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code,
|
|
"FID_INPUT_ISCD": fid_input_iscd,
|
|
"FID_INPUT_DATE_1": fid_input_date_1,
|
|
"FID_INPUT_DATE_2": fid_input_date_2,
|
|
"FID_PERIOD_DIV_CODE": fid_period_div_code,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
# output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_daily_chartprice(
|
|
fid_cond_mrkt_div_code,
|
|
fid_input_iscd,
|
|
fid_input_date_1,
|
|
fid_input_date_2,
|
|
fid_period_div_code,
|
|
env_dv,
|
|
"N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식조건검색[v1_해외주식-015]
|
|
##############################################################################################
|
|
|
|
def inquire_search(
|
|
auth: str, # 사용자권한정보
|
|
excd: str, # 거래소코드
|
|
co_yn_pricecur: str, # 현재가선택조건
|
|
co_st_pricecur: str, # 현재가시작범위가
|
|
co_en_pricecur: str, # 현재가끝범위가
|
|
co_yn_rate: str, # 등락율선택조건
|
|
co_st_rate: str, # 등락율시작율
|
|
co_en_rate: str, # 등락율끝율
|
|
co_yn_valx: str, # 시가총액선택조건
|
|
co_st_valx: str, # 시가총액시작액
|
|
co_en_valx: str, # 시가총액끝액
|
|
co_yn_shar: str, # 발행주식수선택조건
|
|
co_st_shar: str, # 발행주식시작수
|
|
co_en_shar: str, # 발행주식끝수
|
|
co_yn_volume: str, # 거래량선택조건
|
|
co_st_volume: str, # 거래량시작량
|
|
co_en_volume: str, # 거래량끝량
|
|
co_yn_amt: str, # 거래대금선택조건
|
|
co_st_amt: str, # 거래대금시작금
|
|
co_en_amt: str, # 거래대금끝금
|
|
co_yn_eps: str, # EPS선택조건
|
|
co_st_eps: str, # EPS시작
|
|
co_en_eps: str, # EPS끝
|
|
co_yn_per: str, # PER선택조건
|
|
co_st_per: str, # PER시작
|
|
co_en_per: str, # PER끝
|
|
keyb: str, # NEXT KEY BUFF
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외주식조건검색[v1_해외주식-015]
|
|
해외주식조건검색 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
auth (str): "" (Null 값 설정)
|
|
excd (str): NYS : 뉴욕, NAS : 나스닥, AMS : 아멕스 HKS : 홍콩, SHS : 상해 , SZS : 심천 HSX : 호치민, HNX : 하노이 TSE : 도쿄
|
|
co_yn_pricecur (str): 해당조건 사용시(1), 미사용시 필수항목아님
|
|
co_st_pricecur (str): 단위: 각국통화(JPY, USD, HKD, CNY, VND)
|
|
co_en_pricecur (str): 단위: 각국통화(JPY, USD, HKD, CNY, VND)
|
|
co_yn_rate (str): 해당조건 사용시(1), 미사용시 필수항목아님
|
|
co_st_rate (str): %
|
|
co_en_rate (str): %
|
|
co_yn_valx (str): 해당조건 사용시(1), 미사용시 필수항목아님
|
|
co_st_valx (str): 단위: 천
|
|
co_en_valx (str): 단위: 천
|
|
co_yn_shar (str): 해당조건 사용시(1), 미사용시 필수항목아님
|
|
co_st_shar (str): 단위: 천
|
|
co_en_shar (str): 단위: 천
|
|
co_yn_volume (str): 해당조건 사용시(1), 미사용시 필수항목아님
|
|
co_st_volume (str): 단위: 주
|
|
co_en_volume (str): 단위: 주
|
|
co_yn_amt (str): 해당조건 사용시(1), 미사용시 필수항목아님
|
|
co_st_amt (str): 단위: 천
|
|
co_en_amt (str): 단위: 천
|
|
co_yn_eps (str): 해당조건 사용시(1), 미사용시 필수항목아님
|
|
co_st_eps (str):
|
|
co_en_eps (str):
|
|
co_yn_per (str): 해당조건 사용시(1), 미사용시 필수항목아님
|
|
co_st_per (str):
|
|
co_en_per (str):
|
|
keyb (str): "" 공백 입력
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외주식조건검색 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = inquire_search(
|
|
... auth="", excd="NAS", co_yn_pricecur="1", co_st_pricecur="160", co_en_pricecur="161",
|
|
... co_yn_rate="", co_st_rate="", co_en_rate="", co_yn_valx="", co_st_valx="", co_en_valx="",
|
|
... co_yn_shar="", co_st_shar="", co_en_shar="", co_yn_volume="", co_st_volume="", co_en_volume="",
|
|
... co_yn_amt="", co_st_amt="", co_en_amt="", co_yn_eps="", co_st_eps="", co_en_eps="",
|
|
... co_yn_per="", co_st_per="", co_en_per="", keyb=""
|
|
... )
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not excd:
|
|
logger.error("excd is required. (e.g. 'NAS')")
|
|
raise ValueError("excd is required. (e.g. 'NAS')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
|
|
tr_id = "HHDFS76410000"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/inquire-search"
|
|
|
|
params = {
|
|
"AUTH": auth,
|
|
"EXCD": excd,
|
|
"CO_YN_PRICECUR": co_yn_pricecur,
|
|
"CO_ST_PRICECUR": co_st_pricecur,
|
|
"CO_EN_PRICECUR": co_en_pricecur,
|
|
"CO_YN_RATE": co_yn_rate,
|
|
"CO_ST_RATE": co_st_rate,
|
|
"CO_EN_RATE": co_en_rate,
|
|
"CO_YN_VALX": co_yn_valx,
|
|
"CO_ST_VALX": co_st_valx,
|
|
"CO_EN_VALX": co_en_valx,
|
|
"CO_YN_SHAR": co_yn_shar,
|
|
"CO_ST_SHAR": co_st_shar,
|
|
"CO_EN_SHAR": co_en_shar,
|
|
"CO_YN_VOLUME": co_yn_volume,
|
|
"CO_ST_VOLUME": co_st_volume,
|
|
"CO_EN_VOLUME": co_en_volume,
|
|
"CO_YN_AMT": co_yn_amt,
|
|
"CO_ST_AMT": co_st_amt,
|
|
"CO_EN_AMT": co_en_amt,
|
|
"CO_YN_EPS": co_yn_eps,
|
|
"CO_ST_EPS": co_st_eps,
|
|
"CO_EN_EPS": co_en_eps,
|
|
"CO_YN_PER": co_yn_per,
|
|
"CO_ST_PER": co_st_per,
|
|
"CO_EN_PER": co_en_per,
|
|
"KEYB": keyb,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
# output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
# output1은 단일 객체, output2는 배열일 수 있음
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_search(
|
|
auth,
|
|
excd,
|
|
co_yn_pricecur,
|
|
co_st_pricecur,
|
|
co_en_pricecur,
|
|
co_yn_rate,
|
|
co_st_rate,
|
|
co_en_rate,
|
|
co_yn_valx,
|
|
co_st_valx,
|
|
co_en_valx,
|
|
co_yn_shar,
|
|
co_st_shar,
|
|
co_en_shar,
|
|
co_yn_volume,
|
|
co_st_volume,
|
|
co_en_volume,
|
|
co_yn_amt,
|
|
co_st_amt,
|
|
co_en_amt,
|
|
co_yn_eps,
|
|
co_st_eps,
|
|
co_en_eps,
|
|
co_yn_per,
|
|
co_st_per,
|
|
co_en_per,
|
|
keyb,
|
|
"N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외지수분봉조회[v1_해외주식-031]
|
|
##############################################################################################
|
|
|
|
def inquire_time_indexchartprice(
|
|
fid_cond_mrkt_div_code: str, # 조건 시장 분류 코드
|
|
fid_input_iscd: str, # 입력 종목코드
|
|
fid_hour_cls_code: str, # 시간 구분 코드
|
|
fid_pw_data_incu_yn: str, # 과거 데이터 포함 여부
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외지수분봉조회[v1_해외주식-031]
|
|
해외지수분봉조회 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
fid_cond_mrkt_div_code (str): N 해외지수 X 환율 KX 원화환율
|
|
fid_input_iscd (str): 종목번호(ex. TSLA)
|
|
fid_hour_cls_code (str): 0: 정규장, 1: 시간외
|
|
fid_pw_data_incu_yn (str): Y/N
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외지수분봉조회 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = inquire_time_indexchartprice(
|
|
... fid_cond_mrkt_div_code="N",
|
|
... fid_input_iscd="SPX",
|
|
... fid_hour_cls_code="0",
|
|
... fid_pw_data_incu_yn="Y"
|
|
... )
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not fid_cond_mrkt_div_code:
|
|
logger.error("fid_cond_mrkt_div_code is required. (e.g. 'N')")
|
|
raise ValueError("fid_cond_mrkt_div_code is required. (e.g. 'N')")
|
|
if not fid_input_iscd:
|
|
logger.error("fid_input_iscd is required. (e.g. 'SPX')")
|
|
raise ValueError("fid_input_iscd is required. (e.g. 'SPX')")
|
|
if not fid_hour_cls_code:
|
|
logger.error("fid_hour_cls_code is required. (e.g. '0')")
|
|
raise ValueError("fid_hour_cls_code is required. (e.g. '0')")
|
|
if not fid_pw_data_incu_yn:
|
|
logger.error("fid_pw_data_incu_yn is required. (e.g. 'Y')")
|
|
raise ValueError("fid_pw_data_incu_yn is required. (e.g. 'Y')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
|
|
tr_id = "FHKST03030200"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/inquire-time-indexchartprice"
|
|
|
|
params = {
|
|
"FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code,
|
|
"FID_INPUT_ISCD": fid_input_iscd,
|
|
"FID_HOUR_CLS_CODE": fid_hour_cls_code,
|
|
"FID_PW_DATA_INCU_YN": fid_pw_data_incu_yn,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
|
|
# output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_time_indexchartprice(
|
|
fid_cond_mrkt_div_code,
|
|
fid_input_iscd,
|
|
fid_hour_cls_code,
|
|
fid_pw_data_incu_yn,
|
|
"N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외주식분봉조회[v1_해외주식-030]
|
|
##############################################################################################
|
|
|
|
def inquire_time_itemchartprice(
|
|
auth: str, # 사용자권한정보
|
|
excd: str, # 거래소코드
|
|
symb: str, # 종목코드
|
|
nmin: str, # 분갭
|
|
pinc: str, # 전일포함여부
|
|
next: str, # 다음여부
|
|
nrec: str, # 요청갯수
|
|
fill: str, # 미체결채움구분
|
|
keyb: str, # NEXT KEY BUFF
|
|
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
|
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
|
tr_cont: str = "",
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외주식분봉조회[v1_해외주식-030]
|
|
해외주식분봉조회 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
auth (str): "" 공백으로 입력
|
|
excd (str): NYS : 뉴욕 NAS : 나스닥 AMS : 아멕스 HKS : 홍콩 SHS : 상해 SZS : 심천 HSX : 호치민 HNX : 하노이 TSE : 도쿄 ※ 주간거래는 최대 1일치 분봉만 조회 가능 BAY : 뉴욕(주간) BAQ : 나스닥(주간) BAA : 아멕스(주간)
|
|
symb (str): 종목코드(ex. TSLA)
|
|
nmin (str): 분단위(1: 1분봉, 2: 2분봉, ...)
|
|
pinc (str): 0:당일 1:전일포함 ※ 다음조회 시 반드시 "1"로 입력
|
|
next (str): 처음조회 시, "" 공백 입력 다음조회 시, "1" 입력
|
|
nrec (str): 레코드요청갯수 (최대 120)
|
|
fill (str): "" 공백으로 입력
|
|
keyb (str): 처음 조회 시, "" 공백 입력 다음 조회 시, 이전 조회 결과의 마지막 분봉 데이터를 이용하여, 1분 전 혹은 n분 전의 시간을 입력 (형식: YYYYMMDDHHMMSS, ex. 20241014140100)
|
|
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
|
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
|
tr_cont (str): 연속 거래 여부
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Tuple[pd.DataFrame, pd.DataFrame]: 해외주식분봉조회 데이터
|
|
|
|
Example:
|
|
>>> df1, df2 = inquire_time_itemchartprice(
|
|
... auth="", excd="NAS", symb="TSLA", nmin="5", pinc="1", next="1", nrec="120", fill="", keyb=""
|
|
... )
|
|
>>> print(df1)
|
|
>>> print(df2)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not excd:
|
|
logger.error("excd is required. (e.g. 'NAS')")
|
|
raise ValueError("excd is required. (e.g. 'NAS')")
|
|
if not symb:
|
|
logger.error("symb is required. (e.g. 'TSLA')")
|
|
raise ValueError("symb is required. (e.g. 'TSLA')")
|
|
if not nmin:
|
|
logger.error("nmin is required. (e.g. '5')")
|
|
raise ValueError("nmin is required. (e.g. '5')")
|
|
if not pinc:
|
|
logger.error("pinc is required. (e.g. '1')")
|
|
raise ValueError("pinc is required. (e.g. '1')")
|
|
if not nrec or int(nrec) > 120:
|
|
logger.error("nrec is required. (e.g. '120', 최대120개)")
|
|
raise ValueError("nrec is required. (e.g. '120', 최대120개)")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
if depth >= max_depth:
|
|
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
|
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
|
|
|
tr_id = "HHDFS76950200"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/inquire-time-itemchartprice"
|
|
|
|
params = {
|
|
"AUTH": auth,
|
|
"EXCD": excd,
|
|
"SYMB": symb,
|
|
"NMIN": nmin,
|
|
"PINC": pinc,
|
|
"NEXT": next,
|
|
"NREC": nrec,
|
|
"FILL": fill,
|
|
"KEYB": keyb,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
# Output1 처리
|
|
if hasattr(res.getBody(), 'output1'):
|
|
output_data = res.getBody().output1
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data1 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data1 = pd.DataFrame([output_data])
|
|
|
|
if dataframe1 is not None:
|
|
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
|
else:
|
|
dataframe1 = current_data1
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
else:
|
|
if dataframe1 is None:
|
|
dataframe1 = pd.DataFrame()
|
|
|
|
# Output2 처리
|
|
if hasattr(res.getBody(), 'output2'):
|
|
output_data = res.getBody().output2
|
|
if output_data:
|
|
if isinstance(output_data, list):
|
|
current_data2 = pd.DataFrame(output_data)
|
|
else:
|
|
current_data2 = pd.DataFrame([output_data])
|
|
|
|
if dataframe2 is not None:
|
|
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
|
else:
|
|
dataframe2 = current_data2
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
else:
|
|
if dataframe2 is None:
|
|
dataframe2 = pd.DataFrame()
|
|
|
|
tr_cont = res.getHeader().tr_cont
|
|
|
|
if tr_cont in ["M", "F"]:
|
|
logger.info("Calling next page...")
|
|
ka.smart_sleep()
|
|
return inquire_time_itemchartprice(
|
|
auth,
|
|
excd,
|
|
symb,
|
|
nmin,
|
|
pinc,
|
|
next,
|
|
nrec,
|
|
fill,
|
|
keyb,
|
|
"N", dataframe1, dataframe2, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe1, dataframe2
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame(), pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외뉴스종합(제목) [해외주식-053]
|
|
##############################################################################################
|
|
|
|
def news_title(
|
|
info_gb: str = "", # [필수] 뉴스구분
|
|
class_cd: str = "", # [필수] 중분류
|
|
nation_cd: str = "", # [필수] 국가코드 (ex. 공백:전체, CN:중국, HK:홍콩, US:미국)
|
|
exchange_cd: str = "", # [필수] 거래소코드
|
|
symb: str = "", # [필수] 종목코드
|
|
data_dt: str = "", # [필수] 조회일자
|
|
data_tm: str = "", # [필수] 조회시간
|
|
cts: str = "", # [필수] 다음키
|
|
tr_cont: str = "", # [필수] 연속거래여부
|
|
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> pd.DataFrame:
|
|
"""
|
|
해외뉴스종합(제목) API입니다.
|
|
한국투자 HTS(eFriend Plus) > [7702] 해외뉴스종합 화면의 "우측 상단 뉴스목록" 기능을 API로 개발한 사항으로, 해당 화면을 참고하시면 기능을 이해하기 쉽습니다.
|
|
|
|
Args:
|
|
info_gb (str): [필수] 뉴스구분
|
|
class_cd (str): [필수] 중분류
|
|
nation_cd (str): [필수] 국가코드 (ex. 공백:전체, CN:중국, HK:홍콩, US:미국)
|
|
exchange_cd (str): [필수] 거래소코드
|
|
symb (str): [필수] 종목코드
|
|
data_dt (str): [필수] 조회일자
|
|
data_tm (str): [필수] 조회시간
|
|
cts (str): [필수] 다음키
|
|
tr_cont (str): [필수] 연속거래여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
pd.DataFrame: 해외뉴스종합(제목) 데이터
|
|
|
|
Example:
|
|
>>> df = news_title()
|
|
>>> print(df)
|
|
"""
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe is None:
|
|
return pd.DataFrame()
|
|
else:
|
|
return dataframe
|
|
|
|
tr_id = "HHPSTH60100C1" # 해외뉴스종합(제목)
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/news-title"
|
|
|
|
params = {
|
|
"INFO_GB": info_gb, # 뉴스구분
|
|
"CLASS_CD": class_cd, # 중분류
|
|
"NATION_CD": nation_cd, # 국가코드
|
|
"EXCHANGE_CD": exchange_cd, # 거래소코드
|
|
"SYMB": symb, # 종목코드
|
|
"DATA_DT": data_dt, # 조회일자
|
|
"DATA_TM": data_tm, # 조회시간
|
|
"CTS": cts # 다음키
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
current_data = pd.DataFrame(res.getBody().outblock1)
|
|
|
|
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 in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return news_title(
|
|
info_gb, class_cd, nation_cd, exchange_cd, symb, data_dt, data_tm, cts, "N", dataframe, depth + 1,
|
|
max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 기간별권리조회 [해외주식-052]
|
|
##############################################################################################
|
|
|
|
def period_rights(
|
|
rght_type_cd: str, # 권리유형코드
|
|
inqr_dvsn_cd: str, # 조회구분코드
|
|
inqr_strt_dt: str, # 조회시작일자
|
|
inqr_end_dt: str, # 조회종료일자
|
|
pdno: str = "", # 상품번호
|
|
prdt_type_cd: str = "", # 상품유형코드
|
|
NK50: str = "", # 연속조회키50
|
|
FK50: str = "", # 연속조회검색조건50
|
|
tr_cont: str = "", # 연속거래여부
|
|
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
|
depth: int = 0, # 내부 재귀깊이 (자동관리)
|
|
max_depth: int = 10 # 최대 재귀 횟수 제한
|
|
) -> pd.DataFrame:
|
|
"""
|
|
해외주식 기간별권리조회 API입니다.
|
|
한국투자 HTS(eFriend Plus) > [7520] 기간별해외증권권리조회 화면을 API로 개발한 사항으로, 해당 화면을 참고하시면 기능을 이해하기 쉽습니다.
|
|
|
|
※ 확정여부가 '예정'으로 표시되는 경우는 권리정보가 변경될 수 있으니 참고자료로만 활용하시기 바랍니다.
|
|
|
|
Args:
|
|
rght_type_cd (str): [필수] 권리유형코드 (%%:전체, 01:유상, 02:무상, 03:배당, 11:합병,14:액면분할, 15:액면병합, 17:감자, 54:WR청구,61:원리금상환, 71:WR소멸, 74:배당옵션, 75:특별배당, 76:ISINCODE변경, 77:실권주청약)
|
|
inqr_dvsn_cd (str): [필수] 조회구분코드 (02:현지기준일, 03:청약시작일, 04:청약종료일)
|
|
inqr_strt_dt (str): [필수] 조회시작일자 (20250101)
|
|
inqr_end_dt (str): [필수] 조회종료일자 (20250131)
|
|
pdno (str): 상품번호
|
|
prdt_type_cd (str): 상품유형코드
|
|
NK50 (str): 연속조회키50
|
|
FK50 (str): 연속조회검색조건50
|
|
tr_cont (str): 연속거래여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 내부 재귀깊이 (자동관리)
|
|
max_depth (int): 최대 재귀 횟수 제한
|
|
|
|
Returns:
|
|
pd.DataFrame: 해외주식 기간별권리조회 데이터
|
|
|
|
Example:
|
|
>>> df = period_rights("%%", "02", "20240417", "20240417")
|
|
>>> print(df)
|
|
"""
|
|
|
|
# 필수 파라미터 검증
|
|
if rght_type_cd == "":
|
|
raise ValueError(
|
|
"rght_type_cd is required (e.g. '%%:전체, 01:유상, 02:무상, 03:배당, 11:합병,14:액면분할, 15:액면병합, 17:감자, 54:WR청구,61:원리금상환, 71:WR소멸, 74:배당옵션, 75:특별배당, 76:ISINCODE변경, 77:실권주청약')")
|
|
|
|
if inqr_dvsn_cd == "":
|
|
raise ValueError("inqr_dvsn_cd is required (e.g. '02:현지기준일, 03:청약시작일, 04:청약종료일')")
|
|
|
|
if inqr_strt_dt == "":
|
|
raise ValueError("inqr_strt_dt is required (e.g. '20250101')")
|
|
|
|
if inqr_end_dt == "":
|
|
raise ValueError("inqr_end_dt is required (e.g. '20250131')")
|
|
|
|
if depth > max_depth:
|
|
logging.warning("Max recursive depth reached.")
|
|
if dataframe is None:
|
|
return pd.DataFrame()
|
|
else:
|
|
return dataframe
|
|
|
|
tr_id = "CTRGT011R" # 해외주식 기간별권리조회
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/period-rights"
|
|
|
|
params = {
|
|
"RGHT_TYPE_CD": rght_type_cd, # 권리유형코드
|
|
"INQR_DVSN_CD": inqr_dvsn_cd, # 조회구분코드
|
|
"INQR_STRT_DT": inqr_strt_dt, # 조회시작일자
|
|
"INQR_END_DT": inqr_end_dt, # 조회종료일자
|
|
"PDNO": pdno, # 상품번호
|
|
"PRDT_TYPE_CD": prdt_type_cd, # 상품유형코드
|
|
"CTX_AREA_NK50": NK50, # 연속조회키50
|
|
"CTX_AREA_FK50": FK50 # 연속조회검색조건50
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
if res.isOK():
|
|
current_data = pd.DataFrame(res.getBody().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
|
|
NK50 = res.getBody().ctx_area_nk50
|
|
FK50 = res.getBody().ctx_area_fk50
|
|
|
|
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
|
logging.info("Call Next page...")
|
|
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
|
return period_rights(
|
|
rght_type_cd, inqr_dvsn_cd, inqr_strt_dt, inqr_end_dt,
|
|
pdno, prdt_type_cd, NK50, FK50, "N", dataframe, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logging.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외주식 현재체결가[v1_해외주식-009]
|
|
##############################################################################################
|
|
|
|
def price(
|
|
auth: str, # 사용자권한정보
|
|
excd: str, # 거래소코드
|
|
symb: str, # 종목코드
|
|
env_dv: str = "real", # 실전모의구분
|
|
tr_cont: str = "",
|
|
dataframe: Optional[pd.DataFrame] = None,
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외주식 현재체결가[v1_해외주식-009]
|
|
해외주식 현재체결가 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
auth (str): 사용자권한정보
|
|
excd (str): 거래소코드 (예: "NAS")
|
|
symb (str): 종목코드 (예: "AAPL")
|
|
env_dv (str): 실전모의구분 (real:실전, demo:모의)
|
|
tr_cont (str): 연속 거래 여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 현재체결가 데이터
|
|
|
|
Example:
|
|
>>> df = price("", "NAS", "AAPL")
|
|
>>> print(df)
|
|
"""
|
|
# 로깅 설정
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 필수 파라미터 검증
|
|
if not excd:
|
|
logger.error("excd is required. (e.g. 'NAS')")
|
|
raise ValueError("excd is required. (e.g. 'NAS')")
|
|
|
|
if not symb:
|
|
logger.error("symb is required. (e.g. 'AAPL')")
|
|
raise ValueError("symb is required. (e.g. 'AAPL')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
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()
|
|
|
|
# TR ID 설정 (모의투자 지원 로직)
|
|
if env_dv == "real" or env_dv == "demo":
|
|
tr_id = "HHDFS00000300" # 실전투자, 모의투자 공통 TR ID
|
|
else:
|
|
logger.error("Invalid env_dv value: %s. Must be 'real' or 'demo'.", env_dv)
|
|
raise ValueError("env_dv must be 'real' or 'demo'")
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/price"
|
|
|
|
params = {
|
|
"AUTH": auth,
|
|
"EXCD": excd,
|
|
"SYMB": symb,
|
|
}
|
|
|
|
# API 호출
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
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)
|
|
else:
|
|
current_data = pd.DataFrame()
|
|
|
|
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()
|
|
return price(
|
|
auth,
|
|
excd,
|
|
symb,
|
|
env_dv,
|
|
"N", dataframe, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 기본시세 > 해외주식 현재가상세[v1_해외주식-029]
|
|
##############################################################################################
|
|
|
|
def price_detail(
|
|
auth: str, # 사용자권한정보
|
|
excd: str, # 거래소명
|
|
symb: str, # 종목코드
|
|
tr_cont: str = "",
|
|
dataframe: Optional[pd.DataFrame] = None,
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외주식 현재가상세[v1_해외주식-029]
|
|
해외주식 현재가상세 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
auth (str): 사용자권한정보
|
|
excd (str): 거래소명 (예: HKS, NYS, NAS, AMS, TSE, SHS, SZS, SHI, SZI, HSX, HNX, BAY, BAQ, BAA)
|
|
symb (str): 종목코드
|
|
tr_cont (str): 연속 거래 여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 현재가상세 데이터
|
|
|
|
Example:
|
|
>>> df = price_detail(auth="your_auth_token", excd="NAS", symb="TSLA")
|
|
>>> print(df)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not excd:
|
|
logger.error("excd is required. (e.g. 'NAS')")
|
|
raise ValueError("excd is required. (e.g. 'NAS')")
|
|
if not symb:
|
|
logger.error("symb is required. (e.g. 'TSLA')")
|
|
raise ValueError("symb is required. (e.g. 'TSLA')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
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()
|
|
|
|
tr_id = "HHDFS76200200"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/price-detail"
|
|
|
|
params = {
|
|
"AUTH": auth,
|
|
"EXCD": excd,
|
|
"SYMB": symb,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
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)
|
|
else:
|
|
current_data = pd.DataFrame()
|
|
|
|
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()
|
|
return price_detail(
|
|
auth,
|
|
excd,
|
|
symb,
|
|
"N", dataframe, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 권리종합 [해외주식-050]
|
|
##############################################################################################
|
|
|
|
def rights_by_ice(
|
|
ncod: str, # 국가코드 (CN:중국,HK:홍콩,US:미국,JP:일본,VN:베트남)
|
|
symb: str, # 종목코드
|
|
st_ymd: str = "", # 일자시작일 (미입력시 3개월전)
|
|
ed_ymd: str = "" # 일자종료일 (미입력시 3개월후)
|
|
) -> pd.DataFrame:
|
|
"""
|
|
해외주식 권리종합 API입니다.
|
|
한국투자 HTS(eFriend Plus) > [7833] 해외주식 권리(ICE제공) 화면의 "전체" 탭 기능을 API로 개발한 사항으로, 해당 화면을 참고하시면 기능을 이해하기 쉽습니다.
|
|
|
|
※ 조회기간 기준일 입력시 참고 - 상환: 상환일자, 조기상환: 조기상환일자, 티커변경: 적용일, 그 외: 발표일
|
|
|
|
Args:
|
|
ncod (str): [필수] 국가코드 (ex. CN:중국,HK:홍콩,US:미국,JP:일본,VN:베트남)
|
|
symb (str): [필수] 종목코드
|
|
st_ymd (str): 일자시작일 (ex. 미입력시 3개월전)
|
|
ed_ymd (str): 일자종료일 (ex. 미입력시 3개월후)
|
|
|
|
Returns:
|
|
pd.DataFrame: 해외주식 권리종합 정보
|
|
|
|
Raises:
|
|
ValueError: 필수 파라미터가 누락되었을 때
|
|
|
|
Example:
|
|
>>> df = rights_by_ice("US", "NVDL")
|
|
>>> print(df)
|
|
"""
|
|
|
|
# 필수 파라미터 검증
|
|
if ncod == "":
|
|
raise ValueError("ncod is required (e.g. 'CN:중국,HK:홍콩,US:미국,JP:일본,VN:베트남')")
|
|
|
|
if symb == "":
|
|
raise ValueError("symb is required")
|
|
|
|
tr_id = "HHDFS78330900"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/rights-by-ice"
|
|
|
|
params = {
|
|
"NCOD": ncod, # 국가코드
|
|
"SYMB": symb, # 종목코드
|
|
"ST_YMD": st_ymd, # 일자시작일
|
|
"ED_YMD": ed_ymd # 일자종료일
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, "", params)
|
|
|
|
if res.isOK():
|
|
current_data = pd.DataFrame(res.getBody().output1)
|
|
logging.info("Data fetch complete.")
|
|
return current_data
|
|
else:
|
|
res.printError(url=api_url)
|
|
return pd.DataFrame()
|
|
|
|
|
|
##############################################################################################
|
|
# [해외주식] 시세분석 > 해외주식 상품기본정보[v1_해외주식-034]
|
|
##############################################################################################
|
|
|
|
def search_info(
|
|
prdt_type_cd: str, # 상품유형코드
|
|
pdno: str, # 상품번호
|
|
tr_cont: str = "",
|
|
dataframe: Optional[pd.DataFrame] = None,
|
|
depth: int = 0,
|
|
max_depth: int = 10
|
|
) -> Optional[pd.DataFrame]:
|
|
"""
|
|
[해외주식] 기본시세
|
|
해외주식 상품기본정보[v1_해외주식-034]
|
|
해외주식 상품기본정보 API를 호출하여 DataFrame으로 반환합니다.
|
|
|
|
Args:
|
|
prdt_type_cd (str): 512 미국 나스닥 / 513 미국 뉴욕 / 529 미국 아멕스 515 일본 501 홍콩 / 543 홍콩CNY / 558 홍콩USD 507 베트남 하노이 / 508 베트남 호치민 551 중국 상해A / 552 중국 심천A
|
|
pdno (str): 예) AAPL (애플)
|
|
tr_cont (str): 연속 거래 여부
|
|
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
|
depth (int): 현재 재귀 깊이
|
|
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
|
|
|
Returns:
|
|
Optional[pd.DataFrame]: 해외주식 상품기본정보 데이터
|
|
|
|
Example:
|
|
>>> df = search_info(prdt_type_cd="512", pdno="AAPL")
|
|
>>> print(df)
|
|
"""
|
|
# [필수 파라미터 검증]
|
|
if not prdt_type_cd:
|
|
logger.error("prdt_type_cd is required. (e.g. '512')")
|
|
raise ValueError("prdt_type_cd is required. (e.g. '512')")
|
|
if not pdno:
|
|
logger.error("pdno is required. (e.g. 'AAPL')")
|
|
raise ValueError("pdno is required. (e.g. 'AAPL')")
|
|
|
|
# 최대 재귀 깊이 체크
|
|
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()
|
|
|
|
tr_id = "CTPF1702R"
|
|
|
|
api_url = "/uapi/overseas-price/v1/quotations/search-info"
|
|
|
|
params = {
|
|
"PRDT_TYPE_CD": prdt_type_cd,
|
|
"PDNO": pdno,
|
|
}
|
|
|
|
res = ka._url_fetch(api_url, tr_id, tr_cont, params)
|
|
|
|
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)
|
|
else:
|
|
current_data = pd.DataFrame()
|
|
|
|
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()
|
|
return search_info(
|
|
prdt_type_cd,
|
|
pdno,
|
|
"N", dataframe, depth + 1, max_depth
|
|
)
|
|
else:
|
|
logger.info("Data fetch complete.")
|
|
return dataframe
|
|
else:
|
|
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
|
res.printError(api_url)
|
|
return pd.DataFrame()
|