2. 해외선물/2-4. 해외선물 API (사용)

(키움증권 해외선물 OpenAPI-W) 패턴 만들기 (2) 부동소수점 문제 회피하기

봄이오네 2023. 1. 15. 08:04
반응형
1. 들어가며
2. 사전 설명
1) 부동소수점 개념
2) 부동소수점 예시
3. 해외선물 매매에서 부동소수점 회피하기
4. 적용 사례
1) 최초 받은 로데이터 형태
2) float 붙이는 경우
3) float, abs를 붙이는 경우
4) float, abs, int를 붙여주는 경우
5. 마치며

1. 들어가며

지난 글에서는 해외선물 종목의 시가, 고가, 저가, 종가(OHLC)을
for문을 통해 1분전 데이터를 받아왔다.

이번 글에서는 패턴을 만들기 전에
소수점끼리의 계산 후 발생하는 부동소수점 에러 해결에 대해 알아보자.

(1) 1분전의 봉의 시가, 고가, 저가, 종가 받아오기
(2) 부동소수점 문제 회피하기
(3) 1분전 봉의 패턴만들기

부동소수점 회피하는 이유는...
필자는 패턴을 만들 때,
패턴 = str (고가 - 시가) & str (저가 - 시가) & str (종가 - 시가) 를 만드려고 한다.

즉, 패턴을 만들 때 부동소수점의 문제가 발생하니,
진입 시점을 적용하는게 힘들어서,
해당 문제를 고민하게 되었다.


2. 사전 설명

1) 부동소수점 개념

  • 개념 : 부동소수점은 컴퓨터에서 실수 표현시 소수점의 위치를 고정하지 않는 것을 말한다.
  • 발생 원인 : 컴퓨터는 0과 1의 2진법으로 사용하여 사칙연산을 하는데, 실수의 경우 가장 근사치의 값이 적용된다.

내용이 너무 어렵다. @.@
인터넷 검색을 하면, 자세한 내용이 나오니 참고하시길 바란다.

2) 부동소수점 예시

개념을 완벽하게 이해할 필요는 없는 것 같고,
아래 예시에서 "부동소수점"이 무엇인지 감을 잡도록 하자.
파이썬에서는 0.25-0.21 = 0.04와 동일하지 않다.!

 

더보기
그림1. 부동소수점 예시

 

< 그림1 >에서 확인하였듯이,

0.25 - 0.21 = 0.04와 동일하지 않는다는 내용을 확인할 수 있다.

0.25 - 0.21 = 0.04000000000000001 로 나타나기 때문에 0.04와 동일하지 않다는 것을 확인할 수 있다.

def aaa():
    if 0.25 - 0.21 == 0.04:
        print("동일합니다.")
    else:
        print("동일하지 않습니다.")
        print(f"0.25-0.21의 결과는 {0.25-0.21} 입니다.")
        print(0.04)
aaa()


# (expected result)
# 동일하지 않습니다.
# 0.25-0.21의 결과는 0.04000000000000001 입니다.
# 0.04

 


3. 해외선물 매매에서 부동소수점 회피하기

C언어로 작성된 인터프리터인 CPython이 내재적으로 가지고 있는
부동소수점 문제를 해결하는 것은 고수의 영역이다.
우리에게 필요한 것은 해외선물 매매만 하면 된다. ^^

생뚱맞은 이야기가 될 수 있겠지만,
< 그림2> 의 신호등에서 파란색이 있는가?
어릴 때부터 횡단보도를 건널 때는 "파란불"에 건너야 한다고 배웠는데,
파란색이 없.다.니....

녹색불을 파란불로 부르도록
묵시적인 사회적 합의가 있었기 때문이다.

그림2. 신호등


생뚱맞게 신호등은 왜 언급하였을까???
나스닥은 12345.67의 구조로 이루어져 있다.

나스닥 패턴을 만들 때,
소수점이 존재한 상태에서 사칙연산을 하여야 하는 것인가?

나스닥 12345.67달러 x 100 = 1234567 로 취급하면 부동소수점 문제는
발생하지 않는다.

이유는
부동소수점 문제는
소수점이 있는 실수 형태에서 발생하기 때문이다.

즉, < 그림2 >와 같은 신호등에서 녹색불을 파란색으로 부르듯 부동소수점이 발생하지 않도록 정수화 시키면, 부동소수점 문제는 발생하지 않는다. -_-+

다른 경우를 예를 들면, 브리티쉬 파운드의 경우
1.2008달러 x 10.000 = 12008달러로 정수화 시킨다.


