クリプトHFTとか競プロとか

競技プログラミングや仮想通貨に関することを中心にブログを書いていきます.

Bybitのデータでもドルバーを作りたい【複数日対応】

前回の記事 では,BitMEXの約定履歴からドルバーを作るプログラムを作りましたが,今回の記事ではbybitのデータから複数日のドルバーを作るプログラムを作ります.

前回の記事の問題点

そもそも,前回の記事で作ったプログラムは,BitMEX用でしたが,もう日本からBitMEXを使う方法はありません.

また,mlfinlabを使える環境を用意するにはAnacondaが必要で,環境構築が面倒くさくなるという問題点があります.

そして,一番重大な問題として,BitMEXやBybitではBTC/USDの価格で,Bitcoinの価格を表しますが約定履歴でのsizeはUSD建てでの額となります.

つまり,BitMEXの約定履歴からmlfinlabなどのライブラリを使ってドルバーを作ると,実際に出来上がるのはドル二乗バーとなってしまいます.

この問題はmlfinlabを使ってボリュームバーを作ることによって解決することもできますが, 自前で実装することによって,特徴量の追加なども柔軟に行えるため今回は自前で実装することにしました.

しかし,それによる問題点として,mlfinlabを使った場合に比べて遅いというものがありますが,機械学習の学習に費やす時間と比べたらごく短時間のため今回は無視することにします

実装

import gzip
import os 
import pandas as pd
from datetime import datetime, timedelta
from time import sleep
from urllib import request

#参考: https://qiita.com/yuukiclass/items/88e9ac6c5a3b5ab56cc4


def main():
    baseurl = 'https://public.bybit.com/trading/'
    #https://public.bybit.com/trading/BTCUSD/BTCUSD2020-05-25.csv.gz
    is_concat = input("Do you want to create data for multiple days? (y/n): ")
    
    while not (is_concat == "y" or is_concat == "n"):
        is_concat = input("Do you want to create data for multiple days? (y/n): ")

    if is_concat == "y":
        start_date, end_date, symbol, threshold = input("yyyy-mm-dd(start) yyyy-mm-dd(end) symbol threshold\n").split()
        threshold = int(threshold)
        file_title = f"dollar_bybit-{start_date}-{end_date}.csv"
        df = pd.DataFrame()
        start = datetime.strptime(start_date, "%Y-%m-%d")
        end = datetime.strptime(end_date, "%Y-%m-%d")
        
        # https://thr3a.hatenablog.com/entry/20180813/1534124783
        for i in range((end - start).days + 1):
            date_str = start + timedelta(i)
            date_str = date_str.strftime("%Y-%m-%d")
            filepath = f"{date_str}.csv"
            dlurl =  baseurl + f"{symbol}/{symbol}{date_str}.csv.gz"
            df2 = download(dlurl, filepath)
            df = pd.concat([df,df2])
            print(f"donwloaded {date_str}")
            sleep(0.5)
        
        df_ohlcv = makeCandles(df, symbol)
        dollar = make_volume_bar(df_ohlcv, threshold=threshold)
    else: 
        date, symbol, threshold= input('yyyy-mm-dd symbol threshold\n').split()    
        threshold = int(threshold)
        print('Downloading... ' + baseurl + f"{symbol}/{symbol}{date}.csv.gz")
        filepath = f"{date}.csv.gz"
        df = download(baseurl + f"{symbol}/{symbol}{date}.csv.gz", filepath)
        print('Downloaded execution data.')
        file_title = f"dollar_bybit-{date}.csv"
        df_ohlcv = makeCandles(df, symbol)
        df_ohlcv.to_csv(file_title)
        print()
        dollar = make_volume_bar(df_ohlcv, threshold=threshold)
    
    dollar.to_csv(file_title)
    print("Done!")
    return

    date, symbol, sec= input('yyyy-mm-dd symbol sec\n').split()    
    sec = int(sec)
    baseurl = 'https://public.bybit.com/trading/'
    #https://public.bybit.com/trading/BTCUSD/BTCUSD2020-05-25.csv.gz
    print('Downloading... ' + baseurl + f"{symbol}/{symbol}{date}.csv.gz")
    filepath = "'{}.csv.gz'.format(date)"
    
    request.urlretrieve(baseurl + f"{symbol}/{symbol}{date}.csv.gz", filepath)
    print('Making candles...')
    df = unzip(filepath)
    df_ohlcv = makeCandles(df, symbol, sec, date)
    print('Done!')
    file_title = f"ohlc_bybit-{date}.csv"
    df_ohlcv.to_csv(file_title)    
    
    return

