OpenAPI+ basic API

Open API+ 로그인하기

파이썬에서 OpenAPI+를 사용하려면 PyQt를 사용하는 것이 가장 편리한 방법.

전체코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QAxContainer import *

class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PyStock")
self.setGeometry(300, 300, 300, 150)

self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")

btn1 = QPushButton("Login", self)
btn1.move(20, 20)
btn1.clicked.connect(self.btn1_clicked)

btn2 = QPushButton("Check state", self)
btn2.move(20, 70)
btn2.clicked.connect(self.btn2_clicked)

def btn1_clicked(self):
ret = self.kiwoom.dynamicCall("CommConnect()")

def btn2_clicked(self):
if self.kiwoom.dynamicCall("GetConnectState()") == 0:
self.statusBar().showMessage("Not connected")
else:
self.statusBar().showMessage("Connected")

if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
app.exec_()

파이썬에서 키움증권의 클래스를 사용하려면 PyQtQAxWidget 클래스를 사용하여 인스턴스를 생성해야 함.
키움증권이 제공하는 클래스는 각각 고유의 CLSID 또는 ProgID를 가지는데 해당 값을 QAxWidget 클래스의 생성자로 넘겨주면 인스턴스가 생성됨.

키움증권의 개발 가이드를 참조하면 CLSID{A1574A0D-6BFA-4BD7-9020-DED88711818D} 임을 알 수 있고, 이를 윈도우 레지스트리 편집기를 통해 검색하면
ProgID는 아래와 같이 `‘KHOPENAPI.KHOpenAPICtrl.1’ 임을 알 수 있음.

키움증권 OpenAPI+ ProgID

CLSID는 10진수로 구성되어 있어 조금 복작하기 때문에 파이썬에서 증권사 API가 제공하는 클래스의 인스턴스를 생성할 때는 보통 문자열로 된 ProgID를 사용하는 것이 편리.

키움증권이 제공하는 클래스를 사용하기 위해 ProgIDQAxWidget 클래스의 생성자로 전달하여 인스턴스를 생성.

키움이 제공하는 클래스 사용
1
2
3
4
class MyWindow(QMainWindow):
def __init__(self):
# 코드 생략
self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")

self.kiwoomQAxWidget 클래스의 인스턴스인데 QAxWidget 클래스는 아래와 같이 QWidgetQAxBase라는 클래스를 상속 받음.

QAxWidget Class 상속 구조

OpenAPI+는 로그인 윈도우를 실행하는 CommConnect 메서드를 제공.
즉, 인스턴스를 통해 CommConnect 메서드를 호출하기만 하면 로그인을 위한 윈도우가 자동으로 실행됨.
키움증권의 OpenAPI+OCX 방식으로 개발되었기 때문에 파이썬에서 메서드를 호출하는 방식도 COM 방식과는 다름.

COM 방식에서는 인스턴스를 통해 메서드를 호출했던 것과 달리 OCX 방식에서는 QAxBase 클래스의 dynamicCall 메서드를 사용하여 원하는 메서드를 호출할 수 있음.

정리해보면 OpenAPI+가 제공하는 메서드를 사용하기 위해서는 self.kiwoom 객체를 통해 dynamicCall 메서드를 호출해야 함.
이때 dynamicCall 메서드의 인자로 호출하려는 메서드를 넘겨줌.

call CommConnect
1
self.kiwoom.dynamicCall("CommConnect()")

서버 접속 상태를 확인 하기 위해 GetConnectState 메서드를 호출한 후 메서드의 리턴 값을 확인.

로그인 이벤트 처리하기

OpenAPI+CommConnect 메서드를 통해 키움증권 서버에 로그인을 시도하면 키움증권 서버는 아래와 같이
OnEventConnect라는 이벤트를 발생.
따라서 프로그램이 OnEventConnect 이벤트를 처리한다면 GetConnectState 메서드를 사용하지 않고도 로그인 상태 확인 가능.

OnEventConnect 이벤트 발생

이벤트 처리를 통한 로그인 여부 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QAxContainer import *

class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PyStock")
self.setGeometry(300, 300, 300, 150)

self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
self.kiwoom.dynamicCall("CommConnect()")

self.text_edit = QTextEdit(self)
self.text_edit.setGeometry(10, 60, 280, 80)
self.text_edit.setEnabled(False)

self.kiwoom.OnEventConnect.connect(self.event_connect)

def event_connect(self, err_code):
if err_code == 0:
self.text_edit.append("로그인 성공")

if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
app.exec_()

OpenAPI+는 통신 연결 상태가 변경될 때 OnEventConnect라는 이벤트가 발생.
발생된 OnEventConnect 이벤트를 처리하기 위해 MyWindow 클래스에 event_connect라는 이름의 메서드를 구현하고
이벤트와 이벤트 처리 메서드를 연결 시키면 이벤트(OnEventConnect) 발생 시 자동으로 이벤트 처리 메서드 (self.event_connect)가 호출 됨.

변수와 객체의 바인딩

event_connect 메서드는 메서드의 인자로 넘어오는 err_code 값을 확인.
QTextEdit 객체에 문자열을 추가하기 위해 append라는 메서드를 사용.
참고로 이 append는 파이썬 리스트의 append 메서드와 전혀 관련 없음.

이벤트와 이벤트 처리 메서드의 연결은 connect 메서드를 사용.

기본 정보 요청하기

TR(Transaction) - 서버로 부터 데이터를 주고받는 행위

아래 그림은 KOA Studio를 통해 확인한 OpenAPI+의 TR 목록

TR의 기능을 살펴보고 원하는 기능을 제공하는 TR을 사용하여 데이터를 서버에 요청한 후 전송된 값을 받으면 됨.

OpenAPI+ TR 목록

아래는 opt1001 TR를 사용하여 주식 종목의 기본적인 정보를 얻어오는 프로그램의 모습

기본 정보 요청 프로그램

PyQt를 사용하여 UI를 구성하는 코드 구현

사용된 PyQt 위젯 리스트

클래스 용도
QLabel 간단한 텍스트 나 이미지 출력(‘종목코드:’ 출력)
QLineEdit 간단한 사용자 입력 처리
QPushButton 버튼 생성(‘조회’ 버튼 생성)
QTextEdit 메시지 출력 (실행결과 메시지)

QLabel은 텍스트나 이미지를 출력하는 데 사용. 만약 텍스트를 출력한다면 생성자의 첫 번째 인자로 출력될 문자열을 넘겨주면 됨.
두 번째 인자에는 부모 위젯을 지정. 아래 전체 코드에서 QLableQMainWindow안에 위치하므로 부모 위젯으로 self를 지정.
label = QLabel('종목코드: ', self)

UI 구성 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QAxContainer import *

class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PyStock")
self.setGeometry(300, 300, 300, 150)

label = QLabel('종목코드: ', self)
label.move(20, 20)

self.code_edit = QLineEdit(self)
self.code_edit.move(80, 20)
self.code_edit.setText("039490")

btn1 = QPushButton("조회", self)
btn1.move(190, 20)

self.text_edit = QTextEdit(self)
self.text_edit.setGeometry(10, 60, 280, 80)
self.text_edit.setEnabled(False)

if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
app.exec_()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QAxContainer import *

class MyWindow(QMainWindow):
def __init__(self):
super().__init__()

# Kiwoom Login
self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
self.kiwoom.dynamicCall("CommConnect()")
self.kiwoom.OnEventConnect.connect(self.event_connect)

self.setWindowTitle("PyStock")
self.setGeometry(300, 300, 300, 150)

label = QLabel('종목코드: ', self)
label.move(20, 20)

self.code_edit = QLineEdit(self)
self.code_edit.move(80, 20)
self.code_edit.setText("039490")

btn1 = QPushButton("조회", self)
btn1.move(190, 20)
btn1.clicked.connect(self.btn1_clicked)

self.text_edit = QTextEdit(self)
self.text_edit.setGeometry(10, 60, 280, 80)
self.text_edit.setEnabled(False)

def event_connect(self, err_code):
if err_code == 0:
self.text_edit.append("로그인 성공")

def btn1_clicked(self):
code = self.code_edit.text()
self.text_edit.append("종목코드: " + code)

if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
app.exec_()
opt10001 TR를 통해 기본 정보를 요청하는 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QAxContainer import *

class MyWindow(QMainWindow):
def __init__(self):
super().__init__()

# Kiwoom Login
self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
self.kiwoom.dynamicCall("CommConnect()")

# OpenAPI+ Event
self.kiwoom.OnEventConnect.connect(self.event_connect)
self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)

self.setWindowTitle("PyStock")
self.setGeometry(300, 300, 300, 150)

label = QLabel('종목코드: ', self)
label.move(20, 20)

self.code_edit = QLineEdit(self)
self.code_edit.move(80, 20)
self.code_edit.setText("039490")

btn1 = QPushButton("조회", self)
btn1.move(190, 20)
btn1.clicked.connect(self.btn1_clicked)

self.text_edit = QTextEdit(self)
self.text_edit.setGeometry(10, 60, 280, 80)
self.text_edit.setEnabled(False)

def event_connect(self, err_code):
if err_code == 0:
self.text_edit.append("로그인 성공")

def btn1_clicked(self):
code = self.code_edit.text()
self.text_edit.append("종목코드: " + code)

# SetInputValue
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)

# CommRqData
self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "opt10001_req", "opt10001", 0, "0101")

def receive_trdata(self, screen_no, rqname, trcode, recordname, prev_next, data_len, err_code, msg1, msg2):
if rqname == "opt10001_req":
name = self.kiwoom.dynamicCall("CommGetData(QString, QString, QString, int, QString)", trcode, "", rqname, 0, "종목명")
volume = self.kiwoom.dynamicCall("CommGetData(QString, QString, QString, int, QString)", trcode, "", rqname, 0, "거래량")

self.text_edit.append("종목명: " + name.strip())
self.text_edit.append("거래량: " + volume.strip())

if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
app.exec_()

OpenAPI+의 TR 처리 순서

  1. SetInputValue 메서드를 사용하여 TR 입력 값을 설정.
  2. CommRqData 메서드를 사용하여 TR을 서버로 송신.
  3. 서버로부터 이벤트가 발생할 때까지 이벤트 루프를 사용하여 대기.
  4. CommGetData 메서드를 사용하여 수신 데이터를 가져옴.

C++ 코드에서는 먼저 SetInputValue 메서드를 호출하여 종목코드라는 문자열과 실제 종목 코드 값(예: 039490)을 입력.
그다음 CommRqData 메서드를 호출하여 TR를 서버에 전송

opt10001 TR C++ 샘플코드

파이썬 코드에서는 아래와 같이 구현.

1
2
3
4
5
6
7
8
9
def btn1_clicked(self):
code = self.code_edit.text()
self.text_edit.append("종목코드: " + code)

# SetInputValue
self.kiwoom.dynamicCall("SetInputValue(QString, QString)", "종목코드", code)

# CommRqData
self.kiwoom.dynamicCall("CommRqData(QString, QString, int, QString)", "opt10001_req", "opt10001", 0, "0101")

QLineEdit 위젯을 통해 입력받은 종목 코드를 'SetInputValue` 메서드의 두 번째 인자로 넘겨주는 것을 확인.

