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で答えます.
nを選択した場合は,日付, 通貨のシンボル, バー1本分のしきい値をスペース区切りで入力すると,自動でbybitの約定履歴をダウンロードしドルバーを作成します.
yを選択した場合は,始まりの日付, 終わりの日付, 通貨のシンボル, バー1本分のしきい値をスペース区切りで入力すると,同様にドルバーを作成します.
さいごに
前回の記事でも書いたように,時間バーをドルバーにすることで統計的性質が改善することが知られています.