728x90
반응형

1. 로그인 후 주식 기본정보 가져오는 코드

 

*코드*

더보기
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)

        if rQName == "주식정보":
            # PER, PBR 정보 획득
            name = self.GetCommData(trCode, rQName, 0, "종목명").strip()
            code = self.GetCommData(trCode, rQName, 0, "종목코드").strip().replace("A", "")
            PER = float(self.GetCommData(trCode, rQName, 0, "PER"))
            PBR = float(self.GetCommData(trCode, rQName, 0, "PBR"))
            ROE = float(self.GetCommData(trCode, rQName, 0, "ROE"))

            print('종목명: %s PER: %.2f PBR: %.2f ROE: %.2f' % (name, PER, PBR, ROE))

    @SyncRequestDecorator.kiwoom_sync_request
    def get_opt10001(self, code):
        self.SetInputValue("종목코드", code)
        self.CommRqData("주식정보", "opt10001", 0, "0105")

class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)
        sleep(5)

        ###############################
        # 2. 주식기본정보요청            #
        ###############################
        self._kiwoom.get_opt10001('005930')

if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

 

 

 

 

2. 조건식 추가 및 결과 가져오기

 

- 조건식 추가하는 방법

 

1. 모의투자가 아닌 ID로 로그인 한다.

 

 

 

2. 검색에 '0150'을 입력하여 조건검색창을 열고 조건식 -> 재무분석 -> 주가지표 -> PER를 누른다.

 

 

3. 위의 조건을 원하는대로 선택하고 추가버튼을 누른 후 아래쪽 내 조건식 저장을 누른 후 조건명을 입력하고 확인을 누른다.

 

 

*코드*

더보기
# -*-coding: utf-8 -*-
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

        # 조건식 결과 출력
        print(codeList)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)


class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)

        sleep(5)
        ###############################
        # 2. 조건식 결과 가져오기        #
        ###############################

        # 조건식 불러오기
        self._kiwoom.GetConditionLoad()

        # 조건식 리스트 가져오기 (res="000^스파르타;001^조건식1;002^조건식2;...")
        res = self._kiwoom.GetConditionNameList()
        print(res)
        res = res.split(';')
        print(res)

        # 조건식 중 'TEST' 조건식 존재여부 확인
        condition_index = None
        condition_name = None

        for name in res:
            if 'TEST' in name:
                [condition_index, condition_name] = name.split('^')
                break

        if condition_index == None or condition_name == None:
            print("Can't find a condition.")
            exit(0)

        print("조건식:", condition_name, "번호:", condition_index)

        # '스파르타' 조건식 결과 요청하기
        condition_index = int(condition_index)
        self._kiwoom.SendCondition("0156", condition_name, condition_index, 0)

if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

 

 

 

3. 여러 주식 종목에 대한 정보를 가져오기

 

*코드*

더보기
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)

        if rQName == "복수종목조회":
            cnt = self.GetRepeatCnt(trCode, rQName)

            # 시가총액 및 호가정보 self.li_stock에 저장
            for i in range(cnt):
                name = self.GetCommData(trCode, rQName, i, "종목명").strip()
                code = self.GetCommData(trCode, rQName, i, "종목코드").strip().replace("A", "")
                print("종목명:", name,"종목코드:", code)


class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)

        ###############################
        # 2. 복수종목 조회              #
        ###############################
        self._kiwoom.CommKwRqData("005930;000660", 0, 2, 0, "복수종목조회", "0122")

if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

 

 

4. 잔고조회 및 보유 종목 확인

-> 모의 주식을 대상으로 했기 때문에 계좌번호 입력을 자신의 모의계좌번호로 바꾸셔야합니다.

더보기

*코드*

from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)

        if rQName == '잔고조회':
            # 잔고 정보 획득
            total_budget = int(self.GetCommData(trCode, rQName, 0, "추정예탁자산").strip())
            print("++ 계좌평가현황 ++")
            print("추정예탁자산:", total_budget)

            print("++ 종목별계좌평가현황 ++")
            cnt = self.GetRepeatCnt(trCode, rQName)

            for i in range(cnt):
                name = self.GetCommData(trCode, rQName, i, "종목명").strip()
                code = self.GetCommData(trCode, rQName, i, "종목코드").strip().replace("A", "")
                estimated_amount = int(self.GetCommData(trCode, rQName, i, "평가금액"))
                count = int(self.GetCommData(trCode, rQName, i, "보유수량"))

                print("종목명:", name, "종목코드:", code, "평가금액", estimated_amount, "보유수량", count)

    @SyncRequestDecorator.kiwoom_sync_request
    def get_jango_data(self, prevNext, **kwargs):
        self.SetInputValue("계좌번호", "8162697811")
        self.SetInputValue("비밀번호", "")
        self.SetInputValue("상장폐지조회구분", "0")
        self.SetInputValue("비밀번호입력매체구분", "00")
        self.CommRqData("잔고조회", "OPW00004", prevNext, "1999")


class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)

        ###############################
        # 2. 잔고 조회                 #
        ###############################
        self._kiwoom.get_jango_data("0")

if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

728x90
반응형
728x90
반응형

1. 개발 가이드에서 로그인 버전관리의 관련함수를 살펴본다.

: LONG CommConnect() 함수는 로그인 창을 출력하는 함수입니다.

 

 

 

: LONG GetLoginInfo() 함수에서는 로그인한 사용자에 관련된 다양한 정보를 반환받을 수 있습니다.

 

 

2. 코드 적용 사례

: CommConnect() 함수를 통해 로그인을 시도한 후, 로그인 완료가 될때까지 로그인 대기중을 띄웁니다.

 

