본문 바로가기

코딩

코인 자동 매매 파이썬 백테스트 (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를 다뤄본 적이 없기 때문에 중간중간에 딕셔너리 형태로 기본 데이터를 저장한다든가 데이터를 합친다든가 밸런스 구할 때 셀마다 특정값을 지정해준다든가 코드 한 줄 쓸 때마다 오류를 냈지만 구글링을 해보니 모든 해결책이 나와있었다. 세상에 접근 불가능한 지식은 없고, 관심 없는 지식만 있다는 결론을 나름대로 내려봤다. 코딩을 공부한다는 것은 어떻게 구글링할 지를 공부하는 것이 아닐까?

 

binance 10% 커미션 링크