PYTHON/AUTO TRADE SYSTEM

[자동 매매 시스템 구축하기] 알고리즘 구축하기 (9) - 알고리즘 구현하기 ④

  • -

이번 게시글에서는 지난 게시글에서 구현하지 못했던 일자 데이터를 함께 출력하는 기능을 구현하는 것부터 시작해서, 오류를 바로 잡아 매수 예정 가격 데이터까지 모두 계산해보도록 하자.

 

 

일자 데이터 출력하기

사실 파이썬의 print("")문보다도 더 좋은 게 print(f"")이다. 왜냐하면 그 안에 중괄호, 즉 {}를 넣어 해당 위치에 변수를 직접 입력할 수 있기 때문이다. 이제 코드를 돌려서 언제 고가 데이터가 갱신되었는지 확인해보도록 하자.
※ Line : 8

    def run(self):
        self.highest_price = self.start_price

        for data in self.min5_data.itertuples():
            high = int(data.high)

            if high > self.highest_price:
                print(f"고가가 갱신되었습니다... {data.date}, {data.time}")
                self.highest_price = high  ## 고가 데이터 재입력
                self.buy_price = (self.start_price + self.highest_price) / 2

        print(self.buy_price)

 

세상에, 2021년 7월 1일부터 8월 5일까지 고가가 갱신된 데이터를 가지고 시가 데이터인 58,400원과 계산했고, 그 결과값으로 70,850원을 전달해줬다. 왜 이런 오류가 발생하는 것일까?

고가가 갱신되었습니다... 20210701, 20210701090000
고가가 갱신되었습니다... 20210705, 20210705092000
고가가 갱신되었습니다... 20210706, 20210706090000
고가가 갱신되었습니다... 20210706, 20210706093000
고가가 갱신되었습니다... 20210707, 20210707090000
고가가 갱신되었습니다... 20210803, 20210803150000
고가가 갱신되었습니다... 20210804, 20210804090000
고가가 갱신되었습니다... 20210804, 20210804090500
고가가 갱신되었습니다... 20210804, 20210804113000
고가가 갱신되었습니다... 20210804, 20210804120500
고가가 갱신되었습니다... 20210804, 20210804133500
고가가 갱신되었습니다... 20210804, 20210804143000
고가가 갱신되었습니다... 20210805, 20210805090000
70850.0

 

 

for문의 대상인 self.min5_data를 수정해야 한다.

현재 5분봉 차트 데이터인 self.min5_data는 2021년 7월부터 2022년 7월까지의 모든 5분봉 데이터가 담겨 있기 때문에, for문을 돌리게 되면 2021년부터 시작해서 고가 데이터를 계산하게 된다. 아래의 사진을 보면 2021년 7월 경에 최고가가 83,300원으로 기록되어 있는데, 그렇다면 83,300원과 우리가 얻어낸 시가 데이터인 58,400원의 중심값이 70,850원이 맞는지는 직접 계산해보고 넘어가도록 하자. (직접 계산하길 바란다.)

 

계산이 올바르게 이루어졌다면, 우리는 self.min5_data의 데이터를 어떻게 수정해줘야 할까? 바로 우리가 입력했던 기준일자 데이터인 self.start_date 이후의 데이터만 입력되도록 수정해줘야 한다. 사실 어떻게 보면 당연한 이야기이기도 한 게, 2022년 7월 15일의 시가인 58,400원을 기준으로 고가가 갱신될 때 매수 예정가를 계산하는데 2021년의 데이터가 왜 필요한가? 

물론 사람이 직접 계산한다면 이런 오류는 발생하지 않지만, 프로그래밍이 이렇다. 모든 데이터들을 정확하게 지정해줘야 하고, 엉뚱한 곳으로 빠지지 않게 그 범위를 명확하게 한정지어서 전달해줘야 한다.
※ Line : 9

class algo1:

    def __init__(self, min5_data, start_date):
        self.min5_data = min5_data
        self.start_date = start_date
        self.min5_data['date'] = self.min5_data['time'].str.slice(start=0, stop=8)
        data_of_start_date = self.min5_data[self.min5_data['date'] == self.start_date].reset_index(drop=True)
        self.start_price = int(data_of_start_date.loc[0, 'open'])
        self.tem_min5_data = self.min5_data[self.min5_data['date'] >= self.start_date]      ## 기준일자 이후의 5분봉 차트 데이터

 

이제 run 함수 내에서 for의 대상이 되는 데이터도 self.min5_data가 아닌 self.temp_min5_data로 변경해주도록 하자.
※ Line : 4

    def run(self):
        self.highest_price = self.start_price

        for data in self.min5_data.itertuples():
            high = int(data.high)

            if high > self.highest_price:
                print(f"고가가 갱신되었습니다... {data.date}, {data.time}")
                self.highest_price = high  ## 고가 데이터 재입력
                self.buy_price = (self.start_price + self.highest_price) / 2

        print(self.buy_price)

 

