728x90
반응형

[카카오지도 API] spring / 주소로 장소 표시하기 (tistory.com)

 

[카카오지도 API] spring / 주소로 장소 표시하기

주소로 장소 표시하기 - Kakao 지도 Web API 📝카카오지도 API를 활용하여 주소로 장소 표시하기 위 링크로 접속하고 [Javascript + HTML]을 누르면 카카오지도 API 활용 코드를 확인할 수 있습니다. AddressC

fjdkslvn.tistory.com

 

[카카오지도 API] 좌표로 주소 추출 (tistory.com)

 

[카카오지도 API] 좌표로 주소 추출

 

fjdkslvn.tistory.com

: 해당 글을 참조하면 지번 주소로 변환하는 방법을 알 수 있습니다.

 

📘 주소 입력시 지도에 출력

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<!-- jquery 사용 -->
<script src="http://code.jquery.com/jquery-latest.js"></script> 
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
		<table>
			<tr>
				<td>주소</td>
				<td><input type="text" id="address"></td>
				<td><button type="button" id="searchBtn">검색</button></td>
			</tr>
			<tr>
				<td>상세 주소</td>
				<td><input type="text" name="detailAddress2"></td>
				<td></td>
			</tr>
		</table>
		<div id="map" style="width:100%;height:350px;"></div>
   
	<!-- kakao API -->
	<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=자바스크립트 키&libraries=services"></script>
	<script>
	var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
	    mapOption = {
	        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
	        level: 3 // 지도의 확대 레벨
	    };  
	
	
	$('#searchBtn').click(function(){
		// 버튼을 click했을때
		
		// 지도를 생성합니다    
		var map = new kakao.maps.Map(mapContainer, mapOption); 
		
		// 주소-좌표 변환 객체를 생성합니다
		var geocoder = new kakao.maps.services.Geocoder();
		
		// 주소로 좌표를 검색합니다
		geocoder.addressSearch($('#address').val(), function(result, status) {
	
		    // 정상적으로 검색이 완료됐으면 
		     if (status === kakao.maps.services.Status.OK) {
		        var coords = new kakao.maps.LatLng(result[0].y, result[0].x);
		        
		        // 결과값으로 받은 위치를 마커로 표시합니다
		        var marker = new kakao.maps.Marker({
		            map: map,
		            position: coords
		        });
	
		        // 인포윈도우로 장소에 대한 설명을 표시합니다
		        var infowindow = new kakao.maps.InfoWindow({
		            content: '<div style="width:150px;text-align:center;padding:6px 0;">장소</div>'
		        });
		        infowindow.open(map, marker);
	
		        // 지도의 중심을 결과값으로 받은 위치로 이동시킵니다
		        map.setCenter(coords);
		    } 
		});  
	});
	  
	</script>
</body>
</html>

검색이 실질적으로 되는 부분은 id가 address인 input입니다.

 

해당 부분에 지번 주소, 도로명 주소, 해당 주소 포함 상세주소 등 어떠한 형식으로 입력해도 버튼을 누르면, kakao API를 통해 좌표를 검색하여 지도에 위치를 찍어줍니다.

 

문제점 : 어떠한 방식으로 입력해도 주소에 찍히기 때문에 원하는 형식으로 input의 value값을 바꿔줘야 주소값을 저장하고 활용할 때, 문제가 생기지 않습니다.

 

ex) 서울 마포구 러아니푸로 8길 79-5 순돌아파트 101동 809호

     : 서울특별시로 저장해야하는데 서울로 되어있음

     : 러아니푸로 8길이 띄어쓰기 적용이 되어있음

     : 건물 부번까지(79-5)만 저장해야 하는데 상세 주소가 포함되어있음

     : 도로명 주소를 받아야하는데 지번주소로 되어있음

 

 

 

 

