< 6. 알고리즘 전 후 수익율 테스트 (키움 open api 주식 알고리즘 테스트 프로그래밍) >


1. 키움 open api를 이용한 주식 알고리즘 테스트 프로그래밍

2. DB 설계

3. DB 구현

4. 주식 데이터 불러오기

5. 알고리즘을 통한 매수, 매도 테스트


해당 포스트를 읽기 전에 위의 포스트들을 읽기 바란다.


우리는 이전 포스트까지 구현한 알고리즘을 통해 KODEX 코스닥 150 레버리지를 가상으로 매수/매도했고 이를 DB에 저장했다. 이번 포스트에서는 알고리즘을 이용한 매수/매도가 알고리즘 전보다 얼마나 효율적인치 비교하는 코드를 구현할 것이다. 사용자가 일정 기간을 설정하면 알고리즘 적용한 매수/매도의 당일 수익율과 적용 전 당일 수익율을 구한 다음 이를 그래프로 나타낼 것이다. 알고리즘 적용 전 매수/매도는 KODEX 코스닥 150 레버리지 당일 시초가에 매수하고 당일 종가에 매도하는 걸로 설정할 것이다. 

다음은 구현할 클래스의 초기화 부분이다.
class Main:
    def __init__(self):
        self.db = Db.StockDb()
        self.backTracking = Backtracking.OrderBacktraking(var1,var2)
        self.before_profits = pd.DataFrame(columns=('date','profit','total_profit'))
        self.after_profits = pd.DataFrame(columns=('date','profit', 'total_profit'))

DB를 이용하기 때문에 DB 객체를 만들고 지난 포스트에서 배운 OrderBacktracking 객체를 만든다. var1과 var2는 지난 포스트에서도 말했지만 알고리즘에 적용하는 변수이다. 이 변수를 기준으로 매수/매도한다. 'self.before_profits' 변수는 알고리즘 적용 전 수익율을 저장한다. date은 수익율을 계산한 일을 의미한다. profit는 당일 수익률이다. total_profit는 총수익율을 의미한다. 매수/매도하는 기간을 정하는 데 기간을 적용한 일부터의 총 수익율이다. 'self.after_profits'는 알고리즘 적용 후 수익율을 저장한다.



다음은 'self.before_profits'와 'self.after_profits'에 데이터를 저장하는 메서드이다. 알고리즘 적용 전과 적용 후 일정기간동안 수익율을 저장한다. 

def compare_Algorithm(self,start, end): self.after_profits = self.backTracking.get_days_profit(start,end) total_profit=1 before_data = self.db.select_Daily_Data(start,end) for i in range(len(before_data)): profit = before_data['end'][i]/before_data['start'][i]*0.9997 total_profit *=profit self.before_profits = self.before_profits.append(pd.Series({'date' : before_data['date'][i], 'profit' : profit, 'total_profit' : total_profit}),ignore_index=True)

매수/매도할 기간을 start와 end로 정한다. start일부터 end일까지의 당일 수익율과 총 수익율을 구한다. 지난 포스트에서 'get_days_profit(start, end)' 메서드를 배웠다. 'get_days_profit()'는 start일부터 end일까지 알고리즘을 적용한 매수/매도의 당일 수익율과 총 수익율을 구한다. 그리고 알고리즘 적용 전 매수/매도 수익율을 구한다. 'select_Daily_Data'로 시가와 종가 데이터를 가져온다. 수익율과 총 수익율을 구한 후 'self.before_profits'에 저장한다.  


