11 minute read

Qthread_3.py 구현

pytrader.py에서 자동매매 부분을 구현하는 코드이다.

선정한 종목을 추가 or 데이터베이스에서 불러와서 실제 거래를 진행한다.

Qthread_3.py - init 생성


import os



from PyQt5.QtCore import *

from kiwoom import Kiwoom

from kiwoomType import *

from PyQt5.QtWidgets import *



class Thread3(QThread) :

    def __init__(self, parent) :

        super().__init__(parent)

        self.parent = parent

        

        self.k = Kiwoom()

        

        account = self.parent.accComboBox.currentText()

        self.account_num = account

        

        self.Load_code()                # 매수 종목/금액/수량 가져오기

        self.orderitemlist_1 = []       # 중복 매수 금지

        self.orderitemlist_2 = []       # 중복 익절 금지

        self.orderitemlist_3 = []       # 중복 손절 금지

        self.orderitemlist_4 = []       # 중복 재매수 금지

        

        self.cancel_the_order = []

        

        self.realType = RealType()      # 실시간 FID 번호

        

        ### 등록된 계좌 전체 해지(작동 정지 시 등록 정보 전부 해제)

        self.k.kiwoom.dynamicCall("SetRealRemove(QString, QString)", ["ALL", "ALL"])

        ###

        

        self.screen_num = 5000

        

        for code in self.k.portfolio_stock_dict.keys() :            # 포트폴리오에 저장된 코드들을 실시간 등록

            fids = self.realType.REALTYPE['주식체결']['체결시간']     # 주식체결에 대한 모든 데이터 로드

            self.k.kiwoom.dynamicCall("SetRealReg(QString, QString, QString, QString)",

                                      self.screen_num, code, fids, "1")     # 실시간 데이터를 받아오기 위해 각 코드들을 서버에 등록

            self.screen_num += 1

            

        print("종목등록 완료")

        print(self.k.portfolio_stock_dict.keys())       #장 시작 전/후 상태 확인용 스크린 번호

        

        self.screen_start_stop_real = "300"

        self.k.kiwoom.dynamicCall("SetRealReg(QString, QString, QString, QString)",

                                  self.screen_start_stop_real, '', self.realType.REALTYPE['장시작시간']['장운영구분'], "0")     # 장의 시작인지 장 외인지등에 대한 정보 수신



        ### 실시간 슬롯

        self.k.kiwoom.OnReceiveRealData.connect(self.realdata_slot)     # 실시간 데이터를 받아오는 곳

        self.k.kiwoom.OnReceiveChejanData.connect(self.chejan_slot)     # (주문접수, 체결통보) = 0, (잔고변경) = 1, 데이터 전송

거래에 필요한 데이터 초기화 및 서버에서 데이터를 받아온다.


데이터베이스 내 종목 불러오기

거래할 종목들이 저장되어 있는 데이터베이스에서 먼저 종목들을 불러온다.


def Load_code(self) :

    if os.path.exists("dist/Selected_code.txt") :

        f = open("dist/Selected_code.txt", "r", encoding="utf8")

        lines = f.readlines()

        screen = 4000

        

        if not lines :

            msg = QMessageBox()

            msg.setIcon(QMessageBox.Warning)

            msg.setText("파일이 비어있습니다.")

            msg.setWindowTitle("경고")

            msg.exec_()

            

        for line in lines :

            if line != "" :

                ls = line.split("\t")

                t_code = ls[0]

                t_name = ls[1]

                current_price = ls[2]

                dept = ls[3]

                buy = ls[4]

                n_o_stock = ls[5]

                profit = ls[6]

                loss = ls[7].split("\n")[0]

                

                self.k.portfolio_stock_dict.update({t_code : {"종목명" : t_name}})

                self.k.portfolio_stock_dict[t_code].update({"현재가" : int(current_price.replace(',',''))})

                self.k.portfolio_stock_dict[t_code].update({"신용비율" : dept})

                self.k.portfolio_stock_dict[t_code].update({"매수가" : int(buy.replace(',',''))})

                self.k.portfolio_stock_dict[t_code].update({"매수수량" : int(n_o_stock.replace(',',''))})

                self.k.portfolio_stock_dict[t_code].update({"익절가" : int(profit.replace(',',''))})

                self.k.portfolio_stock_dict[t_code].update({"손절가" : int(loss.replace(',',''))})

                self.k.portfolio_stock_dict[t_code].update({"주문용스크린번호" : screen})

                screen += 1

        f.close()