4. 적용 사례

※ 받아온 데이터를 활용하는 4가지 사례를 설명한다.
결론부터 이야기하면,
필자는 4번째 유형으로
향후 코드를 설명할 예정이다.

57줄~61줄까지의 함수를 받아오는 형태에 대해 알아본다.

그림3. 수신받아온 데이터


아래는 전체 코드이다.

더보기
import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import pyautogui
import time

class btl_system():
    def __init__(self):
        self.kiwoom = QAxWidget("KFOPENAPI.KFOpenAPICtrl.1")
        print("로그인 시작!")

        self.kiwoom.OnEventConnect.connect(self.login_Connect)
        self.kiwoom.OnReceiveTrData.connect(self.trdata_get)

        self.kiwoom.dynamicCall("CommConnect(1)")
        self.login_event_loop = QEventLoop()
        self.login_event_loop.exec_()

        self.kiwoom.dynamicCall("GetCommonFunc(QString, QString)", "ShowAccountWindow", "")

    ########## 로그인 관련 함수 ##########
    def login_Connect(self, err_code):
        if err_code == 0:
            print('로그인 성공했습니다!')
            self.login_event_loop.exit()
        else:
            print('로그인 실패했습니다!')

    ########## 키움서버에 TR 요청하는 함수 모음 (키움 OPENApi-w에서 제공) ##########
    def rq_data_opc10002(self, stock_code_num, time_unit):
        self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "종목코드", stock_code_num)
        self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "시간단위", time_unit)
        self.kiwoom.dynamicCall("CommRqData(QString, QString, QString, QString)", "opc_10002", "opc10002", "", "1012")
        self.tr_event_loop = QEventLoop()
        self.tr_event_loop.exec_()

    def trdata_get(self, scrno, rqname, trcode, recordname, prenext):
        if rqname == "opc_10002":
            for i in range(1,2):
                dealed_time = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", i, "체결시간").strip()
                open = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", i, "시가").strip()
                high = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", i, "고가").strip()
                low = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", i, "저가").strip()
                close = self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", i, "현재가").strip()

                print(dealed_time)
                print(open)
                print(high)
                print(low)
                print(close)
                self.tr_event_loop.exit()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    btl = btl_system()

    btl.rq_data_opc10002("NQH23", "1")

    app.exec_()

 

1) 최초 받은 로데이터 형태

최초 나스닥 시가는 11111.250000 등으로 받아온다. (문자형)

  • 시가 : 11111.250000
  • 고가 : 11111.750000
  • 저가 : 11104.250000
  • 종가 : 11105.500000

 

 

2) float를 붙여준다.

57줄~61줄에서 시가(open)에 float를 붙여서 실수형으로 바꾼다.
결과는

  • 시가 : 11093.25
  • 고가 : 11095.25
  • 저가 : 11087.0
  • 종가 : 11089.0

 

 

3) float, abs를 붙이는 경우

float 와 abs를 붙이면, 소수점이 나온다. ㅠ_ㅠ

  • 시가 : 111007500.0
  • 고가 : 111067500.0
  • 저가 : 110977500.0
  • 종가 : 111055000.0

 

4) float, abs, int를 붙이는 경우

키움에서 받아온 데이터는 기본적으로 문자형이다.
① 문자형 앞에 float를 붙여주고
② float 에 곱하기 10000을 붙여준다.
③ int를 붙여준다.
④ 음수가 나오는 경우를 방지하기 위해 절대값(abs)를 붙여준다.

 


5. 마치며

패턴 만들기의 사전작업 형태로 부동소수점 문제를 근본적으로 해결할 수는 없지만,
매매를 위해 정수화(int)하여 패턴을 만들 때
사칙연산 시 부동소수점 문제는 회피할 수 있을 것 같다.

필자는 4종목을 매매할 예정이다.
나스닥 : 틱 단위 0.25
S&P 500 : 틱 단위 0.25
엔화 : 틱 단위 0.5
파운드 : 틱 단위 0.0001

시가 등에 10000을 곱한 것도 소수 4자리까지 표기되는
파운드도 정수화시켜서, 부동소수점 문제를 회피하고 싶었기 때문이다.

글이 상당히 길었다.
해외선물의 틱 단위가 소수점이다보니, 발생한 문제이다.
(국내주식은 호가 단위 차이는 있으나, 소수점이 없기 때문에
부동소수점 문제는 발생하지 않는다)

다음 글에서는 패턴 만들기를 알아보자.

반응형