エンジニアの頭の中

フリーランスエンジニアが書く技術と投資のブログ

Python3とCCXTを使用して仮想通貨の自動売買プログラムを作る

f:id:mitsu3204:20180318200600j:plain

目次

  • はじめに
    • CCXTとは何か
  • CCXTを使用するための準備
    • CCXTをインストールする
    • 対応する仮想通貨の取引所を確認する
  • CCXTの使い方
    • 取引所オブジェクトを作成する
    • Public API
      • fetch_markets - 取扱い通貨情報の取得
      • fetch_order_book - 注文情報の取得
      • fetch_trades - 取引履歴の取得
    • Private API
      • fetch_balance - 残高取得
      • create_order - 新規注文
      • cancel_order - 注文のキャンセル
      • fetch_orders - 注文状況一覧の取得

はじめに

Python 3と仮想通貨の自動売買ライブラリであるCCXTを使用して、ビットコインなどの仮想通貨の自動売買プログラムを作成するため、CCXTの使用方法を学びます。 Pythonのバージョンは、3.6.2を使用しましたが、3系なら問題ないと思います。

CCXTとは何か

ビットコインなどの仮想通貨の売買を自動化するためのライブラリであり、言語は、JavaScriptPythonPHPのいずれかで利用することができます。 CCXTというのは「CryptoCurrency eXchange Trading Library」の略です。

github.com

主な特徴は以下のとおりです。

  • 仮想通貨の価格や取引情報の取得、注文の発行が可能。
  • 通貨の交換、分析、裁定取引をサポートしている。
  • 多くの仮想通貨取引所の公開API、非公開APIの両方に対応していて、今後も対応する仮想通貨取引所は増えていく予定。
  • Node 7.6以上、Python 2及び3、PHP 5.3以上、WEBブラウザで機能する。 ※ccxt.asyncの機能を使用する場合は、Pythonのバージョンが3.5.3以上である必要があります。

CCXTを使用するための準備

CCXTをインストールする

まずは、CCXTのインストールを行います。CCXTは、pipでインストールできます。記事執筆時点(2018/3/18)では、1.11.x系が最新バージョンです。

$ pip search ccxt
ccxt (1.11.62)  - A JavaScript / Python / PHP cryptocurrency trading library with support for 90+ exchanges
$ pip install ccxt

対応する仮想通貨の取引所を確認する

CCXTが対応する取引所は、ccxtモジュールのexchangesに定義されています。 CCXTが、自分が自動売買に使用したい取引所のAPIに対応しているかどうは、以下のようにして確認できます。

コード
import ccxt
print(ccxt.exchanges)
実行結果
['_1broker', '_1btcxe', 'acx', 'allcoin', 'anxpro', 'bibox', 'binance', 'bit2c', 'bitbay', 'bitcoincoid', 'bitfinex', 'bitfinex2', 'bitflyer', 'bithumb', 'bitlish', 'bitmarket', 'bitmex', 'bitso', 'bitstamp', 'bitstamp1', 'bittrex', 'bitz', 'bl3p', 'bleutrade', 'braziliex', 'btcbox', 'btcchina', 'btcexchange', 'btcmarkets', 'btctradeim', 'btctradeua', 'btcturk', 'btcx', 'bxinth', 'ccex', 'cex', 'chbtc', 'chilebit', 'cobinhood', 'coincheck', 'coinegg', 'coinexchange', 'coinfloor', 'coingi', 'coinmarketcap', 'coinmate', 'coinsecure', 'coinspot', 'coolcoin', 'cryptopia', 'dsx', 'exmo', 'flowbtc', 'foxbit', 'fybse', 'fybsg', 'gatecoin', 'gateio', 'gdax', 'gemini', 'getbtc', 'hitbtc', 'hitbtc2', 'huobi', 'huobicny', 'huobipro', 'independentreserve', 'itbit', 'jubi', 'kraken', 'kucoin', 'kuna', 'lakebtc', 'liqui', 'livecoin', 'luno', 'lykke', 'mercado', 'mixcoins', 'nova', 'okcoincny', 'okcoinusd', 'okex', 'paymium', 'poloniex', 'qryptos', 'quadrigacx', 'quoinex', 'southxchange', 'surbitcoin', 'therock', 'tidex', 'urdubit', 'vaultoro', 'vbtc', 'virwox', 'wex', 'xbtce', 'yobit', 'yunbi', 'zaif', 'zb']