GetLoginInfo() 함수의 인자값을 'ACCOUNT_CTN'으로 넣어주어 사용자의 보유계좌 갯수를 반환하고 갯수가 받아와지는 즉시 반복문을 탈출하지만, 로그인이 되지 않은 상태라 갯수가 반환되지 않는다면 로그인 대기중을 띄우는 것 입니다.

 

Sleep(5)를 주었는데, 키움 서버로 요청을 너무 많이 보내면 키움측에서 차단할 수 있기 때문에 시간차를 준 것입니다.

728x90
반응형
728x90
반응형

키움 API는 일반 함수와 이벤트 함수로 구성되어있다.

 

- 일반함수 : 호출 시 바로 반환 값을 받을 수 있는 함수이다. 즉 키움서버에 데이터를 전송하지 않는다.

 

- 이벤트 함수(aka 콜백 함수) : 호출 시 다른 함수에서 결과를 받게 되는 함수이다. 즉 키움서버에 데이터 요청하고 원하는 정보를 받아낸다.

<이벤트 함수 예시>

 

모든 이벤트 함수는 비동기 방식이다.  데이터의 요청과 응답이 우리가 생각하는 순서처럼 순차적으로 이루어지지 않기 때문에 키움 API를 사용하여 코딩할 때 유의해야 한다.

728x90
반응형
728x90
반응형

1. main.py에 코드를 넣는다.

 

* 코드 *

더보기
# -*-coding: utf-8 -*-
from PyQt5.QtCore import QObject, pyqtSlot
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtCore import QThread
from threading import Lock, Thread
from collections import deque
from sys import argv
from time import sleep
from json import dumps
from os import kill, getpid


class SyncRequestDecorator:
    """키움 API 비동기 함수 데코레이터
    """

    @staticmethod
    def kiwoom_sync_request(func):
        def func_wrapper(self, *args, **kwargs):
            self.request_thread_worker.request_queue.append((func, args, kwargs))

        return func_wrapper

    @staticmethod
    def kiwoom_sync_callback(func):
        def func_wrapper(self, *args, **kwargs):
            # print("[%s] 키움 함수 콜백: %s %s" % (func.__name__, args, kwargs))

            func(self, *args, **kwargs)  # 콜백 함수 호출
            if self.request_thread_worker.request_thread_lock.locked():
                self.request_thread_worker.request_thread_lock.release()  # 요청 쓰레드 잠금 해제

            # 재시도 횟수 초기화
            myWindow._kiwoom.request_thread_worker.retry_count = 0

        return func_wrapper


class RequestThreadWorker(QObject):
    def __init__(self):
        """요청 쓰레드
        """
        super().__init__()
        self.request_queue = deque()
        self.request_thread_lock = Lock()

        # 간혹 요청에 대한 결과가 콜백으로 오지 않음
        # 마지막 요청을 저장해 뒀다가 일정 시간이 지나도 결과가 안오면 재요청
        self.retry_timer = None
        self.max_retry = 3
        self.retry_count = 0

    def retry(self, request):
        self.retry_count = self.retry_count + 1

        if self.retry_count == self.max_retry:
            kill(getpid(), 9)

        # print("[%s] 키움 함수 재시도: %s %s" % (request[0].__name__, request[1], request[2]))

        self.request_queue.appendleft(request)

    def run(self):
        last_request = None
        while True:
            # 큐에 요청이 있으면 하나 뺌
            # 없으면 블락상태로 있음

            try:
                request = self.request_queue.popleft()
            except IndexError as e:
                if self.request_thread_lock.locked():
                    if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                        self.request_thread_lock.release()
                        self.retry(last_request)
                    else:
                        self.request_thread_lock.release()

                sleep(0.2)
                continue

            # 요청에대한 결과 대기
            if not self.request_thread_lock.acquire(blocking=True, timeout=5):
                if self.request_thread_lock.locked():
                    self.request_thread_lock.release()
                # 요청 실패
                sleep(0.2)

                self.request_queue.appendleft(request)
                self.retry(last_request)  # 실패한 요청 재시도
            else:
                last_request = request
                # 요청 실행
                # print("[%s] 키움 함수 실행: %s %s" % (request[0].__name__, request[1], request[2]))
                request[0](trader, *request[1], **request[2])

            sleep(1)  # 0.2초 이상 대기 후 마무리


