전략 시뮬레이션(1) 편에서 upbit 일봉 데이터를 가져오는 부분에 대하여 설명을 하였습니다. 일봉은 최대 400개만 가져올 수 있기 때문에 아주 간단하게 코딩을 했었는데요. 

이번에는 분봉 및 tick 데이터를 가져오는 방법에 대하여 설명하도록 하겠습니다.

 

분봉과 틱은 아래 형식을 사용하면 됩니다.

  • 분봉 :   1, 3, 5, 10, 30, 60분 봉을 가져올 수 있습니다. 아래 형식에서 minutes 뒤에 원하는 분 정보를 넣으면 됩니다.
crix-api-endpoint.upbit.com/v1/crix/candles/minutes/1?code=CRIX.UPBIT.KRW-BTC&count=10

 

  • 틱봉 : 틱봉을 가져오는 url입니다. 틱봉은 접속 주소도 틀리고 인자들도 조금 틀립니다. 특히 코드명 방식도 틀리니 참고하시기 바랍니다.
api.upbit.com/v1/trades/ticks?market=KRW-JST&count=500

 

우선 분봉은 숫자 상 제한이 없는 것 같기도 합니다. 1분 봉을 받아보니 한참을 받더군요.

문제는 분봉도 한번에 받을 수 있는 수량은 500개로 제한된다는 것입니다. 계속 이어 받기 위해서는 to 필드에 적절한 값을 넣어서 데이터를 받는 함수를 계속 호출하여야 합니다.

형식은 아래와 같습니다.

https://crix-api-endpoint.upbit.com/v1/crix/candles/minutes/1?code=CRIX.UPBIT.KRW-BTC&count=10&to=2019-09-01 00:00:00

 

to 값은 앞에서 받은 데이터 중 가장 마지막 데이터의 'candleDateTime' 값을 사용하면 됩니다.

 

그리고 받는 중간에 연결이 끊어질 수 있으므로 500개씩 받은 데이터를 파일에 계속 써 나가는게 좋을 것 같습니다. 방식은 코인이름과 일자 그리고 일련번호를 쭉 붙여나가는 방식이면 될 것 같습니다. 우선 간단하게 관련 코드만 소개해드리고, tick 데이터를 모두 받는 코드가 정리되면 함께 github에 올리겠습니다. 

 

# ago : tick인 경우에만 사용
#       오늘 기준으로 며칠 전 : 예 오늘:0, 어제:1
def get_data_continue(candle, coin, ty='day', interval=1, count=10, frm=None, to=None, ago=0) :  
    end = False
    cnt = 1
    while(end == False) :
        t = int(time.time())
        if candle : 
            ret = get_candle_history(coin, ty, interval, count, to)

        if ret != None :
            if len(ret) < 2 : # no more data
                return

            # 마지막에서 두 번째 시간을 to로 사용함. 
            # to를 포함하지 않는 시간까지 자료가 넘어옴. 
            # 이전에 검색한 to까지 자료가 부족할 수 있음
            # 차후에 merge할 때 to 시간이 겹칠 수 있으므로 빼는 부분 추가하여야 함
            info = ret[-2]  

            # candle과 tick 정보가 다름.
            # dt, to용 값을 구함
            if candle :
                dt = info['candleDateTime'].split('+')
                to = dt[0].replace('T', ' ')
                day = to.split(' ')[0]
                
            if frm != None : # from보다 이전 데이터인지 확인
                info = ret[-1] # 마지막 데이터 
                if candle :
                    if info['candleDateTime'] < frm : 
                        ret = remove_data(ret, frm, 'candleDateTime')
                        end = True
                    
            # cnt 번호를 추가하여 파일이름 생성
            fname = coin+'_' + ty + '_' + str(interval) + '_' + format(cnt, '03d') + '_' + day + '.csv'
            cnt += 1
            save_to_file_csv(fname, ret)
            print ('save ', fname)

            if ty == 'day' : # day는 400개만 받을 수 있다.ㅣ
                end = True
            else :  # 분 봉은 계속 받을 수 있다.
                time.sleep(1)

        else :
            end = True

 

프로그램이 실행되고 나면 아래와 같은 형식으로 파일이 생성됩니다.

 

 

 

이후에는 파일 하나로 합치면 됩니다. 이 부분도 현재 작업 중이므로, 함께 소개하도록 하겠습니다.

 

