initial commit

This commit is contained in:
2026-02-04 00:16:34 +09:00
commit ae11528dd9
867 changed files with 209640 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
#### **[당사에서 제공하는 샘플코드에 대한 유의사항]** ####
- 샘플 코드는 한국투자증권 오픈API(KIS Develpers)를 연동하는 예시입니다. 고객님의 개발부담을 줄이고자 참고용으로 제공되고 있습니다.
- 샘플 코드는 별도의 공지 없이 지속적으로 업데이트될 수 있습니다.
- 샘플 코드를 활용하여 제작한 고객님의 프로그램으로 인한 손해에 대해서는 당사에서 책임지지 않습니다.
![header](https://capsule-render.vercel.app/api?type=waving&color=gradient&height=300&section=header&text=한국투자증권%20KIS%20Developers&fontSize=50&animation=fadeIn&fontAlignY=38&desc=Open%20Trading%20API%20Docs&descAlignY=51&descAlign=62)
[작성중]
## 1. 샘플코드 이용 안내 (API 호출 이제부터 한줄이면 됩니다.)
한국투자증권 오픈API 활용하는 매매프로그램 개발을 좀더 쉽게 가능하도록 API호출에 필요한 Header, Body 정의를 Import 만으로 한줄로만 호출이 가능합니다.

View File

@@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 15 07:56:54 2022
"""
#kis_api module 을 찾을 수 없다는 에러가 나는 경우 sys.path에 kis_api.py 가 있는 폴더를 추가해준다.
import kis_auth as ka
import kis_domstk as kb
import pandas as pd
import sys
# 토큰 발급
ka.auth()
#====| 국내주식(kis_domstk) import 파일을 하신후 프로그램에서 필요한 API 호출 샘플 아래 참고하시기 바랍니다. |=====================
#====| 국내주식(kis_domstk) import 파일을 하신후 프로그램에서 필요한 API 호출 샘플 아래 참고하시기 바랍니다. |=====================
#====| 국내선물옵션, 해외주식, 해외선물옵션, 채권 등 지속적으로 추가하도록 하겠습니다. 2024.05.16 KIS Developers Team |======================
#====| [국내주식] 주문/계좌 |============================================================================================================================
# [국내주식] 주문/계좌 > 주식현금주문 (매수매도구분 buy,sell + 종목번호 6자리 + 주문수량 + 주문단가)
# 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 kis_domstk.py get_order_cash 수정요망!
rt_data = kb.get_order_cash(ord_dv="buy",itm_no="071050", qty=10, unpr=65000)
print(rt_data.KRX_FWDG_ORD_ORGNO + "+" + rt_data.ODNO + "+" + rt_data.ORD_TMD) # 주문접수조직번호+주문접수번호+주문시각
# [국내주식] 주문/계좌 > 주식주문(정정취소) (한국거래소전송주문조직번호 5자리+원주문번호 10자리('0'을 채우지 않아도됨)+정정취소구분코드+정정및취소주문수량+잔량전부주문여부)
# 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 kis_domstk.py get_order_rvsecncl 수정요망!
rt_data = kb.get_order_rvsecncl(ord_orgno="06010", orgn_odno="0000224003", ord_dvsn="00", rvse_cncl_dvsn_cd="01", ord_qty=0, ord_unpr=64900, qty_all_ord_yn="Y")
print(rt_data.KRX_FWDG_ORD_ORGNO + "+" + rt_data.ODNO + "+" + rt_data.ORD_TMD) # 주문접수조직번호+주문접수번호+주문시각
# [국내주식] 주문/계좌 > 주식정정취소가능주문내역조회
rt_data = kb.get_inquire_psbl_rvsecncl_lst()
print(rt_data)
# [국내주식] 주문/계좌 > 주식일별주문체결(현황)조회
# dv="01" 01:3개월 이내 국내주식체결내역 (월단위 ex: 2024.04.25 이면 2024.01월~04월조회)
# dv="02" 02:3개월 이전 국내주식체결내역 (월단위 ex: 2024.04.25 이면 2024.01월이전)
rt_data = kb.get_inquire_daily_ccld_obj(dv="01")
print(rt_data)
# [국내주식] 주문/계좌 > 주식일별주문체결(내역)조회
# dv="01" 01:3개월 이내 국내주식체결내역 (월단위 ex: 2024.04.25 이면 2024.01월~04월조회)
# dv="02" 02:3개월 이전 국내주식체결내역 (월단위 ex: 2024.04.25 이면 2024.01월이전)
rt_data = kb.get_inquire_daily_ccld_lst(dv="01")
print(rt_data)
# [국내주식] 주문/계좌 > 주식잔고조회 (잔고현황)
rt_data = kb.get_inquire_balance_obj()
print(rt_data)
# [국내주식] 주문/계좌 > 주식잔고조회 (보유종목리스트)
rt_data = kb.get_inquire_balance_lst()
print(rt_data)
# [국내주식] 주문/계좌 > 매수가능조회 (종목번호 5자리 + 종목단가)
rt_data = kb.get_inquire_psbl_order(pdno="", ord_unpr=0)
ord_psbl_cash_value = rt_data.loc[0, 'ord_psbl_cash'] # ord_psbl_cash 주문가능현금
ord_psbl_cash_value = rt_data.loc[0, 'nrcvb_buy_amt'] # nrcvb_buy_amt 미수없는매수가능금액
print(rt_data)
# [국내주식] 주문/계좌 > 주식예약주문 (매수매도구분 buy,sell + 종목번호 6자리 + 주문수량 + 주문단가 + 주문구분코드 )
# 주문구분코드 : 00 : 지정가 01 : 시장가 02 : 조건부지정가 05 : 장전 시간외
rt_data = kb.get_order_resv(ord_dv="buy", itm_no="052400", qty=100, unpr=12650, ord_dvsn_cd="01")
print(rt_data.RSVN_ORD_SEQ) # 예약주문순번
# [국내주식] 주문/계좌 > 주식예약주문(취소) (예약주문순번)
rt_data = kb.get_order_resv_cncl(rsvn_ord_seq="93601")
print(rt_data)
# [국내주식] 주문/계좌 > 주식예약주문(정정) (종목번호 6자리 + 주문수량 + 주문단가 + 매도매수구분코드 + 주문구분코드 + 주문대상잔고구분코드 + 예약주문순번)
# 매도매수구분코드 01 : 매도 02 : 매수
# 주문구분코드 00 : 지정가 01 : 시장가 02 : 조건부지정가 05 : 장전 시간외
# 주문대상잔고구분코드 10 : 현금 12 : 주식담보대출 14 : 대여상환 21 : 자기융자신규 .........
rt_data = kb.get_order_resv_rvse(pdno="052400", ord_qty=1, ord_unpr=12700, sll_buy_dvsn_cd="02", ord_dvsn="00", ord_objt_cblc_dvsn_cd="", rsvn_ord_seq=93605)
print(rt_data)
# [국내주식] 주문/계좌 > 주식예약주문조회[v1_국내주식-020] (조회시작일자 + 조회종료일자)
rt_data = kb.get_order_resv_ccnl(inqr_strt_dt="20240429", inqr_end_dt="20240430")
print(rt_data)
# [국내주식] 주문/계좌 > 주식잔고조회_실현손익[v1_국내주식-041]
rt_data = kb.get_inquire_balance_rlz_pl_obj()
print(rt_data)
rt_data = kb.get_inquire_balance_rlz_pl_lst()
print(rt_data)
# [국내주식] 주문/계좌 > 신용매수가능조회
rt_data = kb.get_inquire_credit_psamount()
print(rt_data)
# [국내주식] 주문/계좌 > 기간별매매손익현황조회
rt_data = kb.get_inquire_period_trade_profit_obj()
print(rt_data)
rt_data = kb.get_inquire_period_trade_profit_lst()
print(rt_data)
# [국내주식] 주문/계좌 > 기간별손익일별합산조회
rt_data = kb.get_inquire_period_profit_obj()
print(rt_data)
rt_data = kb.get_inquire_period_profit_lst()
print(rt_data)
# [국내주식] 기본시세 > 주식현재가 시세 (종목번호 6자리)
rt_data = kb.get_inquire_price(itm_no="071050")
print(rt_data.stck_prpr+ " " + rt_data.prdy_vrss) # 현재가, 전일대비
# [국내주식] 기본시세 > 주식현재가 체결 (종목번호 6자리)
rt_data = kb.get_inquire_ccnl(itm_no="071050")
# [국내주식] 기본시세 > 주식현재가 일자별 (종목번호 6자리 + 기간분류코드)
# 기간분류코드 D : (일)최근 30거래일 W : (주)최근 30주 M : (월)최근 30개월
# 수정주가기준이며 수정주가미반영 기준을 원하시면 인자값 adj_prc_code="2" 추가
rt_data = kb.get_inquire_daily_price(itm_no="071050", period_code="M")
# [국내주식] 기본시세 > 주식현재가 호가 (종목번호 6자리)
rt_data = kb.get_inquire_asking_price_exp_ccn(itm_no="071050")
# [국내주식] 기본시세 > 주식현재가 예상체결 (출력구분="2" + 종목번호 6자리)
rt_data = kb.get_inquire_asking_price_exp_ccn(output_dv="2", itm_no="071050")
# [국내주식] 기본시세 > 주식현재가 투자자 (종목번호 6자리)
rt_data = kb.get_inquire_investor(itm_no="071050")
# [국내주식] 기본시세 > 주식현재가 회원사 (종목번호 6자리)
rt_data = kb.get_inquire_member(itm_no="071050")
# [국내주식] 기본시세 > 국내주식기간별시세(일/주/월/년) (현재) (종목번호 6자리)
rt_data = kb.get_inquire_daily_itemchartprice(itm_no="071050")
# [국내주식] 기본시세 > 국내주식기간별시세(일/주/월/년) (기간별 데이터 Default는 일별이며 조회기간은 100일전(영업일수 아님)부터 금일까지)
rt_data = kb.get_inquire_daily_itemchartprice(output_dv="2", itm_no="071050")
# [국내주식] 기본시세 > 주식현재가 당일시간대별체결 (현재가 : 주식현재가, 전일대비, 전일대비율, 누적거래량,전일거래량, 대표시장한글명))
rt_data = kb.get_inquire_time_itemconclusion(itm_no="071050")
# [국내주식] 기본시세 > 주식현재가 당일시간대별체결 (시간대별체결내역)
rt_data = kb.get_inquire_time_itemconclusion(output_dv='2', itm_no="071050") # 기준시각 미지정시 현재시각 이전 체결 내역이 30건 조회됨
rt_data = kb.get_inquire_time_itemconclusion(output_dv='2', itm_no="071050", inqr_hour='100000') # 지정 기준시각 이전 체결 내역이 30건 조회됨
# [국내주식] 기본시세 > 주식현재가 시간외현재주가 (시간외 현재가 : 주식현재가, 전일대비, 전일대비율, 누적거래량, 거래량, 거래대금, 단일가상한가 .... 등)
rt_data = kb.get_inquire_daily_overtimeprice(itm_no="071050")
# [국내주식] 기본시세 > 주식현재가 시간외일자별주가 (최근 30일) (출력구분 + 종목번호 6자리)
rt_data = kb.get_inquire_daily_overtimeprice(output_dv='2', itm_no="071050")
# [국내주식] 기본시세 > 주식당일분봉조회 (종목번호 6자리) (출력구분 + 종목번호 6자리)
rt_data = kb.get_inquire_time_itemchartprice(itm_no="071050") #
rt_data = kb.get_inquire_time_itemchartprice(output_dv="2", itm_no="071050")
# [국내주식] 기본시세 > 주식현재가 시세2
rt_data = kb.get_inquire_daily_price_2(itm_no="071050")
# [국내주식] 기본시세 > ETF/ETN 현재가
rt_data = kb.get_quotations_inquire_price(itm_no="071050")
# [국내주식] 기본시세 > NAV 비교추이(종목)
rt_data = kb.get_quotations_nav_comparison_trend(itm_no="071050") # ETF 종목 정보
rt_data = kb.get_quotations_nav_comparison_trend(output_dv="2", itm_no="071050") # ETF NAV 정보
# [국내주식] 업종/기타 > 국내휴장일조회
rt_data = kb.get_quotations_ch_holiday(dt="20240302")
print(rt_data)

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 15 07:56:54 2022
"""
#kis_api module 을 찾을 수 없다는 에러가 나는 경우 sys.path에 kis_api.py 가 있는 폴더를 추가해준다.
import kis_auth as ka
import kis_domfuopt as kb
import pandas as pd
import sys
# 토큰 발급
ka.auth()
#====| 국내선물옵션(야간포함)(kis_domfuopt) import 파일을 하신후 프로그램에서 필요한 API 호출 샘플 아래 참고하시기 바랍니다. |=======================
#====| 국내선물옵션(야간포함)(kis_domfuopt) import 파일을 하신후 프로그램에서 필요한 API 호출 샘플 아래 참고하시기 바랍니다. |=======================
#====| 국내선물옵션(야간포함)(kis_domfuopt) import 파일을 하신후 프로그램에서 필요한 API 호출 샘플 아래 참고하시기 바랍니다. |=======================
#====| 국내선물옵션, 해외주식, 해외선물옵션, 채권 등 지속적으로 추가하도록 하겠습니다. 2024.05.16 KIS Developers Team |======================
#====| [국내선물옵션] 주문/계좌 |============================================================================================================================
# [국내선물옵션] 주문/계좌 > 선물옵션 주문[v1_국내선물-001] (주간:01/야간(Eurex):02구분 + 매수매도구분 buy,sell + 종목번호 + 주문수량 + 주문단가)
# 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 kis_domstk.py get_order_cash 수정요망!
rt_data = kb.get_domfuopt_order(dv_cd="01", sll_buy_dvsn_cd="02", dvsn_cd="02", itm_no="101V06", qty=100, unpr=0)
print(rt_data)
# [국내선물옵션] 주문/계좌 > 선물옵션 정정취소주문[v1_국내선물-002] (주간:01/야간(Eurex):02구분 + 정정취소구분코드(정정:01. 취소:02) + 원주문번호 + 주문구분 + 주문수량 + 주문단가 + 잔량전부여부)
# 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 kis_domfuopt.py get_domfuopt_order_rvsecncl 수정요망!
rt_data = kb.get_domfuopt_order_rvsecncl(orgn_odno="0000362027", ord_dvsn="01", rvse_cncl_dvsn_cd="01", ord_qty=1, ord_unpr=366.45, rmn_qty_yn="")
print(rt_data)
# [[국내선물옵션] 주문/계좌 > (야간)선물옵션 주문체결 현황조회 [국내선물-009] (조회시작일 + 조회종료일)
rt_data = kb.get_domfuopt_inquire_ngt_ccnl_obj(inqr_strt_dt="20230701", inqr_end_dt="20230731")
print(rt_data)
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 주문체결 내역조회 [국내선물-009] (조회시작일 + 조회종료일)
rt_data = kb.get_domfuopt_inquire_ngt_ccnl_lst(inqr_strt_dt="20230701", inqr_end_dt="20230731")
print(rt_data)
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 잔고현황 [국내선물-010]
rt_data = kb.get_domfuopt_inquire_ngt_balance_obj()
print(rt_data)
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 잔고현황
rt_data = kb.get_domfuopt_inquire_ngt_balance_lst()
print(rt_data)
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 주문가능 조회 [국내선물-011]
rt_data = kb.get_domfuopt_inquire_psbl_ngt_order(pdno="101V06", sll_buy_dvsn_cd="02", ord_unpr=0)
print(rt_data)

View File

@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 15 07:56:54 2022
"""
#kis_api module 을 찾을 수 없다는 에러가 나는 경우 sys.path에 kis_api.py 가 있는 폴더를 추가해준다.
import kis_auth as ka
import kis_ovrseastk as kb
import pandas as pd
import sys
# 토큰 발급
ka.auth()
#====| [해외주식] 주문/계좌 |============================================================================================================================
# [해외주식] 주문/계좌 > 주문 (매수매도구분 buy,sell + 종목코드6자리 + 주문수량 + 주문단가)
# 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 kis_ovrseastk.py get_overseas_order 수정요망!
#rt_data = kb.get_overseas_order(ord_dv="buy", excg_cd="NASD", itm_no="TSLA", qty=1, unpr=170)
#rt_data = kb.get_overseas_order(ord_dv="buy", excg_cd="NASD", itm_no="AAPL", qty=1, unpr=216.75)
rt_data = kb.get_overseas_order(ord_dv="buy", excg_cd="NASD", itm_no="NVDA", qty=1, unpr=123.3)
print(rt_data.KRX_FWDG_ORD_ORGNO + "+" + rt_data.ODNO + "+" + rt_data.ORD_TMD) # 주문접수조직번호+주문접수번호+주문시각
# [해외주식] 주문/계좌 > 정정취소주문 (해외거래소코드excg_cd + 종목코드itm_no + 주문번호orgn_odno + 정정취소구분rvse_cncl_dvsn_cd + 수량qty + 주문단가unpr)
# 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 kis_ovrseastk.py get_overseas_order 수정요망!
rt_data = kb.get_overseas_order_rvsecncl(excg_cd="NASD", itm_no="TSLA", orgn_odno="0030089601", rvse_cncl_dvsn_cd="02", qty=1, unpr=0)
print(rt_data) # 주문접수조직번호+주문접수번호+주문시각
# [해외주식] 주문/계좌 > 해외주식 미체결내역 (해외거래소코드)
# 해외거래소코드 NASD:나스닥,NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
rt_data = kb.get_overseas_inquire_nccs(excg_cd="NASD")
print(rt_data)
# [해외주식] 주문/계좌 > 해외주식 미체결전량취소주문 (해외거래소코드excg_cd + 종목코드itm_no)
# 해외거래소코드 NASD:나스닥,NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
rt_data = kb.get_overseas_order_allcncl(excg_cd="NASD", itm_no="")
print(rt_data)
# [해외주식] 주문/계좌 > 해외주식 잔고 현황
# 해외거래소코드 NASD:나스닥,NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
# 거래통화코드 - USD : 미국달러,HKD : 홍콩달러,CNY : 중국위안화,JPY : 일본엔화,VND : 베트남동
rt_data = kb.get_overseas_inquire_balance(excg_cd="NASD", crcy_cd="")
print(rt_data)
# [해외주식] 주문/계좌 > 해외주식 잔고 내역
# 해외거래소코드 NASD:나스닥,NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
# 거래통화코드 - USD : 미국달러,HKD : 홍콩달러,CNY : 중국위안화,JPY : 일본엔화,VND : 베트남동
rt_data = kb.get_overseas_inquire_balance(excg_cd="NASD", crcy_cd="")
print(rt_data)
# [해외주식] 주문/계좌 > 해외주식 주문체결내역
# 해외거래소코드 NASD:미국시장 전체(나스닥,뉴욕,아멕스),NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
rt_data = kb.get_overseas_inquire_ccnl(st_dt="", ed_dt="")
print(rt_data)
# [해외주식] 주문/계좌 > 해외주식 체결기준현재잔고
# dv : 01 보유종목, 02 외화잔고, 03 체결기준현재잔고
# dvsn : 01 원화, 02 외화
# natn 국가코드 : 000 전체,840 미국,344 홍콩,156 중국,392 일본,704 베트남
# mkt 거래시장코드 [Request body NATN_CD 000 설정]
# 00 : 전체 , (NATN_CD 840 인경우) 00:전체,01:나스닥(NASD),02:뉴욕거래소(NYSE),03:미국(PINK SHEETS),04:미국(OTCBB),05:아멕스(AMEX) (다른시장 API문서 참조)
rt_data = kb.get_overseas_inquire_present_balance(dv="02", dvsn="01", natn="000", mkt="00", inqr_dvsn="00")
print(rt_data)
# [해외주식] 주문/계좌 > 미국주간주문 (매수매도구분 buy,sell + 종목번호 + 주문수량 + 주문단가)
# 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 kis_ovrseastk.py get_overseas_order 수정요망!
#rt_data = kb.get_overseas_daytime_order(ord_dv="buy", excg_cd="NASD", itm_no="TSLA", qty=1, unpr=251)
#rt_data = kb.get_overseas_daytime_order(ord_dv="buy", excg_cd="NASD", itm_no="AAPL", qty=1, unpr=216.75)
rt_data = kb.get_overseas_daytime_order(ord_dv="buy", excg_cd="NASD", itm_no="NVDA", qty=1, unpr=123.3)
print(rt_data.KRX_FWDG_ORD_ORGNO + "+" + rt_data.ODNO + "+" + rt_data.ORD_TMD) # 주문접수조직번호+주문접수번호+주문시각
# [해외주식] 주문/계좌 > 미국주간정정취소 (해외거래소코드excg_cd + 종목코드itm_no + 주문번호orgn_odno + 정정취소구분rvse_cncl_dvsn_cd + 수량qty + 주문단가unpr)
# 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 kis_ovrseastk.py get_overseas_order 수정요망!
rt_data = kb.get_overseas_daytime_order_rvsecncl(excg_cd="NASD", itm_no="TSLA", orgn_odno="0030089601", rvse_cncl_dvsn_cd="02", qty=1, unpr=0)
print(rt_data) # 주문접수조직번호+주문접수번호+주문시각
# [해외주식] 주문/계좌 > 해외주식 기간손익[v1_해외주식-032] (해외거래소코드 + 통화코드 + 종목번호 6자리 + 조회시작일 + 조회종료일)
# 해외거래소코드 NASD:미국,SEHK:홍콩,SHAA:중국,TKSE:일본,HASE:베트남
rt_data = kb.get_overseas_inquire_period_profit(excg_cd="", crcy="", itm_no="", st_dt="20240601", ed_dt="20240709")
print(rt_data)
# [해외주식] 주문/계좌 > 해외주식 기간손익(매매일자종목별 기간손익) (해외거래소코드 + 통화코드 + 종목번호 6자리 + 조회시작일 + 조회종료일)
rt_data = kb.get_overseas_inquire_period_profit_output1(excg_cd="NASD", crcy="", itm_no="", st_dt="20240501", ed_dt="20240709")
print(rt_data)
# [해외주식] 주문/계좌 > 해외증거금 통화별조회
rt_data = kb.get_overseas_inquire_foreign_margin()
print(rt_data)
# [해외주식] 주문/계좌 > 해외증거금 일별거래내역 (해외거래소코드 + 매도매수구분코드 + 종목번호 6자리 + 조회시작일 + 조회종료일)
rt_data = kb.get_overseas_inquire_period_trans(excg_cd="", dvsn="", itm_no="", st_dt="20240601", ed_dt="20240709")
# [해외주식] 주문/계좌 > 해외증거금 일별거래내역[합계]
rt_data = kb.get_overseas_inquire_period_trans_output2(excg_cd="", dvsn="", itm_no="", st_dt="20240601", ed_dt="20240709")
print(rt_data)
# [해외주식] 주문/계좌 > 해외주식 결제기준현재잔고
# dv : 01 보유종목, 02 외화잔고, 03 결제기준현재잔고
# dt : 기준일자(YYYYMMDD)
# dvsn : 01 원화, 02 외화
# inqr_dvsn : 00(전체), 01(일반), 02(미니스탁)
rt_data = kb.get_overseas_inquire_paymt_stdr_balance(dv="03", dt="", dvsn="01", inqr_dvsn="00")
print(rt_data)
#====| [해외주식] 기본시세 |============================================================================================================================
# [해외주식] 기본시세 > 해외주식 현재체결가 (해외거래소코드, 종목번호)
rt_data = kb.get_overseas_price_quot_search_info(excd="NAS", itm_no="AAPL")
print(rt_data) # 해외주식 현재체결가
# [해외주식] 기본시세 > 해외주식 기간별시세
# ※ 기준일(bymd) 지정일자 이후 100일치 조회, 미입력시 당일자 기본 셋팅
rt_data = kb.get_overseas_price_quot_dailyprice(excd="NAS", itm_no="AAPL", gubn="0", bymd="")
print(rt_data) # 해외주식 기간별시세
# [해외주식] 기본시세 > 해외주식 종목/지수/환율기간별시세(일/주/월/년)
# ※ 기준일(bymd) 지정일자 이후 100일치 조회, 미입력시 당일자 기본 셋팅
rt_data = kb.get_overseas_price_quot_inquire_daily_price(div="N", itm_no="AAPL", inqr_strt_dt="", inqr_end_dt="", period="D")
rt_data = kb.get_overseas_price_quot_inquire_daily_chartprice(div="N", itm_no="AAPL", inqr_strt_dt="20240605", inqr_end_dt="20240610", period="D")
print(rt_data) # 해외주식 종목/지수/환율기간별시세(일/주/월/년)
# [해외주식] 기본시세 > 해외주식조건검색 div 01 : 검색결과종목수, 02:검색결과종목리스트
rt_data = kb.get_overseas_price_quot_inquire_search(div="02", excd="NAS", pr_st="160", pr_en="170")
print(rt_data) # 해외주식조건검색
# [해외주식] 기본시세 > 해외결재일자조회 (기준일자)
rt_data = kb.get_overseas_price_quot_countries_holiday(dt="")
print(rt_data) # 해외결재일자조회
# [해외주식] 기본시세 > 해외주식 현재가상세 (해외거래소시장코드, 종목코드)
rt_data = kb.get_overseas_price_quot_price_detail(excd="NAS", itm_no="AAPL")
print(rt_data) # 해외주식 현재가상세
# [해외주식] 기본시세 > 해외주식 해외주식분봉조회 (조회구분 div-02:분봉데이터,01:시장별장운영시간, 해외거래소시장코드, 종목코드, 분갭, 전일포함여부)
rt_data = kb.get_overseas_price_quot_inquire_time_itemchartprice(div="02", excd="NAS", itm_no="AAPL", nmin="", pinc="0")
print(rt_data) # 해외주식 해외주식분봉조회
# [해외주식] 기본시세 > 해외주식 해외지수분봉조회 (조회구분 div-02:분봉데이터,01:지수정보, 조건시장분류코드, 입력종목코드, 시간구분코드, 과거데이터포함여부)
rt_data = kb.get_overseas_price_quot_inquire_time_indexchartprice(div="02", code="N", iscd="SPX", tm_dv="0", inc="Y")
print(rt_data) # 해외주식 해외지수분봉조회
# [해외주식] 기본시세 > 해외주식 상품기본정보 (종목번호, 종목유형)
# 종목유형 : 512 미국 나스닥/513 미국 뉴욕/529 미국 아멕스/515 일본/501 홍콩/543 홍콩CNY/558 홍콩USD/507 베트남 하노이/508 베트남 호치민/551 중국 상해A/552 중국 심천A
rt_data = kb.get_overseas_price_search_info(itm_no="AAPL", prdt_type_cd="512")
print("종목코드("+rt_data.std_pdno+") 종목명(" +rt_data.prdt_eng_name+") 거래시장(" +rt_data.ovrs_excg_cd+":" +rt_data.tr_mket_name+")") # 해외주식 상품기본정보
print(rt_data) # 해외주식 상품기본정보
# [해외주식] 기본시세 > 해외주식 현재가 10호가 (조회구분 01:기본시세 02:10호가 , 해외거래소코드, 종목번호)
rt_data = kb.get_overseas_price_inquire_asking_price(div="02", excd="NAS", itm_no="AAPL")
print(rt_data) # 해외주식 상품기본정보

View File

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 15 07:56:54 2022
"""
#kis_api module 을 찾을 수 없다는 에러가 나는 경우 sys.path에 kis_api.py 가 있는 폴더를 추가해준다.
import kis_auth as ka
import kis_ovrseafuopt as kb
import pandas as pd
import sys
# 토큰 발급
ka.auth()
#====| [해외선물옵션] 주문/계좌 |============================================================================================================================
# [해외선물옵션] 주문/계좌 > 해외선물옵션주문 (종목번호<6자리 5자리> + 매수매도구분ord_dv + 가격구분dvsn + 주문수량qty + 주문가격limt_pric + 주문가격stop_pric)
# 매수매도구분ord_dv 01 : 매도, 02 : 매수 # 가격구분dvsn : 1.지정, 2. 시장, 3. STOP, 4 S/L
# 주문가격limt_pric : 지정가인 경우 가격 입력 * 시장가, STOP주문인 경우, 빈칸("") 입력
# 주문가격stop_pric : STOP 주문 가격 입력 * 시장가, 지정가인 경우, 빈칸("") 입력
rt_data = kb.get_overseasfuopt_order(itm_no="OESU24 C6000", ord_dv="02", dvsn="1", qty=1, limt_pric=13.75, stop_pric=0)
print(rt_data.ORD_DT + "+" + rt_data.ODNO) # 주문일자+주문접수번호
# [해외선물옵션] 주문/계좌 > 해외선물옵션 정정취소주문 (정정취소구분dvsn + 원주문일자ord_dt + 원주문번호orgn_odno + 주문가격limt_pric + 주문가격stop_pric + 청산주문가격lqd_limt_pric + 청산주문가격lqd_stop_pric)
rt_data = kb.get_overseasfuopt_order_rvsecncl(dvsn="01", ord_dt="", orgn_odno="", limt_pric=0, stop_pric=0, lqd_limt_pric=0, lqd_stop_pric=0)
print(rt_data.ORD_DT + "+" + rt_data.ODNO) # 주문일자+주문접수번호
# [해외선물옵션] 주문/계좌 > 해외선물옵션 정정취소주문 (정정취소구분dvsn + 원주문일자ord_dt + 원주문번호orgn_odno + 주문가격limt_pric + 주문가격stop_pric + 청산주문가격lqd_limt_pric + 청산주문가격lqd_stop_pric)
rt_data = kb.get_overseasfuopt_order_rvsecncl(dvsn="01", ord_dt="", orgn_odno="", limt_pric=0, stop_pric=0, lqd_limt_pric=0, lqd_stop_pric=0)
print(rt_data.ORD_DT + "+" + rt_data.ODNO) # 주문일자+주문접수번호
# [해외선물옵션] 주문/계좌 > 해외선물옵션 정정취소주문 (체결미체결구분ccld_dv + 매도매수구분코드ord_dv + 선물옵션구분fuop_dvsn)
# 체결미체결구분 01:전체,02:체결,03:미체결 # 매도매수구분코드 %%:전체,01:매도,02:매수 # 선물옵션구분 00:전체 / 01:선물 / 02:옵션
rt_data = kb.get_overseasfuopt_inquire_ccld(ccld_dv="01", ord_dv="%%", fuop_dvsn="00")
print(rt_data)
# [해외선물옵션] 주문/계좌 > 해외선물옵션 미결제내역조회(잔고) (선물옵션구분fuop_dvsn)
# 선물옵션구분 00:전체 / 01:선물 / 02:옵션
rt_data = kb.get_overseasfuopt_inquire_unpd(fuop_dvsn="00")
print(rt_data)
# [해외선물옵션] 주문/계좌 > 해외선물옵션 해외선물옵션 주문가능조회 (선물옵션구분fuop_dvsn)
# 선물옵션구분 00:전체 / 01:선물 / 02:옵션
rt_data = kb.get_overseasfuopt_inquire_psamount(itm_no="OESU24 C6000", dvsn="02", pric=0, ordyn="")
print(rt_data)
# [해외선물옵션] 주문/계좌 > 해외선물옵션 기간계좌손익 일별 (조회구분inqr_dvsn + 조회시작일자fr_dt + 조회종료일자to_dt + 통화코드crcy + 선물옵션구분fuop_dvsn")
# 조회구분코드 : 01 통화별, 02 종목별
rt_data = kb.get_overseasfuopt_inquire_period_ccld(inqr_dvsn="01", fr_dt="", to_dt="", crcy="%%%", fuop_dvsn="00")
print(rt_data)
# [해외선물옵션] 주문/계좌 > 해외선물옵션 일별 체결내역 (조회시작일자fr_dt + 조회종료일자to_dt + 선물옵션구분fuop_dvsn + 통화코드crcy + 매도매수구분코드dvsn)
rt_data = kb.get_overseasfuopt_inquire_daily_ccld(fr_dt="", to_dt="", fuop_dvsn="00", crcy="%%%", dvsn="%%")
print(rt_data)
# [해외선물옵션] 주문/계좌 > 해외선물옵션 예수금현황 (통화코드crcy + 조회일자inqr_dt)
rt_data = kb.get_overseasfuopt_inquire_deposit(crcy="%%%", inqr_dt="")
print(rt_data)
# [해외선물옵션] 주문/계좌 > 해외선물옵션 일별 주문내역 (조회시작일자fr_dt + 조회종료일자to_dt + 체결미체결구분ccld_dvsn + 매수매도구분dvsn + 선물옵션구분fuop_dvsn)
# ccld_dvsn 01:전체 / 02:체결 / 03:미체결 dvsn %% : 전체 / 01 : 매도 / 02 : 매수 fuop_dvsn 00:전체 / 01:선물 / 02:옵션
rt_data = kb.get_overseasfuopt_inquire_daily_order(fr_dt="20240401", to_dt="", ccld_dvsn="01", dvsn="%%", fuop_dvsn="00")
print(rt_data)
# [해외선물옵션] 주문/계좌 > 해외선물옵션 기간계좌거래내역 (조회시작일자fr_dt, 조회종료일자to_dt)
rt_data = kb.get_overseasfuopt_inquire_period_trans(fr_dt="20240101", to_dt="20240717")
print(rt_data)
# [해외선물옵션] 주문/계좌 > 해외선물옵션 증거금상세 (통화구분crcy + 조회일자inqr_dt)
rt_data = kb.get_overseasfuopt_inquire_margin_detail(crcy="TUS", inqr_dt="20240717")
print(rt_data)
#====| [해외선물옵션] 기본시세 |============================================================================================================================
# [해외선물옵션] 기본시세 > 해외선물종목상세 (종목코드)
rt_data = kb.get_overseas_fuopt_stock_detail(itm_no="6EU24")
print(rt_data) # 해외선물종목상세
# [해외선물옵션] 기본시세 > 해외선물종목현재가 (종목코드)
rt_data = kb.get_overseas_fuopt_inquire_price(itm_no="6EU24")
print(rt_data) # 해외선물종목현재가
# [해외선물옵션] 기본시세 > 분봉조회 (종목코드 + 거래소시장코드 + 조회시작일 + 조회종료일 + 조회건수(120건) + 갭(5분))
# 조회일자 1주일 이내 (조회시작일~조회종료일)
rt_data = kb.get_overseas_fuopt_inquire_time_futurechartprice(itm_no="6EU24", exch="CME", st_dt="20240709", ed_dt="20240710", cnt="120", gap="5", idx="")
print(rt_data) # 해외선물분봉조회
# [해외선물옵션] 기본시세 > 해외선물 체결추이(주간)[해외선물-017] (종목코드 + 거래소시장코드 + 조회시작일 + 조회종료일)
# 조회일자 1주일 이내 (조회시작일~조회종료일)
rt_data = kb.get_overseas_fuopt_weekly_ccnl(itm_no="6EU24", exch="CME", st_dt="20240709", ed_dt="20240710")
print(rt_data) # 체결추이(주간)
# [해외선물옵션] 기본시세 > 해외선물 체결추이(일간)[해외선물-018] (종목코드 + 거래소시장코드 + 조회시작일 + 조회종료일)
# 조회일자 1주일 이내 (조회시작일~조회종료일)
rt_data = kb.get_overseas_fuopt_daily_ccnl(itm_no="6EU24", exch="CME", st_dt="20240707", ed_dt="20240710")
print(rt_data) # 체결추이(일간)
# [해외선물옵션] 기본시세 > 해외선물 체결추이(틱)[해외선물-019] (종목코드 + 거래소시장코드)
# 조회일자는 당일만 조회하도록 샘플코드 구성
rt_data = kb.get_overseas_fuopt_tick_ccnl(itm_no="6EU24", exch="CME")
print(rt_data) # 체결추이(틱)
# [해외선물옵션] 기본시세 > 해외선물 체결추이(월간)[해외선물-020] (종목코드 + 거래소시장코드 + 조회시작일 + 조회종료일)
# 조회일자 1주일 이내 (조회시작일~조회종료일)
rt_data = kb.get_overseas_fuopt_monthly_ccnl(itm_no="6EU24", exch="CME", st_dt="20240707", ed_dt="20240710")
print(rt_data) # 체결추이(일간)
# [해외선물옵션] 기본시세 > 해외선물 호가 [해외선물-031] (종목코드)
rt_data = kb.get_overseas_fuopt_inquire_asking_price(itm_no="6EU24")
print(rt_data) # 해외선물 호가
# [해외선물옵션] 기본시세 > 해외선물 상품기본정보 [해외선물-023] (종목코드)
rt_data = kb.get_overseas_fuopt_search_contract_detail(itm_no01="6EU24")
print(rt_data) # 해외선물 상품기본정보
# [해외선물옵션] 기본시세 > 해외선물 장운영시간 [해외선물-030] (클래스코드 + 거래소코드 + 옵션여부)
rt_data = kb.get_overseas_fuopt_market_time(clas="", excg="CME", opt="%")
print(rt_data) # 해외선물 장운영시간
# [해외선물옵션] 기본시세 > 해외선물 미결제추이 [해외선물-029] (상품 + 거래소코드 + 옵션여부)
# 상품(PROD_ISCD) : 상품 (GE, ZB, ZF,ZN,ZT), 금속(GC, PA, PL,SI, HG),농산물(CC, CT,KC, OJ, SB, ZC,ZL, ZM, ZO, ZR, ZS, ZW),
# 에너지(CL, HO, NG, WBS), 지수(ES, NQ, TF, YM, VX), 축산물(GF, HE, LE), 통화(6A, 6B, 6C, 6E, 6J, 6N, 6S, DX)
# 100건씩 조회되면 다음 페이지 조회는 응답메시지 (bsop_date 일자 순으로) 마지막 응답 데이터 일자 +1일 일자 값셋팅(bsop_date)하여 조회
rt_data = kb.get_overseas_fuopt_investor_unpd_trend(iscd="CL", dt="20240612", kbn="0", ctskey="1")
print(rt_data) # 해외선물 장운영시간
# [해외선물옵션] 기본시세 > 해외옵션 호가 [해외선물-033] (조회구분 + 옵션종목코드)
# dv(조회구분) : 01 현재가, 02 호가
rt_data = kb.get_overseas_fuopt_opt_asking_price(dv="02", itm_no="OESU24 C6000")
print(rt_data) # 해외옵션 호가

View File

@@ -0,0 +1,355 @@
# -*- coding: utf-8 -*-
"""
Created on Wed Feb 15 16:57:19 2023
@author: Administrator
"""
#====| 토큰 발급에 필요한 API 호출 샘플 아래 참고하시기 바랍니다. |=====================
#====| 토큰 발급에 필요한 API 호출 샘플 아래 참고하시기 바랍니다. |=====================
#====| 토큰 발급에 필요한 API 호출 샘플 아래 참고하시기 바랍니다. |=====================
#====| API 호출 공통 함수 포함 |=====================
import time, copy
import yaml
import requests
import json
# 웹 소켓 모듈을 선언한다.
import asyncio
import os
import pandas as pd
from collections import namedtuple
from datetime import datetime
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import b64decode
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
key_bytes = 32
config_root = 'd:\\KIS\\config\\' # 토큰 파일이 저장될 폴더, 제3자가 찾지 어렵도록 경로 설정하시기 바랍니다.
#token_tmp = config_root + 'KIS000000' # 토큰 로컬저장시 파일 이름 지정, 파일이름을 토큰값이 유추가능한 파일명은 삼가바랍니다.
#token_tmp = config_root + 'KIS' + datetime.today().strftime("%Y%m%d%H%M%S") # 토큰 로컬저장시 파일명 년월일시분초
token_tmp = config_root + 'KIS' + datetime.today().strftime("%Y%m%d") # 토큰 로컬저장시 파일명 년월일
# 접근토큰 관리하는 파일 존재여부 체크, 없으면 생성
if os.path.exists(token_tmp) == False:
f = open(token_tmp, "w+")
# 앱키, 앱시크리트, 토큰, 계좌번호 등 저장관리, 자신만의 경로와 파일명으로 설정하시기 바랍니다.
# pip install PyYAML (패키지설치)
with open(config_root + 'kis_devlp.yaml', encoding='UTF-8') as f:
_cfg = yaml.load(f, Loader=yaml.FullLoader)
_TRENV = tuple()
_last_auth_time = datetime.now()
_autoReAuth = False
_DEBUG = False
_isPaper = False
# 기본 헤더값 정의
_base_headers = {
"Content-Type": "application/json",
"Accept": "text/plain",
"charset": "UTF-8",
'User-Agent': _cfg['my_agent']
}
# 토큰 발급 받아 저장 (토큰값, 토큰 유효시간,1일, 6시간 이내 발급신청시는 기존 토큰값과 동일, 발급시 알림톡 발송)
def save_token(my_token, my_expired):
valid_date = datetime.strptime(my_expired, '%Y-%m-%d %H:%M:%S')
# print('Save token date: ', valid_date)
with open(token_tmp, 'w', encoding='utf-8') as f:
f.write(f'token: {my_token}\n')
f.write(f'valid-date: {valid_date}\n')
# 토큰 확인 (토큰값, 토큰 유효시간_1일, 6시간 이내 발급신청시는 기존 토큰값과 동일, 발급시 알림톡 발송)
def read_token():
try:
# 토큰이 저장된 파일 읽기
with open(token_tmp, encoding='UTF-8') as f:
tkg_tmp = yaml.load(f, Loader=yaml.FullLoader)
# 토큰 만료 일,시간
exp_dt = datetime.strftime(tkg_tmp['valid-date'], '%Y-%m-%d %H:%M:%S')
# 현재일자,시간
now_dt = datetime.today().strftime("%Y-%m-%d %H:%M:%S")
# print('expire dt: ', exp_dt, ' vs now dt:', now_dt)
# 저장된 토큰 만료일자 체크 (만료일시 > 현재일시 인경우 보관 토큰 리턴)
if exp_dt > now_dt:
return tkg_tmp['token']
else:
# print('Need new token: ', tkg_tmp['valid-date'])
return None
except Exception as e:
# print('read token error: ', e)
return None
# 토큰 유효시간 체크해서 만료된 토큰이면 재발급처리
def _getBaseHeader():
if _autoReAuth: reAuth()
return copy.deepcopy(_base_headers)
# 가져오기 : 앱키, 앱시크리트, 종합계좌번호(계좌번호 중 숫자8자리), 계좌상품코드(계좌번호 중 숫자2자리), 토큰, 도메인
def _setTRENV(cfg):
nt1 = namedtuple('KISEnv', ['my_app', 'my_sec', 'my_acct', 'my_prod', 'my_token', 'my_url'])
d = {
'my_app': cfg['my_app'], # 앱키
'my_sec': cfg['my_sec'], # 앱시크리트
'my_acct': cfg['my_acct'], # 종합계좌번호(8자리)
'my_prod': cfg['my_prod'], # 계좌상품코드(2자리)
'my_token': cfg['my_token'], # 토큰
'my_url': cfg['my_url'] # 실전 도메인 (https://openapi.koreainvestment.com:9443)
} # 모의 도메인 (https://openapivts.koreainvestment.com:29443)
# print(cfg['my_app'])
global _TRENV
_TRENV = nt1(**d)
def isPaperTrading(): # 모의투자 매매
return _isPaper
# 실전투자면 'prod', 모의투자면 'vps'를 셋팅 하시기 바랍니다.
def changeTREnv(token_key, svr='prod', product=_cfg['my_prod']):
cfg = dict()
global _isPaper
if svr == 'prod': # 실전투자
ak1 = 'my_app' # 실전투자용 앱키
ak2 = 'my_sec' # 실전투자용 앱시크리트
_isPaper = False
elif svr == 'vps': # 모의투자
ak1 = 'paper_app' # 모의투자용 앱키
ak2 = 'paper_sec' # 모의투자용 앱시크리트
_isPaper = True
cfg['my_app'] = _cfg[ak1]
cfg['my_sec'] = _cfg[ak2]
if svr == 'prod' and product == '01': # 실전투자 주식투자, 위탁계좌, 투자계좌
cfg['my_acct'] = _cfg['my_acct_stock']
elif svr == 'prod' and product == '30': # 실전투자 증권저축계좌
cfg['my_acct'] = _cfg['my_acct_stock']
elif svr == 'prod' and product == '03': # 실전투자 선물옵션(파생)
cfg['my_acct'] = _cfg['my_acct_future']
elif svr == 'prod' and product == '08': # 실전투자 해외선물옵션(파생)
cfg['my_acct'] = _cfg['my_acct_future']
elif svr == 'vps' and product == '01': # 모의투자 주식투자, 위탁계좌, 투자계좌
cfg['my_acct'] = _cfg['my_paper_stock']
elif svr == 'vps' and product == '03': # 모의투자 선물옵션(파생)
cfg['my_acct'] = _cfg['my_paper_future']
cfg['my_prod'] = product
cfg['my_token'] = token_key
cfg['my_url'] = _cfg[svr]
# print(cfg)
_setTRENV(cfg)
def _getResultObject(json_data):
_tc_ = namedtuple('res', json_data.keys())
return _tc_(**json_data)
# Token 발급, 유효기간 1일, 6시간 이내 발급시 기존 token값 유지, 발급시 알림톡 무조건 발송
# 모의투자인 경우 svr='vps', 투자계좌(01)이 아닌경우 product='XX' 변경하세요 (계좌번호 뒤 2자리)
def auth(svr='prod', product=_cfg['my_prod'], url=None):
p = {
"grant_type": "client_credentials",
}
# 개인 환경파일 "kis_devlp.yaml" 파일을 참조하여 앱키, 앱시크리트 정보 가져오기
# 개인 환경파일명과 위치는 고객님만 아는 위치로 설정 바랍니다.
if svr == 'prod': # 실전투자
ak1 = 'my_app' # 앱키 (실전투자용)
ak2 = 'my_sec' # 앱시크리트 (실전투자용)
elif svr == 'vps': # 모의투자
ak1 = 'paper_app' # 앱키 (모의투자용)
ak2 = 'paper_sec' # 앱시크리트 (모의투자용)
# 앱키, 앱시크리트 가져오기
p["appkey"] = _cfg[ak1]
p["appsecret"] = _cfg[ak2]
# 기존 발급된 토큰이 있는지 확인
saved_token = read_token() # 기존 발급 토큰 확인
# print("saved_token: ", saved_token)
if saved_token is None: # 기존 발급 토큰 확인이 안되면 발급처리
url = f'{_cfg[svr]}/oauth2/tokenP'
res = requests.post(url, data=json.dumps(p), headers=_getBaseHeader()) # 토큰 발급
rescode = res.status_code
if rescode == 200: # 토큰 정상 발급
my_token = _getResultObject(res.json()).access_token # 토큰값 가져오기
my_expired= _getResultObject(res.json()).access_token_token_expired # 토큰값 만료일시 가져오기
save_token(my_token, my_expired) # 새로 발급 받은 토큰 저장
else:
print('Get Authentification token fail!\nYou have to restart your app!!!')
return
else:
my_token = saved_token # 기존 발급 토큰 확인되어 기존 토큰 사용
# 발급토큰 정보 포함해서 헤더값 저장 관리, API 호출시 필요
changeTREnv(f"Bearer {my_token}", svr, product)
_base_headers["authorization"] = _TRENV.my_token
_base_headers["appkey"] = _TRENV.my_app
_base_headers["appsecret"] = _TRENV.my_sec
global _last_auth_time
_last_auth_time = datetime.now()
if (_DEBUG):
print(f'[{_last_auth_time}] => get AUTH Key completed!')
# end of initialize, 토큰 재발급, 토큰 발급시 유효시간 1일
# 프로그램 실행시 _last_auth_time에 저장하여 유효시간 체크, 유효시간 만료시 토큰 발급 처리
def reAuth(svr='prod', product=_cfg['my_prod']):
n2 = datetime.now()
if (n2 - _last_auth_time).seconds >= 86400: # 유효시간 1일
auth(svr, product)
def getEnv():
return _cfg
def getTREnv():
return _TRENV
# 주문 API에서 사용할 hash key값을 받아 header에 설정해 주는 함수
# 현재는 hash key 필수 사항아님, 생략가능, API 호출과정에서 변조 우려를 하는 경우 사용
# Input: HTTP Header, HTTP post param
# Output: None
def set_order_hash_key(h, p):
url = f"{getTREnv().my_url}/uapi/hashkey" # hashkey 발급 API URL
res = requests.post(url, data=json.dumps(p), headers=h)
rescode = res.status_code
if rescode == 200:
h['hashkey'] = _getResultObject(res.json()).HASH
else:
print("Error:", rescode)
# API 호출 응답에 필요한 처리 공통 함수
class APIResp:
def __init__(self, resp):
self._rescode = resp.status_code
self._resp = resp
self._header = self._setHeader()
self._body = self._setBody()
self._err_code = self._body.msg_cd
self._err_message = self._body.msg1
def getResCode(self):
return self._rescode
def _setHeader(self):
fld = dict()
for x in self._resp.headers.keys():
if x.islower():
fld[x] = self._resp.headers.get(x)
_th_ = namedtuple('header', fld.keys())
return _th_(**fld)
def _setBody(self):
_tb_ = namedtuple('body', self._resp.json().keys())
return _tb_(**self._resp.json())
def getHeader(self):
return self._header
def getBody(self):
return self._body
def getResponse(self):
return self._resp
def isOK(self):
try:
if (self.getBody().rt_cd == '0'):
return True
else:
return False
except:
return False
def getErrorCode(self):
return self._err_code
def getErrorMessage(self):
return self._err_message
def printAll(self):
print("<Header>")
for x in self.getHeader()._fields:
print(f'\t-{x}: {getattr(self.getHeader(), x)}')
print("<Body>")
for x in self.getBody()._fields:
print(f'\t-{x}: {getattr(self.getBody(), x)}')
def printError(self, url):
print('-------------------------------\nError in response: ', self.getResCode(), ' url=', url)
print('rt_cd : ', self.getBody().rt_cd, '/ msg_cd : ',self.getErrorCode(), '/ msg1 : ',self.getErrorMessage())
print('-------------------------------')
# end of class APIResp
########### API call wrapping : API 호출 공통
def _url_fetch(api_url, ptr_id, tr_cont, params, appendHeaders=None, postFlag=False, hashFlag=True):
url = f"{getTREnv().my_url}{api_url}"
headers = _getBaseHeader() # 기본 header 값 정리
# 추가 Header 설정
tr_id = ptr_id
if ptr_id[0] in ('T', 'J', 'C'): # 실전투자용 TR id 체크
if isPaperTrading(): # 모의투자용 TR id 식별
tr_id = 'V' + ptr_id[1:]
headers["tr_id"] = tr_id # 트랜젝션 TR id
headers["custtype"] = "P" # 일반(개인고객,법인고객) "P", 제휴사 "B"
headers["tr_cont"] = tr_cont # 트랜젝션 TR id
if appendHeaders is not None:
if len(appendHeaders) > 0:
for x in appendHeaders.keys():
headers[x] = appendHeaders.get(x)
if (_DEBUG):
print("< Sending Info >")
print(f"URL: {url}, TR: {tr_id}")
print(f"<header>\n{headers}")
print(f"<body>\n{params}")
if (postFlag):
#if (hashFlag): set_order_hash_key(headers, params)
res = requests.post(url, headers=headers, data=json.dumps(params))
else:
res = requests.get(url, headers=headers, params=params)
if res.status_code == 200:
ar = APIResp(res)
if (_DEBUG): ar.printAll()
return ar
else:
print("Error Code : " + str(res.status_code) + " | " + res.text)
return None

View File

@@ -0,0 +1,67 @@
#====| 사용자 환경 샘플 아래 참고하시기 바랍니다. |======================
#====| 본 샘플은 토큰 발급 후 파일 저장 방식이므로 보안강화를 위해 메모리 방식 등 사용자 원하시는 방식으로 구현하시기 바랍니다. |=====
#====| kis_auth.py에서 환경파일 위치를 사용자가 정하시기 바랍니다. . 2024.05.16 KIS Developers Team |======================
#====| kis_auth.py에서 환경파일 위치를 사용자가 정하시기 바랍니다. . 2024.05.16 KIS Developers Team |======================
#====| kis_auth.py에서 환경파일 위치를 사용자가 정하시기 바랍니다. . 2024.05.16 KIS Developers Team |======================
#
############################### kis_auth.py ##################################################
# clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
#
# key_bytes = 32
#
# config_root = 'd:\\KIS\\config\\' # 토큰 파일이 저장될 폴더, 제3자가 찾지 어렵도록 경로 설정하시기 바랍니다. <<<==== 파일 위치 지정
# #token_tmp = config_root + 'KIS000000' # 토큰 로컬저장시 파일 이름 지정, 파일이름을 토큰값이 유추가능한 파일명은 삼가바랍니다. <<<==== 토큰 저장 파일 방식
# #token_tmp = config_root + 'KIS' + datetime.today().strftime("%Y%m%d%H%M%S") # 토큰 로컬저장시 파일명 년월일시분초
# token_tmp = config_root + 'KIS' + datetime.today().strftime("%Y%m%d") # 토큰 로컬저장시 파일명 년월일
#
# # 접근토큰 관리하는 파일 존재여부 체크, 없으면 생성
# if os.path.exists(token_tmp) == False:
# f = open(token_tmp, "w+")
#
# # 앱키, 앱시크리트, 토큰, 계좌번호 등 저장관리, 자신만의 경로와 파일명으로 설정하시기 바랍니다.
# # pip install PyYAML (패키지설치)
# with open(config_root + 'kis_devlp.yaml', encoding='UTF-8') as f:
# _cfg = yaml.load(f, Loader=yaml.FullLoader)
#
# _TRENV = tuple()
# _last_auth_time = datetime.now()
# _autoReAuth = False
# _DEBUG = False
# _isPaper = False
#
# 기본 헤더값 정의
# _base_headers = {
# "Content-Type": "application/json",
# "Accept": "text/plain",
# "charset": "UTF-8",
# 'User-Agent': _cfg['my_agent']
# }
######################################################################################################
#홈페이지에서 API서비스 신청시 받은 Appkey, Appsecret 값 설정
#모의투자
#my_app: "앱키"
#my_sec: "앱시크리트"
#실전투자
my_app: "앱키"
my_sec: "앱시크리트"
#계좌번호 앞 8자리
my_acct: "계좌번호 8자리"
my_acct_stock: "증권계좌 8자리"
my_acct_future: "선물옵션계좌 8자리"
#계좌번호 뒤 2자리
my_prod: "01"
#my_prod: "03"
#실전투자
prod: "https://openapi.koreainvestment.com:9443"
#모의투자
vps: "https://openapivts.koreainvestment.com:29443"
#디스코드 웹훅 URL
DISCORD_WEBHOOK_URL: ""
my_agent : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"

View File

@@ -0,0 +1,366 @@
# -*- coding: utf-8 -*-
"""
Created on Wed Feb 15 16:57:19 2023
@author: Administrator
"""
import kis_auth as kis
import time, copy
import requests
import json
import pandas as pd
from collections import namedtuple
from datetime import datetime
from pandas import DataFrame
#====| [국내선물옵션] 주문/계좌 |===========================================================================================================================
##############################################################################################
# [국내선물옵션] 주문/계좌 > 선물옵션 주문[v1_국내선물-001]
##############################################################################################
# Input: None (Option) 상세 Input값 변경이 필요한 경우 API문서 참조
# Output: DataFrame (Option) output API 문서 참조 등
def get_domfuopt_order(dv_cd="02", sll_buy_dvsn_cd="", dvsn_cd="01", itm_no="", qty=0, unpr=0, tr_cont="", FK100="", NK100="", dataframe=None): # [국내선물옵션] 주문/계좌 > 선물옵션 주문
url = '/uapi/domestic-futureoption/v1/trading/order'
if dv_cd == "01":
tr_id = "TTTO1101U" # 선물 옵션 매수 매도 주문 주간 [모의투자] VTTO1101U : 선물 옵션 매수 매도 주문 주간
elif dv_cd == "02":
tr_id = "JTCE1001U" # 선물 옵션 매수 매도 주문 야간 [모의투자] VTCE1001U : 선물 옵션 매수 매도 주문 야간
else:
print("선물옵션매수매도주문주간/선물옵션매수매도주문야간 구분 확인요망!!!")
return None
if sll_buy_dvsn_cd == "":
print("매도매수구분코드 확인요망!!!")
return None
if itm_no == "":
print("주문종목번호 확인요망!!!")
return None
if qty == 0:
print("주문수량 확인요망!!!")
return None
#if unpr == 0:
# print("주문단가 확인요망!!!")
# return None
params = {
"ORD_PRCS_DVSN_CD": "02", # 주문처리구분코드 02 : 주문전송
"CANO": kis.getTREnv().my_acct, # 종합계좌번호 8자리
"ACNT_PRDT_CD": kis.getTREnv().my_prod, # 계좌상품코드 2자리
"SLL_BUY_DVSN_CD": sll_buy_dvsn_cd, # 매수매도구분코드
"SHTN_PDNO": itm_no, # 종목코드(단축상품번호 6자리) 선물 6자리 (예: 101S03) 옵션 9자리 (예: 201S03370)
"ORD_QTY": str(int(qty)), # 주문수량
"UNIT_PRICE": str(int(unpr)), # 주문가격
"NMPR_TYPE_CD": "", # 호가유형코드 ※ ORD_DVSN_CD(주문구분코드)를 입력한 경우 ""(공란)으로 입력해도 됨
"KRX_NMPR_CNDT_CD": "", # 한국거래소호가조건코드 ※ ORD_DVSN_CD(주문구분코드)를 입력한 경우 ""(공란)으로 입력해도 됨
"CTAC_TLNO": "", # 연락전화번호
"FUOP_ITEM_DVSN_CD": "", # 선물옵션종목구분코드
"ORD_DVSN_CD": dvsn_cd # 주문구분코드 01 : 지정가 02 : 시장가 03 : 조건부 04 : 최유리 10 : 지정가(IOC) .....
}
res = kis._url_fetch(url, tr_id, tr_cont, params, postFlag=True)
if str(res.getBody().rt_cd) == "0":
current_data = pd.DataFrame(res.getBody().output, index=[0])
dataframe = current_data
else:
print(res.getBody().msg_cd + "," + res.getBody().msg1)
dataframe = None
return dataframe
##############################################################################################
# [국내선물옵션] 주문/계좌 > 선물옵션 정정취소주문[v1_국내선물-002]
##############################################################################################
# Input: None (Option) 상세 Input값 변경이 필요한 경우 API문서 참조
# Output: DataFrame (Option) output API 문서 참조 등
def get_domfuopt_order_rvsecncl(dv_cd="01", rvse_cncl_dvsn_cd="", orgn_odno="", ord_dvsn="01", ord_qty=0,
ord_unpr=0, rmn_qty_yn="", tr_cont="", dataframe=None): # [국내선물옵션] 선물옵션 정정취소주문[v1_국내선물-002]
url = '/uapi/domestic-futureoption/v1/trading/order-rvsecncl'
if dv_cd == "01":
tr_id = "TTTO1103U" # 선물 옵션 정정 취소 주문 주간 [모의투자] VTTO1103U : 선물 옵션 정정 취소 주문 주간
elif dv_cd == "02":
tr_id = "JTCE1002U" # 선물 옵션 정정 취소 주문 야간 [모의투자] VTCE1002U : 선물 옵션 정정 취소 주문 야간
else:
print("선물옵션정정취소주문주간/선물옵션정정취소주문야간 구분 확인요망!!!")
return None
if not rvse_cncl_dvsn_cd in ["01","02"]:
print("정정취소구분코드 확인요망!!!") # 정정:01. 취소:02
return None
if orgn_odno == "":
print("원주문번호 확인요망!!!")
return None
if ord_dvsn == "":
print("주문구분 확인요망!!!")
return None
if rmn_qty_yn == "Y" and ord_qty > 0:
print("잔량전부 취소/정정주문인 경우 주문수량 0 처리!!!")
ord_qty = 0
if rmn_qty_yn == "N" and ord_qty == 0:
print("취소/정정 수량 확인요망!!!")
return None
if rvse_cncl_dvsn_cd == "01" and ord_unpr == 0:
print("주문단가 확인요망!!!")
return None
params = {
"ORD_PRCS_DVSN_CD": "02", # 주문처리구분코드 02 : 주문전송
"CANO": kis.getTREnv().my_acct, # 종합계좌번호 8자리
"ACNT_PRDT_CD": kis.getTREnv().my_prod, # 계좌상품코드 2자리
"RVSE_CNCL_DVSN_CD": rvse_cncl_dvsn_cd, # 정정취소구분코드 01 : 정정 02 : 취소
"ORGN_ODNO": orgn_odno, # 원주문번호 정정 혹은 취소할 주문의 번호
"ORD_QTY": str(int(ord_qty)), # 주문수량 전량일 경우 0으로 입력, 일부수량 정정 및 취소 불가, 주문수량 반드시 입력 (공백 불가) 일부 미체결 시 잔량 전체에 대해서 취소 가능
"UNIT_PRICE": str(int(ord_unpr)), # 주문가격 시장가나 최유리의 경우 0으로 입력 (취소 시에도 0 입력)
"NMPR_TYPE_CD": "", # 호가유형코드 ※ ORD_DVSN_CD(주문구분코드)를 입력한 경우 ""(공란)으로 입력해도 됨
"KRX_NMPR_CNDT_CD": "", # 한국거래소호가조건코드 ※ ORD_DVSN_CD(주문구분코드)를 입력한 경우 ""(공란)으로 입력해도 됨
"RMN_QTY_YN": rmn_qty_yn, # 잔여수량여부 Y : 전량 N : 일부
"FUOP_ITEM_DVSN_CD": "", # 선물옵션종목구분코드 (주간) 공란 (야간) 01:선물 02:콜옵션 03:풋옵션 04:스프레드
"ORD_DVSN_CD": ord_dvsn # 주문구분코드 (취소) 01, (정정) 01 : 지정가 02 : 시장가 03 : 조건부 04 : 최유리 10 : 지정가(IOC) .....
}
res = kis._url_fetch(url, tr_id, tr_cont, params, postFlag=True)
if str(res.getBody().rt_cd) == "0":
current_data = pd.DataFrame(res.getBody().output, index=[0])
dataframe = current_data
else:
print(res.getBody().msg_cd + "," + res.getBody().msg1)
#print(res.getErrorCode() + "," + res.getErrorMessage())
dataframe = None
return dataframe
##############################################################################################
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 주문체결 내역조회 [국내선물-009]
##############################################################################################
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 주문체결 내역조회 [국내선물-009] Object를 DataFrame 으로 반환
# Input: None (Option) 상세 Input값 변경이 필요한 경우 API문서 참조
# (JTCE5005R)
# Output: DataFrame (Option) output2 API 문서 참조 등
def get_domfuopt_inquire_ngt_ccnl_obj(inqr_strt_dt=None, inqr_end_dt=None, sll_buy_dvsn_cd="00", tr_cont="", FK100="", NK100="", dataframe=None):
url = '/uapi/domestic-futureoption/v1/trading/inquire-ngt-ccnl'
tr_id = "JTCE5005R"
if inqr_strt_dt is None:
inqr_strt_dt = datetime.today().strftime("%Y%m%d") # 시작일자 값이 없으면 현재일자
if inqr_end_dt is None:
inqr_end_dt = datetime.today().strftime("%Y%m%d") # 종료일자 값이 없으면 현재일자
params = {
"CANO": kis.getTREnv().my_acct, # 종합계좌번호 8자리
"ACNT_PRDT_CD": kis.getTREnv().my_prod, # 계좌상품코드 2자리
"STRT_ORD_DT": inqr_strt_dt, # 시작주문일자
"END_ORD_DT": inqr_end_dt, # 종료주문일자
"SLL_BUY_DVSN_CD": sll_buy_dvsn_cd, # 매도매수구분코드 00:전체 01:매도, 02:매수
"CCLD_NCCS_DVSN": "01", # 체결미체결구분
"SORT_SQN": "", # 정렬순서 DS : 정순, 그외 : 역순
"STRT_ODNO": "", # 시작주문번호 00:전체, 01:체결, 02:미체결
"PDNO": "", # 상품번호 (종목번호)
"MKET_ID_CD": "", # 시장ID코드
"FUOP_DVSN_CD": "", # 선물옵션구분코드 공란 : 전체, 01 : 선물, 02 : 옵션
"SCRN_DVSN": "02", # 화면구분 02 default
"CTX_AREA_FK200": FK100, # 공란 : 최초 조회시 이전 조회 Output CTX_AREA_FK100 값 : 다음페이지 조회시(2번째부터)
"CTX_AREA_NK200": NK100 # 공란 : 최초 조회시 이전 조회 Output CTX_AREA_NK100 값 : 다음페이지 조회시(2번째부터)
}
res = kis._url_fetch(url, tr_id, tr_cont, params)
# print(res.getBody())
if res.isOK():
# API 응답의 output 속성이 스칼라 값인지 확인
output_data = res.getBody().output2
if not isinstance(output_data, list):
# 스칼라 값이면 리스트로 감싸서 반환
output_data = [output_data]
# DataFrame 생성 시 index 매개변수를 추가하여 스칼라 값일 경우 처리
current_data = pd.DataFrame(output_data, index=[0])
return current_data
else:
res.printError()
return pd.DataFrame()
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 주문체결 내역조회 [국내선물-009] List를 DataFrame 으로 반환
# Input: None (Option) 상세 Input값 변경이 필요한 경우 API문서 참조
# (JTCE5005R)
# Output: DataFrame (Option) output1 API 문서 참조 등
def get_domfuopt_inquire_ngt_ccnl_lst(inqr_strt_dt=None, inqr_end_dt=None, sll_buy_dvsn_cd="00", tr_cont="", FK100="", NK100="", dataframe=None):
url = '/uapi/domestic-futureoption/v1/trading/inquire-ngt-ccnl'
tr_id = "JTCE5005R"
if inqr_strt_dt == "":
inqr_strt_dt = datetime.today().strftime("%Y%m%d") # 시작일자 값이 없으면 현재일자
if inqr_end_dt == "":
inqr_end_dt = datetime.today().strftime("%Y%m%d") # 종료일자 값이 없으면 현재일자
params = {
"CANO": kis.getTREnv().my_acct, # 종합계좌번호 8자리
"ACNT_PRDT_CD": kis.getTREnv().my_prod, # 계좌상품코드 2자리
"STRT_ORD_DT": inqr_strt_dt, # 시작주문일자
"END_ORD_DT": inqr_end_dt, # 종료주문일자
"SLL_BUY_DVSN_CD": sll_buy_dvsn_cd, # 매도매수구분코드 00:전체 01:매도, 02:매수
"CCLD_NCCS_DVSN": "00", # 체결미체결구분
"SORT_SQN": "DS", # 정렬순서 DS : 정순, 그외 : 역순
"STRT_ODNO": "", # 시작주문번호 00:전체, 01:체결, 02:미체결
"PDNO": "", # 상품번호 (종목번호)
"MKET_ID_CD": "", # 시장ID코드
"FUOP_DVSN_CD": "", # 선물옵션구분코드 공란 : 전체, 01 : 선물, 02 : 옵션
"SCRN_DVSN": "02", # 화면구분 02 default
"CTX_AREA_FK200": FK100, # 공란 : 최초 조회시 이전 조회 Output CTX_AREA_FK100 값 : 다음페이지 조회시(2번째부터)
"CTX_AREA_NK200": NK100 # 공란 : 최초 조회시 이전 조회 Output CTX_AREA_NK100 값 : 다음페이지 조회시(2번째부터)
}
res = kis._url_fetch(url, tr_id, tr_cont, params) # API 호출, kis_auth.py에 존재
# Assuming 'output1' is a dictionary that you want to convert to a DataFrame
current_data = pd.DataFrame(res.getBody().output1)
# Append to the existing DataFrame if it exists
if dataframe is not None:
dataframe = pd.concat([dataframe, current_data], ignore_index=True) #
else:
dataframe = current_data
tr_cont, FK100, NK100 = res.getHeader().tr_cont, res.getBody().ctx_area_fk200, res.getBody().ctx_area_nk200 # 페이징 처리 getHeader(), getBody() kis_auth.py 존재
# print(dv, tr_cont, FK100, NK100)
if tr_cont == "D" or tr_cont == "E": # 마지막 페이지
print("The End")
return dataframe
elif tr_cont == "F" or tr_cont == "M": # 다음 페이지 존재하는 경우 자기 호출 처리
print('Call Next')
time.sleep(0.1) # 시스템 안정적 운영을 위하여 반드시 지연 time 필요
tr_cont = "N" # 다음조회
return get_domfuopt_inquire_ngt_ccnl_lst(inqr_strt_dt, inqr_end_dt, sll_buy_dvsn_cd, "N", FK100, NK100, dataframe)
##############################################################################################
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 잔고현황 [국내선물-010]
##############################################################################################
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 잔고현황 Object를 DataFrame 으로 반환
# Input: None (Option) 상세 Input값 변경이 필요한 경우 API문서 참조
# Output: DataFrame (Option) output2 -
def get_domfuopt_inquire_ngt_balance_obj(tr_cont="", FK100="", NK100="", dataframe=None):
url = '/uapi/domestic-futureoption/v1/trading/inquire-ngt-balance'
tr_id = "JTCE6001R"
params = {
"CANO": kis.getTREnv().my_acct, # 종합계좌번호 8자리
"ACNT_PRDT_CD": kis.getTREnv().my_prod, # 계좌상품코드 2자리
"MGNA_DVSN": "01", # 증거금구분 01 : 개시, 02 : 유지
"EXCC_STAT_CD": "1", # 정산상태코드 1 : 정산 (정산가격으로 잔고 조회) 2 : 본정산 (매입가격으로 잔고 조회)
"CTX_AREA_FK200": FK100, # 공란 : 최초 조회시 이전 조회 Output CTX_AREA_FK100 값 : 다음페이지 조회시(2번째부터)
"CTX_AREA_NK200": NK100 # 공란 : 최초 조회시 이전 조회 Output CTX_AREA_NK100 값 : 다음페이지 조회시(2번째부터)
}
res = kis._url_fetch(url, tr_id, tr_cont, params)
# print(res.getBody())
if res.isOK():
# API 응답의 output 속성이 스칼라 값인지 확인
output_data = res.getBody().output2
if not isinstance(output_data, list):
# 스칼라 값이면 리스트로 감싸서 반환
output_data = [output_data]
# DataFrame 생성 시 index 매개변수를 추가하여 스칼라 값일 경우 처리
current_data = pd.DataFrame(output_data, index=[0])
return current_data
else:
res.printError()
return pd.DataFrame()
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 잔고현황 종목별 List를 DataFrame 으로 반환
# Input: None (Option) 상세 Input값 변경이 필요한 경우 API문서 참조
# Output: DataFrame (Option) output2 - 종목번호, 상품명(종목명), 매매구분명(매수매도구분), 전일매수수량 ... 등
def get_domfuopt_inquire_ngt_balance_lst(tr_cont="", FK100="", NK100="", dataframe=None): # 국내주식주문 > 주식잔고조회(현재종목별 잔고)
url = '/uapi/domestic-futureoption/v1/trading/inquire-ngt-balance'
tr_id = "JTCE6001R"
params = {
"CANO": kis.getTREnv().my_acct, # 종합계좌번호 8자리
"ACNT_PRDT_CD": kis.getTREnv().my_prod, # 계좌상품코드 2자리
"MGNA_DVSN": "01", # 증거금구분 01 : 개시, 02 : 유지
"EXCC_STAT_CD": "1", # 정산상태코드 1 : 정산 (정산가격으로 잔고 조회) 2 : 본정산 (매입가격으로 잔고 조회)
"CTX_AREA_FK200": FK100, # 공란 : 최초 조회시 이전 조회 Output CTX_AREA_FK100 값 : 다음페이지 조회시(2번째부터)
"CTX_AREA_NK200": NK100 # 공란 : 최초 조회시 이전 조회 Output CTX_AREA_NK100 값 : 다음페이지 조회시(2번째부터)
}
res = kis._url_fetch(url, tr_id, tr_cont, params) # API 호출, kis_auth.py에 존재
print(res.getBody())
# Assuming 'output1' is a dictionary that you want to convert to a DataFrame
current_data = pd.DataFrame(res.getBody().output1)
# Append to the existing DataFrame if it exists
if dataframe is not None:
dataframe = pd.concat([dataframe, current_data], ignore_index=True) #
else:
dataframe = current_data
tr_cont, FK100, NK100 = res.getHeader().tr_cont, res.getBody().ctx_area_fk200, res.getBody().ctx_area_nk200 # 페이징 처리 getHeader(), getBody() kis_auth.py 존재
if tr_cont == "D" or tr_cont == "E": # 마지막 페이지
print("The End")
return dataframe
elif tr_cont == "F" or tr_cont == "M": # 다음 페이지 존재하는 경우 자기 호출 처리
print('Call Next')
time.sleep(0.1) # 시스템 안정적 운영을 위하여 반드시 지연 time 필요
return get_domfuopt_inquire_ngt_balance_lst("N", FK100, NK100, dataframe)
##############################################################################################
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 주문가능 조회 [국내선물-011]
##############################################################################################
# [국내선물옵션] 주문/계좌 > (야간)선물옵션 주문가능 조회 [국내선물-011] List를 DataFrame 으로 반환
# Input: None (Option) 상세 Input값 변경이 필요한 경우 API문서 참조
# Output: DataFrame (Option) output2 -
def get_domfuopt_inquire_psbl_ngt_order(pdno="", sll_buy_dvsn_cd="", ord_unpr=0, ord_dvsn="01", tr_cont="", FK100="", NK100="", dataframe=None): # 국내주식주문 > 매수가능조회
url = '/uapi/domestic-futureoption/v1/trading/inquire-psbl-ngt-order'
tr_id = "JTCE1004R"
params = {
"CANO": kis.getTREnv().my_acct, # 종합계좌번호 8자리
"ACNT_PRDT_CD": kis.getTREnv().my_prod, # 계좌상품코드 2자리
"PDNO": pdno, # 상품번호
"PRDT_TYPE_CD": "301", # 상품유형코드 301 : 선물옵션
"SLL_BUY_DVSN_CD": sll_buy_dvsn_cd, # 매도매수구분코드 01 : 매도 , 02 : 매수
"UNIT_PRICE": ord_unpr, # 주문가격
"ORD_DVSN_CD": ord_dvsn # 주문구분코드 01 : 지정가 02 : 시장가 03 : 조건부 04 : 최유리 10 : 지정가(IOC) .....
}
res = kis._url_fetch(url, tr_id, tr_cont, params) # API 호출, kis_auth.py에 존재
print(res.getBody())
if res.isOK():
# API 응답의 output 속성이 스칼라 값인지 확인
output_data = res.getBody().output
if not isinstance(output_data, list):
# 스칼라 값이면 리스트로 감싸서 반환
output_data = [output_data]
# DataFrame 생성 시 index 매개변수를 추가하여 스칼라 값일 경우 처리
current_data = pd.DataFrame(output_data, index=[0])
return current_data
else:
res.printError()
return pd.DataFrame()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,487 @@
# 국내주식 실시간 websocket sample
import websocket
import kis_auth as ka
import kis_domstk as kb
import os
import json
import requests
import pandas as pd
import numpy as np
import time
import datetime
from io import StringIO
from threading import Thread
from collections import namedtuple, deque
try:
import websockets
except ImportError:
print("websocket-client 설치중입니다.")
os.system('python3 -m pip3 install websocket-client')
from enum import StrEnum
class KIS_WSReq(StrEnum):
BID_ASK = 'H0STASP0' # 실시간 국내주식 호가
CONTRACT = 'H0STCNT0' # 실시간 국내주식 체결
NOTICE = 'H0STCNI0' # 실시간 계좌체결발생통보
import talib as ta
class BasicPlan:
def __init__(self, stock_code, window=20):
self._stock_code = stock_code
self._queue = deque(maxlen=window)
self._prev_ma = None
def push(self, value):
self._queue.append(value)
ma = sum(self._queue) / len(self._queue)
diff = ma - self._prev_ma if self._prev_ma is not None else None
self._prev_ma = ma
print(f"{self._stock_code}****** value: {value}, MA: {ma}, diff: {diff}...")
class RSI_ST: # RSI(Relative Strength Index, 상대강도지수)라는 주가 지표 계산
def __init__(self, stock_code, window=21):
self._stock_code = stock_code
self._queue = deque(maxlen=window)
self.rsi_period = window
def eval(self):
# dftt = getStreamdDF(self._stock_code)
# print(self)
dftt = contract_sub_df.get(self._stock_code).copy()
dftt = dftt.set_index(['TICK_HOUR'])
dftt['STCK_PRPR'] = pd.to_numeric(dftt['STCK_PRPR'], errors='coerce').convert_dtypes()
np_closes = np.array(dftt['STCK_PRPR'], dtype=np.float64)
rsi = ta.RSI(np_closes, self.rsi_period)
last_rsi = rsi[-1]
if last_rsi < 30:
print(f"({self._stock_code})[BUY] ***RSI: {last_rsi}") # 통상적으로 RSI가 30 이하면 과매도 상태인 것으로 판단하고 시장이 과도하게 하락했음을 나타냄
elif last_rsi < 70 and last_rsi >= 30:
print(f"({self._stock_code})[N/A] ***RSI: {last_rsi}")
elif last_rsi >= 70:
print(f"({self._stock_code})[SELL] ***RSI: {last_rsi}") # 통상적으로 RSI가 70 이상이면 과매수 상태로 간주하고 시장이 과열되었을 가능성이 있음을 나타냄
else:
print(self._stock_code)
_today__ = datetime.date.today().strftime("%Y%m%d")
ka.auth()
__DEBUG__ = False # True
# 실시간 국내주식 계좌체결통보 복호화를 위한 부분-start
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import b64decode
# AES256 DECODE: Copied from KIS Developers Github sample code
def aes_cbc_base64_dec(key, iv, cipher_text):
"""
:param key: str type AES256 secret key value
:param iv: str type AES256 Initialize Vector
:param cipher_text: Base64 encoded AES256 str
:return: Base64-AES256 decodec str
"""
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
# 실시간 국내주식 계좌체결통보 복호화를 위한 부분 - end
contract_sub_df = dict() # 실시간 국내주식 체결 결과를 종목별로 저장하기 위한 container
tr_plans = dict() # 실시간 국내주식 체결 값에 따라 무언가를 수행할 Class 를 저장하기 위한 container
reserved_cols = ['TICK_HOUR', 'STCK_PRPR', 'ACML_VOL'] # 실시간 국내주식 체결 중 사용할 column 만 추출하기 위한 column 정의
# 실시간 국내주식체결 column header
contract_cols = ['MKSC_SHRN_ISCD',
'TICK_HOUR', # pandas time conversion 편의를 위해 이 필드만 이름을 통일한다
'STCK_PRPR', # 현재가
'PRDY_VRSS_SIGN', # 전일 대비 부호
'PRDY_VRSS', # 전일 대비
'PRDY_CTRT', # 전일 대비율
'WGHN_AVRG_STCK_PRC', # 가중 평균 주식 가격
'STCK_OPRC', # 시가
'STCK_HGPR', # 고가
'STCK_LWPR', # 저가
'ASKP1', # 매도호가1
'BIDP1', # 매수호가1
'CNTG_VOL', # 체결 거래량
'ACML_VOL', # 누적 거래량
'ACML_TR_PBMN', # 누적 거래 대금
'SELN_CNTG_CSNU', # 매도 체결 건수
'SHNU_CNTG_CSNU', # 매수 체결 건수
'NTBY_CNTG_CSNU', # 순매수 체결 건수
'CTTR', # 체결강도
'SELN_CNTG_SMTN', # 총 매도 수량
'SHNU_CNTG_SMTN', # 총 매수 수량
'CCLD_DVSN', # 체결구분 (1:매수(+), 3:장전, 5:매도(-))
'SHNU_RATE', # 매수비율
'PRDY_VOL_VRSS_ACML_VOL_RATE', # 전일 거래량 대비 등락율
'OPRC_HOUR', # 시가 시간
'OPRC_VRSS_PRPR_SIGN', # 시가대비구분
'OPRC_VRSS_PRPR', # 시가대비
'HGPR_HOUR',
'HGPR_VRSS_PRPR_SIGN',
'HGPR_VRSS_PRPR',
'LWPR_HOUR',
'LWPR_VRSS_PRPR_SIGN',
'LWPR_VRSS_PRPR',
'BSOP_DATE', # 영업 일자
'NEW_MKOP_CLS_CODE', # 신 장운영 구분 코드
'TRHT_YN',
'ASKP_RSQN1',
'BIDP_RSQN1',
'TOTAL_ASKP_RSQN',
'TOTAL_BIDP_RSQN',
'VOL_TNRT', # 거래량 회전율
'PRDY_SMNS_HOUR_ACML_VOL', # 전일 동시간 누적 거래량
'PRDY_SMNS_HOUR_ACML_VOL_RATE', # 전일 동시간 누적 거래량 비율
'HOUR_CLS_CODE', # 시간 구분 코드(0 : 장중 )
'MRKT_TRTM_CLS_CODE',
'VI_STND_PRC']
# 실시간 국내주식호가 column eader
bid_ask_cols = ['MKSC_SHRN_ISCD',
'TICK_HOUR', # pandas time conversion 편의를 위해 이 필드만 이름을 통일한다
'HOUR_CLS_CODE', # 시간 구분 코드(0 : 장중 )
'ASKP1', # 매도호가1
'ASKP2',
'ASKP3',
'ASKP4',
'ASKP5',
'ASKP6',
'ASKP7',
'ASKP8',
'ASKP9',
'ASKP10',
'BIDP1', # 매수호가1
'BIDP2',
'BIDP3',
'BIDP4',
'BIDP5',
'BIDP6',
'BIDP7',
'BIDP8',
'BIDP9',
'BIDP10',
'ASKP_RSQN1', # 매도호가 잔량1
'ASKP_RSQN2',
'ASKP_RSQN3',
'ASKP_RSQN4',
'ASKP_RSQN5',
'ASKP_RSQN6',
'ASKP_RSQN7',
'ASKP_RSQN8',
'ASKP_RSQN9',
'ASKP_RSQN10',
'BIDP_RSQN1', # 매수호가 잔량1
'BIDP_RSQN2',
'BIDP_RSQN3',
'BIDP_RSQN4',
'BIDP_RSQN5',
'BIDP_RSQN6',
'BIDP_RSQN7',
'BIDP_RSQN8',
'BIDP_RSQN9',
'BIDP_RSQN10',
'TOTAL_ASKP_RSQN', # 총 매도호가 잔량
'TOTAL_BIDP_RSQN', # 총 매수호가 잔량
'OVTM_TOTAL_ASKP_RSQN',
'OVTM_TOTAL_BIDP_RSQN',
'ANTC_CNPR',
'ANTC_CNQN',
'ANTC_VOL',
'ANTC_CNTG_VRSS',
'ANTC_CNTG_VRSS_SIGN',
'ANTC_CNTG_PRDY_CTRT',
'ACML_VOL', # 누적 거래량
'TOTAL_ASKP_RSQN_ICDC',
'TOTAL_BIDP_RSQN_ICDC',
'OVTM_TOTAL_ASKP_ICDC',
'OVTM_TOTAL_BIDP_ICDC',
'STCK_DEAL_CLS_CODE']
# 실시간 계좌체결발생통보 column header
notice_cols = ['CUST_ID', # HTS ID
'ACNT_NO',
'ODER_NO', # 주문번호
'OODER_NO', # 원주문번호
'SELN_BYOV_CLS', # 매도매수구분
'RCTF_CLS', # 정정구분
'ODER_KIND', # 주문종류(00 : 지정가,01 : 시장가,02 : 조건부지정가)
'ODER_COND', # 주문조건
'STCK_SHRN_ISCD', # 주식 단축 종목코드
'CNTG_QTY', # 체결 수량(체결통보(CNTG_YN=2): 체결 수량, 주문·정정·취소·거부 접수 통보(CNTG_YN=1): 주문수량의미)
'CNTG_UNPR', # 체결단가
'STCK_CNTG_HOUR', # 주식 체결 시간
'RFUS_YN', # 거부여부(0 : 승인, 1 : 거부)
'CNTG_YN', # 체결여부(1 : 주문,정정,취소,거부,, 2 : 체결 (★ 체결만 볼 경우 2번만 ))
'ACPT_YN', # 접수여부(1 : 주문접수, 2 : 확인 )
'BRNC_NO', # 지점
'ODER_QTY', # 주문수량
'ACNT_NAME', # 계좌명
'ORD_COND_PRC', # 호가조건가격 (스톱지정가 시 표시)
'ORD_EXG_GB', # 주문거래소 구분 (1:KRX, 2:NXT, 3:SOR-KRX, 4:SOR-NXT)
'POPUP_YN', # 실시간체결창 표시여부 (Y/N)
'FILLER', # 필러
'CRDT_CLS', # 신용구분
'CRDT_LOAN_DATE', # 신용대출일자
'CNTG_ISNM40', # 체결종목명40
'ODER_PRC' # 주문가격
]
# 웹소켓 접속키 발급
def get_approval():
url = ka.getTREnv().my_url
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": ka.getTREnv().my_app,
"secretkey": ka.getTREnv().my_sec}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
_connect_key = get_approval() # websocker 연결Key
_iv = None # for 복호화
_ekey = None # for 복호화
executed_df = pd.DataFrame(data=None, columns=contract_cols) # 체결통보 저장용 DF
# added_data 는 종목코드(실시간체결, 실시간호가) 또는 HTS_ID(체결통보)
def _build_message(app_key, tr_id, added_data, tr_type='1'):
_h = {
"approval_key": app_key,
"custtype": 'P',
"tr_type": tr_type,
"content-type": "utf-8"
}
_inp = {
"tr_id": tr_id,
"tr_key": added_data
}
_b = {
"input": _inp
}
_data = {
"header": _h,
"body": _b
}
d1 = json.dumps(_data)
return d1
# sub_data 는 종목코드(실시간체결, 실시간호가) 또는 HTS_ID(실시간 계좌체결발생통보)
def subscribe(ws, sub_type, app_key, sub_data): # 세션 종목코드(실시간체결, 실시간호가) 등록
ws.send(_build_message(app_key, sub_type, sub_data), websocket.ABNF.OPCODE_TEXT)
time.sleep(.1)
def unsubscribe(ws, sub_type, app_key, sub_data): # 세션 종목코드(실시간체결, 실시간호가) 등록해제
ws.send(_build_message(app_key, sub_type, sub_data, '2'), websocket.ABNF.OPCODE_TEXT)
time.sleep(.1)
# streaming data 를 이용해 주어진 bar 크기(예: 1분, 5분 등)의 OHLC(x분봉) 데이터프레임을 반환한다.
# 이때 streamign data 는 websocket client 가 시작한 다음부터 지금까지의 해당 종목의 가격 정보를 의미한다.
# ** 동시호가 시간은 OHLC data 가 모두 NA 가 된다.
def getStreamdDF(stock_code, bar_sz='1Min'):
df3 = contract_sub_df.get(stock_code).copy()
df3 = df3.set_index(['TICK_HOUR'])
df3['STCK_PRPR'] = pd.to_numeric(df3['STCK_PRPR'], errors='coerce').convert_dtypes()
df3 = df3['STCK_PRPR'].resample(bar_sz).ohlc()
return df3
# 수신데이터 파싱
def _dparse(data):
global executed_df
d1 = data.split("|")
dp_ = None
hcols = []
if len(d1) >= 4:
tr_id = d1[1]
if tr_id == KIS_WSReq.CONTRACT: # 실시간체결
hcols = contract_cols
elif tr_id == KIS_WSReq.BID_ASK: # 실시간호가
hcols = bid_ask_cols
elif tr_id == KIS_WSReq.NOTICE: # 계좌체결통보
hcols = notice_cols
else:
pass
if tr_id in (KIS_WSReq.CONTRACT, KIS_WSReq.BID_ASK): # 실시간체결, 실시간호가
dp_ = pd.read_csv(StringIO(d1[3]), header=None, sep='^', names=hcols, dtype=object) # 수신데이터 parsing
print(dp_) # 실시간체결, 실시간호가 수신 데이터 파싱 결과 확인
dp_['TICK_HOUR'] = _today__ + dp_['TICK_HOUR'] # 수신시간
dp_['TICK_HOUR'] = pd.to_datetime(dp_['TICK_HOUR'], format='%Y%m%d%H%M%S', errors='coerce')
else: # 실시간 계좌체결발생통보는 암호화되어서 수신되므로 복호화 과정이 필요
dp_ = pd.read_csv(StringIO(aes_cbc_base64_dec(_ekey, _iv, d1[3])), header=None, sep='^', names=hcols, # 수신데이터 parsing 및 복호화
dtype=object)
print(dp_) # 실시간 계좌체결발생통보 수신 파싱 결과 확인
if __DEBUG__: print(f'***EXECUTED NOTICE [{dp_.to_string(header=False, index=False)}]')
if tr_id == KIS_WSReq.CONTRACT: # 실시간 체결
if __DEBUG__: print(dp_.to_string(header=False, index=False))
stock_code = dp_[dp_.columns[0]].values.tolist()[0]
df2_ = dp_[reserved_cols]
# dft_ = pd.concat([contract_sub_df.get(stock_code), df2_], axis=0, ignore_index=True)
# 선택된 열이 비어 있거나 모든 값이 NA인지 확인
selected_df = contract_sub_df.get(stock_code)
if selected_df is not None and not selected_df.dropna().empty:
dft_ = pd.concat([selected_df, df2_], axis=0, ignore_index=True)
else:
dft_ = df2_
contract_sub_df[stock_code] = dft_
######### 이 부분에서 로직을 적용한 후 매수/매도를 수행하면 될 듯!!
val1 = dp_['STCK_PRPR'].tolist()[0]
tr_plans[stock_code].push(int(val1)) # 이동평균값 활용
# tr_plans[stock_code].eval() # RSI(Relative Strength Index, 상대강도지수)라는 주가 지표 계산 활용
# [국내주식] 주문/계좌 > 매수가능조회 (종목번호 5자리 + 종목단가) REST API
rt_data = kb.get_inquire_psbl_order(pdno=stock_code, ord_unpr=val1)
ord_qty = rt_data.loc[0, 'nrcvb_buy_qty'] # nrcvb_buy_qty 미수없는매수수량
print("[미수없는매수주문가능수량!] : " + ord_qty)
# 국내주식 현금 주문
# rt_data = kb.get_order_cash(ord_dv="buy",itm_no=stock_code, qty=ord_qty, unpr=val1)
# print(rt_data.KRX_FWDG_ORD_ORGNO + "+" + rt_data.ODNO + "+" + rt_data.ORD_TMD) # 주문접수조직번호+주문접수번호+주문시각
print("매수/매도 조건 주문 : " + val1)
#########################################################
elif tr_id == KIS_WSReq.NOTICE: # 체결통보의 경우, 일단 executed_df 에만 저장해 둠
if __DEBUG__: print(dp_.to_string(header=False, index=False))
executed_df = pd.concat([executed_df, dp_], axis=0, ignore_index=True)
else:
pass
else:
print("Data length error...{data}")
def _get_sys_resp(data):
global _iv
global _ekey
isPingPong = False
isUnSub = False
isOk = False
tr_msg = None
tr_key = None
rdic = json.loads(data)
tr_id = rdic['header']['tr_id']
if tr_id != "PINGPONG": tr_key = rdic['header']['tr_key']
if rdic.get("body", None) is not None:
isOk = True if rdic["body"]["rt_cd"] == "0" else False
tr_msg = rdic["body"]["msg1"]
# 복호화를 위한 key 를 추출
if 'output' in rdic["body"]:
_iv = rdic["body"]["output"]["iv"]
_ekey = rdic["body"]["output"]["key"]
isUnSub = True if tr_msg[:5] == "UNSUB" else False
else:
isPingPong = True if tr_id == "PINGPONG" else False
nt2 = namedtuple('SysMsg', ['isOk', 'tr_id', 'tr_key', 'isUnSub', 'isPingPong'])
d = {
'isOk': isOk,
'tr_id': tr_id,
'tr_key': tr_key,
'isUnSub': isUnSub,
'isPingPong': isPingPong
}
return nt2(**d)
def on_data(ws, data, resp_type, data_continu):
# print(f"On data => {resp_type}, {data_continu}, {data}") #return only 1, True
pass
def on_message(ws, data):
if data[0] in ('0', '1'): # 실시간체결 or 실시간호가
_dparse(data)
else: # system message or PINGPONG
rsp = _get_sys_resp(data)
if rsp.isPingPong:
ws.send(data, websocket.ABNF.OPCODE_PING)
else:
if (not rsp.isUnSub and rsp.tr_id == KIS_WSReq.CONTRACT):
contract_sub_df[rsp.tr_key] = pd.DataFrame(columns=reserved_cols)
########################################################################
#### 이 부분에서 전략을 수행할 class 를 등록한다.
#### 실제 주문 실행은 _dparse 함수에서 처리
tr_plans[rsp.tr_key] = BasicPlan(rsp.tr_key) # 이동 평균선 계산 (웹소켓 프로그램 실행시 수집된 데이터만 반영)
# tr_plans[rsp.tr_key] = RSI_ST(rsp.tr_key) # RSI(Relative Strength Index, 상대강도지수)라는 주가 지표 계산
########################################################################
elif (rsp.isUnSub):
del (contract_sub_df[rsp.tr_key])
else:
print(rsp)
def on_error(ws, error):
print('error=', error)
def on_close(ws, status_code, close_msg):
print('on_close close_status_code=', status_code, " close_msg=", close_msg)
def on_open(ws):
# stocks 에는 40개까지만 가능
stocks = ('009540', '012630', '052300', '089860', '218410', '330590', '357550', '419080', '348370')
for scode in stocks:
subscribe(ws, KIS_WSReq.BID_ASK, _connect_key, scode) # 실시간 호가
subscribe(ws, KIS_WSReq.CONTRACT, _connect_key, scode) # 실시간 체결
# unsubscribe(ws, KIS_WSReq.CONTRACT, _connect_key, "005930") #실시간 체결 해제
# subscribe(ws, KIS_WSReq.BID_ASK, _connect_key, "005930") #실시간 호가
# 실시간 계좌체결발생통보를 등록한다. 계좌체결발생통보 결과는 executed_df 에 저장된다.
subscribe(ws, KIS_WSReq.NOTICE, _connect_key, "HTS ID 입력") # HTS ID 입력
ws = websocket.WebSocketApp("ws://ops.koreainvestment.com:21000/tryitout",
on_open=on_open, on_message=on_message, on_error=on_error, on_data=on_data)
ws.run_forever() # 실시간 웹소켓 연결 작동

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,508 @@
# 국내주식 실시간 websocket sample
import websocket
import kis_auth as ka
import kis_ovrseafuopt as kb
import os
import json
import requests
import pandas as pd
import numpy as np
import time
import datetime
from io import StringIO
from threading import Thread
from collections import namedtuple, deque
try:
import websockets
except ImportError:
print("websocket-client 설치중입니다.")
os.system('python3 -m pip3 install websocket-client')
from enum import StrEnum
class KIS_WSReq(StrEnum):
CONTRACT = 'HDFFF020' # 해외선물옵션 실시간체결가
BID_ASK = 'HDFFF010' # 해외선물옵션 실시간호가
ORDERNOTICE = 'HDFFF1C0' # 실시간 해외선물옵션 주문내역발생통보
CCLDNOTICE = 'HDFFF2C0' # 실시간 해외선물옵션 체결내역발생통보
import talib as ta
class BasicPlan:
def __init__(self, stock_code, window=20):
self._stock_code = stock_code
self._queue = deque(maxlen=window)
self._prev_ma = None
def push(self, value):
self._queue.append(value)
ma = sum(self._queue) / len(self._queue)
diff = ma - self._prev_ma if self._prev_ma is not None else None
self._prev_ma = ma
print(f"{self._stock_code}****** value: {value}, MA: {ma}, diff: {diff}...")
class RSI_ST: # RSI(Relative Strength Index, 상대강도지수)라는 주가 지표 계산
def __init__(self, stock_code, window=21):
self._stock_code = stock_code
self._queue = deque(maxlen=window)
self.rsi_period = window
def eval(self):
# dftt = getStreamdDF(self._stock_code)
# print(self)
dftt = contract_sub_df.get(self._stock_code).copy()
dftt = dftt.set_index(['TICK_HOUR'])
dftt['LAST_PRICE'] = pd.to_numeric(dftt['LAST_PRICE'], errors='coerce').convert_dtypes()
np_closes = np.array(dftt['LAST_PRICE'], dtype=np.float64)
rsi = ta.RSI(np_closes, self.rsi_period)
last_rsi = rsi[-1]
if last_rsi < 30:
print(f"({self._stock_code})[BUY] ***RSI: {last_rsi}") # 통상적으로 RSI가 30 이하면 과매도 상태인 것으로 판단하고 시장이 과도하게 하락했음을 나타냄
elif last_rsi < 70 and last_rsi >= 30:
print(f"({self._stock_code})[N/A] ***RSI: {last_rsi}")
elif last_rsi >= 70:
print(f"({self._stock_code})[SELL] ***RSI: {last_rsi}") # 통상적으로 RSI가 70 이상이면 과매수 상태로 간주하고 시장이 과열되었을 가능성이 있음을 나타냄
else:
print(self._stock_code)
_today__ = datetime.date.today().strftime("%Y%m%d")
ka.auth()
__DEBUG__ = False # True
# 실시간 해외주식 계좌체결통보 복호화를 위한 부분 - start
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import b64decode
# AES256 DECODE: Copied from KIS Developers Github sample code
def aes_cbc_base64_dec(key, iv, cipher_text):
"""
:param key: str type AES256 secret key value
:param iv: str type AES256 Initialize Vector
:param cipher_text: Base64 encoded AES256 str
:return: Base64-AES256 decodec str
"""
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
# 실시간 해외주식 계좌체결통보 복호화를 위한 부분 - end
contract_sub_df = dict() # 실시간 해외주식 체결 결과를 종목별로 저장하기 위한 container
tr_plans = dict() # 실시간 해외주식 체결 값에 따라 무언가를 수행할 Class 를 저장하기 위한 container
excg_dict = {
'NYS' : 'NYSE', #미국뉴욕
'NAS' : 'NASD', #미국나스닥
'AMS' : 'AMEX', #미국아멕스
'TSE' : 'TKSE', #일본도쿄
'HKS' : 'SEHK', #홍콩
'SHS' : 'SHAA', #중국상해
'SZS' : 'SZAA', #중국심천
'HSX' : 'VNSE', #베트남호치민,
'HNX' : 'HASE', #베트남하노이
'BAY' : 'NYSE', #미국뉴욕(주간)
'BAQ' : 'NASD', #미국나스닥(주간),
'BAA' : 'AMEX' #미국아멕스(주간)
}
#reserved_cols = ['TICK_HOUR', 'STCK_PRPR', 'ACML_VOL'] # 실시간 해외주식 체결 중 사용할 수신시간, 현재가, 누적거래량 만 추출하기 위한 column 정의
reserved_cols = ['TICK_HOUR', 'LAST_PRICE', 'VOL'] # 실시간 해외선물옵션 체결 중 사용할 column 만 추출하기 위한 column 정의
# 해외선물옵션 실시간체결가 column header
contract_cols = ['SERIES_CD', # 종목코드 * 각 항목사이에는 구분자로 ^ 사용, 모든 데이터타입은 STRING으로 변환되어 PUSH 처리됨'
'BSNS_DATE', # 영업일자
'MRKT_OPEN_DATE', # 장개시일자
'MRKT_OPEN_TIME', # 장개시시각
'MRKT_CLOSE_DATE', # 장종료일자
'MRKT_CLOSE_TIME', # 장종료시각
'PREV_PRICE', # 전일종가 ※ 전일종가, 체결가격, 전일대비가, 시가, 고가, 저가 ※ FFCODE.MST(해외선물종목마스터 파일)의 SCALCDESZ(계산 소수점) 값 참고
'RECV_DATE', # 수신일자
'TICK_HOUR', # 수신시각 ※ 수신시각(RECV_TIME) = 실제 체결시각 ★ pandas time conversion 편의를 위해 이 필드만 이름을 통일한다 'KHMS' 한국시간
'ACTIVE_FLAG', # 본장_전산장구분
'LAST_PRICE', # 체결가격
'LAST_QNTT', # 체결수량
'PREV_DIFF_PRICE', # 전일대비가
'PREV_DIFF_RATE', # 등락률
'OPEN_PRICE', # 시가
'HIGH_PRICE', # 고가
'LOW_PRICE', # 저가
'VOL', # 누적거래량
'PREV_SIGN', # 전일대비부호
'QUOTSIGN', # 체결구분 ※ 2:매수체결 5:매도체결
'RECV_TIME2', # 수신시각2 만분의일초
'PSTTL_PRICE', # 전일정산가
'PSTTL_SIGN', # 전일정산가대비
'PSTTL_DIFF_PRICE', # 전일정산가대비가격
'PSTTL_DIFF_RATE'] # 전일정산가대비율
# 실시간 해외선물옵션호가 column eader
bid_ask_cols = ['SERIES_CD', # 종목코드 '각 항목사이에는 구분자로 ^ 사용,모든 데이터타입은 STRING으로 변환되어 PUSH 처리됨'
'RECV_DATE', # 수신일자
'TICK_HOUR', # 수신시각 ※ 수신시각(RECV_TIME) = 실제 체결시각 ★ pandas time conversion 편의를 위해 이 필드만 이름을 통일한다 'KHMS' 한국시간
'PREV_PRICE', # 전일종가 ※ 전일종가, 매수1호가~매도5호가 ※ FFCODE.MST(해외선물종목마스터 파일)의 SCALCDESZ(계산 소수점) 값 참고
'BID_QNTT_1', # 매수1수량
'BID_NUM_1', # 매수1번호
'BID_PRICE_1', # 매수1호가
'ASK_QNTT_1', # 매도1수량
'ASK_NUM_1', # 매도1번호
'ASK_PRICE_1', # 매도1호가
'BID_QNTT_2', # 매수2수량
'BID_NUM_2', # 매수2번호
'BID_PRICE_2', # 매수2호가
'ASK_QNTT_2', # 매도2수량
'ASK_NUM_2', # 매도2번호
'ASK_PRICE_2', # 매도2호가
'BID_QNTT_3', # 매수3수량
'BID_NUM_3', # 매수3번호
'BID_PRICE_3', # 매수3호가
'ASK_QNTT_3', # 매도3수량
'ASK_NUM_3', # 매도3번호
'ASK_PRICE_3', # 매도3호가
'BID_QNTT_4', # 매수4수량
'BID_NUM_4', # 매수4번호
'BID_PRICE_4', # 매수4호가
'ASK_QNTT_4', # 매도4수량
'ASK_NUM_4', # 매도4번호
'ASK_PRICE_4', # 매도4호가
'BID_QNTT_5', # 매수5수량
'BID_NUM_5', # 매수5번호
'BID_PRICE_5', # 매수5호가
'ASK_QNTT_5', # 매도5수량
'ASK_NUM_5', # 매도5번호
'ASK_PRICE_5', # 매도5호가
'STTL_PRICE'] # 전일정산가
# 실시간 계좌주문내역발생통보 column header
ordernotice_cols = ['USER_ID', # 유저ID 각 항목사이에는 구분자로 ^ 사용, 모든 데이터타입은 STRING으로 변환되어 PUSH 처리됨'
'ACCT_NO', # 계좌번호
'ORD_DT', # 주문일자
'ODNO', # 주문번호
'ORGN_ORD_DT', # 원주문일자
'ORGN_ODNO', # 원주문번호
'SERIES', # 종목명
'RVSE_CNCL_DVSN_CD',# 정정취소구분코드 해당없음 : 00 , 정정 : 01 , 취소 : 02
'SLL_BUY_DVSN_CD', # 매도매수구분코드 01 : 매도, 02 : 매수
'CPLX_ORD_DVSN_CD', # 복합주문구분코드 0 (HEDGE청산만 이용)
'PRCE_TP', # 가격구분코드', # 1:LIMIT, 2:MARKET, 3:STOP(STOP가격시 시장가)
'FM_EXCG_RCIT_DVSN_CD', # FM거래소접수구분코드 01:접수전, 02:응답, 03:거부
'ORD_QTY', # 주문수량
'FM_LMT_PRIC', # FMLIMIT가격
'FM_STOP_ORD_PRIC', # FMSTOP주문가격
'TOT_CCLD_QTY', # 총체결수량
'TOT_CCLD_UV', # 총체결단가
'ORD_REMQ', # 잔량
'FM_ORD_GRP_DT', # FM주문그룹일자 주문일자(ORD_DT)와 동일
'ORD_GRP_STNO', # 주문그룹번호
'ORD_DTL_DTIME', # 주문상세일시
'OPRT_DTL_DTIME', # 조작상세일시
'WORK_EMPL', # 주문자
'CRCY_CD', # 통화코드
'LQD_YN', # 청산여부(Y/N)
'LQD_LMT_PRIC', # 청산LIMIT가격
'LQD_STOP_PRIC', # 청산STOP가격
'TRD_COND', # 체결조건코드
'TERM_ORD_VALD_DTIME', # 기간주문유효상세일시
'SPEC_TP', # 계좌청산유형구분코드
'ECIS_RSVN_ORD_YN', # 행사예약주문여부
'FUOP_ITEM_DVSN_CD',# 선물옵션종목구분코드
'AUTO_ORD_DVSN_CD'] # 자동주문 전략구분
# 실시간 계좌체결내역발생통보 column header
ccldnotice_cols = ['USER_ID', # 유저ID '각 항목사이에는 구분자로 ^ 사용, 모든 데이터타입은 STRING으로 변환되어 PUSH 처리됨'
'ACCT_NO', # 계좌번호
'ORD_DT', # 주문일자
'ODNO', # 주문번호
'ORGN_ORD_DT', # 원주문일자
'ORGN_ODNO', # 원주문번호
'SERIES', # 종목명
'RVSE_CNCL_DVSN_CD',# 정정취소구분코드', # 해당없음 : 00 , 정정 : 01 , 취소 : 02
'SLL_BUY_DVSN_CD', # 매도매수구분코드', # 01 : 매도, 02 : 매수
'CPLX_ORD_DVSN_CD', # 복합주문구분코드', # 0 (HEDGE청산만 이용)
'PRCE_TP', # 가격구분코드
'FM_EXCG_RCIT_DVSN_CD', # FM거래소접수구분코드
'ORD_QTY', # 주문수량
'FM_LMT_PRIC', # FMLIMIT가격
'FM_STOP_ORD_PRIC', # FMSTOP주문가격
'TOT_CCLD_QTY', # 총체결수량', # 동일한 주문건에 대한 누적된 체결수량 (하나의 주문건에 여러건의 체결내역 발생)
'TOT_CCLD_UV', # 총체결단가
'ORD_REMQ', # 잔량
'FM_ORD_GRP_DT', # FM주문그룹일자
'ORD_GRP_STNO', # 주문그룹번호
'ORD_DTL_DTIME', # 주문상세일시
'OPRT_DTL_DTIME', # 조작상세일시
'WORK_EMPL', # 주문자
'CCLD_DT', # 체결일자
'CCNO', # 체결번호
'API_CCNO', # API 체결번호
'CCLD_QTY', # 체결수량', # 매 체결 단위 체결수량임 (여러건 체결내역 누적 체결수량인 총체결수량과 다름)
'FM_CCLD_PRIC', # FM체결가격
'CRCY_CD', # 통화코드
'TRST_FEE', # 위탁수수료
'ORD_MDIA_ONLINE_YN', # 주문매체온라인여부
'FM_CCLD_AMT', # FM체결금액
'FUOP_ITEM_DVSN_CD']# 선물옵션종목구분코드
# 웹소켓 접속키 발급
def get_approval():
url = ka.getTREnv().my_url
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": ka.getTREnv().my_app,
"secretkey": ka.getTREnv().my_sec}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
_connect_key = get_approval() # websocker 연결Key
_iv = None # for 복호화
_ekey = None # for 복호화
executed_df = pd.DataFrame(data=None, columns=contract_cols) # 체결통보 저장용 DF
# added_data 는 종목코드(실시간체결, 실시간호가) 또는 HTS_ID(체결통보)
def _build_message(app_key, tr_id, added_data, tr_type='1'):
_h = {
"approval_key": app_key,
"custtype": 'P',
"tr_type": tr_type,
"content-type": "utf-8"
}
_inp = {
"tr_id": tr_id,
"tr_key": added_data
}
_b = {
"input": _inp
}
_data = {
"header": _h,
"body": _b
}
d1 = json.dumps(_data)
return d1
# sub_data 는 종목코드(실시간체결, 실시간호가) 또는 HTS_ID(실시간 계좌체결발생통보)
def subscribe(ws, sub_type, app_key, sub_data): # 세션 종목코드(실시간체결, 실시간호가) 등록
ws.send(_build_message(app_key, sub_type, sub_data), websocket.ABNF.OPCODE_TEXT)
time.sleep(.1)
def unsubscribe(ws, sub_type, app_key, sub_data): # 세션 종목코드(실시간체결, 실시간호가) 등록해제
ws.send(_build_message(app_key, sub_type, sub_data, '2'), websocket.ABNF.OPCODE_TEXT)
time.sleep(.1)
# streaming data 를 이용해 주어진 bar 크기(예: 1분, 5분 등)의 OHLC(x분봉) 데이터프레임을 반환한다.
# 이때 streamign data 는 websocket client 가 시작한 다음부터 지금까지의 해당 종목의 가격 정보를 의미한다.
# ** 동시호가 시간은 OHLC data 가 모두 NA 가 된다.
def getStreamdDF(stock_code, bar_sz='1Min'):
df3 = contract_sub_df.get(stock_code).copy()
df3 = df3.set_index(['TICK_HOUR'])
df3['LAST_PRICE'] = pd.to_numeric(df3['LAST_PRICE'], errors='coerce').convert_dtypes()
df3 = df3['LAST_PRICE'].resample(bar_sz).ohlc()
return df3
# 수신데이터 파싱
def _dparse(data):
global executed_df
d1 = data.split("|")
dp_ = None
hcols = []
if len(d1) >= 4:
tr_id = d1[1]
if tr_id == KIS_WSReq.CONTRACT: # 실시간체결
hcols = contract_cols
elif tr_id == KIS_WSReq.BID_ASK: # 해외선물옵션 실시간호가
hcols = bid_ask_cols
elif tr_id == KIS_WSReq.ORDERNOTICE: # 주문내역발생통보
hcols = ordernotice_cols
elif tr_id == KIS_WSReq.CCLDNOTICE: # 체결내역발생통보
hcols = ccldnotice_cols
else:
pass
if tr_id in (KIS_WSReq.CONTRACT, KIS_WSReq.BID_ASK): # 실시간체결, 실시간호가
dp_ = pd.read_csv(StringIO(d1[3]), header=None, sep='^', names=hcols, dtype=object) # 수신데이터 parsing
print(dp_) # 실시간체결, 실시간호가 수신 데이터 파싱 결과 확인
dp_['TICK_HOUR'] = _today__ + dp_['TICK_HOUR'] # 수신시간
dp_['TICK_HOUR'] = pd.to_datetime(dp_['TICK_HOUR'], format='%Y%m%d%H%M%S', errors='coerce')
else: # 실시간 계좌체결발생통보는 암호화되어서 수신되므로 복호화 과정이 필요
dp_ = pd.read_csv(StringIO(aes_cbc_base64_dec(_ekey, _iv, d1[3])), header=None, sep='^', names=hcols, # 수신데이터 parsing 및 복호화
dtype=object)
print(dp_) # 실시간 계좌체결발생통보 수신 파싱 결과 확인
if __DEBUG__: print(f'***EXECUTED CCLDNOTICE [{dp_.to_string(header=False, index=False)}]')
if tr_id == KIS_WSReq.CONTRACT: # 실시간 체결
if __DEBUG__: print(dp_.to_string(header=False, index=False))
stock_code = dp_[dp_.columns[0]].values.tolist()[0]
df2_ = dp_[reserved_cols]
# dft_ = pd.concat([contract_sub_df.get(stock_code), df2_], axis=0, ignore_index=True)
# 선택된 열이 비어 있거나 모든 값이 NA인지 확인
selected_df = contract_sub_df.get(stock_code)
if selected_df is not None and not selected_df.dropna().empty:
dft_ = pd.concat([selected_df, df2_], axis=0, ignore_index=True)
else:
dft_ = df2_
contract_sub_df[stock_code] = dft_
######### 이 부분에서 로직을 적용한 후 매수/매도를 수행하면 될 듯!!
val1 = dp_['LAST_PRICE'].tolist()[0]
tr_plans[stock_code].push(float(val1)) # 이동평균값 활용
# tr_plans[stock_code].eval() # RSI(Relative Strength Index, 상대강도지수)라는 주가 지표 계산 활용
stock_df = dp_['SERIES_CD'].tolist()[0] # 종목코드
# [해외선물옵션] 주문/계좌 > 해외선물옵션 주문가능조회 (선물옵션구분fuop_dvsn)
# 선물옵션구분 00:전체 / 01:선물 / 02:옵션
rt_data = kb.get_overseasfuopt_inquire_psamount(itm_no=stock_df, dvsn="00", pric=0, ordyn="")
ord_qty = rt_data.loc[0, 'fm_new_ord_psbl_qty'] # 신규주문가능수량 총주문가능수량(fm_tot_ord_psbl_qty), 시장가총주문가능수량(fm_mkpr_tot_ord_psbl_qty)
print("[주문가능수량!] : " + ord_qty)
###########################################################
# [해외선물옵션] 주문/계좌 > 해외선물옵션주문 (종목번호<6자리 5자리> + 매수매도구분ord_dv + 가격구분dvsn + 주문수량qty + 주문가격limt_pric + 주문가격stop_pric)
# 매수매도구분ord_dv 01 : 매도, 02 : 매수 # 가격구분dvsn : 1.지정, 2. 시장, 3. STOP, 4 S/L
# 주문가격limt_pric : 지정가인 경우 가격 입력 * 시장가, STOP주문인 경우, 빈칸("") 입력
# 주문가격stop_pric : STOP 주문 가격 입력 * 시장가, 지정가인 경우, 빈칸("") 입력
# rt_data = kb.get_overseasfuopt_order(itm_no=stock_df, ord_dv="02", dvsn="1", qty=ord_qty, limt_pric=val1, stop_pric=0)
# print(rt_data.ORD_DT + "+" + rt_data.ODNO) # 주문일자+주문접수번호
print("매수/매도 조건 주문 : " + val1)
###########################################################
elif tr_id == KIS_WSReq.CCLDNOTICE: # 체결통보의 경우, 일단 executed_df 에만 저장해 둠
if __DEBUG__: print(dp_.to_string(header=False, index=False))
executed_df = pd.concat([executed_df, dp_], axis=0, ignore_index=True)
else:
pass
else:
print("Data length error...{data}")
def _get_sys_resp(data):
global _iv
global _ekey
isPingPong = False
isUnSub = False
isOk = False
tr_msg = None
tr_key = None
rdic = json.loads(data)
tr_id = rdic['header']['tr_id']
if tr_id != "PINGPONG": tr_key = rdic['header']['tr_key']
if rdic.get("body", None) is not None:
isOk = True if rdic["body"]["rt_cd"] == "0" else False
tr_msg = rdic["body"]["msg1"]
# 복호화를 위한 key 를 추출
if 'output' in rdic["body"]:
_iv = rdic["body"]["output"]["iv"]
_ekey = rdic["body"]["output"]["key"]
isUnSub = True if tr_msg[:5] == "UNSUB" else False
else:
isPingPong = True if tr_id == "PINGPONG" else False
nt2 = namedtuple('SysMsg', ['isOk', 'tr_id', 'tr_key', 'isUnSub', 'isPingPong'])
d = {
'isOk': isOk,
'tr_id': tr_id,
'tr_key': tr_key,
'isUnSub': isUnSub,
'isPingPong': isPingPong
}
return nt2(**d)
def on_data(ws, data, resp_type, data_continu):
# print(f"On data => {resp_type}, {data_continu}, {data}") #return only 1, True
pass
def on_message(ws, data):
if data[0] in ('0', '1'): # 실시간체결 or 실시간호가
_dparse(data)
else: # system message or PINGPONG
rsp = _get_sys_resp(data)
if rsp.isPingPong:
ws.send(data, websocket.ABNF.OPCODE_PING)
else:
if (not rsp.isUnSub and rsp.tr_id == KIS_WSReq.CONTRACT):
contract_sub_df[rsp.tr_key] = pd.DataFrame(columns=reserved_cols)
########################################################################
#### 이 부분에서 전략을 수행할 class 를 등록한다.
#### 실제 주문 실행은 _dparse 함수에서 처리
tr_plans[rsp.tr_key] = BasicPlan(rsp.tr_key) # 이동 평균선 계산 (웹소켓 프로그램 실행시 수집된 데이터만 반영)
# tr_plans[rsp.tr_key] = RSI_ST(rsp.tr_key) # RSI(Relative Strength Index, 상대강도지수)라는 주가 지표 계산
########################################################################
elif (rsp.isUnSub):
del (contract_sub_df[rsp.tr_key])
else:
print(rsp)
def on_error(ws, error):
print('error=', error)
def on_close(ws, status_code, close_msg):
print('on_close close_status_code=', status_code, " close_msg=", close_msg)
def on_open(ws):
# stocks 에는 40개까지만 가능
stocks = ('6EV24', '6EU24', 'ESU24', 'OESU24 C5450', 'ONQU24 C18900', 'OESU24 C6000') # 해외선물옵션
for scode in stocks:
subscribe(ws, KIS_WSReq.BID_ASK, _connect_key, scode) # 실시간 호가(미국)
subscribe(ws, KIS_WSReq.CONTRACT, _connect_key, scode) # 실시간 체결
# unsubscribe(ws, KIS_WSReq.CONTRACT, _connect_key, "RBAQAAPL") #실시간 체결 연결해제
# subscribe(ws, KIS_WSReq.CONTRACT, _connect_key, "RBAQAAPL") #실시간 체결 연결등록
# unsubscribe(ws, KIS_WSReq.BID_USA, _connect_key, "RBAQAAPL") #실시간 호가(미국) 연결해제
# subscribe(ws, KIS_WSReq.BID_USA, _connect_key, "RBAQAAPL") #실시간 호가(미국) 연결등록
# 실시간 계좌체결발생통보를 등록한다. 계좌체결발생통보 결과는 executed_df 에 저장된다.
#subscribe(ws, KIS_WSReq.ORDERNOTICE, _connect_key, "HTS ID 입력") # "HTS ID 입력 하세요" 계좌주문내역발생통보
subscribe(ws, KIS_WSReq.CCLDNOTICE, _connect_key, "HTS ID 입력") # "HTS ID 입력 하세요" 계좌체결내역발생통보
ws = websocket.WebSocketApp("ws://ops.koreainvestment.com:21000/tryitout",
on_open=on_open, on_message=on_message, on_error=on_error, on_data=on_data)
ws.run_forever() # 실시간 웹소켓 연결 작동

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,468 @@
# 해외주식 실시간 websocket sample
import websocket
import kis_auth as ka
import kis_ovrseastk as kb
import os
import json
import requests
import pandas as pd
import numpy as np
import time
import datetime
from io import StringIO
from threading import Thread
from collections import namedtuple, deque
try:
import websockets
except ImportError:
print("websocket-client 설치중입니다.")
os.system('python3 -m pip3 install websocket-client')
from enum import StrEnum
class KIS_WSReq(StrEnum):
BID_USA = 'HDFSASP0' # 해외주식 실시간지연호가(미국)
BID_ASA = 'HDFSASP1' # 해외주식 실시간지연호가(아시아)
CONTRACT = 'HDFSCNT0' # 해외주식 실시간지연 체결가
NOTICE = 'H0GSCNI0' # 실시간 해외주식 체결통보
import talib as ta
class BasicPlan:
def __init__(self, stock_code, window=20):
self._stock_code = stock_code
self._queue = deque(maxlen=window)
self._prev_ma = None
def push(self, value):
self._queue.append(value)
ma = sum(self._queue) / len(self._queue)
diff = ma - self._prev_ma if self._prev_ma is not None else None
self._prev_ma = ma
print(f"{self._stock_code}****** value: {value}, MA: {ma}, diff: {diff}...")
class RSI_ST: # RSI(Relative Strength Index, 상대강도지수)라는 주가 지표 계산
def __init__(self, stock_code, window=21):
self._stock_code = stock_code
self._queue = deque(maxlen=window)
self.rsi_period = window
def eval(self):
# dftt = getStreamdDF(self._stock_code)
# print(self)
dftt = contract_sub_df.get(self._stock_code).copy()
dftt = dftt.set_index(['TICK_HOUR'])
dftt['LAST'] = pd.to_numeric(dftt['LAST'], errors='coerce').convert_dtypes()
np_closes = np.array(dftt['LAST'], dtype=np.float64)
rsi = ta.RSI(np_closes, self.rsi_period)
last_rsi = rsi[-1]
if last_rsi < 30:
print(f"({self._stock_code})[BUY] ***RSI: {last_rsi}") # 통상적으로 RSI가 30 이하면 과매도 상태인 것으로 판단하고 시장이 과도하게 하락했음을 나타냄
elif last_rsi < 70 and last_rsi >= 30:
print(f"({self._stock_code})[N/A] ***RSI: {last_rsi}")
elif last_rsi >= 70:
print(f"({self._stock_code})[SELL] ***RSI: {last_rsi}") # 통상적으로 RSI가 70 이상이면 과매수 상태로 간주하고 시장이 과열되었을 가능성이 있음을 나타냄
else:
print(self._stock_code)
_today__ = datetime.date.today().strftime("%Y%m%d")
ka.auth()
__DEBUG__ = False # True
# 실시간 해외주식 계좌체결통보 복호화를 위한 부분 - start
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import b64decode
# AES256 DECODE: Copied from KIS Developers Github sample code
def aes_cbc_base64_dec(key, iv, cipher_text):
"""
:param key: str type AES256 secret key value
:param iv: str type AES256 Initialize Vector
:param cipher_text: Base64 encoded AES256 str
:return: Base64-AES256 decodec str
"""
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
return bytes.decode(unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size))
# 실시간 해외주식 계좌체결통보 복호화를 위한 부분 - end
contract_sub_df = dict() # 실시간 해외주식 체결 결과를 종목별로 저장하기 위한 container
tr_plans = dict() # 실시간 해외주식 체결 값에 따라 무언가를 수행할 Class 를 저장하기 위한 container
excg_dict = {
'NYS' : 'NYSE', #미국뉴욕
'NAS' : 'NASD', #미국나스닥
'AMS' : 'AMEX', #미국아멕스
'TSE' : 'TKSE', #일본도쿄
'HKS' : 'SEHK', #홍콩
'SHS' : 'SHAA', #중국상해
'SZS' : 'SZAA', #중국심천
'HSX' : 'VNSE', #베트남호치민,
'HNX' : 'HASE', #베트남하노이
'BAY' : 'NYSE', #미국뉴욕(주간)
'BAQ' : 'NASD', #미국나스닥(주간),
'BAA' : 'AMEX' #미국아멕스(주간)
}
#reserved_cols = ['TICK_HOUR', 'STCK_PRPR', 'ACML_VOL'] # 실시간 해외주식 체결 중 사용할 수신시간, 현재가, 누적거래량 만 추출하기 위한 column 정의
reserved_cols = ['TICK_HOUR', 'LAST'] # 실시간 해외주식 체결 중 사용할 column 만 추출하기 위한 column 정의
# 해외주식 실시간지연체결가 column header
contract_cols = ['RSYM', # 실시간종목코드
'SYMB', # 종목코드
'ZDIV', # 수수점자리수
'TYMD', # 현지영업일자
'XYMD', # 현지일자
'XHMS', # 현지시간
'KYMD', # 한국일자
'TICK_HOUR', # pandas time conversion 편의를 위해 이 필드만 이름을 통일한다 'KHMS' 한국시간
'OPEN', # 시가
'HIGH', # 고가
'LOW', # 저가
'LAST', # 현재가
'SIGN', # 대비구분
'DIFF', # 전일대비
'RATE', # 등락율
'PBID', # 매수호가
'PASK', # 매도호가
'VBID', # 매수잔량
'VASK', # 매도잔량
'EVOL', # 체결량
'TVOL', # 거래량
'TAMT', # 거래대금
'BIVL', # 매도체결량
'ASVL', # 매수체결량
'STRN', # 체결강도
'MTYP'] # 시장구분 1:장중,2:장전,3:장후
# 실시간 해외주식호가(미국) column eader
bid_usa_cols = ['RSYM', #실시간종목코드
'SYMB', # 종목코드
'ZDIV', # 소숫점자리수
'XYMD', # 현지일자
'XHMS', # 현지시간
'KYMD', # 한국일자
'TICK_HOUR', # pandas time conversion 편의를 위해 이 필드만 이름을 통일한다 'KHMS' 한국시간
'BVOL', # 매수총잔량
'AVOL', # 매도총잔량
'BDVL', # 매수총잔량대비
'ADVL', # 매도총잔량대비
'PBID1', # 매수호가1
'PASK1', # 매도호가1
'VBID1', # 매수잔량1
'VASK1', # 매도잔량1
'DBID1', # 매수잔량대비1
'DASK1' ] # 매도잔량대비1
# 실시간 해외주식호가(아시아) column eader
bid_asa_cols = ['RSYM', #실시간종목코드
'SYMB', #종목코드
'ZDIV', #소수점자리수
'XYMD', #현지일자
'XHMS', #현지시간
'KYMD', #한국일자
'TICK_HOUR', # pandas time conversion 편의를 위해 이 필드만 이름을 통일한다 'KHMS' 한국시간
'BVOL', #매수총잔량
'AVOL', #매도총잔량
'BDVL', #매수총잔량대비
'ADVL', #매도총잔량대비
'PBID1', #매수호가1
'PASK1', #매도호가1
'VBID1', #매수잔량1
'VASK1', #매도잔량1
'DBID1', #매수잔량대비1
'DASK1'] #매도잔량대비1
# 실시간 계좌체결발생통보 column header
notice_cols = ['CUST_ID', # HTS ID
'ACNT_NO',
'ODER_NO', # 주문번호
'OODER_NO', # 원주문번호
'SELN_BYOV_CLS', # 매도매수구분
'RCTF_CLS', # 정정구분
'ODER_KIND2', # 주문종류2(1:시장가 2:지정자 6:단주시장가 7:단주지정가 A:MOO B:LOO C:MOC D:LOC)
'STCK_SHRN_ISCD', # 주식 단축 종목코드
'CNTG_QTY', # 체결 수량 - 주문통보의 경우 해당 위치에 주문수량이 출력, - 체결통보인 경우 해당 위치에 체결수량이 출력
'CNTG_UNPR', # 체결단가 ※ 주문통보 시에는 주문단가가, 체결통보 시에는 체결단가가 수신 됩니다. ※ 체결단가의 경우, 국가에 따라 소수점 생략 위치가 상이합니다.
# 미국 4 일본 1 중국 3 홍콩 3 베트남 0 EX) 미국 AAPL(현재가 : 148.0100)의 경우 001480100으로 체결단가가 오는데, 4번째 자리에 소수점을 찍어 148.01로 해석하시면 됩니다.
'STCK_CNTG_HOUR', # 주식 체결 시간
'RFUS_YN', # 거부여부(0 : 승인, 1 : 거부)
'CNTG_YN', # 체결여부(1 : 주문,정정,취소,거부,, 2 : 체결 (★ 체결만 볼 경우 2번만 ))
'ACPT_YN', # 접수여부(1:주문접수 2:확인 3:취소(FOK/IOC))
'BRNC_NO', # 지점
'ODER_QTY', # 주문수량 - 주문통보인 경우 해당 위치 미출력 (주문통보의 주문수량은 CNTG_QTY 위치에 출력) - 체결통보인 경우 해당 위치에 주문수량이 출력
'ACNT_NAME', # 계좌명
'CNTG_ISNM', # 체결종목명
'ODER_COND', # 해외종목구분
'DEBT_GB', # 담보유형코드 10:현금 15:해외주식담보대출
'DEBT_DATE'] # 담보대출일자 대출일(YYYYMMDD)
# 웹소켓 접속키 발급
def get_approval():
url = ka.getTREnv().my_url
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": ka.getTREnv().my_app,
"secretkey": ka.getTREnv().my_sec}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
_connect_key = get_approval() # websocker 연결Key
_iv = None # for 복호화
_ekey = None # for 복호화
executed_df = pd.DataFrame(data=None, columns=contract_cols) # 체결통보 저장용 DF
# added_data 는 종목코드(실시간체결, 실시간호가) 또는 HTS_ID(체결통보)
def _build_message(app_key, tr_id, added_data, tr_type='1'):
_h = {
"approval_key": app_key,
"custtype": 'P',
"tr_type": tr_type,
"content-type": "utf-8"
}
_inp = {
"tr_id": tr_id,
"tr_key": added_data
}
_b = {
"input": _inp
}
_data = {
"header": _h,
"body": _b
}
d1 = json.dumps(_data)
return d1
# sub_data 는 종목코드(실시간체결, 실시간호가) 또는 HTS_ID(실시간 계좌체결발생통보)
def subscribe(ws, sub_type, app_key, sub_data): # 세션 종목코드(실시간체결, 실시간호가) 등록
ws.send(_build_message(app_key, sub_type, sub_data), websocket.ABNF.OPCODE_TEXT)
time.sleep(.1)
def unsubscribe(ws, sub_type, app_key, sub_data): # 세션 종목코드(실시간체결, 실시간호가) 등록해제
ws.send(_build_message(app_key, sub_type, sub_data, '2'), websocket.ABNF.OPCODE_TEXT)
time.sleep(.1)
# streaming data 를 이용해 주어진 bar 크기(예: 1분, 5분 등)의 OHLC(x분봉) 데이터프레임을 반환한다.
# 이때 streamign data 는 websocket client 가 시작한 다음부터 지금까지의 해당 종목의 가격 정보를 의미한다.
# ** 동시호가 시간은 OHLC data 가 모두 NA 가 된다.
def getStreamdDF(stock_code, bar_sz='1Min'):
df3 = contract_sub_df.get(stock_code).copy()
df3 = df3.set_index(['TICK_HOUR'])
df3['LAST'] = pd.to_numeric(df3['LAST'], errors='coerce').convert_dtypes()
df3 = df3['LAST'].resample(bar_sz).ohlc()
return df3
# 수신데이터 파싱
def _dparse(data):
global executed_df
d1 = data.split("|")
dp_ = None
hcols = []
if len(d1) >= 4:
tr_id = d1[1]
if tr_id == KIS_WSReq.CONTRACT: # 실시간체결
hcols = contract_cols
elif tr_id == KIS_WSReq.BID_USA: # 해외주식 실시간지연호가(미국)
hcols = bid_usa_cols
elif tr_id == KIS_WSReq.BID_ASA: # 해외주식 실시간지연호가(아시아)
hcols = bid_asa_cols
elif tr_id == KIS_WSReq.NOTICE: # 계좌체결통보
hcols = notice_cols
else:
pass
if tr_id in (KIS_WSReq.CONTRACT, KIS_WSReq.BID_USA, KIS_WSReq.BID_ASA): # 실시간체결, 실시간지연호가(미국), 실시간지연호가(아시아)
dp_ = pd.read_csv(StringIO(d1[3]), header=None, sep='^', names=hcols, dtype=object) # 수신데이터 parsing
print(dp_) # 실시간체결, 실시간호가 수신 데이터 파싱 결과 확인
dp_['TICK_HOUR'] = _today__ + dp_['TICK_HOUR'] # 수신시간
dp_['TICK_HOUR'] = pd.to_datetime(dp_['TICK_HOUR'], format='%Y%m%d%H%M%S', errors='coerce')
else: # 실시간 계좌체결발생통보는 암호화되어서 수신되므로 복호화 과정이 필요
dp_ = pd.read_csv(StringIO(aes_cbc_base64_dec(_ekey, _iv, d1[3])), header=None, sep='^', names=hcols, # 수신데이터 parsing 및 복호화
dtype=object)
print(dp_) # 실시간 계좌체결발생통보 수신 파싱 결과 확인
if __DEBUG__: print(f'***EXECUTED NOTICE [{dp_.to_string(header=False, index=False)}]')
if tr_id == KIS_WSReq.CONTRACT: # 실시간 체결
if __DEBUG__: print(dp_.to_string(header=False, index=False))
stock_code = dp_[dp_.columns[0]].values.tolist()[0]
df2_ = dp_[reserved_cols]
# dft_ = pd.concat([contract_sub_df.get(stock_code), df2_], axis=0, ignore_index=True)
# 선택된 열이 비어 있거나 모든 값이 NA인지 확인
selected_df = contract_sub_df.get(stock_code)
if selected_df is not None and not selected_df.dropna().empty:
dft_ = pd.concat([selected_df, df2_], axis=0, ignore_index=True)
else:
dft_ = df2_
contract_sub_df[stock_code] = dft_
######### 이 부분에서 로직을 적용한 후 매수/매도를 수행하면 될 듯!!
val1 = dp_['LAST'].tolist()[0]
tr_plans[stock_code].push(float(val1)) # 이동평균값 활용
# tr_plans[stock_code].eval() # RSI(Relative Strength Index, 상대강도지수)라는 주가 지표 계산 활용
excg_df = excg_dict[stock_code[1:4]] # 해외거래소코드(3자리) 주문API 사용가능 해외거래소코드(4자리) 변환
stock_df = dp_['SYMB'].tolist()[0] # 종목코드
# [국내주식] 주문/계좌 > 매수가능조회 (종목번호 5자리 + 종목단가) REST API
#rt_data = kb.get_inquire_psbl_order(pdno=stock_code, ord_unpr=val1, itm_no="TSLA")
rt_data = kb.get_overseas_inquire_psamount(excg=excg_df, itm_no=stock_df)
ord_qty = rt_data.loc[0, 'ord_psbl_qty'] # ord_psbl_qty 주문가능수량 또는 외화인 경우 max_ord_psbl_qty 최대주문가능수량
print("[주문가능수량!] : " + ord_qty)
###########################################################
# 해외주식(미국) 현금 주문
# rt_data = kb.get_overseas_order(ord_dv="buy", excg_cd=excg_df, itm_no=stock_df, qty=1, unpr=123.3)
# print(rt_data.KRX_FWDG_ORD_ORGNO + "+" + rt_data.ODNO + "+" + rt_data.ORD_TMD) # 주문접수조직번호+주문접수번호+주문시각
# 해외주식(미국) 현금 주문(주간)
# rt_data = kb.get_overseas_daytime_order(ord_dv="buy", excg_cd=excg_df, itm_no=stock_df, qty=1, unpr=123.3)
# print(rt_data.KRX_FWDG_ORD_ORGNO + "+" + rt_data.ODNO + "+" + rt_data.ORD_TMD) # 주문접수조직번호+주문접수번호+주문시각
print("매수/매도 조건 주문 : " + val1)
###########################################################
elif tr_id == KIS_WSReq.NOTICE: # 체결통보의 경우, 일단 executed_df 에만 저장해 둠
if __DEBUG__: print(dp_.to_string(header=False, index=False))
executed_df = pd.concat([executed_df, dp_], axis=0, ignore_index=True)
else:
pass
else:
print("Data length error...{data}")
def _get_sys_resp(data):
global _iv
global _ekey
isPingPong = False
isUnSub = False
isOk = False
tr_msg = None
tr_key = None
rdic = json.loads(data)
tr_id = rdic['header']['tr_id']
if tr_id != "PINGPONG": tr_key = rdic['header']['tr_key']
if rdic.get("body", None) is not None:
isOk = True if rdic["body"]["rt_cd"] == "0" else False
tr_msg = rdic["body"]["msg1"]
# 복호화를 위한 key 를 추출
if 'output' in rdic["body"]:
_iv = rdic["body"]["output"]["iv"]
_ekey = rdic["body"]["output"]["key"]
isUnSub = True if tr_msg[:5] == "UNSUB" else False
else:
isPingPong = True if tr_id == "PINGPONG" else False
nt2 = namedtuple('SysMsg', ['isOk', 'tr_id', 'tr_key', 'isUnSub', 'isPingPong'])
d = {
'isOk': isOk,
'tr_id': tr_id,
'tr_key': tr_key,
'isUnSub': isUnSub,
'isPingPong': isPingPong
}
return nt2(**d)
def on_data(ws, data, resp_type, data_continu):
# print(f"On data => {resp_type}, {data_continu}, {data}") #return only 1, True
pass
def on_message(ws, data):
if data[0] in ('0', '1'): # 실시간체결 or 실시간호가
_dparse(data)
else: # system message or PINGPONG
rsp = _get_sys_resp(data)
if rsp.isPingPong:
ws.send(data, websocket.ABNF.OPCODE_PING)
else:
if (not rsp.isUnSub and rsp.tr_id == KIS_WSReq.CONTRACT):
contract_sub_df[rsp.tr_key] = pd.DataFrame(columns=reserved_cols)
########################################################################
#### 이 부분에서 전략을 수행할 class 를 등록한다.
#### 실제 주문 실행은 _dparse 함수에서 처리
tr_plans[rsp.tr_key] = BasicPlan(rsp.tr_key) # 이동 평균선 계산 (웹소켓 프로그램 실행시 수집된 데이터만 반영)
# tr_plans[rsp.tr_key] = RSI_ST(rsp.tr_key) # RSI(Relative Strength Index, 상대강도지수)라는 주가 지표 계산
########################################################################
elif (rsp.isUnSub):
del (contract_sub_df[rsp.tr_key])
else:
print(rsp)
def on_error(ws, error):
print('error=', error)
def on_close(ws, status_code, close_msg):
print('on_close close_status_code=', status_code, " close_msg=", close_msg)
def on_open(ws):
# stocks 에는 40개까지만 가능
stocks = ('RBAQAAPL', 'RBAQTSLA', 'RBAQAMZN', 'RBAQNVDA', 'RBAQINTC', 'RBAQMSFT') # 미국주식 주간거래
#stocks = ('DNASAAPL', 'DNASTSLA', 'DNASAMZN', 'DNASNVDA', 'DNASINTC', 'DNASMSFT') # 미국주식 야간거래(정규시장)
for scode in stocks:
subscribe(ws, KIS_WSReq.BID_USA, _connect_key, scode) # 실시간 호가(미국)
#subscribe(ws, KIS_WSReq.BID_USA, _connect_key, scode) # 실시간 호가(아시아)
subscribe(ws, KIS_WSReq.CONTRACT, _connect_key, scode) # 실시간 체결
# unsubscribe(ws, KIS_WSReq.CONTRACT, _connect_key, "RBAQAAPL") #실시간 체결 연결해제
# subscribe(ws, KIS_WSReq.CONTRACT, _connect_key, "RBAQAAPL") #실시간 체결 연결등록
# unsubscribe(ws, KIS_WSReq.BID_USA, _connect_key, "RBAQAAPL") #실시간 호가(미국) 연결해제
# subscribe(ws, KIS_WSReq.BID_USA, _connect_key, "RBAQAAPL") #실시간 호가(미국) 연결등록
# 실시간 계좌체결발생통보를 등록한다. 계좌체결발생통보 결과는 executed_df 에 저장된다.
subscribe(ws, KIS_WSReq.NOTICE, _connect_key, "HTS ID 입력") # HTS ID 입력 계좌체결발생통보
ws = websocket.WebSocketApp("ws://ops.koreainvestment.com:21000/tryitout",
on_open=on_open, on_message=on_message, on_error=on_error, on_data=on_data)
ws.run_forever() # 실시간 웹소켓 연결 작동