戻り値はリスト型で、対応している取引所の名前が要素として格納されています。 この記事では、bitflyerAPIを使用していきます。

なお、CCXTが対応している取引所は、ccxt/README.md at master · ccxt/ccxt · GitHubからでも確認できます。

取引所のAPIキーを取得する

取引所のAPIを使用するには、APIキーが必要となる場合があります。Public API の場合、APIキーがなくても利用できるものもありますが、自分の口座残高や取引履歴などは、Private APIとなり、APIキー無しでAPIを利用することはできません。

APIキーの取得方法については、取引所のヘルプや、他のブログなどをご確認ください。

CCXTの使い方

では、CCXTの使い方を解説していきます。

取引所オブジェクトを作成する

使用したい取引所のAPIに応じて、ccxtモジュールから、対応するクラスのオブジェクトを作成します。ここで作成したオブジェクトから、各APIを使用するためのメソッドを呼び出すことによって、APIの呼び出しが可能です。

bitflyerAPIを呼び出すためのオブジェクトは以下のように作成します。引数に指定しているapiKeysecretは、公開APIを使用する分には空文字で構いません。自分の口座の情報や、注文の発行などを行うPrivate APIを使用する際は、認証が必要になるため、ご自分のAPIキーとAPIシークレット情報を指定してください。

bf = ccxt.bitflyer({
    'apiKey': '<APIキー>',
    'secret': '<APIシークレット>'
})

例えば、bitflyerでなくコインチェックAPIを利用したい場合であれば、以下のようにcoincheckを呼び出します。

cc = ccxt.coincheck({
    'apiKey': '<APIキー>',
    'secret': '<APIシークレット>'
})

このような感じで各取引所に応じたクラスのオブジェクトを作成します。 取引所オブジェクトを作成したら、あとは、取引所ごとのAPI仕様の差異に関わらず、共通の操作(メソッド)が可能です。

ただし、共通のメソッドで操作が可能といっても、結局のところ、取引所ごとにAPIの仕様は異なるため、同じメソッドを使用していても、取引所が変われば、戻り値の内容も異なってきます。
 CCXTの各メソッドは、取引所のAPIを呼び出して受け取った戻り値は、CCXTとして共通の戻り値の形へのマッピングされます。メソッドによっては、取引所から受け取ったままの独自の戻り値も返ってきますが、詳細は後述します。

Public API

Public APIから見ていきましょう。Public APIというのは、名前のとおり公開されたAPIで、口座残高などの個人に紐づく情報を操作することはできません。 価格情報、取引量、板情報などの取得が可能です。自動売買をするにあたり、発注の判断に必要な情報を取得するのに使用します。

各メソッドの戻り値は、基本的にJSON形式で受け取ったAPIの実行結果を、辞書形式にして返されます。戻り値の内容を読みやすくするため、jsonモジュールを使用して、辞書をJSON形式のテキストに変換し、インデントを整えたうえで出力していきます。 具体的には、以下のコードで戻り値を出力します。

import json
print(json.dumps(<CCXTのメソッドの戻り値>, indent=True))

fetch_markets - 取扱い通貨情報の取得

取引所が扱う通貨の情報を取得します。 BTC/JPYETH/USDなど、その取引所で利用可能な通貨ペアの情報を取得することができます。通貨の情報は、辞書(dict)で返されます。取引所が複数の通貨を扱っている場合は、通貨の情報が格納された辞書を要素とするリスト(list)で返されます。

コード
result = bf.fetch_markets()
print(json.dumps(result, indent=True))
引数
  • なし
戻り値
  • dict または list
