python trading system

개발 1일차

개발 2일차

개발 3일차

개발 4일차

요구 사항

  1. Anaconda - Python 3.5 32 bit
  2. Pycharm IDE는 항상 관리자 권한으로 실행.
  3. 키움증권 계좌 개설
  4. 키움증권 OpenAPI+ 신청
  5. OpenAPI+ 모듈
  6. Microsoft Visual C++ 2010 SP1 재배포 가능 패키지(x86)

사전 작업

  1. Anaconda 32bit 설치
  2. Python 3.5 사용 환경 만들기 - conda create -n [envname] python=3.5 anaconda
  3. 번개 HTS 설치.
  4. 키움 Open API+ 설치.
  5. powershell 설정 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

Get-ExecutionPolicy -List # 전체 권한 확인
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine

Scope에서 CurrentUser가 RemoteSigned로 되어 있어야 정상적으로 작동
For more information, see Set-ExecutionPolicy.

powershell 권한 설정

개발 1일차

자동 버전처리 스크립트

OpenAPI+를 이용해 개발한 프로그램에서 로그인을 시도할 때 버전 처리가 필요하면 버전 처리 메시지 창이 아래와 같이 나타남.

버전 처리 메시지 창

버전 처리를 가장 쉽게 할 수 있는 방법은 번개 HTS를 사용하는 것.
번개 HTS를 실행하면 자동으로 버전 처리가 완료되기 때문에 OpenAPI+를 사용해 개발한 프로그램을 실행하기 전에 먼저 번개 HTS를 실행하면 버전 처리 문제가 해결됨.

고객 ID비밀번호를 입력하는 자동화는 pywinauto 패키지를 이용하면 해결.
pywinauto 패키지는 윈도우 대화상자에 자동으로 마우스나 키보드 이벤트를 보낼 수 있음.

pip install pywinauto를 입력해 패키지를 설치

pywinquto 패키지를 이용해 코드를 작성하기 위해 PyCharm과 같은 파이썬 IDE를 관리자 권한으로 실행.

아래와 같이 코드를 작성 후 실행하면 키움 번개 로그인 창이 출력 됨.

로그인 창 출력
1
2
3
4
5
6
7
8
9
10
from pywinauto import application
from pywinauto import timings
import time
import os

app = application.Application()
app.start("C:/Kiwoom/KiwoomFlash3/bin/nkministarter.exe")

title = "번개3 Login"
dlg = timings.WaitUntilPasses(20, 0.5, lambda: app.window_(title=title))

키움번개 로그인 창

마우스와 키보드 입력 자동화

마우스와 키보드 입력 자동화
1
2
3
4
5
6
7
8
9
10
pass_ctrl = dlg.Edit2
pass_ctrl.SetFocus()
pass_ctrl.TypeKeys('xxxx') # 로그인 비밀 번호 입력

cert_ctrl = dlg.Edit3
cert_ctrl.SetFocus()
cert_ctrl.TypeKyes('yyyy') # 인증 비밀 번호 입력

btn_ctrl = dlg.Button0
btn_ctrl.Click()

위의 모든 코드를 실행하면 자동으로 로그인이 이뤄진 후 아래와 같이 키움 번개가 정상적으로 실행되는 것을 확인.
이 과정에서 업데이트가 있는 경우 업데이트 파일을 다운로드하기 때문에 자동으로 버전 처리가 완료 됨.

키움 번개3

윈도우에서는 taskkill 명령을 이용해 특정 프로그램을 종료할 수 있음.
파이썬에서 윈도우 명령을 실행하려면 os 모듈의 system 함수를 사용하면 됨.
참고로 로그인 후에 업데이트를 수행하는 과정에 시간이 소용되기 때문에 time.sleep 함수를 호출해 약 50초 정도 대기.

50초 후 키움 종료
1
2
time.sleep(50)
os.system("taskkill /im nkmini.exe")
전체코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pywinauto import application
from pywinauto import timings
import time
import os

app = application.Application()
app.start("C:/Kiwoom/KiwoomFlash2/khministarter.exe")

title = "번개 Login"
dlg = timings.WaitUntilPasses(20, 0.5, lambda: app.window_(title=title))

pass_ctrl = dlg.Edit2
pass_ctrl.SetFocus()
pass_ctrl.TypeKeys('xxxx')

cert_ctrl = dlg.Edit3
cert_ctrl.SetFocus()
cert_ctrl.TypeKeys('yyyy')

btn_ctrl = dlg.Button0
btn_ctrl.Click()

time.sleep(50)
os.system("taskkill /im khmini.exe")

만약 PyCharm을 관리자 권한으로 실행하기 않을 경우 아래와 같은 에러가 발생.

관리자 권한으로 실행하지 않은 경우 발생하는 에러

윈도우 대화상자의 이름과 각 컨트롤의 이름을 알아내가 위해 SWAPY 라는 실행 프로그램을 사용.

SWAPY 프로그램

윈도우 작업 스케쥴러

윈도우의 작업 스케쥴러를 이용해 정해진 시간에 파이썬 스크립트 자동 실행.

작업 스케쥴러 실행

새 작업 만들기

[일반] 탭 설정

트리거 설정(1)

트리거 설정(2)

새 동작 만들기

python.exe 대신 pythonw.exe를 선택한 것은 스크립트 실행 시 콘솔

조건 항목 설정

PyTrader 구현

Qt Designer를 이용해 메인 윈도우를 만듬.

PyQt를 이용한 GUI 프로그래밍 참조.

프로그램에 사용할 무료 아이콘 MyIconFinderFlaticon

PyTrader에서 UI는 Qt Designer를 통해 생성한 pytrader.ui 파일을 불러와서 사용.
키움 OpenAPI+와 관련된 코드는 Kiwoom 클래스를 사용.

Kiwoom.py
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import sys
from PyQt5.QtWidgets import *
from PyQt5.QAxContainer import *
from PyQt5.QtCore import *
import time
import pandas as pd
import sqlite3

TR_REQ_TIME_INTERVAL = 0.2

class Kiwoom(QAxWidget):
def __init__(self):
super().__init__()
self._create_kiwoom_instance()
self._set_signal_slots()