TR 구성이 완료됐다면 CommRqData 메서드를 사용하여 TR을 서버로 송신.
CommRqData의 첫 번째 인자는 사용자가 TR을 구분하기 위한 용도.
두 번째 인자는 요청하는 TR 이름으로 opt10001을 입력.
세 번째 인자는 단순 조회 TR일 때는 0을 입력.
네 번째 인자는 4자리의 화면 번호.

TR을 서버로 송신했으므로 OnReceiveTrData라는 이벤트를 처리할 메서드를 구현하고 이를 이벤트와 연결.

OnReceiveTrData 이벤트가 발생했다는 것은 서버로부터 데이터를 전달 받았음을 의미하므로 OnReceiveTrData 메서드에서 CommGetData 메서드를 호출하여 데이터를 얻음.

아래와 같이 이벤트와 이벤트 처리용 메서드를 연결

1
2
3
4
5
6
7
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
# 생략

# OpenAPI+ Event
self.kiwoom.OnReceiveTrData.connect(self.receive_trdata)

계좌 정보 얻어 오기

GetLoginInfo 메서드를 사용하면 계좌 개수, 계좌 번호, 사용자 ID, 사용자명등의 정보를 얻어올 수 있음.

계좌 정보 얻어 오기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QAxContainer import *

class MyWindow(QMainWindow):
def __init__(self):
super().__init__()

