코인 자동 매매 파이썬 백테스트 (1)
엑셀로 한땀한땀 백테스트하는 것도 좋지만 시간이 너무 오래 걸리는 게 단점이다.
오늘은 엑셀로 가상화폐투자마법공식 투자전략 8(5일 상승장, 변동성 돌파, 변동성 조절)을 백테스트해보겠다.
import pyupbit
import numpy as np
import pandas as pd
백테스트에 필요한 것들이다.
ss = {'btc': pyupbit.get_ohlcv("KRW-BTC", count=1582), 'eth': pyupbit.get_ohlcv("KRW-ETH", count=1582),
'xrp': pyupbit.get_ohlcv("KRW-XRP", count=1582), 'ltc': pyupbit.get_ohlcv("KRW-ltc", count=1582)}
우선 각 코인들의 데이터를 불러온다. 추후에 for문으로 데이터프레임 칼럼을 많이 채우기 때문에 딕셔너리 형태로 저장한다.
1582일의 데이터만 사용하는 이유는 오늘(2022/2/21) 기준으로 1582일 이전이 2017년 10월 24일이고 비트코인을 제외한 나머지 코인들이 10월 21일 ~ 23일 데이터가 업비트에서 안불러와지기 때문이다.
for i in ss:
del ss[i]['volume'], ss[i]['value']
ss[i].columns = [f"{i}_open", f"{i}_high", f"{i}_low", f"{i}_close"]
dfs = pd.concat([ss['btc'], ss['eth'], ss['xrp'], ss['ltc']], axis=1)
dfs = dfs[['btc_open', 'eth_open', 'xrp_open', 'ltc_open', 'btc_high', 'eth_high', 'xrp_high', 'ltc_high',
'btc_low', 'eth_low', 'xrp_low', 'ltc_low', 'btc_close', 'eth_close', 'xrp_close', 'ltc_close']]
불러온 데이터 중 볼륨과 밸류는 사용하지 않는 데이터이기 때문에 삭제해주고, 데이터가 모두 같은 이름의 칼럼으로 되어있으면 분간하기 힘들기 때문에 ohlc앞에 코인 이름을 붙여서 칼럼(기동)이 btc_open과 같은 형태로 나오도록 한다.
그리고 pandas의 concat함수를 통해 데이터를 하나의 데이터프레임으로 합친다.
마지막 문단은 사실 백테스트 자체에는 필요 없지만 엑셀로 백테스트할 때 정리하던 습관이 있어서 그 습관대로 정리했다. 무시해도 지장없다.
k = 0.5
coins = ['btc', 'eth', 'xrp', 'ltc']
target_v = 0.05
slpy = 0.002
변수들을 지정해둔다. 위에서부터 k(타겟 가격 설정 시 레인지에 곱하는 값), 사용하는 코인들, 타겟 변동성, 슬리피지이다.
for i in coins:
dfs[f'{i}_range'] = dfs[f'{i}_high'] - dfs[f'{i}_low']
dfs[f'{i}_range'] = dfs[f'{i}_range'].shift(1)
레인지를 설정한다. 어제 레인지를 오늘 활용하기 때문에 밑으로 한 칸 내린다. 엑셀에서는 다음과 같은 형태로 나타난다.
for i in coins:
dfs[f'{i}_range_r'] = (dfs[f'{i}_range'].shift(-1))/(dfs[f'{i}_open'])
dfs[f'{i}_range_r'] = dfs[f'{i}_range_r'].shift(1)
레인지 비율을 설정한다.
for i in coins:
dfs[f'{i}_target'] = dfs[f'{i}_open'] + (dfs[f'{i}_range'] * k)
매수 타겟 가격을 설정한다.
for i in coins:
dfs[f'{i}_ma5'] = dfs[f'{i}_open'].rolling(window=5).mean()
for i in coins:
dfs[f'{i}_h>t'] = np.where(dfs[f'{i}_high'] > dfs[f'{i}_target'], 1, 0)
for i in coins:
dfs[f'{i}_o>m'] = np.where(dfs[f'{i}_open'] > dfs[f'{i}_ma5'], 1, 0)
for i in coins:
dfs[f'{i}_signal'] = dfs[f'{i}_o>m'] * dfs[f'{i}_h>t']
for i in coins:
dfs[f'{i}_percent'] = np.where(
target_v > dfs[f'{i}_range_r'], (1/4), (1/4) * (target_v/dfs[f'{i}_range_r']))
for i in coins:
dfs[f'{i}_R'] = (dfs[f'{i}_close'] * (1-slpy)) / \\
(dfs[f'{i}_target'] * (1+slpy)) - 1
위에서부터 순서대로
5일 이동평균선 값을 구한다.
당일 고가가 금일 타겟가격보다 높은지 구한다.
당일 시가가 5일 이동평균선보다 높은지 구한다.
당일 매수 시그널(고가>타겟, 시가≥이평선)이 떴는지 구한다.(당일 고가를 백테스트에 넣는 게 이상하다고 생각할 수 있지만 당일 고가는 수익률에 영향을 주지 않는다. 수익률 = 종가/타겟가격)
변동성 조절 값을 구한다.
코인 별 수익률을 구한다.(시그널 상관없이 타겟가격에 사고 종가에 팔았을 때 수익률이다. 슬리피지는 매수, 매도 시 0.2%로 설정했다.)
dfs['P_R'] = dfs['btc_R'] * dfs['btc_signal'] * dfs['btc_percent'] + dfs['eth_R'] * dfs['eth_signal'] * dfs['eth_percent'] + dfs['xrp_R'] * \\
dfs['xrp_signal'] * dfs['xrp_percent'] + dfs['ltc_R'] * dfs['ltc_signal'] * \\
dfs['ltc_percent']
자동매매 수익률을 구한다.
(코인 별 수익률 * 코인 별 시그널 * 코인 별 투자 비중)의 방식으로 각각의 코인 별 자동매매 수익을 계산해서 더한다.
length = len(dfs)
dfs.at[dfs.index[0], 'P_B'] = 1
for i in range(1, length):
dfs.at[dfs.index[i], 'P_B'] = dfs['P_B'][i-1] * (1 + dfs['P_R'][i])
데이터프레임의 길이(행 개수)를 구하고 처음 포트폴리오 자산 1원으로 설정한다.
당일 포트폴리오 가치 = 전일 포트폴리오 가치 * (1 + 당일 자동매매 수익률)
위의 수식으로 당일 포트폴리오 가치를 구한다.
dfs['MDD'] = dfs['P_B']/dfs['P_B'].cummax() -1
포트폴리오 MDD를 구한다.
s = dfs['P_B'][length - 1]
cagr = s ** (1/(length/365)) - 1
mdd = dfs['MDD'].min()
dfs.at[dfs.index[0], 'result_1'] = '수익률'
dfs.at[dfs.index[1], 'result_1'] = 'CAGR'
dfs.at[dfs.index[2], 'result_1'] = 'MDD'
dfs.at[dfs.index[0], 'result_2'] = s
dfs.at[dfs.index[1], 'result_2'] = cagr
dfs.at[dfs.index[2], 'result_2'] = mdd
수익률, cagr, mdd를 구한다.
전체 코드는 여기서 볼 수 있다.
맨 앞에서 말한 것처럼 엑셀로 한땀한땀 백테스트하는 것도 좋지만 파이썬으로 백테스트를 하면 k값, 슬리피지, 타겟변동성 등등 조절 가능한 팩터들을 모두 백테스트해서 최적 값을 찾는 것이 가능해서 좋다. 앞으로는 대부분 파이썬으로 백테스트를 할 것 같다.
후기 -
pandas나 numpy를 다뤄본 적이 없기 때문에 중간중간에 딕셔너리 형태로 기본 데이터를 저장한다든가 데이터를 합친다든가 밸런스 구할 때 셀마다 특정값을 지정해준다든가 코드 한 줄 쓸 때마다 오류를 냈지만 구글링을 해보니 모든 해결책이 나와있었다. 세상에 접근 불가능한 지식은 없고, 관심 없는 지식만 있다는 결론을 나름대로 내려봤다. 코딩을 공부한다는 것은 어떻게 구글링할 지를 공부하는 것이 아닐까?