class Kiwoom(QObject):
    # Variables

    def __init__(self):
        super().__init__()
        self.ocx = QAxWidget("KHOPENAPI.KHOpenAPICtrl.1")
        self.ocx.OnEventConnect[int].connect(self.OnEventConnect)
        self.ocx.OnReceiveMsg[str, str, str, str].connect(self.OnReceiveMsg)
        self.ocx.OnReceiveTrData[str, str, str, str, str, int, str, str, str].connect(self.OnReceiveTrData)
        self.ocx.OnReceiveRealData[str, str, str].connect(self.OnReceiveRealData)
        self.ocx.OnReceiveChejanData[str, int, str].connect(self.OnReceiveChejanData)
        self.ocx.OnReceiveConditionVer[int, str].connect(self.OnReceiveConditionVer)
        self.ocx.OnReceiveTrCondition[str, str, str, int, int].connect(self.OnReceiveTrCondition)
        self.ocx.OnReceiveRealCondition[str, str, str, str].connect(self.OnReceiveRealCondition)

        # 요청 쓰레드
        self.request_thread_worker = RequestThreadWorker()
        self.request_thread = QThread()
        self.request_thread_worker.moveToThread(self.request_thread)
        self.request_thread.started.connect(self.request_thread_worker.run)
        self.request_thread.start()

    # 로그인
    # 0 - 성공, 음수값은 실패
    @pyqtSlot(result=int)
    def CommConnect(self):
        return self.ocx.dynamicCall("CommConnect()")

    # 로그인 상태 확인
    # 0:미연결, 1:연결완료, 그외는 에러
    @pyqtSlot(result=int)
    def GetConnectState(self):
        res = self.ocx.dynamicCall("GetConnectState()")
        if res == 1:
            print('Online')
        else:
            print('Offline')
        return res

    # 로그 아웃
    @pyqtSlot()
    def CommTerminate(self):
        self.ocx.dynamicCall("CommTerminate()")

    # 로그인한 사용자 정보를 반환한다.
    # “ACCOUNT_CNT” – 전체 계좌 개수를 반환한다.
    # "ACCNO" – 전체 계좌를 반환한다. 계좌별 구분은 ‘;’이다.
    # “USER_ID” - 사용자 ID를 반환한다.
    # “USER_NAME” – 사용자명을 반환한다.
    # “KEY_BSECGB” – 키보드보안 해지여부. 0:정상, 1:해지
    # “FIREW_SECGB” – 방화벽 설정 여부. 0:미설정, 1:설정, 2:해지
    @pyqtSlot(str, result=str)
    def GetLoginInfo(self, tag):
        return self.ocx.dynamicCall("GetLoginInfo(QString)", [tag])

    # Tran 입력 값을 서버통신 전에 입력값일 저장한다.
    @pyqtSlot(str, str)
    def SetInputValue(self, id, value):
        self.ocx.dynamicCall("SetInputValue(QString, QString)", [id, value])

    # 통신 데이터를 송신한다.
    # 0이면 정상
    # OP_ERR_SISE_OVERFLOW – 과도한 시세조회로 인한 통신불가
    # OP_ERR_RQ_STRUCT_FAIL – 입력 구조체 생성 실패
    # OP_ERR_RQ_STRING_FAIL – 요청전문 작성 실패
    # OP_ERR_NONE – 정상처리
    @pyqtSlot(str, str, int, str, result=int)
    def CommRqData(self, rQName, trCode, prevNext, screenNo):
        return self.ocx.dynamicCall("CommRqData(QString, QString, int, QString)", [rQName, trCode, prevNext, screenNo])

    # 수신 받은 데이터의 반복 개수를 반환한다.
    @pyqtSlot(str, str, result=int)
    def GetRepeatCnt(self, trCode, recordName):
        return self.ocx.dynamicCall("GetRepeatCnt(QString, QString)", [trCode, recordName])

    # Tran 데이터, 실시간 데이터, 체결잔고 데이터를 반환한다.
    # 1. Tran 데이터o
    # 2. 실시간 데이터
    # 3. 체결 데이터
    # 1. Tran 데이터
    # sJongmokCode : Tran명
    # sRealType : 사용안함
    # sFieldName : 레코드명
    # nIndex : 반복인덱스
    # sInnerFieldName: 아이템명
    # 2. 실시간 데이터
    # sJongmokCode : Key Code
    # sRealType : Real Type
    # sFieldName : Item Index (FID)
    # nIndex : 사용안함
    # sInnerFieldName:사용안함
    # 3. 체결 데이터
    # sJongmokCode : 체결구분
    # sRealType : “-1”
    # sFieldName : 사용안함
    # nIndex : ItemIndex
    # sInnerFieldName:사용안함
    @pyqtSlot(str, str, str, int, str, result=str)
    def CommGetData(self, jongmokCode, realType, fieldName, index, innerFieldName):
        return self.ocx.dynamicCall("CommGetData(QString, QString, QString, int, QString)",
                                    [jongmokCode, realType, fieldName, index, innerFieldName]).strip()

    # strRealType – 실시간 구분
    # nFid – 실시간 아이템
    # Ex) 현재가출력 - openApi.GetCommRealData(“주식시세”, 10);
    # 참고)실시간 현재가는 주식시세, 주식체결 등 다른 실시간타입(RealType)으로도 수신가능
    @pyqtSlot(str, int, result=str)
    def GetCommRealData(self, realType, fid):
        return self.ocx.dynamicCall("GetCommRealData(QString, int)", [realType, fid]).strip()

    # 주식 주문을 서버로 전송한다.
    # sRQName - 사용자 구분 요청 명
    # sScreenNo - 화면번호[4]
    # sAccNo - 계좌번호[10]
    # nOrderType - 주문유형 (1:신규매수, 2:신규매도, 3:매수취소, 4:매도취소, 5:매수정정, 6:매도정정)
    # sCode, - 주식종목코드
    # nQty – 주문수량
    # nPrice – 주문단가
    # sHogaGb - 거래구분
    # sHogaGb – 00:지정가, 03:시장가, 05:조건부지정가, 06:최유리지정가, 07:최우선지정가, 10:지정가IOC, 13:시장가IOC, 16:최유리IOC, 20:지정가FOK, 23:시장가FOK, 26:최유리FOK, 61:장전시간외종가, 62:시간외단일가, 81:장후시간외종가
    # ※ 시장가, 최유리지정가, 최우선지정가, 시장가IOC, 최유리IOC, 시장가FOK, 최유리FOK, 장전시간외, 장후시간외 주문시 주문가격을 입력하지 않습니다.
    # ex)
    # 지정가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 48500, "00", "");
    # 시장가 매수 - openApi.SendOrder("RQ_1", "0101", "5015123410", 1, "000660", 10, 0, "03", "");
    # 매수 정정 - openApi.SendOrder("RQ_1","0101", "5015123410", 5, "000660", 10, 49500, "00", "1");
    # 매수 취소 - openApi.SendOrder("RQ_1", "0101", "5015123410", 3, "000660", 10, "00", "2");
    # sOrgOrderNo – 원주문번호
    @pyqtSlot(str, str, str, int, str, int, int, str, str, result=int)
    def SendOrder(self, rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo):
        print("sendOrder", rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo)
        return self.ocx.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)",
                                    [rQName, screenNo, accNo, orderType, code, qty, price, hogaGb, orgOrderNo])

    # 체결잔고 데이터를 반환한다.
    @pyqtSlot(int, result=str)
    def GetChejanData(self, fid):
        return self.ocx.dynamicCall("GetChejanData(int)", [fid])

    # 서버에 저장된 사용자 조건식을 가져온다.
    @pyqtSlot(result=int)
    def GetConditionLoad(self):
        res = self.ocx.dynamicCall("GetConditionLoad()")
        if res == 1:
            print('GetConditionLoad() success')
        else:
            print('GetConditionLoad() failed')

    # 조건검색 조건명 리스트를 받아온다.
    # 조건명 리스트(인덱스^조건명)
    # 조건명 리스트를 구분(“;”)하여 받아온다
    @pyqtSlot(result=str)
    def GetConditionNameList(self):
        return self.ocx.dynamicCall("GetConditionNameList()")

    # 조건검색 종목조회TR송신한다.
    # LPCTSTR strScrNo : 화면번호
    # LPCTSTR strConditionName : 조건명
    # int nIndex : 조건명인덱스
    # int nSearch : 조회구분(0:일반조회, 1:실시간조회, 2:연속조회)
    # 1:실시간조회의 화면 개수의 최대는 10개
    @pyqtSlot(str, str, int, int)
    @SyncRequestDecorator.kiwoom_sync_request
    def SendCondition(self, scrNo, conditionName, index, search):
        self.ocx.dynamicCall("SendCondition(QString,QString, int, int)", [scrNo, conditionName, index, search])

    # 실시간 조건검색을 중지합니다.
    # ※ 화면당 실시간 조건검색은 최대 10개로 제한되어 있어서 더 이상 실시간 조건검색을 원하지 않는 조건은 중지해야만 카운트 되지 않습니다.
    @pyqtSlot(str, str, int)
    def SendConditionStop(self, scrNo, conditionName, index):
        self.ocx.dynamicCall("SendConditionStop(QString, QString, int)", [scrNo, conditionName, index])

    # 복수종목조회 Tran을 서버로 송신한다.
    # OP_ERR_RQ_STRING – 요청 전문 작성 실패
    # OP_ERR_NONE - 정상처리
    #
    # sArrCode – 종목간 구분은 ‘;’이다.
    # nTypeFlag – 0:주식관심종목정보, 3:선물옵션관심종목정보
    @pyqtSlot(str, bool, int, int, str, str)
    @SyncRequestDecorator.kiwoom_sync_request
    def CommKwRqData(self, arrCode, next, codeCount, typeFlag, rQName, screenNo):
        self.ocx.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)",
                             [arrCode, next, codeCount, typeFlag, rQName, screenNo])

    # 실시간 등록을 한다.
    # strScreenNo : 화면번호
    # strCodeList : 종목코드리스트(ex: 039490;005930;…)
    # strFidList : FID번호(ex:9001;10;13;…)
    #   9001 – 종목코드
    #   10 - 현재가
    #   13 - 누적거래량
    # strOptType : 타입(“0”, “1”)
    # 타입 “0”은 항상 마지막에 등록한 종목들만 실시간등록이 됩니다.
    # 타입 “1”은 이전에 실시간 등록한 종목들과 함께 실시간을 받고 싶은 종목을 추가로 등록할 때 사용합니다.
    # ※ 종목, FID는 각각 한번에 실시간 등록 할 수 있는 개수는 100개 입니다.
    @pyqtSlot(str, str, str, int, result=int)
    def SetRealReg(self, screenNo, codeList, fidList, optType):
        return self.ocx.dynamicCall("SetRealReg(QString, QString, QString, QString)",
                                    [screenNo, codeList, fidList, optType])

    # 종목별 실시간 해제
    # strScrNo : 화면번호
    # strDelCode : 실시간 해제할 종목코드
    # -화면별 실시간해제
    # 여러 화면번호로 걸린 실시간을 해제하려면 파라메터의 화면번호와 종목코드에 “ALL”로 입력하여 호출하시면 됩니다.
    # SetRealRemove(“ALL”, “ALL”);
    # 개별화면별로 실시간 해제 하시려면 파라메터에서 화면번호는 실시간해제할
    # 화면번호와 종목코드에는 “ALL”로 해주시면 됩니다.
    # SetRealRemove(“0001”, “ALL”);
    # -화면의 종목별 실시간해제
    # 화면의 종목별로 실시간 해제하려면 파라메터에 해당화면번호와 해제할
    # 종목코드를 입력하시면 됩니다.
    # SetRealRemove(“0001”, “039490”);
    @pyqtSlot(str, str)
    def SetRealRemove(self, scrNo, delCode):
        self.ocx.dynamicCall("SetRealRemove(QString, QString)", [scrNo, delCode])

    # 차트 조회한 데이터 전부를 배열로 받아온다.
    # LPCTSTR strTrCode : 조회한TR코드
    # LPCTSTR strRecordName: 조회한 TR명
    # ※항목의 위치는 KOA Studio의 TR목록 순서로 데이터를 가져옵니다.
    # 예로 OPT10080을 살펴보면 OUTPUT의 멀티데이터의 항목처럼 현재가, 거래량, 체결시간등 순으로 항목의 위치가 0부터 1씩 증가합니다.
    @pyqtSlot(str, str, result=str)
    def GetCommDataEx(self, trCode, recordName):
        return dumps(self.ocx.dynamicCall("GetCommDataEx(QString, QString)", [trCode, recordName]))

    # 차트의 특정 조회데이터를 받아온다.
    @pyqtSlot(str, str, int, str, result=str)
    def GetCommData(self, trCode, recordName, nIndex, itemName):
        return self.ocx.dynamicCall("GetCommData(QString, QString, int, QString)",
                                    [trCode, recordName, nIndex, itemName])

    # 리얼 시세를 끊는다.
    # 화면 내 모든 리얼데이터 요청을 제거한다.
    # 화면을 종료할 때 반드시 위 함수를 호출해야 한다.
    # Ex) openApi.DisconnectRealData(“0101”);
    @pyqtSlot(str)
    def DisconnectRealData(self, scnNo):
        self.ocx.dynamicCall("DisconnectRealData(QString)", [scnNo])

    # 종목코드의 한글명을 반환한다.
    # strCode – 종목코드
    # 종목한글명
    @pyqtSlot(str, result=str)
    def GetMasterCodeName(self, code):
        return self.ocx.dynamicCall("GetMasterCodeName(QString)", [code])

    # 국내 주식 시장별 종목코드를 ;로 구분하여 전달
    # strMarket – 종목코드
    # 마켓 구분값
    # 0 : 장내
    # 10 : 코스닥
    # 3 : ELW
    # 8 : ETF
    # 50 : KONEX
    # ...
    @pyqtSlot(str, result=str)
    def GetCodeListByMarket(self, strMarket):
        return self.ocx.dynamicCall("GetCodeListByMarket(QString)", [strMarket])

    # 입력한 종목의 전일가를 전달
    # strCode – 종목코드
    def GetMasterLastPrice(self, code):
        return self.ocx.dynamicCall("GetMasterLastPrice(QString)", [code])

    # 통신 연결 상태 변경시 이벤트
    # nErrCode가 0이면 로그인 성공, 음수면 실패
    def OnEventConnect(self, errCode):
        if errCode == 0:
            print('로그인 성공!')
        else:
            print('Error')
            kill(getpid(), 9)

    # 수신 메시지 이벤트
    def OnReceiveMsg(self, scrNo, rQName, trCode, msg):
        print('_OnReceiveMsg()', scrNo, rQName, trCode, msg)

    # 실시간 시세 이벤트
    def OnReceiveRealData(self, jongmokCode, realType, realData):
        # print('_OnReceiveRealData', jongmokCode, realType, realData)
        pass

    # 체결데이터를 받은 시점을 알려준다.
    # sGubun – 0:주문체결통보, 1:잔고통보, 3:특이신호
    # sFidList – 데이터 구분은 ‘;’ 이다.
    def OnReceiveChejanData(self, gubun, itemCnt, fidList):
        # print('_OnReceiveChejanData()', gubun, itemCnt, fidList)
        pass

    # 로컬에 사용자조건식 저장 성공여부 응답 이벤트
    def OnReceiveConditionVer(self, ret, msg):
        print('_OnReceiveConditionVer()', ret, msg)

    # 편입, 이탈 종목이 실시간으로 들어옵니다.
    # strCode : 종목코드
    # strType : 편입(“I”), 이탈(“D”)
    # strConditionName : 조건명
    # strConditionIndex : 조건명 인덱스
    def OnReceiveRealCondition(self, code, strType, conditionName, conditionIndex):
        print('_OnReceiveRealCondition()', code, strType, conditionName, conditionIndex)

    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrCondition(self, scrNo, codeList, conditionName, index, next, **kwargs):
        print('_OnReceiveTrCondition()', scrNo, codeList, conditionName, index, next)

    # Tran 수신시 이벤트
    @SyncRequestDecorator.kiwoom_sync_callback
    def OnReceiveTrData(self, scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message, splmMsg,
                        **kwargs):
        print('OnReceiveTrData()', scrNo, rQName, trCode, recordName, prevNext, dataLength, errorCode, message,
              splmMsg)