📘 주소 입력시 지도에 출력

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<!-- jquery 사용 -->
<script src="http://code.jquery.com/jquery-latest.js"></script> 
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
		<table>
			<tr>
				<td>주소</td>
				<td><input type="text" name="detailAddress" id="address"></td>
				<td><button type="button" id="searchBtn">검색</button></td>
			</tr>
			<tr>
				<td>상세 주소</td>
				<td><input type="text" name="detailAddress2"></td>
				<td></td>
			</tr>
		</table>
		<div id="map" style="width:100%;height:350px;"></div>
   
	<!-- kakao API -->
	<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=자바스크립트 키&libraries=services"></script>
	<script>
	var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
	    mapOption = {
	        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
	        level: 3 // 지도의 확대 레벨
	    };  
	
	
	$('#searchBtn').click(function(){
		// 버튼을 click했을때
		
		// 지도를 생성합니다    
		var map = new kakao.maps.Map(mapContainer, mapOption); 
		
		// 주소-좌표 변환 객체를 생성합니다
		var geocoder = new kakao.maps.services.Geocoder();
		
		// 주소로 좌표를 검색합니다
		geocoder.addressSearch($('#address').val(), function(result, status) {
	
		    // 정상적으로 검색이 완료됐으면 
		     if (status === kakao.maps.services.Status.OK) {
		        var coords = new kakao.maps.LatLng(result[0].y, result[0].x);
		        
		        // 추출한 좌표를 통해 도로명 주소 추출
		        let lat = result[0].y;
		        let lng = result[0].x;
		        getAddr(lat,lng);
		        function getAddr(lat,lng){
		            let geocoder = new kakao.maps.services.Geocoder();
	
		            let coord = new kakao.maps.LatLng(lat, lng);
		            let callback = function(result, status) {
		                if (status === kakao.maps.services.Status.OK) {
		                	// 추출한 도로명 주소를 해당 input의 value값으로 적용
		                    $('#address').val(result[0].road_address.address_name);
		                }
		            }
		            geocoder.coord2Address(coord.getLng(), coord.getLat(), callback);
		        }
		        
		        // 결과값으로 받은 위치를 마커로 표시합니다
		        var marker = new kakao.maps.Marker({
		            map: map,
		            position: coords
		        });
	
		        // 인포윈도우로 장소에 대한 설명을 표시합니다
		        var infowindow = new kakao.maps.InfoWindow({
		            content: '<div style="width:150px;text-align:center;padding:6px 0;">장소</div>'
		        });
		        infowindow.open(map, marker);
	
		        // 지도의 중심을 결과값으로 받은 위치로 이동시킵니다
		        map.setCenter(coords);
		    } 
		});  
	});
	  
	</script>
</body>
</html>

이슈를 해결하는 코드입니다.

 

주소를 통해 좌표값을 추출하면, 해당 좌표값으로 다시 원하는 형태의 주소(도로명 주소)를 추출합니다.

 

원하는 형태의 주소값은 처음 주소를 입력받은 id가 address인 input의 value값으로 다시 넣어줍니다.

 

해당 input에 원하는 형태의 주소 형태로 값이 적용된 것을 확인할 수 있습니다.

728x90
반응형
728x90
반응형

📗 좌표로 주소 데이터 확인

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=자바스크립트 키&libraries=services"></script>
<script>
let lat = 37.5566803113882;
let lng = 126.904501286522;
getAddr(lat,lng);
function getAddr(lat,lng){
    let geocoder = new kakao.maps.services.Geocoder();

    let coord = new kakao.maps.LatLng(lat, lng);
    let callback = function(result, status) {
        if (status === kakao.maps.services.Status.OK) {
            console.log(result);
        }
    }
    geocoder.coord2Address(coord.getLng(), coord.getLat(), callback);
}
</script>

원하는 위치의 좌표를 lat, lng변수에 넣어 실행하면 kakao API를 통해 지번주소와 도로명 주소를 추출 할 수 있습니다.

 

(위치는 제가 좋아하는 맛집입니다..)

 

 

 

 

 

 

