일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Bybit
- 프리미엄지수
- latest_big_deal
- 머신러닝
- place_active_order
- xgboost
- 백테스팅
- 변동성돌파
- myposition
- API
- 롱숏비율
- Query_Index_Price_Kline
- Python
- 비트코인
- kline
- 바이비트
- open_interest
- 파이썬
- 호가창
- 파아썬
- Machine Learning
- bitcoin
- 데이터불러오기
- Query_Kline
- 모멘텀지표
- 자동매매
- Query_Premium_Index_Kline
- 코인
- Public_Trading_Records
- orderbook
- Today
- Total
돈벌고싶다
Back Testing의 함정 본문
설명
우리는 백테스팅에는 문제가 존재한다는 것을 알지만 그럼에도 수많은 백테스팅을 한다. 어찌되었던 간에 유용한 것은 맞기 때문이다. 오늘은 백테스팅을 진행하며 내가 그동안 중요하다고 생각했던 부분에 대해 말할까 한다. 당연한 내용이기에, 다른 블로그 글과 크게 특별한 내용은 없지만, 백테스팅에 대한 코드 정도는 유용할지도 모른다.
코드
1. 라이브러리
import numpy as np
import pandas as pd
import schedule
import time
import datetime
import calendar
import math
import ccxt
import matplotlib.pyplot as plt
2. 함수 호출
def get_binance_data(symbol, interval, end_date):
btc_ohlcv = binance.fetch_ohlcv(symbol, interval, limit=1000, params={'endTime':end_date})
df = pd.DataFrame(btc_ohlcv, columns=['datetime', 'open', 'high', 'low', 'close', 'volume'])
df['datetime'] = pd.to_datetime(df['datetime'], unit='ms')
df.set_index('datetime', inplace=True)
return df
def to_mstimestamp(string):
string = datetime.datetime.strptime(string, "%Y-%m-%d %H:%M:%S")
string = datetime.datetime.timestamp(string)
string = int(string) * 1000
return string
# MFI 구하기 : https://sjblog1.tistory.com/45
def get_mfi(df, period):
df['ma20'] = df['close'].rolling(window=20).mean() # 20일 이동평균
df['stddev'] = df['close'].rolling(window=20).std() # 20일 이동표준편차
df['upper'] = df['ma20'] + 2*df['stddev'] # 상단밴드
df['lower'] = df['ma20'] - 2*df['stddev'] # 하단밴드
df['PB'] = (df['close'] - df['lower']) / (df['upper'] - df['lower'])
df['TP'] = (df['high'] + df['low'] + df['close']) / 3
df['PMF'] = 0
df['NMF'] = 0
for i in range(len(df.close)-1):
if df.TP.values[i] < df.TP.values[i+1]:
df.PMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
df.NMF.values[i+1] = 0
else:
df.NMF.values[i+1] = df.TP.values[i+1] * df.volume.values[i+1]
df.PMF.values[i+1] = 0
df['MFR'] = (df.PMF.rolling(window=period).sum() /
df.NMF.rolling(window=period).sum())
df['mfi'] = 100 - 100 / (1 + df['MFR'])
del df['ma20']
del df['stddev']
del df['upper']
del df['lower']
del df['PB']
del df['TP']
del df['PMF']
del df['NMF']
del df['MFR']
return df
3. 데이터 가공
binance = ccxt.binance()
data = get_binance_data('BTC/USDT', '1d', to_mstimestamp("2022-07-20 23:59:59"))
data = get_mfi(data, 14)
data['ma_mfi'] = data['mfi'].rolling(window=10).mean()
data['ma5'] = data['close'].rolling(window=5).mean()
data['ma15'] = data['close'].rolling(window=15).mean()
data['ma30'] = data['close'].rolling(window=30).mean()
data['ma50'] = data['close'].rolling(window=50).mean()
data['ma200'] = data['close'].rolling(window=200).mean()
이번 글에서는 MFI를 이용한 매매 전략 백테스팅을 예로 들겠다. 따라서 해당 전략을 구현하기 전에 필요한 데이터를 계산하였다.
4. 백테스팅
# leverage = 1
fees = 0.048
data['long_leverage'] = data.apply(lambda x: 1 if x['open'] > x['ma5'] else 1, axis=1)
data['short_leverage'] = data.apply(lambda x: 1 if x['ma5'] > x['open'] else 1, axis=1)
data['long'] = (data['close']/data['open']).shift(-1)
data['short'] = (data['open']/data['close']).shift(-1)
data['long'] = data.apply(lambda x: (x['long']-(fees/100)*2-1)*x['long_leverage']+1 if (x['mfi'] > x['ma_mfi']) & (x['ma15'] < x['close']) else 1, axis=1)
data['short'] = data.apply(lambda x: (x['short']-(fees/100)*2-1)*x['short_leverage']+1 if (x['mfi'] < x['ma_mfi']) & (x['ma15'] > x['close']) else 1, axis=1)
- long_leverage & short_leverage : moving average를 기준으로 레버리지를 얼마나 줄 것인가에 대한 column이다. 위에서는 일단은 구현은 한 상태이나 레버리지를 두고 싶지는 않아서 모두 1로 설정하였다.
- long & short : 롱으로 투기할 경우, 숏으로 투기할 경우에 대한 각각의 수익률이다. 데이터 가공을 진행하면서 종가(close)까지 모두 이용하였기 때문에, 수익률을 계산할 때는 shift(-1)을 해줘야 data leakage가 발생하지 않는 다는 것에 유의하자.
매매 전략은 다음과 같다. MFI값이 MFI moving average값보다 크며, 종가가 15일 이동평균선보다 클 경우, 매수하여 24시간 후 매도한다.
5. 결과
data['long'].cumprod().iloc[-2]
위 코드에 대한 출력값은 2.05로, 지난 1000일 동안 수익률은 대략 100% 발생하였다. 그렇다면 지금 당장 해당 전략을 사용하여 돈 복사를 진행하면 되는 것일까? 당연히 안된다. 아래 코드를 보자.
data['long'] = data['long'].cumprod()
data['short'] = data['short'].cumprod()
data[['long', 'close']].plot(figsize=(12, 5), secondary_y=['long'])
plt.axhline(1, color='lightgray', linestyle='--', linewidth=2)
plt.axhline(data['long'].iloc[-2], color='red', linestyle='--', linewidth=2)
- Orange Line : 종가 그래프
- Blue Line : 매매 전략에 따른 수익률 그래프
- Gray Line : 수익률 시작점
- Red Line : 수익률 마감
시각화된 자료를 보면 알겠지만, 사실상 그냥 비트코인과 동일하게 움직였다는 것을 알 수 있다. 그냥 종목을 들고 있을때와 동일한 수익률이며, 심지어 비트코인이 우상향 했기 때문에 돈을 번 것이지 전략이 좋아 돈을 번 것이 아니게 된다. 전체 기간에 대해 다양한 테스트를 진행한 결과 비트코인의 경우 대부분의 long 전략이 수익을 낼 수 밖에 없다. 비트코인 자체가 워낙에 강하게 상승장이였던 기간이 있었기 때문이다.
즉 백테스팅에서 수익이 발생했다는 것에만 초점을 둬야 하는 것이 아니며, 전체 기간에 대한 수익률을 시각화하여 수익률 그래프가 장과는 상관없이 계속해서 우상향 하고 있는가가 중요하며, 그 어떠한 장에서도 흔들리지 않는 전략을 짜는 것에 초점을 두어야 한다.
'코인과코딩, 그리고 인공지능' 카테고리의 다른 글
파이썬을 이용한 페어 트레이딩과 실패 (1) | 2022.08.29 |
---|---|
머신러닝으로 지지선 & 저항선 긋기 (10) | 2022.07.22 |
파이썬으로 모멘텀 지표 주가추세 매매 백테스팅 (0) | 2022.06.30 |
가격 데이터, 지수 가격 데이터, 그리고 프리미엄 지수 가격 데이터 (0) | 2022.05.16 |
파이썬을 이용한 매우 간단한 백테스팅 (1) | 2022.04.27 |