시뮬레이션은 데이터를 만드는 과정을 잘 정의해놓아야 필요할 때 원하는 구간의 데이터를 쉽게 빼올 수 있습니다. 처음에는 간단하게 시작했는데, 이 부분에서 작업할 양이 많은 것 같습니다.

 

 

 

 

 

반응형

설정

트랙백

댓글

기존 코드를 한번 더 리펙토링을 해야할 것 같습니다.

 

이번에 수정하고자 하는 부분은 아래와 같습니다.

1. 지표를 추가할 때 변경하는 부분 최소화

2. 기존 전략을 수정하거나 새로운 전략을 추가할 때 변경할 부분 최소화

 

우선 지표를 추가하는 부분을 다시 보겠습니다.

일단 새로운 지표인 경우에는 지표를 코딩하는 부분은 어쩔 수가 없이 추가하여야 합니다. 다만 새로운 지표가 추가되었을 때 simulation 과정에서 중간 값을 저장하는 부분에 새로운 지표를 추가하여야 하는데요. 이런 경우에 관계없는 부분도 변경하여야 하기 때문에 좋은 구조라고 볼 수 없습니다.

 

우선 기존 코드입니다. save_mid_values 함수 하나로 모든 변수를 저장하였습니다. 이러다 보니 관계없는 부분에서도 수정을 해야합니다.

def save_mid_values(candle_data, buy_price, sell_price, profit, total_profit, dd, trend, ma7) :

 

이 문제를 해결하는 방법은 함수를 쪼개는 것입니다.

관련 변수를 모아서 몇 개의 함수로 나눠보겠습니다. 우선 save_mid_values()에 default 값을 추가하여 초기화하는 부분에서는 save_mid_values()만 호출하면 됩니다. 

만약 새로운 지표가 추가된다면 save_mid_values()에 인자를 추가하고, save_mid_values_jipyo()에 관련 변수를 추가하면 됩니다. 

# 지표가 추가되는 경우에는 마지막에 변수 추가 
def save_mid_values(candle_data, buy_price=0, sell_price=0, profit=0, total_profit=0, dd=0, trend=0, ma7=0) :
    save_mid_values_buy_sell(candle_data, buy_price, sell_price)
    save_mid_values_result(candle_data, profit, total_profit, dd)
    save_mid_values_jipyo(candle_data, trend, ma7)

def save_mid_values_buy_sell(candle_data, buy_price, sell_price) :
    candle_data['buy_price'] = buy_price # buy 여부 저장
    candle_data['sell_price'] = sell_price # buy 여부 저장

def save_mid_values_result(candle_data, profit, total_profit, dd) :
    candle_data['profit'] = profit # profit 저장
    candle_data['total_profit'] = total_profit # total_profit 저장
    candle_data['dd'] = dd # downd draw 저장

def save_mid_values_jipyo(candle_data, trend, ma7) :
    candle_data['trend'] = trend # 추세값 저장
    candle_data['ma7'] = ma7 # downd draw 저장

 

이 함수를 사용하는 부분도 적절하게 변경하였습니다.

 

 

다음은 전략 부분을 refactoring해보겠습니다.

이전 글에서 횡보구간에서는 매수를 하지 않는 전략을 추가하였는데요. 아래와 같이 적용할 전략이 아닌 부분은 # 처리했었습니다. 이렇게 되면 코드 관리가 어려워집니다. 따라서 어떤 방식건 # 처리를 하지 않고 각 전략에 쉽게 적용할 수 있어야 합니다. 이 문제를 풀기위해 전략 코딩 부분을 refactoring 해보겠습니다.

    def is_enter_condition(self, candle) :  
        enter = 0
        # 아래 세 조건 중에 원하는 조건 선택
#        if self.range > 0 :
#        if self.range > 0 and self.trend > 0.2 # trend 동시 적용
        if self.range > 0 and self.trend > 0.2 and (self.MA != 0 and self.MA < candle.open) : # trend & MA 동시 적용

 

전략코딩 부분을 찬찬히 살펴보면 이런 식으로 구성되어 있습니다.

 

candle이 변경되었을 때 관련 변수 수정

if 매수 중이면 

   전략에 따른 매도할 가격을 받는다.

   현재 candle 기준으로 매도 조건이면

      매도주문    

else # 매수대기

   전략에 따른 매수할 가격을 받는다.

   현재 candle 기준으로 매수 조건이면

       매수주문

 