Selecrted_code.txt 파일에 저장되어 있는 종목의 종목명, 현재가, 신용비율, 매수가 등 데이터를 불러오는 코드이다.


실시간 FID 번호 등록

본격적으로 주식 자동매매 거래를 하기 전, 서버가 증권 업무를 처리할 때 각 업무마다 부여된 FID 번호가 존재한다.

실시간 서비스에서 사용자가 서버에 데이터를 요청할 때, 업무에 맞는 FID 번호를 전달한 후 데이터를 얻어와야 모든 데이터를 사용할 수 있다.

그렇기에 각각의 업무에 맞는 FID를 파일로 저장하는 과정이 필요하고, 이는 KOAStudio를 참고하여 제작할 수 있다.

다음은 kiwoomType.py 파일의 코드이다.

class RealType(object):
    SENDTYPE = {
        '거래구분': {
            '지정가': '00',
            '시장가': '03',
            '조건부지정가': '05',
            '최유리지정가': '06',
            '최우선지정가': '07',
            '지정가IOC': '10',
            '시장가IOC': '13',
            '최유리IOC': '16',
            '지정가FOK': '20',
            '시장가FOK': '23',
            '최유리FOK': '26',
            '장전시간외종가': '61',
            '시간외단일가매매': '62',
            '장후시간외종가': '81'
        }
    }

    REALTYPE = {

        '주식체결': {
            '체결시간': 20,
            '현재가': 10, #체결가
            '전일대비': 11,
            '등락율': 12,
            '(최우선)매도호가': 27,
            '(최우선)매수호가': 28,
            '거래량': 15,
            '누적거래량': 13,
            '누적거래대금': 14,
            '시가': 16,
            '고가': 17,
            '저가': 18,
            '전일대비기호': 25,
            '전일거래량대비': 26,
            '거래대금증감': 29,
            '전일거래량대비': 30,
            '거래회전율': 31,
            '거래비용': 32,
            '체결강도': 228,
            '시가총액(억)': 311,
            '장구분': 290,
            'KO접근도': 691,
            '상한가발생시간': 567,
            '하한가발생시간': 568
        },

        '주식호가잔량': {
            '순매수잔량': 128,
            '순매도잔량': 138,
            '매수호가총잔량': 125,
            '매도호가총잔량': 121
        },

        '장시작시간': {
            '장운영구분': 215,
            '시간': 20, #(HHMMSS)
            '장시작예상잔여시간':214
        },

        '주문체결': {
            '계좌번호': 9201,
            '주문번호': 9203,
            '관리자사번': 9205,
            '종목코드': 9001,
            '주문업무분류': 912, #(jj:주식주문)
            '주문상태': 913, #(접수, 확인, 체결) (10:원주문, 11:정정주문, 12:취소주문, 20:주문확인, 21:정정확인, 22:취소확인, 90,92:주문거부) #https://bbn.kiwoom.com/bbn.openAPIQnaBbsDetail.do
            '종목명': 302,
            '주문수량': 900,
            '주문가격': 901,
            '미체결수량': 902,
            '체결누계금액': 903,
            '원주문번호': 904,
            '주문구분': 905, #(+매수, -매도, -매도정정, +매수정정, 매수취소, 매도취소)
            '매매구분': 906, #(보통, 시장가등)
            '매도수구분': 907, # 매도(매도정정, 매도취도 포함)인 경우 1, 매수(매수정정, 매수취소 포함)인 경우 2
            '주문/체결시간': 908, #(HHMMSS)
            '체결번호': 909,
            '체결가': 910,
            '체결량': 911,
            '현재가': 10,
            '(최우선)매도호가': 27,
            '(최우선)매수호가': 28,
            '단위체결가': 914,
            '단위체결량': 915,
            '당일매매수수료': 938,
            '당일매매세금': 939,
            '거부사유': 919,
            '화면번호': 920,
            '터미널번호': 921,
            '신용구분(실시간 체결용)': 922,
            '대출일(실시간 체결용)': 923,
        },

        '매도수구분': {
            '1': '매도',
            '2': '매수'
        },

        '잔고': {
            '계좌번호': 9201,
            '종목코드': 9001,
            '종목명': 302,
            '현재가': 10,
            '보유수량': 930,
            '매입단가': 931,
            '총매입가': 932,
            '주문가능수량': 933,
            '당일순매수량': 945,
            '매도매수구분': 946,
            '당일총매도손익': 950,
            '예수금': 951,
            '(최우선)매도호가': 27,
            '(최우선)매수호가': 28,
            '기준가': 307,
            '손익율': 8019
        },
    }

