今日も窓辺でプログラム

外資系企業勤めのエンジニアが勉強した内容をまとめておくブログ

ビットコインの約定履歴を取得してローソク足チャートを描いてみる

はじめに

ここ最近、ビットコインを筆頭とした仮想通貨が世間を騒がせているようです。私は完全に乗り遅れてしまいました。仮想通貨の取引所はAPI等も多く公開しているようなので、遅れながらも何もやらないよりはと思い、公開APIを読んでビットコイン取引のローソク足チャートを作成してみました。

将来的には何らかのアルゴリズムで取引を自動で行うプログラムを書きたいですが、まずは簡単なところから行ってみます。

用意した環境

数ある取引所の中で、以前から口座だけは持っていたのと、APIの使用感が他に比べると良いという評判なので、bitFlyerのAPIを使用することにしました。
bitFlyer ビットコインを始めるなら安心・安全な取引所で

今回の記事で扱うAPIはPublic APIという扱いのもので、口座を持っていなくても使用できます。

使用した主なライブラリ等のバージョンは次の通りです

  • Python 3.5
  • pandas 0.21.1
  • matplotlib 2.1.1

約定履歴を取得する

約定情報のJSON形式

bitFlyerではgetexecutionsという約定履歴を取得するためのAPIが無料で公開されているので、これを使用して全約定履歴を取得します。

約定情報はJSONオブジェクトのリストの形で取得できます。まずはこの約定情報を扱うためのクラスを用意しておきます。

# 約定情報
class Execution(object):
    def __init__(self, jsonobj=None):
        if jsonobj:
            self.id = jsonobj['id']
            self.side = jsonobj['side']
            self.price = jsonobj['price']
            self.size = jsonobj['size']
            self.timestamp = jsonobj['exec_date']

    def to_csv_line(self):
        line = '{},{},{},{},{}\n'
        line = line.format(self.id, self.side, self.price,
                           self.size, self.timestamp)
        return line

約定情報には取引の順番に沿ってIDが一意に割り当てられています。他にも、約定した価格、通貨の量、時刻、およびこの注文は売り手買い手のどちらが先に出したものかの情報を取り出すことにしました。
あとでファイルに書き出すことも見据え、カンマ区切りで文字列にするto_csv_lineメソッドも用意しました。

実際にエンドポイントを呼んでみる

実際にエンドポイントに対してHTTPリクエストを投げるための、PublicApiというクラスを定義します。
get_executions()というメソッドで、パラメータを渡して結果を取得することができます。
一度に取得する約定情報の個数を指定するcountというパラメータは、500が上限のようなのでデフォルト値を500に設定してあります。
beforeやafterに約定情報のIDを指定すると、そのIDより前や後(exclusive)の約定情報を取得することができます。

# bitFlyerのPublic APIを触るためのクラス
class PublicApi(object):

    API_URI = 'https://api.bitflyer.jp/v1/'

    def __init__(self, currency_pair):
        self.currency_pair = currency_pair

    def __get_request(self, endpoint, params=None):
        response = requests.get(endpoint, params=params)
        if response.status_code < 200 or response.status_code >= 300:
            raise Exception('Bad status code {}. Url={}. Body={}'.format(
                response.status_code,
                response.url,
                response.text))
        return response

    def get_executions(self, count=500, before=0, after=0):
        endpoint = PublicApi.API_URI + 'getexecutions'
        params = {
            'product_code': self.currency_pair,
            'count': count,
            'before': before
        }

        if after > 0: params['after'] = after
        response = self.__get_request(endpoint, params=params)
        results = response.json()
        executions = [Execution(x) for x in results]
        return executions

例えば、下記のような形でget_executions()を呼んであげると、IDが2より前(つまりbitFlyerでの一番最初の取引!)が1件取得できます。

public = PublicApi('BTC_JPY')
public.get_executions(count=1, before=2)[0].to_csv_line()
'1,SELL,30195.0,0.01,2015-06-24T05:58:48.773\n'

500件より多い約定情報を取得する

一度に取得できる約定情報は500件までですが、beforeやafterを指定してあげると繰り返しAPIを呼び出して500件より多い数の約定情報を取得することが可能です。
例えば直近N件の約定情報が欲しい場合は、まずは直近500件を取得し、一番小さい約定IDを記憶しておきます。次にその約定IDより前の500件を取得し…といった形で約定情報をN件取得できるまで繰り返せばOKです。
結果をCSVに吐き出しながら繰り返しAPIを呼ぶ関数は、例えば次のような形になります。(PublicApiクラスのメソッドです)

    def save_all_executions(self, csv_file, count=10000):
        """ Get all execution history from public api """
        with open(csv_file, 'w', encoding='utf-8') as f:
            latest = self.get_executions(count=1)
            before = latest[0].id + 1
            saved = 0

            f.write('id,side,price,size,timestamp\n')
            while before > 1:
                try:
                    executions = self.get_executions(before=before)
                    before = executions[-1].id
                    lines = [x.to_csv_line() for x in executions]
                    f.writelines(lines)
                    saved += len(executions)
                    time.sleep(0.12)
                except:
                    print('Failed to process executions. before={}'.format(before))
                    print(traceback.format_exc())

                if count and saved >= count:
                    break

