본문 바로가기

파이썬으로 퀀트 프로그램 만들기 project

비트코인 차트에 코사인 유사도 접목하기

728x90

이번에는 "코사인 유사도"를 이용하여 비트코인 차트 매매에 사용해보겠습니다.

우선 코사인 유사도란, 내적공간의 두 벡터간 각도의 코사인값을 이용하여 측정된 벡터간의 유사한 정도를 의미합니다.

과거 차트중에 현재 차트 모양과 비슷한(코사인 유사도가 높은) 차트 구간을 찾습니다.

그리고 해당 차트 구간 뒤의 봉을 몇개 확인합니다.

그리고 그 몇개의 봉이 상승하면, 현재의 차트도 상승, 하락하면 현재의 차트도 하락 할것이라고 예상할 수 있습니다.

 

즉, '과거의 차트에서 이 모양일 때 이렇게 움직였으니, 지금의 비슷한 모양의 차트도 이렇게 움직일것이다' 라고 예측할 수 있을 것입니다.

 

나머지는 코드를 통해 추가 설명 해드리겠습니다.

 

#코드

from binance.client import Client
import config
import pandas as pd

client = Client(config.apiKey, config.apiSecurity)

저는 binance api를 사용했습니다. binance clinent 객체를 만들어 줍니다.

apikey와 apisecurity는 config.py파일을 따로 만들어서 값을 입력해뒀습니다.

 

## get_ohlcv
import datetime
import time