포트폴리오에 등록된 종목 및 장 운영 상태 데이터 수신


for code in self.k.portfolio_stock_dict.keys():  # 포트폴리오에 저장된 코드들을 실시간 등록

    fids = self.realType.REALTYPE['주식체결']['체결시간']  # '주식체결' 이벤트에 대한 데이터 필드 ID를 가져옴

    self.k.kiwoom.dynamicCall("SetRealReg(QString, QString, QString, QString)",

                              self.screen_num, code, fids, "1")  # 실시간 데이터 등록

    self.screen_num += 1

해당 코드 부분에서 포트폴리오에 저장된 종목 코드들을 가져오고, 각 종목 코드들에 대해 고유한 스크린 번호를 부여한다.

따라서 포트폴리오에 등록된 종목들에 대해 실시간 데이터를 서버에 등록한다.


주식장 시간 확인


self.screen_start_stop_real = "300"

self.k.kiwoom.dynamicCall("SetRealReg(QString, QString, QString, QString)",

                          self.screen_start_stop_real, '', self.realType.REALTYPE['장시작시간']['장운영구분'], "0")

주식 거래를 하기 위해서 장외 시간인지 시작 시간인지를 파악한다.

이를 통해 장 시작 시 특정 작업을 자동으로 실행할 수 있고, 장 외 시간에 거래되는 종목들을 모니터링할 수 있도록 해준다.


주식 체결

주식 체결 - 장 상태 파악 및 종목 데이터 수신

선정한 종목 거래를 위한 알고리즘이다. 코드가 길기때문에 분할해서 설명한다.