class SpartaQuant(QMainWindow):
    def __init__(self):
        super().__init__()
        self._kiwoom = Kiwoom()

        t1 = Thread(target=self.main_thread)
        t1.daemon = True
        t1.start()

    def main_thread(self):
        ###############################
        # 1. 로그인                    #
        ###############################

        # 로그인 시도
        self._kiwoom.CommConnect()

        # 로그인 완료 대기
        while True:
            if self._kiwoom.GetLoginInfo("ACCOUNT_CNT") != "":
                break
            print("로그인 대기 중...")
            sleep(5)
        sleep(5)


if __name__ == "__main__":
    app = QApplication(argv)
    myWindow = SpartaQuant()
    trader = myWindow._kiwoom
    app.exec_()

 

 

2. 아래쪽 터미널을 열고 'pip install PyQt5'를 입력하여 모듈을 설치한다.

< 그냥 빨간줄 눌러서 설치하셔도 됩니다 >

 

 

 

 

3. 프로그램 Run을 하면 로그인을 입력하는 화면이 나온다. 꼭 모의투자 접속에 체크하여 로그인 하기!

: 버전 문제로 해당 프로그램을 종료하고 확인을 누르라는 말이 뜰 수 있는데, 하라는 대로 하면 됩니다. Run 버튼 쪽에 빨간 네모를 누르시면 종료할 수 있습니다.

 

 

 