📗 좌표로 지번 주소 추출

해당 주소 데이터에서 address_name을 추출하는 방법을 알려드리겠습니다.

 

 

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=자바스크립트 키&libraries=services"></script>
<script>
let lat = 37.5566803113882;
let lng = 126.904501286522;
getAddr(lat,lng);
function getAddr(lat,lng){
    let geocoder = new kakao.maps.services.Geocoder();

    let coord = new kakao.maps.LatLng(lat, lng);
    let callback = function(result, status) {
        if (status === kakao.maps.services.Status.OK) {
            console.log(result[0].address.address_name);
        }
    }
    geocoder.coord2Address(coord.getLng(), coord.getLat(), callback);
}
</script>

이전에는 주소데이터의 결과값인 result만을 출력하여 데이터 집합을 출력하였습니다.

 

현재는 result[0].address.address_name을 통해 지번 주소에 직접 접근하여 데이터를 추출하였습니다.

 

 

 

📗 좌표로 도로명 주소 추출

해당 주소 데이터에서 address_name을 추출하는 방법을 알려드리겠습니다.

 

 

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=자바스크립트 키&libraries=services"></script>
<script>
let lat = 37.5566803113882;
let lng = 126.904501286522;
getAddr(lat,lng);
function getAddr(lat,lng){
    let geocoder = new kakao.maps.services.Geocoder();

    let coord = new kakao.maps.LatLng(lat, lng);
    let callback = function(result, status) {
        if (status === kakao.maps.services.Status.OK) {
            console.log(result[0].road_address.address_name);
        }
    }
    geocoder.coord2Address(coord.getLng(), coord.getLat(), callback);
}
</script>

지번 주소의 경우와 마찬가지 입니다.

 

result[0].address.address_name을 통해 도로명 주소에 직접 접근하여 데이터를 추출하였습니다.

 

같은 방식으로 원하는 데이터에 자유롭게 접근할 수 있습니다.

 

728x90
반응형
728x90
반응형

 

🎃 카카오지도 API 오류

[오류명]

- Failed to load resource : the server responded with a status of 401 (Unauthorized)

- Uncought ReferenceError : kakao is not defined

- Failed to load resource : the server responded with a status of 404 ( )

 

 

 

 

 

 

 

💊 오류 해결 방법

 

1. API 키값 오류

kakao developers에서 발급받은 본인의 API key값을 확인합니다.

 

 

 

 

<!-- kakao API -->
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=본인의 키값&libraries=services"></script>

kakao 지도 API를 사용하기 위해 작성한 script코드 속 appkey값에 문제점이 없는지 확인합니다. 잘못된 키 값을 사용하면 오류가 생길 수 있습니다.

 

 

 

 

2. 플랫폼 -> Web -> 사이트 도메인 확인

해당 위치에 프로젝트 도메인을 입력하지 않으면, 오류가 발생합니다.

 

저는 기존에 사이트 도메인으로 http://localhost:8080 넣어두고 localhost 8080번 포트에서 프로젝트를 진행했습니다. 이후에 :8080을 붙이는 것이 너무 불편하여 80번 포트로 변경 후 프로그램을 실행했더니 해당 오류가 발생하였습니다. 찾아본 결과 도메인을 추가하지 않아서 발생한 문제인것을 알고 추가하여 오류를 해결하였습니다.

728x90
반응형
728x90
반응형

주소로 장소 표시하기 - Kakao 지도 Web API

 

 

 

 

📝카카오지도 API를 활용하여 주소로 장소 표시하기

 

위 링크로 접속하고 [Javascript + HTML]을 누르면 카카오지도 API 활용 코드를 확인할 수 있습니다.

 

 

 

 

 

 

AddressController.java

package com.practice.fjdkslvn.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class AddressController {
	
	@GetMapping("/address") 
	public String address() {
		System.out.println("카카오 API 테스트");
		
		return "address";
	}
}

address.jsp를 포워딩하는 코드를 작성합니다.

 

 

 

 

 

 

 

 

