주식자동매매 48강. 실시간 자동매매 구현(8), 실시간 종목 원하는 가격에 매수하기
본문 바로가기

주식 자동매매 강의/기초반(모든 코딩의 뿌리)

주식자동매매 48강. 실시간 자동매매 구현(8), 실시간 종목 원하는 가격에 매수하기

반응형

47강에서 실시간 주식체결 정보를 가져온 후 portfolio_stock_dict에 저장을 완료하였습니다. 이번 강의에서는 현재가/매수가/매수수량으로 키움 서버에 원하는 종목을 매수해 보겠습니다.

 

[유튜브 강의, 링크]와 같이 보시면 많은 도움이 되실 겁니다.

 

1. 종목 매수를 위한 Sendorder() 함수 이해하기

 KOA Studio를 보면 아래 그림과 같이 Sendorder() 함수를 사용하기 위한 9개의 기본 입력값이 정해져 있습니다. 그리고 반드시 알고 계셔야 할 것은 1초에 5번 매수 주문만 가능하다는 점 입니다. 혹시, 엄청 빠른 스캘핑을 진행할 경우 반드시 시간을 고려하셔야 됩니다.

Sendorder() 함수 이해하기
Sendorder() 함수 이해하기

 

  그리고 거래구분을 보시면 지정가/시장가/조건부지정가 등의 값들이 있습니다. 빠르게 매수/매도하시려면 시장가로 하시고 웬만하면 지정가를 추천드립니다. 그리고 시간 외 매수를 하기 위해서는 아래와 같은 거래 구분 코드가 필요합니다.

 

시간외 매수
시간외 매수

 

2. 실시간 종목 매수하기

 아래 그림을 보시면 드디어 매수 알고리즘 코딩에 대해서 설명하고 있습니다. 구조는 아래와 같습니다.

 1) 매수조건 판단

 2) 중복매수 금지

 3) 매수 주문

 4) 데이터 베이스

 5) 주문 상태 확인

 

매수 알고리즘 코딩
매수 알고리즘 코딩

 

 1) 매수조건 판단

  - if self.k.portfolio_stock_dict[sCode]["현재가"] <= self.k.portfolio_stock_dict[sCode]["매수가"] : 수신되는 sCode의  portfolio_stock_dict에 들어있는 현재가와 매수가를 서로 비교합니다. 현재가는 실시간으로 업데이트되고 있고 매수 가는 고정 되어 있습니다. 현재가(7,000) <= 매수가(7,000)가 되는 순간 아래 코드를 수행합니다.

 

 2) 중복 매수 금지

  - if sCode not in self.orderitemlist_1 : self.orderitemlist_1는 리스트로 구성되어 있으며, 한번 매수가 된 코드를 저장하게 됩니다. 즉, 1번 매수가 완료된 코드를 중복 매수되지 않도록 막아주는 코드입니다. 그리고 아래 그림처럼 Qthread_3에 self.orderitemlist_1를 리스트([])로 반드시 생성해 주셔야 됩니다. 

orderitemlist_1 생성
orderitemlist_1 생성

- wa = []

  - wa.append(sCode)

  - if len(wa) > 1:

        wa.clear()

        pass

  - 위의 코드를 간단히 설명드리겠습니다. 네트워크에 오류가 생겼거나 1초에 수십 번의 틱이 변하여 self.orderitemlist_1를 뚫고 2개 이상의 sCdoe가 내려오게 되면, 반드시 중복 매수가 되게 됩니다. 따라서, wa를 리스트로 만들고 sCdoe를 저장합니다. 혹시 sCdoe의 길이가 1개 이상이면, 다음번 종목에 대한 매수를 위해 wa를 비우고 기존의 sCdoe에 대한 매수를 수행하지 않습니다. 다시 말씀드리지만 100만 원짜리 주식이 중복 매수되면 200만 원이 매수되므로 상당히 위험할 수 있습니다. 차라리 이러한 에러 상황에서는 매수를 하지 않고 다음번 매수를 기다리는 것이 더 현명합니다.

 