이제 코드를 돌려보면 아래와 같은 결과 데이터를 확인할 수 있다. 우리가 이전에 계산했던 값들로 보면, 58,400원과 이후 형성된 최고가인 62,100원의 중심값인 60,250원이 출력되는 것이 맞았는데, 어떤 결과값을 보여줄지 확인해보자.

고가가 갱신되었습니다... 20220715, 20220715090000
고가가 갱신되었습니다... 20220715, 20220715090500
고가가 갱신되었습니다... 20220715, 20220715110500
고가가 갱신되었습니다... 20220715, 20220715112000
고가가 갱신되었습니다... 20220715, 20220715125000
고가가 갱신되었습니다... 20220715, 20220715125500
고가가 갱신되었습니다... 20220715, 20220715132000
고가가 갱신되었습니다... 20220715, 20220715135500
고가가 갱신되었습니다... 20220715, 20220715144500
고가가 갱신되었습니다... 20220715, 20220715145000
고가가 갱신되었습니다... 20220715, 20220715145500
고가가 갱신되었습니다... 20220715, 20220715153000
고가가 갱신되었습니다... 20220718, 20220718090000
고가가 갱신되었습니다... 20220718, 20220718092000
고가가 갱신되었습니다... 20220718, 20220718122500
고가가 갱신되었습니다... 20220718, 20220718123500
고가가 갱신되었습니다... 20220718, 20220718135500
고가가 갱신되었습니다... 20220718, 20220718142000
고가가 갱신되었습니다... 20220718, 20220718142500
고가가 갱신되었습니다... 20220718, 20220718151500
고가가 갱신되었습니다... 20220720, 20220720090000
60250.0

 

 


728x90

 

 

아직도 갈 길이 멀다.

진짜 진짜 갈 길이 멀다. 이제 매수 예정가 하나 계산했기 때문이다. 알고리즘 파일의 코드 줄 수를 보면 알고리즘이라기엔 추할 정도로 40줄이 채 안 되는 길이이다. 

이제 뭘 구현해야 할지 감이 안 오겠지만, 간단하다. 매수 가격을 계산했으니, 해당 종목이 그 매수 가격을 터치했던 이력이 있는지를 살펴봐야 한다. 그러니까, 이미 살 수 있었던 건지 아니면 살 수 없었으므로 현재의 매수 가격이 유효한지를 살펴보자는 것이다. 이는 곧 매수의 유효성을 살펴보는 것과도 동일하다.

 

그럼 데이터가 매수 예정 가격에 닿았는지 안 닿았는지는 어떤 기준에 의해 판단할 수 있을까? 이 부분도 단순하게 인간의 입장에서 생각하지 말고, 컴퓨터의 입장에서 생각해봐야 한다. 우리가 구현하고자 하는 알고리즘을 컴퓨터에게 일러주는 것은 예상 외로 복잡하고 어려운 일이다.

일단은 매수 예정 가격 데이터가 정상적으로 계산되었으니 다른 기능은 이후에 구현하는 것으로 하고, 여기서는 주가 데이터에 대한 알고리즘 구현의 이론적인 부분을 조금 더 살펴보고 넘어가도록 하자. 

 

 

우리가 가장 먼저 직면할, 그리고 가장 대표적인 오류

가장 먼저 우리는 시가와 이후 형성된 고가 간 중심값을 매수 예정가로 계산하고 있는데, 매수 예정가를 이탈했었는가를 판단하는 과정에서 우리는 아래와 같은 코드를 구현할 것이다.

if data.low <= self.buy_price:
    print("이미 매수되었습니다.")

 

여기서, 이 조건문에 해당하는 시점이 우리가 원하는 시점이 아닐 수 있다. 어떤 이야긴지 아래의 도식을 살펴보도록 하자. 일단 주가의 움직임이 좌측의 도식처럼 쭈욱 상승한 후에 고점을 형성하고 하락한다면 이 내용은 아무짝에도 쓸모없는 내용이겠지만, 실제 주가의 움직임은 우측의 도식처럼 상승과 하락, 즉 등락을 반복한다. 따라서 우측의 차트 데이터를 기반으로 매수 예정가를 계산할 때, 우리는 어떤 문제점에 직면하게 될까? 

 

아래와 같은 문제점이 분명히 발생할 수밖에 없다. 바로, 똑같은 봉의 형태라 하더라도 어떤 경우(좌측 도식)에는 이전에 중심값을 터치한 것으로 간주되어 매수의 기회가 없다고 판단할 수도 있지만, 어떤 경우(우측 도식)에는 매수가 가능한 것으로 판단할 수 있는 것이다. 

그렇다면 여기서 우리가 원하는 모습이 어떠한 모습인지에 대한 디테일들을 조금 더 추가해나갈 필요가 있다. 즉, 어떠한 형태일 때 (컴퓨터로 하여금) 매수가 가능한 것으로 판단하도록 할 것인지 그리고 어떠한 흐름일 때 (컴퓨터로 하여금) 매수가 불가능한 것으로 판단하도록 할 것인지에 대한 디테일들을 살려줘야 한다는 것이다.