def _create_kiwoom_instance(self):
self.setControl("KHOPENAPI.KHOpenAPICtrl.1")

def _set_signal_slots(self):
self.OnEventConnect.connect(self._event_connect)
self.OnReceiveTrData.connect(self._receive_tr_data)

def comm_connect(self):
self.dynamicCall("CommConnect()")
self.login_event_loop = QEventLoop()
self.login_event_loop.exec_()

def _event_connect(self, err_code):
if err_code == 0:
print("connected")
else:
print("disconnected")

self.login_event_loop.exit()

def get_code_list_by_market(self, market):
code_list = self.dynamicCall("GetCodeListByMarket(QString)", market)
code_list = code_list.split(';')
return code_list[:-1]

def get_master_code_name(self, code):
code_name = self.dynamicCall("GetMasterCodeName(QString)", code)
return code_name

def get_connect_state(self):
ret = self.dynamicCall("GetConnectState()")
return ret

def set_input_value(self, id, value):
self.dynamicCall("SetInputValue(QString, QString)", id, value)

def comm_rq_data(self, rqname, trcode, next, screen_no):
self.dynamicCall("CommRqData(QString, QString, int, QString)", rqname, trcode, next, screen_no)
self.tr_event_loop = QEventLoop()
self.tr_event_loop.exec_()

def _comm_get_data(self, code, real_type, field_name, index, item_name):
ret = self.dynamicCall("CommGetData(QString, QString, QString, int, QString)", code,
real_type, field_name, index, item_name)
return ret.strip()

def _get_repeat_cnt(self, trcode, rqname):
ret = self.dynamicCall("GetRepeatCnt(QString, QString)", trcode, rqname)
return ret

def _receive_tr_data(self, screen_no, rqname, trcode, record_name, next, unused1, unused2, unused3, unused4):
if next == '2':
self.remained_data = True
else:
self.remained_data = False

if rqname == "opt10081_req":
self._opt10081(rqname, trcode)

try:
self.tr_event_loop.exit()
except AttributeError:
pass

def _opt10081(self, rqname, trcode):
data_cnt = self._get_repeat_cnt(trcode, rqname)

for i in range(data_cnt):
date = self._comm_get_data(trcode, "", rqname, i, "일자")
open = self._comm_get_data(trcode, "", rqname, i, "시가")
high = self._comm_get_data(trcode, "", rqname, i, "고가")
low = self._comm_get_data(trcode, "", rqname, i, "저가")
close = self._comm_get_data(trcode, "", rqname, i, "현재가")
volume = self._comm_get_data(trcode, "", rqname, i, "거래량")

self.ohlcv['date'].append(date)
self.ohlcv['open'].append(int(open))
self.ohlcv['high'].append(int(high))
self.ohlcv['low'].append(int(low))
self.ohlcv['close'].append(int(close))
self.ohlcv['volume'].append(int(volume))

pytrader.py 파일에 모듈을 임포트하고 ui 파일을 불러오는 코드를 구현.

pytrader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import uic
from Kiwoom import *

form_class = uic.loadUiType("pytrader.ui")[0]

class MyWindow(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)

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

PyTrader 프로그램이 실행될 때 키움 로그인이 진행되도록 해주기 위해 생성자에서 키움 객체를 생성한 후 Comm_Connect 메서드를 호출.

call comm_connect pytrader.py
1
2
3
4
5
6
7
class MyWindow(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)

self.kiwoom = Kiwoom()
self.kiwoom.comm_connect()

StatusBar 위젯에 서버 연결 상태 및 현재 시간을 출력하는 기능 구현.
서버 연결 상태는 Kiwoom class에 추가한 get_connect_state 메서드를 사용.

현재 시간을 출력하는 기능은 일정한 단위로 현재 시간을 얻어온 후 이를 StatusBar에 출력.
이를 위해서는 주기적으로 이벤트를 발생시키는 Timer가 필요.
예를 들어, Timer가 1초에 한 번 이벤트(시그널)를 발생시키면 이 이벤트를 처리하는 메서드(슬롯)에서 현재 시간을 얻어온 후 이를 StatusBar에 출력

Qt의 QTimer 클래스를 사용하면 정해진 시간마다 이벤트를 발생시킬 수 있음.

1
2
3
self.timer = QTimer(self)
self.timer.start(1000)
self.timer.timeout.connect(self.timout)

QTimer 클래스는 start 메서드를 제공, 이 메서드에 인자로 1000을 지정하면 1초에 한 번씩 주기적으로 timeout 시그널이 발생.
따라서 timeout 시그널이 발생할 때 이를 처리할 슬롯으로 self.timeout을 설정하면 됨.

timout class pytrader.py
1
2
3
4
5
6
7
8
9
10
11
12
def timeout(self):
current_time = QTime.currentTime()
text_time = current_time.toString("hh:mm:ss")
time_msg = "현재시간: " + text_time

state = self.kiwoom.GetConnectState()
if state == 1:
state_msg = "서버 연결 중"
else:
state_msg = "서버 미 연결 중"

self.statusbar.showMessage(state_msg + " | " + time_msg)
전체 pytrader.py 코드
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.QtCore import *
from PyQt5 import uic
from Kiwoom import *

form_class = uic.loadUiType("pytrader.ui")[0]

class MyWindow(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)

self.kiwoom = Kiwoom()
self.kiwoom.comm_connect()

self.timer = QTimer(self)
self.timer.start(1000)
self.timer.timeout.connect(self.timeout)

def timeout(self):
current_time = QTime.currentTime()
text_time = current_time.toString("hh:mm:ss")
time_msg = "현재시간: " + text_time

state = self.kiwoom.get_connect_state()
if state == 1:
state_msg = "서버 연결 중"
else:
state_msg = "서버 미 연결 중"

self.statusbar.showMessage(state_msg + " | " + time_msg)

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

키움 OpenAPI+ 자동 로그인

자동화를 위해 사용자 계정 컨트롤 기능 비활성화

사용자 계정 컨트롤 설정