def realdata_slot(self, sCode, sRealType, sRealData) :

    if sRealType == "장시작시간" :

        fid = self.realType.REALTYPE[sRealType]['장운영구분']

        

        # 실시간시세 데이터 수신 이벤트인 OnReceiveRealData() 발생될 때 실시간 데이터를 얻어오는 함수

        value = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid)

        

        if value == '0' :

            print("장 시작전")

        

        elif value == '3' :

            print("장 시작")

            

        elif value == '2' :

            print("장 종료, 동시호가로 넘어감")

        

        elif value == '4' :

            print("장 마감")

            

    elif sRealType == "주식체결" and sCode in self.k.portfolio_stock_dict :

        fid1 = self.realType.REALTYPE[sRealType]['체결시간']        # 체결시간은 String

        a = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid1)



        fid2 = self.realType.REALTYPE[sRealType]['현재가']          # 현재가는 +/-

        b = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid2)

        b = abs(int(b))

        

        fid3 = self.realType.REALTYPE[sRealType]['전일대비']        # 전달 대비 상승/하락 가격

        c = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid3)

        c = abs(int(c))

        

        fid4 = self.realType.REALTYPE[sRealType]['등락율']          # 전달 대비 상승/하락 퍼센트

        d = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid4)

        d = abs(float(d))

        

        fid5 = self.realType.REALTYPE[sRealType]['(최우선)매도호가']        # 매도 첫 번째 부분

        e = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid5)

        e = abs(int(e))

        

        fid6 = self.realType.REALTYPE[sRealType]['(최우선)매수호가']        # 매수 첫 번째 부분

        f = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid6)

        f = abs(int(f))

        

        fid7 = self.realType.REALTYPE[sRealType]['거래량']          # 틱봉의 현재 거래량

        g = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid7)

        g = abs(int(g))

        

        fid8 = self.realType.REALTYPE[sRealType]['누적거래량']

        h = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid8)

        h = abs(int(h))

        

        fid9 = self.realType.REALTYPE[sRealType]['고가']            # 오늘자 제일 높은 가격

        i = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid9)

        i = abs(int(i))

        

        fid10 = self.realType.REALTYPE[sRealType]['시가']

        j = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid10)

        j = abs(int(j))

        

        fid11 = self.realType.REALTYPE[sRealType]['저가']           # 오늘자 제일 낮은 가격

        k = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid11)

        k = abs(int(k))

        

        fid12 = self.realType.REALTYPE[sRealType]['거래회전율']     # 누적 거래회전율

        l = self.k.kiwoom.dynamicCall("GetCommRealData(QString, int)", sCode, fid12)

        l = abs(float(l))

        

        if sCode not in self.k.portfolio_stock_dict :               # 만약 서버에 등록된 코드가 포트폴리오에 미존재 시 코드 등록

            self.k.portfolio_stock_dict.update({sCode : {}})

            

        self.k.portfolio_stock_dict[sCode].update({"체결시간" : a})

        self.k.portfolio_stock_dict[sCode].update({"현재가" : b})

        self.k.portfolio_stock_dict[sCode].update({"전일대비" : c})

        self.k.portfolio_stock_dict[sCode].update({"등락율" : d})

        self.k.portfolio_stock_dict[sCode].update({"(최우선)매도호가" : e})

        self.k.portfolio_stock_dict[sCode].update({"(최우선)매수호가" : f})

        self.k.portfolio_stock_dict[sCode].update({"거래량" : g})

        self.k.portfolio_stock_dict[sCode].update({"누적거래량" : h})

        self.k.portfolio_stock_dict[sCode].update({"고가" : i})

        self.k.portfolio_stock_dict[sCode].update({"시가" : j})

        self.k.portfolio_stock_dict[sCode].update({"저가" : k})

        self.k.portfolio_stock_dict[sCode].update({"거래회전율" : l})

기본적으로 주식 거래는 장이 운영되는 시간에 거래할 수 있다.

물론, 매수/매도 예약과 같은 장 외 시간에도 거래를 할 수 있지만 일반적으로 장 운영 시간에 거래가 체결된다.

따라서 현재 주식장의 상태를 먼저 파악한다.

다음으로 포트폴리오에 있는 종목에 대한 주식체결 기본 데이터들을 받아온다.

체결시간, 현재가, 전일대비, 등락율 등의 데이터들을 받아오고 포트폴리오에 해당 종목이 존재하지 않는다면 종목을 포트폴리오에 넣어준다.


주식 체결 - 매수 알고리즘


if self.k.portfolio_stock_dict[sCode]["현재가"] <= self.k.portfolio_stock_dict[sCode]["매수가"] :

    if sCode not in self.orderitemlist_1 :

        wa = []

        wa.append(sCode)

        

        if len(wa) > 1 :

            wa.clear()

            pass

        else :

            print("매수 시작 %s" % sCode)

            

            self.orderitemlist_1.append(sCode)

            order_success1 = self.k.kiwoom.dynamicCall("SendOrder(Qstirng, QString, QString, int, QString, int, int, QString, QString)",

            ['신규매수', self.k.portfolio_stock_dict[sCode]['주문용스크린번호'], self.account_num, 1, sCode,

                self.k.portfolio_stock_dict[sCode]['매수수량'], self.k.portfolio_stock_dict[sCode]['현재가'],

                self.realType.SENDTYPE['거래구분']['지정가'], ""])

            

            wf2 = open("dist/buy_database.txt", "a", encoding="utf8")

            wf2.write("%s\t%s\t%s\t%s\n" % ("1매수정보", self.k.portfolio_stock_dict[sCode]['종목명'], b, self.k.portfolio_stock_dict[sCode]['체결시간']))

            wf2.close()

            

            if order_success1 == 0 :

                print("최우선매수호가로 주문 전달 성공")

            else :

                print("최우선매수호가로 주문 전달 실패")