따라서 이런 구조로 class를 정의하면 새로운 전략을 추가하는 경우에 필요한 부분만 변경하면 됩니다. class를 사용하면 이런 구조를 아주 효과적으로 개발할 수 있습니다.

 

기존적인 전략에 대한 코딩은 base class에서 하고, 새로운 전략을 적용하는 경우에는 매수/매도 가격 결정, 매수/매도 조건 결정하는 함수만 개발하면 됩니다.

 

class의 가장 큰 장점이 상속기능입니다. 개념이 조금 복잡하지만 쉽게 설명하면 공통으로 사용하는 함수는 부모 클래스가 가지고 있고 차이가 나는 부분은 자식클래스에서 개발하자는 취지입니다.

 

앞에서 소개한 TR_LW를 base class로 옮기고, 이 중 전략별로 다르게 개발하여햐하는 함수만 자식 class에서 개발하면 됩니다.

 

우선 TR_Basic()은 기존 TR_LW와 같다고 보시면 됩니다. 특이한 부분은 자식 클래스입니다. 우선 TR_LW_SIM 선언시에 누가 부모인지를 인자로 넣습니다. 그 후 필요한 경우에는 초기화를 하는데요. 이때 부모 클래스의 초기화를 진행해야하기 때문에 super()라는 키워드를 사용하여 부모 클래스에 있는 함수를 호출합니다. 그 후 자기 class에서 필요한 변수를 만들고 초기화합니다. LW 전략이므로 range와 k 값을 초기화합니다.

class TR_Basic() :  # 전략 부모 클래스
    def __init__(self, interval, num) :
        # 공통 초기화 부분
        
class TR_LW_SIM(TR_Basic) : # LW 시뮬레이션용 클래스 
    def __init__(self, interval, num) :
        super().__init__(interval, num)
        self.range = 0
        self.k = 0.5

 

전략에서 필요한 함수는 크게 5가지로 구분할 수 있습니다.

    def update_new_candle(self, cur) : # candle이 변경되었을 때 관련 변수 변경
    def get_buy_price(self, candle) : # 매수할 가격 구하기
    def is_meet_buy_cond(self, candle, buy_price) : # 매수 조건 만족?
    def get_sell_price(self, candle) : # 매도할 가격 구하기
    def is_meet_sell_cond(self, candle, sell_price) : # 매도 조건 만족?

 

이렇게 만든 상태에서 비추세 구간에는 매수를 하지 않는 전략으로 수정을 한다고 가정을 합시다.

기존 TR_LW_SIM과 비교하여 차이가 나는 부분은 매수 가격을 정하는 부분입니다. 따라서 TR_LW_SIM을 부모로 갖는 TR_LW_TREND라는 classs를 만들고 get_buy_price함수에 관련 코드를 작성하면 됩니다.

 

class TR_LW_TREND(TR_LW_SIM) :
    # 매수 조건을 만족하면 매수가를 돌려준다.
    def get_buy_price(self, candle) :
        if self.range > 0 and self.trend > 0.2 : # trend 동시 적용
            buy_price = (candle.open + self.range * self.k)
            return buy_price
        return 0

 이렇게 만든 전략을 사용하는 경우에는 아래와 같이 class 명을 명시하시면 됩니다. 비추세와 이평을 이용하는 전략도 함께 수정해놓았으니 참고하시기 바랍니다.

 

tr_logic = TR_LW_TREND('min', 1)

 

시뮬레이션은 비교적 간단한 개발이기 때문에 이 정도로 동작을 하지만 실제 매매와 연동을 하게되면 구조가 좀 더 복잡해집니다. 이것도 시간이 되면 정리해서 올리도록 하겠습니다.

 

관련 src코드 파일명과 github 위치는 아래와 같습니다.

Larry_williams4.py
TR_LW_1.py
my_candle.py
my_util.py

github.com/multizone-quant/System_trading_ex

반응형

설정

트랙백

댓글

LW 전략 적용해보고, 그 결과를 차트로 그려서 매매 상황을 확인해 볼 수 있는 부분까지 마무리를 하였습니다. 이번에는 기본 LW 전략에 몇몇 아이디어를 추가해보도록 하겠습니다.

 

일단 비추세 구간에서는 LW 매수 시그널이 나오더라도 이익도 적고, 손실이 발생할 확률이 높습니다. 따라서 비추세 기간에는 매매를 하지 않도록 수정해보겠습니다.

 