4. 로그인에 성공하였으면 컴퓨터 오른쪽 화단에 위를 가르키는 표시가 있는데, 이를 누르고 화려한 네모를 우클릭하여 계좌비밀번호 저장을 누른다.

 

 

 

5. 빈칸에 0000(모의계좌 비밀번호)를 입력하고 등록을 누른 후 AURO(자동로그인)을 체크한 후 닫기를 누른다.

 

 

 

 

6. 파이참 프로그램을 중지시키고 다시 실행하면 자동 로그인이 되는 것을 확인할 수 있다.

 

 

* 만약 자동로그인을 해제하고 싶다면*

더보기

c 드라이브 -> OpenAPI -> system 에서 Autologin.dat을 지우면 된다.

 

 

 

 

7. 코드 설명

<request>
<callback>

 

비동기식 키움 API를 동기식으로 사용하기위해 SyncRequestDecorator를 사용하여 데이터 요청후 즉시 응답을 받을 수 있도록 구성되어있다.

728x90
반응형
728x90
반응형

*파이썬 설치 링크*

https://www.python.org/downloads/windows/

 

Python Releases for Windows

The official home of the Python Programming Language

www.python.org

 

1. 키움 API와 통신하기 위해서는 무조건 32-bit로 깔아야 한다. 파이썬을 설치하고 경로를 복사해둔다.