매수 알고리즘 코딩
매수 알고리즘 코딩

 3) 매수 주문

  - else : wa의 길이가 2개 이하이면(중복 매수 위험성이 없으면) 아래 코드를 실행합니다.

  - self.orderitemlist_1.append(sCode) : 중복 매수를 막기 위해 매수가 시작되면, sCode를 self.orderitemlist_1에 저장시킵니다.

 - order_sucess1 = self.k.kiwoom.dynamicCall("SendOrder()) :드디어 SendOrder() 함수를 사용해 보겠습니다. 우선 self.k.kiwoom.dynamicCall을 이용하여 키움 서버에 명령을 전송할 준비를 합니다. 그리고 매수가 성공적으로 수행되면 0이라는 숫자를 반환합니다.

  * sRQName : 신규 매수

  * sScreenNo : self.k.portfolio_stock_dict[sCode]["주문용스크린번호"], 우리가 예전 강의에서 저장한 스크린 번호가 입력됩니다.

  * sAccNo : self.account_num, 콤보 박스에서 받아온 계좌번호를 입력시킵니다.

  * nOrderType : 1, 신규 매수를 수행합니다(나중에 매도는 2를 하면 되겠죠?).

  * sCode : 종목코드를 나타내는 sCode를 그대로 입력합니다.

  * nQty : 주문 수량은 self.k.portfolio_stock_dict[sCode]["매수수량"]을 그대로 입력합니다.

  * nPrice : 주문 가격은 self.k.portfolio_stock_dict[sCode]["현재가"]를 입력합니다. 혹시 시장가와 마찬가지로 즉시 거래되길 원하시면, 최우선 매도호가 및 최수 선매 수호가를 사용하시면 됩니다.

  * sHogaGb : 거래 구분은 self.realType.SENDTYPE["거래구분"]["지정가"]와 같이 지정가로 합니다.

  * sOrgOrderNo : 원주 문 번호는 주문을 정정 및 취소할 때 넣어야 될 번호로 신규 주문은 공백 ""을 입력합니다. 차 후에 주문 정정 및 취소에 대해서도 말씀드리겠습니다.

 위와 같이 코딩하시면, 드디어 저희가 DB 파일에 저장된 원하는 종목에 대하여 매수 주문이 들어갔습니다. 축하드립니다.

 

 4) 데이터 베이스

 우리가 매수한 종목에 대한 데이터 베이스를 하는 구간입니다. 차 후에 프로그램이 종료되어 중복 매수를 막거나, 매수/매도 상태, 알고리즘 검증 등을 위해서 반드시 필요한 구간입니다. 앞서 배운 텍스트 만들기 이므로 간단히 설명하고 넘어가겠습니다.

  - wf2 = open(dist/mesu_database.txt, "a", encoding="utf8") : dist 폴더에 utf8형식의 mesu_database텍스트를 만듭니다.

  - wf2.write(%s\t%s\t%s\t%s\n % (1매수정보, self.k.portfolio_stock_dict[sCode]["종목명"], b, self.k.portfolio_stock_dict[sCode]["체결시간"])) : 텍스트에 [1매수정보 종목명 현재가 체결시간]을 저장합니다.

  - wf2.close() : 열린 텍스트를 종료합니다.

 

 5) 주문 상태 확인

  - if order_success1 == 0 : 

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

    else:

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

 

  - 간단하게 order_success1가 0이면 주문 전달 성공이고. 그 외의 값들이 수신되면 모두 주문 전달 실패로 기록됩니다.

 

3. 요약 및 코드 공개

 여러분 드디어 실시간으로 원하는 종목에 대하여 매수 주문을 요청하였습니다. 다음 강의에서는 매도도 구현할 터인데 딱 1줄만 수정하면 됩니다. 이제 강의가 정말 얼마 남지 않은 것 같습니다. 다들 파이팅 하시고 돈 복사해보시죠. 감사합니다.

