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,478 @@
#### **[당사에서 제공하는 샘플코드에 대한 유의사항]** ####
- 샘플 코드는 한국투자증권 오픈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. KIS Developers 개발자 센터 소개
[KIS Developers](https://apiportal.koreainvestment.com/)는 한국투자증권의 트레이딩 서비스를 오픈API로 제공하여 개발자들이 다양한 금융서비스를 만들 수 있도록 지원하는 개발자 센터입니다. KIS Developers에서는 개발자의 금융 서비스 개발을 지원하기 위해 API 문서 메뉴 내 API에 대한 상세한 설명과 예제를 제공합니다. 전문 개발자가 아닌 일반인들도 쉽게 금융 서비스를 만들 수 있습니다.
* 제휴 문의는 [제휴안내 페이지](https://apiportal.koreainvestment.com/howto-register)에서 제휴 신청 부탁드립니다.
* API 사용 관련 문의는 [Q&A 페이지](https://apiportal.koreainvestment.com/community/10000000-0000-0011-0000-000000000003)에서 문의 부탁드립니다.
## 2. 이용 안내
### 2.1. 사전 준비
KIS Developers 서비스 신청을 위해서는 2가지 사전 준비물이 필요합니다. 첫 번째로는 한국투자증권 계좌가 개설되어있어야 하며, 두 번째로는 한국투자증권 ID 등록이 필요합니다. 만약 계좌가 없으시다면 한국투자증권 앱을 통해 비대면 개설을 진행하거나, 가까운 영업점을 방문해주시길 바랍니다. (모의투자계좌의 경우, 한국투자 홈페이지 혹은 MTS에서 모의투자 서비스 신청 후 발급받은 모의계좌번호로 API 신청을 하셔야 합니다.)
- 한국투자증권 앱 : [https://securities.koreainvestment.com/main/customer/cusGuide/customerGuide.jsp](https://securities.koreainvestment.com/main/customer/cusGuide/customerGuide.jsp)
- 가까운 영업점 찾기 : [https://securities.koreainvestment.com/main/customer/guide/branch/branch.jsp](https://securities.koreainvestment.com/main/customer/guide/branch/branch.jsp)
- 모의투자계좌 발급 : [https://securities.koreainvestment.com/main/research/virtual/_static/TF07da010000.jsp](https://securities.koreainvestment.com/main/research/virtual/_static/TF07da010000.jsp)
### 2.2. 서비스 신청
KIS Developers 서비스 신청 페이지까지 2가지 경로로 접속 가능합니다.
* KIS Developers 우상단 API신청 버튼 클릭
* 한국투자증권 홈페이지 [서비스신청 > Open API > KIS Developers > KIS Developers 서비스 신청하기]
서비스 신청 화면에 접속하면 홈페이지 인증서 로그인 화면이 뜨고, 인증을 완료하면 아래와 같이 KIS Developers 서비스 신청하기의 첫 화면인 휴대폰 인증 화면이 뜨게 됩니다.
![](https://wikidocs.net/images/page/159301/kis_restapi_1.png)
해당 화면에서 등록하신 휴대폰 인증을 통해 본인 인증을 완료합니다.
* HTS ID에 $나 @가 포함되면 추후에 HTS ID 변경 시 Open API 서비스 사용에 제한이 있을 수 있으니, HTS ID 변경 후 서비스 가입을 권유드립니다.
* 다만 기존 가입내역이 있으며 HTS ID 변경 후 Open API 서비스 사용에 어려움을 겪으실 경우, KIS Developers 홈페이지 Q&A 게시판에 문의 남겨주시면 조치해 드리겠습니다.
### 2.3. 유의사항 확인
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/9413332d-105c-47aa-89de-7d419714a0fb)
다음으로는 유의사항확인입니다. 서비스 사용에 관한 유의사항을 정독하여 충분하게 숙지하신 뒤, 동의를 진행합니다. 개인정보 수집·이용 동의서, 고객 이용약관 및 이용약관 개정 알림에 대해 미동의 시, 서비스 이용이 불가합니다.
### 2.4. KIS Developers 서비스 신청하기
![](https://wikidocs.net/images/page/159301/kis_restapi_3.png)
유의사항 확인이 완료되면 기존 신청내역이 없으실 경우, 곧바로 서비스 신청하기 팝업 화면이 뜨게 됩니다. 해당 화면에서 보유하고 계신 계좌에 대하여 API 신청하실 수 있습니다. 한 번에 2개의 계좌까지 API 신청 가능하며, 다계좌 API 신청을 원하실 경우, 신청하기 완료 후 신청정보 페이지에서 추가신청하기 기능을 이용하시기 바랍니다.
* 실전투자계좌의 경우 동일한 종합 계좌번호의 주식(국내, 해외), 선물/옵션 계좌로 API 이용 가능합니다.
* 모의투자계좌의 경우 한국투자 홈페이지 혹은 MTS에서 모의투자 서비스 신청 후 발급받은 모의계좌번호로 API 신청을 하셔야 합니다.
계좌번호 선택 하단에 위치한 테이블에 있는 KIS Developers 사용자 ID는 향후 KIS Developers 포탈에서 사용되는 ID로 기존 등록된 HTS ID와 동일합니다. 두번째로 API그룹은 현재 KIS Developers에서 이용 가능한 API 기능들로, 지속적으로 추가 개발될 예정입니다. 세번째로 문자메시지 설정을 통해 서비스 만료 1개월 전 메시지를 받을 수 있으니 참고하시길 바랍니다. 마지막으로 이용기간은 신청일로부터 1년입니다.
계좌번호를 선택 혹은 입력하시고 신청 버튼을 누르시면 처음 홈페이지 로그인하신 방식으로 인증 팝업이 뜨게 됩니다.
![](https://wikidocs.net/images/page/159301/kis_restapi_6.png)
정상적으로 인증을 완료하였다는 메시지가 창에 뜨고, 해당 계좌번호로 API 신청 및 APP Key, APP Secret 발급이 완료됩니다.
![](https://wikidocs.net/images/page/159301/kis_restapi_4.png)
이상으로 신청이 완료되었습니다. 카카오톡 알림톡 혹은 문자 메시지로 오픈API 서비스 신청완료 안내 메시지와 함께 KIS Developers 홈페이지 초기 패스워드가 발송됩니다. KIS Developers 홈페이지의 ID는 HTS ID와 같으며, 메시지로 받으신 임시비밀번호로 홈페이지에 로그인하셔서 API문서 등을 확인하실 수 있습니다.
### 2.5. 신청정보
![](https://wikidocs.net/images/page/159301/kis_restapi_7.png)
신청이 완료되면 신청정보 화면의 신청현황 테이블에서 해당 계좌의 App Key와 App Secret을 확인하실 수 있습니다. App Key와 App Secret은 노출되어 있지 않아, 클립보드에 복사 후 원하시는 곳에 붙여넣어 사용 부탁드립니다. App Key와 App Secret 두 암호키를 통해 계좌에 접근할 수 있는 토큰을 발급받을 수 있어, 타인에게 유출을 금하며 관리에 유의해야합니다. 유출시 즉시 홈페이지에서 재발급 하시기 바랍니다.
갱신 기능과 해지 기능에 대해 설명드리겠습니다. 갱신(기간 연장)은 만료 30일 전부터 신청 가능하며, 그 전까지는 갱신 버튼이 비활성화되어 클릭이 불가능합니다. 갱신 시, 신청일로부터 1년 기간이 연장되며, APP Key, APP Secret가 재발급됩니다. 해지는 언제든지 신청 가능하며, 해지 완료 시 발급되었던 APP Key, APP Secret는 사용이 불가능합니다.
### 2.6. 추가신청하기
![](https://wikidocs.net/images/page/159301/kis_restapi_5.png)
신청현황 화면 하단의 추가신청하기 기능을 이용하여 더 많은 계좌의 API 신청이 가능합니다. 실전투자계좌의 경우 최대 89개, 모의투자계좌의 경우 최대 2개까지 신청 가능합니다. 추가 신청 프로세스는 맨 처음 신청했을 때와 동일하게 계좌 선택 및 비밀번호 인증, 인증서 인증을 거쳐 이뤄집니다.
### 2.7. 초기 로그인 시 패스워드 변경 방법
오픈API 서비스 신청 완료 후 KIS Developers 포탈로 이동하여 로그인을 진행합니다. 초기 아이디는 HTS 아이디와 동일하며, 홈페이지 신청 정보를 통해 확인하실 수 있습니다. 초기 패스워드는 안내 메시지 카카오톡 알림톡 혹은 문자 메시지로 발송됩니다. 반드시 로그인 후 패스워드를 변경하시길 바랍니다. 패스워드 변경은 아래와 같이 진행하실 수 있습니다.
**■ 초기 로그인 시 패스워드 변경 방법**
![image](https://github.com/user-attachments/assets/954e3853-f18d-4f9a-aa86-e86cc9358b73)
![image](https://github.com/user-attachments/assets/83553b44-c5f2-4f6d-adde-44e58f2fa2f2)
### 2.8. 이전 ID 신청정보 삭제 방법
KIS Developers 서비스 가입 후 HTS ID를 변경하여 '처리계좌의 ID와 사용자정보가 상이하여 주문처리 불가능 합니다.' 오류가 발생하셨을 경우 아래와 같이 한국투자증권 홈페이지에서 이전 ID 신청정보 삭제하신 후 핸드폰 인증 후 신청현황에서 신청을 다시 하시고 발급받으신 앱키를 이용하시면 정상 이용하실 수 있습니다.
**■ 이전 ID 신청정보 삭제 방법**
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/c331efc3-0c5c-4722-8e69-3c50924a73b4)
한국투자증권 홈페이지 > 트레이딩 > Open API > KIS Developers > KIS Developers 서비스 신청하기 접속 후 'ID 변경 전 신청정보 삭제' 클릭
(https://securities.koreainvestment.com/main/customer/systemdown/RestAPIService.jsp)
## 3. API 제공 목록 <a id="apiList">
|구분 |API명 |모의투자 제공 여부|
|--|--|--|
|OAuth인증|웹소켓접속키발급|⭕|
|OAuth인증|접근토큰발급|⭕|
|OAuth인증|접근토큰폐기|⭕|
|OAuth인증|Hashkey|⭕|
|[국내주식]주문/계좌|주식주문(현금)|⭕|
|[국내주식]주문/계좌|주식주문(신용)| |
|[국내주식]주문/계좌|주식주문(정정취소)|⭕|
|[국내주식]주문/계좌|주식정정취소가능주문조회| |
|[국내주식]주문/계좌|주식일별주문체결조회|⭕|
|[국내주식]주문/계좌|주식잔고조회|⭕|
|[국내주식]주문/계좌|매수가능조회|⭕|
|[국내주식]주문/계좌|주식예약주문| |
|[국내주식]주문/계좌|주식예약주문정정취소| |
|[국내주식]주문/계좌|주식예약주문조회| |
|[국내주식]주문/계좌|퇴직연금 체결기준잔고| |
|[국내주식]주문/계좌|퇴직연금 미체결내역| |
|[국내주식]주문/계좌|퇴직연금 매수가능조회| |
|[국내주식]주문/계좌|퇴직연금 예수금조회| |
|[국내주식]주문/계좌|퇴직연금 잔고조회| |
|[국내주식]주문/계좌|주식잔고조회_실현손익| |
|[국내주식]주문/계좌|신용매수가능조회| |
|[국내주식]주문/계좌|투자계좌자산현황조회| |
|[국내주식]주문/계좌|기간별매매손익현황조회| |
|[국내주식]주문/계좌|기간별손익일별합산조회| |
|[국내주식]주문/계좌|매도가능수량조회| |
|[국내주식]주문/계좌|주식통합증거금 현황| |
|[국내주식]주문/계좌|기간별계좌권리현황조회| |
|[국내주식]기본시세|주식현재가 시세|⭕|
|[국내주식]기본시세|주식현재가 체결|⭕|
|[국내주식]기본시세|주식현재가 일자별|⭕|
|[국내주식]기본시세|주식현재가 호가/예상체결|⭕|
|[국내주식]기본시세|주식현재가 투자자|⭕|
|[국내주식]기본시세|주식현재가 회원사|⭕|
|[국내주식]기본시세|국내주식기간별시세(일/주/월/년)|⭕|
|[국내주식]기본시세|주식현재가 당일시간대별체결|⭕|
|[국내주식]기본시세|주식현재가 시간외일자별주가|⭕|
|[국내주식]기본시세|주식당일분봉조회|⭕|
|[국내주식]기본시세|주식일별분봉조회|⭕|
|[국내주식]기본시세|주식현재가 시세2| |
|[국내주식]기본시세|ETF/ETN 현재가| |
|[국내주식]기본시세|NAV 비교추이(종목)| |
|[국내주식]기본시세|NAV 비교추이(분)| |
|[국내주식]기본시세|NAV 비교추이(일)| |
|[국내주식]기본시세|국내주식 장마감 예상체결가| |
|[국내주식]기본시세|ETF 구성종목시세| |
|[국내주식]기본시세|국내주식 시간외현재가| |
|[국내주식]기본시세|국내주식 시간외호가| |
|[국내주식]ELW시세|ELW현재가 시세|⭕|
|[국내주식]ELW시세|ELW 상승률순위| |
|[국내주식]ELW시세|ELW 거래량순위| |
|[국내주식]ELW시세|ELW 지표순위| |
|[국내주식]ELW시세|ELW 민감도 순위| |
|[국내주식]ELW시세|ELW 당일급변종목| |
|[국내주식]ELW시세|ELW 신규상장종목| |
|[국내주식]ELW시세|ELW 투자지표추이(체결)| |
|[국내주식]ELW시세|ELW 투자지표추이(분별)| |
|[국내주식]ELW시세|ELW 투자지표추이(일별)| |
|[국내주식]ELW시세|ELW 변동성 추이(틱)| |
|[국내주식]ELW시세|ELW 변동성 추이(체결)| |
|[국내주식]ELW시세|ELW 변동성 추이(분별)| |
|[국내주식]ELW시세|ELW 변동성 추이(일별)| |
|[국내주식]ELW시세|ELW 민감도 추이(체결)| |
|[국내주식]ELW시세|ELW 민감도 추이(일별)| |
|[국내주식]ELW시세|ELW 기초자산별 종목시세| |
|[국내주식]ELW시세|ELW LP매매추이| |
|[국내주식]ELW시세|ELW 비교대상종목조회| |
|[국내주식]ELW시세|ELW 종목검색| |
|[국내주식]ELW시세|ELW 기초자산 목록조회| |
|[국내주식]ELW시세|ELW 만기예정/만기종목| |
|[국내주식]업종/기타|국내주식업종기간별시세(일/주/월/년)|⭕|
|[국내주식]업종/기타|국내휴장일조회| |
|[국내주식]업종/기타|업종분봉조회| |
|[국내주식]업종/기타|변동성완화장치(VI) 현황| |
|[국내주식]업종/기타|국내업종 현재지수| |
|[국내주식]업종/기타|국내업종 일자별지수| |
|[국내주식]업종/기타|국내업종 구분별전체시세| |
|[국내주식]업종/기타|국내주식 예상체결 전체지수| |
|[국내주식]업종/기타|국내업종 시간별지수(틱)| |
|[국내주식]업종/기타|국내업종 시간별지수(분)| |
|[국내주식]업종/기타|국내주식 예상체결지수 추이| |
|[국내주식]업종/기타|금리 종합(국내채권/금리)| |
|[국내주식]업종/기타|종합 시황/공시(제목)| |
|[국내주식]업종/기타|국내선물 영업일조회| |
|[국내주식]종목정보|상품기본조회| |
|[국내주식]종목정보|주식기본조회| |
|[국내주식]종목정보|국내주식 대차대조표| |
|[국내주식]종목정보|국내주식 손익계산서| |
|[국내주식]종목정보|국내주식 재무비율| |
|[국내주식]종목정보|국내주식 수익성비율| |
|[국내주식]종목정보|국내주식 기타주요비율| |
|[국내주식]종목정보|국내주식 안정성비율| |
|[국내주식]종목정보|국내주식 성장성비율| |
|[국내주식]종목정보|국내주식 당사 신용가능종목| |
|[국내주식]종목정보|예탁원정보(배당일정)| |
|[국내주식]종목정보|예탁원정보(주식매수청구일정)| |
|[국내주식]종목정보|예탁원정보(합병/분할일정)| |
|[국내주식]종목정보|예탁원정보(액면교체일정)| |
|[국내주식]종목정보|예탁원정보(자본감소일정)| |
|[국내주식]종목정보|예탁원정보(상장정보일정)| |
|[국내주식]종목정보|예탁원정보(공모주청약일정)| |
|[국내주식]종목정보|예탁원정보(실권주일정)| |
|[국내주식]종목정보|예탁원정보(의무예치일정)| |
|[국내주식]종목정보|예탁원정보(유상증자일정)| |
|[국내주식]종목정보|예탁원정보(무상증자일정)| |
|[국내주식]종목정보|예탁원정보(주주총회일정)| |
|[국내주식]종목정보|국내주식 종목추정실적| |
|[국내주식]종목정보|당사 대주가능 종목| |
|[국내주식]종목정보|국내주식 종목투자의견| |
|[국내주식]종목정보|국내주식 증권사별 투자의견| |
|[국내주식]시세분석|국내기관_외국인 매매종목가집계| |
|[국내주식]시세분석|종목조건검색 목록조회| |
|[국내주식]시세분석|종목조건검색조회| |
|[국내주식]시세분석|종목별 프로그램매매추이(체결)| |
|[국내주식]시세분석|종목별 프로그램매매추이(일별)| |
|[국내주식]시세분석|종목별 외인기관 추정가집계| |
|[국내주식]시세분석|종목별일별매수매도체결량| |
|[국내주식]시세분석|시장별 투자자매매동향(시세)| |
|[국내주식]시세분석|시장별 투자자매매동향(일별)| |
|[국내주식]시세분석|국내주식 신용잔고 일별추이| |
|[국내주식]시세분석|국내주식 예상체결가 추이| |
|[국내주식]시세분석|국내주식 공매도 일별추이| |
|[국내주식]시세분석|국내주식 시간외예상체결등락률| |
|[국내주식]시세분석|프로그램매매 투자자매매동향(당일)| |
|[국내주식]시세분석|프로그램매매 종합현황(시간)| |
|[국내주식]시세분석|프로그램매매 종합현황(일별)| |
|[국내주식]시세분석|외국계 매매종목 가집계| |
|[국내주식]시세분석|종목별 외국계 순매수추이| |
|[국내주식]시세분석|국내주식 체결금액별 매매비중| |
|[국내주식]시세분석|국내 증시자금 종합| |
|[국내주식]시세분석|관심종목 그룹별 종목조회| |
|[국내주식]시세분석|관심종목 그룹조회| |
|[국내주식]시세분석|관심종목(멀티종목) 시세조회| |
|[국내주식]시세분석|국내주식 상하한가 포착| |
|[국내주식]시세분석|회원사 실시간 매매동향(틱)| |
|[국내주식]시세분석|국내주식 매물대/거래비중| |
|[국내주식]시세분석|주식현재가 회원사 종목매매동향| |
|[국내주식]순위분석|거래량순위| |
|[국내주식]순위분석|국내주식 등락률 순위| |
|[국내주식]순위분석|국내주식 호가잔량 순위| |
|[국내주식]순위분석|국내주식 수익자산지표 순위| |
|[국내주식]순위분석|국내주식 시가총액 상위| |
|[국내주식]순위분석|국내주식 재무비율 순위| |
|[국내주식]순위분석|국내주식 시간외잔량 순위| |
|[국내주식]순위분석|국내주식 우선주/괴리율 상위| |
|[국내주식]순위분석|국내주식 이격도 순위| |
|[국내주식]순위분석|국내주식 시장가치 순위| |
|[국내주식]순위분석|국내주식 체결강도 상위| |
|[국내주식]순위분석|국내주식 관심종목등록 상위| |
|[국내주식]순위분석|국내주식 예상체결 상승/하락상위| |
|[국내주식]순위분석|국내주식 당사매매종목 상위| |
|[국내주식]순위분석|국내주식 신고/신저근접종목 상위| |
|[국내주식]순위분석|국내주식 대량체결건수 상위| |
|[국내주식]순위분석|국내주식 공매도 상위종목| |
|[국내주식]순위분석|국내주식 신용잔고 상위| |
|[국내주식]순위분석|국내주식 배당률 상위| |
|[국내주식]순위분석|국내주식 시간외등락율순위| |
|[국내주식]순위분석|국내주식 시간외거래량순위| |
|[국내주식]순위분석|HTS조회상위20종목| |
|[국내주식]실시간시세|국내주식 실시간체결가|⭕|
|[국내주식]실시간시세|국내주식 실시간호가|⭕|
|[국내주식]실시간시세|국내주식 실시간체결통보|⭕|
|[국내주식]실시간시세|국내지수 실시간체결| |
|[국내주식]실시간시세|국내지수 실시간예상체결| |
|[국내주식]실시간시세|국내지수 실시간프로그램매매| |
|[국내주식]실시간시세|국내주식 실시간회원사| |
|[국내주식]실시간시세|국내주식 실시간프로그램매매| |
|[국내주식]실시간시세|국내주식 장운영정보| |
|[국내주식]실시간시세|국내주식 실시간예상체결| |
|[국내주식]실시간시세|ELW 실시간체결가| |
|[국내주식]실시간시세|ELW 실시간호가| |
|[국내주식]실시간시세|국내주식 시간외 실시간체결가| |
|[국내주식]실시간시세|국내주식 시간외 실시간예상체결| |
|[국내주식]실시간시세|국내주식 시간외 실시간호가| |
|[국내주식]실시간시세|ELW 실시간예상체결| |
|[국내주식]실시간시세|국내ETF NAV추이| |
|[국내주식]실시간시세|국내주식 실시간체결가 (NXT)| |
|[국내주식]실시간시세|국내주식 실시간호가 (NXT)| |
|[국내주식]실시간시세|국내지수 실시간예상체결 (NXT)| |
|[국내주식]실시간시세|국내지수 실시간프로그램매매 (NXT)| |
|[국내주식]실시간시세|국내주식 실시간회원사 (NXT)| |
|[국내주식]실시간시세|국내주식 장운영정보 (NXT)| |
|[국내주식]실시간시세|국내주식 실시간체결가 (통합)||
|[국내주식]실시간시세|국내주식 실시간호가 (통합)||
|[국내주식]실시간시세|국내지수 실시간예상체결 (통합)| |
|[국내주식]실시간시세|국내지수 실시간프로그램매매 (통합)| |
|[국내주식]실시간시세|국내주식 실시간회원사 (통합)| |
|[국내주식]실시간시세|국내주식 장운영정보 (통합)| |
|[국내선물옵션]주문/계좌|선물옵션 주문|⭕|
|[국내선물옵션]주문/계좌|선물옵션 정정취소주문|⭕|
|[국내선물옵션]주문/계좌|선물옵션 주문체결내역조회|⭕|
|[국내선물옵션]주문/계좌|선물옵션 잔고현황|⭕|
|[국내선물옵션]주문/계좌|선물옵션 주문가능|⭕|
|[국내선물옵션]주문/계좌|(야간)선물옵션 주문체결내역조회| |
|[국내선물옵션]주문/계좌|(야간)선물옵션 잔고현황| |
|[국내선물옵션]주문/계좌|(야간)선물옵션 주문가능| |
|[국내선물옵션]주문/계좌|선물옵션 잔고정산손익내역| |
|[국내선물옵션]주문/계좌|선물옵션 총자산현황| |
|[국내선물옵션]주문/계좌|선물옵션 잔고평가손익내역| |
|[국내선물옵션]주문/계좌|선물옵션 기준일체결내역| |
|[국내선물옵션]주문/계좌|선물옵션기간약정수수료일별| |
|[국내선물옵션]주문/계좌|(야간)선물옵션 증거금 상세| |
|[국내선물옵션]기본시세|선물옵션 시세|⭕|
|[국내선물옵션]기본시세|선물옵션 시세호가|⭕|
|[국내선물옵션]기본시세|선물옵션기간별시세(일/주/월/년)|⭕|
|[국내선물옵션]기본시세|선물옵션 분봉조회| |
|[국내선물옵션]기본시세|선물옵션 일중예상체결추이| |
|[국내선물옵션]기본시세|국내선물 기초자산 시세|⭕|
|[국내선물옵션]기본시세|국내옵션전광판_옵션월물리스트| |
|[국내선물옵션]기본시세|국내옵션전광판_선물| |
|[국내선물옵션]기본시세|국내옵션전광판_콜풋| |
|[국내선물옵션]실시간시세|지수선물 실시간체결가| |
|[국내선물옵션]실시간시세|지수선물 실시간호가| |
|[국내선물옵션]실시간시세|선물옵션 실시간체결 통보| |
|[국내선물옵션]실시간시세|지수옵션 실시간체결가| |
|[국내선물옵션]실시간시세|지수옵션 실시간호가| |
|[국내선물옵션]실시간시세|상품선물 실시간체결가| |
|[국내선물옵션]실시간시세|상품선물 실시간호가| |
|[국내선물옵션]실시간시세|주식선물 실시간체결가| |
|[국내선물옵션]실시간시세|주식선물 실시간호가| |
|[국내선물옵션]실시간시세|주식옵션 실시간체결가| |
|[국내선물옵션]실시간시세|주식옵션 실시간호가| |
|[국내선물옵션]실시간시세|지수선물 실시간체결가| |
|[국내선물옵션]실시간시세|지수선물 실시간호가| |
|[국내선물옵션]실시간시세|선물옵션 실시간체결 통보|⭕|
|[국내선물옵션]실시간시세|지수옵션 실시간체결가| |
|[국내선물옵션]실시간시세|지수옵션 실시간호가| |
|[국내선물옵션]실시간시세|상품선물 실시간체결가| |
|[국내선물옵션]실시간시세|상품선물 실시간호가| |
|[국내선물옵션]실시간시세|주식선물 실시간체결가| |
|[국내선물옵션]실시간시세|주식선물 실시간호가| |
|[국내선물옵션]실시간시세|주식선물 실시간예상체결| |
|[국내선물옵션]실시간시세|주식옵션 실시간체결가| |
|[국내선물옵션]실시간시세|주식옵션 실시간호가| |
|[국내선물옵션]실시간시세|주식옵션 실시간예상체결| |
|[국내선물옵션]실시간시세|KRX야간옵션 실시간체결가| |
|[국내선물옵션]실시간시세|KRX야간옵션 실시간호가| |
|[국내선물옵션]실시간시세|KRX야간옵션 실시간예상체결| |
|[국내선물옵션]실시간시세|KRX야간옵션 실시간체결통보| |
|[국내선물옵션]실시간시세|CME야간선물 실시간체결가| |
|[국내선물옵션]실시간시세|CME야간선물 실시간호가| |
|[국내선물옵션]실시간시세|CME야간선물 실시간체결통보| |
|[해외주식]주문/계좌|해외주식 주문|⭕|
|[해외주식]주문/계좌|해외주식 정정취소주문|⭕|
|[해외주식]주문/계좌|해외주식 예약주문접수|⭕|
|[해외주식]주문/계좌|해외주식 예약주문접수취소|⭕|
|[해외주식]주문/계좌|해외주식 미체결내역|⭕|
|[해외주식]주문/계좌|해외주식 잔고|⭕|
|[해외주식]주문/계좌|해외주식 주문체결내역|⭕|
|[해외주식]주문/계좌|해외주식 체결기준현재잔고|⭕|
|[해외주식]주문/계좌|해외주식 예약주문조회| |
|[해외주식]주문/계좌|해외주식 매수가능금액조회| |
|[해외주식]주문/계좌|해외주식 미국주간주문| |
|[해외주식]주문/계좌|해외주식 미국주간정정취소| |
|[해외주식]주문/계좌|해외주식 기간손익| |
|[해외주식]주문/계좌|해외증거금 통화별조회| |
|[해외주식]주문/계좌|해외주식 일별거래내역| |
|[해외주식]주문/계좌|해외주식 결제기준잔고| |
|[해외주식]주문/계좌|해외주식 지정가체결내역조회| |
|[해외주식]주문/계좌|해외주식 지정가주문번호조회| |
|[해외주식]기본시세|해외주식 현재체결가|⭕|
|[해외주식]기본시세|해외주식 기간별시세|⭕|
|[해외주식]기본시세|해외주식 종목/지수/환율기간별시세(일/주/월/년)|⭕|
|[해외주식]기본시세|해외주식 조건검색|⭕|
|[해외주식]기본시세|해외결제일자조회| |
|[해외주식]기본시세|해외주식 현재가상세| |
|[해외주식]기본시세|해외주식분봉조회| |
|[해외주식]기본시세|해외지수분봉조회| |
|[해외주식]기본시세|해외주식 상품기본정보| |
|[해외주식]기본시세|해외주식 현재가 1호가| |
|[해외주식]기본시세|해외주식 업종별코드조회| |
|[해외주식]기본시세|해외주식 업종별시세| |
|[해외주식]시세분석|해외주식 기간별권리조회| |
|[해외주식]시세분석|해외뉴스종합(제목)| |
|[해외주식]시세분석|해외주식 권리종합| |
|[해외주식]시세분석|당사 해외주식담보대출 가능 종목| |
|[해외주식]시세분석|해외속보(제목)| |
|[해외주식]시세분석|해외주식 거래증가율순위| |
|[해외주식]시세분석|해외주식 가격급등락| |
|[해외주식]시세분석|해외주식 거래대금순위| |
|[해외주식]시세분석|해외주식 거래량급증| |
|[해외주식]시세분석|해외주식 신고/신저가| |
|[해외주식]시세분석|해외주식 매수체결강도상위| |
|[해외주식]시세분석|해외주식 거래회전율순위| |
|[해외주식]시세분석|해외주식 시가총액순위| |
|[해외주식]시세분석|해외주식 상승율/하락율| |
|[해외주식]실시간시세|해외주식 실시간호가| |
|[해외주식]실시간시세|해외주식 지연호가(아시아)| |
|[해외주식]실시간시세|해외주식 실시간지연체결가| |
|[해외주식]실시간시세|해외주식 실시간체결통보| |
|[해외선물옵션]주문/계좌|해외선물옵션 주문| |
|[해외선물옵션]주문/계좌|해외선물옵션 정정취소주문| |
|[해외선물옵션]주문/계좌|해외선물옵션 당일주문내역조회| |
|[해외선물옵션]주문/계좌|해외선물옵션 미결제내역조회(잔고)| |
|[해외선물옵션]주문/계좌|해외선물옵션 주문가능조회| |
|[해외선물옵션]주문/계좌|해외선물옵션 기간계좌손익 일별| |
|[해외선물옵션]주문/계좌|해외선물옵션 일별 체결내역| |
|[해외선물옵션]주문/계좌|해외선물옵션 예수금현황| |
|[해외선물옵션]주문/계좌|해외선물옵션 일별 주문내역| |
|[해외선물옵션]주문/계좌|해외선물옵션 기간계좌거래내역| |
|[해외선물옵션]주문/계좌|해외선물옵션 증거금상세| |
|[해외선물옵션]기본시세|해외선물종목상세|⭕|
|[해외선물옵션]기본시세|해외선물종목현재가|⭕|
|[해외선물옵션]기본시세|해외선물 분봉조회| |
|[해외선물옵션]기본시세|해외선물 체결추이(주간)| |
|[해외선물옵션]기본시세|해외선물 체결추이(일간)| |
|[해외선물옵션]기본시세|해외선물 체결추이(틱)| |
|[해외선물옵션]기본시세|해외선물 체결추이(월간)| |
|[해외선물옵션]기본시세|해외선물 호가| |
|[해외선물옵션]기본시세|해외선물 상품기본정보| |
|[해외선물옵션]기본시세|해외선물 장운영시간| |
|[해외선물옵션]기본시세|해외선물 미결제추이| |
|[해외선물옵션]기본시세|해외옵션 호가| |
|[해외선물옵션]실시간시세|해외선물옵션 실시간체결가| |
|[해외선물옵션]실시간시세|해외선물옵션 실시간호가| |
|[해외선물옵션]실시간시세|해외선물옵션 실시간주문내역통보| |
|[해외선물옵션]실시간시세|해외선물옵션 실시간체결내역통보| |
|[장내채권]주문/계좌|장내채권 매도주문| |
|[장내채권]주문/계좌|장내채권 매수주문| |
|[장내채권]주문/계좌|장내채권 정정취소주문| |
|[장내채권]주문/계좌|채권정정취소가능주문조회| |
|[장내채권]주문/계좌|장내채권 주문체결내역| |
|[장내채권]주문/계좌|장내채권 잔고조회| |
|[장내채권]주문/계좌|장내채권 매수가능조회| |
|[장내채권]기본시세|장내채권 발행정보| |
|[장내채권]기본시세|장내채권 기본조회| |
|[장내채권]기본시세|장내채권현재가(호가)| |
|[장내채권]기본시세|장내채권 평균단가조회| |
|[장내채권]기본시세|장내채권 기간별시세(일)| |
|[장내채권]기본시세|장내채권현재가(시세)| |
|[장내채권]기본시세|장내채권현재가(체결)| |
|[장내채권]기본시세|장내채권현재가(일별)| |
|[장내채권]실시간시세|일반채권 실시간체결가| |
|[장내채권]실시간시세|일반채권 실시간호가| |
|[장내채권]실시간시세|채권지수 실시간체결가| |
## 4. API 예제 목록
|구분|예제명|언어|상세|
|--|--|--|--|
|Sample01|kis_api01.py|python|[국내주식] 주문/계좌조회/시세 호출|
|Sample01|kis_api02.py|python|[국내선물옵션] 주문/계좌조회/시세 호출|
|Sample01|kis_api03.py|python|[해외주식] 주문/계좌조회/시세 호출|
|Sample01|kis_api04.py|python|[해외선물옵션] 주문/계좌조회/시세 호출|
|Sample01|kis_auth.py|python|토큰 발급 관리 및 공통 샘플|
|Sample01|kis_devlp.yaml|yaml|사용자 환경 샘플|
|Sample01|kis_domfuopt.py|python|[국내선물옵션] 주문/계좌조회/시세 함수|
|Sample01|kis_domstk.py|python|[국내주식] 주문/계좌조회/시세 함수|
|Sample01|kis_domstk_ws.py|python|[국내주식] 실시간(웹소켓) 체결/호가/계좌체결발생+REST 호출 함수|
|Sample01|kis_ovrseafuopt.py|python|[해외선물옵션] 주문/계좌조회/시세 함수|
|Sample01|kis_ovrseafuopt_ws.py|python|[해외선물옵션] 실시간(웹소켓) 체결/호가/계좌주문내역발생/계좌체결발생+REST 호출 함수|
|Sample01|kis_ovrseastk.py|python|[해외주식] 주문/계좌조회/시세 함수|
|Sample01|kis_ovrseastk_ws.py|python|[해외주식] 실시간(웹소켓) 체결/호가/계좌체결발생+REST 호출 함수|
|rest|current_price_sample.py|python|국내주식 현재가 조회 샘플|
|rest|get_interest_stocks_price.py|python|관심 종목 복수 시세 조회 샘플|
|rest|get_ovsfut_chart_price.py|python|해외선물 분봉 다음 조회 샘플|
|rest|get_ovsstk_chart_price.py|python|해외주식 분봉 다음 조회 샘플|
|rest|kis_api.py|python|국내주식 주문 샘플|
|rest|kis_auth.py|python|OAuth 접근 토큰 발급 및 갱신 관련 인증 관리|
|websocket|ops_ws_sample.py|python|국내주식 웹소켓 연결|
|websocket|ws_commodity_future.py|python|상품선물옵션 실시간시세 일회성 구독|
|websocket|ws_domestic+overseas_stock.py|python|국내/해외주식 실시간시세 일회성 구독|
|websocket|ws_domestic_future.py|python|국내주식 실시간시세 일회성 구독|
|websocket|ws_domestic_overseas_all.py|python|국내/해외·주식/선물옵션, 장내채권 실시간시세 일회성 구독|
|websocket|ws_domestic_stock.py|python|국내주식 실시간시세 일회성 구독|
|websocket|ws_overseas_future.py|python|해외선물옵션 실시간시세 일회성 구독|
|websocket|ws_overseas_stock.py|python|해외주식 실시간시세 일회성 구독|
## 5. Wikidocs(참고 교안)
|구분|도서명|지원언어|링크|
|--|--|--|--|
|rest|파이썬으로 배우는 오픈API 트레이딩 초급 예제|python|https://wikidocs.net/book/7559|
|websocket|파이썬으로 배우는 한국투자증권 Websocket 사용 예제|python|https://wikidocs.net/book/7847|
![Footer](https://capsule-render.vercel.app/api?type=waving&color=gradient&height=200&section=footer)

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() # 실시간 웹소켓 연결 작동

View File

@@ -0,0 +1,499 @@
using System.Data;
using System.Text;
using YamlDotNet.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace KIS_Common {
class Common
{
private static string configRoot = @"d:\KIS\config\";
private static string tokenTmp = configRoot + "KIS" + DateTime.Now.ToString("yyyyMMdd");
private static Dictionary<string, string> _cfg;
public static (string my_app, string my_sec , string my_acct, string my_prod, string my_token, string my_url) _TRENV;
private static DateTime _lastAuthTime;
private static bool _DEBUG = false;
public static bool _isPaper = false;
private static Dictionary<string, string> _baseHeaders;
private static string _mode = "prod";
public static int _rescode;
public static HttpResponseMessage _resp;
public static Dictionary<string, string> _header;
public static Dictionary<string, object> _body;
public static string _errCode;
public static string _errMessage;
public static async Task doAuth(String mode)
{
if (!File.Exists(tokenTmp)){
File.Create(tokenTmp).Close();
}
using (var reader = new StreamReader(configRoot + "kis_devlp.yaml", Encoding.UTF8)){
var deserializer = new Deserializer();
_cfg = deserializer.Deserialize<Dictionary<string, string>>(reader);
}
_baseHeaders = new Dictionary<string, string>{
{ "Content-Type", "application/json" },
{ "Accept", "text/plain" },
{ "charset", "UTF-8" },
{ "User-Agent", _cfg["my_agent"] }
};
if(mode.Equals("V")) {
_DEBUG = true;
_mode = "vps";
await Auth(_mode, _cfg["my_prod"]);
}
else if(mode.Equals("D")) {
_DEBUG = true;
_mode = "dev";
await Auth(_mode, _cfg["my_prod"]);
}
else {
_DEBUG = false;
_mode = "prod";
await Auth(_mode, _cfg["my_prod"]);
}
}
private static void SaveToken(string myToken, string myExpired, string mode)
{
Console.WriteLine("===== Exp " + myExpired);
using (var writer = new StreamWriter(tokenTmp, false, Encoding.UTF8))
{
writer.WriteLine($"token; {myToken}");
writer.WriteLine($"valid-date; {myExpired}");
writer.Write($"mode; {mode}");
}
}
private static string? ReadToken()
{
try
{
var lines = File.ReadAllLines(tokenTmp, Encoding.UTF8);
var tokenData = lines.Select(line => line.Split(';')).ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim());
string expDt = DateTime.Parse(tokenData["valid-date"]).ToString("yyyy-MM-dd HH:mm:ss");
string nowDt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
string getMode = tokenData["mode"];
Console.WriteLine("Mode : " + getMode);
if (string.Compare(expDt, nowDt) > 0 && _mode.Equals(getMode)){
return tokenData["token"];
}
else{
return null;
}
}
catch
{
return null;
}
}
private static Dictionary<string, string> GetBaseHeader()
{
DateTime n2 = DateTime.Now;
if ((n2 - _lastAuthTime).TotalSeconds >= 86400){
ReAuth(_mode, _cfg["my_prod"]);
}
return new Dictionary<string, string>(_baseHeaders);
}
private static (string my_app, string my_sec , string my_acct, string my_prod, string my_token, string my_url) GetTRENV()
{
return _TRENV;
}
public static void SetTRENV(Dictionary<string, string> cfg)
{
_TRENV = (cfg["my_app"],cfg["my_sec"],cfg["my_acct"],cfg["my_prod"],cfg["my_token"],cfg["my_url"]);
}
private static void ChangeTREnv(string tokenKey, string svr, string product)
{
var cfg = new Dictionary<string, string>();
if (svr == "prod")
{
cfg["my_app"] = _cfg["my_app"];
cfg["my_sec"] = _cfg["my_sec"];
cfg["my_acct"] = _cfg["my_acct"];
_isPaper = false;
}
else if (svr == "vps")
{
cfg["my_app"] = _cfg["paper_app"];
cfg["my_sec"] = _cfg["paper_sec"];
cfg["my_acct"] = _cfg["paper_acct"];
_isPaper = true;
}
else {
cfg["my_app"] = _cfg["dev_app"];
cfg["my_sec"] = _cfg["dev_sec"];
cfg["my_acct"] = _cfg["dev_acct"];
_isPaper = false;
}
cfg["my_prod"] = product;
cfg["my_token"] = tokenKey;
cfg["my_url"] = _cfg[svr];
SetTRENV(cfg);
}
private static async Task<string?>Auth(string svr, string product)
{
var p = new Dictionary<string, string>{
{ "grant_type", "client_credentials" }
};
string ak1 = svr == "prod" ? "my_app" : (svr == "vps" ? "paper_app" : "dev_app");
string ak2 = svr == "prod" ? "my_sec" : (svr == "vps" ? "paper_sec" : "dev_sec");
p["appkey"] = _cfg[ak1];
p["appsecret"] = _cfg[ak2];
string savedToken = ReadToken();
if (savedToken == null){
var url = $"{_cfg[svr]}/oauth2/tokenP";
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = new StringContent(JsonConvert.SerializeObject(p), Encoding.UTF8, "application/json");
var response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode){
var jsonData = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync());
string myToken = jsonData["access_token"];
string myExpired = jsonData["access_token_token_expired"];
SaveToken(myToken, myExpired, svr);
savedToken = ReadToken();
ChangeTREnv($"Bearer {savedToken}", svr, product);
}
else{
Console.WriteLine("Get Authentication token fail!\nYou have to restart your app!!!");
return null;
}
}
else{
ChangeTREnv($"Bearer {savedToken}", svr, product);
}
_baseHeaders["authorization"] = _TRENV.Item5;
_baseHeaders["appkey"] = _TRENV.Item1;
_baseHeaders["appsecret"] = _TRENV.Item2;
_lastAuthTime = DateTime.Now;
if (_DEBUG)
{
Console.WriteLine($"[{_lastAuthTime}] => get AUTH Key completed!");
}
return null;
}
private static async void ReAuth(string svr, string product)
{
await Auth(svr, product);
}
private static Dictionary<string, string> GetEnv()
{
return _cfg;
}
public static (string my_app, string my_sec , string my_acct, string my_prod, string my_token, string my_url) GetTREnv()
{
return _TRENV;
}
private static async Task SetOrderHashKey(Dictionary<string, string> h, Dictionary<string, string> p)
{
string url = $"{GetTREnv().my_url}/uapi/hashkey";
using (var client = new HttpClient())
{
var content = new StringContent(JsonConvert.SerializeObject(p), Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
var jsonData = JsonConvert.DeserializeObject<Dictionary<string, string>>(await response.Content.ReadAsStringAsync());
h["hashkey"] = jsonData["HASH"];
}
else
{
Console.WriteLine("Error: " + response.StatusCode);
}
}
}
private static bool IsPaperTrading()
{
return _isPaper;
}
// 응답값 사용을 위한 변수 세팅 메서드
public static void SetAPIRespVal(HttpResponseMessage resp)
{
_rescode = (int)resp.StatusCode;
_resp = resp;
_header = _SetHeader();
_body = _SetBody();
_errCode = (string)_body["msg_cd"];
_errMessage = (string)_body["msg1"];
}
// 응답코드 리턴값
public int GetResCode()
{
return _rescode;
}
// 응답메시지 리턴값
public HttpResponseMessage GetResponse()
{
return _resp;
}
// 트랜잭션 에러코드 리턴값
public string GetErrorCode()
{
return _errCode;
}
// 트랜잭션 에러메시지 리턴값
public string GetErrorMessage()
{
return _errMessage;
}
// 헤더 지정
private static Dictionary<string, string> _SetHeader()
{
return _resp.Headers.ToDictionary(x => x.Key.ToLower(), x => string.Join(", ", x.Value));
}
// 헤더 수신
public Dictionary<string, string> GetHeader()
{
return _header;
}
// 바디 지정
private static Dictionary<string, object> _SetBody()
{
return JsonConvert.DeserializeObject<Dictionary<string, object>>(_resp.Content.ReadAsStringAsync().Result);
}
// 바디 수신
public Dictionary<string, object> GetBody()
{
return _body;
}
// 트랜잭션 정상여부 확인 부울 값
public bool IsOK()
{
return (string) _body["rt_cd"] == "0";
}
// 디버그 출력용
public static void PrintAll()
{
Common ka = new Common();
Console.WriteLine("<Header>");
foreach (var x in ka.GetHeader().Keys)
{
Console.WriteLine($"\t-{x}: {ka.GetHeader()[x]}");
}
Console.WriteLine("<Body>");
foreach (var x in ka.GetBody().Keys)
{
Console.WriteLine($"\t-{x}: {ka.GetBody()[x]}");
}
}
// 에러 메시지 출력용
public void PrintError(string url)
{
Console.WriteLine("-------------------------------\nError in response: " + GetResCode() + " url=" + url);
Console.WriteLine("rt_cd : " + GetBody()["rt_cd"] + " / msg_cd : " + GetErrorCode() + " / msg1 : " + GetErrorMessage());
Console.WriteLine("-------------------------------");
}
// 특정 JToken 결과값을 DataTable로 변환하는 메서드
public static DataTable ConvertToDataTable(JToken? data){
DataTable dataTable = new DataTable();
JArray jArray = new JArray();
if (data is null) {
Console.WriteLine("조회 값 없음");
return null;
}
else {
Console.WriteLine("Return Type : " + data.Type.ToString());
}
if (data.Type.ToString().Equals("Array")) {
jArray = (JArray) data;
}
else {
jArray.Add(data);
}
if (jArray.Count > 0)
{
// Add columns to DataTable
foreach (var firstRow in jArray.Children<JObject>())
{
foreach (var property in firstRow.Properties())
{
if (!dataTable.Columns.Contains(property.Name))
{
dataTable.Columns.Add(property.Name);
}
}
break;
}
// Add rows to DataTable
foreach (var row in jArray.Children<JObject>())
{
DataRow dataRow = dataTable.NewRow();
foreach (var property in row.Properties())
{
dataRow[property.Name] = property.Value.ToString();
}
dataTable.Rows.Add(dataRow);
}
}
return dataTable;
}
// JObject 결과값을 DataTable로 변환하는 메서드
public static DataTable ConvertToDataTable(JObject data)
{
var dataTable = new DataTable();
Console.WriteLine("Return Type : " + data.Type.ToString());
foreach (var property in data.Properties())
{
dataTable.Columns.Add(property.Name);
}
var row = dataTable.NewRow();
foreach (var property in data.Properties())
{
row[property.Name] = property.Value;
}
dataTable.Rows.Add(row);
return dataTable;
}
// DataTable 출력용
public static void PrintDataTable(DataTable dataTable)
{
string line = "";
foreach (DataColumn item in dataTable.Columns)
{
line += item.ColumnName +" ";
}
line += "\n";
foreach (DataRow row in dataTable.Rows)
{
for (int i = 0; i < dataTable.Columns.Count; i++)
{
line += row[i].ToString() + " ";
}
line += "\n";
}
Console.WriteLine("==============================================================================");
Console.WriteLine(line) ;
}
// API 호출 및 응답값 회신
public static Task<HttpResponseMessage> UrlFetch(Dictionary<string, object> paramsDict, string apiUrl = "", string ptrId = "", string trCont = "", Dictionary<string, string>? appendHeaders = null, bool postFlag = true , bool hashFlag = false)
{
// 요청 URL
string url = $"{GetTREnv().my_url}{apiUrl}";
// HTTPClient 사용, RestSharp 사용 시 별도 코드 변환 필요
var client = new HttpClient();
HttpResponseMessage resp = new HttpResponseMessage();
// 최대 타임아웃 설정 → 기본 100ms, 네트워크 상황에 따라 TimeSpan으로 타임아웃 시간 설정
client.Timeout = TimeSpan.FromMinutes(10);
// 요청 헤더 설정
var headers = GetBaseHeader();
string trId = ptrId;
if (IsPaperTrading()){ //모의투자인 경우, 트랜잭션 ID 앞에 'V'가 붙음
if (ptrId[0] == 'T' || ptrId[0] == 'J' || ptrId[0] == 'C'){
trId = "V" + ptrId.Substring(1);
}
}
headers["tr_id"] = trId;
headers["custtype"] = "P";
headers["tr_cont"] = trCont;
if (appendHeaders != null && appendHeaders.Count > 0)
{
foreach (var x in appendHeaders.Keys)
{
headers[x] = appendHeaders[x];
}
}
// 클라이언트 요청 헤더 값에 최종 설정
foreach (var x in headers) {
client.DefaultRequestHeaders.TryAddWithoutValidation(x.Key , x.Value);
}
// 디버그 기능 사용 시, 출력용
if (_DEBUG)
{
Console.WriteLine("< Sending Info >");
Console.WriteLine($"URL: {url}, TR: {trId}");
Console.WriteLine($"<header>\n{string.Join(", ", headers.Select(h => $"{h.Key}: {h.Value}"))}");
Console.WriteLine($"<body>\n{string.Join(", ", paramsDict.Select(p => $"{p.Key}: {p.Value}"))}");
}
// POST 방식, GET 방식에 따른 분기
if (postFlag){
resp = client.PostAsync(url, new StringContent(JsonConvert.SerializeObject(paramsDict), Encoding.UTF8, "application/json")).ConfigureAwait(false).GetAwaiter().GetResult();
}
else{
resp = client.GetAsync(url + "?" + string.Join("&", paramsDict.Select(p => $"{p.Key}={p.Value}"))).ConfigureAwait(false).GetAwaiter().GetResult();
}
// 응답코드가 성공으로 나온 경우 결과 값을 최종 설정함. 오류인 경우에는 오류코드와 메시지 출력
if (resp.IsSuccessStatusCode){
SetAPIRespVal(resp);
if (_DEBUG) { PrintAll(); }
}
else{
Console.WriteLine("Error Code : " + (int)resp.StatusCode + " | " + resp.Content);
}
return Task.FromResult(resp);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,166 @@
using System.Data;
using KIS_Common;
using KIS_Domestic;
using KIS_Oversea;
public class Program {
public static void Main(String [] args) {
Program p = new Program();
p.CallForeignStock();
}
public async void CallForeignStock() {
// 파이썬에서 Dataframe 으로 처리하던 방식을 DataTable, DataRow 사용방식으로 변경
DataTable rt_data = new DataTable();
// [공통] 토큰 발급 및 갱신 처리
// 별도 경로 지정한 yaml 파일 값을 읽어와서 처리
// 사용 인수 : P (실전투자) 또는 V (모의투자)
await Common.doAuth("D");
// [해외주식] 주문/계좌 ===================================================================================================================================
// [해외주식] 주문/계좌 > 주문 (매수매도구분 buy,sell + 종목코드6자리 + 주문수량 + 주문단가)
// 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 KIS_OvrseaStk.cs GetOverseasOrder 수정요망!
// rt_data = KIS_OverseaStk.get_overseas_order(ord_dv="buy", excg_cd="NASD", itm_no="TSLA", qty=1, unpr=170)
// rt_data = KIS_OverseaStk.get_overseas_order(ord_dv="buy", excg_cd="NASD", itm_no="AAPL", qty=1, unpr=216.75)
rt_data = KIS_OverseaStk.GetOverseasOrder ("buy", "AMEX", "SGOV", "00", 1, 4335);
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data); // # 주문접수조직번호+주문접수번호+주문시각
// [해외주식] 주문/계좌 > 정정취소주문 (해외거래소코드excg_cd + 종목코드itm_no + 주문번호orgn_odno + 정정취소구분rvse_cncl_dvsn_cd + 수량qty + 주문단가unpr)
// 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 KIS_OvrseaStk.cs GetOverseasOrder 수정요망!
rt_data = KIS_OverseaStk.GetOverseasOrderRvseCncl("NASD", "TSLA", "0030089601", "02", 1, 0);
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data); // # 주문접수조직번호+주문접수번호+주문시각
// [해외주식] 주문/계좌 > 해외주식 미체결내역 (해외거래소코드)
// 해외거래소코드 NASD:나스닥,NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
rt_data = KIS_OverseaStk.GetOverseasInquireNccs("NYSE");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 해외주식 미체결전량취소주문 (해외거래소코드excg_cd + 종목코드itm_no)
// 해외거래소코드 NASD:나스닥,NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
rt_data = KIS_OverseaStk.GetOverseasOrderAllCncl("NASD", "");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 해외주식 잔고 현황
// 해외거래소코드 NASD:나스닥,NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
// 거래통화코드 - USD : 미국달러,HKD : 홍콩달러,CNY : 중국위안화,JPY : 일본엔화,VND : 베트남동
rt_data = KIS_OverseaStk.GetOverseasInquireBalance("NASD", "");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 해외주식 잔고 내역
// 해외거래소코드 NASD:나스닥,NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
// 거래통화코드 - USD : 미국달러,HKD : 홍콩달러,CNY : 중국위안화,JPY : 일본엔화,VND : 베트남동
rt_data = KIS_OverseaStk.GetOverseasInquireBalanceLst("NASD", "");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 해외주식 주문체결내역
// 해외거래소코드 NASD:미국시장 전체(나스닥,뉴욕,아멕스),NYSE:뉴욕,AMEX:아멕스,SEHK:홍콩,SHAA:중국상해,SZAA:중국심천,TKSE:일본,HASE:베트남하노이,VNSE:호치민
rt_data = KIS_OverseaStk.GetOverseasInquireCcnl("", "", "");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(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 = KIS_OverseaStk.GetOverseasInquirePresentBalance("02", "01", "000", "00", "00");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 미국주간주문 (매수매도구분 buy,sell + 종목번호 + 주문수량 + 주문단가)
// 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 KIS_OvrseaStk.cs GetOverseasOrder 수정요망
// !! 현재 미국주간주문은 일시적으로 중단되어 있으며, 추후 재개는 한국투자증권 홈페이지를 통해 공지 예정입니다.
// rt_data = KIS_OverseaStk.GetOverseasDaytimeOrder("buy", "NASD", "TSLA", 1, 251);
// rt_data = KIS_OverseaStk.GetOverseasDaytimeOrder("buy", "NASD", "AAPL", 1, 216.75);
rt_data = KIS_OverseaStk.GetOverseasDaytimeOrder("buy", "NASD", "NVDA", 1, 123.3);
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 미국주간정정취소 (해외거래소코드excg_cd + 종목코드itm_no + 주문번호orgn_odno + 정정취소구분rvse_cncl_dvsn_cd + 수량qty + 주문단가unpr)
// 지정가 기준이며 시장가 옵션(주문구분코드)을 사용하는 경우 KIS_OvrseaStk.cs GetOverseasOrder 수정요망!
// !! 현재 미국주간주문은 일시적으로 중단되어 있으며, 추후 재개는 한국투자증권 홈페이지를 통해 공지 예정입니다.
rt_data = KIS_OverseaStk.GetOverseasDaytimeOrderRvseCncl("NASD", "TSLA", "0030089601", "02", 1, 0);
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data); // 주문접수조직번호+주문접수번호+주문시각
// [해외주식] 주문/계좌 > 해외주식 기간손익[v1_해외주식-032] (해외거래소코드 + 통화코드 + 종목번호 6자리 + 조회시작일 + 조회종료일)
// 해외거래소코드 - 미입력 : 전체, NASD:미국, SEHK:홍콩, SHAA:중국 상해, TKSE:일본, HASE:베트남
rt_data = KIS_OverseaStk.GetOverseasInquirePeriodProfit("", "", "", "20240601", "20240709");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 해외주식 기간손익(매매일자종목별 기간손익) (해외거래소코드 + 통화코드 + 종목번호 6자리 + 조회시작일 + 조회종료일)
rt_data = KIS_OverseaStk.GetOverseasInquirePeriodProfitOutput1("NASD", "" , "", "20240501", "20240709");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 해외증거금 통화별조회
rt_data = KIS_OverseaStk.GetOverseasInquireForeignMargin();
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 해외증거금 일별거래내역 (해외거래소코드 + 매도매수구분코드 + 종목번호 6자리 + 조회시작일 + 조회종료일)
rt_data = KIS_OverseaStk.GetOverseasInquirePeriodTrans("", "", "", "20240601", "20240709");
// [해외주식] 주문/계좌 > 해외증거금 일별거래내역[합계]
rt_data = KIS_OverseaStk.GetOverseasInquirePeriodTransOutput2("", "", "", "20240601", "20240709");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 주문/계좌 > 해외주식 결제기준현재잔고
// dv : 01 보유종목, 02 외화잔고, 03 결제기준현재잔고
// dt : 기준일자(YYYYMMDD)
// dvsn : 01 원화, 02 외화
// inqr_dvsn : 00(전체), 01(일반), 02(미니스탁)
rt_data = KIS_OverseaStk.GetOverseasInquirePaymtStdrBalance("03", "", "01", "00");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 ===================================================================================================================================
// [해외주식] 기본시세 > 해외주식 현재체결가 (해외거래소코드, 종목번호)
rt_data = KIS_OverseaStk.GetOverseasPriceQuotPrice ("NAS", "AAPL");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 > 해외주식 기간별시세
// ※ 기준일(bymd) 지정일자 이후 100일치 조회, 미입력시 당일자 기본 셋팅
rt_data = KIS_OverseaStk.GetOverseasPriceQuotDailyPrice("NAS", "AAPL", "0", "");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 > 해외주식 종목/지수/환율기간별시세(일/주/월/년)
// ※ 기준일(bymd) 지정일자 이후 100일치 조회, 미입력시 당일자 기본 셋팅
rt_data = KIS_OverseaStk.GetOverseasPriceQuotInquireDailyPrice("N", "AAPL" , "", "", "D");
rt_data = KIS_OverseaStk.GetOverseasPriceQuotInquireDailyChartPrice ("N", "AAPL" , "20240605", "20240610", "D");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 > 해외주식조건검색 div 01 : 검색결과종목수, 02:검색결과종목리스트
rt_data = KIS_OverseaStk.GetOverseasPriceQuotInquireSearch ("02", "NAS", "160", "170");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 > 해외결제일자조회 (기준일자)
rt_data = KIS_OverseaStk.GetOverseasPriceQuotCountriesHoliday("");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 > 해외주식 현재가상세 (해외거래소시장코드, 종목코드)
rt_data = KIS_OverseaStk.GetOverseasPriceQuotPriceDetail("NAS", "AAPL");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 > 해외주식 해외주식분봉조회 (조회구분 div-02:분봉데이터,01:시장별장운영시간, 해외거래소시장코드, 종목코드, 분갭, 전일포함여부)
rt_data = KIS_OverseaStk.GetOverseasPriceQuotInquireTimeItemChartPrice("02", "NAS", "AAPL" , "", "0");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 > 해외주식 해외지수분봉조회 (조회구분 div-02:분봉데이터,01:지수정보, 조건시장분류코드, 입력종목코드, 시간구분코드, 과거데이터포함여부)
rt_data = KIS_OverseaStk.GetOverseasPriceQuotInquireTimeIndexChartPrice("02", "N", "SPX", "0", "Y");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 > 해외주식 상품기본정보 (종목번호, 종목유형)
// 종목유형 : 512 미국 나스닥/513 미국 뉴욕/529 미국 아멕스/515 일본/501 홍콩/543 홍콩CNY/558 홍콩USD/507 베트남 하노이/508 베트남 호치민/551 중국 상해A/552 중국 심천A
rt_data = KIS_OverseaStk.GetOverseasPriceSearchInfo ("AAPL", "512");
//print("종목코드("+rt_data.std_pdno+") 종목명(" +rt_data.prdt_eng_name+") 거래시장(" +rt_data.ovrs_excg_cd+":" +rt_data.tr_mket_name+")")
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
// [해외주식] 기본시세 > 해외주식 현재가 10호가 (조회구분 01:기본시세 02:10호가 , 해외거래소코드, 종목번호)
rt_data = KIS_OverseaStk.GetOverseasPriceInquireAskingPrice("02", "NAS", "AAPL");
if (rt_data is not null && rt_data.Rows.Count > 0 ) Common.PrintDataTable(rt_data);
}
}

View File

@@ -0,0 +1,34 @@
#====| 사용자 환경 샘플 아래 참고하시기 바랍니다. |======================
#====| 본 샘플은 토큰 발급 후 파일 저장 방식이므로 보안강화를 위해 메모리 방식 등 사용자 원하시는 방식으로 구현하시기 바랍니다. |=====
#====| Common.cs에서 환경파일 위치를 사용자가 정하시기 바랍니다. . 2024.05.16 KIS Developers Team |======================
######################################################################################################
#홈페이지에서 API서비스 신청시 받은 Appkey, Appsecret 값 설정
#실전투자
my_app: "실전앱키"
my_sec: "실전앱시크릿"
#모의투자
paper_app: "모의앱키"
paper_sec: "모의앱시크릿"
#계좌번호 앞 8자리
my_acct: "실전계좌"
paper_acct: "모의계좌"
my_acct_stock: "실전계좌(주식계좌)"
my_acct_future: "실전계좌(선물옵션계좌)"
#계좌번호 뒤 2자리
my_prod: "01" # 01 : 국내/해외주식, 03 : 국내선물/옵션, 08 : 해외선물/옵션
#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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,333 @@
#### **[당사에서 제공하는 샘플코드에 대한 유의사항]** ####
- 샘플 코드는 한국투자증권 오픈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%20Postman%20Sample%20Code&descAlignY=51&descAlign=62)
## 1. Postman이란
[포스트맨(Postman)](https://www.postman.com/)은 개발자들이 API를 디자인하고 빌드하고 테스트하고 반복하기 위한 API 플랫폼입니다. 2022년 4월 기준으로 포스트맨의 등록 사용자는 20,000,000명 이상, 개방 API 수는 75,000개로 보고되었으며, 세계 최대의 공개 API 허브입니다. (출처 : [위키백과-포스트맨](https://ko.wikipedia.org/wiki/포스트맨_(소프트웨어)))
## 2. 사전 준비 사항
1. 한국투자증권 홈페이지([실전계좌 개설](https://securities.koreainvestment.com/main/customer/guide/_static/TF04aa090000.jsp)/[모의계좌 개설](https://securities.koreainvestment.com/main/research/virtual/_static/TF07da010000.jsp))에서 계좌 개설 → **계좌번호 준비**
2. [KIS Developers 홈페이지](https://apiportal.koreainvestment.com/)에서 API신청 - 사용할 계좌번호로 API신청 → **APP KEY & APP SECRET 준비**
3. [Postman](https://www.postman.com/downloads/) 설치
## 3. Postman을 활용한 API 호출 방법
### 3.1. Postman 실행 및 json 파일 Import
Postman 실행 후 아래 4개의 json 파일들을 다운로드 하신 후 Import 해주세요. Import하는 방법은 Postman 좌측 상단의 Import 버튼을 누르시고 File 탭에 파일을 끌어다 놓아주세요. 파일이 정상적으로 끌어와지면 아래 이미지와 같이 파일들이 식별되며, 이후 Import 버튼을 누르시면 됩니다.
* 모의계좌만을 사용하시는 경우 1,2번 파일을, 실전계좌만을 사용하시는 경우 3,4번 파일만 Import하셔도 됩니다.
|순번 |파일명 |파일 상세 |
|--|--|--|
|1|모의계좌_POSTMAN_샘플코드_v1.5.json|postman collections json file(모의계좌용)|
|2|모의계좌_POSTMAN_환경변수.json|postman environments json file(모의계좌용)|
|3|실전계좌_POSTMAN_샘플코드_v2.4.json|postman collections json file(실전계좌용)|
|4|실전계좌_POSTMAN_환경변수.json|postman environments json file(실전계좌용)|
![image](https://user-images.githubusercontent.com/87407853/200476672-c9d15708-5476-4703-89c9-29046ca00d37.png)
### 3.2. 환경변수 설정
Import가 완료되면 환경변수 설정을 해줍니다. 모의계좌를 활용하여 API테스트를 하실 경우 왼쪽 바의 Environments의 모의Env를, 실전계좌를 활용하여 API테스트를 하실 경우 실전 Env를 환경변수로 사용합니다. 따라서 사용하실 환경변수 값들을 채워주어야 합니다. 아래 설명대로 값들을 전부 채워 넣어주세요. 값을 채워 넣을 때는 Initial Value, Current Value 모두 값을 넣어주셔야 합니다. (VTS, PROD는 이미 값이 채워져 있으니 수정하지 말아주세요.)
* API 신청한 계좌번호와 해당 계좌의 APPKEY, APPSECRET 값을 채워주시면 됩니다.
* 주식거래계좌(01)의 경우 아래 각 표의 1,3,4번을 채워주시면 되고, 선물옵션계좌(03 or 08)의 경우 아래 각 표의 2,5,6번을 채워주시면 됩니다.
#### 3.2.1. 모의Env의 경우 아래의 값들을 각각 채워 넣어줍니다.
|순번 |환경변수명 |값(Initial Value, Current Value) |값 예시 |
|--|--|--|--|
|1|CANO|본인의 모의계좌 종합계좌번호 8자리(주식)|ex.50012345|
|2|CANO_T|본인의 모의계좌 종합계좌번호 8자리(선물옵션)|ex.60012345|
|3|VTS_APPKEY|홈페이지에서 발급 받은 계좌번호(주식) APP KEY|ex.PSabcmEJH4U9dfewefwJdfsa4P5qewrPdf4n|
|4|VTS_APPSECRET|홈페이지에서 발급 받은 계좌번호(주식) APP SECRET|ex.FoB6uLRLw5o0Ozxsdfkejklskjkr...uFg9Ya0=|
|5|VTT_APPKEY|홈페이지에서 발급 받은 계좌번호(선물옵션) APP KEY|ex.PSabcmEJH4U9dfewefwJdfsa4P5qewrPdf4n|
|6|VTT_APPSECRET|홈페이지에서 발급 받은 계좌번호(선물옵션) APP SECRET|ex.FoB6uLRLw5o0Ozxsdfkejklskjkr...uFg9Ya0=|
#### 3.2.2. 실전Env의 경우 아래의 값들을 각각 채워 넣어줍니다.
|순번 |환경변수명 |값(Initial Value, Current Value) |값 예시 |
|--|--|--|--|
|1|CANO_REAL|본인의 실전계좌 종합계좌번호(주식)|ex.50012345|
|2|CANO_REAL_T|본인의 실전계좌 종합계좌번호(선물옵션)|ex.60012345|
|3|PROD_APPKEY|홈페이지에서 발급 받은 계좌번호(주식) APP KEY|ex.PSabcmEJH4U9dfewefwJdfsa4P5qewrPdf4n|
|4|PROD_APPSECRET|홈페이지에서 발급 받은 계좌번호(주식) APP SECRET|ex.FoB6uLRLw5o0Ozxsdfkejklskjkr...uFg9Ya0=|
|5|PROT_APPKEY|홈페이지에서 발급 받은 계좌번호(선물옵션) APP KEY|ex.PSabcmEJH4U9dfewefwJdfsa4P5qewrPdf4n|
|6|PROT_APPSECRET|홈페이지에서 발급 받은 계좌번호(선물옵션) APP SECRET|ex.FoB6uLRLw5o0Ozxsdfkejklskjkr...uFg9Ya0=|
### 3.3. 환경변수 선택
환경변수 설정이 완료되면 다시 Collections 바로 돌아가셔서 사용할 환경변수를 선택을 해줍니다. 환경변수 선택은 Postman 화면 우측 상단에서 아래 화살표()를 눌러 선택 가능합니다. 모의계좌를 활용하여 API테스트를 하실 경우 모의투자(모의Env)를, 실전계좌를 활용하여 API테스트를 하실 경우 실전투자(실전Env)를 환경변수로 선택합니다.
![image](https://user-images.githubusercontent.com/87407853/200476740-beceda63-4d7e-48e2-ba4e-f69a8a850fea.png)
### 3.4. API 호출
호출하고 싶은 API를 각 폴더에서 찾아 header값, body값을 변경하시면서 사용하시면 됩니다.
* 각 API 이름 앞에 V는 모의계좌를, J는 실전계좌를 의미합니다.
* GET 요청의 경우, 계좌번호 환경변수가 불러와져 그대로 사용하시면 되지만, POST 요청의 경우 계좌번호(CANO)를 BODY값에 직접 입력하셔야 하는 점 유의 부탁드립니다. **(중요) 따라서 POST API 호출 테스트하실 때는 반드시 Body 값의 계좌번호(CANO)를 본인의 종합계좌번호 8자리로 수정 후 호출하셔야 합니다.**
### 4. 코드 자동생성 기능
포스트맨에는 소스코드를 생성하는 기능이 있습니다. 우측의 </> 부분을 클릭하면 소스코드가 생성되며, 생성 언어 또한 선택할 수 있습니다.
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/e22da880-1ac1-490f-b483-4917cb712953)
## 5. Postman 샘플코드 목록
|구분 |API명 |모의투자 제공 여부 |실전투자 제공 여부 |
|--|--|--|--|
|OAuth인증|웹소켓접속키발급|⭕|⭕|
|OAuth인증|접근토큰발급|⭕|⭕|
|OAuth인증|접근토큰폐기|⭕|⭕|
|OAuth인증|Hashkey|⭕|⭕|
|[국내주식]주문/계좌|주식주문(현금)|⭕|⭕|
|[국내주식]주문/계좌|주식주문(신용)| |⭕|
|[국내주식]주문/계좌|주식주문(정정취소)|⭕|⭕|
|[국내주식]주문/계좌|주식정정취소가능주문조회| |⭕|
|[국내주식]주문/계좌|주식일별주문체결조회|⭕|⭕|
|[국내주식]주문/계좌|주식잔고조회|⭕|⭕|
|[국내주식]주문/계좌|매수가능조회|⭕|⭕|
|[국내주식]주문/계좌|주식예약주문| |⭕|
|[국내주식]주문/계좌|주식예약주문정정취소| |⭕|
|[국내주식]주문/계좌|주식예약주문조회| |⭕|
|[국내주식]주문/계좌|퇴직연금 체결기준잔고| |⭕|
|[국내주식]주문/계좌|퇴직연금 미체결내역| |⭕|
|[국내주식]주문/계좌|퇴직연금 매수가능조회| |⭕|
|[국내주식]주문/계좌|퇴직연금 예수금조회| |⭕|
|[국내주식]주문/계좌|퇴직연금 잔고조회| |⭕|
|[국내주식]주문/계좌|주식잔고조회_실현손익| |⭕|
|[국내주식]주문/계좌|신용매수가능조회| |⭕|
|[국내주식]주문/계좌|투자계좌자산현황조회| |⭕|
|[국내주식]주문/계좌|기간별매매손익현황조회| |⭕|
|[국내주식]주문/계좌|기간별손익일별합산조회| |⭕|
|[국내주식]주문/계좌|매도가능수량조회| |⭕|
|[국내주식]주문/계좌|주식통합증거금현황| |⭕|
|[국내주식]기본시세|주식현재가 시세|⭕|⭕|
|[국내주식]기본시세|주식현재가 체결|⭕|⭕|
|[국내주식]기본시세|주식현재가 일자별|⭕|⭕|
|[국내주식]기본시세|주식현재가 호가 예상체결|⭕|⭕|
|[국내주식]기본시세|주식현재가 투자자|⭕|⭕|
|[국내주식]기본시세|주식현재가 회원사|⭕|⭕|
|[국내주식]기본시세|국내주식기간별시세(일/주/월/년)|⭕|⭕|
|[국내주식]기본시세|주식현재가 당일시간대별체결|⭕|⭕|
|[국내주식]기본시세|주식현재가 시간외일자별주가|⭕|⭕|
|[국내주식]기본시세|주식당일분봉조회|⭕|⭕|
|[국내주식]기본시세|주식현재가 시세2| |⭕|
|[국내주식]기본시세|ETF/ETN 현재가| |⭕|
|[국내주식]기본시세|NAV 비교추이(종목)| |⭕|
|[국내주식]기본시세|NAV 비교추이(분)| |⭕|
|[국내주식]기본시세|NAV 비교추이(일)| |⭕|
|[국내주식]기본시세|국내주식 장마감 예상체결가| |⭕|
|[국내주식]기본시세|ETF 구성종목시세| |⭕|
|[국내주식]기본시세|국내주식 시간외현재가| |⭕|
|[국내주식]기본시세|국내주식 시간외호가| |⭕|
|[국내주식]ELW시세|ELW현재가 시세|⭕|⭕|
|[국내주식]ELW시세|ELW 상승률순위| |⭕|
|[국내주식]ELW시세|ELW 거래량순위| |⭕|
|[국내주식]ELW시세|ELW 지표순위| |⭕|
|[국내주식]ELW시세|ELW 민감도 순위| |⭕|
|[국내주식]ELW시세|ELW 당일급변종목| |⭕|
|[국내주식]ELW시세|ELW 신규상장종목| |⭕|
|[국내주식]ELW시세|ELW 투자지표추이(체결)| |⭕|
|[국내주식]ELW시세|ELW 투자지표추이(분별)| |⭕|
|[국내주식]ELW시세|ELW 투자지표추이(일별)| |⭕|
|[국내주식]ELW시세|ELW 변동성 추이(틱)| |⭕|
|[국내주식]ELW시세|ELW 변동성 추이(체결)| |⭕|
|[국내주식]ELW시세|ELW 변동성 추이(분별)| |⭕|
|[국내주식]ELW시세|ELW 변동성 추이(일별)| |⭕|
|[국내주식]ELW시세|ELW 민감도 추이(체결)| |⭕|
|[국내주식]ELW시세|ELW 민감도 추이(일별)| |⭕|
|[국내주식]ELW시세|ELW 기초자산별 종목시세| |⭕|
|[국내주식]ELW시세|ELW LP매매추이| |⭕|
|[국내주식]ELW시세|ELW 비교대상종목조회| |⭕|
|[국내주식]ELW시세|ELW 종목검색| |⭕|
|[국내주식]ELW시세|ELW 기초자산 목록조회| |⭕|
|[국내주식]ELW시세|ELW 만기예정/만기종목| |⭕|
|[국내주식]업종/기타|국내주식업종기간별시세(일/주/월/년)|⭕|⭕|
|[국내주식]업종/기타|국내휴장일조회| |⭕|
|[국내주식]업종/기타|업종분봉조회| |⭕|
|[국내주식]업종/기타|변동성완화장치(VI) 현황| |⭕|
|[국내주식]업종/기타|국내업종 현재지수| |⭕|
|[국내주식]업종/기타|국내업종 일자별지수| |⭕|
|[국내주식]업종/기타|국내업종 구분별전체시세| |⭕|
|[국내주식]업종/기타|국내주식 예상체결 전체지수| |⭕|
|[국내주식]업종/기타|국내업종 시간별지수(틱)| |⭕|
|[국내주식]업종/기타|국내업종 시간별지수(분)| |⭕|
|[국내주식]업종/기타|국내주식 예상체결지수 추이| |⭕|
|[국내주식]업종/기타|금리 종합(국내채권/금리)| |⭕|
|[국내주식]업종/기타|종합 시황/공시(제목)| |⭕|
|[국내주식]종목정보|상품기본조회| |⭕|
|[국내주식]종목정보|주식기본조회| |⭕|
|[국내주식]종목정보|국내주식 대차대조표| |⭕|
|[국내주식]종목정보|국내주식 손익계산서| |⭕|
|[국내주식]종목정보|국내주식 재무비율| |⭕|
|[국내주식]종목정보|국내주식 수익성비율| |⭕|
|[국내주식]종목정보|국내주식 기타주요비율| |⭕|
|[국내주식]종목정보|국내주식 안정성비율| |⭕|
|[국내주식]종목정보|국내주식 성장성비율| |⭕|
|[국내주식]종목정보|국내주식 당사 신용가능종목| |⭕|
|[국내주식]종목정보|예탁원정보(배당일정)| |⭕|
|[국내주식]종목정보|예탁원정보(주식매수청구일정)| |⭕|
|[국내주식]종목정보|예탁원정보(합병/분할일정)| |⭕|
|[국내주식]종목정보|예탁원정보(액면교체일정)| |⭕|
|[국내주식]종목정보|예탁원정보(자본감소일정)| |⭕|
|[국내주식]종목정보|예탁원정보(상장정보일정)| |⭕|
|[국내주식]종목정보|예탁원정보(공모주청약일정)| |⭕|
|[국내주식]종목정보|예탁원정보(실권주일정)| |⭕|
|[국내주식]종목정보|예탁원정보(의무예치일정)| |⭕|
|[국내주식]종목정보|예탁원정보(유상증자일정)| |⭕|
|[국내주식]종목정보|예탁원정보(무상증자일정)| |⭕|
|[국내주식]종목정보|예탁원정보(주주총회일정)| |⭕|
|[국내주식]종목정보|국내주식 종목추정실적| |⭕|
|[국내주식]종목정보|당사 대주가능 종목| |⭕|
|[국내주식]종목정보|국내주식 종목투자의견| |⭕|
|[국내주식]종목정보|국내주식 증권사별 투자의견| |⭕|
|[국내주식]시세분석|국내기관_외국인 매매종목가집계| |⭕|
|[국내주식]시세분석|종목조건검색 목록조회| |⭕|
|[국내주식]시세분석|종목조건검색조회| |⭕|
|[국내주식]시세분석|종목별 프로그램매매추이(체결)| |⭕|
|[국내주식]시세분석|종목별 프로그램매매추이(일별)| |⭕|
|[국내주식]시세분석|종목별 외인기관 추정가집계| |⭕|
|[국내주식]시세분석|종목별일별매수매도체결량| |⭕|
|[국내주식]시세분석|시장별 투자자매매동향(시세)| |⭕|
|[국내주식]시세분석|시장별 투자자매매동향(일별)| |⭕|
|[국내주식]시세분석|국내주식 신용잔고 일별추이| |⭕|
|[국내주식]시세분석|국내주식 예상체결가 추이| |⭕|
|[국내주식]시세분석|국내주식 공매도 일별추이| |⭕|
|[국내주식]시세분석|국내주식 시간외예상체결등락률| |⭕|
|[국내주식]시세분석|프로그램매매 투자자매매동향(당일)| |⭕|
|[국내주식]시세분석|프로그램매매 종합현황(시간)| |⭕|
|[국내주식]시세분석|프로그램매매 종합현황(일별)| |⭕|
|[국내주식]시세분석|외국계 매매종목 가집계| |⭕|
|[국내주식]시세분석|종목별 외국계 순매수추이| |⭕|
|[국내주식]시세분석|국내주식 체결금액별 매매비중| |⭕|
|[국내주식]시세분석|국내 증시자금 종합| |⭕|
|[국내주식]시세분석|관심종목 그룹별 종목조회| |⭕|
|[국내주식]시세분석|관심종목 그룹조회| |⭕|
|[국내주식]시세분석|관심종목(멀티종목) 시세조회| |⭕|
|[국내주식]시세분석|국내주식 상하한가 포착| |⭕|
|[국내주식]시세분석|회원사 실시간 매매동향(틱)| |⭕|
|[국내주식]시세분석|국내주식 매물대/거래비중| |⭕|
|[국내주식]시세분석|주식현재가 회원사 종목매매동향| |⭕|
|[국내주식]순위분석|거래량순위| |⭕|
|[국내주식]순위분석|국내주식 등락률 순위| |⭕|
|[국내주식]순위분석|국내주식 호가잔량 순위| |⭕|
|[국내주식]순위분석|국내주식 수익자산지표 순위| |⭕|
|[국내주식]순위분석|국내주식 시가총액 상위| |⭕|
|[국내주식]순위분석|국내주식 재무비율 순위| |⭕|
|[국내주식]순위분석|국내주식 시간외잔량 순위| |⭕|
|[국내주식]순위분석|국내주식 우선주/괴리율 상위| |⭕|
|[국내주식]순위분석|국내주식 이격도 순위| |⭕|
|[국내주식]순위분석|국내주식 시장가치 순위| |⭕|
|[국내주식]순위분석|국내주식 체결강도 상위| |⭕|
|[국내주식]순위분석|국내주식 관심종목등록 상위| |⭕|
|[국내주식]순위분석|국내주식 예상체결 상승/하락상위| |⭕|
|[국내주식]순위분석|국내주식 당사매매종목 상위| |⭕|
|[국내주식]순위분석|국내주식 신고/신저근접종목 상위| |⭕|
|[국내주식]순위분석|국내주식 대량체결건수 상위| |⭕|
|[국내주식]순위분석|국내주식 공매도 상위종목| |⭕|
|[국내주식]순위분석|국내주식 신용잔고 상위| |⭕|
|[국내주식]순위분석|국내주식 배당률 상위| |⭕|
|[국내주식]순위분석|국내주식 시간외등락율순위| |⭕|
|[국내주식]순위분석|국내주식 시간외거래량순위| |⭕|
|[국내선물옵션]주문/계좌|선물옵션 주문|⭕|⭕|
|[국내선물옵션]주문/계좌|선물옵션 정정취소주문|⭕|⭕|
|[국내선물옵션]주문/계좌|선물옵션 주문체결내역조회|⭕|⭕|
|[국내선물옵션]주문/계좌|선물옵션 잔고현황|⭕|⭕|
|[국내선물옵션]주문/계좌|선물옵션 주문가능|⭕|⭕|
|[국내선물옵션]주문/계좌|(야간)선물옵션 주문체결내역조회| |⭕|
|[국내선물옵션]주문/계좌|(야간)선물옵션 잔고현황| |⭕|
|[국내선물옵션]주문/계좌|(야간)선물옵션 주문가능| |⭕|
|[국내선물옵션]주문/계좌|선물옵션 잔고정산손익내역| |⭕|
|[국내선물옵션]주문/계좌|선물옵션 총자산현황| |⭕|
|[국내선물옵션]주문/계좌|선물옵션 잔고평가손익내역| |⭕|
|[국내선물옵션]주문/계좌|선물옵션 기준일체결내역| |⭕|
|[국내선물옵션]주문/계좌|선물옵션기간약정수수료일별| |⭕|
|[국내선물옵션]주문/계좌|(야간)선물옵션 증거금 상세| |⭕|
|[국내선물옵션]기본시세|선물옵션 시세|⭕|⭕|
|[국내선물옵션]기본시세|선물옵션 시세호가|⭕|⭕|
|[국내선물옵션]기본시세|선물옵션기간별시세(일/주/월/년)|⭕|⭕|
|[국내선물옵션]기본시세|선물옵션 분봉조회| |⭕|
|[국내선물옵션]기본시세|선물옵션 일중예상체결추이| |⭕|
|[국내선물옵션]기본시세|국내선물 기초자산 시세|⭕|⭕|
|[국내선물옵션]기본시세|국내옵션전광판_옵션월물리스트| |⭕|
|[국내선물옵션]기본시세|국내옵션전광판_콜풋| |⭕|
|[국내선물옵션]기본시세|국내옵션전광판_선물| |⭕|
|[해외주식]주문/계좌|해외주식 주문|⭕|⭕|
|[해외주식]주문/계좌|해외주식 정정취소주문|⭕|⭕|
|[해외주식]주문/계좌|해외주식 예약주문접수|⭕|⭕|
|[해외주식]주문/계좌|해외주식 예약주문접수취소|⭕|⭕|
|[해외주식]주문/계좌|해외주식 미체결내역|⭕|⭕|
|[해외주식]주문/계좌|해외주식 잔고|⭕|⭕|
|[해외주식]주문/계좌|해외주식 주문체결내역|⭕|⭕|
|[해외주식]주문/계좌|해외주식 체결기준현재잔고|⭕|⭕|
|[해외주식]주문/계좌|해외주식 예약주문조회| |⭕|
|[해외주식]주문/계좌|해외주식 매수가능금액조회| |⭕|
|[해외주식]주문/계좌|해외주식 미국주간주문| |⭕|
|[해외주식]주문/계좌|해외주식 미국주간정정취소| |⭕|
|[해외주식]주문/계좌|해외주식 기간손익| |⭕|
|[해외주식]주문/계좌|해외증거금 통화별조회| |⭕|
|[해외주식]주문/계좌|해외주식 일별거래내역| |⭕|
|[해외주식]주문/계좌|해외주식 결제기준잔고| |⭕|
|[해외주식]기본시세|해외주식 현재체결가|⭕|⭕|
|[해외주식]기본시세|해외주식 기간별시세|⭕|⭕|
|[해외주식]기본시세|해외주식 종목/지수/환율기간별시세(일/주/월/년)|⭕|⭕|
|[해외주식]기본시세|해외주식 조건검색|⭕|⭕|
|[해외주식]기본시세|해외결제일자조회| |⭕|
|[해외주식]기본시세|해외주식 현재가상세| |⭕|
|[해외주식]기본시세|해외주식분봉조회| |⭕|
|[해외주식]기본시세|해외지수분봉조회| |⭕|
|[해외주식]기본시세|해외주식 상품기본정보| |⭕|
|[해외주식]기본시세|해외주식 현재가 10호가| |⭕|
|[해외주식]시세분석|해외주식 기간별권리조회| |⭕|
|[해외주식]시세분석|해외뉴스종합(제목)| |⭕|
|[해외주식]시세분석|해외주식 권리종합| |⭕|
|[해외주식]시세분석|당사 해외주식담보대출 가능 종목| |⭕|
|[해외주식]시세분석|해외속보(제목)| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 주문| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 정정취소주문| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 당일주문내역조회| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 미결제내역조회(잔고)| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 주문가능조회| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 기간계좌손익 일별| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 일별 체결내역| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 예수금현황| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 일별 주문내역| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 기간계좌거래내역| |⭕|
|[해외선물옵션]주문/계좌|해외선물옵션 증거금상세| |⭕|
|[해외선물옵션]기본시세|해외선물종목상세|⭕|⭕|
|[해외선물옵션]기본시세|해외선물종목현재가|⭕|⭕|
|[해외선물옵션]기본시세|해외선물 분봉조회| |⭕|
|[해외선물옵션]기본시세|해외선물 체결추이(주간)| |⭕|
|[해외선물옵션]기본시세|해외선물 체결추이(일간)| |⭕|
|[해외선물옵션]기본시세|해외선물 체결추이(틱)| |⭕|
|[해외선물옵션]기본시세|해외선물 체결추이(월간)| |⭕|
|[해외선물옵션]기본시세|해외선물 호가| |⭕|
|[해외선물옵션]기본시세|해외선물 상품기본정보| |⭕|
|[해외선물옵션]기본시세|해외선물 장운영시간| |⭕|
|[해외선물옵션]기본시세|해외선물 미결제추이| |⭕|
|[해외선물옵션]기본시세|해외옵션 호가| |⭕|
|[장내채권]주문/계좌|장내채권 매수주문| |⭕|
|[장내채권]주문/계좌|장내채권 매도주문| |⭕|
|[장내채권]주문/계좌|장내채권 정정취소주문| |⭕|
|[장내채권]주문/계좌|채권정정취소가능주문조회| |⭕|
|[장내채권]주문/계좌|장내채권 주문체결내역| |⭕|
|[장내채권]주문/계좌|장내채권 잔고조회| |⭕|
|[장내채권]주문/계좌|장내채권 매수가능조회| |⭕|
|[장내채권]기본시세|장내채권 발행정보| |⭕|
|[장내채권]기본시세|장내채권 기본조회| |⭕|
|[장내채권]기본시세|장내채권현재가(호가)| |⭕|
|[장내채권]기본시세|장내채권 평균단가조회| |⭕|
|[장내채권]기본시세|장내채권 기간별시세(일)| |⭕|
|[장내채권]기본시세|장내채권현재가(시세)| |⭕|
|[장내채권]기본시세|장내채권현재가(체결)| |⭕|
|[장내채권]기본시세|장내채권현재가(일별)| |⭕|
**사용하시면서 어려운 점이 생기면 KIS Developers - Q&A 게시판에 문의 부탁드립니다 :)**
https://apiportal.koreainvestment.com/community/qna
![Footer](https://capsule-render.vercel.app/api?type=waving&color=gradient&height=200&section=footer)

View File

@@ -0,0 +1 @@
{"id":"03e21ca7-e652-41b6-8eae-bce9fce1170f","name":"모의Env","values":[{"key":"VTS","value":"https://openapivts.koreainvestment.com:29443","enabled":true,"type":"default"},{"key":"PROD","value":"https://openapi.koreainvestment.com:9443","enabled":true,"type":"default"},{"key":"CANO","value":"","enabled":true,"type":"default"},{"key":"CANO_T","value":"","enabled":true,"type":"default"},{"key":"VTS_APPKEY","value":"","enabled":true,"type":"default"},{"key":"VTS_APPSECRET","value":"","enabled":true,"type":"default"},{"key":"VTS_TOKEN","value":"","enabled":true,"type":"default"},{"key":"VTS_HASH","value":"","enabled":true,"type":"any"},{"key":"VTT_APPKEY","value":"","enabled":true,"type":"default"},{"key":"VTT_APPSECRET","value":"","enabled":true,"type":"default"},{"key":"VTT_TOKEN","value":"","enabled":true,"type":"default"},{"key":"VTT_HASH","value":"","enabled":true,"type":"default"}]}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
{"id":"a1551400-f523-4e7a-8232-63f8b2b775a3","name":"실전Env","values":[{"key":"VTS","value":"https://openapivts.koreainvestment.com:29443","enabled":true,"type":"default"},{"key":"PROD","value":"https://openapi.koreainvestment.com:9443","enabled":true,"type":"default"},{"key":"CANO_REAL","value":"","enabled":true,"type":"default"},{"key":"CANO_REAL_T","value":"","enabled":true,"type":"default"},{"key":"PROD_APPKEY","value":"","enabled":true,"type":"default"},{"key":"PROD_APPSECRET","value":"","enabled":true,"type":"default"},{"key":"PROD_TOKEN","value":"","enabled":true,"type":"default"},{"key":"PROD_HASH","value":"","enabled":true,"type":"default"},{"key":"PROT_APPKEY","value":"","enabled":true,"type":"default"},{"key":"PROT_APPSECRET","value":"","enabled":true,"type":"default"},{"key":"PROT_TOKEN","value":"","enabled":true,"type":"default"},{"key":"PROT_HASH","value":"","enabled":true,"type":"default"}]}

View File

@@ -0,0 +1,64 @@
![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=개발%20환경%20준비%20(파이썬%20세팅하기)&descAlignY=51&descAlign=62)
## 1. 개발 환경 준비 (파이썬 세팅하기)
### **1.1. 아나콘다(파이썬) 설치**
① 아나콘다 홈페이지([https://www.anaconda.com/download#downloads](https://www.anaconda.com/download#downloads))에서 본인 OS에 맞는 인스톨러를 다운로드
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/7c3fdfe0-24b3-4dae-80ae-9c3eb83b1ba2)
② 설치파일 관리자 권한으로 실행 > Next 클릭 > 라이선스 화면 I Agree 클릭 > 설치타입 All Users 클릭 > 설치 경로 C:\Anaconda3 으로 변경 & Next 클릭 > Advanced Options 화면에서 기본 값 그대로 Install 클릭
③ 설치 완료
### **1.2. 가상환경 생성 + 모듈 설치**
① Windows 로고 키 클릭 > Anaconda prompt 클릭
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/8ae27b27-3990-4a70-b98d-41e2e6457440)
② prompt 창에서 아래 명령어를 순서대로 입력
**- (가상환경 생성 명령어) conda create n koreainvest python=3.8**
**- (가상환경 실행 명령어) conda activate koreainvest**
**- (모듈 설치 명령어) pip install websockets pycryptodome requests pyyaml**
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/2a6651cf-25be-45fd-bd7e-5634cf3866fe)
- python websocket 사용을 위해 websockets 라이브러리를 설치하며, 주식체결통보 AES256 복호화 사용을 위한 pycrypodome 를 설치합니다.
- REST api 호출을 위해 requests 라이브러리를 설치하며, 개인정보(appkey, appsecret 등) 파일을 읽어들이기 위해 pyyaml 라이브러리를 설치합니다.
### **1.3. Pycharm 설치**
Pycharm은 파이썬 개발에 가장 널리 사용되는 통합 개발 환경으로 community 버전은 무료로 설치 가능합니다.
① Jetbrain 홈페이지(https://www.jetbrains.com/ko-kr/pycharm/download) 에서 Pycharm 인스톨러를 다운로드
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/0a816854-f748-4778-aeca-5f8cf66fc118)
② 인스톨러 실행 > Next 클릭 > 설치 완료 시 Finish 클릭 > Pycharm 실행
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/c368369d-ea00-470e-92bc-c2d7bde015f1)
### **1.4. Project 생성 및 Python Interpreter 설정하기**
① New Project 클릭 > 하단에 Previously configured Interpreter 클릭 > 우측의 Add Interpreter Add Local Interpreter 클릭
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/2e90ce4b-c7d8-4b41-a2f2-9fce45660a83)
② 새 창이 뜨면 좌측의 Conda Environment 클릭 > Use existing environment - koreainvest 선택 > OK 클릭 > Create 클릭
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/4bbf7777-6d23-46ed-8914-ba294bd708c7)
### **1.5. (option) Postman 설치 및 사용하기**
포스트맨(Postman)은 개발자들이 API를 디자인하고 빌드하고 테스트하고 반복하기 위한 API 플랫폼입니다.
Postman는 테스트베드 기능을 효율적으로 제공하여, 코드를 작성하기 전 API를 호출해보고 응답값을 확인해볼 수 있는 테스트해볼 수 있으며, 여러 언어(C, Java, Python 등)로 샘플코드를 자동으로 생성해주는 기능을 제공합니다.
![image](https://github.com/koreainvestment/open-trading-api/assets/87407853/dcfea9fb-5a95-49a9-86f2-f333b5b2f067)
- 진행방법: 한국투자 Github ([https://github.com/koreainvestment/open-trading-api/tree/main/postman](https://github.com/koreainvestment/open-trading-api/tree/main/postman)) 의 순서대로 진행

View File

@@ -0,0 +1,14 @@
# 홈페이지에서 API서비스 신청시 받은 Appkey, Appsecret 값 설정
APP_KEY: "PS..."
APP_SECRET: "UH..."
# 계좌번호 앞 8자리
C ANO: "12345678"
# 계좌번호 뒤 2자리
ACNT_PRDT_CD: "01"
# 도메인
URL_BASE: "https://openapi.koreainvestment.com:9443"
# HTS ID
HTS_ID: "hantu34"

View File

@@ -0,0 +1,54 @@
import requests
import json
APP_KEY = ""
APP_SECRET = ""
ACCESS_TOKEN = ""
URL_BASE = "https://openapi.koreainvestment.com:9443" #실전투자
# Auth
def auth():
headers = {"content-type":"application/json"}
body = {
"grant_type":"client_credentials",
"appkey":APP_KEY,
"appsecret":APP_SECRET
}
PATH = "oauth2/tokenP"
URL = f"{URL_BASE}/{PATH}"
res = requests.post(URL, headers=headers, data=json.dumps(body))
global ACCESS_TOKEN
ACCESS_TOKEN = res.json()["access_token"]
# 주식현재가 시세
def get_current_price(stock_no):
PATH = "uapi/domestic-stock/v1/quotations/inquire-price"
URL = f"{URL_BASE}/{PATH}"
# 헤더 설정
headers = {"Content-Type":"application/json",
"authorization": f"Bearer {ACCESS_TOKEN}",
"appKey":APP_KEY,
"appSecret":APP_SECRET,
"tr_id":"FHKST01010100"}
params = {
"fid_cond_mrkt_div_code":"J",
"fid_input_iscd": stock_no
}
# 호출
res = requests.get(URL, headers=headers, params=params)
if res.status_code == 200 and res.json()["rt_cd"] == "0" :
return(res.json())
# 토큰 만료 시
elif res.status_code == 200 and res.json()["msg_cd"] == "EGW00123" :
auth()
get_current_price(stock_no)
else:
print("Error Code : " + str(res.status_code) + " | " + res.text)
return None
get_current_price("005930")

View File

@@ -0,0 +1,141 @@
## 관심종목 복수시세조회 파이썬 샘플코드
### config.yaml 파일을 설정한 뒤 get_interest_stocks_price.py를 실행하여
### 1) 관심종목 그룹조회 → 2) 관심종목 그룹별 종목조회 → 3) 관심종목(멀티종목) 시세조회 순서대로 호출하여 관심종목 복수시세 조회 가능
# (0) 필요 모듈 임포트
import pandas as pd
import requests
import json
import yaml
import time
# (1) 개인정보 파일 가져오기
with open('config.yaml', encoding='UTF-8') as f:
_cfg = yaml.load(f, Loader=yaml.FullLoader)
APP_KEY = _cfg['APP_KEY']
APP_SECRET = _cfg['APP_SECRET']
ACCESS_TOKEN = ""
ACCESS_TOKEN_EXPIRED = ""
CANO = _cfg['CANO']
ACNT_PRDT_CD = _cfg['ACNT_PRDT_CD']
URL_BASE = _cfg['URL_BASE']
HTS_ID = _cfg['HTS_ID']
# (2) 함수 정의
## 1. 접근 토큰 발급
def get_access_token():
""" OAuth 인증 > 접근토큰발급 """
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": APP_KEY,
"appsecret": APP_SECRET}
PATH = "oauth2/tokenP"
URL = f"{URL_BASE}/{PATH}"
res = requests.post(URL, headers=headers, data=json.dumps(body))
time.sleep(0.1) # 유량제한 예방 (REST: 1초당 20건 제한)
try:
ACCESS_TOKEN = res.json()["access_token"]
ACCESS_TOKEN_EXPIRED = res.json()["access_token_token_expired"]
return ACCESS_TOKEN, ACCESS_TOKEN_EXPIRED
except:
print("접근 토큰 발급이 불가능합니다")
print(res.json())
## 2. 관심종목 그룹 조회
def get_interest_groups():
PATH = "/uapi/domestic-stock/v1/quotations/intstock-grouplist"
URL = f"{URL_BASE}/{PATH}"
params = {
"TYPE": "1",
"FID_ETC_CLS_CODE": "00",
"USER_ID": HTS_ID
}
headers = {
"content-type": "application/json",
"authorization": f"Bearer {ACCESS_TOKEN}",
"appKey": APP_KEY,
"appSecret": APP_SECRET,
"tr_id": "HHKCM113004C7", # 실전투자
"custtype": "P"
}
time.sleep(0.05) # 유량제한 예방 (REST: 1초당 20건 제한)
res = requests.get(URL, headers=headers, params=params)
return res.json()
## 3. 관심종목 그룹별 종목 조회
def get_group_stocks(interest_group):
PATH = "/uapi/domestic-stock/v1/quotations/intstock-stocklist-by-group"
URL = f"{URL_BASE}/{PATH}"
params = {
"TYPE": "1",
"USER_ID": HTS_ID,
"DATA_RANK": "",
"INTER_GRP_CODE": interest_group,
"INTER_GRP_NAME": "",
"HTS_KOR_ISNM": "",
"CNTG_CLS_CODE": "",
"FID_ETC_CLS_CODE": "4"
}
headers = {
"content-type": "application/json",
"authorization": f"Bearer {ACCESS_TOKEN}",
"appKey": APP_KEY,
"appSecret": APP_SECRET,
"tr_id": "HHKCM113004C6", # 실전투자
"custtype": "P"
}
time.sleep(0.05) # 유량제한 예방 (REST: 1초당 20건 제한)
res = requests.get(URL, headers=headers, params=params)
return res.json()
## 4. 관심종목(멀티종목) 시세조회 (30종목까지 조회 가능)
def get_multi_stock_prices(jongs):
PATH = "/uapi/domestic-stock/v1/quotations/intstock-multprice"
URL = f"{URL_BASE}/{PATH}"
headers = {
"content-type": "application/json",
"authorization": f"Bearer {ACCESS_TOKEN}",
"appKey": APP_KEY,
"appSecret": APP_SECRET,
"tr_id": "FHKST11300006", # 실전투자
"custtype": "P"
}
time.sleep(0.05) # 유량제한 예방 (REST: 1초당 20건 제한)
res = requests.get(URL, headers=headers, params=jongs)
print(res.text)
print(res.json())
return res.json()
# (3) 함수 실행
## 1. 접근토큰 발급
ACCESS_TOKEN, ACCESS_TOKEN_EXPIRED = get_access_token()
## 2. 관심종목 그룹 조회
interest_groups = get_interest_groups()
print(interest_groups)
## 3. 사용자로부터 관심종목 그룹 선택
interest_group = str(input("관심종목 그룹 번호(inter_grp_code)를 입력하세요: "))
## 4. 선택한 관심종목 그룹별 종목 조회
group_stocks = get_group_stocks(interest_group)
print(group_stocks)
interest_mrkt_code_list = [item["fid_mrkt_cls_code"] for item in group_stocks["output2"]]
interest_jong_code_list = [item["jong_code"] for item in group_stocks["output2"]]
print(interest_mrkt_code_list)
print(interest_jong_code_list)
## 5. 관심종목 최대30종목 리스트 생성
jongs = {}
for i in range(min(len(interest_jong_code_list), 30)): # 관심종목 (최대) 30종목 리스트 생성
jongs[f"FID_COND_MRKT_DIV_CODE_{i}"] = interest_mrkt_code_list[i]
jongs[f"FID_INPUT_ISCD_{i}"] = interest_jong_code_list[i]
print("jongs",jongs)
print("jongs 길이",len(jongs))
## 6. 관심종목 시세 조회
multi_stock_prices = get_multi_stock_prices(jongs)
print(multi_stock_prices) # 최대 30개 관심종목 데이터 출력
print(pd.DataFrame(multi_stock_prices['output'])) # 전체 데이터 데이터프레임으로 변환
print(pd.DataFrame(multi_stock_prices['output'])[['inter_kor_isnm','inter_shrn_iscd','inter2_prpr']]) #종목 한글명, 종목코드, 현재가만 출력

View File

@@ -0,0 +1,150 @@
## 해외선물분봉조회: 해외선물 상품의 특정 기간동안의 분봉을 받아서 엑셀로 저장하는 파이썬 샘플코드
## config.yaml 파일을 설정한 뒤 get_ovsfut_chart_price.py를 실행해서 분봉 데이터 저장 가능
## 해외선물 종목정보 마스터파일 다운로드 : https://new.real.download.dws.co.kr/common/master/ffcode.mst.zip
# (0) 필요 모듈 임포트
import pandas as pd
import requests
import json
import yaml
import time
# (1) 개인정보 파일 가져오기
with open('config.yaml', encoding='UTF-8') as f:
_cfg = yaml.load(f, Loader=yaml.FullLoader)
APP_KEY = _cfg['APP_KEY']
APP_SECRET = _cfg['APP_SECRET']
ACCESS_TOKEN = ""
ACCESS_TOKEN_EXPIRED = ""
CANO = _cfg['CANO']
ACNT_PRDT_CD = _cfg['ACNT_PRDT_CD']
URL_BASE = _cfg['URL_BASE']
HTS_ID = _cfg['HTS_ID']
# (2) 함수 정의
def get_access_token():
""" OAuth 인증 > 접근토큰발급 """
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": APP_KEY,
"appsecret": APP_SECRET}
PATH = "oauth2/tokenP"
URL = f"{URL_BASE}/{PATH}"
res = requests.post(URL, headers=headers, data=json.dumps(body))
time.sleep(0.1)
try:
ACCESS_TOKEN = res.json()["access_token"]
ACCESS_TOKEN_EXPIRED = res.json()["access_token_token_expired"]
return ACCESS_TOKEN, ACCESS_TOKEN_EXPIRED
except:
print("접근 토큰 발급이 불가능합니다")
print(res.json())
def get_overseas_future_time_price(SRS="6AZ23", EXCH="CME", STRT_DT="",END_DT="", CNT="100", GAP="1", dataframe=None, file_name=None): # 해외선물옵션시세 > 해외선물 분봉조회
"""
해외선물옵션시세 > 해외선물 분봉조회를 위한 함수
Parameters:
- SRS: 종목 코드
- EXCH: 거래소 코드
- STRT_DT: 조회 시작일
- END_DT: 조회 종료일
- CNT: 호출건당 조회 갯수
- GAP: 조회 간격
- dataframe: 초기 데이터프레임
- file_name: 저장할 파일 이름
Returns"":
- 최종 데이터프레임
"""
PATH = "/uapi/overseas-futureoption/v1/quotations/inquire-time-futurechartprice"
URL = f"{URL_BASE}/{PATH}"
headers = {"Content-Type":"application/json",
"authorization":f"Bearer {ACCESS_TOKEN}",
"appKey":APP_KEY,
"appSecret":APP_SECRET,
"tr_id":"HHDFC55020400", # 실전투자
"custtype":"P"
}
params = {
"SRS_CD": SRS,
"EXCH_CD": EXCH,
"START_DATE_TIME": "",
"CLOSE_DATE_TIME": END_DT,
"QRY_TP": "",
"QRY_CNT": CNT,
"QRY_GAP": GAP,
"INDEX_KEY": ""
}
res = requests.get(URL, headers=headers, params=params)
time.sleep(0.1) # 유량제한 예방 (REST: 1초당 20건 제한)
output = res.json()['output1']
# 파일 이름이 제공되지 않은 경우에만 생성
if file_name is None:
file_name = f"{SRS}_{EXCH}_{STRT_DT}_{END_DT}"
# 초기 dataframe 생성
if dataframe is None:
dataframe = pd.DataFrame()
current_dataframe = pd.DataFrame(output)
# 이전 응답값이 존재하고, 이전 응답값과 현재 응답값이 동일하면
if not dataframe.empty and dataframe.iloc[-1,0] == current_dataframe.iloc[-1,0] and dataframe.iloc[-1,1] == current_dataframe.iloc[-1,1]:
# 이전 데이터프레임에 현재 데이터프레임 추가
dataframe = pd.concat([dataframe, current_dataframe], ignore_index=True)
# STRT_DT와 END_DT가 같은 경우 현재 dataframe을 return
if STRT_DT == END_DT:
dataframe = dataframe.drop_duplicates(ignore_index=True)
# 최종 데이터프레임 정제 작업(이상치 삭제 및 date, time으로 정렬)
## 'data_date'와 'data_time'을 문자열로 변환하여 문자열을 합쳐 datetime 형식으로 만들기
dataframe['datetime'] = pd.to_datetime(dataframe['data_date'].astype(str) + dataframe['data_time'].astype(str).str.zfill(6), errors='coerce', format='%Y%m%d%H%M%S')
## 유효한 datetime이 아닌 행 삭제
dataframe = dataframe.dropna(subset=['datetime'])
## 'datetime'을 기준으로 정렬 후 'datetime' 열 삭제
dataframe = dataframe.sort_values(by=['datetime']).drop(columns=['datetime']).reset_index(drop=True)
# 현재 위치에 엑셀파일로 저장
dataframe.to_excel(f"{file_name}.xlsx",index=False)
print("File Saved")
# 최종 데이터프레임 return
return dataframe
else:
# END_DT를 하루 이전으로 설정하고 재귀호출
END_DT = pd.to_datetime(END_DT) - pd.DateOffset(days=1)
END_DT = END_DT.strftime("%Y%m%d")
return get_overseas_future_time_price(SRS, EXCH, STRT_DT, END_DT, "100", GAP, dataframe, file_name)
else:
# 이전 데이터프레임에 현재 데이터프레임 추가
dataframe = pd.concat([dataframe, current_dataframe], ignore_index=True)
# 이전 응답값과 현재 응답값이 다르면 CNT를 100늘려서 재호출
CNT = str(int(CNT) + 100)
# print(SRS, EXCH, STRT_DT, END_DT, CNT, GAP)
return get_overseas_future_time_price(SRS, EXCH, STRT_DT, END_DT, CNT, GAP, dataframe, file_name)
# (3) 함수 실행
# 접근토큰 발급
ACCESS_TOKEN, ACCESS_TOKEN_EXPIRED = get_access_token()
# 사용자 입력 받기
SRS = input("종목 코드를 입력하세요(ex. 6AZ23): ")
EXCH = input("거래소 코드를 입력하세요(ex. CME): ")
STRT_DT = input("조회 시작일을 입력하세요 (YYYYMMDD 형식): ")
END_DT = input("조회 종료일을 입력하세요 (YYYYMMDD 형식): ")
GAP = input("조회 간격(분)을 입력하세요 (ex. 1) : ")
# 해외선물분봉조회 호출
print("Downloading...")
result_dataframe = get_overseas_future_time_price(SRS=SRS, EXCH=EXCH, STRT_DT=STRT_DT, END_DT=END_DT, CNT="100", GAP=GAP)
result_dataframe[:3]

View File

@@ -0,0 +1,182 @@
## 해외주식분봉조회: 해외주식 종목의 특정 기간동안의 분봉을 받아서 엑셀로 저장하는 파이썬 샘플코드 (최대 약 1개월 기간 분봉 확인 가능)
## config.yaml 파일을 설정한 뒤 get_ovsstk_chart_price.py를 실행해서 분봉 데이터 저장 가능
## 해외주식 종목정보 마스터파일 다운로드 :
### (나스닥) https://new.real.download.dws.co.kr/common/master/nasmst.cod.zip
### (뉴욕) https://new.real.download.dws.co.kr/common/master/nysmst.cod.zip
### (아멕스) https://new.real.download.dws.co.kr/common/master/amsmst.cod.zip
# (0) 필요 모듈 임포트
from datetime import datetime, timedelta
import pandas as pd
import requests
import json
import yaml
import time
# (1) 개인정보 파일 가져오기
with open('config.yaml', encoding='UTF-8') as f:
_cfg = yaml.load(f, Loader=yaml.FullLoader)
APP_KEY = _cfg['APP_KEY']
APP_SECRET = _cfg['APP_SECRET']
ACCESS_TOKEN = ""
ACCESS_TOKEN_EXPIRED = ""
CANO = _cfg['CANO']
ACNT_PRDT_CD = _cfg['ACNT_PRDT_CD']
URL_BASE = _cfg['URL_BASE']
HTS_ID = _cfg['HTS_ID']
print(APP_KEY, APP_SECRET, ACCESS_TOKEN, HTS_ID)
# (2) 함수 정의
## 1. 접근 토큰 발급
def get_access_token():
""" OAuth 인증 > 접근토큰발급 """
headers = {"content-type": "application/json"}
body = {
"grant_type": "client_credentials",
"appkey": APP_KEY,
"appsecret": APP_SECRET
}
PATH = "oauth2/tokenP"
URL = f"{URL_BASE}/{PATH}"
time.sleep(0.1) # 유량제한 예방 (REST: 1초당 20건 제한)
res = requests.post(URL, headers=headers, data=json.dumps(body))
if res.status_code == 200:
try:
access_token = res.json().get("access_token")
access_token_expired = res.json().get("access_token_expired") # 수정된 키
return access_token, access_token_expired
except KeyError as e:
print(f"토큰 발급 중 키 에러 발생: {e}")
print(res.json())
return None, None
else:
print("접근 토큰 발급이 불가능합니다. 응답 코드:", res.status_code)
print("응답 내용:", res.json())
return None, None
## 2. 해외 주식 분봉 조회
def call_api(exc_code, sym_code, nmin, keyb="", next_value="", access_token=""):
PATH = "/uapi/overseas-price/v1/quotations/inquire-time-itemchartprice"
URL = f"{URL_BASE}/{PATH}"
params = {
"AUTH": "",
"EXCD": exc_code, # 거래소 코드
"SYMB": sym_code, # 종목 코드
"NMIN": nmin, # 분봉 주기
"PINC": "1",
"NEXT": next_value,
"NREC": "120",
"FILL": "",
"KEYB": keyb
}
print(params)
headers = {
'content-type': 'application/json',
'authorization': f'Bearer {access_token}', # 매개변수로 받은 access_token 사용
'appkey': APP_KEY, # 미리 정의된 appkey 사용
'appsecret': APP_SECRET, # 미리 정의된 appsecret 사용
'tr_id': 'HHDFS76950200',
'custtype': 'P'
}
time.sleep(0.1) # 유량제한 예방 (REST: 1초당 20건 제한)
response = requests.get(URL, headers=headers, params=params)
if response.status_code == 200:
data = response.json()
print("API 호출 성공")
return data
else:
print(f"API 호출 실패, 상태 코드: {response.status_code}")
try:
print("오류 메시지:", response.json())
except json.JSONDecodeError:
print("JSON 디코딩 실패. 응답:", response.text)
return None
## 3. 다음 조회용 keyb 값 계산 함수 (nmin에 따라 시간 조정)
def get_next_keyb(output2, nmin):
last_record = output2[-1]
last_time_str = last_record["xymd"] + last_record["xhms"] # YYYYMMDDHHMMSS 형태의 문자열
last_time = datetime.strptime(last_time_str, "%Y%m%d%H%M%S") # 문자열을 datetime 객체로 변환
next_keyb_time = last_time - timedelta(minutes=nmin) # nmin 값만큼 이전 시간 계산
return next_keyb_time.strftime("%Y%m%d%H%M%S") # 다시 문자열로 변환하여 반환
## 4. 데이터를 판다스 DataFrame으로 변환하는 함수
def convert_to_dataframe(data):
if "output2" in data:
# output2 데이터를 데이터프레임으로 변환
df = pd.DataFrame(data["output2"])
# 필요한 열만 선택 및 시간 데이터 처리
df = df[['tymd', 'xhms', 'open', 'high', 'low', 'last', 'evol', 'eamt']]
df['datetime'] = pd.to_datetime(df['tymd'] + df['xhms'], format='%Y%m%d%H%M%S')
# 데이터프레임 정리 (시간 순서대로 정렬)
df = df.sort_values(by='datetime').reset_index(drop=True)
# 필요 없는 열 삭제
df.drop(columns=['tymd', 'xhms'], inplace=True)
return df
else:
return pd.DataFrame()
## 5. 반복 조회 및 데이터 저장 함수
def fetch_and_save_data(exc_code, sym_code, nmin, period, access_token):
all_data = pd.DataFrame() # 전체 데이터를 저장할 데이터프레임
first_call = call_api(exc_code, sym_code, nmin, access_token=access_token)
if not first_call:
return
# 첫 조회 데이터 변환 및 저장
df = convert_to_dataframe(first_call)
all_data = pd.concat([all_data, df], ignore_index=True)
# 다음 조회를 위한 변수 초기화
next_value = first_call["output1"]["next"]
keyb = get_next_keyb(first_call["output2"], nmin) # nmin에 따라 1분 또는 n분 전 시간 계산
for _ in range(period - 1):
# 다음 조회 실행
next_call = call_api(exc_code, sym_code, nmin, keyb=keyb, next_value=next_value, access_token=access_token)
if not next_call:
break
# 다음 조회 데이터 변환 및 저장
df = convert_to_dataframe(next_call)
all_data = pd.concat([all_data, df], ignore_index=True)
# 다음 조회를 위한 keyb 및 next 값 갱신
next_value = next_call["output1"]["next"]
keyb = get_next_keyb(next_call["output2"], nmin) # nmin에 따라 갱신된 keyb 값
# 결과 데이터프레임을 시간순으로 정렬하여 저장
all_data = all_data.sort_values(by='datetime').reset_index(drop=True).drop_duplicates() # 중복 제거
all_data.to_csv(f'{sym_code}_fetched_data.csv', index=False) # CSV 파일로 저장
print(f"{sym_code} 데이터가 CSV 파일로 저장되었습니다.")
return all_data
# (4) 함수 실행
## 1. 접근토큰 발급
ACCESS_TOKEN, ACCESS_TOKEN_EXPIRED = get_access_token()
if ACCESS_TOKEN:
## 2. 사용자 입력 받기
exc_code = input("거래소 코드를 입력하세요 (예: NAS): ")
sym_code = input("종목 코드를 입력하세요 (예: TSLA): ")
nmin = int(input("분봉 주기를 입력하세요 (예: 1, 5, 10): "))
period = int(input("반복 조회할 횟수를 입력하세요 (예: 4): "))
## 3. 사용자 입력에 따른 분봉 조회 후 데이터 저장
all_data = fetch_and_save_data(exc_code, sym_code, nmin, period, ACCESS_TOKEN)
else:
print("토큰 발급 실패로 프로그램이 종료됩니다.")

View File

@@ -0,0 +1,663 @@
# -*- coding: utf-8 -*-
"""
Created on Mon Apr 18 11:23:04 2022
@author: KIS Developers
"""
import time, copy
import yaml
import requests
import json
import pandas as pd
from collections import namedtuple
from datetime import datetime
with open(r'kisdev_vi.yaml', encoding='UTF-8') as f:
_cfg = yaml.load(f, Loader=yaml.FullLoader)
_TRENV = tuple()
_last_auth_time = datetime.now()
_autoReAuth = False
_DEBUG = True
_isPaper = True
_base_headers = {
"Content-Type": "application/json",
"Accept": "text/plain",
"charset": "UTF-8",
'User-Agent': _cfg['my_agent']
}
def _getBaseHeader():
if _autoReAuth: reAuth()
return copy.deepcopy(_base_headers)
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'],
'my_prod': cfg['my_prod'],
'my_token': cfg['my_token'],
'my_url' : cfg['my_url']
}
global _TRENV
_TRENV = nt1(**d)
def isPaperTrading():
return _isPaper
def changeTREnv(token_key, svr='prod', product='01'):
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 == '03':
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]
_setTRENV(cfg)
def _getResultObject(json_data):
_tc_ = namedtuple('res', json_data.keys())
return _tc_(**json_data)
def auth(svr='prod', product='01'):
p = {
"grant_type": "client_credentials",
}
print(svr)
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]
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
else:
print('Get Authentification token fail!\nYou have to restart your app!!!')
return
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
def reAuth(svr='prod', product='01'):
n2 = datetime.now()
if (n2-_last_auth_time).seconds >= 86400:
auth(svr, product)
def getEnv():
return _cfg
def getTREnv():
return _TRENV
#주문 API에서 사용할 hash key값을 받아 header에 설정해 주는 함수
# Input: HTTP Header, HTTP post param
# Output: None
def set_order_hash_key(h, p):
url = f"{getTREnv().my_url}/uapi/hashkey"
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)
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.rt_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):
print('-------------------------------\nError in response: ', self.getResCode())
print(self.getBody().rt_cd, self.getErrorCode(), self.getErrorMessage())
print('-------------------------------')
# end of class APIResp
########### API call wrapping
def _url_fetch(api_url, ptr_id, params, appendHeaders=None, postFlag=False, hashFlag=True):
url = f"{getTREnv().my_url}{api_url}"
headers = _getBaseHeader()
#추가 Header 설정
tr_id = ptr_id
if ptr_id[0] in ('T', 'J', 'C'):
if isPaperTrading():
tr_id = 'V' + ptr_id[1:]
headers["tr_id"] = tr_id
headers["custtype"] = "P"
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
# 계좌 잔고를 DataFrame 으로 반환
# Input: None (Option) rtCashFlag=True 면 예수금 총액을 반환하게 된다
# Output: DataFrame (Option) rtCashFlag=True 면 예수금 총액을 반환하게 된다
def get_acct_balance(rtCashFlag=False):
url = '/uapi/domestic-stock/v1/trading/inquire-balance'
tr_id = "TTTC8434R"
params = {
'CANO': getTREnv().my_acct,
'ACNT_PRDT_CD': '01',
'AFHR_FLPR_YN': 'N',
'FNCG_AMT_AUTO_RDPT_YN': 'N',
'FUND_STTL_ICLD_YN': 'N',
'INQR_DVSN': '01',
'OFL_YN': 'N',
'PRCS_DVSN': '01',
'UNPR_DVSN': '01',
'CTX_AREA_FK100': '',
'CTX_AREA_NK100': ''
}
t1 = _url_fetch(url, tr_id, params)
if rtCashFlag and t1.isOK():
r2 = t1.getBody().output2
return int(r2[0]['dnca_tot_amt'])
output1 = t1.getBody().output1
if t1.isOK() and output1: #body 의 rt_cd 가 0 인 경우만 성공
tdf = pd.DataFrame(output1)
tdf.set_index('pdno', inplace=True)
cf1 = ['prdt_name','hldg_qty', 'ord_psbl_qty', 'pchs_avg_pric', 'evlu_pfls_rt', 'prpr', 'bfdy_cprs_icdc', 'fltt_rt']
cf2 = ['종목명', '보유수량', '매도가능수량', '매입단가', '수익율', '현재가' ,'전일대비', '등락']
tdf = tdf[cf1]
tdf[cf1[1:]] = tdf[cf1[1:]].apply(pd.to_numeric)
ren_dict = dict(zip(cf1, cf2))
return tdf.rename(columns=ren_dict)
else:
t1.printError()
return pd.DataFrame()
#종목의 주식, ETF, 선물/옵션 등의 구분값을 반환. 현재는 무조건 주식(J)만 반환
def _getStockDiv(stock_no):
return 'J'
# 종목별 현재가를 dict 로 반환
# Input: 종목코드
# Output: 현재가 Info dictionary. 반환된 dict 가 len(dict) < 1 경우는 에러로 보면 됨
def get_current_price(stock_no):
url = "/uapi/domestic-stock/v1/quotations/inquire-price"
tr_id = "FHKST01010100"
params = {
'FID_COND_MRKT_DIV_CODE': _getStockDiv(stock_no),
'FID_INPUT_ISCD': stock_no
}
t1 = _url_fetch(url, tr_id, params)
if t1.isOK():
return t1.getBody().output
else:
t1.printError()
return dict()
# 주문 base function
# Input: 종목코드, 주문수량, 주문가격, Buy Flag(If True, it's Buy order), order_type="00"(지정가)
# Output: HTTP Response
def do_order(stock_code, order_qty, order_price, prd_code="01", buy_flag=True, order_type="00"):
url = "/uapi/domestic-stock/v1/trading/order-cash"
if buy_flag:
tr_id = "TTTC0802U" #buy
else:
tr_id = "TTTC0801U" #sell
params = {
'CANO': getTREnv().my_acct,
'ACNT_PRDT_CD': prd_code,
'PDNO': stock_code,
'ORD_DVSN': order_type,
'ORD_QTY': str(order_qty),
'ORD_UNPR': str(order_price),
'CTAC_TLNO': '',
'SLL_TYPE': '01',
'ALGO_NO': ''
}
t1 = _url_fetch(url, tr_id, params, postFlag=True, hashFlag=True)
if t1.isOK():
return t1
else:
t1.printError()
return None
# 사자 주문. 내부적으로는 do_order 를 호출한다.
# Input: 종목코드, 주문수량, 주문가격
# Output: True, False
def do_sell(stock_code, order_qty, order_price, prd_code="01", order_type="00"):
t1 = do_order(stock_code, order_qty, order_price, buy_flag=False, order_type=order_type)
return t1.isOK()
# 팔자 주문. 내부적으로는 do_order 를 호출한다.
# Input: 종목코드, 주문수량, 주문가격
# Output: True, False
def do_buy(stock_code, order_qty, order_price, prd_code="01", order_type="00"):
t1 = do_order(stock_code, order_qty, order_price, buy_flag=True, order_type=order_type)
return t1.isOK()
# 정정취소 가능한 주문 목록을 DataFrame 으로 반환
# Input: None
# Output: DataFrame
def get_orders(prd_code='01'):
url = "/uapi/domestic-stock/v1/trading/inquire-psbl-rvsecncl"
tr_id = "TTTC8036R"
params = {
"CANO": getTREnv().my_acct,
"ACNT_PRDT_CD": prd_code,
"CTX_AREA_FK100": '',
"CTX_AREA_NK100": '',
"INQR_DVSN_1": '0',
"INQR_DVSN_2": '0'
}
t1 = _url_fetch(url, tr_id, params)
if t1.isOK():
tdf = pd.DataFrame(t1.getBody().output)
tdf.set_index('odno', inplace=True)
cf1 = ['pdno', 'ord_qty', 'ord_unpr', 'ord_tmd', 'ord_gno_brno','orgn_odno']
cf2 = ['종목코드', '주문수량', '주문가격', '시간', '주문점', '원번호']
tdf = tdf[cf1]
ren_dict = dict(zip(cf1, cf2))
return tdf.rename(columns=ren_dict)
else:
t1.printError()
return pd.DataFrame()
# 특정 주문 취소(01)/정정(02)
# Input: 주문 번호(get_orders 를 호출하여 얻은 DataFrame 의 index column 값이 취소 가능한 주문번호임)
# 주문점(통상 06010), 주문수량, 주문가격, 상품코드(01), 주문유형(00), 정정구분(취소-02, 정정-01)
# Output: APIResp object
def _do_cancel_revise(order_no, order_branch, order_qty, order_price, prd_code, order_dv, cncl_dv, qty_all_yn):
url = "/uapi/domestic-stock/v1/trading/order-rvsecncl"
tr_id = "TTTC0803U"
params = {
"CANO": getTREnv().my_acct,
"ACNT_PRDT_CD": prd_code,
"KRX_FWDG_ORD_ORGNO": order_branch,
"ORGN_ODNO": order_no,
"ORD_DVSN": order_dv,
"RVSE_CNCL_DVSN_CD": cncl_dv, #취소(02)
"ORD_QTY": str(order_qty),
"ORD_UNPR": str(order_price),
"QTY_ALL_ORD_YN": qty_all_yn
}
t1 = _url_fetch(url, tr_id, params=params, postFlag=True)
if t1.isOK():
return t1
else:
t1.printError()
return None
# 특정 주문 취소
#
def do_cancel(order_no, order_qty, order_price="01", order_branch='06010', prd_code='01', order_dv='00', cncl_dv='02',qty_all_yn="Y"):
return _do_cancel_revise(order_no, order_branch, order_qty, order_price, prd_code, order_dv, cncl_dv, qty_all_yn)
# 특정 주문 정정
#
def do_revise(order_no, order_qty, order_price, order_branch='06010', prd_code='01', order_dv='00', cncl_dv='01', qty_all_yn="Y"):
return _do_cancel_revise(order_no, order_branch, order_qty, order_price, prd_code, order_dv, cncl_dv, qty_all_yn)
# 모든 주문 취소
# Input: None
# Output: None
def do_cancel_all():
tdf = get_orders()
od_list = tdf.index.to_list()
qty_list = tdf['주문수량'].to_list()
price_list = tdf['주문가격'].to_list()
branch_list = tdf['주문점'].to_list()
cnt = 0
for x in od_list:
ar = do_cancel(x, qty_list[cnt], price_list[cnt], branch_list[cnt])
cnt += 1
print(ar.getErrorCode(), ar.getErrorMessage())
time.sleep(.2)
# 내 계좌의 일별 주문 체결 조회
# Input: 시작일, 종료일 (Option)지정하지 않으면 현재일
# output: DataFrame
def get_my_complete(sdt, edt=None, prd_code='01', zipFlag=True):
url = "/uapi/domestic-stock/v1/trading/inquire-daily-ccld"
tr_id = "TTTC8001R"
if (edt is None):
ltdt = datetime.now().strftime('%Y%m%d')
else:
ltdt = edt
params = {
"CANO": getTREnv().my_acct,
"ACNT_PRDT_CD": prd_code,
"INQR_STRT_DT": sdt,
"INQR_END_DT": ltdt,
"SLL_BUY_DVSN_CD": '00',
"INQR_DVSN": '00',
"PDNO": "",
"CCLD_DVSN": "00",
"ORD_GNO_BRNO": "",
"ODNO":"",
"INQR_DVSN_3": "00",
"INQR_DVSN_1": "",
"INQR_DVSN_2": "",
"CTX_AREA_FK100": "",
"CTX_AREA_NK100": ""
}
t1 = _url_fetch(url, tr_id, params)
#output1 과 output2 로 나뉘어서 결과가 옴. 지금은 output1만 DF 로 변환
if t1.isOK():
tdf = pd.DataFrame(t1.getBody().output1)
tdf.set_index('odno', inplace=True)
if (zipFlag):
return tdf[['ord_dt','orgn_odno', 'sll_buy_dvsn_cd_name', 'pdno', 'ord_qty', 'ord_unpr', 'avg_prvs', 'cncl_yn','tot_ccld_amt','rmn_qty']]
else:
return tdf
else:
t1.printError()
return pd.DataFrame()
# 매수 가능(현금) 조회
# Input: None
# Output: 매수 가능 현금 액수
def get_buyable_cash(stock_code='', qry_price=0, prd_code='01'):
url = "/uapi/domestic-stock/v1/trading/inquire-daily-ccld"
tr_id = "TTTC8908R"
params = {
"CANO": getTREnv().my_acct,
"ACNT_PRDT_CD": prd_code,
"PDNO": stock_code,
"ORD_UNPR": str(qry_price),
"ORD_DVSN": "02",
"CMA_EVLU_AMT_ICLD_YN": "Y", #API 설명부분 수정 필요 (YN)
"OVRS_ICLD_YN": "N"
}
t1 = _url_fetch(url, tr_id, params)
if t1.isOK():
return int(t1.getBody().output['ord_psbl_cash'])
else:
t1.printError()
return 0
# 시세 Function
# 종목별 체결 Data
# Input: 종목코드
# Output: 체결 Data DataFrame
# 주식체결시간, 주식현재가, 전일대비, 전일대비부호, 체결거래량, 당일 체결강도, 전일대비율
def get_stock_completed(stock_no):
url = "/uapi/domestic-stock/v1/quotations/inquire-ccnl"
tr_id = "FHKST01010300"
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": stock_no
}
t1 = _url_fetch(url, tr_id, params)
if t1.isOK():
return pd.DataFrame(t1.getBody().output)
else:
t1.printError()
return pd.DataFrame()
# 종목별 history data (현재 기준 30개만 조회 가능)
# Input: 종목코드, 구분(D, W, M 기본값은 D)
# output: 시세 History DataFrame
def get_stock_history(stock_no, gb_cd='D'):
url = "/uapi/domestic-stock/v1/quotations/inquire-daily-price"
tr_id = "FHKST01010400"
params = {
"FID_COND_MRKT_DIV_CODE": _getStockDiv(stock_no),
"FID_INPUT_ISCD": stock_no,
"FID_PERIOD_DIV_CODE": gb_cd,
"FID_ORG_ADJ_PRC": "0000000001"
}
t1 = _url_fetch(url, tr_id, params)
if t1.isOK():
return pd.DataFrame(t1.getBody().output)
else:
t1.printError()
return pd.DataFrame()
# 종목별 history data 를 표준 OHLCV DataFrame 으로 반환
# Input: 종목코드, 구분(D, W, M 기본값은 D), (Option)adVar 을 True 로 설정하면
# OHLCV 외에 inter_volatile 과 pct_change 를 추가로 반환한다.
# output: 시세 History OHLCV DataFrame
def get_stock_history_by_ohlcv(stock_no, gb_cd='D', adVar=False):
hdf1 = get_stock_history(stock_no, gb_cd)
chosend_fld = ['stck_bsop_date', 'stck_oprc', 'stck_hgpr', 'stck_lwpr', 'stck_clpr', 'acml_vol']
renamed_fld = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume']
hdf1 = hdf1[chosend_fld]
ren_dict = dict()
i = 0
for x in chosend_fld:
ren_dict[x] = renamed_fld[i]
i += 1
hdf1.rename(columns = ren_dict, inplace=True)
hdf1[['Date']] = hdf1[['Date']].apply(pd.to_datetime)
hdf1[['Open','High','Low','Close','Volume']] = hdf1[['Open','High','Low','Close','Volume']].apply(pd.to_numeric)
hdf1.set_index('Date', inplace=True)
if(adVar):
hdf1['inter_volatile'] = (hdf1['High']-hdf1['Low'])/hdf1['Close']
hdf1['pct_change'] = (hdf1['Close'] - hdf1['Close'].shift(-1))/hdf1['Close'].shift(-1) * 100
return hdf1
# 투자자별 매매 동향
# Input: 종목코드
# output: 매매 동향 History DataFrame (Date, PerBuy, ForBuy, OrgBuy) 30개 row를 반환
def get_stock_investor(stock_no):
url = "/uapi/domestic-stock/v1/quotations/inquire-investor"
tr_id = "FHKST01010900"
params = {
"FID_COND_MRKT_DIV_CODE": _getStockDiv(stock_no),
"FID_INPUT_ISCD": stock_no
}
t1 = _url_fetch(url, tr_id, params)
if t1.isOK():
hdf1 = pd.DataFrame(t1.getBody().output)
chosend_fld = ['stck_bsop_date', 'prsn_ntby_qty', 'frgn_ntby_qty', 'orgn_ntby_qty']
renamed_fld = ['Date', 'PerBuy', 'ForBuy', 'OrgBuy']
hdf1 = hdf1[chosend_fld]
ren_dict = dict()
i = 0
for x in chosend_fld:
ren_dict[x] = renamed_fld[i]
i += 1
hdf1.rename(columns = ren_dict, inplace=True)
hdf1[['Date']] = hdf1[['Date']].apply(pd.to_datetime)
hdf1[['PerBuy','ForBuy','OrgBuy']] = hdf1[['PerBuy','ForBuy','OrgBuy']].apply(pd.to_numeric)
hdf1['EtcBuy'] = (hdf1['PerBuy'] + hdf1['ForBuy'] + hdf1['OrgBuy']) * -1
hdf1.set_index('Date', inplace=True)
#sum을 맨 마지막에 추가하는 경우
#tdf.append(tdf.sum(numeric_only=True), ignore_index=True) <- index를 없애고 만드는 경우
#tdf.loc['Total'] = tdf.sum() <- index 에 Total 을 추가하는 경우
return hdf1
else:
t1.printError()
return pd.DataFrame()

View File

@@ -0,0 +1,208 @@
"""
해당 접근토큰발급관리 파일을 그대로 사용하시면 오류 발생할 수 있습니다.
토큰저장 및 발급 함수 등을 참고하시되 본인 환경에 맞게 코드 수정하셔서 사용하시기 바랍니다.
Created on Wed Feb 15 16:57:19 2023
@author: Administrator
"""
import time, copy
import yaml
import requests
import json
import os
import pandas as pd
from collections import namedtuple
from datetime import datetime
config_root = os.getcwd() + '\\'
# 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)
global _TRENV
_TRENV = nt1(**d)
def isPaperTrading(): # 모의투자 매매
return _isPaper
# 실전투자면 'prod', 모의투자면 'vps'를 셋팅 하시기 바랍니다.
def changeTREnv(token_key, svr='prod', product='01'):
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 == '03': # 실전투자 선물옵션(파생)
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]
_setTRENV(cfg)
def _getResultObject(json_data):
_tc_ = namedtuple('res', json_data.keys())
return _tc_(**json_data)
# Token 발급, 유효기간 1일, 6시간 이내 발급시 기존 token값 유지, 발급시 알림톡 무조건 발송
def auth(svr='prod', product='01', 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='01'):
n2 = datetime.now()
if (n2 - _last_auth_time).seconds >= 86400: # 유효시간 1일
auth(svr, product)
# 접근토큰발급 저장
auth()
# 접근토큰 조회
gettoken = read_token()
print(gettoken)

View File

@@ -0,0 +1,28 @@
#홈페이지에서 API서비스 신청시 받은 Appkey, Appsecret 값 설정
#실전투자
my_app: ""
my_sec: ""
#모의투자
paper_app: ""
paper_sec: ""
#계좌번호 앞 8자리
my_acct_stock: "12345678"
my_acct_future: ""
my_paper_stock: ""
my_paper_future: ""
#계좌번호 뒤 2자리
my_prod: "01"
#domain info
prod: "https://openapi.koreainvestment.com:9443" #서비스
ops: "ws://ops.koreainvestment.com:21000" #웹소켓
vps: "https://openapivts.koreainvestment.com:29443" #모의투자서비스
vops: "ws://ops.koreainvestment.com:31000" #모의투자웹소켓
my_token: ""
# User-Agent; Chrome > F12 개발자 모드 > Console > navigator.userAgent > 자신의 userAgent 확인가능
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,20 @@
my_app: ""
my_sec: ""
paper_app: ""
paper_sec: ""
my_acct_stock: ""
my_acct_future: ""
my_paper_stock: ""
my_paper_future: ""
# User Agent
my_agent: "'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36"
#domain info
prod: "https://openapi.koreainvestment.com:9443" #서비스
ops: "ws://ops.koreainvestment.com:21000" #웹소켓
vps: "https://openapivts.koreainvestment.com:29443" #모의투자서비스
vops: "ws://ops.koreainvestment.com:31000" #모의투자웹소켓

Binary file not shown.

View File

@@ -0,0 +1,14 @@
program KISWebSocket;
uses
Vcl.Forms,
MainForm in 'MainForm.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,183 @@
object Form1: TForm1
Left = 0
Top = 0
Caption = #54620#44397#53804#51088#51613#44428' WebSocket '#53580#49828#53944' v1.0'
ClientHeight = 600
ClientWidth = 750
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
Position = poScreenCenter
OnCreate = FormCreate
TextHeight = 15
object Label1: TLabel
Left = 24
Top = 24
Width = 73
Height = 15
Caption = 'Approval Key:'
end
object Label2: TLabel
Left = 24
Top = 58
Width = 32
Height = 15
Caption = 'TR_ID:'
end
object Label3: TLabel
Left = 24
Top = 92
Width = 89
Height = 15
Caption = 'TR_KEY:'#51333#47785#53076#46300
end
object edtApprovalKey: TEdit
Left = 120
Top = 21
Width = 420
Height = 23
TabOrder = 0
TextHint = 'Approval Key '#51077#47141' (e.g. e44ffe64-7a22-...)'
end
object edtTrId: TEdit
Left = 120
Top = 55
Width = 150
Height = 23
TabOrder = 1
Text = 'H0STCNT0'
TextHint = 'TR_ID ('#50696': H0STCNT0)'
OnChange = edtTrIdChange
end
object edtTrKey: TEdit
Left = 120
Top = 89
Width = 150
Height = 23
TabOrder = 2
Text = '005930'
TextHint = #51333#47785#53076#46300' ('#50696': 005930)'
end
object btnConnect: TButton
Left = 560
Top = 19
Width = 170
Height = 30
Caption = #50672#44208
TabOrder = 3
OnClick = btnConnectClick
end
object btnSubscribe: TButton
Left = 560
Top = 55
Width = 80
Height = 30
Caption = #44396#46021
TabOrder = 4
OnClick = btnSubscribeClick
end
object btnUnsubscribe: TButton
Left = 650
Top = 55
Width = 80
Height = 30
Caption = #44396#46021#54644#51228
TabOrder = 5
OnClick = btnUnsubscribeClick
end
object btnDisconnect: TButton
Left = 560
Top = 91
Width = 170
Height = 30
Caption = #50672#44208' '#54644#51228
TabOrder = 6
OnClick = btnDisconnectClick
end
object Memo1: TMemo
Left = 24
Top = 136
Width = 706
Height = 441
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Consolas'
Font.Style = []
ParentFont = False
ScrollBars = ssVertical
TabOrder = 7
end
object sgcWebSocketClient1: TsgcWebSocketClient
Port = 80
ConnectTimeout = 0
ReadTimeout = -1
WriteTimeout = 0
TLS = False
Proxy.Enabled = False
Proxy.Port = 8080
Proxy.ProxyType = pxyHTTP
HeartBeat.Enabled = True
HeartBeat.HeartBeatType = hbtAlways
HeartBeat.Interval = 30000
HeartBeat.Timeout = 0
IPVersion = Id_IPv4
OnConnect = sgcWebSocketClient1Connect
OnMessage = sgcWebSocketClient1Message
OnDisconnect = sgcWebSocketClient1Disconnect
OnError = sgcWebSocketClient1Error
Authentication.Enabled = False
Authentication.URL.Enabled = True
Authentication.Session.Enabled = False
Authentication.Basic.Enabled = False
Authentication.Token.Enabled = False
Authentication.Token.AuthName = 'Bearer'
Extensions.DeflateFrame.Enabled = False
Extensions.DeflateFrame.WindowBits = 15
Extensions.PerMessage_Deflate.Enabled = False
Extensions.PerMessage_Deflate.ClientMaxWindowBits = 15
Extensions.PerMessage_Deflate.ClientNoContextTakeOver = False
Extensions.PerMessage_Deflate.MemLevel = 9
Extensions.PerMessage_Deflate.ServerMaxWindowBits = 15
Extensions.PerMessage_Deflate.ServerNoContextTakeOver = False
Options.CleanDisconnect = False
Options.FragmentedMessages = frgOnlyBuffer
Options.Parameters = '/'
Options.RaiseDisconnectExceptions = True
Options.ValidateUTF8 = False
Specifications.Drafts.Hixie76 = False
Specifications.RFC6455 = True
NotifyEvents = neAsynchronous
LogFile.Enabled = False
QueueOptions.Binary.Level = qmNone
QueueOptions.Ping.Level = qmNone
QueueOptions.Text.Level = qmNone
WatchDog.Attempts = 0
WatchDog.Enabled = False
WatchDog.Interval = 10
Throttle.BitsPerSec = 0
Throttle.Enabled = False
LoadBalancer.Enabled = False
LoadBalancer.Port = 0
LoadBalancer.TLS = False
TLSOptions.VerifyCertificate = False
TLSOptions.VerifyDepth = 0
TLSOptions.Version = tlsUndefined
TLSOptions.IOHandler = iohOpenSSL
TLSOptions.OpenSSL_Options.APIVersion = oslAPI_1_0
TLSOptions.OpenSSL_Options.LegacyProvider.Enabled = False
TLSOptions.OpenSSL_Options.LegacyProvider.LibPath = oslpNone
TLSOptions.OpenSSL_Options.LibPath = oslpNone
TLSOptions.OpenSSL_Options.UnixSymLinks = oslsSymLinksDefault
TLSOptions.OpenSSL_Options.VersionMin = tlsUndefined
TLSOptions.OpenSSL_Options.X509Checks.Mode = []
TLSOptions.SChannel_Options.CertStoreName = scsnMY
TLSOptions.SChannel_Options.CertStorePath = scspStoreCurrentUser
TLSOptions.SChannel_Options.UseLegacyCredentials = False
Left = 400
Top = 64
end
end

View File

@@ -0,0 +1,306 @@
unit MainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, System.JSON,
Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, Vcl.Dialogs,
sgcWebSocket, sgcWebSocket_Classes, sgcWebSocket_Client, sgcBase_Classes,
sgcSocket_Classes, sgcTCP_Classes, sgcWebSocket_Classes_Indy;
type
TForm1 = class(TForm)
sgcWebSocketClient1: TsgcWebSocketClient;
btnConnect: TButton;
btnSubscribe: TButton;
btnUnsubscribe: TButton;
btnDisconnect: TButton;
Memo1: TMemo;
edtApprovalKey: TEdit;
edtTrId: TEdit;
edtTrKey: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure btnConnectClick(Sender: TObject);
procedure btnSubscribeClick(Sender: TObject);
procedure btnUnsubscribeClick(Sender: TObject);
procedure btnDisconnectClick(Sender: TObject);
procedure sgcWebSocketClient1Connect(Connection: TsgcWSConnection);
procedure sgcWebSocketClient1Disconnect(Connection: TsgcWSConnection; Code: Integer);
procedure sgcWebSocketClient1Message(Connection: TsgcWSConnection; const Text: string);
procedure sgcWebSocketClient1Error(Connection: TsgcWSConnection; const Error: string);
procedure FormCreate(Sender: TObject);
procedure edtTrIdChange(Sender: TObject);
private
APPROVAL_KEY: string;
procedure LogMessage(const Msg: string);
procedure SendSubscription(const TrType: string);
procedure UpdateWebSocketURL;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// 웹소켓 초기 설정
sgcWebSocketClient1.Active := False;
sgcWebSocketClient1.HeartBeat.Enabled := True;
sgcWebSocketClient1.HeartBeat.Interval := 30000;
btnSubscribe.Enabled := False;
btnUnsubscribe.Enabled := False;
btnDisconnect.Enabled := False;
Memo1.Clear;
// 기본값 설정
edtTrId.Text := 'H0STCNT0'; // 국내주식 체결통보
edtTrKey.Text := '005930'; // 삼성전자
// URL 초기 설정
UpdateWebSocketURL;
LogMessage('═══════════════════════════════════════════');
LogMessage(' 한국투자증권 웹소켓 클라이언트 v1.0');
LogMessage('═══════════════════════════════════════════');
LogMessage('');
LogMessage('[TR_ID 예시]');
LogMessage(' H0STCNT0 : 국내주식 체결가');
LogMessage(' H0STASP0 : 국내주식 호가');
LogMessage(' HDFSCNT0 : 해외주식 지연체결가');
LogMessage(' HDFSASP0 : 해외주식 호가');
LogMessage('');
LogMessage('[종목코드 예시]');
LogMessage(' 국내: 005930 (삼성전자), 000660 (SK하이닉스)');
LogMessage(' 해외: DNASTSLA (나스닥 테슬라), DNYSBABA (뉴욕 알리바바)');
LogMessage('═══════════════════════════════════════════');
end;
procedure TForm1.UpdateWebSocketURL;
var
trId: string;
begin
trId := Trim(edtTrId.Text);
if trId <> '' then
begin
sgcWebSocketClient1.URL := 'ws://ops.koreainvestment.com:21000/tryitout/' + trId;
LogMessage('URL 업데이트: ws://ops.koreainvestment.com:21000/tryitout/' + trId);
end;
end;
procedure TForm1.edtTrIdChange(Sender: TObject);
begin
// TR_ID가 변경되면 URL도 자동 업데이트
if not sgcWebSocketClient1.Active then
UpdateWebSocketURL;
end;
procedure TForm1.LogMessage(const Msg: string);
begin
Memo1.Lines.Add(FormatDateTime('[hh:nn:ss] ', Now) + Msg);
if Memo1.Lines.Count > 0 then
Memo1.Perform(EM_SCROLLCARET, 0, 0);
end;
procedure TForm1.btnConnectClick(Sender: TObject);
var
trId: string;
begin
APPROVAL_KEY := Trim(edtApprovalKey.Text);
trId := Trim(edtTrId.Text);
if APPROVAL_KEY = '' then
begin
ShowMessage('Approval Key를 입력해주세요.');
edtApprovalKey.SetFocus;
Exit;
end;
if trId = '' then
begin
ShowMessage('TR_ID를 입력해주세요.');
edtTrId.SetFocus;
Exit;
end;
try
// URL 최종 확인 및 업데이트
UpdateWebSocketURL;
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
LogMessage('웹소켓 연결 시도...');
LogMessage('TR_ID: ' + trId);
sgcWebSocketClient1.Active := True;
except
on E: Exception do
begin
LogMessage('연결 실패: ' + E.Message);
ShowMessage('연결 실패: ' + E.Message);
end;
end;
end;
procedure TForm1.btnDisconnectClick(Sender: TObject);
begin
if sgcWebSocketClient1.Active then
begin
LogMessage('연결 해제 중...');
sgcWebSocketClient1.Active := False;
end;
end;
procedure TForm1.SendSubscription(const TrType: string);
var
jHeader, jBody, jInput, jSend: TJSONObject;
sJson: string;
begin
if not sgcWebSocketClient1.Active then
begin
ShowMessage('웹소켓이 연결되지 않았습니다.');
Exit;
end;
jHeader := TJSONObject.Create;
jInput := TJSONObject.Create;
jBody := TJSONObject.Create;
jSend := TJSONObject.Create;
try
// Header 구성
jHeader.AddPair('approval_key', APPROVAL_KEY);
jHeader.AddPair('custtype', 'P');
jHeader.AddPair('tr_type', TrType);
jHeader.AddPair('content-type', 'utf-8');
// Input 구성
jInput.AddPair('tr_id', 'H0STCNT0');
jInput.AddPair('tr_key', '000660');
// Body 구성
jBody.AddPair('input', jInput); // jInput 소유권은 jBody가 가져감
// 전체 JSON 구성
jSend.AddPair('header', jHeader); // jHeader 소유권은 jSend가 가져감
jSend.AddPair('body', jBody); // jBody 소유권은 jSend가 가져감
sJson := jSend.ToJSON;
LogMessage('전송 데이터: ' + sJson);
sgcWebSocketClient1.WriteData(sJson);
if TrType = '1' then
LogMessage('구독 요청 전송 완료')
else
LogMessage('구독 해제 요청 전송 완료');
finally
// ✅ 하위 객체는 해제하지 말고, 최상위 객체(jSend)만 해제
jSend.Free;
end;
end;
procedure TForm1.btnSubscribeClick(Sender: TObject);
begin
SendSubscription('1');
end;
procedure TForm1.btnUnsubscribeClick(Sender: TObject);
begin
SendSubscription('2');
end;
procedure TForm1.sgcWebSocketClient1Connect(Connection: TsgcWSConnection);
begin
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
LogMessage('✓ 웹소켓 연결 성공!');
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
btnConnect.Enabled := False;
btnSubscribe.Enabled := True;
btnUnsubscribe.Enabled := True;
btnDisconnect.Enabled := True;
edtApprovalKey.Enabled := False;
end;
procedure TForm1.sgcWebSocketClient1Disconnect(Connection: TsgcWSConnection; Code: Integer);
begin
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
LogMessage('✗ 웹소켓 연결 해제됨');
LogMessage('종료 코드: ' + IntToStr(Code));
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
btnConnect.Enabled := True;
btnSubscribe.Enabled := False;
btnUnsubscribe.Enabled := False;
btnDisconnect.Enabled := False;
edtApprovalKey.Enabled := True;
end;
procedure TForm1.sgcWebSocketClient1Message(Connection: TsgcWSConnection; const Text: string);
var
jResponse: TJSONObject;
jHeader, jBody: TJSONObject;
trId, rtCd, msgCd, msg1: string;
begin
LogMessage('수신: ' + Text);
try
jResponse := TJSONObject.ParseJSONValue(Text) as TJSONObject;
if Assigned(jResponse) then
try
// Header 파싱
if jResponse.TryGetValue<TJSONObject>('header', jHeader) then
begin
jHeader.TryGetValue<string>('tr_id', trId);
LogMessage('TR_ID: ' + trId);
end;
// Body 파싱
if jResponse.TryGetValue<TJSONObject>('body', jBody) then
begin
// 응답 코드 확인
rtCd := jBody.GetValue<string>('rt_cd', '');
msgCd := jBody.GetValue<string>('msg_cd', '');
msg1 := jBody.GetValue<string>('msg1', '');
if rtCd <> '' then
begin
if rtCd = '0' then
LogMessage('✓ 성공: ' + msg1 + ' [' + msgCd + ']')
else
LogMessage('✗ 실패: ' + msg1 + ' [' + msgCd + ']');
end
else
begin
// 실시간 체결 데이터
LogMessage('실시간 데이터: ' + jBody.ToJSON);
end;
end;
finally
jResponse.Free;
end;
except
on E: Exception do
LogMessage('메시지 파싱 오류: ' + E.Message);
end;
end;
procedure TForm1.sgcWebSocketClient1Error(Connection: TsgcWSConnection; const Error: string);
begin
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
LogMessage('❌ 에러 발생: ' + Error);
LogMessage('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
ShowMessage('에러: ' + Error);
end;
end.

View File

@@ -0,0 +1,560 @@
import asyncio
import websockets
import json
import time
from multiprocessing import Process, Queue, Manager
import json
import time
import requests
import asyncio
import websockets
import urllib3
import os
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 웹소켓 접속키 발급
def get_approval(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"secretkey": secret}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
# 국내주식호가 출력라이브러리
def stockhoka(data):
""" 넘겨받는데이터가 정상인지 확인
print("stockhoka[%s]"%(data))
"""
recvvalue = data.split('^') # 수신데이터를 split '^'
"""
print("유가증권 단축 종목코드 [" + recvvalue[0] + "]")
print("영업시간 [" + recvvalue[1] + "]" + "시간구분코드 [" + recvvalue[2] + "]")
print("======================================")
print("매도호가10 [%s] 잔량10 [%s]" % (recvvalue[12], recvvalue[32]))
print("매도호가09 [%s] 잔량09 [%s]" % (recvvalue[11], recvvalue[31]))
print("매도호가08 [%s] 잔량08 [%s]" % (recvvalue[10], recvvalue[30]))
print("매도호가07 [%s] 잔량07 [%s]" % (recvvalue[9], recvvalue[29]))
print("매도호가06 [%s] 잔량06 [%s]" % (recvvalue[8], recvvalue[28]))
print("매도호가05 [%s] 잔량05 [%s]" % (recvvalue[7], recvvalue[27]))
print("매도호가04 [%s] 잔량04 [%s]" % (recvvalue[6], recvvalue[26]))
print("매도호가03 [%s] 잔량03 [%s]" % (recvvalue[5], recvvalue[25]))
print("매도호가02 [%s] 잔량02 [%s]" % (recvvalue[4], recvvalue[24]))
print("매도호가01 [%s] 잔량01 [%s]" % (recvvalue[3], recvvalue[23]))
print("--------------------------------------")
print("매수호가01 [%s] 잔량01 [%s]" % (recvvalue[13], recvvalue[33]))
print("매수호가02 [%s] 잔량02 [%s]" % (recvvalue[14], recvvalue[34]))
print("매수호가03 [%s] 잔량03 [%s]" % (recvvalue[15], recvvalue[35]))
print("매수호가04 [%s] 잔량04 [%s]" % (recvvalue[16], recvvalue[36]))
print("매수호가05 [%s] 잔량05 [%s]" % (recvvalue[17], recvvalue[37]))
print("매수호가06 [%s] 잔량06 [%s]" % (recvvalue[18], recvvalue[38]))
print("매수호가07 [%s] 잔량07 [%s]" % (recvvalue[19], recvvalue[39]))
print("매수호가08 [%s] 잔량08 [%s]" % (recvvalue[20], recvvalue[40]))
print("매수호가09 [%s] 잔량09 [%s]" % (recvvalue[21], recvvalue[41]))
print("매수호가10 [%s] 잔량10 [%s]" % (recvvalue[22], recvvalue[42]))
print("======================================")
print("총매도호가 잔량 [%s]" % (recvvalue[43]))
print("총매도호가 잔량 증감 [%s]" % (recvvalue[54]))
print("총매수호가 잔량 [%s]" % (recvvalue[44]))
print("총매수호가 잔량 증감 [%s]" % (recvvalue[55]))
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[45]))
print("시간외 총매수호가 증감 [%s]" % (recvvalue[46]))
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[56]))
print("시간외 총매수호가 증감 [%s]" % (recvvalue[57]))
print("예상 체결가 [%s]" % (recvvalue[47]))
print("예상 체결량 [%s]" % (recvvalue[48]))
print("예상 거래량 [%s]" % (recvvalue[49]))
print("예상체결 대비 [%s]" % (recvvalue[50]))
print("부호 [%s]" % (recvvalue[51]))
print("예상체결 전일대비율 [%s]" % (recvvalue[52]))
print("누적거래량 [%s]" % (recvvalue[53]))
print("주식매매 구분코드 [%s]" % (recvvalue[58]))
"""
# 국내주식체결처리 출력라이브러리
def stockspurchase(data_cnt, data):
print("============================================")
menulist = "유가증권단축종목코드|주식체결시간|주식현재가|전일대비부호|전일대비|전일대비율|가중평균주식가격|주식시가|주식최고가|주식최저가|매도호가1|매수호가1|체결거래량|누적거래량|누적거래대금|매도체결건수|매수체결건수|순매수체결건수|체결강도|총매도수량|총매수수량|체결구분|매수비율|전일거래량대비등락율|시가시간|시가대비구분|시가대비|최고가시간|고가대비구분|고가대비|최저가시간|저가대비구분|저가대비|영업일자|신장운영구분코드|거래정지여부|매도호가잔량|매수호가잔량|총매도호가잔량|총매수호가잔량|거래량회전율|전일동시간누적거래량|전일동시간누적거래량비율|시간구분코드|임의종료구분코드|정적VI발동기준가"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
## print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
## print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 국내주식체결통보 출력라이브러리
def stocksigningnotice_domestic(data, key, iv):
# AES256 처리 단계
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
pValue = aes_dec_str.split('^')
if pValue[13] == '2': # 체결통보
## print("#### 국내주식 체결 통보 ####")
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|체결수량|체결단가|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|주문가격"
menustr1 = menulist.split('|')
else:
## print("#### 국내주식 주문·정정·취소·거부 접수 통보 ####")
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|주문수량|주문가격|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|체결단가"
menustr1 = menulist.split('|')
i = 0
for menu in menustr1:
print("%s [%s]" % (menu, pValue[i]))
i += 1
def data_processor_worker(worker_id, data_queue, stock_code):
"""
데이터 처리 전용 워커 프로세스
Args:
worker_id: 워커 식별 번호
data_queue: 메인 프로세스로부터 데이터를 받을 큐
stock_code: 담당 종목코드
"""
import sys
process_id = os.getpid()
print(f"\n{'#'*80}")
print(f"[Processor-{worker_id}] 시작: PID={process_id}, 담당종목={stock_code}")
print(f"{'#'*80}\n")
processed_count = 0
message = None # 에러 추적용
error_count = 0
try:
while True:
try:
# 큐에서 데이터 가져오기 (타임아웃 1초)
message = data_queue.get(timeout=1.0)
if message is None: # 종료 신호
print(f"[Processor-{worker_id}] [PID:{process_id}] 종료 신호 수신")
break
# 메시지 유효성 검사
if not isinstance(message, dict):
print(f"[Processor-{worker_id}] [PID:{process_id}] 잘못된 메시지 타입: {type(message)}, 내용: {message}")
continue
msg_type = message.get('type')
data = message.get('data')
source_stock = message.get('stock_code')
if not msg_type:
print(f"[Processor-{worker_id}] [PID:{process_id}] 메시지 타입 없음: {message}")
continue
if not source_stock:
print(f"[Processor-{worker_id}] [PID:{process_id}] 종목코드 없음: {message}")
continue
# 자신이 담당하는 종목의 데이터만 처리
if source_stock != stock_code:
continue
processed_count += 1
if msg_type == 'hoka':
print(f"[Processor-{worker_id}] [PID:{process_id}] [종목:{stock_code}] "
f"호가 데이터 처리 중... (처리건수: {processed_count})")
stockhoka(data)
elif msg_type == 'chegyeol':
data_cnt = message.get('data_cnt', 1)
print(f"[Processor-{worker_id}] [PID:{process_id}] [종목:{stock_code}] "
f"체결 데이터 처리 중... (체결건수: {data_cnt}, 총처리: {processed_count})")
stockspurchase(data_cnt, data)
elif msg_type == 'signing_notice':
aes_key = message.get('aes_key')
aes_iv = message.get('aes_iv')
print(f"[Processor-{worker_id}] [PID:{process_id}] [종목:{stock_code}] "
f"체결통보 처리 중... (처리건수: {processed_count})")
stocksigningnotice(data, aes_key, aes_iv)
except Exception as e:
error_count += 1
error_msg = str(e)
# Queue.Empty는 정상 동작
if "Empty" in str(type(e).__name__):
continue
# 기타 에러는 상세 출력
import traceback
error_details = traceback.format_exc()
print(f"\n{'!'*80}")
print(f"[Processor-{worker_id}] [PID:{process_id}] 🔴 처리 에러 발생 (에러 #{error_count})")
print(f" 에러 타입: {type(e).__name__}")
print(f" 에러 메시지: '{error_msg}' (길이: {len(error_msg)})")
print(f" 에러 repr: {repr(e)}")
if message:
print(f" 문제 메시지 타입: {type(message)}")
try:
print(f" 문제 메시지 내용: {message}")
except:
print(f" 문제 메시지 내용: [출력 불가]")
else:
print(f" 문제 메시지: None")
print(f"\n 상세 스택 트레이스:")
print(error_details)
print(f"{'!'*80}\n")
# 에러가 너무 많으면 종료
if error_count > 100:
print(f"[Processor-{worker_id}] [PID:{process_id}] 에러 과다 발생으로 종료")
break
continue
except KeyboardInterrupt:
print(f"[Processor-{worker_id}] [PID:{process_id}] 인터럽트로 종료")
except Exception as e:
print(f"[Processor-{worker_id}] [PID:{process_id}] 치명적 에러: {e}")
import traceback
traceback.print_exc()
print(f"[Processor-{worker_id}] [PID:{process_id}] 종료. 총 처리건수: {processed_count}, 에러: {error_count}")
def parse_stock_code_from_data(raw_data, trid):
"""
실시간 데이터에서 종목코드 추출
Args:
raw_data: 파이프로 구분된 실시간 데이터
trid: TR ID (H0STCNT0, H0STASP0 등)
Returns:
종목코드 (6자리)
"""
try:
# 디버깅: 처음 3번만 전체 데이터 출력
if not hasattr(parse_stock_code_from_data, 'debug_count'):
parse_stock_code_from_data.debug_count = 0
if parse_stock_code_from_data.debug_count < 3:
print(f"\n[DEBUG parse_stock_code] TR_ID: {trid}")
print(f"[DEBUG parse_stock_code] Raw data: {raw_data[:200]}")
parse_stock_code_from_data.debug_count += 1
# 한국투자증권 실시간 데이터는 ^ 구분자로 필드가 나뉨
fields = raw_data.split('^')
if parse_stock_code_from_data.debug_count <= 3:
print(f"[DEBUG parse_stock_code] 필드 개수: {len(fields)}")
print(f"[DEBUG parse_stock_code] 첫 5개 필드: {fields[:5]}\n")
if len(fields) > 0:
# 첫 번째 필드가 보통 종목코드
stock_code = fields[0].strip()
# 종목코드 검증 (6자리 숫자)
if stock_code.isdigit() and len(stock_code) == 6:
return stock_code
# 파싱 실패 시 None 반환
return None
except Exception as e:
print(f"[parse_stock_code] 에러: {e}, data: {raw_data[:100]}")
return None
async def websocket_receiver(url, approval_key, stock_codes, data_queues, custtype='P'):
"""
단일 웹소켓 세션으로 여러 종목 데이터 수신
수신한 데이터를 각 프로세스의 큐에 분배
Args:
url: 웹소켓 URL
approval_key: 승인 키
stock_codes: 구독할 종목 리스트
data_queues: 각 워커의 데이터 큐 딕셔너리 {종목코드: Queue}
custtype: 고객 타입
"""
main_pid = os.getpid()
aes_key = None
aes_iv = None
retry_count = 0
max_retries = 5
while retry_count < max_retries:
try:
print(f"\n{'='*80}")
print(f"[WebSocket Main] PID: {main_pid}")
print(f"[WebSocket Main] 연결 시도... URL: {url}")
print(f"[WebSocket Main] 구독 종목: {', '.join(stock_codes)}")
print(f"{'='*80}\n")
async with websockets.connect(url, ping_interval=None) as websocket:
# 웹소켓 세션 정보
local_address = websocket.local_address
remote_address = websocket.remote_address
print(f"\n{'*'*80}")
print(f"[WebSocket Main] 세션 정보")
print(f" - 메인 프로세스 PID: {main_pid}")
print(f" - 로컬 주소: {local_address[0]}:{local_address[1]}")
print(f" - 원격 주소: {remote_address[0]}:{remote_address[1]}")
print(f" - 웹소켓 객체 ID: {id(websocket)}")
print(f" - 구독 종목 수: {len(stock_codes)}")
print(f"{'*'*80}\n")
# 각 종목에 대해 구독 요청 전송
for i, stock_code in enumerate(stock_codes):
await asyncio.sleep(0.5) # 요청 간격
senddata = {
"header": {
"approval_key": approval_key,
"custtype": custtype,
"tr_type": "1",
"content-type": "utf-8"
},
"body": {
"input": {
"tr_id": "H0STCNT0", # 주식체결
"tr_key": stock_code
}
}
}
senddata_str = json.dumps(senddata, ensure_ascii=False)
print(f"[WebSocket Main] [{i+1}/{len(stock_codes)}] 구독 요청: {stock_code}")
await websocket.send(senddata_str)
retry_count = 0 # 연결 성공 시 재시도 카운트 초기화
subscribe_count = 0
# 데이터 수신 및 분배 루프
print(f"\n[WebSocket Main] 데이터 수신 대기 중...\n")
while True:
try:
data = await asyncio.wait_for(websocket.recv(), timeout=30.0)
except asyncio.TimeoutError:
print(f"[WebSocket Main] 타임아웃 - 연결 유지 중...")
continue
# 실시간 데이터 처리 (0 또는 1로 시작)
if data[0] in ('0', '1'):
recvstr = data.split('|')
if len(recvstr) < 4:
print(f"[WebSocket Main] Invalid data: {data}")
continue
trid0 = recvstr[1]
raw_data = recvstr[3]
# 실제 데이터에서 종목코드 추출
target_stock = parse_stock_code_from_data(raw_data, trid0)
# 종목코드 추출 실패 또는 구독하지 않은 종목
if not target_stock:
print(f"[WebSocket Main] ⚠️ 종목코드 추출 실패: {raw_data[:50]}...")
continue
if target_stock not in data_queues:
print(f"[WebSocket Main] ⚠️ 구독하지 않은 종목: {target_stock}")
continue
if data[0] == '0':
# 주식호가
if trid0 == "H0STASP0":
message = {
'type': 'hoka',
'data': raw_data,
'stock_code': target_stock
}
data_queues[target_stock].put(message)
print(f"[WebSocket Main] 호가 데이터 → Processor (종목: {target_stock})")
# 주식체결
elif trid0 == "H0STCNT0":
data_cnt = int(recvstr[2])
message = {
'type': 'chegyeol',
'data': raw_data,
'data_cnt': data_cnt,
'stock_code': target_stock
}
data_queues[target_stock].put(message)
print(f"[WebSocket Main] 체결 데이터 → Processor (종목: {target_stock}, 건수: {data_cnt})")
elif data[0] == '1':
# 주식체결 통보
if trid0 in ("K0STCNI0", "K0STCNI9", "H0STCNI0", "H0STCNI9"):
if aes_key and aes_iv:
message = {
'type': 'signing_notice',
'data': raw_data,
'aes_key': aes_key,
'aes_iv': aes_iv,
'stock_code': target_stock
}
data_queues[target_stock].put(message)
print(f"[WebSocket Main] 체결통보 → Processor (종목: {target_stock})")
# JSON 메시지 처리
else:
try:
jsonObject = json.loads(data)
trid = jsonObject["header"]["tr_id"]
# PINGPONG 처리
if trid == "PINGPONG":
print(f"[WebSocket Main] RECV [PINGPONG]")
await websocket.send(data)
print(f"[WebSocket Main] SEND [PINGPONG]")
# 일반 응답 처리
else:
rt_cd = jsonObject["body"]["rt_cd"]
tr_key = jsonObject["header"]["tr_key"]
msg = jsonObject["body"].get("msg1", "")
if rt_cd == '1': # 에러
print(f"[WebSocket Main] ❌ ERROR [종목:{tr_key}] MSG [{msg}]")
elif rt_cd == '0': # 정상
print(f"[WebSocket Main] ✓ SUCCESS [종목:{tr_key}] MSG [{msg}]")
if "SUBSCRIBE SUCCESS" in msg or "SUCCESS" in msg:
subscribe_count += 1
print(f"[WebSocket Main] 🎉 구독 완료! ({subscribe_count}/{len(stock_codes)})")
# AES 키 저장
if trid in ("H0STCNI0", "H0STCNI9"):
aes_key = jsonObject["body"]["output"]["key"]
aes_iv = jsonObject["body"]["output"]["iv"]
print(f"[WebSocket Main] AES KEY 저장: {aes_key[:20]}...")
except json.JSONDecodeError as e:
print(f"[WebSocket Main] JSON error: {e}")
except KeyError as e:
print(f"[WebSocket Main] Key error: {e}")
except websockets.exceptions.ConnectionClosed as e:
print(f"[WebSocket Main] 연결 종료: {e}")
retry_count += 1
await asyncio.sleep(2 ** retry_count)
except Exception as e:
print(f"[WebSocket Main] Exception: {e}")
import traceback
traceback.print_exc()
retry_count += 1
await asyncio.sleep(2 ** retry_count)
print(f"[WebSocket Main] 최대 재시도 횟수 초과")
def main():
"""메인 함수"""
# API 키 설정
g_appkey = '앱키 입력해주세요'
g_appsecret = '앱시크릿키 입력해주세요'
custtype = 'P' # 개인
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
# 모니터링할 종목 리스트
stock_codes = [
'005930', # 삼성전자
'000660', # SK하이닉스
'035420', # NAVER
'005380', # 현대차
'051910', # LG화학
]
print(f"\n{'='*80}")
print(f"메인 프로세스 PID: {os.getpid()}")
print(f"구독 종목: {', '.join(stock_codes)}")
print(f"{'='*80}\n")
try:
# 1. Approval key 발급
print("=== Approval Key 발급 중 ===")
approval_key = get_approval(g_appkey, g_appsecret)
print(f"Approval Key: {approval_key}\n")
# 2. 각 종목별 데이터 큐 생성 (Manager 사용)
manager = Manager()
data_queues = {}
for stock_code in stock_codes:
data_queues[stock_code] = manager.Queue(maxsize=1000)
# 3. 데이터 처리 워커 프로세스 생성
processes = []
for i, stock_code in enumerate(stock_codes):
worker_id = i + 1
p = Process(
target=data_processor_worker,
args=(worker_id, data_queues[stock_code], stock_code)
)
p.start()
processes.append(p)
print(f"✓ Processor-{worker_id} 시작: PID={p.pid}, 종목={stock_code}")
time.sleep(0.2)
print(f"\n=== 모든 프로세서 시작 완료 ({len(processes)}개) ===\n")
# 4. 웹소켓 수신 시작 (메인 프로세스에서 실행)
print("=== 웹소켓 연결 시작 ===\n")
asyncio.run(websocket_receiver(url, approval_key, stock_codes, data_queues, custtype))
except KeyboardInterrupt:
print("\n\n=== 프로그램 종료 중 ===")
# 모든 워커에 종료 신호 전송
for stock_code in stock_codes:
try:
data_queues[stock_code].put(None)
except:
pass
# 프로세스 종료 대기
for p in processes:
p.join(timeout=2)
if p.is_alive():
p.terminate()
print("모든 프로세서 종료 완료")
except Exception as e:
print(f"메인 프로세스 에러: {e}")
import traceback
traceback.print_exc()
for p in processes:
p.terminate()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,299 @@
# 웹 소켓 모듈을 선언한다.
import websockets
import json
import requests
import os
import asyncio
import time
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
# AES256 DECODE
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))
# 웹소켓 접속키 발급
def get_approval(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"secretkey": secret}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
# 주식체결 출력라이브러리
def stockhoka(data):
""" 넘겨받는데이터가 정상인지 확인
print("stockhoka[%s]"%(data))
"""
recvvalue = data.split('^') # 수신데이터를 split '^'
print("유가증권 단축 종목코드 [" + recvvalue[0] + "]")
print("영업시간 [" + recvvalue[1] + "]" + "시간구분코드 [" + recvvalue[2] + "]")
print("======================================")
print("매도호가10 [%s] 잔량10 [%s]" % (recvvalue[12], recvvalue[32]))
print("매도호가09 [%s] 잔량09 [%s]" % (recvvalue[11], recvvalue[31]))
print("매도호가08 [%s] 잔량08 [%s]" % (recvvalue[10], recvvalue[30]))
print("매도호가07 [%s] 잔량07 [%s]" % (recvvalue[9], recvvalue[29]))
print("매도호가06 [%s] 잔량06 [%s]" % (recvvalue[8], recvvalue[28]))
print("매도호가05 [%s] 잔량05 [%s]" % (recvvalue[7], recvvalue[27]))
print("매도호가04 [%s] 잔량04 [%s]" % (recvvalue[6], recvvalue[26]))
print("매도호가03 [%s] 잔량03 [%s]" % (recvvalue[5], recvvalue[25]))
print("매도호가02 [%s] 잔량02 [%s]" % (recvvalue[4], recvvalue[24]))
print("매도호가01 [%s] 잔량01 [%s]" % (recvvalue[3], recvvalue[23]))
print("--------------------------------------")
print("매수호가01 [%s] 잔량01 [%s]" % (recvvalue[13], recvvalue[33]))
print("매수호가02 [%s] 잔량02 [%s]" % (recvvalue[14], recvvalue[34]))
print("매수호가03 [%s] 잔량03 [%s]" % (recvvalue[15], recvvalue[35]))
print("매수호가04 [%s] 잔량04 [%s]" % (recvvalue[16], recvvalue[36]))
print("매수호가05 [%s] 잔량05 [%s]" % (recvvalue[17], recvvalue[37]))
print("매수호가06 [%s] 잔량06 [%s]" % (recvvalue[18], recvvalue[38]))
print("매수호가07 [%s] 잔량07 [%s]" % (recvvalue[19], recvvalue[39]))
print("매수호가08 [%s] 잔량08 [%s]" % (recvvalue[20], recvvalue[40]))
print("매수호가09 [%s] 잔량09 [%s]" % (recvvalue[21], recvvalue[41]))
print("매수호가10 [%s] 잔량10 [%s]" % (recvvalue[22], recvvalue[42]))
print("======================================")
print("총매도호가 잔량 [%s]" % (recvvalue[43]))
print("총매도호가 잔량 증감 [%s]" % (recvvalue[54]))
print("총매수호가 잔량 [%s]" % (recvvalue[44]))
print("총매수호가 잔량 증감 [%s]" % (recvvalue[55]))
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[45]))
print("시간외 총매수호가 증감 [%s]" % (recvvalue[46]))
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[56]))
print("시간외 총매수호가 증감 [%s]" % (recvvalue[57]))
print("예상 체결가 [%s]" % (recvvalue[47]))
print("예상 체결량 [%s]" % (recvvalue[48]))
print("예상 거래량 [%s]" % (recvvalue[49]))
print("예상체결 대비 [%s]" % (recvvalue[50]))
print("부호 [%s]" % (recvvalue[51]))
print("예상체결 전일대비율 [%s]" % (recvvalue[52]))
print("누적거래량 [%s]" % (recvvalue[53]))
print("주식매매 구분코드 [%s]" % (recvvalue[58]))
# 주식체결처리 출력라이브러리
def stockspurchase(data_cnt, data):
print("============================================")
menulist = "유가증권단축종목코드|주식체결시간|주식현재가|전일대비부호|전일대비|전일대비율|가중평균주식가격|주식시가|주식최고가|주식최저가|매도호가1|매수호가1|체결거래량|누적거래량|누적거래대금|매도체결건수|매수체결건수|순매수체결건수|체결강도|총매도수량|총매수수량|체결구분|매수비율|전일거래량대비등락율|시가시간|시가대비구분|시가대비|최고가시간|고가대비구분|고가대비|최저가시간|저가대비구분|저가대비|영업일자|신장운영구분코드|거래정지여부|매도호가잔량|매수호가잔량|총매도호가잔량|총매수호가잔량|거래량회전율|전일동시간누적거래량|전일동시간누적거래량비율|시간구분코드|임의종료구분코드|정적VI발동기준가"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]"%(cnt+1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 국내주식체결통보 출력라이브러리
def stocksigningnotice(data, key, iv):
# AES256 처리 단계
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
pValue = aes_dec_str.split('^')
if pValue[13] == '2': # 체결통보
print("#### 국내주식 체결 통보 ####")
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|체결수량|체결단가|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|주문가격"
menustr1 = menulist.split('|')
else:
print("#### 국내주식 주문·정정·취소·거부 접수 통보 ####")
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|주문수량|주문가격|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|체결단가"
menustr1 = menulist.split('|')
i = 0
for menu in menustr1:
print("%s [%s]" % (menu, pValue[i]))
i += 1
async def connect():
try:
# 웹 소켓에 접속.( 주석은 koreainvest test server for websocket)
## 시세데이터를 받기위한 데이터를 미리 할당해서 사용한다.
g_appkey = '앱키를 입력하세요'
g_appsecret = '앱 시크릿키를 입력하세요'
stockcode = '005930' # 테스트용 임시 종목 설정, 삼성전자
htsid = 'HTS ID를 입력하세요' # 체결통보용 htsid 입력
custtype = 'P' # customer type, 개인:'P' 법인 'B'
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
g_approval_key = get_approval(g_appkey, g_appsecret)
print("approval_key [%s]" % (g_approval_key))
async with websockets.connect(url, ping_interval=None) as websocket:
"""" 주석처리는 더블쿼트 3개로 처리
"""
print("1.주식호가, 2.주식호가해제, 3.주식체결, 4.주식체결해제, 5.주식체결통보(고객), 6.주식체결통보해제(고객), 7.주식체결통보(모의), 8.주식체결통보해제(모의)")
print("Input Command :")
cmd = input()
# 입력값 체크 step
if cmd < '0' or cmd > '8':
print("> Wrong Input Data", cmd)
elif cmd == '0':
print("Exit!!")
# 입력값에 따라 전송 데이터셋 구분 처리
if cmd == '1': # 주식호가 등록
tr_id = 'H0STASP0'
tr_type = '1'
elif cmd == '2': # 주식호가 등록해제
tr_id = 'H0STASP0'
tr_type = '2'
elif cmd == '3': # 주식체결 등록
tr_id = 'H0STCNT0'
tr_type = '1'
elif cmd == '4': # 주식체결 등록해제
tr_id = 'H0STCNT0'
tr_type = '2'
elif cmd == '5': # 주식체결통보 등록(고객용)
tr_id = 'H0STCNI0' # 고객체결통보
tr_type = '1'
elif cmd == '6': # 주식체결통보 등록해제(고객용)
tr_id = 'H0STCNI0' # 고객체결통보
tr_type = '2'
elif cmd == '7': # 주식체결통보 등록(모의)
tr_id = 'H0STCNI9' #테스트용 직원체결통보
tr_type = '1'
elif cmd == '8': # 주식체결통보 등록해제(모의)
tr_id = 'H0STCNI9' # 테스트용 직원체결통보
tr_type = '2'
else:
senddata = 'wrong inert data'
# send json, 체결통보는 tr_key 입력항목이 상이하므로 분리를 한다.
if cmd == '5' or cmd == '6' or cmd == '7' or cmd == '8':
senddata = '{"header":{"approval_key":"' + g_approval_key + '","custtype":"'+custtype+'","tr_type":"' + tr_type + '","content-type":"utf-8"},"body":{"input":{"tr_id":"' + tr_id + '","tr_key":"' + htsid + '"}}}'
else :
senddata = '{"header":{"approval_key":"' + g_approval_key + '","custtype":"'+custtype+'","tr_type":"' + tr_type + '","content-type":"utf-8"},"body":{"input":{"tr_id":"' + tr_id + '","tr_key":"' + stockcode + '"}}}'
print('Input Command is :', senddata)
await websocket.send(senddata)
# 무한히 데이터가 오기만 기다린다.
while True:
data = await websocket.recv()
# print("Recev Command is :", data)
if data[0] == '0' or data[0] == '1': # 실시간 데이터일 경우
trid = jsonObject["header"]["tr_id"]
if data[0] == '0':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0STASP0": # 주식호가tr 일경우의 처리 단계
print("#### 주식호가 ####")
stockhoka(recvstr[3])
await asyncio.sleep(1)
elif trid0 == "H0STCNT0": # 주식체결 데이터 처리
print("#### 주식체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase(data_cnt, recvstr[3])
elif data[0] == '1':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0STCNI0" or trid0 == "H0STCNI9": # 주실체결 통보 처리
stocksigningnotice(recvstr[3], aes_key, aes_iv)
# clearConsole()
# break;
else:
jsonObject = json.loads(data)
trid = jsonObject["header"]["tr_id"]
if trid != "PINGPONG":
rt_cd = jsonObject["body"]["rt_cd"]
if rt_cd == '1': # 에러일 경우 처리
print("### ERROR RETURN CODE [ %s ] MSG [ %s ]" % (rt_cd, jsonObject["body"]["msg1"]))
break
elif rt_cd == '0': # 정상일 경우 처리
print("### RETURN CODE [ %s ] MSG [ %s ]" % (rt_cd, jsonObject["body"]["msg1"]))
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
if trid == "H0STCNI0" or trid == "H0STCNI9":
aes_key = jsonObject["body"]["output"]["key"]
aes_iv = jsonObject["body"]["output"]["iv"]
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
elif trid == "PINGPONG":
print("### RECV [PINGPONG] [%s]" % (data))
await websocket.pong(data)
print("### SEND [PINGPONG] [%s]" % (data))
# ----------------------------------------
# 모든 함수의 공통 부분(Exception 처리)
# ----------------------------------------
except Exception as e:
print('Exception Raised!')
print(e)
print('Connect Again!')
time.sleep(0.1)
# 웹소켓 다시 시작
await connect()
# # 비동기로 서버에 접속한다.
# asyncio.get_event_loop().run_until_complete(connect())
# asyncio.get_event_loop().close()
# -----------------------------------------------------------------------------
# - Name : main
# - Desc : 메인
# -----------------------------------------------------------------------------
async def main():
try:
# 웹소켓 시작
await connect()
except Exception as e:
print('Exception Raised!')
print(e)
if __name__ == "__main__":
# noinspection PyBroadException
try:
# ---------------------------------------------------------------------
# Logic Start!
# ---------------------------------------------------------------------
# 웹소켓 시작
asyncio.run(main())
except KeyboardInterrupt:
print("KeyboardInterrupt Exception 발생!")
print(traceback.format_exc())
sys.exit(-100)
except Exception:
print("Exception 발생!")
print(traceback.format_exc())
sys.exit(-200)

View File

@@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
### 모듈 임포트 ###
import os
import sys
import json
import time
import requests
import asyncio
import traceback
import websockets
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
### 함수 정의 ###
# AES256 DECODE
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))
# 웹소켓 접속키 발급
def get_approval(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"secretkey": secret}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
# 상품선물호가 출력라이브러리
def stockhoka_productfuts(data):
# print(data)
recvvalue = data.split('^') # 수신데이터를 split '^'
print("상품선물 ["+recvvalue[ 0]+"]")
print("영업시간 ["+recvvalue[ 1]+"]")
print("====================================")
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
print("====================================")
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
# 상품선물체결처리 출력라이브러리
def stockspurchase_productfuts(data_cnt, data):
print("============================================")
# print(data)
menulist = "선물단축종목코드|영업시간|선물전일대비|전일대비부호|선물전일대비율|선물현재가|선물시가|선물최고가|선물최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드|미결제약정수량|미결제약정수량증감|시가시간|시가대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|선물매도호가|선물매수호가|매도호가잔량|매수호가잔량|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|협의대량거래량|실시간상한가|실시간하한가|실시간가격제한구분"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 선물옵션 체결통보 출력라이브러리
def stocksigningnotice_futsoptn(data, key, iv):
# AES256 처리 단계
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
print(aes_dec_str)
pValue = aes_dec_str.split('^')
print(pValue)
if pValue[6] == '0': # 체결통보
print("#### 지수선물옵션 체결 통보 ####")
menulist_sign = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|체결수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_sign.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
else: # pValue[6] == 'L', 주문·정정·취소·거부 접수 통보
if pValue[5] == '1': # 정정 접수 통보 (정정구분이 1일 경우)
print("#### 지수선물옵션 정정 접수 통보 ####")
menulist_revise = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|정정수량|정정단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_revise.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
elif pValue[5] == '2': # 취소 접수 통보 (정정구분이 2일 경우)
print("#### 지수선물옵션 취소 접수 통보 ####")
menulist_cancel = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|취소수량|주문단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_cancel.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
elif pValue[11] == '1': # 거부 접수 통보 (거부여부가 1일 경우)
print("#### 지수선물옵션 거부 접수 통보 ####")
menulist_refuse = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|주문수량|주문단가|주문시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_refuse.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
else: # 주문 접수 통보
print("#### 지수선물옵션 주문접수 통보 ####")
menulist_order = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|주문수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_order.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
### 앱키 정의 ###
async def connect():
try:
g_appkey = "앱키를 입력하세요"
g_appsecret = "앱 시크릿키를 입력하세요"
g_approval_key = get_approval(g_appkey, g_appsecret)
print("approval_key [%s]" % (g_approval_key))
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기
code_list = [['1','H0CFASP0','175V08'],['1','H0CFCNT0','175T11'], # 상품선물호가, 체결가
['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
senddata_list=[]
for i,j,k in code_list:
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
senddata_list.append(temp)
async with websockets.connect(url, ping_interval=None) as websocket:
for senddata in senddata_list:
await websocket.send(senddata)
await asyncio.sleep(0.5)
print(f"Input Command is :{senddata}")
while True:
data = await websocket.recv()
# await asyncio.sleep(0.5)
# print(f"Recev Command is :{data}") # 정제되지 않은 Request / Response 출력
if data[0] == '0':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0CFASP0": # 상품선물호가 tr 일경우의 처리 단계
print("#### 상품선물호가 ####")
stockhoka_productfuts(recvstr[3])
elif trid0 == "H0CFCNT0": # 상품선물체결 데이터 처리
print("#### 상품선물체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_productfuts(data_cnt, recvstr[3])
elif data[0] == '1':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0IFCNI0": # 선물옵션체결 통보 처리
print("#### 선물옵션 통보 처리 ####")
stocksigningnotice_futsoptn(recvstr[3], aes_key, aes_iv)
else:
jsonObject = json.loads(data)
trid = jsonObject["header"]["tr_id"]
if trid != "PINGPONG":
rt_cd = jsonObject["body"]["rt_cd"]
if rt_cd == '1': # 에러일 경우 처리
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
break
elif rt_cd == '0': # 정상일 경우 처리
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
if trid == "H0IFCNI0":
aes_key = jsonObject["body"]["output"]["key"]
aes_iv = jsonObject["body"]["output"]["iv"]
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
elif trid == "PINGPONG":
print("### RECV [PINGPONG] [%s]" % (data))
await websocket.pong(data)
print("### SEND [PINGPONG] [%s]" % (data))
# ----------------------------------------
# 모든 함수의 공통 부분(Exception 처리)
# ----------------------------------------
except Exception as e:
print('Exception Raised!')
print(e)
print('Connect Again!')
time.sleep(0.1)
# 웹소켓 다시 시작
await connect()
# # 비동기로 서버에 접속한다.
# asyncio.get_event_loop().run_until_complete(connect())
# asyncio.get_event_loop().close()
# -----------------------------------------------------------------------------
# - Name : main
# - Desc : 메인
# -----------------------------------------------------------------------------
async def main():
try:
# 웹소켓 시작
await connect()
except Exception as e:
print('Exception Raised!')
print(e)
if __name__ == "__main__":
# noinspection PyBroadException
try:
# ---------------------------------------------------------------------
# Logic Start!
# ---------------------------------------------------------------------
# 웹소켓 시작
asyncio.run(main())
except KeyboardInterrupt:
print("KeyboardInterrupt Exception 발생!")
print(traceback.format_exc())
sys.exit(-100)
except Exception:
print("Exception 발생!")
print(traceback.format_exc())
sys.exit(-200)

View File

@@ -0,0 +1,397 @@
# -*- coding: utf-8 -*-
### 모듈 임포트 ###
import os
import sys
import json
import time
import requests
import asyncio
import traceback
import websockets
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
### 함수 정의 ###
# AES256 DECODE
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))
# 웹소켓 접속키 발급
def get_approval(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"secretkey": secret}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
# [필수] 유료 시세 수신을 위한 access_token 발급 함수
# 해외주식/해외선물 유료 시세 수신 전 반드시 이 함수를 호출해 access_token을 발급받아야 함
#
# === 해외 유료 시세 수신 안내 ===
# ▒ 해외주식 (HDFSASP0, HDFSASP1, HDFSCNT0: 미국, 중국, 일본, 베트남, 홍콩)
# - 무료 시세: 별도 신청 없이 수신 가능
# - 유료 시세: HTS 또는 MTS에서 신청 후 access_token 발급 필요
# > HTS(eFriend Plus/Force): [7781] 시세신청(실시간)
# > MTS(한국투자 앱): 고객지원 > 거래서비스 신청 > 해외증권 > 해외 실시간 시세 신청
#
# ▒ 해외선물 (HDFFF020, HDFFF010: CME, SGX / 기타 거래소는 무료 시세 제공)
# - CME, SGX: 무료 시세 없음 → 유료 시세 신청 필수
# - 유료 시세: HTS에서 신청 후 access_token 발급 필요
# > HTS(eFriend Plus/Force): [7936] 해외선물옵션 실시간 시세신청/조회
#
# ▒ 유료 시세 수신 절차
# 1. HTS 또는 MTS에서 유료 시세 신청
# 2. get_access_token()으로 access_token 발급 (※ 신청 후에 발급해야 유효)
# 3. 토큰 발급 시점 기준 최대 2시간 이내에 유료 권한 자동 반영
# 4. 이후 웹소켓 연결 → 유료 시세 수신 가능
def get_access_token(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"appsecret": secret}
PATH = "oauth2/tokenP"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
access_token = res.json()["access_token"]
return access_token
### 1. 국내주식 ###
# 국내주식호가 출력라이브러리
def stockhoka_domestic(data):
""" 넘겨받는데이터가 정상인지 확인
print("stockhoka[%s]"%(data))
"""
recvvalue = data.split('^') # 수신데이터를 split '^'
print("유가증권 단축 종목코드 [" + recvvalue[0] + "]")
print("영업시간 [" + recvvalue[1] + "]" + "시간구분코드 [" + recvvalue[2] + "]")
print("======================================")
print("매도호가10 [%s] 잔량10 [%s]" % (recvvalue[12], recvvalue[32]))
print("매도호가09 [%s] 잔량09 [%s]" % (recvvalue[11], recvvalue[31]))
print("매도호가08 [%s] 잔량08 [%s]" % (recvvalue[10], recvvalue[30]))
print("매도호가07 [%s] 잔량07 [%s]" % (recvvalue[9], recvvalue[29]))
print("매도호가06 [%s] 잔량06 [%s]" % (recvvalue[8], recvvalue[28]))
print("매도호가05 [%s] 잔량05 [%s]" % (recvvalue[7], recvvalue[27]))
print("매도호가04 [%s] 잔량04 [%s]" % (recvvalue[6], recvvalue[26]))
print("매도호가03 [%s] 잔량03 [%s]" % (recvvalue[5], recvvalue[25]))
print("매도호가02 [%s] 잔량02 [%s]" % (recvvalue[4], recvvalue[24]))
print("매도호가01 [%s] 잔량01 [%s]" % (recvvalue[3], recvvalue[23]))
print("--------------------------------------")
print("매수호가01 [%s] 잔량01 [%s]" % (recvvalue[13], recvvalue[33]))
print("매수호가02 [%s] 잔량02 [%s]" % (recvvalue[14], recvvalue[34]))
print("매수호가03 [%s] 잔량03 [%s]" % (recvvalue[15], recvvalue[35]))
print("매수호가04 [%s] 잔량04 [%s]" % (recvvalue[16], recvvalue[36]))
print("매수호가05 [%s] 잔량05 [%s]" % (recvvalue[17], recvvalue[37]))
print("매수호가06 [%s] 잔량06 [%s]" % (recvvalue[18], recvvalue[38]))
print("매수호가07 [%s] 잔량07 [%s]" % (recvvalue[19], recvvalue[39]))
print("매수호가08 [%s] 잔량08 [%s]" % (recvvalue[20], recvvalue[40]))
print("매수호가09 [%s] 잔량09 [%s]" % (recvvalue[21], recvvalue[41]))
print("매수호가10 [%s] 잔량10 [%s]" % (recvvalue[22], recvvalue[42]))
print("======================================")
print("총매도호가 잔량 [%s]" % (recvvalue[43]))
print("총매도호가 잔량 증감 [%s]" % (recvvalue[54]))
print("총매수호가 잔량 [%s]" % (recvvalue[44]))
print("총매수호가 잔량 증감 [%s]" % (recvvalue[55]))
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[45]))
print("시간외 총매수호가 증감 [%s]" % (recvvalue[46]))
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[56]))
print("시간외 총매수호가 증감 [%s]" % (recvvalue[57]))
print("예상 체결가 [%s]" % (recvvalue[47]))
print("예상 체결량 [%s]" % (recvvalue[48]))
print("예상 거래량 [%s]" % (recvvalue[49]))
print("예상체결 대비 [%s]" % (recvvalue[50]))
print("부호 [%s]" % (recvvalue[51]))
print("예상체결 전일대비율 [%s]" % (recvvalue[52]))
print("누적거래량 [%s]" % (recvvalue[53]))
print("주식매매 구분코드 [%s]" % (recvvalue[58]))
# 국내주식체결처리 출력라이브러리
def stockspurchase_domestic(data_cnt, data):
print("============================================")
menulist = "유가증권단축종목코드|주식체결시간|주식현재가|전일대비부호|전일대비|전일대비율|가중평균주식가격|주식시가|주식최고가|주식최저가|매도호가1|매수호가1|체결거래량|누적거래량|누적거래대금|매도체결건수|매수체결건수|순매수체결건수|체결강도|총매도수량|총매수수량|체결구분|매수비율|전일거래량대비등락율|시가시간|시가대비구분|시가대비|최고가시간|고가대비구분|고가대비|최저가시간|저가대비구분|저가대비|영업일자|신장운영구분코드|거래정지여부|매도호가잔량|매수호가잔량|총매도호가잔량|총매수호가잔량|거래량회전율|전일동시간누적거래량|전일동시간누적거래량비율|시간구분코드|임의종료구분코드|정적VI발동기준가"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 국내주식체결통보 출력라이브러리
def stocksigningnotice_domestic(data, key, iv):
# AES256 처리 단계
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
pValue = aes_dec_str.split('^')
if pValue[13] == '2': # 체결통보
print("#### 국내주식 체결 통보 ####")
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|체결수량|체결단가|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|주문가격"
menustr1 = menulist.split('|')
else:
print("#### 국내주식 주문·정정·취소·거부 접수 통보 ####")
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|주문수량|주문가격|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|체결단가"
menustr1 = menulist.split('|')
i = 0
for menu in menustr1:
print("%s [%s]" % (menu, pValue[i]))
i += 1
### 2. 해외주식 ###
# 해외주식호가 출력라이브러리
def stockhoka_overseas(data):
""" 넘겨받는데이터가 정상인지 확인
print("stockhoka[%s]"%(data))
"""
recvvalue = data.split('^') # 수신데이터를 split '^'
print("실시간종목코드 [" + recvvalue[0] + "]" + ", 종목코드 [" + recvvalue[1] + "]")
print("소숫점자리수 [" + recvvalue[2] + "]")
print("현지일자 [" + recvvalue[3] + "]" + ", 현지시간 [" + recvvalue[4] + "]")
print("한국일자 [" + recvvalue[5] + "]" + ", 한국시간 [" + recvvalue[6] + "]")
print("======================================")
print("매수총 잔량 [%s]" % (recvvalue[7]))
print("매수총잔량대비 [%s]" % (recvvalue[9]))
print("매도총 잔량 [%s]" % (recvvalue[8]))
print("매도총잔략대비 [%s]" % (recvvalue[10]))
print("매수호가 [%s]" % (recvvalue[11]))
print("매도호가 [%s]" % (recvvalue[12]))
print("매수잔량 [%s]" % (recvvalue[13]))
print("매도잔량 [%s]" % (recvvalue[14]))
print("매수잔량대비 [%s]" % (recvvalue[15]))
print("매도잔량대비 [%s]" % (recvvalue[16]))
# 해외주식체결처리 출력라이브러리
def stockspurchase_overseas(data_cnt, data):
print("============================================")
menulist = "실시간종목코드|종목코드|수수점자리수|현지영업일자|현지일자|현지시간|한국일자|한국시간|시가|고가|저가|현재가|대비구분|전일대비|등락율|매수호가|매도호가|매수잔량|매도잔량|체결량|거래량|거래대금|매도체결량|매수체결량|체결강도|시장구분"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 해외주식체결통보 출력라이브러리
def stocksigningnotice_overseas(data, key, iv):
# AES256 처리 단계
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
pValue = aes_dec_str.split('^')
if pValue[12] == '2': # 체결통보
print("#### 해외주식 체결 통보 ####")
menulist = "고객 ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류2|단축종목코드|체결수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|체결종목명|해외종목구분|담보유형코드|담보대출일자|분할매수매도시작시간|분할매수매도종료시간|시간분할타입유형|체결단가12"
menustr1 = menulist.split('|')
else:
print("#### 해외주식 주문·정정·취소·거부 접수 통보 ####")
menulist = "고객 ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류2|단축종목코드|주문수량|주문단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량_미출력|계좌명|체결종목명|해외종목구분|담보유형코드|담보대출일자|분할매수매도시작시간|분할매수매도종료시간|시간분할타입유형|체결단가12"
menustr1 = menulist.split('|')
i = 0
for menu in menustr1:
print("%s [%s]" % (menu, pValue[i]))
i += 1
### 웹소켓 연결 ###
async def connect():
try:
g_appkey = "앱키를 입력하세요"
g_appsecret = "앱 시크릿키를 입력하세요"
# 해외주식/해외선물(CME, SGX) 유료시세 사용 시 필수(2시간 이내 유료신청정보 동기화)
# access_token = get_access_token(appkey, appsecret)
g_approval_key = get_approval(g_appkey, g_appsecret)
print("approval_key [%s]" % (g_approval_key))
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기 # 모의투자 국내주식 체결통보: H0STCNI9
code_list = [['1','H0STASP0','005930'],['1','H0STCNT0','005930'],['1','H0STCNI0','HTS ID를 입력하세요'],
['1','H0STASP0','DNASAAPL'],['1','HDFSCNT0','DNASAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요']]
code_list = [['1','H0STASP0','005930'],['1','H0STASP0','RBAQAAPL']]
code_list = [['1','H0STASP0','005930'],['1','H0STCNT0','005930'],['1','H0STCNI0','HTS ID를 입력하세요'],
['1','H0STASP0','DNASAAPL'],['1','HDFSCNT0','DNASAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요']]
senddata_list=[]
for i,j,k in code_list:
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
senddata_list.append(temp)
async with websockets.connect(url, ping_interval=None) as websocket:
for senddata in senddata_list:
await websocket.send(senddata)
await asyncio.sleep(0.5)
print(f"Input Command is :{senddata}")
while True:
data = await websocket.recv()
# await asyncio.sleep(0.5)
# print(f"Recev Command is :{data}")
if data[0] == '0':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0STASP0": # 주식호가tr 일경우의 처리 단계
print("#### 주식호가 ####")
stockhoka_domestic(recvstr[3])
await asyncio.sleep(0.5)
elif trid0 == "H0STCNT0": # 주식체결 데이터 처리
print("#### 주식체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_domestic(data_cnt, recvstr[3])
await asyncio.sleep(0.5)
elif trid0 == "HDFSASP1": # 해외주식호가tr 일경우의 처리 단계
print("#### 해외주식호가 ####")
stockhoka_overseas(recvstr[3])
await asyncio.sleep(0.5)
elif trid0 == "HDFSCNT0": # 주식체결 데이터 처리
print("#### 해외주식체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_overseas(data_cnt, recvstr[3])
await asyncio.sleep(0.5)
elif data[0] == '1':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0STCNI0" or trid0 == "H0STCNI9": # 주실체결 통보 처리
stocksigningnotice_domestic(recvstr[3], aes_key, aes_iv)
elif trid0 == "H0GSCNI0" or trid0 == "H0GSCNI9": # 해외주실체결 통보 처리
stocksigningnotice_overseas(recvstr[3], aes_key, aes_iv)
else:
jsonObject = json.loads(data)
trid = jsonObject["header"]["tr_id"]
if trid != "PINGPONG":
rt_cd = jsonObject["body"]["rt_cd"]
if rt_cd == '1': # 에러일 경우 처리
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
break
elif rt_cd == '0': # 정상일 경우 처리
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
if trid == "H0STCNI0" or trid == "H0STCNI9":
aes_key = jsonObject["body"]["output"]["key"]
aes_iv = jsonObject["body"]["output"]["iv"]
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
elif trid == "H0GSCNI0" or trid == "H0GSCNI9":
aes_key = jsonObject["body"]["output"]["key"]
aes_iv = jsonObject["body"]["output"]["iv"]
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
elif trid == "PINGPONG":
print("### RECV [PINGPONG] [%s]" % (data))
await websocket.pong(data)
print("### SEND [PINGPONG] [%s]" % (data))
# ----------------------------------------
# 모든 함수의 공통 부분(Exception 처리)
# ----------------------------------------
except Exception as e:
print('Exception Raised!')
print(e)
print('Connect Again!')
time.sleep(0.1)
# 웹소켓 다시 시작
await connect()
# # 비동기로 서버에 접속한다.
# asyncio.get_event_loop().run_until_complete(connect())
# asyncio.get_event_loop().close()
# -----------------------------------------------------------------------------
# - Name : main
# - Desc : 메인
# -----------------------------------------------------------------------------
async def main():
try:
# 웹소켓 시작
await connect()
except Exception as e:
print('Exception Raised!')
print(e)
if __name__ == "__main__":
# noinspection PyBroadException
try:
# ---------------------------------------------------------------------
# Logic Start!
# ---------------------------------------------------------------------
# 웹소켓 시작
asyncio.run(main())
except KeyboardInterrupt:
print("KeyboardInterrupt Exception 발생!")
print(traceback.format_exc())
sys.exit(-100)
except Exception:
print("Exception 발생!")
print(traceback.format_exc())
sys.exit(-200)

View File

@@ -0,0 +1,633 @@
# -*- coding: utf-8 -*-
### 모듈 임포트 ###
import os
import sys
import json
import time
import requests
import asyncio
import traceback
import websockets
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
### 함수 정의 ###
# AES256 DECODE
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))
# 웹소켓 접속키 발급
def get_approval(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"secretkey": secret}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
# 지수선물호가 출력라이브러리
def stockhoka_futs(data):
# print(data)
recvvalue = data.split('^') # 수신데이터를 split '^'
print("지수선물 ["+recvvalue[ 0]+"]")
print("영업시간 ["+recvvalue[ 1]+"]")
print("====================================")
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
print("====================================")
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
# 지수옵션호가 출력라이브러리
def stockhoka_optn(data):
# print(data)
recvvalue = data.split('^') # 수신데이터를 split '^'
print("지수옵션 ["+recvvalue[ 0]+"]")
print("영업시간 ["+recvvalue[ 1]+"]")
print("====================================")
print("옵션매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
print("옵션매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
print("옵션매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
print("옵션매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
print("옵션매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
print("옵션매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
print("옵션매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
print("옵션매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
print("옵션매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
print("옵션매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
print("====================================")
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
# 지수선물체결처리 출력라이브러리
def stockspurchase_futs(data_cnt, data):
print("============================================")
# print(data)
menulist = "선물단축종목코드|영업시간|선물전일대비|전일대비부호|선물전일대비율|선물현재가|선물시가|선물최고가|선물최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드|미결제약정수량|미결제약정수량증감|시가시간|시가대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|선물매도호가|선물매수호가|매도호가잔량|매수호가잔량|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|협의대량거래량|실시간상한가|실시간하한가|실시간가격제한구분"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 지수옵션체결처리 출력라이브러리
def stockspurchase_optn(data_cnt, data):
print("============================================")
# print(data)
menulist = "옵션단축종목코드|영업시간|옵션현재가|전일대비부호|옵션전일대비|전일대비율|옵션시가|옵션최고가|옵션최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|HTS미결제약정수량|미결제약정수량증감|시가시간|시가대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수2비율|프리미엄값|내재가치값|시간가치값|델타|감마|베가|세타|로우|HTS내재변동성|괴리도|미결제약정직전수량증감|이론베이시스|역사적변동성|체결강도|괴리율|시장베이시스|옵션매도호가1|옵션매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|평균변동성|협의대량누적거래량|실시간상한가|실시간하한가|실시간가격제한구분"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 상품선물호가 출력라이브러리
def stockhoka_productfuts(data):
# print(data)
recvvalue = data.split('^') # 수신데이터를 split '^'
print("상품선물 ["+recvvalue[ 0]+"]")
print("영업시간 ["+recvvalue[ 1]+"]")
print("====================================")
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
print("====================================")
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
# 상품선물체결처리 출력라이브러리
def stockspurchase_productfuts(data_cnt, data):
print("============================================")
# print(data)
menulist = "선물단축종목코드|영업시간|선물전일대비|전일대비부호|선물전일대비율|선물현재가|선물시가|선물최고가|선물최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드|미결제약정수량|미결제약정수량증감|시가시간|시가대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|선물매도호가|선물매수호가|매도호가잔량|매수호가잔량|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|협의대량거래량|실시간상한가|실시간하한가|실시간가격제한구분"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 주식선물호가 출력라이브러리
def stockhoka_stockfuts(data):
# print(data)
recvvalue = data.split('^') # 수신데이터를 split '^'
print("주식선물 ["+recvvalue[ 0]+"]")
print("영업시간 ["+recvvalue[ 1]+"]")
print("====================================")
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
print("====================================")
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
# 주식선물체결처리 출력라이브러리
def stockspurchase_stockfuts(data_cnt, data):
print("============================================")
print(data)
menulist = "선물단축종목코드|영업시간|주식현재가|전일대비부호|전일대비|선물전일대비율|주식시가2|주식최고가|주식최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드1|HTS미결제약정수량|미결제약정수량증감|시가시간|시가2대비현재가부호|시가2대비현재가|최고가시간|최고가대비현재가부호|최고가대비현재가|최저가시간|최저가대비현재가부호|최저가대비현재가|매수2비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|매도호가1|매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율|실시간상한가|실시간하한가|실시간가격제한구분"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 주식옵션호가 출력라이브러리
def stockhoka_stockoptn(data):
# print(data)
recvvalue = data.split('^') # 수신데이터를 split '^'
print("주식옵션 ["+recvvalue[ 0]+"]")
print("영업시간 ["+recvvalue[ 1]+"]")
print("====================================")
print("옵션매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
print("옵션매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
print("옵션매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
print("옵션매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
print("옵션매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
print("옵션매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
print("옵션매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
print("옵션매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
print("옵션매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
print("옵션매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
print("====================================")
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
# 주식옵션체결처리 출력라이브러리
def stockspurchase_stockoptn(data_cnt, data):
print("============================================")
# print(data)
menulist = "옵션단축종목코드|영업시간|옵션현재가|전일대비부호|옵션전일대비|전일대비율|옵션시가2|옵션최고가|옵션최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|HTS미결제약정수량|미결제약정수량증감|시가시간|시가2대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수2비율|프리미엄값|내재가치값|시간가치값|델타|감마|베가|세타|로우|HTS내재변동성|괴리도|미결제약정직전수량증감|이론베이시스|역사적변동성|체결강도|괴리율|시장베이시스|옵션매도호가1|옵션매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 야간선물(CME)체결처리 출력라이브러리
def stockspurchase_cmefuts(data_cnt, data):
print("============================================")
print(data)
menulist = "선물단축종목코드|영업시간|선물전일대비|전일대비부호|선물전일대비율|선물현재가|선물시가2|선물최고가|선물최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|시장베이시스|괴리율|근월물약정가|원월물약정가|스프레드1|HTS미결제약정수량|미결제약정수량증감|시가시간|시가2대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수2비율|체결강도|괴리도|미결제약정직전수량증감|이론베이시스|선물매도호가1|선물매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 야간선물(CME)호가 출력라이브러리
def stockhoka_cmefuts(data):
# print(data)
recvvalue = data.split('^') # 수신데이터를 split '^'
print("야간선물(CME) ["+recvvalue[ 0]+"]")
print("영업시간 ["+recvvalue[ 1]+"]")
print("====================================")
print("선물매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
print("선물매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
print("선물매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
print("선물매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
print("선물매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
print("선물매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
print("선물매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
print("선물매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
print("선물매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
print("선물매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
print("====================================")
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
# 야간옵션(EUREX)체결처리 출력라이브러리
def stockspurchase_eurexoptn(data_cnt, data):
print("============================================")
print(data)
menulist = "옵션단축종목코드|영업시간|옵션현재가|전일대비부호|옵션전일대비|전일대비율|옵션시가2|옵션최고가|옵션최저가|최종거래량|누적거래량|누적거래대금|HTS이론가|HTS미결제약정수량|미결제약정수량증감|시가시간|시가2대비현재가부호|시가대비지수현재가|최고가시간|최고가대비현재가부호|최고가대비지수현재가|최저가시간|최저가대비현재가부호|최저가대비지수현재가|매수2비율|프리미엄값|내재가치값|시간가치값|델타|감마|베가|세타|로우|HTS내재변동성|괴리도|미결제약정직전수량증감|이론베이시스|역사적변동성|체결강도|괴리율|시장베이시스|옵션매도호가1|옵션매수호가1|매도호가잔량1|매수호가잔량1|매도체결건수|매수체결건수|순매수체결건수|총매도수량|총매수수량|총매도호가잔량|총매수호가잔량|전일거래량대비등락율"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 야간옵션(EUREX)호가 출력라이브러리
def stockhoka_eurexoptn(data):
# print(data)
recvvalue = data.split('^') # 수신데이터를 split '^'
print("야간옵션(EUREX) ["+recvvalue[ 0]+"]")
print("영업시간 ["+recvvalue[ 1]+"]")
print("====================================")
print("옵션매도호가1 ["+recvvalue[ 2]+"]"+", 매도호가건수1 ["+recvvalue[12]+"]"+", 매도호가잔량1 ["+recvvalue[22]+"]")
print("옵션매도호가2 ["+recvvalue[ 3]+"]"+", 매도호가건수2 ["+recvvalue[13]+"]"+", 매도호가잔량2 ["+recvvalue[23]+"]")
print("옵션매도호가3 ["+recvvalue[ 4]+"]"+", 매도호가건수3 ["+recvvalue[14]+"]"+", 매도호가잔량3 ["+recvvalue[24]+"]")
print("옵션매도호가4 ["+recvvalue[ 5]+"]"+", 매도호가건수4 ["+recvvalue[15]+"]"+", 매도호가잔량4 ["+recvvalue[25]+"]")
print("옵션매도호가5 ["+recvvalue[ 6]+"]"+", 매도호가건수5 ["+recvvalue[16]+"]"+", 매도호가잔량5 ["+recvvalue[26]+"]")
print("옵션매수호가1 ["+recvvalue[ 7]+"]"+", 매수호가건수1 ["+recvvalue[17]+"]"+", 매수호가잔량1 ["+recvvalue[27]+"]")
print("옵션매수호가2 ["+recvvalue[ 8]+"]"+", 매수호가건수2 ["+recvvalue[18]+"]"+", 매수호가잔량2 ["+recvvalue[28]+"]")
print("옵션매수호가3 ["+recvvalue[ 9]+"]"+", 매수호가건수3 ["+recvvalue[19]+"]"+", 매수호가잔량3 ["+recvvalue[29]+"]")
print("옵션매수호가4 ["+recvvalue[10 ]+"]"+", 매수호가건수4 ["+recvvalue[20]+"]"+", 매수호가잔량4 ["+recvvalue[30]+"]")
print("옵션매수호가5 ["+recvvalue[11]+"]"+", 매수호가건수5 ["+recvvalue[21]+"]"+", 매수호가잔량5 ["+recvvalue[31]+"]")
print("====================================")
print("총매도호가건수 ["+recvvalue[32]+"]"+", 총매도호가잔량 ["+recvvalue[34]+"]"+", 총매도호가잔량증감 ["+recvvalue[36]+"]")
print("총매수호가건수 ["+recvvalue[33]+"]"+", 총매수호가잔량 ["+recvvalue[35]+"]"+", 총매수호가잔량증감 ["+recvvalue[37]+"]")
# 야간옵션(EUREX)예상체결처리 출력라이브러리
def stocksexppurchase_eurexoptn(data_cnt, data):
print("============================================")
print(data)
menulist = "옵션단축종목코드|영업시간|예상체결가|예상체결대비|예상체결대비부호|예상체결전일대비율|예상장운영구분코드"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 선물옵션 체결통보 출력라이브러리
def stocksigningnotice_futsoptn(data, key, iv):
# AES256 처리 단계
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
print(aes_dec_str)
pValue = aes_dec_str.split('^')
print(pValue)
if pValue[6] == '0': # 체결통보
print("#### 지수선물옵션 체결 통보 ####")
menulist_sign = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|체결수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_sign.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
else: # pValue[6] == 'L', 주문·정정·취소·거부 접수 통보
if pValue[5] == '1': # 정정 접수 통보 (정정구분이 1일 경우)
print("#### 선물옵션 정정 접수 통보 ####")
menulist_revise = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|정정수량|정정단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_revise.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
elif pValue[5] == '2': # 취소 접수 통보 (정정구분이 2일 경우)
print("#### 선물옵션 취소 접수 통보 ####")
menulist_cancel = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|취소수량|주문단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_cancel.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
elif pValue[11] == '1': # 거부 접수 통보 (거부여부가 1일 경우)
print("#### 선물옵션 거부 접수 통보 ####")
menulist_refuse = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|주문수량|주문단가|주문시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_refuse.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
else: # 주문 접수 통보
print("#### 선물옵션 주문접수 통보 ####")
menulist_order = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|단축종목코드|주문수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|체결수량|계좌명|체결종목명|주문조건|주문그룹ID|주문그룹SEQ|주문가격"
menustr = menulist_order.split('|')
i = 0
for menu in menustr:
print("%s [%s]" % (menu, pValue[i]))
i += 1
### 앱키 정의 ###
async def connect():
try:
g_appkey = "앱키를 입력하세요"
g_appsecret = "앱 시크릿키를 입력하세요"
g_approval_key = get_approval(g_appkey, g_appsecret)
print("approval_key [%s]" % (g_approval_key))
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
### 3-1. 국내 지수선물옵션 호가, 체결가, 체결통보 ### # 모의투자 선물옵션 체결통보: H0IFCNI9
# code_list = [['1','H0IFASP0','101T12'],['1','H0IFCNT0','101T12'], # 지수선물호가, 체결가
# ['1','H0IOASP0','201T11317'],['1','H0IOCNT0','201T11317'], # 지수옵션호가, 체결가
# ['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
### 3-2. 국내 상품선물 호가, 체결가, 체결통보 ###
# code_list = [['1','H0CFASP0','175T11'],['1','H0CFCNT0','175T11'], # 상품선물호가, 체결가
# ['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
### 3-3. 국내 주식선물옵션 호가, 체결가, 체결통보 ###
# code_list = [['1', 'H0ZFCNT0', '111V06'], ['1', 'H0ZFASP0', '111V06'],['1', 'H0ZFANC0', '111V06'], # 주식선물호가, 체결가, 예상체결
# ['1', 'H0ZOCNT0', '211V05059'], ['1', 'H0ZOASP0', '211V05059'], ['1', 'H0ZOANC0', '211V05059'], # 주식옵션호가, 체결가, 예상체결
# ['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
### 3-4. 국내 야간옵션(EUREX) 호가, 체결가, 예상체결, 체결통보 ###
# code_list = [['1', 'H0EUCNT0', '101V06'], ['1', 'H0EUASP0', '101V06'], ['1', 'H0EUANC0', '101V06'], ['1', 'H0EUCNI0', 'HTS ID를 입력하세요']]
### 3-5. 국내 야간선물(CME) 호가, 체결가, 체결통보 ###
# code_list = [['1', 'H0MFCNT0', '101V06'], ['1', 'H0MFASP0', '101V06'], ['1', 'H0MFCNI0', 'HTS ID를 입력하세요']]
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기
code_list = [['1','H0IFASP0','101T12'],['1','H0IFCNT0','101T12'], # 지수선물호가, 체결가
['1','H0IOASP0','201T11317'],['1','H0IOCNT0','201T11317'], # 지수옵션호가, 체결가
['1','H0CFASP0','175V06'],['1','H0CFCNT0','175V06'], # 상품선물호가, 체결가
['1', 'H0ZFCNT0', '111V06'], ['1', 'H0ZFASP0', '111V06'], # 주식선물 체결, 호가
['1', 'H0ZOCNT0', '211V05059'], ['1', 'H0ZOASP0', '211V05059'], # 주식옵션 체결, 호가
['1','H0IFCNI0','HTS ID를 입력하세요']] # 선물옵션체결통보
senddata_list=[]
for i,j,k in code_list:
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
senddata_list.append(temp)
async with websockets.connect(url, ping_interval=None) as websocket:
for senddata in senddata_list:
await websocket.send(senddata)
await asyncio.sleep(0.5)
print(f"Input Command is :{senddata}")
while True:
data = await websocket.recv()
# await asyncio.sleep(0.5)
# print(f"Recev Command is :{data}") # 정제되지 않은 Request / Response 출력
if data[0] == '0':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0IFASP0": # 지수선물호가 tr 일경우의 처리 단계
print("#### 지수선물 호가 ####")
stockhoka_futs(recvstr[3])
# await asyncio.sleep(0.5)
elif trid0 == "H0IFCNT0": # 지수선물체결 데이터 처리
print("#### 지수선물 체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_futs(data_cnt, recvstr[3])
# await asyncio.sleep(0.5)
elif trid0 == "H0IOASP0": # 지수옵션호가 tr 일경우의 처리 단계
print("#### 지수옵션 호가 ####")
stockhoka_optn(recvstr[3])
# await asyncio.sleep(0.5)
elif trid0 == "H0IOCNT0": # 지수옵션체결 데이터 처리
print("#### 지수옵션 체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_optn(data_cnt, recvstr[3])
# await asyncio.sleep(0.5)
elif trid0 == "H0CFASP0": # 상품선물호가 tr 일경우의 처리 단계
print("#### 상품선물 호가 ####")
stockhoka_productfuts(recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0CFCNT0": # 상품선물체결 데이터 처리
print("#### 상품선물 체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_productfuts(data_cnt, recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0ZFCNT0": # 주식선물 체결 데이터 처리
print("#### 주식선물 체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_stockfuts(data_cnt, recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0ZFASP0": # 주식선물 호가 데이터 처리
print("#### 주식선물 호가 ####")
stockhoka_stockfuts(recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0ZFANC0": # 주식선물 예상체결 데이터 처리
print("#### 주식선물 예상체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stocksexppurchase_stockfuts(data_cnt, recvstr[3])
elif trid0 == "H0ZOCNT0": # 주식옵션 체결 데이터 처리
print("#### 주식옵션 체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_stockoptn(data_cnt, recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0ZOASP0": # 주식옵션 호가 데이터 처리
print("#### 주식옵션 호가 ####")
stockhoka_stockoptn(recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0ZOANC0": # 주식옵션 예상체결 데이터 처리
print("#### 주식옵션 예상체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stocksexppurchase_stockoptn(data_cnt, recvstr[3])
elif trid0 == "H0MFCNT0": # 야간선물(CME) 체결 데이터 처리
print("#### 야간선물(CME) 체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_cmefuts(data_cnt, recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0MFASP0": # 야간선물(CME) 호가 데이터 처리
print("#### 야간선물(CME) 호가 ####")
stockhoka_cmefuts(recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0EUCNT0": # 야간옵션(EUREX) 체결 데이터 처리
print("#### 야간옵션(EUREX) 체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_eurexoptn(data_cnt, recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0EUASP0": # 야간옵션(EUREX) 호가 데이터 처리
print("#### 야간옵션(EUREX) 호가 ####")
stockhoka_eurexoptn(recvstr[3])
# await asyncio.sleep(0.2)
elif trid0 == "H0EUANC0": # 야간옵션(EUREX) 예상체결 데이터 처리
print("#### 야간옵션(EUREX) 예상체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stocksexppurchase_eurexoptn(data_cnt, recvstr[3])
elif data[0] == '1':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0IFCNI0": # 지수선물옵션체결 통보 처리
stocksigningnotice_futsoptn(recvstr[3], aes_key, aes_iv)
else:
jsonObject = json.loads(data)
trid = jsonObject["header"]["tr_id"]
if trid != "PINGPONG":
rt_cd = jsonObject["body"]["rt_cd"]
if rt_cd == '1': # 에러일 경우 처리
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
break
elif rt_cd == '0': # 정상일 경우 처리
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
if trid == "H0IFCNI0" or trid == "H0MFCNI0" or trid == "H0EUCNI0": # 지수/상품/주식 선물옵션 & 야간선물옵션
aes_key = jsonObject["body"]["output"]["key"]
aes_iv = jsonObject["body"]["output"]["iv"]
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
elif trid == "PINGPONG":
print("### RECV [PINGPONG] [%s]" % (data))
await websocket.pong(data)
print("### SEND [PINGPONG] [%s]" % (data))
# ----------------------------------------
# 모든 함수의 공통 부분(Exception 처리)
# ----------------------------------------
except Exception as e:
print('Exception Raised!')
print(e)
print('Connect Again!')
time.sleep(0.1)
# 웹소켓 다시 시작
await connect()
# # 비동기로 서버에 접속한다.
# asyncio.get_event_loop().run_until_complete(connect())
# asyncio.get_event_loop().close()
# -----------------------------------------------------------------------------
# - Name : main
# - Desc : 메인
# -----------------------------------------------------------------------------
async def main():
try:
# 웹소켓 시작
await connect()
except Exception as e:
print('Exception Raised!')
print(e)
if __name__ == "__main__":
# noinspection PyBroadException
try:
# ---------------------------------------------------------------------
# Logic Start!
# ---------------------------------------------------------------------
# 웹소켓 시작
asyncio.run(main())
except KeyboardInterrupt:
print("KeyboardInterrupt Exception 발생!")
print(traceback.format_exc())
sys.exit(-100)
except Exception:
print("Exception 발생!")
print(traceback.format_exc())
sys.exit(-200)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,312 @@
# -*- coding: utf-8 -*-
### 모듈 임포트 ###
import os
import sys
import json
import time
import requests
import asyncio
import traceback
import websockets
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
# AES256 DECODE
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))
# 웹소켓 접속키 발급
def get_approval(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"secretkey": secret}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
# 국내주식호가 출력라이브러리
def stockhoka(data):
""" 넘겨받는데이터가 정상인지 확인
print("stockhoka[%s]"%(data))
"""
recvvalue = data.split('^') # 수신데이터를 split '^'
print("유가증권 단축 종목코드 [" + recvvalue[0] + "]")
print("영업시간 [" + recvvalue[1] + "]" + "시간구분코드 [" + recvvalue[2] + "]")
print("======================================")
print("매도호가10 [%s] 잔량10 [%s]" % (recvvalue[12], recvvalue[32]))
print("매도호가09 [%s] 잔량09 [%s]" % (recvvalue[11], recvvalue[31]))
print("매도호가08 [%s] 잔량08 [%s]" % (recvvalue[10], recvvalue[30]))
print("매도호가07 [%s] 잔량07 [%s]" % (recvvalue[9], recvvalue[29]))
print("매도호가06 [%s] 잔량06 [%s]" % (recvvalue[8], recvvalue[28]))
print("매도호가05 [%s] 잔량05 [%s]" % (recvvalue[7], recvvalue[27]))
print("매도호가04 [%s] 잔량04 [%s]" % (recvvalue[6], recvvalue[26]))
print("매도호가03 [%s] 잔량03 [%s]" % (recvvalue[5], recvvalue[25]))
print("매도호가02 [%s] 잔량02 [%s]" % (recvvalue[4], recvvalue[24]))
print("매도호가01 [%s] 잔량01 [%s]" % (recvvalue[3], recvvalue[23]))
print("--------------------------------------")
print("매수호가01 [%s] 잔량01 [%s]" % (recvvalue[13], recvvalue[33]))
print("매수호가02 [%s] 잔량02 [%s]" % (recvvalue[14], recvvalue[34]))
print("매수호가03 [%s] 잔량03 [%s]" % (recvvalue[15], recvvalue[35]))
print("매수호가04 [%s] 잔량04 [%s]" % (recvvalue[16], recvvalue[36]))
print("매수호가05 [%s] 잔량05 [%s]" % (recvvalue[17], recvvalue[37]))
print("매수호가06 [%s] 잔량06 [%s]" % (recvvalue[18], recvvalue[38]))
print("매수호가07 [%s] 잔량07 [%s]" % (recvvalue[19], recvvalue[39]))
print("매수호가08 [%s] 잔량08 [%s]" % (recvvalue[20], recvvalue[40]))
print("매수호가09 [%s] 잔량09 [%s]" % (recvvalue[21], recvvalue[41]))
print("매수호가10 [%s] 잔량10 [%s]" % (recvvalue[22], recvvalue[42]))
print("======================================")
print("총매도호가 잔량 [%s]" % (recvvalue[43]))
print("총매도호가 잔량 증감 [%s]" % (recvvalue[54]))
print("총매수호가 잔량 [%s]" % (recvvalue[44]))
print("총매수호가 잔량 증감 [%s]" % (recvvalue[55]))
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[45]))
print("시간외 총매수호가 증감 [%s]" % (recvvalue[46]))
print("시간외 총매도호가 잔량 [%s]" % (recvvalue[56]))
print("시간외 총매수호가 증감 [%s]" % (recvvalue[57]))
print("예상 체결가 [%s]" % (recvvalue[47]))
print("예상 체결량 [%s]" % (recvvalue[48]))
print("예상 거래량 [%s]" % (recvvalue[49]))
print("예상체결 대비 [%s]" % (recvvalue[50]))
print("부호 [%s]" % (recvvalue[51]))
print("예상체결 전일대비율 [%s]" % (recvvalue[52]))
print("누적거래량 [%s]" % (recvvalue[53]))
print("주식매매 구분코드 [%s]" % (recvvalue[58]))
# 국내주식체결처리 출력라이브러리
def stockspurchase(data_cnt, data):
print("============================================")
menulist = "유가증권단축종목코드|주식체결시간|주식현재가|전일대비부호|전일대비|전일대비율|가중평균주식가격|주식시가|주식최고가|주식최저가|매도호가1|매수호가1|체결거래량|누적거래량|누적거래대금|매도체결건수|매수체결건수|순매수체결건수|체결강도|총매도수량|총매수수량|체결구분|매수비율|전일거래량대비등락율|시가시간|시가대비구분|시가대비|최고가시간|고가대비구분|고가대비|최저가시간|저가대비구분|저가대비|영업일자|신장운영구분코드|거래정지여부|매도호가잔량|매수호가잔량|총매도호가잔량|총매수호가잔량|거래량회전율|전일동시간누적거래량|전일동시간누적거래량비율|시간구분코드|임의종료구분코드|정적VI발동기준가"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 국내주식체결통보 출력라이브러리
def stocksigningnotice_domestic(data, key, iv):
# AES256 처리 단계
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
pValue = aes_dec_str.split('^')
if pValue[13] == '2': # 체결통보
print("#### 국내주식 체결 통보 ####")
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|체결수량|체결단가|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|주문가격"
menustr1 = menulist.split('|')
else:
print("#### 국내주식 주문·정정·취소·거부 접수 통보 ####")
menulist = "고객ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류|주문조건|주식단축종목코드|주문수량|주문가격|주식체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|호가조건가격|주문거래소구분|실시간체결창표시여부|필러|신용구분|신용대출일자|체결종목명40|체결단가"
menustr1 = menulist.split('|')
i = 0
for menu in menustr1:
print("%s [%s]" % (menu, pValue[i]))
i += 1
async def connect():
# 웹 소켓에 접속.( 주석은 koreainvest test server for websocket)
## 시세데이터를 받기위한 데이터를 미리 할당해서 사용한다.
try:
g_appkey = '앱키를 입력하세요'
g_appsecret = '앱 시크릿키를 입력하세요'
stockcode = '005930' # 테스트용 임시 종목 설정, 삼성전자
htsid = 'HTS ID를 입력하세요' # 체결통보용 htsid 입력
custtype = 'P' # customer type, 개인:'P' 법인 'B'
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
g_approval_key = get_approval(g_appkey, g_appsecret)
print("approval_key [%s]" % (g_approval_key))
async with websockets.connect(url, ping_interval=None) as websocket:
"""" 주석처리는 더블쿼트 3개로 처리
"""
print("1.주식호가, 2.주식호가해제, 3.주식체결, 4.주식체결해제, 5.주식체결통보(고객), 6.주식체결통보해제(고객), 7.주식체결통보(모의), 8.주식체결통보해제(모의)")
print("Input Command :")
cmd = input()
# 입력값 체크 step
if cmd < '0' or cmd > '9':
print("> Wrong Input Data", cmd)
elif cmd == '0':
print("Exit!!")
# 입력값에 따라 전송 데이터셋 구분 처리
if cmd == '1': # 주식호가 등록
tr_id = 'H0STASP0'
tr_type = '1'
elif cmd == '2': # 주식호가 등록해제
tr_id = 'H0STASP0'
tr_type = '2'
elif cmd == '3': # 주식체결 등록
tr_id = 'H0STCNT0'
tr_type = '1'
elif cmd == '4': # 주식체결 등록해제
tr_id = 'H0STCNT0'
tr_type = '2'
elif cmd == '5': # 주식체결통보 등록(고객용)
tr_id = 'H0STCNI0' # 고객체결통보
tr_type = '1'
elif cmd == '6': # 주식체결통보 등록해제(고객용)
tr_id = 'H0STCNI0' # 고객체결통보
tr_type = '2'
elif cmd == '7': # 주식체결통보 등록(모의)
tr_id = 'H0STCNI9' # 테스트용 직원체결통보
tr_type = '1'
elif cmd == '8': # 주식체결통보 등록해제(모의)
tr_id = 'H0STCNI9' # 테스트용 직원체결통보
tr_type = '2'
else:
senddata = 'wrong inert data'
# send json, 체결통보는 tr_key 입력항목이 상이하므로 분리를 한다.
if cmd == '5' or cmd == '6' or cmd == '7' or cmd == '8':
senddata = '{"header":{"approval_key":"' + g_approval_key + '","custtype":"' + custtype + '","tr_type":"' + tr_type + '","content-type":"utf-8"},"body":{"input":{"tr_id":"' + tr_id + '","tr_key":"' + htsid + '"}}}'
else:
senddata = '{"header":{"approval_key":"' + g_approval_key + '","custtype":"' + custtype + '","tr_type":"' + tr_type + '","content-type":"utf-8"},"body":{"input":{"tr_id":"' + tr_id + '","tr_key":"' + stockcode + '"}}}'
print('Input Command is :', senddata)
await websocket.send(senddata)
await asyncio.sleep(0.5)
# 데이터가 오기만 기다린다.
while True:
data = await websocket.recv()
# await asyncio.sleep(0.5)
# print("Recev Command is :", data)
if data[0] == '0' or data[0] == '1': # 실시간 데이터일 경우
trid = jsonObject["header"]["tr_id"]
if data[0] == '0':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0STASP0": # 주식호가tr 일경우의 처리 단계
print("#### 주식호가 ####")
stockhoka(recvstr[3])
await asyncio.sleep(0.5)
elif trid0 == "H0STCNT0": # 주식체결 데이터 처리
print("#### 주식체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase(data_cnt, recvstr[3])
await asyncio.sleep(0.5)
elif data[0] == '1':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "K0STCNI0" or trid0 == "K0STCNI9" or trid0 == "H0STCNI0" or trid0 == "H0STCNI9": # 주실체결 통보 처리
stocksigningnotice(recvstr[3], aes_key, aes_iv)
# clearConsole()
# break;
else:
jsonObject = json.loads(data)
trid = jsonObject["header"]["tr_id"]
if trid != "PINGPONG":
rt_cd = jsonObject["body"]["rt_cd"]
if rt_cd == '1': # 에러일 경우 처리
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
#break
elif rt_cd == '0': # 정상일 경우 처리
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
if trid == "H0STCNI0" or trid == "H0STCNI9":
aes_key = jsonObject["body"]["output"]["key"]
aes_iv = jsonObject["body"]["output"]["iv"]
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
elif trid == "PINGPONG":
print("### RECV [PINGPONG] [%s]" % (data))
await websocket.pong(data)
print("### SEND [PINGPONG] [%s]" % (data))
# ----------------------------------------
# 모든 함수의 공통 부분(Exception 처리)
# ----------------------------------------
except Exception as e:
print('Exception Raised!')
print(e)
print('Connect Again!')
time.sleep(0.1)
# 웹소켓 다시 시작
await connect()
# # 비동기로 서버에 접속한다.
# asyncio.get_event_loop().run_until_complete(connect())
# asyncio.get_event_loop().close()
# -----------------------------------------------------------------------------
# - Name : main
# - Desc : 메인
# -----------------------------------------------------------------------------
async def main():
try:
# 웹소켓 시작
await connect()
except Exception as e:
print('Exception Raised!')
print(e)
if __name__ == "__main__":
# noinspection PyBroadException
try:
# ---------------------------------------------------------------------
# Logic Start!
# ---------------------------------------------------------------------
# 웹소켓 시작
asyncio.run(main())
except KeyboardInterrupt:
print("KeyboardInterrupt Exception 발생!")
print(traceback.format_exc())
sys.exit(-100)
except Exception:
print("Exception 발생!")
print(traceback.format_exc())
sys.exit(-200)

View File

@@ -0,0 +1,288 @@
# -*- coding: utf-8 -*-
### 모듈 임포트 ###
import os
import sys
import json
import time
import requests
import asyncio
import traceback
import websockets
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
### 함수 정의 ###
# AES256 DECODE
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))
# 웹소켓 접속키 발급
def get_approval(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"secretkey": secret}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
# [필수] 유료 시세 수신을 위한 access_token 발급 함수
# 해외주식/해외선물 유료 시세 수신 전 반드시 이 함수를 호출해 access_token을 발급받아야 함
#
# === 해외 유료 시세 수신 안내 ===
# ▒ 해외주식 (HDFSASP0, HDFSASP1, HDFSCNT0: 미국, 중국, 일본, 베트남, 홍콩)
# - 무료 시세: 별도 신청 없이 수신 가능
# - 유료 시세: HTS 또는 MTS에서 신청 후 access_token 발급 필요
# > HTS(eFriend Plus/Force): [7781] 시세신청(실시간)
# > MTS(한국투자 앱): 고객지원 > 거래서비스 신청 > 해외증권 > 해외 실시간 시세 신청
#
# ▒ 해외선물 (HDFFF020, HDFFF010: CME, SGX / 기타 거래소는 무료 시세 제공)
# - CME, SGX: 무료 시세 없음 → 유료 시세 신청 필수
# - 유료 시세: HTS에서 신청 후 access_token 발급 필요
# > HTS(eFriend Plus/Force): [7936] 해외선물옵션 실시간 시세신청/조회
#
# ▒ 유료 시세 수신 절차
# 1. HTS 또는 MTS에서 유료 시세 신청
# 2. get_access_token()으로 access_token 발급 (※ 신청 후에 발급해야 유효)
# 3. 토큰 발급 시점 기준 최대 2시간 이내에 유료 권한 자동 반영
# 4. 이후 웹소켓 연결 → 유료 시세 수신 가능
def get_access_token(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"appsecret": secret}
PATH = "oauth2/tokenP"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
access_token = res.json()["access_token"]
return access_token
### 4. 해외선물옵션 ###
# 해외선물옵션호가 출력라이브러리
def stockhoka_overseafut(data):
# print(data)
recvvalue = data.split('^') # 수신데이터를 split '^'
print("종목코드 ["+recvvalue[ 0]+"]")
print("수신일자 ["+recvvalue[ 1]+"]")
print("수신시각 ["+recvvalue[ 2]+"]")
print("전일종가 ["+recvvalue[ 3]+"]")
print("====================================")
print("매수1수량 ["+recvvalue[ 4]+"]"+", 매수1번호 ["+recvvalue[ 5]+"]"+", 매수1호가 ["+recvvalue[ 6]+"]")
print("매도1수량 ["+recvvalue[ 7]+"]"+", 매도1번호 ["+recvvalue[ 8]+"]"+", 매도1호가 ["+recvvalue[ 9]+"]")
print("매수2수량 ["+recvvalue[10]+"]"+", 매수2번호 ["+recvvalue[11]+"]"+", 매수2호가 ["+recvvalue[12]+"]")
print("매도2수량 ["+recvvalue[13]+"]"+", 매도2번호 ["+recvvalue[14]+"]"+", 매도2호가 ["+recvvalue[15]+"]")
print("매수3수량 ["+recvvalue[16]+"]"+", 매수3번호 ["+recvvalue[17]+"]"+", 매수3호가 ["+recvvalue[18]+"]")
print("매도3수량 ["+recvvalue[19]+"]"+", 매도3번호 ["+recvvalue[20]+"]"+", 매도3호가 ["+recvvalue[21]+"]")
print("매수4수량 ["+recvvalue[22]+"]"+", 매수4번호 ["+recvvalue[23]+"]"+", 매수4호가 ["+recvvalue[24]+"]")
print("매도4수량 ["+recvvalue[25]+"]"+", 매도4번호 ["+recvvalue[26]+"]"+", 매도4호가 ["+recvvalue[27]+"]")
print("매수5수량 ["+recvvalue[28 ]+"]"+", 매수5번호 ["+recvvalue[29]+"]"+", 매수5호가 ["+recvvalue[30]+"]")
print("매도5수량 ["+recvvalue[31]+"]"+", 매도5번호 ["+recvvalue[32]+"]"+", 매도5호가 ["+recvvalue[33]+"]")
print("====================================")
print("전일정산가 ["+recvvalue[32]+"]")
# 해외선물옵션체결처리 출력라이브러리
def stockspurchase_overseafut(data_cnt, data):
print("============================================")
menulist = "종목코드|영업일자|장개시일자|장개시시각|장종료일자|장종료시각|전일종가|수신일자|수신시각|본장_전산장구분|체결가격|체결수량|전일대비가|등락률|시가|고가|저가|누적거래량|전일대비부호|체결구분|수신시각2만분의일초|전일정산가|전일정산가대비|전일정산가대비가격|전일정산가대비율"
menustr = menulist.split('|')
pValue = data.split('^')
# print(pValue)
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 해외선물옵션 체결통보 출력라이브러리
def stocksigningnotice_overseafut(data, key, iv):
menulist = "유저ID|계좌번호|주문일자|주문번호|원주문일자|원주문번호|종목명|정정취소구분코드|매도매수구분코드|복합주문구분코드|가격구분코드|FM거래소접수구분코드|주문수량|FMLIMIT가격|FMSTOP주문가격|총체결수량|총체결단가|잔량|FM주문그룹일자|주문그룹번호|주문상세일시|조작상세일시|주문자|체결일자|체결번호|API체결번호|체결수량|FM체결가격|통화코드|위탁수수료|주문매체온라인여부|FM체결금액|선물옵션종목구분코드"
menustr1 = menulist.split('|')
# AES256 처리 단계
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
# print(aes_dec_str)
pValue = aes_dec_str.split('^')
# print(pValue)
print("#### 해외선물옵션 체결통보 처리 ####")
i = 0
for menu in menustr1:
print("%s [%s]" % (menu, pValue[i]))
i += 1
### 앱키 정의 ###
async def connect():
try:
g_appkey = "앱키를 입력하세요"
g_appsecret = "앱 시크릿키를 입력하세요"
# 해외주식/해외선물(CME, SGX) 유료시세 사용 시 필수(2시간 이내 유료신청정보 동기화)
# access_token = get_access_token(appkey, appsecret)
g_approval_key = get_approval(g_appkey, g_appsecret)
print("approval_key [%s]" % (g_approval_key))
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기
### 4. 해외선물옵션 호가, 체결가, 체결통보 ###
# code_list = [['1','HDFFF020','FCAZ22']] # 해외선물체결
# code_list = [['1','HDFFF010','FCAZ22']] # 해외선물호가
# code_list = [['1','HDFFF020','OESH23 C3900']] # 해외옵션체결
# code_list = [['1','HDFFF010','OESH23 C3900']] # 해외옵션호가
# code_list = [['1','HDFFF2C0','HTS ID를 입력하세요']] # 해외선물옵션체결통보
code_list = [['1','HDFFF020','FCAZ22'],['1','HDFFF010','FCAZ22'],['1','HDFFF020','OESH23 C3900'],['1','HDFFF010','OESH23 C3900'],['1','HDFFF2C0','HTS ID를 입력하세요']]
senddata_list=[]
for i,j,k in code_list:
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
senddata_list.append(temp)
async with websockets.connect(url, ping_interval=None) as websocket:
for senddata in senddata_list:
await websocket.send(senddata)
await asyncio.sleep(0.5)
print(f"Input Command is :{senddata}")
while True:
data = await websocket.recv()
# await asyncio.sleep(0.5)
# print(f"Recev Command is :{data}") # 정제되지 않은 Request / Response 출력
if data[0] == '0':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "HDFFF010": # 해외선물옵션호가 tr 일경우의 처리 단계
print("#### 해외선물옵션호가 ####")
stockhoka_overseafut(recvstr[3])
await asyncio.sleep(0.5)
elif trid0 == "HDFFF020": # 해외선물옵션체결 데이터 처리
print("#### 해외선물옵션체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_overseafut(data_cnt, recvstr[3])
await asyncio.sleep(0.5)
elif data[0] == '1':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "HDFFF2C0": # 해외선물옵션체결 통보 처리
stocksigningnotice_overseafut(recvstr[3], aes_key, aes_iv)
else:
jsonObject = json.loads(data)
trid = jsonObject["header"]["tr_id"]
if trid != "PINGPONG":
rt_cd = jsonObject["body"]["rt_cd"]
if rt_cd == '1': # 에러일 경우 처리
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
break
elif rt_cd == '0': # 정상일 경우 처리
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
if trid == "HDFFF2C0": # 해외선물옵션
aes_key = jsonObject["body"]["output"]["key"]
aes_iv = jsonObject["body"]["output"]["iv"]
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
elif trid == "PINGPONG":
print("### RECV [PINGPONG] [%s]" % (data))
await websocket.pong(data)
print("### SEND [PINGPONG] [%s]" % (data))
# ----------------------------------------
# 모든 함수의 공통 부분(Exception 처리)
# ----------------------------------------
except Exception as e:
print('Exception Raised!')
print(e)
print('Connect Again!')
time.sleep(0.1)
# 웹소켓 다시 시작
await connect()
# # 비동기로 서버에 접속한다.
# asyncio.get_event_loop().run_until_complete(connect())
# asyncio.get_event_loop().close()
# -----------------------------------------------------------------------------
# - Name : main
# - Desc : 메인
# -----------------------------------------------------------------------------
async def main():
try:
# 웹소켓 시작
await connect()
except Exception as e:
print('Exception Raised!')
print(e)
if __name__ == "__main__":
# noinspection PyBroadException
try:
# ---------------------------------------------------------------------
# Logic Start!
# ---------------------------------------------------------------------
# 웹소켓 시작
asyncio.run(main())
except KeyboardInterrupt:
print("KeyboardInterrupt Exception 발생!")
print(traceback.format_exc())
sys.exit(-100)
except Exception:
print("Exception 발생!")
print(traceback.format_exc())
sys.exit(-200)

View File

@@ -0,0 +1,303 @@
# -*- coding: utf-8 -*-
### 모듈 임포트 ###
import os
import sys
import json
import time
import requests
import asyncio
import traceback
import websockets
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
### 함수 정의 ###
# AES256 DECODE
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))
# 웹소켓 접속키 발급
def get_approval(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"secretkey": secret}
PATH = "oauth2/Approval"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
approval_key = res.json()["approval_key"]
return approval_key
# [필수] 유료 시세 수신을 위한 access_token 발급 함수
# 해외주식/해외선물 유료 시세 수신 전 반드시 이 함수를 호출해 access_token을 발급받아야 함
#
# === 해외 유료 시세 수신 안내 ===
# ▒ 해외주식 (HDFSASP0, HDFSASP1, HDFSCNT0: 미국, 중국, 일본, 베트남, 홍콩)
# - 무료 시세: 별도 신청 없이 수신 가능
# - 유료 시세: HTS 또는 MTS에서 신청 후 access_token 발급 필요
# > HTS(eFriend Plus/Force): [7781] 시세신청(실시간)
# > MTS(한국투자 앱): 고객지원 > 거래서비스 신청 > 해외증권 > 해외 실시간 시세 신청
#
# ▒ 해외선물 (HDFFF020, HDFFF010: CME, SGX / 기타 거래소는 무료 시세 제공)
# - CME, SGX: 무료 시세 없음 → 유료 시세 신청 필수
# - 유료 시세: HTS에서 신청 후 access_token 발급 필요
# > HTS(eFriend Plus/Force): [7936] 해외선물옵션 실시간 시세신청/조회
#
# ▒ 유료 시세 수신 절차
# 1. HTS 또는 MTS에서 유료 시세 신청
# 2. get_access_token()으로 access_token 발급 (※ 신청 후에 발급해야 유효)
# 3. 토큰 발급 시점 기준 최대 2시간 이내에 유료 권한 자동 반영
# 4. 이후 웹소켓 연결 → 유료 시세 수신 가능
def get_access_token(key, secret):
# url = https://openapivts.koreainvestment.com:29443' # 모의투자계좌
url = 'https://openapi.koreainvestment.com:9443' # 실전투자계좌
headers = {"content-type": "application/json"}
body = {"grant_type": "client_credentials",
"appkey": key,
"appsecret": secret}
PATH = "oauth2/tokenP"
URL = f"{url}/{PATH}"
time.sleep(0.05)
res = requests.post(URL, headers=headers, data=json.dumps(body))
access_token = res.json()["access_token"]
return access_token
### 2. 해외주식 ###
# 해외주식호가 출력라이브러리
def stockhoka_overseas(data):
""" 넘겨받는데이터가 정상인지 확인
print("stockhoka[%s]"%(data))
"""
recvvalue = data.split('^') # 수신데이터를 split '^'
print("실시간종목코드 [" + recvvalue[0] + "]" + ", 종목코드 [" + recvvalue[1] + "]")
print("소숫점자리수 [" + recvvalue[2] + "]")
print("현지일자 [" + recvvalue[3] + "]" + ", 현지시간 [" + recvvalue[4] + "]")
print("한국일자 [" + recvvalue[5] + "]" + ", 한국시간 [" + recvvalue[6] + "]")
print("======================================")
print("매수총 잔량 [%s]" % (recvvalue[7]))
print("매수총잔량대비 [%s]" % (recvvalue[9]))
print("매도총 잔량 [%s]" % (recvvalue[8]))
print("매도총잔략대비 [%s]" % (recvvalue[10]))
print("매수호가 [%s]" % (recvvalue[11]))
print("매도호가 [%s]" % (recvvalue[12]))
print("매수잔량 [%s]" % (recvvalue[13]))
print("매도잔량 [%s]" % (recvvalue[14]))
print("매수잔량대비 [%s]" % (recvvalue[15]))
print("매도잔량대비 [%s]" % (recvvalue[16]))
# 해외주식체결처리 출력라이브러리
def stockspurchase_overseas(data_cnt, data):
print("============================================")
menulist = "실시간종목코드|종목코드|수수점자리수|현지영업일자|현지일자|현지시간|한국일자|한국시간|시가|고가|저가|현재가|대비구분|전일대비|등락율|매수호가|매도호가|매수잔량|매도잔량|체결량|거래량|거래대금|매도체결량|매수체결량|체결강도|시장구분"
menustr = menulist.split('|')
pValue = data.split('^')
i = 0
for cnt in range(data_cnt): # 넘겨받은 체결데이터 개수만큼 print 한다
print("### [%d / %d]" % (cnt + 1, data_cnt))
for menu in menustr:
print("%-13s[%s]" % (menu, pValue[i]))
i += 1
# 해외주식체결통보 출력라이브러리
def stocksigningnotice_overseas(data, key, iv):
# AES256 처리 단계
aes_dec_str = aes_cbc_base64_dec(key, iv, data)
pValue = aes_dec_str.split('^')
if pValue[12] == '2': # 체결통보
print("#### 해외주식 체결 통보 ####")
menulist = "고객 ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류2|단축종목코드|체결수량|체결단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량|계좌명|체결종목명|해외종목구분|담보유형코드|담보대출일자|분할매수매도시작시간|분할매수매도종료시간|시간분할타입유형|체결단가12"
menustr1 = menulist.split('|')
else:
print("#### 해외주식 주문·정정·취소·거부 접수 통보 ####")
menulist = "고객 ID|계좌번호|주문번호|원주문번호|매도매수구분|정정구분|주문종류2|단축종목코드|주문수량|주문단가|체결시간|거부여부|체결여부|접수여부|지점번호|주문수량_미출력|계좌명|체결종목명|해외종목구분|담보유형코드|담보대출일자|분할매수매도시작시간|분할매수매도종료시간|시간분할타입유형|체결단가12"
menustr1 = menulist.split('|')
i = 0
for menu in menustr1:
print("%s [%s]" % (menu, pValue[i]))
i += 1
async def connect():
try:
g_appkey = '앱키를 입력하세요'
g_appsecret = '앱 시크릿키를 입력하세요'
# 해외주식/해외선물(CME, SGX) 유료시세 사용 시 필수(2시간 이내 유료신청정보 동기화)
# access_token = get_access_token(appkey, appsecret)
g_approval_key = get_approval(g_appkey, g_appsecret)
print("approval_key [%s]" % (g_approval_key))
# url = 'ws://ops.koreainvestment.com:31000' # 모의투자계좌
url = 'ws://ops.koreainvestment.com:21000' # 실전투자계좌
# 원하는 호출을 [tr_type, tr_id, tr_key] 순서대로 리스트 만들기
### 해외주식(미국) 호가, 체결가, 체결통보 ###
# code_list = [['1','HDFSASP0','DNASAAPL'],['1','HDFSCNT0','DNASAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요']]
### 해외주식(미국-주간) 호가, 체결가, 체결통보 ###
# code_list = [['1','HDFSASP0','RBAQAAPL'],['1','HDFSCNT0','RBAQAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요']]
### 해외주식(아시아) 호가, 체결가, 체결통보 ###
# code_list = [['1','HDFSASP1','DHKS00003'],['1','HDFSCNT0','DHKS00003'],['1','H0GSCNI0','HTS ID를 입력하세요']]
### 해외주식(미국) 호가, 체결가, 체결통보, 해외주식(아시아) 호가, 체결가, 체결통보 ###
code_list = [['1','HDFSASP0','DNASAAPL'],['1','HDFSCNT0','DNASAAPL'],['1','H0GSCNI0','HTS ID를 입력하세요'],
['1','HDFSASP1','DHKS00003'],['1','HDFSCNT0','DHKS00003'],['1','H0GSCNI0','HTS ID를 입력하세요']]
senddata_list=[]
for i,j,k in code_list:
temp = '{"header":{"approval_key": "%s","custtype":"P","tr_type":"%s","content-type":"utf-8"},"body":{"input":{"tr_id":"%s","tr_key":"%s"}}}'%(g_approval_key,i,j,k)
senddata_list.append(temp)
async with websockets.connect(url, ping_interval=None) as websocket:
for senddata in senddata_list:
await websocket.send(senddata)
await asyncio.sleep(0.5)
print(f"Input Command is :{senddata}")
while True:
data = await websocket.recv()
# await asyncio.sleep(0.5)
# print(f"Recev Command is :{data}") # 정제되지 않은 Request / Response 출력
if data[0] == '0':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "HDFSASP0": # 해외주식호가tr 일경우의 처리 단계
print("#### 해외(미국)주식호가 ####")
stockhoka_overseas(recvstr[3])
await asyncio.sleep(0.5)
elif trid0 == "HDFSASP1": # 해외주식호가tr 일경우의 처리 단계
print("#### 해외(아시아)주식호가 ####")
stockhoka_overseas(recvstr[3])
await asyncio.sleep(0.5)
elif trid0 == "HDFSCNT0": # 해외주식체결 데이터 처리
print("#### 해외주식체결 ####")
data_cnt = int(recvstr[2]) # 체결데이터 개수
stockspurchase_overseas(data_cnt, recvstr[3])
await asyncio.sleep(0.5)
elif data[0] == '1':
recvstr = data.split('|') # 수신데이터가 실데이터 이전은 '|'로 나뉘어져있어 split
trid0 = recvstr[1]
if trid0 == "H0GSCNI0" or trid0 == "H0GSCNI9": # 해외주식체결 통보 처리
stocksigningnotice_overseas(recvstr[3], aes_key, aes_iv)
else:
jsonObject = json.loads(data)
trid = jsonObject["header"]["tr_id"]
if trid != "PINGPONG":
rt_cd = jsonObject["body"]["rt_cd"]
if rt_cd == '1': # 에러일 경우 처리
if jsonObject["body"]["msg1"] != 'ALREADY IN SUBSCRIBE':
print("### ERROR RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
break
elif rt_cd == '0': # 정상일 경우 처리
print("### RETURN CODE [ %s ][ %s ] MSG [ %s ]" % (jsonObject["header"]["tr_key"], rt_cd, jsonObject["body"]["msg1"]))
# 체결통보 처리를 위한 AES256 KEY, IV 처리 단계
if trid == "H0GSCNI0": # 해외주식
aes_key = jsonObject["body"]["output"]["key"]
aes_iv = jsonObject["body"]["output"]["iv"]
print("### TRID [%s] KEY[%s] IV[%s]" % (trid, aes_key, aes_iv))
elif trid == "PINGPONG":
print("### RECV [PINGPONG] [%s]" % (data))
await websocket.pong(data)
print("### SEND [PINGPONG] [%s]" % (data))
# ----------------------------------------
# 모든 함수의 공통 부분(Exception 처리)
# ----------------------------------------
except Exception as e:
print('Exception Raised!')
print(e)
print('Connect Again!')
time.sleep(0.1)
# 웹소켓 다시 시작
await connect()
# # 비동기로 서버에 접속한다.
# asyncio.get_event_loop().run_until_complete(connect())
# asyncio.get_event_loop().close()
# -----------------------------------------------------------------------------
# - Name : main
# - Desc : 메인
# -----------------------------------------------------------------------------
async def main():
try:
# 웹소켓 시작
await connect()
except Exception as e:
print('Exception Raised!')
print(e)
if __name__ == "__main__":
# noinspection PyBroadException
try:
# ---------------------------------------------------------------------
# Logic Start!
# ---------------------------------------------------------------------
# 웹소켓 시작
asyncio.run(main())
except KeyboardInterrupt:
print("KeyboardInterrupt Exception 발생!")
print(traceback.format_exc())
sys.exit(-100)
except Exception:
print("Exception 발생!")
print(traceback.format_exc())
sys.exit(-200)