: 저도 64-bit 쓰고있었지만 32-bit를 추가로 깔았습니다.

 

 

 

 

2. 파이참 다운로드 사이트에 접속하여 community로 설치한다.

 

https://www.jetbrains.com/ko-kr/pycharm/download/#section=windows

 

다운로드 PyCharm: JetBrains가 만든 전문 개발자용 Python IDE

 

www.jetbrains.com

< 설치는 defualt(기본)값으로 설치 >

 

 

 

3. 윈도우즈 검색에서 pycharm을 실행시키고 사용에 동의한다.

< 동의 후 메일은 보내는 것으로 합시다 >

 

 

4. New Project를 눌러 새로운 프로젝트를 생성한다.

 

 

 

: 위치는 바탕화면(Desktop)으로 설정하고 Create 버튼을 눌러 생성합니다. (저는 바탕화면에서 폴더를 하나 더 생성하여 넣었습니다.)

 

 

 

 

5. 위에 재생버튼 처럼 생긴 Run 버튼을 눌러주어 하단에 'Hi, PyCharm'이 잘 나오는 것을 확인한다.

 

 

 

6. main.py에 해당 코드를 넣고 실행한다.

import platform
print(platform.architecture())

 

: 하단에 '32bit'라고 나와야 정상이다. 본인은 64bit로 연동이 된 것 같으니 고쳐보도록 하겠다.

 

▼32bit로 연동하는 방법

더보기

1. File -> settings로 이동한다.

 

 

 

2. Project: SpartaQuant(자신의 파일 이름) -> Python Interpreter -> 톱니바퀴 -> Add를 클릭한다.

 

 

 

3. Virtualenv Environment -> Existing environment 클릭 -> ... 클릭

 

 

4. 파이썬 설치 위치를 복붙해서 위에 \AppData\Local 등이 적혀진 곳에 넣고 python.exe 파일을 선택한 후 OK를 누른다.

: 파이썬이 설치된 위치인 AppData는 숨겨진 폴더이므로 직접 위치를 작성해줘야 한다. 위치를 모르는 경우에는 하단 링크를 참고하면 좋을 것 같다.

기본 파이썬 모듈 설치하는 방법 (크롤링 준비) :: 러아니푸의 공부방 (tistory.com)

 

기본 파이썬 모듈 설치하는 방법 (크롤링 준비)

1. 찾기에서 idle을 검색하고 파일 위치 열기를 누른다. 2. 바로가기 프로그램이기 때문에 거기서 한번 더 파일 위치 열기를 누른다. 3. 파일 위치를 열고 Scripts를 누른 후, AppDate부터 위치를 복사한

fjdkslvn.tistory.com

 

 

 

5. 이어서 OK 버튼을 쭉 누른다.

 

 

 

6. 다시 코드를 Run하여 '32bit'가 나오는 것을 확인한다.

 

 

 

 

7. 주식자동매매를 위한 파이썬 환경 설정이 끝났다.

728x90
반응형
728x90
반응형

https://www.kiwoom.com/h/customer/download/VOpenApiInfoView

 

키움증권

키움 Open API+는 프로그램 개발 확장성을 고려하여 OCX 컨트롤로 제작 지원합니다. 사용자 편의에 따라 VB, 엑셀, 웹기반, MFC 등으로 프로그램 제작이 가능합니다. 데이터 요청 및 수신은 TR 서비스

www.kiwoom.com

 

1. 링크로 들어가서 로그인을 한 후 '사용 신청하러 가기'를 클릭한다.

 

 

2. 서비스 사용 등록과 개인정보 수집 이용 모두 동의 후 등록 버튼들을 누른다.

 

 

3. 처음 들어갔던 링크로 다시 돌아가서 2단계인 OpenAPI+ 모듈을 다운로드 및 설치한다.

< 설치는 default(기본)값으로 하기 >

 

 

4. 3단계에서 KOA Studio를 다운받는다.

: 압축파일이 받아지기 때문에 압축을 풀어둡니다.

 

 

 

 

5. Visual C++ 2010 재배포 가능 패키지를 다운받는다.

Download Microsoft Visual C++ 2010 Service Pack 1 재배포 가능 패키지 MFC 보안 업데이트 from Official Microsoft Download Center

 

Microsoft Visual C++ 2010 Service Pack 1 재배포 가능 패키지 MFC 보안 업데이트

Visual Studio 2010를 사용하여 빌드하고 Microsoft Visual C++ 2010 서비스 팩 1 재배포 가능 패키지와 함께 제공되는 MFC 응용 프로그램을 약화시키는 보안 문제가 확인되었습니다.

www.microsoft.com

 

: vcredist_x86.exe만 다운받고 실행하면 됩니다.

 

 

 

 

 

6. KOAStudio를 실행한다.