지금까지 알고리즘을 적용 후와 전 매수/매도 당일 수익율과 총 수익율을 구했다. 이를 그래프에 표현해서 비교해보자. 실행 결과물은 다음과 같다. "2017년 10월 1일"부터 "2017년 11월 7일"까지 데이터를 이용한 실행 결과물이다. 아래 그래프에서 10월 10일부터 표시된 이유는 추석연휴기간이였기 때문이다. 주황색 선은 알고리즘을 이용한 매수/매도 수익율이고 파란색 선은 알고리즘 적용 안 한 매수/매도 수익율이다. 두 개의 그래프 중 위 그래프는 당일 수익율을 나타내고 아래 그래프는 총 수익율을 나타낸다. 비교 결과 알고리즘 적용 후가 수익율이 5퍼 정도 높게 나오는 걸 알 수 있다. 이에 대한 코드는 아래와 같다.


    def show_compare_Algorithm(self,start,end):
        main.compare_Algorithm(start, end)

        fig = plt.figure()
        top_axes = fig.add_subplot(2, 1, 1)
        bottom_axes = fig.add_subplot(2, 1, 2)

        day_index = [datetime.datetime.strptime(sday, "%Y%m%d") for sday in main.before_profits['date']]
        top_axes.set_xlabel('날짜')
        top_axes.set_ylabel('당일 수익')
        top_axes.grid(True)
        top_axes.plot(day_index, main.before_profits['profit'], label='before profit')
        top_axes.plot(day_index, main.after_profits['profit'], label='after profit')
        top_axes.legend(loc='upper left')

        bottom_axes.set_xlabel('날짜')
        bottom_axes.set_ylabel('총 수익')
        bottom_axes.grid(True)
        bottom_axes.plot(day_index, main.before_profits['total_profit'], label='before total_profit')
        bottom_axes.plot(day_index, main.after_profits['total_profit'], label='after total_profit')
        bottom_axes.legend(loc='upper left')

        plt.tight_layout()
        plt.show()

위 코드는 필자가 소개했던 '파이썬으로 배우는 알고리즘 트레이딩' 사이트에 나와있다. 모르겠다면 해당 사이트에 들어가서 찾아보길 바란다.



이번 포스트를 통해 알고리즘 적용 전과 후의 수익율을 비교해보았다. 다음 포스트에서는 알고리즘에 적용된 변수를 최적화해 최대 수익율을 낼 수 있는 알고리즘 변수를 구하는 방법에 대해서 알아보겠다.

< 5. 알고리즘을 통한 매수/매도 테스트, 수익율 계산 (키움 open api 주식 알고리즘 테스트 프로그래밍) >


1. 키움 open api를 이용한 주식 알고리즘 테스트 프로그래밍

2. DB 설계

3. DB 구현

4. 주식 데이터 불러오기


해당 포스트를 읽기 전에 위의 포스트들을 읽기 바란다.


우리는 이전 포스트까지 KODEX 코스닥 150 레버리지 종목의 일봉, 분봉 데이터를 불러와서 DB에 저장했다. 이번 포스트는 분봉 데이터를 토대로 자신이 구현한 알고리즘에 의해 가상으로 매수, 매도하고 수익율을 계산하는 코드에 대해 배운다. 블로그 저자가 실제 구현한 알고리즘은 공개하지 않겠다. 이번 포스트는 세부적인 코드는 공개하지 않을 것이다. 알고리즘은 각자 자신의 생각으로 구현하길 바란다. 


필자는 매수/매도 알고리즘에 사용하는 변수로 두 개를 사용했다. 이를 var1, var2라고 할 것이다. var1과 var2를 기준으로 매수, 매도한다. 이번 포스트에서 var1과 var2를 기준으로 매수/매도하고 수익율을 계산하는 코드를 포스팅한다. 다음 포스트는 알고리즘을 구현하기 전인 KODEX 코스닥 150 레버리지 당일 시가 매수, 당일 종가 매도 수익율과 알고리즘을 적용한 당일 수익율을 비교하는 코드와 수익율이 최대가 되는 var1과 var2를 구하는 코드에 대해 포스팅할 예정이다. 


이번에 구현할 코드는 특정 일의 주식 장 시작 시간부터 주식 끝날 때까지의 분봉 데이터와 var1, var2를 토대로 매수/매도하고 당일 수익율을 구하는 기능과 사용자가 지정한 기간동안 알고리즘 매수/매도를 통한 총 수익율을 구하는 기능이 있다.  



먼저 다음 코드를 보자. OrderBacktracking 클래스를 초기화하는 부분이다.