키움 증권 로그인 과정 자동화.
키움증권은 사용자의 편의를 위해 자동 로그인 기능을 지원.
자동 로그인 설정을 위해 pytrader.py 파일을 실행시켜 로그인을 진행.
정상적으로 로그인되면 아래와 같이 윈도우 오른쪽 아래의 아이콘 중 키움증권 아이콘에서 마우스 오른쪽 버튼을 클릭한 후 계좌비밀번호 저장을 선택.

카움 OpenAPI+ 자동 로그인 설정

자동 로그인 설정은 계좌 단위로 가능한데, 계좌를 선택한 후 등록 부분에 계좌 비밀번호를 입력하고 등록 버튼을 눌러 등록.
비밀번호가 잘 등록되면 AUTO라는 체크박스를 체크.

자동 로그인 설정

설정을 모두 완료했다면 실행시킨 PyTrader 프로그램을 종료.
그리고 다시 pytrader.py 파일을 실행해 보면 로그인 창이 화면에 나타나지 않고 바로 로그인이 수행된 후 PyTrader 프로그램이 실행 됨.

정리

  1. 윈도우 작업 스케쥴러를 이용해 매일 08:00에 키움 번개 자동 로그인 스크립트(login.py)를 실행.
  2. 해당 스크립트가 실행되면 키움 번개에 로그인되면 버전 처리 완료.
  3. 키움 번개 HTS 자동 종료.
  4. 08:15 윈도우 작업 스케쥴러를 이용해 pytrader.py 파일을 실행.
  5. 이때 위 그림 같이 자동 로그인 설정해 둠.
  6. 아래 그림과 같은 순서로 프로그램이 실행되게 해두면 버전 처리 및 로그인 자동화.

버전 처리 및 로그인 실행 시나리오

PyTrader 개발과 관련해서 키움 OpenAPI+ 관련 코드는 모두 Kiwoom.py에 구현.
UI는 Qt Designer를 사용해 pytrader.ui 파일에 구현.
pytrader.py에는 이벤트 처리하는 코드만 구현.

에러사항

KOA Studio에서 모의투자 체크 박스가 정상적으로 안되는 경우 발생.
일단 모의 투자로 접속하고 계좌비밀번호 저장에서 AUTO 체크박스 해제로 일반 투자로 접속 가능.
모의투자는 최대 3개월만 사용가능하여 기간 종료 시에는 모의 투자로 접속이 안되어 다시 모의 투자 가입하여야 함.

개발 2일차

주문 기능 추가.

1) UI 구성

QGroupBox 위젯을 MainWindow 안으로 배치.

Group Box 위젯