途中sleepを入れているのは、APIの呼び出し制限に引っかからないための処置です。bitFlyerでは1分あたり約500回が上限と定められているので、適切な間隔を置いてAPIを呼び出してください。

この関数を下のように呼び出すと、executions.csvに1000件の約定履歴が保存されることになります。

public = PublicApi('BTC_JPY')
public.save_all_executions('./executions.csv', count=1000)

CSVを読み込んでみる

pandasを使ってCSVを読み込んで、約定価格をプロットしてみます。

# CSVからロードしてくる
import pandas as pd
csv_file = './executions.csv'
df = pd.read_csv(csv_file, parse_dates=['timestamp'])

# そのままグラフを書いてみる
df[['timestamp', 'price']].set_index('timestamp').plot(figsize=(18,9))

f:id:kanohk:20171229163420p:plain

これでも価格の上下が見れて良いのですが、せっかくなのでローソク足チャート描いてみましょう。

ローソク足チャートを描いてみる

昔の記事で、日経平均株価のデータを使ってローソク足チャートを描いてみたことがありました。今回も同様の方法で、取得したデータから分足のチャートを描いてみます。
www.madopro.net

データの調整

まずはデータを調整します。今回は1分足のチャートを描きたいので、タイムスタンプの秒とマイクロ秒の部分を0にしてしまいます。

df['timestamp'] = df['timestamp'].map(lambda x: x.replace(second=0, microsecond=0))

ここの処理を変えることで、自由に集計の幅を変えることができます。例えばsecond=x.second//5*5などとすると、タイムスタンプが5秒ごとの値になり、5秒足のチャートを描くことができます。

次に高値、安値、始値、終値、取引量を1分毎に計算していきます。pandasのデータフレームのgroupbyを使用します。

# 1分毎の安値と高値(min, max)、始値と終値(first, last)、および取引量(size) を集計
summary = df[['timestamp', 'price']].groupby(['timestamp']).min().rename(columns={'price': 'min'})
summary = summary.merge(
    df[['timestamp', 'price']].groupby(['timestamp']).max().rename(columns={'price': 'max'}),
    left_index=True, right_index=True)
summary = summary.merge(
    df[['timestamp', 'price']].groupby(['timestamp']).last().rename(columns={'price': 'first'}),
    left_index=True, right_index=True)
summary = summary.merge(
    df[['timestamp', 'price']].groupby(['timestamp']).first().rename(columns={'price': 'last'}),
    left_index=True, right_index=True)
summary = summary.merge(
    df[['timestamp', 'size']].groupby(['timestamp']).sum(),
    left_index=True, right_index=True)

これでsummaryというデータフレームに、毎分の高値・安値等が格納された状態になりました。

描いてみる

コードの解説は以前の記事にあるので、今回はいきなりコードと結果をお見せします。

import matplotlib.pyplot as plt
from matplotlib.finance import candlestick2_ohlc, volume_overlay

df2 = summary[-1000:]

# ローソク足をプロット
fig = plt.figure(figsize=(18, 9))
ax = plt.subplot(1, 1, 1)
candlestick2_ohlc(ax, df2["first"], df2["max"], df2["min"], df2["last"], width=0.9, colorup="b", colordown="r")
ax.set_xticklabels([(df2.index[int(x)] if x < df2.shape[0] else x) for x in ax.get_xticks()], rotation=90)
ax.set_xlim([0, df2.shape[0]])
ax.set_ylabel("Price")

# ローソク足を上側75%に収める
bottom, top = ax.get_ylim()
ax.set_ylim(bottom - (top - bottom) / 4, top)

# 出来高のチャートをプロット
ax2 = ax.twinx()
volume_overlay(ax2, df2["first"], df2["last"], df2["size"], width=1, colorup="g", colordown="g")
ax2.set_xlim([0, df2.shape[0]])

# 出来高チャートは下側25%に収める
ax2.set_ylim([0, df2["size"].max() * 4])
ax2.set_ylabel("Volume")

f:id:kanohk:20171229163954p:plain

これで直近のデータをもとに分足のチャートを描くことができました。
過去のチャートは自由に描くことができるようになったので、適当なテクニカル指標を実装して、バックテストして、うまくいきそうなら自動で取引させてみる、なんていうことができたら面白そうですね。。


ソースコード

今回の手順がすぐに試せるJupyter Notebookは下記にアップロードしてあります。
cryptocurrency/article1.ipynb at master · kanoh-k/cryptocurrency · GitHub