def get_ohlcv(ticker, interval, count, isFuture = False):
    info = client.get_historical_klines(ticker,interval,limit=2)
    dif = info[1][0]-info[0][0]
    st = info[1][0] - dif*(count-1)
    if isFuture:
        info = client.futures_historical_klines(ticker,interval,start_str=st)
    else:
        info = client.get_historical_klines(ticker,interval,start_str=st)

    length = len(info) 
    info = pd.DataFrame(info)
    
    for i in range(length):
        t = int(info.iloc[i,0])
        t = t/1000
        t = time.localtime(t)
        dt = datetime.datetime(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
        info.iloc[i,0] = dt


    
    info.set_index(keys=[info.loc[:,0].values], inplace=True)
           
    info = info[[1,2,3,4,5]] 
    info[[1,2,3,4,5]] = info[[1,2,3,4,5]].astype('float')
    info.columns = ['open','high','low','close','volume']
    info
    
    return info.copy()

binance api에는 따로 get_ohlcv 메소드가 없습니다.

기존에 제가 사용하던 pyupbit 라이브러리와 형식에 맞게 get_ohlcv 함수를 구현해줬습니다.

 

# consine similarity
import numpy as np

def cosine_similarity(x, y):
    return np.dot(x, y)/(np.sqrt(np.dot(x, x))*np.sqrt(np.dot(y, y)))

def cosine_similarity_ohlc(obj, df_rel):
    close = np.where(df_rel.columns == 'close')[0][0]
    l = len(obj)
    count = len(df_rel)
    df_rel_copy = df_rel.copy()
    df_rel_copy['similarity'] = np.nan
    similarity = np.where(df_rel_copy.columns == 'similarity')[0][0]

    for i in range(1,count-l):
        if obj.index[0] == df_rel.index[i]:
            continue
            
        sim = cosine_similarity(obj,df_rel.iloc[i:i+l,close]) 
        df_rel_copy.iloc[i,similarity] = sim

    return df_rel_copy.copy()

cosine_similarity는 코사인 유사도를 구해주는 함수입니다.

cosine_similarity_ohlc는 비교대상이 되는 차트에 대해서 모든 구간에 대해 코사인 유사도를 구해주는 함수입니다.

예를 들어 obj가 50개 봉 데이터 이고, df_rel이 2000개 봉 데이터 이면 1950번의 반복문을 돌면서 코사인 유사도를 계산해줍니다.

 

그런데 obj와 비슷한 시간은 비슷한 모양으로 계산될 여지가 있습니다. 그리고 비슷한 시간대에는 비슷한 차트 유사도를 보일 수 있으므로 그것을 걸러주는 함수가 필요합니다. 

이것을 filtering함수로 구현했습니다.

# filtering 함수, 기준 근처 봉들은 삭제, 가장 위에 있는 것들 우선으로 남기기
def filtering(rt, obj ,interval):
    info = client.get_historical_klines("BTCUSDT",interval,limit=2)
    dif = info[1][0]-info[0][0]
    unit = dif/1000/60

    stay_lst = []
    getout = 0
    for i in rt.index:
        if abs(obj.index[0] - i) <= datetime.timedelta(minutes= 15*unit):
            continue

        elif len(stay_lst) == 0:
            stay_lst.append(i)
   
        elif len(stay_lst) == 8:
            break

        else:
            for j in stay_lst:
                if abs(i-j) <= datetime.timedelta(minutes= 15*unit):
                    getout = 1
            
            if getout == 0:
                stay_lst.append(i)
            
        getout = 0


    rt = rt.loc[stay_lst]
    return rt.copy()

 

 

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

ticker = "BTCUSDT"
count = 30000
interval = '5m'
df = get_ohlcv(ticker, interval=interval,count = count)

데이터를 다운받아줍니다.

 

## 여기서 시작
num = 50



obj = get_ohlcv(ticker,interval,count = num) 
df_rel_sim = cosine_similarity_ohlc(obj['close'], df)
rt = df_rel_sim.sort_values(by=['similarity'], ascending=False)
rt = filtering(rt,obj,interval)
rt

30000개의 봉 데이터에서 50개의 봉 데이터인 obj에 대해 비슷한 모양의 구간을 찾아줍니다.

10개까지 띄워주게 설정해놨습니다.

 

 

# set_num: 0 에 가까울 수록 더 유사함
# aft: 예측 봉의 수
set_num = 0
alpha = 50

# 대상과 가장 비슷하다고 꼽는 구간 + alpha
import mpl_finance
from matplotlib import gridspec


df_obj = df.loc[df.index >= rt.index[set_num]]
df_obj_plot = df_obj.iloc[:num+alpha,:]
df_obj = df_obj.iloc[:num,:]

fig = plt.subplots(figsize=(15,12), sharex = True)#sharex를 통해 두그림의 x축을 공유함
gs = gridspec.GridSpec(nrows=2, ncols=1, height_ratios=[1,1])#gridspec을 통해 여러 축들을 원하는 대로 배치 가능

ax1 = plt.subplot(gs[0])

mpl_finance.candlestick2_ohlc(ax1, df_obj_plot['open'], df_obj_plot['high'], df_obj_plot['low'], df_obj_plot['close'], width=0.5, colorup='r', colordown='b')
ax1.vlines(num+0.5,min(df_obj_plot['low']),max(df_obj_plot['high']))
ax1.hlines(df_obj.iloc[-1,3]*(99.6)*(0.01), num, num+alpha)
ax1.hlines(df_obj.iloc[-1,3]*(100.4)*(0.01), num, num+alpha)
title_font = {
    'fontsize': 20,
    'fontweight': 'bold'
}
plt.title("predict",fontdict=title_font, loc='left')
ax1.set_xlim(0,num+alpha)
plt.gca().axes.xaxis.set_visible(False)
plt.grid()

ax2 = plt.subplot(gs[1])
# 실제 대상
df_real = obj

mpl_finance.candlestick2_ohlc(ax2, df_real['open'], df_real['high'], df_real['low'], df_real['close'], width=0.5, colorup='r', colordown='b')
title_font = {
    'fontsize': 20,
    'fontweight': 'bold'
}
plt.title("real",fontdict=title_font,loc='left')
ax2.set_xlim(0,num+alpha)
ax2.vlines(num+0.5,min(df_real['low']),max(df_real['high']))
plt.grid()
plt.subplots_adjust(wspace=0, hspace=0.07)

set_num은 선정한 10개의 구간 중 어떤 구간을 볼 지를 선택하는 것입니다. 0~9까지 선택할 수 있고, 숫자가 작을 수록 코사인 유사도가 높습니다.

alpha는 선택한 구간에서 몇개의 봉을 더 볼 지를 설정하는 것입니다.

그리고 ±4퍼센트 구간에 수평선을 그었습니다. 이 선은 차트가 앞으로 횡보하는지 아니면 상승 혹은 하락하는 지 판단하는 기준이 될 수 있습니다. 

 

 

실제 차트 구간과 코사인 유사도를 통해 선정한 차트 구간이 꽤나 유사해보입니다.

predict 차트에서 검은색 수직선 이후로 차트가 횡보하는 것으로 보아 섣불리 상승에 배팅하는 것은 위험해 보입니다.

 

 

#느낀점

생각 보다 코사인 유사도가 유용해 보입니다. 이것을 더욱 발전시켜서 매매 알고리즘에 넣을 수 있을듯합니다.

그리하여 백테스팅도 해 볼 수 있을것같습니다.

하지만 단순히 종가를 통해 코사인 유사도를 계산한 것이여서, 사용되는 정보가 손실된다는 점이 아쉽습니다.

ohlcv에 대해 전부 코사인 유사도를 비교해서 평균적으로 가장 유사도가 높은 구간을 선택하는 것으로 발전시킬 수 있을것 같습니다.