address.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>주소로 장소 표시하기</title>
    
</head>
<body>
<p style="margin-top:-12px">
    <em class="link">
        <a href="javascript:void(0);" onclick="window.open('http://fiy.daum.net/fiy/map/CsGeneral.daum', '_blank', 'width=981, height=650')">
            혹시 주소 결과가 잘못 나오는 경우에는 여기에 제보해주세요.
        </a>
    </em>
</p>
<div id="map" style="width:100%;height:350px;"></div>

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=당신의 키값을 넣어주세요&libraries=services"></script>
<script>
var mapContainer = document.getElementById('map'), // 지도를 표시할 div 
    mapOption = {
        center: new kakao.maps.LatLng(33.450701, 126.570667), // 지도의 중심좌표
        level: 3 // 지도의 확대 레벨
    };  

// 지도를 생성합니다    
var map = new kakao.maps.Map(mapContainer, mapOption); 

// 주소-좌표 변환 객체를 생성합니다
var geocoder = new kakao.maps.services.Geocoder();

// 주소로 좌표를 검색합니다
geocoder.addressSearch('서울 송파구 올림픽로 240', function(result, status) {

    // 정상적으로 검색이 완료됐으면 
     if (status === kakao.maps.services.Status.OK) {

        var coords = new kakao.maps.LatLng(result[0].y, result[0].x);

        // 결과값으로 받은 위치를 마커로 표시합니다
        var marker = new kakao.maps.Marker({
            map: map,
            position: coords
        });

        // 인포윈도우로 장소에 대한 설명을 표시합니다
        var infowindow = new kakao.maps.InfoWindow({
            content: '<div style="width:150px;text-align:center;padding:6px 0;">롯데월드</div>'
        });
        infowindow.open(map, marker);

        // 지도의 중심을 결과값으로 받은 위치로 이동시킵니다
        map.setCenter(coords);
    } 
});    
</script>
</body>
</html>

포워딩 되는 jsp는 처음 걸려있던 링크의 코드를 복붙하고 자신의 javascript키값을 넣은 후, 원하는 주소를 입력하여 활용하면 됩니다.

 

저는 롯데월드를 넣어 실행해보았습니다.

 

 

 

 

 

 

저는 카카오지도 API를 활용하기 위한 플랫폼 설정을 localhost로 해두었습니다. 그래서 localhost 아래에 있는 주소는 모두 정상적으로 API가 적용되도록 되어있습니다. 좌표를 직접 구하지 않고 도로명주소와 지번주소를 통해 쉽게 좌표를 찍을 수 있었습니다.

 

 

 

 

 

[플랫폼 설정 포스팅]

카카오 지도 API 사용을 위한 키 발급 받기 (tistory.com)

 

카카오 지도 API 사용을 위한 키 발급 받기

Kakao Developers Kakao Developers 카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다. developers.kakao.com 🔑 카카오 API를..

fjdkslvn.tistory.com

 

728x90
반응형
728x90
반응형

Kakao Developers

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

 

🔑 카카오 API를 위한 키 발급 받기

 

위 링크로 들어가서 카카오 계정으로 로그인을 해줍니다.

 

 

 

 

 

내 애플리케이션으로 들어와서 '애플리케이션 추가하기'를 누릅니다.

 

 

 

 

 

 

앱 이름을 본인이 알아보기 쉬운것으로 설정하고, 사업자명은 아무거나 적어도 상관없습니다.

 

 

 

 

 

 

 

애플리케이션을 추가했으면 플랫폼을 설정해줍니다. (제 키는 가려놨습니다)

 

 

 

 

 

 

원하는 플랫폼을 선택하고 플랫폼을 등록할 수 있도록 작업을 진행합니다.

 

Web의 경우에는 사이트 도메인을 입력해야합니다. 저는 localhost로 두었습니다.

 

 

 

 

 

 

 

 

 