実行結果
[
 {
  "id": "BTC_JPY",
  "symbol": "BTC/JPY",
  "base": "BTC",
  "quote": "JPY",
  "info": {
   "product_code": "BTC_JPY"
  }
 },
 {
  "id": "FX_BTC_JPY",
  "symbol": "FX_BTC_JPY",
  "base": "BTC",
  "quote": "JPY",
  "info": {
   "product_code": "FX_BTC_JPY"
  }
 },
 {
  "id": "ETH_BTC",
  "symbol": "ETH/BTC",
  "base": "ETH",
  "quote": "BTC",
  "info": {
   "product_code": "ETH_BTC"
  }
 },
 {
  "id": "BCH_BTC",
  "symbol": "BCH/BTC",
  "base": "BCH",
  "quote": "BTC",
  "info": {
   "product_code": "BCH_BTC"
  }
 },
 {
  "id": "BTCJPY16MAR2018",
  "symbol": "BTCJPY16MAR2018",
  "base": "BTC",
  "quote": "JPY",
  "info": {
   "product_code": "BTCJPY16MAR2018",
   "alias": "BTCJPY_MAT1WK"
  }
 },
 {
  "id": "BTCJPY23MAR2018",
  "symbol": "BTCJPY23MAR2018",
  "base": "BTC",
  "quote": "JPY",
  "info": {
   "product_code": "BTCJPY23MAR2018",
   "alias": "BTCJPY_MAT2WK"
  }
 },
 {
  "id": "BTC_USD",
  "symbol": "BTC/USD",
  "base": "BTC",
  "quote": "USD",
  "info": {
   "product_code": "BTC_USD"
  }
 },
 {
  "id": "BTC_EUR",
  "symbol": "BTC/EUR",
  "base": "BTC",
  "quote": "EUR",
  "info": {
   "product_code": "BTC_EUR"
  }
 }
]

戻り値の辞書オブジェクトには、infoというキーにさらに辞書オブジェクトが紐づけられていますが、このinfoの中身は、取引所のAPIの戻り値をそのまま格納しています。

CCXTは取引所間のAPIの差分を吸収するためのライブラリであるため、どの取引所のAPIを呼び出しても、共通の戻り値の形を返すよう、CCXTのメソッド内で戻り値の加工を行いますが、取引所のAPIが返した未加工の状態を、infoに辞書で格納しています。これは、他のメソッドも同様です。

fetch_ticker - 価格情報の取得