반응형

 

import os                                   # 현재 디렉토리 확인 기능
from PyQt5.QtCore import *                  # 쓰레드 함수를 불러온다.
from kiwoom import Kiwoom                   # 로그인을 위한 클래스
from kiwoomType import *


class Thread3(QThread):
    def __init__(self, parent):   # 부모의 윈도우 창을 가져올 수 있다.
        super().__init__(parent)  # 부모의 윈도우 창을 초기화 한다.
        self.parent = parent      # 부모의 윈도우를 사용하기 위한 조건

        ################## 키움서버 함수를 사용하기 위해서 kiwoom의 능력을 상속 받는다.
        self.k = Kiwoom()
        ##################

        ################## 사용되는 변수
        account = self.parent.accComboBox.currentText()  # 콤보박스 안에서 가져오는 부분
        self.account_num = account
        # 계좌번호 가져오는 부분은 Qthread_3 분리 시 로그인 후 계좌번호를 가져오는 함수로 교체된다. Lecture_0529.py


        ################# 매수관련 변수
        self.Load_code()          # 매수 종목/금액/수량 가져오기
        self.orderitmelist_1 = [] # 중복 매수 금지

        ####### 주문 전송 시 필요한 FID 번호
        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("실시간 등록 : %s, 스크린번호 : %s, FID  번호 : %s" % (code, screen_num, fids))
        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
            for line in lines:
                if line != "":                     # 만약에 line이 비어 있지 않다면
                    ls = line.split("\t")  # \t(tap)로 구분을 지어 놓는다.
                    t_code = ls[0]
                    t_name = ls[1]
                    curren_price = ls[2]
                    dept = ls[3]
                    mesu = 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(curren_price)})
                    self.k.portfolio_stock_dict[t_code].update({"신용비율": dept})
                    self.k.portfolio_stock_dict[t_code].update({"매수가": int(mesu)})
                    self.k.portfolio_stock_dict[t_code].update({"매수수량": int(n_o_stock)})
                    self.k.portfolio_stock_dict[t_code].update({"익절가": int(profit)})
                    self.k.portfolio_stock_dict[t_code].update({"손절가": int(loss)})
                    self.k.portfolio_stock_dict[t_code].update({"주문용스크린번호": screen})  # 아래 내용을 업데이트
                    screen += 1
            f.close()



    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으로 나온다. HHMMSS
            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 = 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})


            ###############################################################
            ############# 실시간을 위한 조건문 구성하기 ########################
            ###############################################################


            #1. 매수 알고리즘 가동

            #1차#############################################################################################
            if self.k.portfolio_stock_dict[sCode]["현재가"] <= self.k.portfolio_stock_dict[sCode]["매수가"]:
                if sCode not in self.orderitmelist_1:

                    wa = []
                    wa.append(sCode)

                    if len(wa) > 1:
                        wa.clear()
                        pass
                    else:
                        print("매수 시작 %s" % sCode)

                        self.orderitmelist_1.append(sCode)  # 이 기법을 더이상 사용하지 못하게 하기
                        order_success1 = 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/mesu_database.txt", "a", encoding="utf8")  # "a" 달아 쓴다. "w" 덮어 쓴다. files라느 파이썬 페키지 볼더를 만든다.
                        wf2.write("%s\t%s\t%s\t%s\n" % ("1매수정보", self.k.portfolio_stock_dict[sCode]["종목명"], b, self.k.portfolio_stock_dict[sCode]["채결시간"]))  # t는 tap을 의미한다.
                        wf2.close()

                        if order_success1 == 0:
                            print("최우선매수호가로 주문 전달 성공")
                        else:
                            print("최우선매수호가로 주문 전달 실패")

반응형

.link_tit