NO_CHANGE = 0 # 이번 minute에 아무 행동 안 함 CHANGE =1 # 이번 minute에 매수하거나 매도함 BUY_REASON = "VAR1과 VAR2 때문에 매수" SELL_REASON_VAR1 = "VAR1 때문에 매도" SELL_REASON_VAR2 = "VAR2 때문에 매도" class OrderBacktracking: def __init__(self,var1, var2): self.db = Db.StockDb() self.change = NO_CHANGE self.reason="" self.var1=var1 self.var2=var2

'Db.StockDb()'는 '3.DB 구현' 포스트에서 배웠다. 'self.change'는 알고리즘을 적용한 분에 매수했는 지 안 했는지 유무를 나타낸다. 즉, 장 시작 9시부터 장 끝나는 15시 30분까지 차례대로 분마다 알고리즘을 적용해 매수할 지, 매도할 지 결정하는 데 해당 분에 매수 또는 매도 했다면 'self.change'가 'CHANGE'가 되고 안 햇으면 'NO_CHANGE'가 된다. 'reason'은 위 코드에서 보이는 'BUY_REASON', 'SELL_REASON_VAR1', 'SELL_REASON_VAR2'을 나타낸다. 



아래 메서드는 알고리즘을 적용할 day를 정하고 day의 분봉 데이터과 알고리즘을 토대로 day일 매수/매도 리스트를 만드는 기능을 한다. 

def set_day(self,day): self.day = day self.daily_data = self.db.select_MinuteData(day) self.daily_start = self.db.select_DailyStart(day) self._init_by_day() self._order_history()

'select_MinuteData(day)' 를 이용해 day일의 분봉 데이터를 가져와 'daily_data' 데이터 프레임 객체에 넣는다. 'select_DailyStart(day)'는 day일의 일봉 시초가 데이터를 가져온다. '_init_by_day()' 메서드는 다음과 같다.


    def _init_by_day(self):
        self.buy_list = pd.DataFrame(columns=('date', 'buy_price', 'reason'))
        self.sell_list = pd.DataFrame(columns=('date', 'sell_price', 'reason'))
        self.order_id = 0
        self.order = False  # False : 가진 주식 없음, True : 가진 주식 있음
'_init_by_day()'는 특정 day의 매수, 매도 정보를 저장할 데이터를 초기화한다. 'set_day(day)' 메서드에서 '_init_by_day()'를 호출하는 이유는 위의 'buy_list'와 'sell_list'가 특정 day의 매수, 매도 정보를 나타내기 때문에 OrderBacktracking 객체의 day 정보가 바뀌면 'buy_list'와 'sell_list'의 기존 데이터는 필요없어지기 때문이다. 수익율을 계산할 때 특정 분에 매수한 가격과 바로 다음에 매도한 가격을 매칭해야 한다. 매칭하려면 매수정보와 매도정보에서 같은 값이 필요한데 이 때문에 'order_id'를 사용한다.   


'_order_history()'는 var1과 var2를 토대로 알고리즘을 적용하여 매수, 매도 데이터를 저장하는 기능을 한다.

def _order_history(self): if(self.is_order_data()): return data_len = len(self.daily_data) - 1 #3시 20분에서 3시 30분 제외 for i in range(data_len):

분봉 데이터, var1, var2 를 토대로 알고리즘을 적용해 매수/매도를 buy_list와 sell_list에 저장한다.             'i'분에 매수 또는 매도했다면 self.change가 'CHANGE'가 된다. 매수했다면 self.order가 True가 되고

매도했다면 self.order가 False가 된다.

if self.change == CHANGE: if self.order: data = self.buy_list.ix[self.order_id] self.db.insert_Buy(data['date'],data['buy_price'],data['reason']) else: data = self.sell_list.ix[self.order_id] self.db.insert_Sell(data['date'], data['sell_price'], data['reason']) self.order_id += 1 self.change = NO_CHANGE if self.order: price = self.daily_data['high'][data_len-1] date = self.daily_data['date'][data_len-1] self.sell_list = self.sell_list.append(pd.Series({'date': date, 'sell_price': price, 'reason': "당일 종가"}),ignore_index=True) self.db.insert_Sell(date, int(price), "당일 종가") self.db.commit()