알고리즘, 만만하게 봤다가는 큰 코 다친다. 본인 역시 일년이 넘는 시간동안 하나의 알고리즘이 정상적으로 동작하기까지 수많은 시행착오를 겪었으며 그렇기에 이런 오류가 발생함으로 인해 놓쳤던 거래와 하지 말았어야 했던 거래들이 발생하는 상황을 보고 속이 참 많이 쓰렸다.

실제로 이러한 데이터 외에도 다양한 오류들을 직면하게 될 수 있다. 여기서 말하는 오류란, 우리가 앞서 try: except:로 처리할 수 있는 프로그래밍 과정에서의 오류가 아니다. 그러한 오류와는 전혀 급이 다르다. 예를 들어, 위의 도식 중 좌측의 도식이 "005930" 종목에서 발생했고, 프로그램은 "005930" 종목에 대해 이미 매수가 진행되었어야 하는 종목이며 이미 매수가격으로부터 너무 많은 상승이 발생한 종목이라고 판단하고 우리에게 결과 데이터를 전달해주지 않는다면, 우리는 이 종목이 어떠한 판단 기준에 의해 걸러진 종목인지 전혀 알 수 없다. 오류가 없었으니 그냥 "없구나." 하고 넘어가는 수밖에 없다. 하지만 내면을 살펴보면 오류가 분명하게 존재하는 알고리즘인 것이다. 개발자가 모를 뿐, 프로그램은 온전한 명령에 의해서만 작동하고 있다.

다만 이런 오류들은 약간의 센스를 발휘하면 얼마든지 해결할 수 있다. 여기서 전하고 싶은 내용은 본인이 구현하고자 하는 전략에 대한 기본적인 개념들을 가지고 있던지 아니면 구현하고자 하는 이상적인 알고리즘의 형태에 대한 구상이 있어야만 본인이 원하는 알고리즘을 구현할 수 있다는 것이다. 하지만 그러한 배경이 없다고 아무것도 구현할 수 없는 상황에 처하는 것은 아니다. 다만 구현하고자 하는 이상이 있는 사람들에 비해 많은 시간이 소요될 뿐이다. (이러한 이상은 백테스트를 통해 전략들을 수정해나가면서 본인의 머리 속에 만들어낼 수도 있다.)

 

 

우리가 직면할 수 있는 그 외의 여러 가지 문제점

개발 과정에서, 특히 백테스트 과정이 되었든 아니면 실 거래 과정이 되었든 간에 상관 없이 언제 어디서든 본인이 의도했던 바대로 코드가 기능하지 않는 경우가 발생할 수 있다. 하지만 개발자들은 이러한 오류를 긴 시간이 걸리지 않아도 머지않아 분명하게 눈치챌 수 있으며, 어렵지 않게 해결할 수 있다.

가장 대표적인 개념이 분할 매수와 분할 매도 개념이다. 예를 들어, 4등분법에 의해 특정 종목의 상승 추세 중 중심 지점과 75% 지점에서 매수에 가담하도록 알고리즘을 구현했다고 가정해보자. 이 때 어떤 오류가 발생할 수 있을지 상상이 가는가? 만약 주가가 중심 지점에 닿지 않고 중심 지점보다 한 두 호가 위에서 머무르다가 갑자기 갭 하락으로 75% 지점까지 하락했다면 프로그램은 이를 어떻게 받아들이겠는가? 사람이라면 당연히 중심 지점에서 매수하지 못했던 물량을 75% 지점에서 매수에 가담하겠지만, 프로그램은 그렇게 할 수 없다. 그럼 어떻게 해야 되는가? 그런 예외 사항들을 하나 하나 체크해가면서 알고리즘 내에, 그리고 프로그램 내에 구현해주는 방법밖에 없다.

본인도 상상조차도 못했던 오류가 수도 없이 발생했지만, 약간의 잔꾀들을 부려 어렵지 않게 해결했고 해결해왔다. 이 이야기는 여기까지 하고, 다음 게시글에서는 다시 매수 가능 여부를 판단하는 기능들을 구현해보도록 하자. 이제부터 어려워진다. 하지만 이렇게 생각하면 마음이 한결 가벼울 것이다. 

 

 

당신이 하고자 하는 것을 컴퓨터에게 시키고자 하는 것,
당신이 그걸 하기 위해 쌓아왔던 모든 배경 지식을
컴퓨터에게도 수치화해서 지식을 습득시켜야만 컴퓨터도 그렇게 할 수 있다.

당신이 어렵게 그리고 오랜 기간 쌓아 온 지식일수록,
컴퓨터로 하여금 그 개념들을 인지하도록 하는 기간도 함께 길어진다.

 

이제부터 어려워질 거지만, 사실 자동 매매 시스템을 구현하기 위한 마지막 단계이기도 하다. 왜냐? 매수 가격과 매도 가격을 계산했으면 그걸 기반으로 거래를 진행하도록 구현하면 되기 때문이다. (물론 가격을 계산했으면 이제부터는 계좌를 관리해줘야 한다. 계좌 관리도 드럽게 빡세다. 그래도, 쫄지 말자.)

 

 


728x90
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.