백테스팅 구축 - (2) 차트 데이터 가공하기
지난 포스팅에서 눈치가 빠른 분이라면 뭔가 이상한 부분을 분명히 느끼셨을 것이다. 바로 백테스팅 과정에 대한 설명이 기존 의도와는 다르게 서술되었기 때문이다. 즉, 실제 거래 방법과 동일하게 하루 하루 돌면서 종목을 선정하고 그 선정한 종목을 바탕으로 백테스팅을 하는 방법과 한 종목에 대해 백테스팅을 하는 방법을 이야기하고 전자를 바탕으로 전략을 구축해보겠다고 서술했는데, 그렇게 서술하게 될 경우 종목 선정에 관한 부분을 별도로 설정해야 했기 때문에 후자의 방법으로 코드를 제작했었다. 따라서 이번 게시글에서는 종목 선정 기준을 임의로 설정하고, 그를 바탕으로 하루 하루 조건을 충족시키는 종목을 찾는 코드를 제작해보고자 한다.
가장 흔하디 흔한 거래 전략으로는 이동평균선 간의 골든 크로스 시 매수, 데드 크로스 시 매도와 같은 방법이 있으며 이외에는 엔벨로프 선이나 볼린저밴드 등을 활용한 거래 전략의 설정이 가능하다. 하지만 여기서는 백테스팅 절차를 알아보기에 가장 편리하고 가장 흔한 방법인 이동평균선을 바탕으로 백테스팅 코드를 구축할 예정이다. 그렇다면 일단 이동평균선을 구축해야한다.
차트 데이터 가공하기 (1)
특정 조건에 맞는 종목을 찾기 위해서는 특정 조건을 판단할 수 있는 판단 근거를 제작해줘야 한다. 여기서 이야기하는 판단 근거란 곧 이동평균선의 구축을 의미한다. 그렇다면 이제 이동평균선을 만들어보도록 하자. 여기서는 거래하고자 하는 종목을 선별해내는 근거가 되는 데이터를 제작한 후, 다시 MySQL에 입력해주어야 일자별로 데이터들을 불러오면서 백테스팅을 실시할 수 있다. 물론 한 종목을 대상으로 하여 백테스팅을 실시할 것이라면 굳이 복잡한 절차를 거치지 않아도 된다. 왜냐하면 한 종목의 데이터를 불러온 후, 그를 가공한 다음에 그를 바탕으로 거래하도록 전략을 설정해주기만 하면 되기 때문이다.
하지만 이 방법을 적용하든 저 방법을 적용하든 간에 상관없이 차트 데이터 안에는 이동평균선 데이터가 필요한 것은 매한가지이다. 그렇다면 우리는 저장된 데이터를 불러와서 굳이 가공할 필요 없이, 차트 데이터를 조회하고 저장할 적에(rq_chart_data 함수를 이용했던) 이동평균선(MA)을 같이 제작해서 MySQL에 입력해주면 된다. 그러면 이제 우리가 이전에 제작하고 넘어왔던 '차트 데이터 이어받기' 부분을 찾아주도록 하자. (Ctrl + F를 누른 후에 to_sql을 입력해주면 알아서 찾아줄 것이다.)
이 부분이 바로 우리가 차트 데이터를 이어받을 때 처리해야 하는 코드들이었는데, df_day_data.to_sql()부분이 바로 차트 데이터를 저장하는 부분이다. 그렇다면 우리는 rq_chart_data의 처리 이후에 차트 데이터가 저장되어 있는 df_day_data 변수를 바탕으로 이동평균선을 제작한 후에 to_sql을 사용하면 된다. 이 코드 안에 함수를 제작하면 복잡해지니, 새로운 파일을 만든 후에 새로운 함수를 제작해서 이동평균선을 제작한 후, 결과값을 반환해주도록 하자.
for code in code_list:
print(code)
if code in code_list_inDB:
saved_date = posting_mysql.select_data_where('sv_day', 'item_savepoint', 'item_savepoint', 'code', code)
print("종목코드:", code)
print("저장일자:", saved_date[0][0])
print("최신일자:", latest_date)
if saved_date[0][0] == latest_date:
print("종목코드:", code, " 데이터가 최신 데이터입니다.")
else:
print("종목코드:", code, " 데이터가 구식 데이터입니다.")
trade.rq_chart_data(code, latest_date, 1)
df_day_data = pandas.DataFrame(trade.day_data, columns=['date', 'open', 'high', 'low', 'close', 'volume', 'trade_volume'])
print(df_day_data)
df_day_data.to_sql(name="s" + code, con=engine_all, index=False, if_exists='replace')
print("종목코드:", code, " 데이터베이스에 저장되었습니다.")
posting_mysql.update_data('item_savepoint', 'item_savepoint', 'sv_day', latest_date, 'code', code)
else:
print("안에 없음")
차트 데이터 가공하기 (2)
데이터 처리를 보조해줄 별도의 파일을 제작한 후, 그 파일 내에서 변수를 받고 계산을 진행한 후 결과값을 반환해 줄 함수를 제작함으로써 이후에도 계속해서 불러와 사용할 수 있다. Open API 코드가 제작되어 있는 부분에서 사용하지 않는 이유는, 이후에 또 다른 알고리즘을 제작하게 되거나 별도의 값들을 바탕으로 또 다시 이동평균선을 계산하게 될 수도 있기 때문이다.
새롭게 제작한 파일의 이름은 posting_manage_data이며 함수의 이름은 make_ma로 설정했다. 이 함수를 제작할 때 주의해야 할 부분은 다음과 같다.
- 어떤 형태의 데이터를 입력하든 간에 일자 별로 순서를 나열하여 그를 바탕으로 이동평균선을 제작할 것
일단 아래의 코드를 보면 함수의 아래에 print(df)를 했고, 이동평균선을 제작한 후에 print(df)를 설정했다. 전처리의 결과값을 전후로 확인하고 제대로 처리되었는지를 확인하기 위함이다.
## file_name : posting_manage_data
def make_ma(chart_data):
df = chart_data
print(df)
ma3 = df['close'].rolling(window=3).mean()
ma5 = df['close'].rolling(window=5).mean()
ma10 = df['close'].rolling(window=10).mean()
ma15 = df['close'].rolling(window=15).mean()
ma20 = df['close'].rolling(window=20).mean()
ma60 = df['close'].rolling(window=60).mean()
ma120 = df['close'].rolling(window=120).mean()
df.insert(len(df.columns), "MA3", ma3)
df.insert(len(df.columns), "MA5", ma5)
df.insert(len(df.columns), "MA10", ma10)
df.insert(len(df.columns), "MA15", ma15)
df.insert(len(df.columns), "MA20", ma20)
df.insert(len(df.columns), "MA60", ma60)
df.insert(len(df.columns), "MA120", ma120)
print(df)
return df
date open high low close volume trade_volume
0 20210702 14650 15100 14400 15050 233137 3470
1 20210701 14500 14700 14400 14650 55072 804
2 20210630 14350 14700 13950 14700 97966 1405
3 20210629 14550 14550 14150 14300 26679 383
4 20210628 14200 14600 14200 14450 35122 507
... ... ... ... ... ... ... ...
8331 19890712 19878 20209 19878 19878 2732 2
8332 19890711 19547 20011 19547 19878 5871 4
8333 19890710 19812 19812 19547 19679 558 0
8334 19890708 18950 19547 18950 19348 2279 1
8335 19890707 19215 19613 19083 19613 2535 2
[8336 rows x 7 columns]
date open high ... MA20 MA60 MA120
0 20210702 14650 15100 ... NaN NaN NaN
1 20210701 14500 14700 ... NaN NaN NaN
2 20210630 14350 14700 ... NaN NaN NaN
3 20210629 14550 14550 ... NaN NaN NaN
4 20210628 14200 14600 ... NaN NaN NaN
... ... ... ... ... ... ... ...
8331 19890712 19878 20209 ... 19735.55 20119.833333 19533.425000
8332 19890711 19547 20011 ... 19775.30 20130.883333 19544.466667
8333 19890710 19812 19812 ... 19805.10 20135.300000 19552.750000
8334 19890708 18950 19547 ... 19811.75 20134.200000 19558.275000
8335 19890707 19215 19613 ... 19801.80 20132.000000 19567.108333
[8336 rows x 14 columns]
위의 결과값을 보니, 뭔가 이상하다. 즉, 이동평균선은 가장 옛날의 데이터부터 시작해서 오늘까지 제작되어야 하기 때문에 120일 이동평균선은 1989년 7월 7일에 없고 2021년 7월 2일에 있어야 하지만, 반대로 되어 있는 것이다. 이를 처리하기 위해서는 우리는 받아온 데이터를 date라는 칼럼을 기준으로 정렬하고, 인덱스를 재설정하는 절차가 필요하다. 이 절차를 구축하면 데이터가 뒤죽박죽으로 들어온다고 하더라도 한 번 정렬을 한 후에 이동평균성르 실행하게 되므로 문제 없는 데이터 처리가 가능해진다. 이러한 데이터 처리는 pandas의 sort_values와 reset_index 함수를 통해 사용할 수 있다. 그렇다면 첫 번째 print(df)의 하단에 아래와 같은 문구를 추가한 후에, 다시 실행하고 그 결과값을 확인해보도록 하자. 추가로 drop_duplicates 함수를 통해 혹시 모를 중복 데이터를 삭제해주도록 하자.
## import pandas as pd 사용해야 함
df = chart_data.drop_duplicates()
df = df.sort_values(['date']).reset_index()
print(df)
date open high low close volume trade_volume
0 20210702 14650 15100 14400 15050 233137 3470
1 20210701 14500 14700 14400 14650 55072 804
2 20210630 14350 14700 13950 14700 97966 1405
3 20210629 14550 14550 14150 14300 26679 383
4 20210628 14200 14600 14200 14450 35122 507
... ... ... ... ... ... ... ...
8331 19890712 19878 20209 19878 19878 2732 2
8332 19890711 19547 20011 19547 19878 5871 4
8333 19890710 19812 19812 19547 19679 558 0
8334 19890708 18950 19547 18950 19348 2279 1
8335 19890707 19215 19613 19083 19613 2535 2
[8336 rows x 7 columns]
index date open high low close volume trade_volume
0 8335 19890707 19215 19613 19083 19613 2535 2
1 8334 19890708 18950 19547 18950 19348 2279 1
2 8333 19890710 19812 19812 19547 19679 558 0
3 8332 19890711 19547 20011 19547 19878 5871 4
4 8331 19890712 19878 20209 19878 19878 2732 2
... ... ... ... ... ... ... ... ...
8331 4 20210628 14200 14600 14200 14450 35122 507
8332 3 20210629 14550 14550 14150 14300 26679 383
8333 2 20210630 14350 14700 13950 14700 97966 1405
8334 1 20210701 14500 14700 14400 14650 55072 804
8335 0 20210702 14650 15100 14400 15050 233137 3470
[8336 rows x 8 columns]
index date open ... MA20 MA60 MA120
0 8335 19890707 19215 ... NaN NaN NaN
1 8334 19890708 18950 ... NaN NaN NaN
2 8333 19890710 19812 ... NaN NaN NaN
3 8332 19890711 19547 ... NaN NaN NaN
4 8331 19890712 19878 ... NaN NaN NaN
... ... ... ... ... ... ... ...
8331 4 20210628 14200 ... 14575.0 14577.500000 12981.666667
8332 3 20210629 14550 ... 14565.0 14595.833333 13010.000000
8333 2 20210630 14350 ... 14542.5 14617.500000 13041.250000
8334 1 20210701 14500 ... 14520.0 14619.166667 13067.083333
8335 0 20210702 14650 ... 14530.0 14634.166667 13097.500000
sort_values와 reset_index를 적용하기 전에는 20210702 일자의 이동평균선 값이 없고 19890707 일자의 이동평균선 값이 있었으나, 지금은 반대로 바뀌었고 잘 적용되었다는 것을 확인할 수 있다. 하지만 index란을 보면 원래의 index와 새롭게 제작된 index 두 개로 나뉘어 있는 모습을 확인할 수 있는데, 이는 drop 함수를 통해 삭제할 수 있다. 따라서 drop(['index']) 코드를 뒤에 추가해주면 된다. 지금까지 제작한 make_ma 함수의 전문은 아래와 같다.
## file_name : posting_manage_data
import pandas as pd
def make_ma(chart_data):
df = chart_data
df = df.sort_values(['date']).reset_index()
# 이동평균선 값 계산
ma3 = df['close'].rolling(window=3).mean()
ma5 = df['close'].rolling(window=5).mean()
ma10 = df['close'].rolling(window=10).mean()
ma15 = df['close'].rolling(window=15).mean()
ma20 = df['close'].rolling(window=20).mean()
ma60 = df['close'].rolling(window=60).mean()
ma120 = df['close'].rolling(window=120).mean()
# 이동평균선을 df에 추가
df.insert(len(df.columns), "MA3", ma3)
df.insert(len(df.columns), "MA5", ma5)
df.insert(len(df.columns), "MA10", ma10)
df.insert(len(df.columns), "MA15", ma15)
df.insert(len(df.columns), "MA20", ma20)
df.insert(len(df.columns), "MA60", ma60)
df.insert(len(df.columns), "MA120", ma120)
# 두 개의 index 칼럼 중 기존의 index 칼럼 삭제
df = df.drop(['index'], axis=1)
return df
다시 Open API가 제작되어 있는 곳으로 돌아와서 코드를 아래와 같이 수정해주도록 하자.(세 번째 줄의 코드가 추가됨)
trade.rq_chart_data(code, latest_date, 1)
df_day_data = pandas.DataFrame(trade.day_data, columns=['date', 'open', 'high', 'low', 'close', 'volume', 'trade_volume'])
df_day_data = posting_manage_data.make_ma(df_day_data) ## 이 부분이 추가됨
df_day_data.to_sql(name="s" + code, con=engine_all, index=False, if_exists='replace')
print("종목코드:", code, " 데이터베이스에 저장되었습니다.")
그리고 다시 WorkBench를 통해 데이터가 잘 저장되었는지를 확인해보면 이동평균선 제작은 완료된 것이다. 이제 차트 데이터를 불러와서 이동평균선을 제작한다거나 하는 복잡한 절차를 거치지 않아도 되는 것이다.
결과값을 보면 5일차부터 MA5가 생성되고, MA10은 10일차부터, MA20은 20일차부터 생성된다는 것을 확인해볼 수 있다. 마찬가지로 가장 최신일자의 데이터를 보면 MA5부터 MA120까지 모든 값이 생성되어 있다는 것을 확인할 수 있다.
[추신] 데이터를 제작하다보면 이전에 조회했던 차트 데이터 값이 현재 조회하는 차트 데이터 값에 포함되어 함께 계산되는 경우가 발생하게 되는데, 이 문제는 현재 제작한 코드가 class를 초기화하는 절차를 거치는 코드가 아니기 때문에 발생하는 문제이다. 즉, 특정 코드를 바탕으로 클래스를 호출한 후 빠져나오고 다시 클래스를 호출해야 하는데, 현재는 그 절차가 구축되어 있지 않다는 것이다. 이는 if문 밖에서 for문을 돌리기 때문에 발생하는 문제로 볼 수 있는데, 다음 포스팅에서 이 문제를 수정하도록 하겠다.
'AUTO TRADE > Back test' 카테고리의 다른 글
백테스팅 구축 (4) - 일자별 차트 데이터 불러오기 (0) | 2021.07.04 |
---|---|
백테스팅 구축 (3) - 일자 계산하기 (0) | 2021.07.04 |
키움증권 Open API - 차트 데이터 함수 수정 (0) | 2021.07.04 |
백테스팅 구축 - (1) 일자 변수 처리 및 차트 데이터 조회 (0) | 2021.07.03 |
백테스팅 전략 수립 시 주의사항 (0) | 2021.06.16 |
소중한 공감 감사합니다