'self.is_order_data()'는 DB에 지금 알고리즘을 적용할 day의 매수, 매도 데이터가 있는 지 확인한다. DB에 데이터가 있다면 데이터를 다시 계산할 필요없이 DB에서 데이터를 가져오기만 하면 된다. 'self.is_order_data()' 코드는 위 메서드를 설명 후 다시 설명하겠다. 

'data_len'은 분봉 데이터 갯수를 의미한다. 참고로 -1을 하는 이유는 3시 20분부터 30분까지는 분단위로 매매를 할 수 없기 때문에 3시 20분까지만 알고리즘을 이용한 매수/매도를 한다. 'for i in range(data_len)'을 통해 9시 00분부터 3시 20분까지 알고리즘 매수/매도를 한다. 구체적인 알고리즘 개인이 구현하길 바란다. 위 코드에 제시한 대로 매수와 매도 데이터(매수/매도 가격, 매수/매도 시간, 매수/매도 이유)를 저장한다. 해당 분에 매수/매도 했다면 'self.change'값을 'CHANGE'로 바꾼다. 매수했다면 'self.order'를 True로 바꾸고 매도했다면 False로 바꾼다. 

알고리즘을 통한 매수/매도를 했다면 매수/매도 데이터를 DB에 저장해야 한다. 먼저 'self.change'값을 통해 매수/매도 했는지 확인하고 'self.order'을 통해 매도했는지 매수했는지 확인한다. 이 조건들을 판별한 후 'db.insert_Sell()' 또는 'db.insert_Buy()'를 통해 DB에 데이터를 저장한다.  그리고 'self.change'를 NO_CHANGE로 바꾼다. 

위 메서드 마지막 'if self.order:'은 당일 수익율을 계산하려면 무조건 당일이 끝난 후 가지고 있는 주식이 없어야 정당하다. 따라서 주식을 계속 들고 있다면 당일 종가로 매도한다. 

마지막으로 db.commit()를 통해 insert한 데이터를 최종적으로 DB에 저장한다.  


'is_order_data()' 메서드는 위에서 설명한 것처럼 DB에 day일의 매수, 매도 데이터가 있는 지 확인한다.

    def is_order_data(self):
        self.buy_list = self.db.select_Buy(self.day)
        if len(self.buy_list) == 0:
            return False
        else:
            self.sell_list = self.db.select_Sell(self.day)
            return True

'db.select_Buy(day)'로 매수 데이터를 불러온다. 'len(self.buy_list)'를 이용해 매수 데이터가 있는 지 확인하고 없다면 False를 리턴하고 매수 데이터가 있다면 'self.db.select_Sell(day)'로 매도 데이터를 가져오고 True를 리턴한다. 



여기까지가 알고리즘을 통한 매수, 매도 데이터를 저장하는 코드였다. 다음으로 수익율을 계산하는 코드를 보자.


'get_day_profit(day)' 는 day일의 수익율을 계산한다.

def get_day_profit(self,day): self.set_day(day) profit = self.cal_profit() self.print_Profit(day,profit) return profit

위에서 배운 'set_day(day)' 메서드를 통해 day일의 매수, 매도 데이터를 계산한다. 'cal_profit()' 메서드를 이용해 당일 수익율을 계산하고 'print_Profit(day,profit)' 메서드를 이용해 day일의 수익율을 출력한다. 이 두 메서드에 대해 알아보자


'cal_profit()' 메서드는 매수, 매도 데이터를 토대로 당일 수익율을 계산한다.

    def cal_profit(self):
        self.profit=1
        buy_len = len(self.buy_list)
        for i in range(len(self.buy_list)):
            self.profit = self.profit * self.sell_list['sell_price'][i]/self.buy_list['buy_price'][i]*0.9997
        return self.profit

키움증권의 수수료가 0.015%이고 매수, 매도할 때마다 수수료가 붙기 때문에 총 0.03%의 수수료가 부과된다. 따라서 0.9997을 곱한다.


'print_Profit()' 메서드는 day일의 수익율을 출력한다.

    def print_Profit(self,day, profit):
        profit = (profit-1)*100
        print(str(day)+ " 수익률 : "+ str(profit))