: 왼쪽 하단에 버튼들이 있는데 하나씩 눌러보시는 것을 추천드립니다. 그 중 개발가이드는 잘 읽어보시는 것을 추천드립니다.

 

 

7. 파일 -> Open API 접속을 하여 키움증권 로그인을 한다.

 

 

 

 

 

8. TR 목록 ->  opt10001: 주식기본정보요청 -> INPUT -> 종목코드를 클릭하고 우측 종목코드에 삼성전자 코드를 입력한 뒤 조회 버튼을 누른다. (삼성전자: 005930)

: 해당 종목에 대한 정보가 하단에 나오는 것을 확인할 수 있다.

728x90
반응형
728x90
반응형

- 백테스팅이란?

: 뒤를 실험해본다. 즉 지나온 과거를 대상으로 실험해보는 것이다. 과거 데이터를 통해 가상 매매를 하는 방식으로 투자기법을 테스트 해본다.

 

 

* 사용할 백테스팅 사이트*

https://www.intelliquant.co.kr/

 

인텔리퀀트 - IntelliQuant

알고리즘 개발, 백테스팅, 실전투자까지 퀀트 투자를 위한 최고의 플랫폼

www.intelliquant.co.kr

 

 

 

 

 

 

1.  인텔리퀀트에 간단하게 가입한 후에 계정관리로 들어가서 멤버십을 PRO로 바꾼다.

: PRO는 백테스팅 기간 제한이 없기 때문에 업그레이드 하는 것이 좋다.

 

 

: 인텔리퀀트 가입 즉시 3만 포인트를 지급하기 때문에 무료로 업그레이드 할 수 있다.

 

 

 

 

 

 

2. 스튜디오를 누르고 새 블록 알고리즘을 누른다.

 

 

 

 

 

3. 기본 설정으로 만들기 위해 보라색 블록에서 ‘당기순손실 종목 제외‘, ’영업현급흐름 (-)종목 제외‘를 체크 해제하고, 남색 블록에서 'PER', 'ROA', ’12개월수익률‘을 체크 해제한다.

< 생성 직후 모습 >

 

< 체크 해제 후 기본 모습 >

 

 

 

 

 

4. 기본 모습에서 ‘PER', 'PBR'을 체크를 해준다.

: 요약하자면 PERPBR을 가지고 상위 10개 종목을 매수하겠다는 의미다.

 

 

 

 

 

 

5. 날짜를 선택하고 백테스팅을 시작한다.

: 백테스팅 기간이 너무 짧으면 테스팅이 실행되지 않는다. 적어도 1년 이상의 기간을 잡고 테스팅 하는 것이 좋다.

 

 

 

 

 

 

 

6. 성과지표를 누르면 예상 수익률을 간단하게 볼 수 있다. 이제 스크립트로 내보내기를 눌러 저장하자.

 

 

 

 

 

 

 

7. 저장 후 뒤로가기를 누르면 새 블록 알고리즘이 생성된 것을 볼 수 있다. 

: 타입이 검정 네모로 이루어진 것은 블록 알고리즘 형태이고, 타입이 </>형태로 이루어진 것은 자바스크립트 형태이다.

728x90
반응형
728x90
반응형

1. 일반 투자자들이 항상 돈을 잃는 이유

 

- 시장이 상승 할 때 돈이 계속 벌리는 느낌에 계속 돈을 넣다가 결국 파산

: 차익 실현 때문에 주가가 빠지게 되고 이후 더욱 매수를 했을 때 손실이 나게된다. 그런 경우 주가가 다시 오를떄까지 존버를 하게되는데 그동안 좋은 주식을 살 수 없고 주식이 망하는 경우 파산하게 된다.

 

 

- 시장이 하락 할 때 주가가 싸게 느껴져서 계속 돈을 넣다가 결국 파산

: 주가가 1~2%만 떨어져도 굉장히 싸게 느껴지는데 그 순간 많은 매수를 했다가 주가가 10%정도 떨어지게 된다면 파산의 길을 걷게된다.

=> 사실 실제로 주식을 해봤다면 더욱 잘 알고있을 것이다.

 

 

- 멘탈이 너무 약해서 문제

: 이미 수익권이지만 더 오를 것 같아서 가지고있다가 결국 하락하거나, 이미 충분히 올랐다고 생각해서 팔았는데 더 많이 올라서 씁쓸한 마음을 가지면 평정심으 유지할 수 없음

=> 기본적으로 매매 원칙이 없고, 자신의 기법이 믿음직스럽지 않기 때문에 드는 마음

 

 

- 모호한 투자원칙으로 인한 손실누적

: 확실한 투자원칙을 만들고 지켜야 하는데 기분에 따라 매매기법이 달라짐

 

 

- 정보력의 한계

: 막대한 자금을 운용하는 기관과 외국인에 비해 우리는 정보력의 한계가 존재한다. 우리의 귀에 들어온 정보는 가장 마지막에 전달되는 정보이다.

 

 

 

 

 

 

2. 시장을 이길 수 있는 방법

 

- 시간은 우리의 편이다. 분기마다 성과를 내야하는 기관 및 외국인과 달리 우리는 식간에 구애받지 않는다.

 

- 백테스팅을 통한 투자원칙을 통해 실제 과거 데이터를 기반으로 투자기법을 확립한다. 과거에 수익률이 좋았던 주식은 현재에도 수익률이 높을 가능성이 있다.

 

- 백테스팅을 통한 투자로 시간을 아낄 수 있고, 안정적인 수입으로 멘탈을 강화할 수 있다.

728x90
반응형
728x90
반응형

1. 키움증권 로그인 후 상시모의투자에 참가 신청한다.

<1000만원 3개월을 추천함>

 

 

2. 영웅문4를 ID로 로그인하지말고 모의투자로 로그인한다.

 

 