비추세 구간을 확인하는 방법은 여러가지가 있겠지만, 이번에는 아래 글에 나오는 추세추종 필터를 적용해보겠습니다.

 

cafe.naver.com/invest79/939

 

여기에서 사용하는 추세추종 필터는 아래와 같습니다. 

 

A : 7일간 코인 가격 차이의 절대값 = abs(1일전 코인종가 - 7일전 코인종가) 

B : 7일간 당일 코인 움직임 절대값의 총합 : abs(7일전 시/종가 차이)+ ...... + abs(1일전 시/종가 차이)

 

추세추정필터는  A/B 값으로 계산합니다. 값이 크면 추세가 강하다는 의미입니다.

결국 매일의 시/종 가격 변화의 합과 시작/킅 날의 가격 차이의 비율로 추세를 판단하는 방법입니다.

 

이전에 만들었던 프로그램에서 추가할 사항들은 아래와 같습니다.

1. simultaion 중간에 저장할 값으로 추세추종 필터 값 추가 (debuging용)

2. tr_lw에서 추세추종 필터 계산하는 함수

3. candle update하는 update_new_range() 함수에서 추세주총필터 계산하는 함수 호출하기

4. tr_lw에서 is_enter_condition() 수정

 

비교적 변경하는 부분이 적다고 볼 수 있습니다. 

 

그럼 하나씩 추가해보겠습니다.  

 

1. 중간값을 저장하는 함수 수정

def save_mid_values(candle_data, buy_price, sell_price, profit, total_profit, dd, trend) :
    candle_data['buy_price'] = buy_price # buy 여부 저장
    candle_data['sell_price'] = sell_price # buy 여부 저장
    candle_data['profit'] = profit # profit 저장
    candle_data['total_profit'] = total_profit # total_profit 저장
    candle_data['dd'] = dd # downd draw 저장
    candle_data['trend'] = trend # 추세값 저장

 

중간값을 저장하는 함수를 부르는 곳에도 trend 값을 전달할 수 있도록 수정합니다.

        # simulation 중 파일에 저장할 변수들 추가
        trend = tr_logic.get_trend()
        save_mid_values(candle_data[i], 0, 0, 0, 0, 0, trend)

 

2. TR-LW.py에 추세를 계산하는 함수를 추가합니다.

 

def __init__(self, interval, num) :
		:
        self.trend = 1 # 변수 추가
        
def get_trend(self) :
        cur = self.history[-1] # last one

        day_sum = 0
        candle = None
        hist_len = len(self.history)
        if hist_len >= 8 :
            range_total = 0
            pos = len(self.history)-1
            for i in range(7) :
                candle = self.history[pos]
                day_sum += abs(candle.open - candle.close) # candle 시/종가 차이의 절대치 합
                pos -= 1

            period_range = abs(candle.close - cur.close)
            self.trend = period_range / day_sum
            self.trend = float(format(self.trend, '.2f')) # 소숫점 2자리 값으로 변경

        return self.trend

 

history에는 candle 정보가 오름차순으로 저장되어 있으므로, 최신 7 candle의 값을 구하기 위해서는 뒤에서 부터 계산을 해야합니다. 그리고 float 값은 소숫점 이하 긴 숫자로 저장되므로, 소숫점 이하 2자리만 가지고 있기 위해서는 .2f 포맷으로 변경한 후(문자열) 이것을 다시 float로 변경하면 소숫점 2자리 실수를 가지고 있을 수 있습니다.

 

3. 다음은 get_trend() 함수를 부르는 곳을 추가해야 합니다.

    def update_new_range(self, cur) : # 
        self.pre  = cur
        self.history.append(cur)
        self.range = cur.high - cur.low # 전 candle의 range

        self.get_trend()

 새로운 candle이 들어오면 range값을 계산하고 history에 추가하는 함수입니다. 여기에서 get_trend()를 부르면 될 것 같습니다.

 

4. 마지막으로 매수 조건에 trend 값이 특정 값  이상이면 매수하도록 수정합니다. 일단 0.4로 해보겠습니다.

    def is_enter_condition(self, candle) :  
        enter = 0
        if self.range > 0 and self.trend > 0.4 : # 매수 조건
            buy_price = (candle.open + self.range * self.k)
            if candle.high > buy_price :  # 최고가가 buy_price보다 높으면 매수되었다고 가정
                enter = 1
                self.bought_price = buy_price

 

