친절한곳

자동 매매 로그 남기기 


다른 소스 코드 작성에 앞서 로그를 남기는 법을 살펴보도록 하겠습니다.
자동 매매는 데이터 수신 및 체결 속도 등 데이터의 움직임이 빠른 편임으로
후에 거래 검증 또는 오류 확인 등의 목적으로 로그가 필요합니다.

저희는 Python 표준 라이브러리인 logging 모듈을 사용하도록 하겠습니다.
먼저 아래와 같이 ULPTrading 폴더를 마우스 우클릭하여 New > Directory 항목을 클릭합니다.
이후 노출되는 창에서 log를 입력 후 OK 버튼을 클릭하여 ULPTrading 폴더 밑에 log 폴더를 생성합니다.



이후 아래의 소스 코드를 upperLimitPriceTrading.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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
from PyQt5.QAxContainer import *
 
import logging
import logging.handlers
from datetime import datetime
 
form_class = uic.loadUiType("upperLimitPriceTrading.ui")[0]    # ui 파일을 로드하여 form_class 생성
 
class MyWindow(QMainWindow, form_class):    # MyWindow 클래스 QMainWindow, form_class 클래스를 상속 받아 생성됨
    def __init__(self): # MyWindow 클래스의 초기화 함수(생성자)
        super().__init__()  # 부모클래스 QMainWindow 클래스의 초기화 함수(생성자)를 호출
        self.setupUi(self)  # ui 파일 화면 출력
 
        global logger   # 전체에서 로그를 사용하기 위해 global로 선언을 해줌(전역 변수)
        logger = logging.getLogger('upperLimitPriceTradingLogger')  # 로그 인스턴스를 만든다
        self.set_logger()   # 로그 인스턴스 환경 설정을 셋팅함
 
        self.kiwoom = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")  # 키움증권 Open API+의 ProgID를 사용하여 생성된 QAxWidget을 kiwoom 변수에 할당
 
        self.btnStart.setDisabled(True)     # 거래시작 버튼을 비활성화 상태로 변경
        self.btnStop.setDisabled(True)      # 거래중지 버튼을 비활성화 상태로 변경
        self.btnReStart.setDisabled(True)   # 재시작 버튼을 비활성화 상태로 변경
        self.lePrice.setDisabled(True)      # 투입금 입력란을 비활성화 상태로 변경
        self.cbAcctNo.setDisabled(True)     # 계좌번호 선택란을 비활성화 상태로 변경
        self.leD2Price.setDisabled(True)    # D2예수금 입력란을 비활성화 상태로 변경
        self.cbCdtNm.setDisabled(True)      # 조건검색명 선택란을 비활성화 상태로 변경
        self.leTotalPrice.setDisabled(True) # 총투입금 입력란을 비활성화 상태로 변경
        self.leProfits.setDisabled(True)    # 수익금 입력란을 비활성화 상태로 변경
        self.leProfitsPc.setDisabled(True)  # 수익률 입력란을 비활성화 상태로 변경
        self.pteBuyLog.setDisabled(True)    # 매도 종목 노출란을 비활성화 상태로 변경
        self.pteSellLog.setDisabled(True)   # 매수 종목 노출란을 비활성화 상태로 변경
        self.pteLog.setDisabled(True)       # 전체 내역 노출란을 비활성화 상태로 변경
 
        self.btnLogin.clicked.connect(self.btn_login)   # ui 파일을 생성할때 작성한 로그인 버튼의 objectName 으로 클릭 이벤트가 발생할 경우 btn_login 함수를 호출
        self.kiwoom.OnEventConnect.connect(self.event_connect)  # 키움 서버 접속 관련 이벤트가 발생할 경우 event_connect 함수 호출
 
        self.kiwoom.OnReceiveConditionVer.connect(self.receive_condition_var)  # 키움 사용자 조건검색식 수신 관련 이벤트가 발생할 경우 receive_condition_var 함수 호출
 
    def set_logger(self):   # 로그 환경을 설정해주는 함수
        fomatter = logging.Formatter('[%(levelname)s|%(lineno)s] %(asctime)s > %(message)s')  # 로그를 남길 방식으로 "[로그레벨|라인번호] 날짜 시간,밀리초 > 메시지" 형식의 포매터를 만든다
 
        logday = datetime.today().strftime("%Y%m%d")  # 로그 파일 네임에 들어갈 날짜를 만듬 (YYYYmmdd 형태)
 
        fileMaxByte = 1024 * 1024 * 100  # 파일 최대 용량인 100MB를 변수에 할당 (100MB, 102,400KB)
        fileHandler = logging.handlers.RotatingFileHandler('./log/stock_' + str(logday) + '.log', maxBytes=fileMaxByte, backupCount=10# 파일에 로그를 출력하는 핸들러 (100MB가 넘으면 최대 10개까지 신규 생성)
        streamHandler = logging.StreamHandler() # 콘솔에 로그를 출력하는 핸들러
 
        fileHandler.setFormatter(fomatter)  # 파일에 로그를 출력하는 핸들러에 포매터를 지정
        streamHandler.setFormatter(fomatter)    # 콘솔에 로그를 출력하는 핸들러에 포매터를 지정
 
        logger.addHandler(fileHandler)  # 로그 인스턴스에 파일에 로그를 출력하는 핸들러를 추가
        logger.addHandler(streamHandler)    # 로그 인스턴스에 콘솔에 로그를 출력하는 핸들러를 추가
 
        logger.setLevel(logging.DEBUG)  # 로그 레벨을 디버그로 만듬
 
    def btn_login(self): # Login 버튼 클릭 시 실행되는 함수
        logger.debug("Login 버튼 클릭"# debug 레벨 로그를 남김
        ret = self.kiwoom.dynamicCall("CommConnect()")  # 키움 로그인 윈도우를 실행
 
    def event_connect(self, err_code):  # 키움 서버 접속 관련 이벤트가 발생할 경우 실행되는 함수
        if err_code == 0:   # err_code가 0이면 로그인 성공 그외 실패
            logger.info("로그인 성공")   # info 레벨 로그를 남김
            self.pteLog.appendPlainText("로그인 성공")    # ui 파일을 생성할때 작성한 plainTextEdit의 objectName 으로 해당 plainTextEdit에 텍스트를 추가함
 
            account_num = self.kiwoom.dynamicCall("GetLoginInfo(QString)", ["ACCNO"])   # 키움 dynamicCall 함수를 통해 GetLoginInfo 함수를 호출하여 계좌번호를 가져옴
            logger.debug("계좌번호: " + account_num.rstrip(';'))  # debug 레벨 로그를 남김
            self.pteLog.appendPlainText("계좌번호: " + account_num.rstrip(';')) # 키움은 전체 계좌를 반환하며 각 계좌 번호 끝에 세미콜론(;)이 붙어 있음으로 제거하여 plainTextEdit에 텍스트를 추가함
 
            self.cbAcctNo.addItem(account_num.rstrip(';'))  # 계좌번호 선택 박스에 계좌번호 셋팅
 
            self.btnStart.setDisabled(False)  # 거래시작 버튼을 활성화 상태로 변경
            self.lePrice.setDisabled(False)  # 투입금 입력란을 활성화 상태로 변경
            self.cbAcctNo.setDisabled(False)  # 계좌번호 선택란을 활성화 상태로 변경
            self.cbCdtNm.setDisabled(False)  # 조건검색명 선택란을 활성화 상태로 변경
            self.pteBuyLog.setDisabled(False)  # 매도 종목 노출란을 활성화 상태로 변경
            self.pteSellLog.setDisabled(False)  # 매수 종목 노출란을 활성화 상태로 변경
            self.pteLog.setDisabled(False)  # 전체 내역 노출란을 활성화 상태로 변경
 
            self.btnLogin.setDisabled(True)  # 로그인 버튼을 비활성화 상태로 변경
 
            self.leTotalPrice.setDisabled(True)  # 총투입금 입력란을 비활성화 상태로 변경
            self.leProfits.setDisabled(True)  # 수익금 입력란을 비활성화 상태로 변경
            self.leProfitsPc.setDisabled(True)  # 수익률 입력란을 비활성화 상태로 변경
 
            self.leTotalPrice.setText('0')  # 총투입금을 0원으로 셋팅
            self.leProfits.setText('0'# 수익금을 0원으로 셋팅
            self.leProfitsPc.setText('0%')   # 수익률을 0%으로 셋팅
 
            self.kiwoom.dynamicCall('GetConditionLoad()')   # 키움 서버에 사용자 조건식 목록을 요청
 
            QMessageBox.about(self, "message""계좌 비밀번호 입력 및 환경 설정을 진행해주세요.")
        else:
            logger.info("로그인 실패")   # info 레벨 로그를 남김
            self.pteLog.appendPlainText("로그인 실패")    # ui 파일을 생성할때 작성한 plainTextEdit의 objectName 으로 해당 plainTextEdit에 텍스트를 추가함
 
    def receive_condition_var(self, bRet, sMsg):    # 사용자 조건검색식 수신 함수
        logger.debug("receive_condition_var bRet: "+str(bRet))   # debug 레벨 로그를 남김
        logger.debug("receive_condition_var sMsg: "+str(sMsg))   # debug 레벨 로그를 남김
 
        conditionNameList = self.kiwoom.dynamicCall('GetConditionNameList()'# 수신된 사용자 조건검색식 리스트를 받아옴 (ex. 인덱스^조건명;)
        conditionNameListArray = conditionNameList.rstrip(';').split(';')  # 조건검색식 리스트에 마지막 ";" 기호를 삭제하고 ";" 기호 기준 분리
 
        logger.info("conditionNameListArray:" + str(conditionNameListArray))   # info 레벨 로그를 남김
 
        for i in range(0len(conditionNameListArray)): # 조건검색식 개수만큼 반복
            self.cbCdtNm.addItem(conditionNameListArray[i])  # 콤보 박스에 조건 셋팅
 
        index = self.cbCdtNm.findText('191^ULPTrading')   # 조건검색식 명으로 순번을 찾음
        if index >= 0:  # 해당 순번이 있다면 (해당 조건검색식 명이 있다면)
            self.cbCdtNm.setCurrentIndex(index) # 해당 순번을 선택 (해당 조검검색식을 선택)
 
# py 파일 실행시 제일 먼저 동작
if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = MyWindow()  # MyWindow 클래스를 생성하여 myWondow 변수에 할당
    myWindow.show()  # MyWindow 클래스를 노출
    app.exec_()  # 메인 이벤트 루프에 진입 후 프로그램이 종료될 때까지 무한 루프 상태 대기


기존 소스코드에 로그 설정 및 로그를 남기는 부분이 추가되었습니다.
먼저 아래와 같이 logging 모듈을 사용하기 위해 logging 모듈 및 datetime 모듈을 import 하였습니다.
(datetime 모듈은 날짜 및 시간에 사용)

1
2
3
import logging
import logging.handlers
from datetime import datetime


그리고 아래와 같이 init 초기화 함수에서 logger 인스턴스를 생성 및 환경 설정 함수를 호출 합니다.

1
2
3
        global logger   # 전체에서 로그를 사용하기 위해 global로 선언을 해줌(전역 변수)
        logger = logging.getLogger('upperLimitPriceTradingLogger')  # 로그 인스턴스를 만든다
        self.set_logger()   # 로그 인스턴스 환경 설정을 셋팅함


아래는 init 초기화 함수에서 호출한 환경 설정 함수인 set_logger 함수입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    def set_logger(self):   # 로그 환경을 설정해주는 함수
        fomatter = logging.Formatter('[%(levelname)s|%(lineno)s] %(asctime)s > %(message)s')  # 로그를 남길 방식으로 "[로그레벨|라인번호] 날짜 시간,밀리초 > 메시지" 형식의 포매터를 만든다
 
        logday = datetime.today().strftime("%Y%m%d")  # 로그 파일 네임에 들어갈 날짜를 만듬 (YYYYmmdd 형태)
 
        fileMaxByte = 1024 * 1024 * 100  # 파일 최대 용량인 100MB를 변수에 할당 (100MB, 102,400KB)
        fileHandler = logging.handlers.RotatingFileHandler('./log/stock_' + str(logday) + '.log', maxBytes=fileMaxByte, backupCount=10# 파일에 로그를 출력하는 핸들러 (100MB가 넘으면 최대 10개까지 신규 생성)
        streamHandler = logging.StreamHandler() # 콘솔에 로그를 출력하는 핸들러
 
        fileHandler.setFormatter(fomatter)  # 파일에 로그를 출력하는 핸들러에 포매터를 지정
        streamHandler.setFormatter(fomatter)    # 콘솔에 로그를 출력하는 핸들러에 포매터를 지정
 
        logger.addHandler(fileHandler)  # 로그 인스턴스에 파일에 로그를 출력하는 핸들러를 추가
        logger.addHandler(streamHandler)    # 로그 인스턴스에 콘솔에 로그를 출력하는 핸들러를 추가
 
        logger.setLevel(logging.DEBUG)  # 로그 레벨을 디버그로 만듬


주석에도 적혀있지만 자세히 살펴보면 먼저 logging.Formatter를 호출하여 로그를 남길 방식을 설정합니다.
[로그레벨|라인번호] 날짜 시간,밀리초 > 메시지 형식으로 로그를 남길 방식을 설정하였습니다.
(ex. [DEBUG|60] 2019-04-07 14:28:28,990 > Login 버튼 클릭)

1
fomatter = logging.Formatter('[%(levelname)s|%(lineno)s] %(asctime)s > %(message)s')  # 로그를 남길 방식으로 "[로그레벨|라인번호] 날짜 시간,밀리초 > 메시지" 형식의 포매터를 만든다


이후 datetime 모듈을 사용하여 YYYYmmdd 형태의 로그 파일명을 생성해줍니다.

1
logday = datetime.today().strftime("%Y%m%d")  # 로그 파일 네임에 들어갈 날짜를 만듬 (YYYYmmdd 형태)


그리고 로그파일이 일정 크기 이상이 될 경우 자동으로 파일을 새로 생성하여 로그를 남기도록 합니다.
저는 100MB(fileMaxByte) 크기 이상이 될 경우 최대 10개(backupCount) 까지 생성하도록 하였습니다.

해당 설정을 할 경우 지정된 크기 이상이 될 경우 filename.log.1, filename.log.2 와 같은 방식으로 저장되게 됩니다. 

1
2
        fileMaxByte = 1024 * 1024 * 100  # 파일 최대 용량인 100MB를 변수에 할당 (100MB, 102,400KB)
        fileHandler = logging.handlers.RotatingFileHandler('./log/stock_' + str(logday) + '.log', maxBytes=fileMaxByte, backupCount=10# 파일에 로그를 출력하는 핸들러 (100MB가 넘으면 최대 10개까지 신규 생성)


다음으로 콘솔에 로그를 출력하는 핸들러를 가져옵니다.

1
streamHandler = logging.StreamHandler() # 콘솔에 로그를 출력하는 핸들러


그리고 파일에 로그를 출력하는 핸들러와 콘솔에 로그를 출력하는 핸들러에
로그를 출력하는 형식인 포메터를 셋팅해줍니다.

1
2
        fileHandler.setFormatter(fomatter)  # 파일에 로그를 출력하는 핸들러에 포매터를 지정
        streamHandler.setFormatter(fomatter)    # 콘솔에 로그를 출력하는 핸들러에 포매터를 지정


다음으로 로그 인스턴스에 해당 파일에 로그를 출력하는 핸들러와
콘솔에 로그를 출력하는 핸들러를 셋팅해줍니다.

1
2
        logger.addHandler(fileHandler)  # 로그 인스턴스에 파일에 로그를 출력하는 핸들러를 추가
        logger.addHandler(streamHandler)    # 로그 인스턴스에 콘솔에 로그를 출력하는 핸들러를 추가


그리고 다음으로 로그 레벨을 지정해줍니다.
개발 단계에서는 DEBUG로 전체의 로그를 남기도록 하며, 차후에 전체의 로그가 필요없다면
INFO로 변경하여 디버그 로그를 제외한 나머지 로그만 남기셔도 됩니다.

1
logger.setLevel(logging.DEBUG)  # 로그 레벨을 디버그로 만듬


그럼 이제 로그를 작성하기 위한 기초 설정을 완료하였습니다.
logger 인스턴스를 전역변수로 설정하였기 때문에
(어디서나 쓸수 있도록 설정하였기 때문에)
로그를 남기고 싶은 부분에 아래와 같이 debug 또는 info로 추가를 하면
파일과 콘솔에 자동으로 로그가 작성됩니다.

1
2
logger.debug("Login 버튼 클릭"# debug 레벨 로그를 남김
logger.info("로그인 성공")   # info 레벨 로그를 남김


수정된 소스 코드가 적용된 upperLimitPriceTrading.py 파일을  Ctrl + Shift + F10 을 눌러 실행하거나
upperLimitPriceTrading.py 파일을 우클릭 후 Run 'upperLimitPriceTrading' 버튼을 클릭하여 실행하면
아래와 같이 log 폴더 밑에 로그 파일이 생기며 콘솔에도 로그가 남는 것을 확인할 수 있습니다.



이상으로 거래를 분석을 위해

로그를 작성하는 방법을 먼저 알아보았습니다.

다음 챕터에서는 거래시작 버튼을 클릭했을 때의 로직을 진행하도록 하겠습니다.



주식 자동매매, 주식 어플, 주식 정보 웹 스크래이핑, 공시 정보 연동 등

주식에 관심이 있는 사람들에게 도움이 될 정보를 기본부터 차근차근 정리할 예정입니다.

해당 글을 보고 지적 사항, 수정 사항이 있다면 언제든지 댓글, 또는 메일로 연락 부탁드립니다.

여러분의 관심이 글을 보는 다음 누군가에겐 큰 도움이 될 것입니다. 감사합니다.