종목에 대한 현재가가 지정한 매수가보다 낮아야 주식이 체결된다.

따라서 해당 조건을 만족할 때, 종목을 매수한다.

wa 리스트는 매수 알고리즘에서 중복 매수 요청을 방지하기 위한 변수이다.

매수 종목에 대해 매수 주문을 전달하는데, 매수할 때의 정보를 buy_database.txt 데이터베이스 저장한다.


주식 체결 - 매도 알고리즘

매도 알고리즘은 두 가지로 나뉘는데, 익절과 손절이다.

손절


if self.k.portfolio_stock_dict[sCode]["현재가"] <= self.k.portfolio_stock_dict[sCode]["손절가"] :

    if sCode not in self.orderitemlist_3 :

        wa = []

        wa.append(sCode)

        

        if len(wa) > 1 :

            wa.clear()

            pass

        else :

            print("손절 시작 %s" % sCode)

            

            self.orderitemlist_3.append(sCode)

            order_success3 = self.k.kiwoom.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",

                                                        ["신규손절", self.k.portfolio_stock_dict[sCode]['주문용스크린번호'], self.account_num, 2, sCode,

                                                        self.k.portfolio_stock_dict[sCode]['매수수량'], self.k.portfolio_stock_dict[sCode]['현재가'],

                                                        self.realType.SENDTYPE['거래구분']['지정가'], ""])

            

            wf2 = open("dist/buy_database.txt", "a", encoding="utf8")

            wf2.write("%s\t%s\t%s\t%s\n" % ("1손절정보", self.k.portfolio_stock_dict[sCode]["종목명"], b, self.k.portfolio_stock_dict[sCode]['체결시간']))

            wf2.close()

            

            if order_success3 == 0 :

                print("손절가로 주문 전달 성공")

            else :

                print("손절가로 주문 전달 실패")

지정한 손절가보다 현재가격이 내려가면 보유한 종목을 매도해야한다.

보유 종목을 매도하고 매도 정보를 데이터베이스에 저장한다.

만약 주식 가격이 급격하게 떨어진다거나 특정 상황에 의해 거래가 실패될 때가 존재하므로 그에 대한 정보도 알려준다.


익절


if self.k.portfolio_stock_dict[sCode]["현재가"] >= self.k.portfolio_stock_dict[sCode]["익절가"] :

    if sCode not in self.orderitemlist_2 :

        wa = []

        wa.append(sCode)

        

    if len(wa) > 1 :

        wa.clear()

        pass

    else :

        print("익절 시작 %s" % sCode)

        

        self.orderitemlist_2.append(sCode)

        order_success2 = self.k.kiwoom.dynamicCall("SendOrder(QString, QString, QString, int, QString, int ,int, QString, QString)",

                                                    ["신규익절", self.k.portfolio_stock_dict[sCode]['주문용스크린번호'], self.account_num, 2, sCode,

                                                    self.k.portfolio_stock_dict[sCode]['매수수량'], self.k.portfolio_stock_dict[sCode]['현재가'],

                                                    self.realType.SENDTYPE['거래구분']['지정가'], ""])

        

        wf2 = open("dist/buy_database.txt", "a", encoding="utf8")

        wf2.write("%s\t%s\t%s\t%s\n" % ("1익절정보", self.k.portfolio_stock_dict[sCode]["종목명"], b, self.k.portfolio_stock_dict[sCode]["체결시간"]))

        wf2.close()

        

        if order_success2 == 0 :

            print("익절가로 주문 전달 성공")

        else :

            print("익절가로 주문 전달 실패")

손절 알고리즘과 비슷하게 작동한다.

현재가가 익절가보다 올라가면 종목을 매도하고 매도 정보를 데이터베이스에 저장한다.


주식 체결 - 미체결 잔고 매수/매도 취소

특정 상황에 의해 우리가 전달한 체결 주문이 미체결된다면 해당 정보를 계속 서버에 전송하면 안된다.

