PYTHON/AUTO TRADE SYSTEM

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

  • -

지난 게시글에서는 매수 가격과 매도 가격을 계산하는 과정에서 발생할 수 있는 몇 가지 오류와 추후 직면하게 될 오류 상황에 대해 간단하게 살펴봤다. 이번 게시글에서부터는 다시 본론으로 돌아와서, 매수 가격을 기반으로 해당 매수 가격에 닿았던 이력이 있는지 확인하는 방법에 대해 살펴보고자 한다.

 

 

매수가 진행된 종목인가, 아니면 상승만 했던 종목인가?

우리는 현재, 5일 이동평균선이 20일 이동평균선을 상향돌파한 시점에서의 시가 데이터와 이후 형성된 고가 데이터를 기반으로 하여 그 중심값에 해당하는 매수 예정 가격을 계산해냈다. 이제는 고가가 형성된 이후 매수 예정 가격에 닿았는지를 확인해봐야 한다. 어떤 방법으로 확인할 수 있을까?

일단 기본적으로, 현재 반복문(for 문) 내부에서는 고가 데이터가 갱신된 경우에 대해서만 다루고 있다. 저가 역시 고가와 마찬가지로, 가격이 갱신되면 새롭게 계산해줘야 하는 데이터들이 생기기 마련이다. 일단 아래와 같은 코드를 구현한 후에, 세부적인 내용들을 살펴보도록 하자.
※ Line : 2, 7, 15~17

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

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

            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
                self.lowest_price = high

            if low <= self.lowest_price:
                print(f"저가가 갱신되었습니다... {data.date}, {data.time}")
                self.lowest_price = low

        print(self.buy_price)

 

Line 2에서는 self.lowest_price라는 데이터에 0이라는 값을 입력해주었고, 이후 Line 7에서는 고가가 갱신되었을 경우(if high > self.highest_price:)에 self.highest_price에 high 데이터를 입력함과 동시에 self.lowest_price에도 high 데이터를 입력해주었다. 이렇게 하는 이유는, 저가가 갱신되었음을 판단할 수 있는 기준 데이터를 만들어주기 위함이다. 다시 말해, self.lowest_price라는 데이터는 분석 시점 이후에 형성된 일봉 상 저가를 의미하지 않는다. 왜냐하면 분석 시점 이후의 저가는 5일 이동평균선이 20일 이동평균선을 상향 돌파한 시점에 형성된 봉의 저가와 동일하기 때문이다. 아래의 도식을 살펴보자. 따라서 self.lowest_price라는 데이터는 고가 데이터가 갱신될 때마다 새롭게 입력을 해줘야만 고가 데이터의 갱신으로 인해 새롭게 계산된 매수 예정 가격(self.buy_price)에 주가가 닿았는지 안 닿았는지를 판단할 수 있게 된다. 

 

이러한 기본적인 작업이 이루어진 후에 Line 15~17 구간에서는 기존에 고가가 갱신됨과 함께 새롭게 입력된 self.lowest_price의 값보다 현재 조회 중인 주가 데이터의 저가가 낮다면 해당 저가 데이터를 self.lowest_price 데이터에 새롭게 입력하도록 한다. 다시 말해, 진짜 self.lowest_price 값은 저가가 갱신될 경우에 입력되며, 고가가 갱신되었을 때  self.lowest_price 변수에 입력되는 high는 단순하게 저가 갱신 여부를 판단하기 위한 용도로만 사용된다.

여기까지 저가 데이터가 self.lowest_price, 즉 역대 저가보다 낮은 위치에 형성되는 경우를 찾아냈으니 이제는 매수 예정 가격에 닿았는지와 손절가를 이탈했는지라는 두 가지 요구 사항을 판단해야 한다.

 

 


728x90

 

 

매수할 수 있었니?

