バックテストに必要なBitflyerの過去のローソク足の価格データを集めよう!

Bitflyerで使う自動売買BOTの勝率を検証するために、まずは十分な量の過去の価格データを入手する必要があります。取得元はCryptowatchを使います。

CryptowatchのAPIを改めて理解する

まず最初にもう1度、CryptowatchのAPIの特徴を確認しておきましょう。CryptowatchからOHLC価格(ローソク足の始値・高値・安値・終値)を取得するAPIには、以下の3つのパラメーターが設定できます。

periods ・・・ 時間軸を秒で指定
after ・・・ 〇〇(UNIX日時)以降のデータを取得
before ・・・〇〇(UNIX日時)より前のデータを取得

・CryptowatchのAPI仕様書
・UNIX時間変換ツール

今までは1分足の価格データを取得するために「periods」のパラメーターだけを 60秒(1分)に指定していました。このように時間軸だけを指定した場合、デフォルトではCryptowatchは直近500件のデータを返します。

ではパラメーターを指定するとどのようなデータが返ってくるのでしょうか? テストするために、以下のような関数を作ってみましょう。

パラメーターをテストする関数

今回は様々なパラメーターでテストができるように、新しい関数を作ります。今までとは違う新しいpythonファイルを作って以下のコードを記載してください。


import requests
from datetime import datetime
import time


def get_price(min, before=0, after=0):
	price = []
	params = {"periods" : min }
	if before != 0:
		params["before"] = before
	if after != 0:
		params["after"] = after

	response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc",params)
	data = response.json()
	
	if data["result"][str(min)] is not None:
		for i in data["result"][str(min)]:
			price.append({ "close_time" : i[0],
				"close_time_dt" : datetime.fromtimestamp(i[0]).strftime('%Y/%m/%d %H:%M'),
				"open_price" : i[1],
				"high_price" : i[2],
				"low_price" : i[3],
				"close_price": i[4] })
		return price
		
	else:
		print("データが存在しません")
		return None

# ここからメイン
price = get_price(60)

if price is not None:
	print("先頭データ : " + price[0]["close_time_dt"] + "  UNIX時間 : " + str(price[0]["close_time"]))
	print("末尾データ : " + price[-1]["close_time_dt"] + "  UNIX時間 : " + str(price[-1]["close_time"]))
	print("合計 : " + str(len(price)) + "件のローソク足データを取得")
	print("--------------------------")
	print("--------------------------")

今回作った関数では、get_price()の中で、before/afterのパラメーターも設定できるように改良しました。これを get_price.py などの名前で保存しておきます。

以下のように書けば、パラメーター無しの5分足(300秒)のデータが取得できます。

get_price(300)

以下のように書けば、UNIX時間で1521849600(2018/3/24 09:00:00)より前の5分足の価格データを取得できます。

get_price(300,before=1521849600)

以下のように書けば、UNIX時間で1521849600(2018/3/24 09:00:00)以降の1時間足の価格データを取得できます。

get_price(3600,after=1521849600)

返ってくるデータ件数と期間

まずは何もパラメーターを設定しない場合をテストしてみましょう。
以下の3つをそれぞれ実行してみます。

get_price(60)
get_price(300)
get_price(3600)

1分足の場合

先頭データ : 2018/03/29 12:56
末尾データ : 2018/03/29 21:15
500件のローソク足データを取得

およそ8時間分のデータが返ってきました。

5分足の場合

先頭データ : 2018/03/28 03:35
末尾データ : 2018/03/29 21:20
500件のローソク足データを取得

およそ1~2日分のデータが返ってきました。

1時間足の場合

先頭データ : 2018/03/09 03:00
末尾データ : 2018/03/29 22:00
500件のローソク足データを取得

およそ20日分のデータが返ってきました。

結果画面

やはりどの時間軸でも、基本的にはデフォルトで500件のデータが返ってきています。

Beforeを指定してみると…?

それでは、この先頭の日付を《before》パラメータに設定することで、ここからさらに遡って500件を取得できるのでしょうか? 試しに以下を実行してみましょう。

get_price(60,before=1522295760)

先ほど返ってきた1分足のデータの先頭の日時(2018/03/28 03:35 = UNIX時間で1522295760)を基準に、その前のデータを要求してみます。すると、以下のように空データが返ってきてしまいました。