특정 가격에서 매수 or 매도 주문을 전송하였으나 주식 가격은 장 운영시간동안 계속해서 변동하므로 해당 주문을 취소해야한다.


not_stock_list = list(self.k.not_account_stock_dict)

if len(self.k.not_account_stock_dict) > 0 :

    for order_num in not_stock_list :

        code = self.k.not_account_stock_dict[order_num]["종목코드"]

        stock_price = self.k.not_account_stock_dict[order_num]["주문가격"]

        not_quantity = self.k.not_account_stock_dict[order_num]["미체결수량"]

        order_gubun = self.k.not_account_stock_dict[order_num]['주문구분']

        

        # 매수 취소 주문

        if order_gubun == "매수" and not_quantity > 0 and stock_price < self.k.portfolio_stock_dict[sCode]["현재가"] :

            order_success = self.k.kiwoom.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",

                                                        ["매수취소", self.k.portfolio_stock_dict[sCode]['주문용스크린번호'], self.account_num, 3, code, 0, 0,

                                                        self.realType.SENDTYPE['거래구분']['지정가'], order_num])

            

            if order_success == 0 :

                print("%s 매수취소 전달 성공" % code)

                self.cancel_the_order.append(code)

            else :

                print("%s 매수취소 전달 실패" %code)

            

            wf2 = open("dist/cancel_database.txt", "a", encoding="utf8")

            wf2.write("%s\t%s\t%s\t%s\n" % ("매수취소", self.k.portfolio_stock_dict[sCode]["종목명"], not_quantity, self.k.portfolio_stock_dict[sCode]["체결시간"]))

            wf2.close()

        

        elif not_quantity == 0:

            del self.k.not_account_stock_dict[order_num]

    

        # 매도 취소 주문    

        elif order_gubun =="매도" and not_quantity > 0 and self.k.portfolio_stock_dict[sCode]["현재가"] < stock_price :

            order_success = self.k.kiwoom.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",

                                                        ["매도취소", self.k.portfolio_stock_dict[sCode]['주문용스크린번호'], self.account_num, 4, code, 0, 0,

                                                        self.realType.SENDTYPE['거래구분']['지정가'], order_num])

            wf2 = open("dist/cancel_database.txt", "a", encoding="utf8")

            wf2.write("%s\t%s\t%s\t%s\n" % ("매도취소", self.k.portfolio_stock_dict[sCode]['종목명'], not_quantity, self.k.portfolio_stock_dict[sCode]['체결시간']))

            wf2.close()  

        

            if order_success == 0 :

                print("%s 매도취소 전달 성공 % code")

                self.cancel_the_order.append(code)

            else :

                print("%s 매도취소전달 실패" % code)

미체결된 종목들을 리스트에 저장하고 매수/매도 구분에 따라 취소 주문을 서버에 전송한다.

전송 후 취소 주문에 대한 정보를 cancel_databaset.txt 데이터베이스에 저장한다.


재 매수 알고리즘

매수 주문이 취소된 종목들을 다시 매수하는 알고리즘이다.


elif sCode in self.cancel_the_order :

    if self.k.portfolio_stock_dict[sCode]["현재가"] <= self.k.portfolio_stock_dict[sCode]["매수가"] :

        if sCode not in self.orderitemlist_4 :

            wa = []

            wa.append(sCode)

            

            if len(wa) > 1 :

                wa.clear()

                pass

            else :

                print("재매수 시작 %s" % code)

                

                self.orderitemlist_4.append(sCode)

                order_success3 = self.k.kiwoom.dynamicCall("SendOrdeR(QString, QString, QString, int, QString, int, int, QString, QString)",

                                                            ["신규매수", self.k.portfolio_stock_dict[sCode]['주문용스크린번호'], self.account_num, 1, sCode,

                                                            self.k.portfolio_stock_dict[sCode]["매수수량"], self.k.portfolio_stock_dict[sCode]["현재가"],

                                                            self.realType.SENDTYPE['거래구분']['지정가'], ""])

                

            wf2 = open("dist/buy_database.txt", "a", encoding="utf8")

            wf2.write("%s\t%s\t%s\t%s\n" % ("재매수정보", self.k.portfolio_stock_dict[sCode]["종목명"], self.k.portfolio_stock_dict[sCode]["체결시간"]))

            wf2.close()

            

            if order_success3 == 0 :

                print("재매수 주문 전달 성공")

            else :

                print("재매수 주문 전달 실패")