아직은 손절에 대한 명확한 기준을 설정해두지 않았기 때문에 손절은 추후 구현하는 것으로 하고, 일단은 주가 데이터가 매수 예정가에 닿았는지 닿지 않았는지 살펴보도록 하자. print()문을 통해 매수 예정가에 닿았다면 닿은 시점과 그 시점의 저가, 그리고 매수 예정가격 데이터를 출력하도록 해서 정확한 계산이 이루어지고 있는지 확인해보자.
※ Line : 19, 20

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

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

            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
                self.lowest_price = high

            if low <= self.lowest_price:
                print(f"저가가 갱신되었습니다... {data.date}, {data.time}")
                self.lowest_price = low

                if low < self.buy_price:
                    print(f"매수 예정 가격에 닿았습니다... [{data.time}] {low}/{self.buy_price}")

        print(self.buy_price)

 

이후 데이터를 확인해보니, 오전 9시에 고가가 갱신됨과 동시에 매수 예정 가격에 닿았고, 9시 5분에도 고가가 갱신된 동시에 저가가 갱신되었으며, 이러한 반복은 9시 35분까지 이어지다가 이후에는 매수 예정 가격에 닿았다는 결과 데이터를 확인할 수 없었다. 왜 이런 일이 일어난 것일까? 실제 차트를 갖고 살펴보자.

>>>
고가가 갱신되었습니다... 20220715, 20220715090000
저가가 갱신되었습니다... 20220715, 20220715090000
매수 예정 가격에 닿았습니다... [20220715090000] 58300/58550.0
고가가 갱신되었습니다... 20220715, 20220715090500
저가가 갱신되었습니다... 20220715, 20220715090500
매수 예정 가격에 닿았습니다... [20220715090500] 58500/58600.0
저가가 갱신되었습니다... 20220715, 20220715091000
매수 예정 가격에 닿았습니다... [20220715091000] 58500/58600.0
저가가 갱신되었습니다... 20220715, 20220715091500
매수 예정 가격에 닿았습니다... [20220715091500] 58300/58600.0
저가가 갱신되었습니다... 20220715, 20220715092000
매수 예정 가격에 닿았습니다... [20220715092000] 58200/58600.0
저가가 갱신되었습니다... 20220715, 20220715092500
매수 예정 가격에 닿았습니다... [20220715092500] 58100/58600.0
저가가 갱신되었습니다... 20220715, 20220715093000
매수 예정 가격에 닿았습니다... [20220715093000] 58100/58600.0
저가가 갱신되었습니다... 20220715, 20220715093500
매수 예정 가격에 닿았습니다... [20220715093500] 58100/58600.0
고가가 갱신되었습니다... 20220715, 20220715110500
저가가 갱신되었습니다... 20220715, 20220715110500
저가가 갱신되었습니다... 20220715, 20220715111000
                    :
                  (중략)
                    :
저가가 갱신되었습니다... 20220720, 20220720151500
저가가 갱신되었습니다... 20220720, 20220720153000
저가가 갱신되었습니다... 20220720, 20220720153500
60250.0

 

 

차트의 모습을 보고 나니 이러한 오류의 원인은 무엇이었는지 단번에 알아차릴 수 있다. 그 이유는 바로 충분한 상승과 하락이 없었음에도 불구하고 매수 예정가가 계산되었고, 그 가격에 닿는 바람에 매수가 이루어졌다는 것이다. 

그렇다면 이 문제점을 어떻게 해결할 수 있을까? 간단하다. 우리가 조건검색기를 통해 특정 종목을 발견해낸 시점은 언제인가? 바로 장이 마감된 시점이다. 다시 말해, 위 차트 내에 있는 빨간색 네모칸은 실질적으로 장중에 형성되기 때문에 우리는 매수할 수 없는 가격이고 흐름이며 시점이라는 것이다. 따라서 우리는 반복문 내에서 high 데이터가 조건검색식에 집계된 날의 고가에 닿은 이후부터 매수 예정가를 계산하도록 하면 되는 것이다.

 

 

기준일자의 고가 데이터 얻어오기

알고리즘 파일 내 def __init__(self): 부분에서, 우리는 self.temp_min5_data라는 변수 내에 기준일자 이후의 데이터만 저장하도록 해둔 부분이 있다. 우리는 이 방법을 조금만 더 응용해서 기준일자의 고가 데이터를 손쉽게 얻어올 수 있다.
※ Line : 9, 10
Line 9의 start_date_data는 self.를 사용하지 않은 이유는, 여기서만 사용하고 버릴 데이터이기 때문이다.

    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분봉 차트 데이터

        start_date_data = self.min5_data[self.min5_data['date'] == self.start_date]
        self.standard_high = max(start_date_data['high'])

 

 