사전 작업은 끝났습니다. 다음 포스팅에서는 간단하게 html에 지도를 띄워보겠습니다.

728x90
반응형
728x90
반응형

영화진흥위원회 오픈API 사용하는 방법은 해당 포스팅을 봐주시면 감사합니다.

영화진흥위원회 오픈API 사용하는 방법 + csv 추출 :: 러아니푸의 공부방 (tistory.com)

 

영화진흥위원회 오픈API 사용하는 방법 + csv 추출

1. 영화진흥위원회 사이트에 들어가서 회원가입을 한다. 영화진흥위원회: https://www.kobis.or.kr/kobisopenapi/homepg/main/main.do 2. 키 발급/관리로 들어가서 키를 발급받는다. 키 발급받기를 눌러줍니다...

fjdkslvn.tistory.com

 

 

from datetime import datetime, timedelta
import requests
import json
import pandas as pd

#날짜를 문자열로 변환하는 함수
def date_string(date):
    date_list = str(date).split()[0].split('-')
    date_str = date_list[0]+date_list[1]+date_list[2]
    return date_str

yourkey='당신의 영화진흥위원회 API키를 입력하세요' #((키 넣어주시면 됩니다!))

#원하는 날짜 +1 해주세요. 2018년 전체를 하고싶다면 (2019,1,1) ((날짜 수정해주시면 됩니다!))
date = datetime(2019,1,1)
date_str = date_string(date)
print(date_str)

# 2018년 1월1일까지만 할것이다 ((날짜 수정해주시면 됩니다!))
while date_str != '20180101':
    date = date - timedelta(days=1)
    date_str = date_string(date)
    print(date_str)

    url = 'http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key='+yourkey+'&targetDt='+date_str
    res = requests.get(url)
    text= res.text

    d = json.loads(text)

    movie_list = []

    x=d['boxOfficeResult']['showRange'] #박스오피스 일자 추출

    for b in d['boxOfficeResult']['dailyBoxOfficeList']:
        movie_list.append([x, b['movieNm'],b['movieCd'],b['audiCnt'],b['audiInten'],b['audiChange'],b['audiAcc']
                           ,b['openDt'],b['salesAmt'],b['salesShare'],b['salesInten'],b['salesChange'],b['salesAcc'],b['scrnCnt'],b['showCnt'],b['rank'],b['rankInten'],b['rankOldAndNew']])
    # rnum은 순번을 출력하는 것이기 때문에 안넣었습니다

    # 파일 이름 변경하는 곳 ((파일이름 수정해주시면 됩니다!))
    data = pd.DataFrame(movie_list)
    data.to_csv("2018_movie.csv", mode='a', encoding='utf-8', index=False)

csv 파일은 파이썬 파일이 저장된 위치에 저장됩니다.

 

주석에 설명을 달아놨습니다. yourkey에 영화진흥위원회에서 받은 API 키를 입력하고 시작날짜와 끝 날짜를 넣고 사용하시면 됩니다.

728x90
반응형
728x90
반응형

1. 로그인 후 주식 기본정보 가져오는 코드

 

*코드*

더보기
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)

        if rQName == "주식정보":
            # PER, PBR 정보 획득
            name = self.GetCommData(trCode, rQName, 0, "종목명").strip()
            code = self.GetCommData(trCode, rQName, 0, "종목코드").strip().replace("A", "")
            PER = float(self.GetCommData(trCode, rQName, 0, "PER"))
            PBR = float(self.GetCommData(trCode, rQName, 0, "PBR"))
            ROE = float(self.GetCommData(trCode, rQName, 0, "ROE"))

            print('종목명: %s PER: %.2f PBR: %.2f ROE: %.2f' % (name, PER, PBR, ROE))

    @SyncRequestDecorator.kiwoom_sync_request
    def get_opt10001(self, code):
        self.SetInputValue("종목코드", code)
        self.CommRqData("주식정보", "opt10001", 0, "0105")