時間を少しズラシて色々と試してみても、《before》パラメータでは、直近の500件以上のデータは取得できないようです。

Afterを指定してみると…?

それでは、afterで指定してみてはどうでしょうか? 試しにダメ元で2018年1月1日午前9時(UNIX時間で1514764800)以降の1分足データをすべて要求してみます。

get_price(60,after=1514764800)

すると、以下のように6000件のデータが返ってきます。先頭日時は3月25日です。期間にすると、直近4日分くらいの1分足データですね。

先頭データ : 2018/03/25 16:45
末尾データ : 2018/03/29 21:28
6000件のローソク足データを取得

いろいろな日時にパラメーターを変更して試してみても、《after》でパラメーターを指定する方法だと、取得できるデータ件数の上限は直近6000件までのようです。他の時間軸で試しても、以下のような結果になります。

5分足の場合

先頭データ : 2018/03/08 23:45
末尾データ : 2018/03/29 21:35
合計 : 6000件のローソク足データを取得

5分足だと6000件で約2週間分以上のデータが得られることになります。

1時間足の場合

先頭データ : 2017/07/22 15:00
末尾データ : 2018/03/29 22:00
合計 : 6000件のローソク足データを取得

1時間足だと6000件で約7~8カ月分程度のデータが得られることになります。

どの時間軸でバックテストをするにしても、大体、ローソク足6000本くらいのデータで検証すれば、とりあえずテストとしては十分だと思います。もちろんデータは多ければ多いほどいいのですが、無いものは仕方ありません。

自分でデータを集める方法

もっと広範にテストをしたい人は、自前で1分足の価格データを定期的に保存しておくといいでしょう。バックテストの検証に限らず、あらゆる統計的なテストで最も難しいのは「十分な量のデータを集めること」です。自前で十分な量の価格データを持っていれば、それだけ精度の高い検証が可能です。

取得したデータをjsonファイルに保存する

ひとまずjson形式のファイルで保存するだけなら、print()の後に以下のコードを書き足せばOKです。

import json #これだけ先頭に追加で記述

#ファイルに書き込む
file = open("./{0}-{1}-price.json".format(price[0]["close_time"],price[-1]["close_time"]),"w",encoding="utf-8")
json.dump(price,file,indent=4)

これを実行すると、以下のような形式でファイルが自動的に作成され、その中に価格データが保存されます。

・先頭ローソク足の日時-末尾ローソク足の日時-price.json
※ 日時はUNIXタイムスタンプ

中身を開くと以下のようなJSON形式で、価格データが保存されているのがわかります。

保存したjsonファイルを読み込む

逆に読み込むときは、以下のような関数を作っておけばOKです。

import json #これだけ先頭に追加で記述

#jsonファイルを読み込む関数
def get_price_from_file(path):
	file = open(path,"r",encoding="utf-8")
	price = json.load(file)
	return price

# ここからメイン
price = get_price_from_file("./1521978660-1522341240-price.json")

これで上のコードを実行すると、APIを叩いたときと全く同じ結果が得られます。つまり、WEBで取得したデータと同じように扱うことができます。

▽ 上がCryptowatchのAPIで価格データを取得しjsonで保存した場面
  下がそのjsonファイルを読み込んで価格データを取得した場面

なお、もし自分でデータを保存するのであれば、1分足のデータだけあれば十分です。1分足のデータさえあれば、そこから加工して5分足、15分足、1時間足のデータを作るのは簡単(最高値と最安値以外を切り捨てるだけ)です。

また今回の章ではいったん6000件のデータがあれば問題ありません。自前で価格データを収集して蓄積することに興味がある方は、以下の記事を参考にしてください。

・Cryptowatchで過去6000件以上のローソク足を保存して自前で価格データを蓄積する

まとめ

最後に今回使ったpythonのコードをまとめておきます。


import requests
from datetime import datetime
import time
import json

# パラーメータを指定してCryptowatchのAPIを使用する関数
def get_price(min, before=0, after=0):
	price = []
	params = {"periods" : min }
	if before != 0:
		params["before"] = before
	if after != 0:
		params["after"] = after

	response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc",params)
	data = response.json()
	
	if data["result"][str(min)] is not None:
		for i in data["result"][str(min)]:
			price.append({ "close_time" : i[0],
				"close_time_dt" : datetime.fromtimestamp(i[0]).strftime('%Y/%m/%d %H:%M'),
				"open_price" : i[1],
				"high_price" : i[2],
				"low_price" : i[3],
				"close_price": i[4] })
		return price
		
	else:
		print("データが存在しません")
		return None