이제 다시 반복문으로 돌아와서, 고가가 갱신되는 경우에 하나의 조건을 추가해주도록 하자. Line 4에 대한 설명은 나중에 하는 것으로 하고, Line 16~17부터 보도록 하자. 고가가 갱신되고 갱신되면서 상승하는 과정에서, 조회 중인 고가가 기준일자의 고가와 같아졌는가를 조건으로 하여 같아졌을 경우, self.pos_buy라는 변수에 True라는 값을 입력하는 것이다. 다시 말해, self.pos_buy라는 것은 매수가 가능했는가 불가능했는가를 판단해주는 하나의 매개변수라고 생각하면 된다.

이제 Line 4를 보면, def run(self):가 실행될 때 self.pos_buy라는 변수에는 False가 입력되도록 함으로써 매수가 이루어지지 않도록 예방한 후에, 고가 데이터가 기준일자 당일의 고가와 같아진 시점 이후에는 self.pos_buy라는 변수에 True라는 값이 입력되기 때문에 거래를 진행할 수 있게 되는 것이다.

마지막으로 Line 23에서는 if self.pos_buy:를 통해, self.pos_buy라는 변수 안에 True라는 데이터가 입력되어 있을 때만 현재의 저가가 매수 예정 가격(self.buy_price)보다 낮은지를 체크하도록 한다. 이제 코드를 실행해보자.
※ Line : 4, 16~17, 23

    def run(self):
        self.highest_price = self.start_price
        self.lowest_price = 0
        self.pos_buy = False

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

            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
                self.lowest_price = high

                if high == self.standard_high:
                    self.pos_buy = True

            if low <= self.lowest_price:
                print(f"저가가 갱신되었습니다... {data.date}, {data.time}")
                self.lowest_price = low

                if self.pos_buy:
                    if low < self.buy_price:
                        print(f"매수 예정 가격에 닿았습니다... [{data.time}] {low}/{self.buy_price}")
                        pass

        print(self.buy_price)

 

결과 데이터를 확인한 결과, 아직 매수가 이루어졌어야 하는 지점이 발생하지 않았다는 것을 확인할 수 있다.

고가가 갱신되었습니다... 20220715, 20220715090000
저가가 갱신되었습니다... 20220715, 20220715090000
고가가 갱신되었습니다... 20220715, 20220715090500
저가가 갱신되었습니다... 20220715, 20220715090500
저가가 갱신되었습니다... 20220715, 20220715091000
                   :
                 (중략)
                   :
저가가 갱신되었습니다... 20220720, 20220720151000
저가가 갱신되었습니다... 20220720, 20220720151500
저가가 갱신되었습니다... 20220720, 20220720153000
저가가 갱신되었습니다... 20220720, 20220720153500
60250.0

 

 

알고리즘은 이처럼, 사람이 보고 생각하는 것과 동일한 수준에서 사고를 할 수 있도록 데이터 처리 과정을 거쳐 적절한 데이터를 전달하고 여러 조건을 추가해서 어떤 경우에는 특정 작업이 이루어지지 않도록 하는 경우들에 대해 모두 제한해주어야 한다.

아주 단적인 예로, 사람이라면 지수가 하락할 때에는 상승하는 종목보다 하락하는 종목이 많으며 매수세가 특정 종목에 쏠릴 수도 있지만 어지간하면 조정을 거친다는 점을 알고 있다.(물론 이걸 알면서도 심리에 이끌려 그렇게 하지 못하는 건 다른 문제다.) 하지만 프로그램은 지수가 하락한다는 것에 대한 그 어떠한 지식도 없다. 그냥 단순하게 2,200 내지는 2,600, 또는 600 내지는 800 등과 같은 하나의 숫자일 뿐, 상승과 하락이라는 것에 대한 기본적인 배경 개념 그 자체가 없다는 것이다.

이제 다음 게시글에서는 여기까지 계산한 데이터를 어떻게 가공해서 사용할 것인지 알아볼 예정이다.

 

 


728x90
반응형
Contents

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

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