cancel_the_order라는 취소된 종목들이 저장된 리스트에서 매수가가 현재가보다 높을 시 재 매수를 시도한다.

기본 알고리즘은 매수 알고리즘과 유사하다.


체결 통보, 잔고 업데이트

주문이 서버에 전송되면 사용자는 주문 전송에 대한 체결 통보를 받고 잔고를 업데이트해야한다.

체결 통보


def chejan_slot(self, sGubun, nItemCnt, sFIdList) :     # 주문전송 후 주문접수, 체결통보, 잔고통보를 수신

    if sGubun == "0" :

        print("매수/매도 중. 미체결 잔고 업데이트")

    else :

        print("미체결잔고 해결로 실제 잔고 업데이트")

        

    if int(sGubun) == 0 :           # 주문전송 후 미체결 시 아래의 연산 수행

        account_num = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['계좌번호'])

        sCode = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['종목코드'])

        stock_name = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['종목명'])

        stock_name = stock_name.strip()

        origin_order_number = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['원주문번호'])

        order_number = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['주문번호'])

        order_status = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['주문상태'])

        order_quan = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['주문수량'])

        order_quan = int(order_quan)

        order_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['주문가격'])

        order_price = int(order_price)

        not_chegual_quan = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['미체결수량'])

        not_chegual_quan = int(not_chegual_quan)

        order_gubun = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['주문구분'])

        order_gubun = order_gubun.lstrip('+').lstrip('-')

        order_gubun = order_gubun.strip()

        chegual_time_str = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['주문/체결시간'])

        chegual_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['체결가'])

        if chegual_price == '' :

            chegual_price = 0

        else :

            chegual_price = int(chegual_price)

            

        chegual_quantity = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['체결량'])

        if chegual_quantity == '' :

            chegual_quantity = 0

        else :

            chegual_quantity = int(chegual_quantity)

            

        current_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['현재가'])

        current_price = abs(int(current_price))

        first_sell_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['(최우선)매도호가'])

        first_sell_price = abs(int(first_sell_price))

        first_buy_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['주문체결']['(최우선)매수호가'])

        first_buy_price = abs(int(first_buy_price))

        

        if order_number not in self.k.not_account_stock_dict.keys() :

            self.k.not_account_stock_dict.update({order_number : {}})

            

        self.k.not_account_stock_dict[order_number].update({"종목코드" : sCode})

        self.k.not_account_stock_dict[order_number].update({"종목명" : stock_name})

        self.k.not_account_stock_dict[order_number].update({"주문번호" : order_number})

        self.k.not_account_stock_dict[order_number].update({"주문상태" : order_status})

        self.k.not_account_stock_dict[order_number].update({"주문수량" : order_quan})

        self.k.not_account_stock_dict[order_number].update({"주문가격" : order_price})

        self.k.not_account_stock_dict[order_number].update({"주문구분" : order_gubun})

        self.k.not_account_stock_dict[order_number].update({"미체결수량" : not_chegual_quan})

        self.k.not_account_stock_dict[order_number].update({"체결량" : chegual_quantity})

        self.k.not_account_stock_dict[order_number].update({"원주문번호" : origin_order_number})

        self.k.not_account_stock_dict[order_number].update({"주문/체결시간" : chegual_time_str})

        self.k.not_account_stock_dict[order_number].update({"체결가" : chegual_price})

        self.k.not_account_stock_dict[order_number].update({"현재가" : current_price})

        self.k.not_account_stock_dict[order_number].update({"(최우선)매도호가" : first_sell_price})

        self.k.not_account_stock_dict[order_number].update({"(최우선)매수호가" : first_buy_price})

        

        column_head = ["종목코드", "종목명", "주문번호", "주문상태", "주문수량", "주문가격", "미체결수량"]

        colCount = len(column_head)

        rowCount = len(self.k.not_account_stock_dict)

        self.parent.not_account.setColumnCount(colCount)

        self.parent.not_account.setRowCount(rowCount)

        self.parent.not_account.setHorizontalHeaderLabels(column_head)

        

        for index in range(rowCount) :

            self.parent.not_account.setItem(index, 0, QTableWidgetItem(str(sCode)))

            self.parent.not_account.setItem(index, 1, QTableWidgetItem(str(format(stock_name))))

            self.parent.not_account.setItem(index, 2, QTableWidgetItem(str(format(order_number))))

            self.parent.not_account.setItem(index, 3, QTableWidgetItem(str(format(order_status))))

            self.parent.not_account.setItem(index, 4, QTableWidgetItem(str(format(order_quan, ","))))

            self.parent.not_account.setItem(index, 5, QTableWidgetItem(str(format(order_price, ","))))

            self.parent.not_account.setItem(index, 6, QTableWidgetItem(str(format(not_chegual_quan, ","))))

            

        print("미체결잔고 종목 추가 %s 수량 %s" % (self.k.not_account_stock_dict[order_number]["종목명"], self.k.not_account_stock_dict[order_number]["미체결수량"]))