Label을 선택. LabelPyQt의QLabel` 클래스에 해당.

QLabel 위젯

Label 배치

Combo Box를 선택. Combo BoxQComboBox 클래스를 의미

Combo Box

세 개의 Combo BoxMain Window에 배치.

QComboBox

Combo Box 위젯을 더블클릭하면 아래와 같이 세부 항목을 추가 할 수 있는 창이 나타남.

주문 세부 항목 추가

종류 세부 항목 추가

수동주문 관련 UI 테스트

Line Edit를 선택한 후 배치. Line EditQLineEdit 클래스에 해당.

Line Edit 위젯 추가

Edit Styly Sheet

Spin Box를 선택한 후 수량과 가격 옆으로 배치.
Push Button을 선택한 후 맨 아래쪽에 배치.

Spin Box와 Push Button

QSpinBix

2) Kiwoom.py 파일 업데이트

주문과 관련된 API를 처리하는 메서드를 구현.
SendOrder 메서드를 사용하면 주식 주문에 대한 정보를 서버로 전송.

주문이 체결되면 증권사 서버는 아래와 같이 OnReceiveChejanData라는 이벤트를 발생.

앞서 OnReceiveTrData 메서드 내에서 CommGetData 메서드를 호출해 데이터를 얻어왔던 것과 동일하게 체결과 관련해서는 OnReceiveChejanData라는 메서드 내에서 GetChejanData라는 메서드를 호출해서 체결잔고 데이터를 얻어 옴.

Kiwoom 클래스에 send_order 메서드를 추가

1
2
3
def send_order(self, rqname, screen_no, acc_no, order_type, code, quantity, price, hoga, order_no):
self.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
[rqname, screen_no, acc_no, order_type, code, quantity, price, hoga, order_no])

체결잔고 데이터를 가져오는 메서드인 GetChejanData를 사용하는 get_chejan_data 메서드를 Kiwoom 클래스에 추가.

1
2
3
def get_chejan_data(self, fid):
ret = self.dynamicCall("GetChejanData(int)", fid)
return ret

주문체결 시점에서 키움증권 서버가 발생시키는 OnReceiveChejanData 이벤트를 처리하는 메서드를 구현.
먼저 _set_signal_slots 메서드에 시그널과 슬롯을 연결하는 코드 추가.

1
self.OnReceiveChejanData.connect(self._receive_chejan_data)

OnReceiveChejanData 이벤트가 발생할 때 호출되는 _receive_chejan_data는 다음과 같이 구현.

1
2
3
4
5
6
def _receive_chejan_data(self, gubun, item_cnt, fid_list):
print(gubun)
print(self.get_chejan_data(9203))
print(self.get_chejan_data(302))
print(self.get_chejan_data(900))
print(self.get_chejan_data(901))

get_chejan_data 메서드는 함수 인자인 FID 값을 통해 서로 다른 데이터를 얻을 수 있음.
더 자세한 FID 정보는 개발 가이드 8.19절을 참조.

FID 설명
9203 주문번호
302 종목명
900 주문수량
901 주문가격
902 미체결수량
904 원주문번호
905 주문구분
908 주문/체결시간
909 체결번호
911 체결량
10 현재가, 체결가, 실시간 종가

OpenAPI+에서 계좌 정보 및 로그인 사용자 정보를 얻어오는 메서드는 GetLoginInfo.
다음과 같이 dynamicCall 메서드로 GetLoginInfo 메서드를 호출하는 get_login_info 메서드를 Kiwoom 클래스에 추가.

1
2
3
def get_login_info(self, tag):
ret = self.dynamicCall("GetLoginInfo(QString)", tag)
return ret

3) pytrader.py 파일 업데이트

가장 먼저 추가할 기능은 QLinkEdit 위젯에 사용자가 코드명을 입력하면 해당 코드에 대한 종목명을 출력하는 기능.
pytrader.py 파일에 위젯에 대한 코드를 구현할 때 가장 먼저 할 일은 위젯의 이름을 파악하는 것.

objectName 확인

사용자가 lineEdit 객체에 종목 코드를 입력하면 PyTrader는 사용자가 입력한 종목 코드를 읽은 후 키움증권의 OpenAPI+를 사용하여 종목명을 알아내야 함.
이를 위해 먼저 lineEdit 객체가 변경될 때 호출되는 슬롯을 지정.

1
self.lineEdit.textChanged.connect(self.code_changed)

위에서 시그널 슬롯을 설정했기 때문에 lineEdit 객체로부터 textChanged라는 이벤트가 발생하면 MyWindow 클래스의 code_changed 메서드가 호출될 것임.

MyWindow 클래스에 code_changed 메서드를 다음과 같이 구현.
먼저 사용자가 입력한 종목 코드를 얻어 옴.
종목 코드에 대한 종목명은 Kiwoom 클래스에 구현된 get_master_code_name 메서드를 호출하여 알아냄.

1
2
3
4
def coe_changed(self):
code = self.lineEdit.text()
name = self.kiwoom.get_master_code_name(code)
self.lineEdit_2.ssetText(name)

계좌 정보를 QComboBox 위젯에 출력하는 코드 구현.
이를 위해 먼저 전체 계좌 개수와 계좌 번호를 키움증권으로부터 얻어 옴.
Kiwoom 클래스의 get_login_info 메서드를 사용하여 다음과 같이 필요한 데이터를 얻어 옴.

1
2
accouns_num = int(self.kiwoom.get_login_info("ACCOUNT_CNT"))
accounts = self.kiwoom.get_login_info("ACCNO")

계좌 번호를 QComboBox 위젯에 출력하려면 아래와 같이 먼저 objectName을 확인.

QComboBox objectName 확인

계좌가 여러 개인 경우 각 계좌는 ;를 통해 구분됨. 따라서 먼저 얻어온 전체 계좌에 대해 split 메서드를 호출하여 리스트로 분리한 후 슬라이시을 통해 출력할 계좌번호를 선택.
계좌를 QComboBox에 출력하기 위해 addItems 메서드를 호출.

1
2
accounts_list = accounts.split(';')[0:accouns_num]
self.comboBox.addItems(accounts_list)

마지막으로 현금주문에 대한 코드를 구현.
현금주문은 UI 창에서 현금주문 버튼을 클릭할 때 수행됨.
따라서 먼저 버튼의 이름을 확인한 후 해당 버튼에 대한 시그널과 슬롯을 연결.

1
self.pushButton.clicked.connect(self.send_order)

send_order 메서드를 구현
주의할 점은 사용자는 QComboBox를 통해 ‘신규매수’, ‘신규매도’, ‘매수취소’, '매도취소’와 같은 세부 항목을 선택하지만
실제 키움증권의 API에는 세부 항목에 대응되는 정숫값이 전달돼야 함.
마찬가지로 호가에서도 지정가일 때는 00이라는 문자열을 전달해야 하고, 시장가일 때는 03이라는 문자열을 전달해야 함.

1
2
3
4
5
6
7
8
9
10
11
12
def send_order(self):
order_type_lookup = {'신규매수': 1, '신규매도': 2, '매수취소': 3, '매도취소': 4}
hoga_lookup = {'지정가': "00", '시장가': "03"}

account = self.comboBox.currentText()
order_type = self.comboBox_2.currentText()
code = self.lineEdit.text()
hoga = self.comboBox_3.currentText()
num = self.spinBox.value()
price = self.spinBox_2.value()

self.kiwoom.send_order("send_order_req", "0101", account, order_type_lookup[order_type], code, num, price, hoga_lookup[hoga], "")

4) 매수 테스트

  1. pytrader.py 파일을 관리자 권한으로 실행.
  2. 거래할 계좌 번호를 선택한 후 주문을 신규매수로 선택.
  3. 매수하고자 하는 종목코드를 입력
  4. 종류에 시장가를 선택하고 적당한 수량을 입력한 후 현금주문 버튼을 클릭

매수 API가 정상적으로 동작했는지를 확인하는 쉬운 방법은 HTS를 사용하는 것

영웅문을 이용한 매수 종목 확인

다른 방법은 KOA Studio를 사용하는 것.
아래 그림처럼 TR 목록에서 opt10085를 선택한 후 오른쪽 계좌번오에 계좌번호를 입력한 후 조회 버튼을 클릭하면 화면 하단 출력부에 데이터가 출력.

KOA Studio opt1008을 이용한 매수 종목 확인

개발 3일차

보유 주식 현황 출력과 잔고확인 기능 구현.

영웅문의 주식종합 잔고확인 화면(보유 주식 종목 현황

영웅문의 주식종합 잔고확인 화면(잔고 현황)

1) UI 구성

보유 주식 현황과 잔고확인 UI

Table Widget 배치

Edit Items 항목 선택

칼럼 항목 추가

Table Widget을 선택한 후 아래와 같이 Property Editor에서 QTableWidget 항목에서 rowCount 값을 1로 변경.
QTableWidget 객체는 rowCount 값을 제대로 설정하지 않으면 아이템을 추가할 수 없으므로 반드시 값을 설정.

rowCount 조정

보유종목 현황을 확인하기 위한 Table Widget 추가

보유종목 현황 칼럼 추가

Check Box의 경우 Property Editor에서 text 항목을 실시간 조회로 변경하고 버튼의 text 항목을 조회로 변경.

Check Box 및 Push Button 배치

2) Kiwoom.py 파일 업데이트

KOA Studio를 참고하면 잔고 및 보유종목 현황 출력 기능에 필요한 대부분의 데이터는 opw00018이라는 TR를 통해 얻을 수 있음.

opw00018 TR 사용

예수금 정보는 opw00001 TR을 사용.

opw00001 TR 사용

먼저 OnReceiveTrData 이벤트가 발생할 때 수신 데이터를 가져오는 함수인 _opw0001를 Kiwoom 클래스에 추가.

1
2
def _opw00001(self, rqname, trcode):
self.d2_deposit = self._comm_get_data(trcode, "", rqname, 0, "d+2추정예수금")

_receive_tr_data 메서드를 구현할 때 다음 세가지 고려 사항

  1. 여러 종류의 TR을 요청해도 모두 _receive_tr_data 메서드 내에서 처리해야 함.
    따라서 rqname이라는 인자를 통해서 요청한 TR을 구분한 후 TR에 따라서 적당한 데이터를 가져오도록 코딩.
  2. 연속조회에 대한 처리.
  3. 이벤트 루프에 대한 처리.

_receive_tr_data 메서드에서 _opw00001 메서드를 호출하도록 코드 수정.

1
2
3
4
5
if rqname == "opt10081_req":
self._opt10081(rqname, trcode)

elif rqname == "opw00001_req":
self._opw00001(rqname, trcode)

d+2 추정예수금을 잘 얻어오는지 확인하기 위해 Kiwoom.py 파을이 main 코드 부분을 아래와 같이 수정.

Kiwoom.py
1
2
3
4
5
6
7
8
9
10
if __name__ == "__main__":
app = QApplication(sys.argv)
kiwoom = Kiwoom()
kiwoom.comm_connect()

kiwoom.set_input_value("계좌번호", "8087711111")
kiwoom.set_input_value("비밀번호", "0000")
kiwoom.comm_rq_data("opw00001_req", "opw00001", 0, "2000")

print(kiwoom.de_deposit)

얻어온 d+2추정예수금을 확인하면 000000499225300와 같이 문자열의 앞쪽에 0이 존재하는 것을 확인.

보통 금액은 천의 자리마다 콤마를 사용하여 표시. 이를 위해 Kiwoom 클래스에 change_format이라는 static method를 추가.
change_format 메서드는 입력된 문자열에 대해 lstrip 메서드를 통해 문자열 왼쪽에 존재하는 - 또는 0을 제거.
그리고 format 함수를 통해 천의 자리마다 콤마를 추가한 문자열로 변경.

staticmethod
1
2
3
4
5
6
7
8
9
10
11
@staticmethod
def change_format(data):
strip_data = data.lstrip('-0')
if strip_data == '':
strip_data = '0'

format_data = format(int(strip_data), ',d')
if data.startswith('-'):
format_data = '-' + format_data

return format_data

정적 메서드를 호출하려면 정적 메서드 이름 앞에 클래스 이름을 붙여줌.

1
2
3
def _opw00001(self, rqname, trcode):
d2_deposit = self._comm_get_data(trcode, "", rqnam, 0, "d+2추정예수금")
self.d2_deposit = Kiwoom.change_format(d2_deposit)

총매입금액, 총평가금액, 총평가손익금액, 총수익률, 추정예탁자산_comm_get_data 메서드를 통해 얻어 옴.
얻어온 데이터는 change_format 메서드를 통해 포맷을 문자열로 변경

1
2
3
4
5
6
7
8
9
10
11
12
def _opw00018(self, rqname, trcode):
total_purchase_price = self._comm_get_data(trcode, "", rqname, 0, "총매입금액")
total_eval_price = self._comm_get_data(trcode, "", rqname, 0, "총평가금액")
total_eval_profit_loss_price = self._comm_get_data(trcode, "", rqname, 0, "총평가손익금액")
total_earning_rate = self._comm_get_data(trcode, '', rqname, 0, "총수익률(%)")
estimated_deposit = self._comm_get_data(trcode, '', rqname, 0, "추정예탁자산")

print(Kiwoom.change_format(total_purchase_price))
print(Kiwoom.change_format(total_eval_price))
print(Kiwoom.change_format(total_eval_profit_loss_price))
print(Kiwoom.change_format(total_earning_rate))
print(Kiwoom.change_format(estimated_deposit))

_receive_tr_data 메서드에서 _opw00018 메서드를 호출하도록 코드 수정.

1
2
3
4
5
6
if rqname == "opt10081_req":
self._opt10081(rqname, trcode)
elif rqname == "opw00001_req":
self._opw00001(rqname, trcode)
elif rqname == "opw0001_req":
self._opw00018(rqname, trcode)

opw00018 TR을 통해 싱글 데이터를 잘 얻어오는지 테스트하기 위해 Kiwoom.py 파일의 __main__ 코드 부분을 수정.

1
2
3
4
5
6
7
8
9
10
if __name__ == "__main__":
app = QApplication(sys.argv)
kiwoom = Kiwoom()
kiwoom.comm_connect()

account_number = kiwoom.get_login_info("ACCNO")
account_number = account_number.split(';')[0]

kiwoom.set_input_value("계좌번호", account_number)
kiwoom.comm_rq_data("opw00018_req", "opw00018", 0, "2000")

만약 Kiwoom.py 파일을 실행했을 때 아래와 같은 에러가 발생한다면 계좌 비밀번호 등록한 후 자동 로그인 설정 해줌.

계좌 비밀번호 에러

멀티 데이터를 통해 보유 종목별 평가 잔고 데이터를 가져오기.
다음 코드를 _opw00018에 추가.
멀티 데이터는 _get_repeat_cnt 메서드를 호출하여 보유 종목의 개수를 얻어 옴.
그런 다음 해당 개수마큼 반복하면서 각 보유 종목에 대한 상세 데이터를 _comm_get_data를 통해 얻어 옴.
참고로 opw00018 TR을 사용하는 경우 한 번의 TR 요청으로 최대 20개의 보유 종목에 대한 데이터를 얻을 수 있음.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# multi data
rows = self._get_repeat_cnt(trcode, rqname)
for i in range(rows):
name = self._comm_get_data(trcode, "", rqname, i, "종목명")
quantity = self._comm_get_data(trcode, "", rqname, i, "보유수량")
purchase_price = self._comm_get_data(trcode, "", rqname, i, "매입가")
current_price = self._comm_get_data(trcode, "", rqname, i, "현재가")
eval_profit_loss_price = self._comm_get_data(trcode, "", rqname, i, "평가손익")
earning_rate = self._comm_get_data(trcode, "", rqname, i, "수익률(%)")

quantity = Kiwoom.change_format(quantity)
purchase_price = Kiwoom.change_format(purchase_price)
current_price = Kiwoom.change_format(current_price)
eval_profit_loss_price = Kiwoom.change_format(eval_profit_loss_price)
earning_rate = Kiwoom.change_format2(earning_rate)

print(name, quantity, purchase_price, current_price, eval_profit_loss_price, earning_rate)

수익률에 대한 포맷 변경은 change_format2라는 정적 메서드를 사용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@staticmethod
def change_format2(data):
strip_data = data.lstrip('-0')

if strip_data == '':
strip_data = '0'

if strip_data.startswith('.'):
strip_data = '0' + strip_data

if data.startswith('-'):
strip_data = '-' + strip_data

return strip_data

opw00018 TR을 통해 얻어온 데이터를 인스턴스 변수에 저장

Kiwoom 클래스에 다음 메서드 추가

1
2
def reset_opw00018_output(self):
self.opw00018_output = {'single': [], 'multi': []}

_opw00018 메서드는 아래와 같이 수정.
싱글 데이터는 1차원 리스트로 데이터를 저장,
멀티 데이터는 2차원 리스트로 데이터를 저장.

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
def _opw00018(self, rqname, trcode):
# single data
total_purchase_price = self._comm_get_data(trcode, "", rqname, 0, "총매입금액")
total_eval_price = self._comm_get_data(trcode, "", rqname, 0, "총평가금액")
total_eval_profit_loss_price = self._comm_get_data(trcode, "", rqname, 0, "총평가손익금액")
total_earning_rate = self._comm_get_data(trcode, "", rqname, 0, "총수익률(%)")
estimated_deposit = self._comm_get_data(trcode, "", rqname, 0, "추정예탁자산")

self.opw00018_output['single'].append(Kiwoom.change_format(total_purchase_price))
self.opw00018_output['single'].append(Kiwoom.change_format(total_eval_price))
self.opw00018_output['single'].append(Kiwoom.change_format(total_eval_profit_loss_price))
self.opw00018_output['single'].append(Kiwoom.change_format(total_earning_rate))
self.opw00018_output['single'].append(Kiwoom.change_format(estimated_deposit))

# multi data
rows = self._get_repeat_cnt(trcode, rqname)
for i in range(rows):
name = self._comm_get_data(trcode, "", rqname, i, "종목명")
quantity = self._comm_get_data(trcode, "", rqname, i, "보유수량")
purchase_price = self._comm_get_data(trcode, "", rqname, i, "매입가")
current_price = self._comm_get_data(trcode, "", rqname, i, "현재가")
eval_profit_loss_price = self._comm_get_data(trcode, "", rqname, i, "평가손익")
earning_rate = self._comm_get_data(trcode, "", rqname, i, "수익률(%)")

quantity = Kiwoom.change_format(quantity)
purchase_price = Kiwoom.change_format(purchase_price)
current_price = Kiwoom.change_format(current_price)
eval_profit_loss_price = Kiwoom.change_format(eval_profit_loss_price)
earning_rate = Kiwoom.change_format2(earning_rate)

self.opw00018_output['multi'].append([name, quantity, purchase_price, current_price, eval_profit_loss_price, earning_rate])

opw00018 TR을 사용할 때 한가지 주의 사항은 실 서버로 접속할 때와 모의투자 서버로 접속할 때에 제공되는 데이터 형식이 다름.
예를 들어 실 서버에서 수익률은 소수점 표시 없이 전달되지만, 모의투자에선 소수점을 포함해서 데이터가 전달됨.
따라서 접속 서버를 구분하여 데이터를 다르게 처리해야 할 필요가 있음.
Kiwoom 클래스에 다음 메서드를 추가

1
2
3
def get_server_gubun(self):
ret = self.dynamicCall("KOA_Functions(QString, QString)", "GetServerGubun", "")
return ret

_opw00018 메서드에서 모의투자일 때는 총수익률(%)의 값을 100으로 나눈 후 출력 되도록 다음과 같이 코드 수정.

1
2
3
4
5
6
7
total_earning_rate = Kiwoom.change_format(total_earning_rate)

if self.get_server_gubun():
total_earning_rate = float(total_earning_rate) / 100
total_earning_rate = str(total_earning_rate)

self.opw00018_output['single'].append(total_earning_rate)

3) pytrader.py 파일 업데이트

Kiwoom 클래스를 사용해 잔고 및 보유종목 현황 데이터를 요청하고 데이터를 UI에 출력하는 코드 작성.

PyTrader

call check_balance
1
2
# pushButton_2 라는 객체가 클릭될 때 check_balance라는 메서드가 호출
self.pushButton_2.clicked.connect(self.check_balance)

check_balance 메서드 구현.

check_balance method
1
2
3
4
5
6
7
8
9
10
11
12
def check_balance(self):
self.kiwoom.reset_opw00018_output()
account_number = self.kiwoom.get_login_info("ACCNO")
account_number = account_number.split(';')[0]

self.kiwoom.set_input_value("계좌번호", account_number)
self.kiwoom.comm_rq_data("opw00018_req", "opw00018", 0, "2000")

while self.kiwoom.remained_data:
time.sleep(0.2)
self.kiwoom.set_input_value("계좌번호", account_number)
self.kiwoom.comm_rq_data("opw00018_req", "opw00018", 2, "2000")

예수금 데이터를 얻기 위해 opw00001 TR을 요청하는 코드

opw00001 TR을 요청하는 코드
1
2
3
# opw00001
self.kiwoom.set_input_value("계좌번호", account_number)
self.kiwoom.comm_rq_data("opw00001_req", "opw00001", 0, "2000")

데이터가 준비됐다면 데이터를 QTableWidget 객체에 출력하면 됨. self.tableWidget을 통해 해당 객체에 접근 가능.

예수금 데이터를 QTableWidget에 출력하기 위해 먼저 self.kiwoom.d2_deposit에 저장된 예수금 데이터를 QTableWidgetItem 객체로 만들어 줌.
setItem 메서드를 호출해 QTableWidget 객체에 넣으면 됨.

1
2
3
4
# balance
item = QTableWidgetItem(self.kiwoom.d2_deposit)
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.tableWidget.setItem(0, 0, item)

총매입, 총평가, 총손익, 총수익률(%), 추정자산QTableWidget의 칼럼에 추가하는 코드.
데이터는 self.kiwoom.opw00018_output['single']을 통해 얻어올 수 있음.

1
2
3
4
for i in range(1, 6):
item = QTableWidgetItem(self.kiwoom.opw00018_output['single'][i - 1])
item.setTextAlignment(QT.AlignVCenter | Qt.AlignRight)
self.tableWidget.setItem(0, i, item)

resizeRowsToContents 메서드를 호출해서 아이템의 크기에 맞춰 행의 높이를 조절

1
self.tableWidget.resizeRowsToContents()

보유 종목별 평가 잔고 데이터를 추가.
먼저 보유종목의 개수를 확인한 후 행의 개수를 설정

1
2
3
# Item list
item_count = len(self.kiwoom.opw00018_output['multi])
self.tableWidget_2.setRowCount(item_count)

한 종목에 대한 종목명, 보유량, 매입가, 현재가, 평가손익, 수익률(%)은 출력

1
2
3
4
5
6
for j in range(item_count):
row = self.kiwoom.opw00018_output['multi'][j]
for i in range(len(row)):
item = QTableWidgetItem(row[i])
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.tableWidget_2.setItem(j, i, item)
check_balance method 전체코드
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
def check_balance(self):
self.kiwoom.reset_opw00018_output()
account_number = self.kiwoom.get_login_info("ACCNO")
account_number = account_number.split(';')[0]

self.kiwoom.set_input_value("계좌번호", account_number)
self.kiwoom.comm_rq_data("opw00018_req", "opw00018", 0, "2000")

while self.kiwoom.remained_data:
time.sleep(0.2)
self.kiwoom.set_input_value("계좌번호", account_number)
self.kiwoom.comm_rq_data("opw00018_req", "opw00018", 2, "2000")

# opw00001
self.kiwoom.set_input_value("계좌번호", account_number)
self.kiwoom.comm_rq_data("opw00001_req", "opw00001", 0, "2000")

# balance
item = QTableWidgetItem(self.kiwoom.d2_deposit)
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.tableWidget.setItem(0, 0, item)

for i in range(1, 6):
item = QTableWidgetItem(self.kiwoom.opw00018_output['single'][i - 1])
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.tableWidget.setItem(0, i, item)

self.tableWidget.resizeRowsToContents()

# Item list
item_count = len(self.kiwoom.opw00018_output['multi'])
self.tableWidget_2.setRowCount(item_count)

for j in range(item_count):
row = self.kiwoom.opw00018_output['multi'][j]
for i in range(len(row)):
item = QTableWidgetItem(row[i])
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.tableWidget_2.setItem(j, i, item)

self.tableWidget_2.resizeRowsToContents()

실시간 조회 체크 박스 구현.

새로운 타이머 객체를 생성

1
2
3
4
# Timer2
self.timer2 = QTimer(self)
self.timer2.start(1000*10)
self.timer2.timeout.connect(self.timeout2)

timeout2 메서드 구현.
위의 메서드는 QCheckBox가 체크 됐는지 확인한 후 데이터 갱신.
데이터 갱신 처리는 check_balance 메서드가 담당하고 있으므로 다음과 같이 구현

1
2
3
def timeout2(self):
if self.checkBox.isChecked():
self.check_balance()

개발 4일차

선정된 매수/매도 종목을 자동으로 매매하고 해당 종목에 대한 정보를 출력하는 기능 구현.

개발 4일차 실행 화면

1) UI 구성 및 매수/매도 목록 파일 생성

칼럼추가

선정된 매수/매도 종목에 대한 정보가 이미 buy_list.txt라는 파일과 sell_list.txt라는 파일로 저장돼 있다고 가정하고 프로그램을 구현.

선정된 매수 종목 정보

선정된 매도 종목 정보

2) 선정 종목 정보 출력하기

위에서 생성했던 buy_list.txtsell_list.txt 파일을 읽어서 이름 QTableWidget 객체로 출력하는 기능을 구현.
출력하는 기능은 load_buy_sell_list라는 이름의 메서드로 구현할 것임.
프로그램 시작되자마자 출력돼야 하므로 MyWindow 클래스의 생성자에서 load_buy_sell_list를 호출.

1
2
3
4
5
6
7
class MyWindow(QMainWindow, form_class):
sef __init__(self):
super().__init__()
self.setupUi(self)

# 중간 코드 생략
self.load_buy_sell_list()
load_buy_sell_list method
1
2
3
4
5
6
7
8
def load_buy_sell_list(self):
f = open("buy_list.txt", 'rt')
buy_list = f.readlines()
f.close()

f = open("sell_list.txt", 'rt')
sell_list = f.readlines()
f.close()

파일로부터 매수/매도 리스트를 읽었다면 데이터의 총 개수를 확인하여 종목 각각에 대한 데이터 개수를 확인한 후 이 두 값을 더한 값을 QTableWidget 객체의 setRowCount 메서드로 설정.

1
2
row_count = len(buy_list) + len(sell_list)
self.tablewidget_4.setRowCount(row_count)
1
2
3
4
5
6
7
8
9
10
# buy list
for j in range(len(buy_list)):
row_data = buy_list[j]
split_row_data = row_data.split(';')
split_row_data[1] = self.kiwoom.get_master_code_name(split_row_data[1].rsplit())

for i in range(len(split_row_data)):
item = QTableWidgetItem(split_row_data[i].rstrip())
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter)
self.tableWidget_4.setItem(j, i, item)
1
2
3
4
5
6
7
8
9
10
# sell list
for j in range(len(sell_list)):
row_data = sell_list[j]
split_row_data = row_data.split(';')
split_row_data[1] = self.kiwoom.get_master_code_name(split_row_data[1].rstrip())

for i in range(len(split_row_data)):
item = QTableWidgetItem(split_row_data[i].rstrip())
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter)
self.tableWidget_4.setItem(len(buy_list) + j, i, item)
load_buy_sell_list method 전체코드
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
def load_buy_sell_list(self):
f = open("buy_list.txt", 'rt')
buy_list = f.readlines()
f.close()

f = open("sell_list.txt", 'rt')
sell_list = f.readlines()
f.close()

row_count = len(buy_list) + len(sell_list)
self.tableWidget_4.setRowCount(row_count)

# buy list
for j in range(len(buy_list)):
row_data = buy_list[j]
split_row_data = row_data.split(';')
split_row_data[1] = self.kiwoom.get_master_code_name(split_row_data[1].rsplit())

for i in range(len(split_row_data)):
item = QTableWidgetItem(split_row_data[i].rstrip())
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter)
self.tableWidget_4.setItem(j, i, item)

# sell list
for j in range(len(sell_list)):
row_data = sell_list[j]
split_row_data = row_data.split(';')
split_row_data[1] = self.kiwoom.get_master_code_name(split_row_data[1].rstrip())

for i in range(len(split_row_data)):
item = QTableWidgetItem(split_row_data[i].rstrip())
item.setTextAlignment(Qt.AlignVCenter | Qt.AlignCenter)
self.tableWidget_4.setItem(len(buy_list) + j, i, item)

self.tableWidget_4.resizeRowsToContents()

3) 자동 주문 구현하기

각 거래일의 장 시작에 맞춰 정해진 주문 방식에 따라 주문을 수행하는 간단한 방식을 사용.

자동 주문하는 기능은 MyWindow 클래스trade_stocks 메서드에 구현.

1
2
3
4
5
6
7
8
9
10
def trade_stocks(self):
hoga_lookup = {'지정가': "00", '시장가': "03"}

f = open("buy_list.txt", 'rt')
buy_list = f.readlines()
f.close()

f = oepn("sell_list.txt", 'rt')
sell_list = f.readlines()
f.close()

주문할 때 필요한 계좌 정보를 QComboBox 위젯으로부터 얻어 옴.

1
account = self.comboBox.currentText()

buy_list로 부터 데이터를 하나씩 얻어온 후 문자열을 분리해서 주문에 필요한 정보(거래구분, 종목코드, 수량, 가격)를 준비.
읽어 온 데이터의 주문 수행 여부가 '매수전’인 경우에만 해당 주문 데이터를 토대로 send_order 메서드를 통해 매수 주문을 수행.

1
2
3
4
5
6
7
8
9
10
# buy list
for row_data in buy_list:
split_row_data = row_data.split(';')
hoga = split_row_data[2]
code = split_row_data[1]
num = split_row_data[3]
price = split_row_data[4]

if split_row_data[-1].rstrip() == '매수전':
self.kiwoom.send_order("send_order_req", "0101", account, 1, code, num, price, hoga_lookup[hoga], "")

매도 주문 역시 동일한 방식으로 처리

1
2
3
4
5
6
7
8
9
10
# sell list
for row_data in sell_list:
split_row_data = row_data.split(';')
hoga = split_row_data[2]
code = split_row_data[1]
num = split_row_data[3]
price = split_row_data[4]

if split_row_data[-1].rstrip() == '매도전':
self.kiwoom.send_order("send_order_req", "0101", account, 2, code, num, price, hoga_lookup[hoga], "")

매매 주문이 완료되면 buy_list.txt에 저장된 주문 여부를 업데이트.
앞서 매매 주문을 실행했기 때문에 매수전매도전주문완료로 변경.

1
2
3
4
5
6
7
8
9
# buy list
for i, row_data in enumerate(buy_list):
buy_list[i] = buy_list[i].replace("매수전", "주문완료")

# file update
f = open("buy_list.txt", 'wt')
for row_data in buy_list:
f.write(row_data)
f.close()
trade_stocks 전체코드
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
def trade_stocks(self):
hoga_lookup = {'지정가': "00", '시장가': "03"}

f = open("buy_list.txt", 'rt')
buy_list = f.readlines()
f.close()

f = open("sell_list.txt", 'rt')
sell_list = f.readlines()
f.close()

# account
account = self.comboBox.currentText()

# buy list
for row_data in buy_list:
split_row_data = row_data.split(';')
hoga = split_row_data[2]
code = split_row_data[1]
num = split_row_data[3]
price = split_row_data[4]

if split_row_data[-1].rstrip() == '매수전':
self.kiwoom.send_order("send_order_req", "0101", account, 1, code, num, price,
hoga_lookup[hoga], "")

# sell list
for row_data in sell_list:
split_row_data = row_data.split(';')
hoga = split_row_data[2]
code = split_row_data[1]
num = split_row_data[3]
price = split_row_data[4]

if split_row_data[-1].rstrip() == '매도전':
self.kiwoom.send_order("send_order_req", "0101", account, 2, code, num, price,
hoga_lookup[hoga], "")

# buy list
for i, row_data in enumerate(buy_list):
buy_list[i] = buy_list[i].replace("매수전", "주문완료")

# file update
f = open("buy_list.txt", 'wt')
for row_data in buy_list:
f.write(row_data)
f.close()

# sell list
for i, row_data in enumerate(sell_list):
sell_list[i] = sell_list[i].replace("매도전", "주문완료")

# file update
f = open("sell_list.txt", 'wt')
for row_data in sell_list:
f.write(row_data)
f.close()
timeout method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def timeout(self):
market_start_time = QTime(9, 0, 0)
current_time = QTime.currentTime()

if current_time > market_start_time and self.trade_stocks_done is False:
self.trade_stocks()
self.trade_stocks_done = True

text_time = current_time.toString("hh:mm:ss")
time_msg = "현재시간: " + text_time

state = self.kiwoom.get_connect_state()
if state == 1:
state_msg = "서버 연결 중"
else:
state_msg = "서버 미 연결 중"

self.statusbar.showMessage(state_msg + " | " + time_msg)

MyWindow 클래스의 생성자에 trade_stocks_done 속성을 추가.
참고로 trade_stocks_done은 생성자에게 Qtimer 객체를 생성하는 코드보다 먼저 위치해야 함.

1
2
3
4
5
6
class MyWindow(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)

self.trade_stocks_done = False

파이썬으로 배우는 알고리즘 트레이딩

Share