# json形式のファイルから価格データを読み込む関数
def get_price_from_file(path):
	file = open(path,"r",encoding="utf-8")
	price = json.load(file)
	return price


# ここからメイン
price = get_price(60,after=1514764800)

#ファイルから読みこむ場合
#price = get_price_from_file("./ファイル名.json")


if price is not None:
	print("先頭データ : " + price[0]["close_time_dt"] + "  UNIX時間 : " + str(price[0]["close_time"]))
	print("末尾データ : " + price[-1]["close_time_dt"] + "  UNIX時間 : " + str(price[-1]["close_time"]))
	print("合計 : " + str(len(price)) + "件のローソク足データを取得")
	print("--------------------------")
	print("--------------------------")
	
	#ファイルに書き込む場合
	#file = open("./{0}-{1}-price.json".format(price[0]["close_time"],price[-1]["close_time"]),"w",encoding="utf-8")
	#json.dump(price,file,indent=4)

「#」が先頭に付いているコードは、コメントとして扱われるため実行されません。上記のファイル書き込み、読み込みを使う場合は先頭の「#」を外してください。

「バックテストに必要なBitflyerの過去のローソク足の価格データを集めよう!」への7件のフィードバック

  1. どうもこんにちは。
    第一章からコードを試しつつここまで読ませてもらいました。
    以前、自分でもBOTを作った事があったのですが、こちらの記事は機能的にも構造的にもそれよりはるかに素晴らしい内容なので、大変勉強になっています。ありがとうございます。
    ところで今回のコードに関して、ウチの環境だと「ファイルの読み込み」の方が上手くいかず、色々調べてみたら解決したっぽいのでそのご報告です。
    [環境]
    Windows7:Anaconda→condaでpython3.5の仮想環境上で実行
    [エラー内容]
    JSONDecodeError: Expecting property name enclosed in double quotes

    とりあえず、エラー内容の指示に従ってコード上の「’」を一通り「”」にしてみる。
    →読み込み出来る様に。

    以上です。これからの記事やテクニカル的な内容大変楽しみにしています。

    1. コメントありがとうございます!
      こちらこそ1章から読み進めていただいて嬉しいです。
      また報告とても助かります!

      私の環境ではこちら再現できなくて、
      恥ずかしながらよく理由がわかっていないのですが、
      もし同じ方がいたらこのコメントを参考にして欲しいですm(__)m

      今後もぜひ来てください^^

      1. 返信どもです!

        すみません。改めてやり直してみると、普通にサンプルコードコピペ実行で大丈夫でした…..何回も試したんだけどなんでなんだろう?

        後、1分足でデータを書き出すと、時間帯等によってはすごく中途半端なデータ(直近の最新のデータはopen_price,close_timeしかない等)になる場合があって、その場合はjsonファイルをその部分を削ったりして整形しないと、読み込めないみたいですね。ウチの場合はですが….。

        1. 何度も試していただいて恐縮です…!
          ダブルクオートにしておくに超したことはないのかもですね(修正しました)。読み込みのときに失敗したデータを削らないといけないのも考えてなかったです。

          ちょうど書きたかったので、取得した価格データを差分だけJSONファイルに追加保存する解説記事を作りました。こちらは、tobinabe さんの報告を参考にして、取得したデータは変形せずにそのまま保存するかたちにしました。ありがとうございます!^^

          参考:差分だけ追加保存する記事

          1. いえいえ、一方的に学ばせて頂いてるので、こちらの方が逆に恐縮です….。

            まさか記事にしていただけるとは、ありがとうございます!
            内容もさる事ながら更新頻度もすごいですね。早速拝見させて頂きます。

  2. 素晴らしい記事の数々、非常にためになります。
    もしお時間あるようでしたら、
    ta-lib と ccxt
    の組み合わせ例題記事を投稿して欲しいのですが
    記事リクエストは可能でしょうか?

    1. お褒めいただきありがとうございます!
      記事のリクエスト自体は全然大丈夫です^^
      ただ記事にするかどうかは、書くのが面白そうかどうかで決めてます(笑)
      TALibは面白そうなので、時間があれば記事にしたいです!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です