class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)
        sleep(5)

        ###############################
        # 2. 주식기본정보요청            #
        ###############################
        self._kiwoom.get_opt10001('005930')

if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

 

 

 

 

2. 조건식 추가 및 결과 가져오기

 

- 조건식 추가하는 방법

 

1. 모의투자가 아닌 ID로 로그인 한다.

 

 

 

2. 검색에 '0150'을 입력하여 조건검색창을 열고 조건식 -> 재무분석 -> 주가지표 -> PER를 누른다.

 

 

3. 위의 조건을 원하는대로 선택하고 추가버튼을 누른 후 아래쪽 내 조건식 저장을 누른 후 조건명을 입력하고 확인을 누른다.

 

 

*코드*

더보기
# -*-coding: utf-8 -*-
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

        # 조건식 결과 출력
        print(codeList)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)


class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)

        sleep(5)
        ###############################
        # 2. 조건식 결과 가져오기        #
        ###############################

        # 조건식 불러오기
        self._kiwoom.GetConditionLoad()

        # 조건식 리스트 가져오기 (res="000^스파르타;001^조건식1;002^조건식2;...")
        res = self._kiwoom.GetConditionNameList()
        print(res)
        res = res.split(';')
        print(res)

        # 조건식 중 'TEST' 조건식 존재여부 확인
        condition_index = None
        condition_name = None

        for name in res:
            if 'TEST' in name:
                [condition_index, condition_name] = name.split('^')
                break

        if condition_index == None or condition_name == None:
            print("Can't find a condition.")
            exit(0)

        print("조건식:", condition_name, "번호:", condition_index)

        # '스파르타' 조건식 결과 요청하기
        condition_index = int(condition_index)
        self._kiwoom.SendCondition("0156", condition_name, condition_index, 0)

if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

 

 

 

3. 여러 주식 종목에 대한 정보를 가져오기

 

*코드*

더보기
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)

        if rQName == "복수종목조회":
            cnt = self.GetRepeatCnt(trCode, rQName)

            # 시가총액 및 호가정보 self.li_stock에 저장
            for i in range(cnt):
                name = self.GetCommData(trCode, rQName, i, "종목명").strip()
                code = self.GetCommData(trCode, rQName, i, "종목코드").strip().replace("A", "")
                print("종목명:", name,"종목코드:", code)


class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)

        ###############################
        # 2. 복수종목 조회              #
        ###############################
        self._kiwoom.CommKwRqData("005930;000660", 0, 2, 0, "복수종목조회", "0122")

if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

 

 

4. 잔고조회 및 보유 종목 확인

-> 모의 주식을 대상으로 했기 때문에 계좌번호 입력을 자신의 모의계좌번호로 바꾸셔야합니다.

더보기

*코드*

from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)

        if rQName == '잔고조회':
            # 잔고 정보 획득
            total_budget = int(self.GetCommData(trCode, rQName, 0, "추정예탁자산").strip())
            print("++ 계좌평가현황 ++")
            print("추정예탁자산:", total_budget)

            print("++ 종목별계좌평가현황 ++")
            cnt = self.GetRepeatCnt(trCode, rQName)

            for i in range(cnt):
                name = self.GetCommData(trCode, rQName, i, "종목명").strip()
                code = self.GetCommData(trCode, rQName, i, "종목코드").strip().replace("A", "")
                estimated_amount = int(self.GetCommData(trCode, rQName, i, "평가금액"))
                count = int(self.GetCommData(trCode, rQName, i, "보유수량"))

                print("종목명:", name, "종목코드:", code, "평가금액", estimated_amount, "보유수량", count)

    @SyncRequestDecorator.kiwoom_sync_request
    def get_jango_data(self, prevNext, **kwargs):
        self.SetInputValue("계좌번호", "8162697811")
        self.SetInputValue("비밀번호", "")
        self.SetInputValue("상장폐지조회구분", "0")
        self.SetInputValue("비밀번호입력매체구분", "00")
        self.CommRqData("잔고조회", "OPW00004", prevNext, "1999")


