#○分足データで分析して意味あるの?
ファイナンス機械学習(Advanced in Financial Machine Learning)の第2章では、機械学習アルゴリズムの利用に適した金融データの変換方法について書いてありました。
よく仮想通貨に限らず金融データの分析を行う人が扱うデータは大抵、○分足のデータを使って分析していますが、この本の2章では、
そのデータで分析するの意味あるの?
ということが述べられています。
このような一定の時間間隔でOHLC(始値、高値、安値、終値)を集計しているデータをタイムバーと呼びます。
タイムバーがダメな2つの理由
ではなぜタイムバーを使ってはいけないのでしょうか。それは以下の2つの理由が挙げられます。
- 時間によってサンプル数が異なる
- 一定時間ごとにサンプリングされた系列は統計的性質が好ましくない
1.について
例えば、ある日の1分足タイムバーデータから2つ任意に取り出すとします。そのときタイムバーAの取引数は5、タイムバーBの取引数100だとしましょう。この場合2つの情報量は同じでしょうか?当然違いますよね。
bitcoinの場合、株式市場のように開いてる時間が決まっておらず世界中誰でも取引できるので、サンプル数の偏りは少ないかと思いますが、株式市場の場合は寄り付きの時などは取引が殺到するため閑散時間との乖離はより大きいものになるでしょう。
2.について
これも1.が起因している部分もあるかと思いますが、当然データごとの情報量が異なれば系列相関、分散不均一性、非正規分布リターンなどが生まれ分析するのが困難になります。そのため、できるだけ統計的性質を持つようにしたいわけです。
じゃあ、どんなバーを作ればええねん
そこで、この情報量の問題を解消するために名乗りを挙げたバーたちが本書では載っており、
- 「ティックバー」・・・取引数ごとに区切ってOHLCを集計する
- 「ボリュームバー」・・・出来高(株数など)ごとに区切ってOHLCを集計する
- 「ドルバー」・・・売買金額ごとに区切ってOHLCを集計する
があります。
(より発展的なバーの紹介もされていますが、ここでは割愛します^^)
これらのバーでは、統計的性質を持つことが示されており、特にボリュームバーはティックバーよりも独立同分布(iid)の正規分布に近くなるという優れた性質を持ちます。(Clark[1973])
一方、ドルバーは大きな価格変動がある場合の分析では、ティックやボリュームよりも売買代金でサンプリングしたバーの方が良いらしいです。
ここで、ドルバーの名前の「ドル」というのはたまたまドルが使われているだけで、必ずしもドルである必要はありません。日本円に換算するなら「円バー」でもいいかと。
これらの詳細な解説はまた別記事にしたいと思います。
#ここから本題
前置きが長くなりました。
トレーダーのほとんどにタイムバー以外で見るという風習がないので、ティックバーのデータを取得できるようなサイトはほとんどありません。
(自分調べた中ではありませんでしたが、もしティック、ボリューム、ドルバーのデータを取得できるAPIがあれば教えてください……!)
ということで、
「上記3つのバーを作るために、世界中の全取引データを集めてきてください!!!」
えっ!????????
普通に考えてきついですよね。
そうなんです。厳密に上記3つのバーを作るためには全取引のデータが必要で、それを区切る必要があるのです。
私たち一般ピーポーが簡単に得られるデータは、タイムバーのデータです。
なので、このデータを使って近似的にボリューム、ドルバーを作っていきましょう。
使用データ、環境
今回はbitcoinの1分足データを使って変換していきたいと思います。
データは、Kaggleの
Bitcoin Historical Data
から簡単にとってくることができるので、ダウンロードしてみてください。
環境は、Google Colaboratoryを使用します。
#データの前処理
import numpy as np
import pandas as pd
from datetime import datetime
import seaborn as sns
data = pd.read_csv('bitstampUSD_1-min_data_2012-01-01_to_2020-12-31.csv')
data['datetime'] = pd.to_datetime(data.Timestamp, unit='s')
df2 = data.set_index('datetime').drop('Timestamp',axis=1)
df3 = df2.dropna(how='any')['2017':].drop('Weighted_Price',axis=1)
実行すると、df3は以下のような結果になります。
元のデータは2012年からありますが、便宜的に2017年からのみを使っています。
#変換する関数
#Volume_barを作成する関数
#入力dfの列は必ず以下の通りの順番にする
#['Open', 'High', 'Low', 'Close', 'Volume_(BTC)', 'Volume_(Currency)']
import time
def create_bar(df,threshold,bar):
start = time.time()
Cum_currency = 0
Cum_BTC = 0
Current_num = 0
Volume_df = pd.DataFrame(columns=df.columns)
progress_i = 0
notification = 100000
print('{}行ごとに表示します'.format(notification))
for index,row in df3.iterrows():
#処理時間を記述
if progress_i % 100000 == 0:
running_time = time.time() - start
print('処理している行: {}, ここまでの経過時間: {}'.format(index,running_time))
#Volumeがリセットされた次の行での処理
if (Cum_currency == 0) | (Cum_BTC == 0) :
open_time = index
open_price = row[0]
high_price = row[1]
low_price = row[2]
#前の行までの累積の値
prev_Cum_currency = Cum_currency
prev_Cum_BTC = Cum_BTC
#今回の行を足し合わせる
Cum_currency += row[5] #Volume_(Currency)
Cum_BTC += row[4] #Volume_(BTC)
# 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
#足し合わせた結果閾値から遠のいてしまった場合
#前の行までで区切りをつけて、今処理している行を新しい行とする
if (abs(threshold['Currency'] - prev_Cum_currency) < abs(threshold['Currency'] - Cum_currency)) & (bar == 'Currency'):
#区切りをつける
Volume_df.loc[open_time] = [open_price, high_price, low_price, prev_close, prev_Cum_BTC, prev_Cum_currency]
#今回の行を新しい行として処理
Cum_currency = row[5]
Cum_BTC = row[4]
open_time = index
open_price = row[0]
high_price = row[1]
low_price = row[2]
#次の行にとってのprevだからprev_close
prev_close = row[3]
progress_i += 1
continue
if (abs(threshold['BTC'] - prev_Cum_BTC) < abs(threshold['BTC'] - Cum_BTC)) & (bar == 'BTC'):
#区切りをつける
Volume_df.loc[open_time] = [open_price, high_price, low_price, prev_close, prev_Cum_BTC, prev_Cum_currency]
#今回の行を新しい行として処理
Cum_currency = row[5]
Cum_BTC = row[4]
open_time = index
open_price = row[0]
high_price = row[1]
low_price = row[2]
#次の行にとってのprevだからprev_close
prev_close = row[3]
progress_i += 1
continue
# 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
if row[1] > high_price:
high_price = row[1]
if row[2] < low_price:
low_price = row[2]
#次のループで使うかもしれないから終値だけは毎回保存しておく
#次の行にとってのprevだからprev_close
prev_close = row[3]
#Volume_barの時
if (Cum_BTC > threshold['BTC']) & (bar == 'BTC'):
close_price = row[3] #close
Volume_df.loc[open_time] = [open_price, high_price, low_price, close_price, Cum_BTC, Cum_currency]
Cum_currency = 0
Cum_BTC = 0
#ドルバーの時
if (Cum_currency > threshold['Currency']) & (bar == 'Currency'):
close_price = row[3] #close
Volume_df.loc[open_time] = [open_price, high_price, low_price, close_price, Cum_BTC, Cum_currency]
Cum_currency = 0
Cum_BTC = 0
progress_i += 1
return Volume_df
実行は以下のコードで行う。
今回は閾値を200BTCとしボリュームバーを作成します。
BAR = 'BTC'
THRESHOLD = {'Currency':8000000, 'BTC':200}
Volume_df = create_bar(df3,THRESHOLD,BAR)
Volume_df
主な処理の流れ
create_bar
関数の主な処理の流れをボリュームバー(出来高の閾値を200)を例として説明します
- 入力引数として以下の受け取る
-
df
・・・1分足データ -
threshold
・・・出来高と売買金額の閾値 -
BAR
・・・ボリュームバー、ドルバーどちらを作るかを指定する
-
- 閾値に達するまで、1分ごとの出来高を
Cum_BTC
に累積させ、閾値を超えたらそこまでのデータでOHLCと出来高を集計する - 例外処理として、もし閾値を超える前までの
Cum_BTC
の方が、超えた後のCum_BTC
よりも閾値に近いのであれば、そのデータはそのボリュームバーに含めず、次のボリュームバーのデータとして扱う。 - データが尽きるまで2.と3.を繰り返す
出来高に当たる列はVolume_(BTC)
になります。出来高を10BTCとした時、2番目の行まで足し合わせると15.69BTCと10BTCを超えるため、通常この2つのデータからOHLCを集計します。しかし、2行目を足し合わせる前は7.61BTCであり、こちらの方が2行目を足し合わせた後よりも10BTCに近いです。
なので、この場合1行目だけを一つのボリュームバーとして集計します。
#処理結果
今回は閾値を200BTCとしボリュームバーを作成していました。
処理の結果以下のようになります。
確かに、どのデータの出来高も200BTCに近くなっていることがわかります。
では、実際のデータの分布をみてみましょう。
sns.distplot(Volume_df['Volume_(BTC)'])
ほぼ200BTC付近に分布していることがわかりますね。ですが、一部データはかなり右に裾が広くなっていることもわかります。
続いて主な統計量です。
maxが1616BTCととんでもない外れ値があることがわかります。このデータは2017年のBTC暴騰の際のデータで以下に取引されていたかがみて取れますね。
本来ならほぼ200BTCピッタリで組めるものなので、この結果がいいのかどうかは正直わからないのが現状です。
#まとめ
今回は1分足タイムバーをボリュームバー、ドルバーに近似的に変換してみました。
この方法はティックデータを取得できない人間が極力簡単に実装するために編み出した方法であるので、統計的性質が保たれているかはわからないので、今後要検証かと思います。
本来ならタイムバー以外のバーのデータもAPIで取得できれば嬉しいんですけどね!笑
少しでもお役に立てたなら嬉しいです!^^
よろしければ、いいねのほうもお願いいたします!
#参考文献
ファイナンス機械学習-amazon