sGuBun 값에 따라 미체결 잔고(0), 실제 잔고(1)에 대한 정보를 처리한다.

미체결 잔고의 경우 종목에 대한 데이터들(종목코드, 종목명, 주문번호 등)을 수신하고 데이터를 저장한다.

이후 미체결 잔고 데이터를 UI에 표시한다.


잔고 업데이트


elif int(sGubun) == 1 :

    account_num = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['계좌번호'])

    sCode = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['종목코드'])[1:]

    stock_name = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['종목명'])

    stock_name = stock_name.strip()

    current_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['현재가'])

    current_price = abs(int(current_price))

    stock_quan = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['보유수량'])

    stock_quan = int(stock_quan)

    like_quan = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['주문가능수량'])

    like_quan = int(like_quan)

    buy_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['매입단가'])

    buy_price = abs(int(buy_price))

    total_buy_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['총매입가'])

    total_buy_price = int(total_buy_price)

    stock_gubun = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['매도매수구분'])

    stock_gubun = self.realType.REALTYPE['매도수구분'][stock_gubun]

    first_sell_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['(최우선)매도호가'])

    first_sell_price = int(first_sell_price)

    first_buy_price = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['(최우선)매수호가'])

    first_buy_price = int(first_buy_price)

    first_buy_price1 = self.k.kiwoom.dynamicCall("GetChejanData(int)", self.realType.REALTYPE['잔고']['예수금'])

    first_buy_price1 = int(first_buy_price1)

    

    if sCode not in self.k.jango_dict.keys() :

        self.k.jango_dict.update({sCode : {}})

        

    self.k.jango_dict[sCode].update({"현재가" : current_price})

    self.k.jango_dict[sCode].update({"종목코드" : sCode})

    self.k.jango_dict[sCode].update({"종목명" : stock_name})

    self.k.jango_dict[sCode].update({"보유수량" : stock_quan})

    self.k.jango_dict[sCode].update({"주문가능수량" : like_quan})

    self.k.jango_dict[sCode].update({"매입단가" : buy_price})

    self.k.jango_dict[sCode].update({"총매입가" : total_buy_price})

    self.k.jango_dict[sCode].update({"매도매수구분" : stock_gubun})

    self.k.jango_dict[sCode].update({"(최우선)매도호가" : first_sell_price})

    self.k.jango_dict[sCode].update({"(최우선)매수호가" : first_buy_price})

    

    if sCode in self.k.acc_portfolio.keys() and stock_quan == 0 :

        del self.k.acc_portfolio[sCode]

잔고 처리의 경우 거래 전 잔고 → 거래 후 잔고를 처리한다.

종목들의 데이터를 수신하고 사용자의 잔고(jango_dict)를 업데이트한다.

만약 종목 보유 수량이 0이된다면 해당 종목을 잔고에서 제거한다.