지금까지 당일 수익율을 계산해보았다. 다음은 가장 중요한 지정된 기간 사이의 총 수익율을 계산하는 메서드이다. 이 메서드를 통해 해당 알고리즘이 몇일, 몇달을 걸쳐 수익이 나는지 확인할 수 있다.



'get_days_profit(start,end)' 메서드는 start 일부터 end 일까지의 총 수익율을 계산한다.

    def get_days_profit(self, start, end):
        date_data = self.db.select_Date(start, end)
        date_len = len(date_data)
        self.profit_datas= pd.DataFrame(columns=('date','profit','total_profit'))
        total_profit = 1
        if date_len == 0:
            print("두 날짜 사이의 거래일이 없음")

        for i in range(date_len):
            day = date_data['date'][i][0:8]
            self.set_day(day)
            profit = self.cal_profit()
            total_profit *= profit
            self.profit_datas = self.profit_datas.append(pd.Series({'date' : day, 'profit' : profit, 'total_profit' : total_profit}),ignore_index=True)
        self.print_Total_Profit(day, total_profit)
        return self.profit_datas

'self.db.select_Data(start,end)'를 이용해 start일에서 end일까지의 day 데이터를 불러온다. 이렇게 하는 이유는 공휴일이나 주말에는 주식시장이 개장되지 않기 때문이다. 'self.profit_datas'는 date일의 당일 수익율과 start 일부터 date일까지의 총 수익율을 저장한다. 'for i in range(data_len)'을 이용해 start 일부터 end 일까지 'set_day(day)'와 'cal_profit()' 메서드를 이용해 당일 수익율을 계산한 다음 총수익율을 구하고 'profit_datas'에 도출된 데이터를 저장한다. 마지막으로 'print_Total_Profit()'를 통해 총 수익율을 출력한다. 'print_Total_Profit()' 메서드는 다음과 같다.

    def print_Total_Profit(self,day,profit):
        profit = (profit - 1) * 100
        print(str(day) + " 총 수익률 : " + str(profit))


여기까지가 일정 기간동안 총 수익율을 구하는 코드였다. 마지막으로 함수 하나를 추가해야 한다. 다음 포스트부터는 알고리즘을 통한 매수/매도가 얼마나 효율적인지, 어떻게 하면 더 이익을 많이 날 수 있을지 확인할 것이다. 이를 위해서 우리가 알고리즘을 구현할 때 사용한 변수 var1과 var2 변수 값을 계산 변경하면서 최고의 수익율을 내는 var1과 var2 변수를 찾아야 한다. 따라서 var1과 var2에 다른 값을 넣고 초기화하는 함수를 만들어야 한다. 이 함수는 다음과 같다.

def set_var1_and_var2(self,var1,var2): if(self.var1==var1 and self.var2 == var2): return self.db.delete_Buy_Sell() self.var1 = var1 self.var2 = var2 self._init_by_day()

var1과 var2를 다른 값으로 바꾼다. var1과 var2가 바꼈기 때문에 기존에 있던 매수, 매도 데이터는 무가치하다. 따라서 'db.delete_Buy_Sell()' 메서드를 이용해 데이터를 지우고 다시 'self._init_by_day()'를 이용해 새로운 매수, 매도 데이터를 구한다.


여기까지가 알고리즘을 통한 매수, 매도를 테스트하고 수익율을 계산하는 코드였다. 다음은 이에 대한 전체 소스이다.

import sqlite3
import pandas as pd
import Db

NO_CHANGE = 0  # 이번 minute에 아무 행동 안 함
CHANGE =1   # 이번 minute에 매수하거나 매도함

BUY_REASON = "VAR1과 VAR2 때문에 매수"
SELL_REASON_VAR1 = "VAR1 때문에 매도"
SELL_REASON_VAR2 = "VAR2 때문에 매도"