추세추종 필터를 추가하기 위하여 필요한 코딩이 마무리되었습니다. 새로운 기능이 들어오면 변경할 곳이 많아집니다. 이 중 한 곳이라도 수정하지 않으면 정확하게 동작하지 않게 됩니다. 따라서 본인이 작성한 프로그램 구조를 정확하게 파악을 해야합니다.

 

이렇게 반영한 결과를 확인해보겠습니다.

 

profit : ticker :  KRW-BTC
total # trading  :  215
total profit     :  1,926,201.75
trading fee      :  151,161.74
total Net Profit :  1,775,040.00
# winning        :  121
# losing         :  94
MDD              :  51.78
max loss         :  -17,060.96
max gain         :  1,926,201.75

 

아쉽게도 추세추종 필터를 추가하기 전 보다 수익이 더 떨어졌군요. 그 원인을 확인해 보도록 하겠습니다. 두 그래프를 비교해보니 아래 그림과 같이 급락하였다가 다시 원래로 돌아오는 경우에 추세추종 필터가 추가된 경우에는 매수에 참여를 안하고 있습니다. 추세추종 값이 0.18, 0.33이군요. 

추세추종 정도를 변경하면서 돌려본 결과입니다. 수익률 면에서는 추세추종 필터를 사용하지 않은 경우가 제일 좋습니다. 다만 추세추종 필터를 추가하면 수익률은 다소 떨어지지만 MDD 값은 개선이 됨을 알 수 있습니다. 각자 장단점이 있을 것 같습니다.

 

 

결국 어떤 전략에서 특정 필터를 사용할 때 적용 여부를 판단하는 파라미터 값을 찾는 것이 중요합니다. 트레이딩하는 코인의 종류에 따라 최적의 값은 달라질 것입니다. 따라서 실전에서는 많은 시도를 해보면서 계속 변경해야할 것 같습니다.

 

매매를 한 시점을 분석해보니 하락 시점에 매수를 하면 손실이 발생하는 경우가 많군요.

 

하락 기간에는 매수를 하지 않으면 수익이 좋아질지 궁금합니다.

하락 추세를 판단하는 방법도 여러가지가 있겠지만 일단 이동평균선을 사용해보겠습니다. 주식에서는 MA(5일)을 사용하지만 코인은 주말에도 거래가 되므로 MA(7일)을 사용하겠습니다. 

 

하락추세추종 필터와 하는 역할이 같으니 변경되는 부분도 비슷합니다.

 

1. save_mid_values()에 ma값 추가하고 save_mid_values()를 부르는 함수에 ma값 추가

2. TR-LW.py에 MA 계산하는 함수 추가, 원하는 일자로 변경할 수 있도록 일자를 함수 인자로 받도록 하겠습니다.

def __init__(self, interval, num) :
		:
        self.MA = 0 # 변수 추가

def get_MA(self, num_days) :
        hist_len = len(self.history)
        if hist_len >= num_days :
            day_sum = 0
            pos = len(self.history)-1
            for i in range(num_days) :
                candle = self.history[pos]
                pos -= 1
                day_sum += candle.close # 종가 합
            self.MA = day_sum / num_days
            self.MA = float(format(self.MA, '.2f')) # 소숫점 2자리 값으로 변경
        return self.MA

3. get_MA()를 부르는 곳 변경. get_trend()와 같이 update_new_range() 함수에 추가하면 됩니다.

4. is_enter_condition() 변경

   MA 값이 candle의 open 가격보다 낮으면 상승 추세로 가정합니다. 

    def is_enter_condition(self, candle) :  
        enter = 0
        if self.range > 0 and self.trend > 0.4 and (self.MA != 0 and self.MA < candle.open)  :
            buy_price = (candle.open + self.range * self.k)

 

이렇게 변경한 내용을 반영해보았습니다. 추세추종 값은 0.2, MA(7일) 적용 결과입니다. 

 

위에서 언급한 구간에서 매수 후 손실은 사라졌습니다만 수익률은 더 떨어지는군요. 아마 수익이 날 곳에서 매수를 못하고 있을 것 같습니다.

 