3. 영웅문4에서 4989를 검색하여 주문창으로 이동한다.

 

4. 계좌번호 우측에 비밀번호(4자리)를 입력하고 아래 종목의 돋보기를 눌러 원하는 종목을 선택한다.

 

 

5. 종류는 시장가로 설정하고 수량을 원하는 만큼 입력한 후 현금매수(F9)를 누른다.

매도 방식도 동일하다.

728x90
반응형
728x90
반응형

퀀트(Quant) 투자

- Quantitative Analysis의 약자로 정량적 분석이라는 뜻을 가짐

- 재무재표, 주식의 가격 등 숫자 데이터를 분석하여 매매하는 기법을 뜻함

- 퀀트 알고리즘 매매는 2020년 전세계 금융시장 평균 10.3%대로 증가

- 현재 퀀트 시장의 총 규모는 약 1조달러 이상으로 집계

- 오로지 데이터만을 가지고 매매하기 때문에 주식 자동매매에 적합

 

=> 주식의 코로나발 대폭락의 원인이 퀀트 투자 때문이다. 퀀트 알고리즘이 코로나의 위기를 감지하고 많은 양의 주식을 매도했기 때문에 폭락했다.

 

 

 

 

 

 

 

퀀트 투자 기법(1) - 기술적 분석

- 전날 주가가 상승했다면 오늘 매매를 하고, 주가가 하락했다면 매매를 하지 않는다.

- 오늘 시초가+Range 값을 초과했다면 매수한다.

- 다음날 곧바로 매도한다.

 

** 다만 국내 주식에는 적용할 수 없다. 사고 팔 때 마다 누적 세금이 발생하기 때문이다 **

 

 

 

 

 

 

 

 

 

퀀트 투자 기법(2) - 재무재표 분석

 

[조엘 그린블라트, 마법공식]

 

- 당기순이익: 1년동안 발생한 순이익

- ROA: (당기순이익/총자산)*100

=> ROA가 높을수록 자산대비 순이익이 많이 발생한다는 뜻

 

- EPS = 당기순이익/발행주식수

=> 주식을 하나 가지고 있을 때 얼마나 수익을 벌 수 있는지

- PER = 현재주가/주당순이익(EPS)

=> PER는 낮을수록 이득을 볼 수 있음. 주가가 낮고 순이익이 높을수록 싸게 사고 돈을 많이 버는 것이기 때문이다.

 

 

 

ROA는 높을수록, PER는 낮을수록 좋은 것이다.

PER는 낮게, ROA는 높게 설정하여 순위를 20위까지 매기고 20개의 종목을 같은 가격에 매수한다.

 

 

 

- 리밸런싱: 자산들의 비중을 조절하는 과정

: 매월 리밸런싱을 하는데 만약 기존에 사놨던 종목이 20등 밖으로 이탈하면 전액 매도한다. 그리고 20등 안으로 들어온 종목을 매수한다. 만약 이탈 종목이 없다면 주가가 높아진 종목의 일부를 매도하고 주가가 낮아진 종목을 매수한다.

 

 

 

[파마-프렌치, 3팩터 모델]

 

- 낮은 시가총액, 높은 순자산, 낮은 시장의 관심

- BPS: 회사총자산/발행주식수

=> 이 회사의 주식을 하나 갖고 있을 때 이 회사의 자산 중 내가 얼마를 갖고있는지를 뜻한다. 회사가 망해도 최소 얼마는 받을 수 있는지를 알 수 있다.

- PBR: 현재주식가격/주당순자산(BPS)

=> 현재 주식가격이 BPS보다 낮다면 회사가 망해도 이득을 본다. 수치가 낮을수록 좋은 것이다.

 

 

 

 

 

 

 

 

 

퀀트 투자 기법(3) - 모멘텀

 

[토비 모스코비츠, 시계열 모멘텀]

 

- 시계열 모멘텀 = (한달전주가/일년전주가)*100

=> 현재가 아닌 한달 전 주가와 비교하는 이유는 최고점을 찍고 나서 주주들이 이익 실현을 위해 매도하기 때문에 일시적으로 값이 낮아지기 때문이다.

 

 

[게리 안토나치, 듀얼 모멘텀]

 

- 듀얼 모멘텀(상대 모멘텀 + 절대 모멘텀)을 활용한 매매

- 상대 모멭넘 = 시계열 모멘텀

- 절대 모멘텀: 은행 이자보다 작년 주식 수익률이 낮으면 한 해 매매 중지

 

 

 

 

 

 

 

 

퀀트 투자 기법(4) - 기타

 

[조셉 피오트로스키, F-Score]

 

- 9가지 재무건전성 지표를 만들어 각 항목 당 점수를 매김

- 재무건전성 점수가 높은 20개 종목 선정 후 리밸런싱

 

전기 순이익이 흑자 일 경우 1
작년대비 영업활동현금 흐름이 증가 하였으면 1
작년대비 ROA가 개선되었으면 1(수익률)
영업활동현금 흐름이 순이익을 초과하면 1(이익의 건전성)
작년보다 총자산대비 장기부채 비율이 줄어들었으면 1
작년보다 유동비율이 증가하였으면 1(순운전자본 증가)
작년대비 주식수가 증가하지 않았으면 1(향후 잠재적 희석 가능성)
작년대비 매출총이익이 향상되었으면 1
작년대비 총자산회전율이 증가하였으면 1(생산성)

 

 

 

[레이달리오, 올웨더 포트폴리오]

 

- 어느 경제 상황이든 가격이 오르는 자산이 있다는 것을 밝혀냄

- 황금비율 = 주식 30%+장기국채40%+중기국채15%+원자재,7.5%

728x90
반응형

+ Recent posts