class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)

        ###############################
        # 2. 잔고 조회                 #
        ###############################
        self._kiwoom.get_jango_data("0")

if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

728x90
반응형
728x90
반응형

1. 개발 가이드에서 로그인 버전관리의 관련함수를 살펴본다.

: LONG CommConnect() 함수는 로그인 창을 출력하는 함수입니다.

 

 

 

: LONG GetLoginInfo() 함수에서는 로그인한 사용자에 관련된 다양한 정보를 반환받을 수 있습니다.

 

 

2. 코드 적용 사례

: CommConnect() 함수를 통해 로그인을 시도한 후, 로그인 완료가 될때까지 로그인 대기중을 띄웁니다.

 

GetLoginInfo() 함수의 인자값을 'ACCOUNT_CTN'으로 넣어주어 사용자의 보유계좌 갯수를 반환하고 갯수가 받아와지는 즉시 반복문을 탈출하지만, 로그인이 되지 않은 상태라 갯수가 반환되지 않는다면 로그인 대기중을 띄우는 것 입니다.

 

Sleep(5)를 주었는데, 키움 서버로 요청을 너무 많이 보내면 키움측에서 차단할 수 있기 때문에 시간차를 준 것입니다.

728x90
반응형
728x90
반응형

키움 API는 일반 함수와 이벤트 함수로 구성되어있다.

 

- 일반함수 : 호출 시 바로 반환 값을 받을 수 있는 함수이다. 즉 키움서버에 데이터를 전송하지 않는다.

 

- 이벤트 함수(aka 콜백 함수) : 호출 시 다른 함수에서 결과를 받게 되는 함수이다. 즉 키움서버에 데이터 요청하고 원하는 정보를 받아낸다.

<이벤트 함수 예시>

 

모든 이벤트 함수는 비동기 방식이다.  데이터의 요청과 응답이 우리가 생각하는 순서처럼 순차적으로 이루어지지 않기 때문에 키움 API를 사용하여 코딩할 때 유의해야 한다.

728x90
반응형
728x90
반응형

1. main.py에 코드를 넣는다.

 

* 코드 *

더보기
# -*-coding: utf-8 -*-
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)

class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)
        sleep(5)


if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

 

2. 아래쪽 터미널을 열고 'pip install PyQt5'를 입력하여 모듈을 설치한다.

< 그냥 빨간줄 눌러서 설치하셔도 됩니다 >

 

 

 

 

3. 프로그램 Run을 하면 로그인을 입력하는 화면이 나온다. 꼭 모의투자 접속에 체크하여 로그인 하기!

: 버전 문제로 해당 프로그램을 종료하고 확인을 누르라는 말이 뜰 수 있는데, 하라는 대로 하면 됩니다. Run 버튼 쪽에 빨간 네모를 누르시면 종료할 수 있습니다.

 

 

 

4. 로그인에 성공하였으면 컴퓨터 오른쪽 화단에 위를 가르키는 표시가 있는데, 이를 누르고 화려한 네모를 우클릭하여 계좌비밀번호 저장을 누른다.

 

 

 

5. 빈칸에 0000(모의계좌 비밀번호)를 입력하고 등록을 누른 후 AURO(자동로그인)을 체크한 후 닫기를 누른다.

 

 

 

 

6. 파이참 프로그램을 중지시키고 다시 실행하면 자동 로그인이 되는 것을 확인할 수 있다.

 

 

* 만약 자동로그인을 해제하고 싶다면*

더보기

c 드라이브 -> OpenAPI -> system 에서 Autologin.dat을 지우면 된다.

 

 

 

 

7. 코드 설명

<request>
<callback>

 

비동기식 키움 API를 동기식으로 사용하기위해 SyncRequestDecorator를 사용하여 데이터 요청후 즉시 응답을 받을 수 있도록 구성되어있다.

728x90
반응형

+ Recent posts