class OrderBacktracking:
    def __init__(self,var1, var2):
        self.db = Db.StockDb()
        self.change = NO_CHANGE
        self.reason=""
        self.var1=var1
        self.var2=var2

    def _init_by_day(self):
        self.buy_list = pd.DataFrame(columns=('date', 'buy_price', 'reason'))
        self.sell_list = pd.DataFrame(columns=('date', 'sell_price', 'reason'))
        self.order_id = 0
        self.order = False  # False : 가진 주식 없음, True : 가진 주식 있음


    def set_day(self,day):
        self.day = day
        self.daily_data = self.db.select_MinuteData(day)
        self.daily_start = self.db.select_DailyStart(day)
        self._init_by_day()
        self._order_history()

    def set_var1_and_var2(self,var1,var2):
        if(self.var1==var1 and self.var2 == var2):
            return
        self.db.delete_Buy_Sell()
        self.var1 = var1
        self.var2 = var2
        self._init_by_day()


	....알고리즘 매수, 매도 관련 함수



    def _order_history(self):
        if(self.is_order_data()):
            return

        data_len = len(self.daily_data) - 1 #3시 20분에서 3시 30분 제외

        for i in range(data_len):
            
            분봉 데이터, var1, var2 를 토대로 알고리즘을 적용해 매수/매도를 buy_list와 sell_list에 저장한다.
            'i'분에 매수 또는 매도했다면 self.change가 'CHANGE'가 된다. 매수했다면 self.order가 True가 되고
            매도했다면 self.order가 False가 된다.

            if self.change == CHANGE:
                if self.order:
                    data = self.buy_list.ix[self.order_id]
                    self.db.insert_Buy(data['date'],data['buy_price'],data['reason'])
                else:
                    data = self.sell_list.ix[self.order_id]
                    self.db.insert_Sell(data['date'], data['sell_price'], data['reason'])
                    self.order_id += 1
                self.change = NO_CHANGE

        if self.order:
            price = self.daily_data['high'][data_len-1]
            date = self.daily_data['date'][data_len-1]
            self.sell_list = self.sell_list.append(pd.Series({'date': date, 'sell_price': price, 'reason': "당일 종가"}),ignore_index=True)
            self.db.insert_Sell(date, int(price), "당일 종가")

        self.db.commit()

    def cal_profit(self):
        self.profit=1
        buy_len = len(self.buy_list)
        for i in range(len(self.buy_list)):
            self.profit = self.profit * self.sell_list['sell_price'][i]/self.buy_list['buy_price'][i]*0.9997
        return self.profit

    def is_order_data(self):
        self.buy_list = self.db.select_Buy(self.day)
        if len(self.buy_list) == 0:
            return False
        else:
            self.sell_list = self.db.select_Sell(self.day)
            return True

    def get_day_profit(self,day):
        self.set_day(day)
        profit = self.cal_profit()
        self.print_Profit(day,profit)
        return profit

    def get_days_profit(self, start, end):
        date_data = self.db.select_Date(start, end)
        date_len = len(date_data)
        self.profit_datas= pd.DataFrame(columns=('date','profit','total_profit'))
        total_profit = 1
        if date_len == 0:
            print("두 날짜 사이의 거래일이 없음")

        for i in range(date_len):
            day = date_data['date'][i][0:8]
            self.set_day(day)
            profit = self.cal_profit()
            total_profit *= profit
            self.profit_datas = self.profit_datas.append(pd.Series({'date' : day, 'profit' : profit, 'total_profit' : total_profit}),ignore_index=True)
        self.print_Total_Profit(day, total_profit)
        return self.profit_datas


    def print_Profit(self,day, profit):
        profit = (profit-1)*100
        print(str(day)+ " 수익률 : "+ str(profit))

    def print_Total_Profit(self,day,profit):
        profit = (profit - 1) * 100
        print(str(day) + " 총 수익률 : " + str(profit))


if __name__ == "__main__":
    main = OrderBacktraking()
    main.get_days_profit("20170922","20171107")



여기까지가 알고리즘을 통한 매수, 매도 테스트, 수익율 계산 코드이다. 마지막으로 다음 포스트에서는 알고리즘을 적용 후와 적용 전 비교, 최대 수익율을 얻기 위해 알고리즘에 대입하는 변수들을 최적화하는 코드에 대해서 알아볼 것이다. 

+ Recent posts