# Kiwoom Login
self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
self.kiwoom.dynamicCall("CommConnect()")

# OpenAPI+ Event
self.kiwoom.OnEventConnect.connect(self.event_connect)

self.setWindowTitle("계좌 정보")
self.setGeometry(300, 300, 300, 150)

btn1 = QPushButton("계좌 얻기", self)
btn1.move(190, 20)
btn1.clicked.connect(self.btn1_clicked)

self.text_edit = QTextEdit(self)
self.text_edit.setGeometry(10, 60, 280, 80)

def btn1_clicked(self):
account_num = self.kiwoom.dynamicCall("GetLoginInfo(QString)", ["ACCNO"])
self.text_edit.append("계좌번호: " + account_num.rstrip(';'))

def event_connect(self, err_code):
if err_code == 0:
self.text_edit.append("로그인 성공")

if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
app.exec_()

GetLoginInfo 메서드는 이벤트 방식으로 동작하는 것도 아니므로 단순히 dynamicCall 메서드를 통해 호출하면 값을 바로 얻을 수 있음.
다만 메서드의 인자가 한 개인데 실제 인자를 넘겨주는 부분에서 다음 코드와 같이 반드시 리스트 형태로 값을 넘겨줘야 함.

1
account_num = self.kiwoom.dynamicCall("GetLoginInfo(QString)", ["ACCNO"]

함수의 인자로 ACCNO를 넘겨주면 서버는 전체 계좌를 반환해주는데 각 계좌 번호에는 세미콜론이 붙어 있어 계좌 번호를 출력할 때 문자열의 rstrip 메서드를 사용하여 문자열 끝의 세미콜론을 제거.

종목 코드 및 한글명 얻어 오기

증권사 API를 사용할 때 종목 코드는 가장 기본이 되는 데이터.
따라서 종목 코드를 API를 통해서 먼저 얻어 와야 각 종목에 대한 반복적인 작업 등을 자동화시킬 수 있음.

종목 코드 목록을 얻어오는 메서드는 GetCodeListByMarket.
종목 코드로부터 한글 종목명을 얻기 위해서는 GetMasterCodeName 사용.

종목 코드 및 종목 한글명 얻어 오기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QAxContainer import *

class MyWindow(QMainWindow):
def __init__(self):
super().__init__()

self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
self.kiwoom.dynamicCall("CommConnect()")

self.setWindowTitle("종목 코드")
self.setGeometry(300, 300, 300, 150)

btn1 = QPushButton("종목코드 얻기", self)
btn1.move(190, 10)
btn1.clicked.connect(self.btn1_clicked)

self.listWidget = QListWidget(self)
self.listWidget.setGeometry(10, 10, 170, 130)

def btn1_clicked(self):
ret = self.kiwoom.dynamicCall("GetCodeListByMarket(QString)", ["0"])
kospi_code_list = ret.split(';')
kospi_code_name_list = []

for x in kospi_code_list:
name = self.kiwoom.dynamicCall("GetMasterCodeName(QString)", [x])
kospi_code_name_list.append(x + " : " + name)

self.listWidget.addItems(kospi_code_name_list)

if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
sys.exit(app.exec_())

종목 코드 및 종목 한글명을 출력하기 위해 QListWidget 위젯 사용.

GetCodeListByMarket 메서드의 리턴 값은 문자열이고 해당 문자열 내에서 각 종목은 세미콜론으로 구분.
종목 코드를 효과적으로 관리하기 위해 세미콜론을 기준으로 문자열을 분리하여 파이썬 리스트를 생성.

1
kospi_code_list = ret.split(';')
한글 종목명 얻기
1
2
3
4
kospi_code_name_list = []
for x in kospi_code_list:
name = self.kiwoom.dynamicCall("GetMasterCodeName(QString)", [x])
kospi_code_name_list.append(x + " : " + name)

종목 코드와 종목 한글명이 저장된 파이썬 리스트를 addItems 메서드의 인자로 넘겨주어 파이썬 리스트에 있는 항목들이 QListWidget에 추가되도록 해줌.

1
self.listWidget.addItems(kospi_code_name_list)
Share