指定した通貨ペアの現在の価格情報を取得します。 引数に、価格情報を取得したい通貨ペアを指定します。指定可能な値は、fetch_marketsの戻り値のsymbolの値から確認できます。('BTC/JPY’'BTC/USD'など)

取引所固有の項目は、infoというキーで辞書がネストされています。詳細は実行結果をご覧ください。

引数
  • symbol: 通貨ペア
  • params: 取引所固有のパラメータ。必要であれば指定する。
戻り値
  • dict
コード

例として、ビットコイン(BTC)と日本円のペアの情報を取得しています。

result = bf.fetch_ticker(symbol='BTC/JPY')
print(json.dumps(result, indent=True))

paramsという引数が指定できますが、これは取引所のAPIが独自受け取るパラメータを指定したい場合に、辞書形式でパラメータ名と値を指定するようにします。

fetch_ticker(symbol='BTC/JPY', params={'<パラメータ名>': '<値>'})

このparamsという引数は、fetch_tickerに限らず、他のメソッドでも同様の引数を受け取れるようになっています。

bitflyerの場合は、tickerのAPIが受け取るパラメータは、product_codeというtickerの取得対象の通貨を指定するパラメータのみであり、引数symbolで指定した通貨ペアが、CCXTのメソッド内で、このproduct_codeに置き換えられるため、paramsで指定するパラメータは一切ありません。

取引所固有のパラメータを指定する必要があるかどうかは、利用する取引所のAPIドキュメントを確認してください。

実行結果
{
 "symbol": "BTC/JPY",
 "timestamp": 1520679230028,
 "datetime": "2018-03-10T10:53:50.280Z",
 "high": null,
 "low": null,
 "bid": 1003227.0,
 "ask": 1003990.0,
 "vwap": null,
 "open": null,
 "close": null,
 "first": null,
 "last": 1003227.0,
 "change": null,
 "percentage": null,
 "average": null,
 "baseVolume": 23628.09562113,
 "quoteVolume": null,
 "info": {
  "product_code": "BTC_JPY",
  "timestamp": "2018-03-10T10:53:50.28",
  "tick_id": 2042739,
  "best_bid": 1003227.0,
  "best_ask": 1003990.0,
  "best_bid_size": 0.1981,
  "best_ask_size": 0.01,
  "total_bid_depth": 2419.19038488,
  "total_ask_depth": 3312.40865674,
  "ltp": 1003227.0,
  "volume": 247012.0705073,
  "volume_by_product": 23628.09562113
 }
}

fetch_order_book - 注文情報の取得

現在の注文情報(板)を取得します。 戻り値の辞書には、買い注文、売り注文それぞれの情報が格納されており、買い注文はasks、売り注文はbidsというキーで、配列で価格と数量の情報を持っています。

引数
  • symbol: 通貨ペア
  • limit: 最大取得件数を指定するために用意されている引数かと思われますが、bitflyercoincheckzaifなどの主要な取引所のソースコードを確認した限りでは、この引数は使用されていないようです。

戻り値
  • dict
コード

ここでも、ビットコイン(BTC)と日本円のペアの注文情報(板)を取得してみます。

result = bf.fetch_order_book(symbol='BTC/JPY')
print(json.dumps(result, indent=True))
実行結果
{
 "bids": [
  [
   1006100.0, # 価格
   0.95       # 数量
  ],
  [
   1006000.0,
   2.31520001
  ],
  [
   1005840.0,
   1.4
  ],

... 長いため省略 

 "asks": [
  [
   1006299.0,
   0.38228109
  ],
  [
   1006300.0,
   0.86
  ],

... 長いため省略 

  [
   2500000.0,
   0.06
  ]
 ],
 "timestamp": 1520679590015,
 "datetime": "2018-03-10T10:59:50.150Z"
}

板情報のリストがとても長いため、間は省略しましたが、価格と注文量のセットが大量に連なっています。

fetch_trades - 取引履歴の取得

直近の成立した取引の履歴を取得します。

引数
  • symbol: 通貨ペア
  • limit: 最大取得数。取引所によって取得可能な最大取引数が異なるため、取引所の許容可能な最大値よりも大きい値をlimitに指定した場合は、その取引所の取得可能な最大値までしか取引情報は返されません。また、limitに0を指定した場合は、取得可能な取引が全て返されます。負の値を指定した場合は、取得可能な取引数から指定した負の数分だけ、差し引かれた取引数が返されます。
戻り値
  • list
コード
result = bf.fetch_trades(symbol='BTC/JPY', limit=2)
print(json.dumps(result, indent=True))
実行結果
[
 {
  "id": "174800818",
  "info": {
   "id": 174800818,
   "side": "BUY",
   "price": 816683.0,
   "size": 0.03,
   "exec_date": "2018-03-18T05:39:53.9",
   "buy_child_order_acceptance_id": "JRF20180318-053953-470298",
   "sell_child_order_acceptance_id": "JRF20180318-053953-330497"
  },
  "timestamp": 1521351593009,
  "datetime": "2018-03-18T05:39:53.900Z",
  "symbol": "BTC/JPY",
  "order": "JRF20180318-053953-470298",
  "type": null,
  "side": "buy",
  "price": 816683.0,
  "amount": 0.03
 },
 {
  "id": "174800807",
  "info": {
   "id": 174800807,
   "side": "SELL",
   "price": 816594.0,
   "size": 0.5988,
   "exec_date": "2018-03-18T05:39:53.15",
   "buy_child_order_acceptance_id": "JRF20180318-143942-701747",
   "sell_child_order_acceptance_id": "JRF20180318-053952-003651"
  },
  "timestamp": 1521351593015,
  "datetime": "2018-03-18T05:39:53.150Z",
  "symbol": "BTC/JPY",
  "order": "JRF20180318-053952-003651",
  "type": null,
  "side": "sell",
  "price": 816594.0,
  "amount": 0.5988
 }
]

このメソッドの戻り値も、CCXTが加工する前の取引所APIの戻り値は、infoというキーに紐づけて、辞書形式で格納されています。

Private API

続いて、Private APIの使用方法を見ていきます。Private APIは、個人の口座残高、取引履歴など、Public APIでは取得できない個人の情報にアクセスすることができます。また、購入、売却などの発注を行うのもPrivate APIの操作が必要となります。 Private APIを使用するには、APIキーとAPIシークレットによる認証が必須となります。また、APIにもよるかと思いますが、APIでの実行を許可する操作を取引所のAPI管理画面から操作可能だと思います。Private APIを試してみる前に、目的の操作を許可しておいてください。

fetch_balance - 残高取得

APIキーに紐づく口座の残高の情報を取得します。

引数
  • params: 必須ではありません。取引所のAPIに渡すパラメータを渡す必要があれば、辞書形式で指定します。bitflyerの場合、残高取得APIが受け取るパラメータはありません。
コード
result = bf.fetch_balance()
print(json.dumps(result, indent=True))
実行結果
{
 "info": [
  {
   "currency_code": "JPY",
   "amount": 0.0,
   "available": 0.0
  },
  {
   "currency_code": "BTC",
   "amount": 0.0,
   "available": 0.0
  },
  {
   "currency_code": "BCH",
   "amount": 0.0,
   "available": 0.0
  },
  {
   "currency_code": "ETH",
   "amount": 0.0,
   "available": 0.0
  },
  {
   "currency_code": "ETC",
   "amount": 0.0,
   "available": 0.0
  },
  {
   "currency_code": "LTC",
   "amount": 0.0,
   "available": 0.0
  },
  {
   "currency_code": "MONA",
   "amount": 0.0,
   "available": 0.0
  },
  {
   "currency_code": "LSK",
   "amount": 0.0,
   "available": 0.0
  }
 ],
 "BCH": {
  "free": 0.0,
  "used": 0.0,
  "total": 0.0
 },
 "BTC": {
  "free": 0.0,
  "used": 0.0,
  "total": 0.0
 },
 "ETH": {
  "free": 0.0,
  "used": 0.0,
  "total": 0.0
 },
 "EUR": {
  "free": 0.0,
  "used": 0.0,
  "total": 0.0
 },
 "JPY": {
  "free": 0.0,
  "used": 0.0,
  "total": 0.0
 },
 "USD": {
  "free": 0.0,
  "used": 0.0,
  "total": 0.0
 },
 "free": {
  "BCH": 0.0,
  "BTC": 0.0,
  "ETH": 0.0,
  "EUR": 0.0,
  "JPY": 0.0,
  "USD": 0.0
 },
 "used": {
  "BCH": 0.0,
  "BTC": 0.0,
  "ETH": 0.0,
  "EUR": 0.0,
  "JPY": 0.0,
  "USD": 0.0
 },
 "total": {
  "BCH": 0.0,
  "BTC": 0.0,
  "ETH": 0.0,
  "EUR": 0.0,
  "JPY": 0.0,
  "USD": 0.0
 }
}

他のメソッドの戻り値と同様で、infoキーに紐づくオブジェクトは、取引所APIの戻り値がそのまま格納されています。

CCXT共通の項目としては、各通貨名称をキーとして、freeusedtotalという3つのキーを持つ辞書と、freeusedtotalをそれぞれキーとして、各通貨をメンバとして持つ辞書となります。 freeusedtotalの意味は、以下のとおりです。

  • total … 合計残高
  • free … 使用可能な残高(売却注文中の分などは除かれると思われる)
  • used … total - free

create_order - 新規注文

「買い」または「売り」の注文を新規発行します。 本番環境に不意に注文を行なってしまわないように注意しましょう。テスト環境がある取引所の場合は、テスト環境を使用しましょう。

引数
  • symbol: 通貨ペア
  • type: 指値ならlimit、成行ならmarket
  • side: 買い注文の場合はBUY、売り注文の場合はSELL
  • amount: 通貨の量
  • price: 価格
  • params: 取引所のパラメータ
コード

通貨はBTC/JPY、量が0.001、価格に50万l円を指定して指値で買い注文を行なっています。

result = bf.create_order(symbol='BTC/JPY', type='limit', side='BUY', amount=0.001, price=1)
print(json.dumps(result, indent=True))
実行結果
{
 "info": {
  "child_order_acceptance_id": "JRF20180321-122844-950096"
 },
 "id": "JRF20180321-122844-950096"
}

id というキーに文字列が設定されていますが、これがbitflyerに発行した注文のIDとなります。注文のキャンセルなどを行う場合は、このIDを使用します。

cancel_order - 注文のキャンセル

未約定の注文をキャンセルします。

引数
  • id: キャンセルしたい注文のIDを指定します。
  • symbol: 通貨ペアを指定します。
戻り値

なし(bitflyerの場合)

コード

先ほど、create_orderで発行した注文のIDを指定して、この注文を取り消してみます。

result = bf.cancel_order(id='JRF20180321-122844-950096', symbol='BTC/JPY')
print(json.dumps(result, indent=True))
実行結果

戻り値は空っぽで返ってきました。bitflyerAPIドキュメントによると、注文のキャンセルに成功した場合は、HTTPステータスの200のみ返して、ボディは何も返さない仕様のようです。取引所によっては、ボディを返してくるAPIもあると思うので、自分が使用するAPのドキュメントは確認しましょう。

fetch_orders - 注文状況一覧の取得

発注済みで未約定の注文の一覧を取得します。

引数
  • symbol: 通貨ペアを指定します。
  • since: 注文の取得対象とする起点となる日時を指定します。指定した日時以降の注文のみを取得したい場合に使用します。
  • limit: 取得する注文の最大件数を指定します。 sinceやlimitは、取得する注文の量を制限するものですが、APIリクエストのパラメータとして指定されているわけではありません。あくまでもbitflyerから取得した結果を、これらの引数で指定された内容に従ってフィルタリングしているに過ぎません。
戻り値

list

コード
result = bf.fetch_orders(symbol='BTC/JPY')
print(json.dumps(result, indent=True))
実行結果
[
 {
  "id": "JRF20180321-125325-324617",
  "info": {
   "id": 0,
   "child_order_id": "JOR20180321-125325-640119",
   "product_code": "BTC_JPY",
   "side": "BUY",
   "child_order_type": "LIMIT",
   "price": 500000.0,
   "average_price": 0.0,
   "size": 0.001,
   "child_order_state": "ACTIVE",
   "expire_date": "2018-04-20T12:53:25",
   "child_order_date": "2018-03-21T12:53:25",
   "child_order_acceptance_id": "JRF20180321-125325-324617",
   "outstanding_size": 0.001,
   "cancel_size": 0.0,
   "executed_size": 0.0,
   "total_commission": 7e-07
  },
  "timestamp": 1521636805000,
  "datetime": "2018-03-21T12:53:25.000Z",
  "status": "open",
  "symbol": "BTC/JPY",
  "type": "limit",
  "side": "buy",
  "price": 500000.0,
  "cost": 0.0,
  "amount": 0.001,
  "filled": 0.0,
  "remaining": 0.001,
  "fee": {
   "cost": 7e-07,
   "currency": null,
   "rate": null
  }
 }
]

ここでは、1件のみととなっていますが、複数の注文を行なっていれば、それらがリストの要素として返されます。 返される内容としては、注文のID、発注日時、通貨や価格、注文の状態など、注文の内容に関する一通りの情報が返されるようです。ただし、これも取引所のAPI次第で、内容が異なると思われます。おそらく取引所のAPIが対応していない項目については、Noneとなっていると思います。

以上となります。

今回は、仮想通貨の自動売買プログラムを作成するために必要となるCCXTの基本的なAPIの使用方法のみを説明しました。 また機会があれば、CCXTを使用して、実際にマーケットの情報を取得して、売買の判断と発注を行うプログラムについての記事も書いてみようと思います。