그래프를 다시 비교해보니 아래와 같이 하락 후 상승하는 시기에 나오는 양봉에 매수를 못하고 있습니다. 당연한 결과인 것 같습니다. 하락 추세 중 발생하는 양봉은 아직 하락 추세 중이므로, 당연히 매수가 안됩니다. 하지만 이런 경우에 장대 양봉이 나오는 경우가 종종있습니다.  

하락시에 손실이 발생하는 매수를 없애기 위하여 적용한 상승추세 시 매수 전략의 단점이 있군요. A 문제를 풀려고 하니 B 문제가 생기는 셈인데요. 어떻게 할지는 좀 더 고민해봐야 할 것 같습니다.

 

결론적으로 다양한 필터의 특성을 정확하게 파악해서 특정 움직임에 맞는 필터를 자유자재로 적용할 수 있는 내공이 필요해보입니다. 필터에 필수적으로 필요한 파마미터 값을 결정하는 것도 큰 숙제입니다.

 

파이썬을 이용하면 최적의 필터와 파라미터의 조합을 비교적 빠르게 찾을 수 있습니다. 파이썬을 이용하여 시뮬레이션을 하는 이유도 이것 때문이고요.

 

지금까지 간단하게 전략 시뮬레이터를 만들어 보았습니다. 최대한 간단하게 코딩을 했으니, 코드를 읽는데 큰 문제는 없을 것이라고 생각합니다. 수정한 코드는 아래위치에 있습니다. Larry_williams3.py에 지금까지의 수정 사항이 반영되어 있습니다.

 

github.com/multizone-quant/System_trading_ex

 

multizone-quant/System_trading_ex

Contribute to multizone-quant/System_trading_ex development by creating an account on GitHub.

github.com

여기에서 아래 프로그램을 다운받으시면 됩니다.