def makeCandles(df, symbol):
    # 参考: https://note.com/nagi7692/n/ne674d117d1b6?magazine_key=m0b2a506bf904
    df = df.query('symbol == "{}"'.format(symbol))

    df.drop(["symbol", 'tickDirection', 'trdMatchID', 'grossValue', 'homeNotional', 'foreignNotional'], axis=1, inplace=True)
    df = df.sort_values("timestamp")
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit="s")
    df = df.rename(columns={'timestamp': 'date_time', "size": "volume"})
    df = df.reset_index(drop=True)
    print(df)
    return df

def download(url, filepath):
    request.urlretrieve(url, filepath)
    df = unzip(filepath)
    os.remove(filepath)
    
    return df

def unzip(filepath):
    with gzip.open(filepath, 'rt') as f:
        df = pd.read_csv(f)
    return df

def make_volume_bar(df, threshold):
    N = len(df)

    INF = 1e9
    high = -INF
    low = INF
    ret_df = pd.DataFrame(columns=["date_time", "open", "high", "low", "close", "tick_num", "buyTick_num", "sellTick_num", "volume" ,"buy_vol", "sell_vol"])
    get_default_row = lambda : pd.Series([None for i in range(len(ret_df.columns))], index=ret_df.columns)
    print(get_default_row())
    current_row = get_default_row()
    dfs = list()
    
    date_times = df["date_time"]
    sides = df["side"].values
    prices = df["price"].values
    volumes = df["volume"].values

    for i in range(N):
        # 初期化
        if i%1000 == 0:
            print(f"{i}... {i/N * 100}%...")

        if current_row["date_time"] is None:
            current_row["date_time"] = date_times.values[i]
            current_row["open"] = prices[i]
            current_row["high"] = prices[i]
            current_row["low"] = prices[i]

            current_row["tick_num"], current_row["buyTick_num"], current_row["sellTick_num"], current_row["volume"], current_row["buy_vol"], current_row["sell_vol"] = 0,0,0,0,0,0
 
        if df["side"][i] == "Buy":
            current_row["buy_vol"] += volumes[i]
            current_row["buyTick_num"] += 1

        if df["side"][i] == "Sell":
            current_row["sell_vol"] += volumes[i]
            current_row["sellTick_num"] += 1

        # 高値, 安値の更新
        current_row["high"] = max(current_row["high"], prices[i])
        current_row["low"] = min(current_row["low"], prices[i])

        current_row["volume"] += volumes[i]
        current_row["tick_num"] += 1    

        # リセット処理
        if current_row["volume"] >= threshold:     
            current_row["close"] = prices[i]

            current_row = get_default_row()
            dfs.append(current_row)
            #ret_df = ret_df.append(current_row, ignore_index=True)
        
    ret_df = pd.DataFrame(dfs, columns=["date_time", "open", "high", "low", "close", "tick_num", "buyTick_num", "sellTick_num", "volume" ,"buy_vol", "sell_vol"])
    print(ret_df)
    return ret_df

if __name__ == "__main__":
    main()

使い方

プログラムを起動すると,Do you want to create data for multiple days?(複数日にわたってデータを作るか?)と訊かれるのでyもしくはnで答えます. f:id:KabukiMining:20200802153952p:plain

nを選択した場合は,日付, 通貨のシンボル, バー1本分のしきい値をスペース区切りで入力すると,自動でbybitの約定履歴をダウンロードしドルバーを作成します.

f:id:KabukiMining:20200802154426p:plain

yを選択した場合は,始まりの日付, 終わりの日付, 通貨のシンボル, バー1本分のしきい値をスペース区切りで入力すると,同様にドルバーを作成します.

さいごに

前回の記事でも書いたように,時間バーをドルバーにすることで統計的性質が改善することが知られています.

このスクリプトを少しでも機械学習に役立ててくれれば幸いです