Larry_williams3.py
TR_LW.py
my_candle.py
my_util.py
sim_data/*

 

이외에도 lw 매수 조건 계산할 때 k 값을 평균 값으로 변경하기 등 추가할 사항이 더 있습니다. 이 부분도 차후에 반영하겠습니다.

 

오늘 지표를 추가하면서 수정할 부분을 찬찬히 살펴보면 tr_lw.py에 기능 추가하는 부분 이외에 Larry_williams3.py에 추가하는 지표를 저장하기 위하여 코드를 수정하는 부분이 있습니다. 이렇게 여러 파이썬 파일을 수정하는 경우에는 실수할 확률이 높아지므로, 이 부분도 모으면 좋습니다. 

 

 

다음에는 업비트에서 실시간 시세를 받으면서 실제로 투자하는 실전용 코드를 소개하도록 하겠습니다.

 

 

반응형

설정

트랙백

댓글

(2) 편에서 기술한 LW 전략 시뮬레이션 코드를 정리(refactoring)를 해 보겠습니다.

 

우선 class를 만드는 방법을 간단하게 기술하겠습니다. 클래스는 객체지향 언어가 등장하면서 나온 개념입니다. 클래스란 유사한 성질의 객체들을 하나로 그룹화한 것입니다.  쉽게 이야기하면 목적이 유사한 함수들을 모아놓았다고 보셔도 됩니다. 

 

파이썬에서 클래스를 사용하는 방법은 아래와 같습니다.

 

class name() :

  def __init__(self) :

      # 각종 초기화

      self.var1 = 0

  def func1(self) :

      # 함수 1

  def func2(self) :

       # 함수 2

       self.func1() # 함수 1을 부를 때

       self.var1 = 1

 

목적이 유사한 함수를 name이라는 class로 선언한 경우입니다. 여기에서 __init__(self)는 초기화를 위하여 사용되는 함수인데 항상 이름이 __init__이여야 합니다 이외에는 본인이 원하는 함수명을 추가하면 됩니다. 여기에서 self가 문제인데요. 이것도 그냥 외우고 사용하시면 됩니다. self가 사용되는 경우입니다.

 

- class 내 함수의 인자는 항상 self가 먼저 들어가야합니다.

- class 내에서 class내 함수를 부를 때는 항상 self. 으로 시작합니다.

- class 내에서 class내 변수를 사용할 때는 항상 self. 으로 시작합니다.

 

 

다음으로는 import 기능입니다. py 파일 하나에 모든 클래스와 함수가 들어가 있으면 가독성이 떨어집니다. 화면 스크롤을 하면서 소스를 봐야하는데 수 십 페이지면 특정 부분을 찾기가 힘들겠죠. 그래서 class로 만든 경우에는 대부분 별도 파이썬 파일로 분리합니다. 그리고 공통 lib의 경우에도 별도 파이썬으로 분리하면 좋습니다. 다른 프로젝트를 개발할 때 사용할 수 있으니까요.

 

이렇게 분리된 파이썬 파일은 import라는 명령어를 이용하여 불러올 수 있습니다. 

 

전편에 만든 소스를 아래와 같이 파이썬 파일 3개를 추가로 만들었습니다.

 

- my_util.py : file io와 같이 공통적으로 사용하는 함수들

- my_candle.py : candle class

- TR_LW : LW class

 

이렇게 나뉘어진 class 혹은 파일을 사용하는 경우에는 아래와 같이 import를 하면 됩니다.

 

from my_util import *
from my_candle import *
from TR_LW import *

 

기존 프로그램이 총 4개의 파일로 나뉘어졌습니다. 그럼에도 불구하고 simulation 함수의 줄 수가 너무 많습니다. 여기도 손을 좀 보겠습니다. 우선 각종 통계를 위하여 사용하는 변수가 많습니다. 이런 경우에는 통계용 변수를 위한 class를 별도로 만들어 보겠습니다.

 

그냥 class 명 하나 정하고, 사용할 변수들 이동하면 됩니다. 그리고 필요한 함수도 만들면 됩니다.

 

class sim_stat :

  def __init__(self) : # 각종 통계용 변수들 초기화

  def update_stat(self, profit) : # 트레이딩이 완료되었을 때 수익금 update

 

이렇게 클래스로 뽑으면 아래와 같이 함수 한 줄로 대체가 가능합니다. 

 

그리고 거래세 관련 부분, 시뮬레이션 중간 결과 저장하는 부분, 화면에 결과를 출력하는 부분, 결과를 파일에 저장하는 부분도 별도 함수로 뽑았습니다. 역시 복잡한 내용이 사라지고 함수 한 줄로 대체가 됩니다.

 

다음으로는 트레이딩 부분도 좀 더 재사용이 가능하게 변경하였습니다.

현재 LW 로직에서는 일봉을 대상으로 하기 때문에 매수 조건을 만족하면 당일 종가로 매도가 된다고 코딩을 했었습니다.

 

하지만 당일 종가가 아닌 다른 조건에 매도를 할 수도 있기 때문에 아래와 같이 수정하였습니다.

 

if 매수조건 :

   매수

if 사졌다면 :

   if 매도 조건이면 :

      매도 

 

현재와 같이 일봉 기준으로 당일 종가에 판다고 가정하였을 때 TR_LW class에서 매도 조건은 아래과 같이 코딩이 가능합니다.

 

def is_exit_condition(self, candle) :

 

     return candle.close

 

만약 매도 조건을 바꾼다면 이 함수에서만 수정하면 됩니다. 다른 부분은 수정할 필요가 없기 때문에 오류가 날 확률이 줄어들게 됩니다. 

 

여러 변화를 준 코드를 돌려보았습니다. 이전에  만든 프로그램과 결과가 같아야 되겠죠? 같은 결과가 나왔습니다. 이렇게 기존 코드를 수정하는 경우에는 반드시 이전과 결과가 같은지 확인을 해 보아야합니다. 이런 검증 과정도 아주 시간이 많이 걸리는데요. testcase를 이용하면 좀 더 효율적으로 변경한 코드의 정확성을 확인할 수 있습니다. 이 부분도 차후에 소개하도록 하겠습니다.

 

 

전체적인 구조가 많이 변경되었지만 프로그램 내용은 변화가 없기 때문에 이해하는데 큰 문제는 없을 것입니다.

 

아직도 class로 쪼개거나 class로 만들어야 하는 부분이 더 있습니다. 우선은 여기까지만 정리해보도록 하겠습니다.

 

다음에는 현재 개발한 LW 실행 결과를 그래프로 확인하는 과정과 성능을 개선하는 과정을 소개하도록 하겠습니다.

 

 

수정한 소스는 아래 github에 있습니다.

 

github.com/multizone-quant/System_trading_ex/tree/main

 

multizone-quant/System_trading_ex

Contribute to multizone-quant/System_trading_ex development by creating an account on GitHub.

github.com

여기에서 아래 프로그램들을 다운받으시면 됩니다.

반응형

설정

트랙백

댓글