ビットコインをAPIで自動売買する

ビットコインの自動裁定取引システムのプロトタイプを開発しました。
以下の取引所に対し、3秒ごとに板情報を解析し、裁定機会があれば注文を送信します。

  • bitFlyer
  • Quoine
  • Coincheck

ソースコードをGitHubに公開しています。TypeScriptで実装しており、Node.js環境でコンソールアプリとして動作します。

R2 Bitcoin Arbitrager GitHub stars Build Status Coverage Status

R2はMac OS, Windows, Linux環境で実行可能です。各取引所に口座開設をし、APIキーを取得すればだれでも実行可能です。

ライセンスはMITです。

  • 無償で無制限に利用可能です
  • 複製、販売の制限もありません
  • 作者は本ソフトウェアによって生じる一切の損害について責任を負いません

⚠️オークションサイトで本ソフトウェアを高額で出品している方がいますが、作者とは無関係です。

質問・機能追加依頼はGitHubイシューGitHub issuesからお願いします。

裁定(アービトラージ)取引で収益をあげられるのか?

ビットコインには、取引所間での価格差を利用した裁定機会が残っています。実際に各取引所で配信されている価格をみると、タイミングによってはかなりの価格差があります。

この価格差を利用して、ある取引所で安く買い、同時に他の取引所で高く売れば、Bitcoinの価格変動にかかわらず、リスクなしに収益を得られることになります。

大半のビットコイン取引所はAPIを公開しており、理論的には一瞬の価格差を利用して、自動取引で継続的に収益を上げることが可能です。仮に価格差が非常に小さかったとしても、10秒単位で繰り返し取引を行うことで累積収益は十分に大きくなる可能性があります。

例えば、10秒に一度裁定機会をみつけ、10円の収益を上げる自動裁定システムを開発したと仮定しましょう。一見たった10円に思えるかもしれませんが、秒速1円の収益です。その(架空の)システムを24時間一ヶ月間連続で作動させると60*60*24*30 = 2,592,000、つまり250万以上の収益となります。悪く見積もって1分に10円の収益であっても、月45万となります。

そのようなシステムを実際に作ることができるのか、という疑問を解消するために自分で開発してみました。

実際の実行結果

以下は実際にこの自動裁定取引システムを実行したときのスクリーン動画です。

rinjani.gif

約30秒で二回の裁定機会を捉え、66円の収益を上げています。取引所の手数料等を差し引いても、秒速1円以上の収益を上げています。

このことから、ビットコイン市場には十分な裁定機会がある、と結論付けることができます。

しかし、実際にしばらく運用した後、長期運用にはいくつかの現実的な手間とコストが発生することが判明しました。それについては後述します。

自動裁定取引システムの動作

裁定取引フロー

  1. 3秒ごとに各取引所から板情報(価格・数量ペアのリスト)を取得する。
  2. 現在のBitcoinポジションのネットエクスポージャーが設定された最大値を超えていないかチェックする。超えている場合、システムを停止する。
  3. 各取引所から取得した板情報のうち、裁定取引の対象になりえないものを取り除く。例えば、Bitflyerで現在のポジションが0で、かつ空売りをしない設定の場合、Bitflyerからのビッド(売値)は取り除く。
  4. 最良ビッド(売値)、最良アスク(買値)を計算する。もしそのスプレッドが逆転してない場合(買値が売値より高い場合)、裁定機会は存在しないため、ステップ1に戻る。
  5. スプレッドが逆転しているとき、期待収益(仮に最良ビッド/最良アスクで売り買いできたときにどれだけの収益が得られるか)を計算する。その値が設定された目標収益を超えている場合、各取引所に指値注文を送信する。
  6. 注文送信後、3秒ごとに各注文が約定したかチェックする。
  7. もし売り買い注文両方が約定した場合、収益を表示しステップ1に戻る。

裁定ポジション解消フロー

上記裁定取引フローでポジションができた後は、以下の処理が1と2の間に追加される。

i. minExitTargetProfitより大きい期待収益で、オープン中のポジション(買いと売りのペア)を解消できるかチェックする。古いペアからチェックする。(FIFO)
ii. もし解消できる場合、そのペアの逆方向の指値注文を対象の取引所の最良価格で送信する。

minExitTargetProfitの代わりにminExitTargetProfitPercentで取引価値に対する割合でもしきい値を設定可能。
また、exitNetProfitRatioでオープン時の収益に基づいてクローズ時のしきい値を動的に変化させることも可能。詳細は全体設定の項目を参照。

アーキテクチャ概要

各コンポーネントの説明はGitHubのドキュメントを参照ください。

diagram_ja.png

インストール方法

1) Node.js 8.5以降をインストール
2) ターミナルからgitリポジトリをクローン

git clone https://github.com/bitrinjani/r2.git

3) フォルダr2に移動し、npm installをコンソールで実行

cd r2
npm install

4) インストールフォルダ内のconfig_default.jsonconfig.jsonにリネーム
⚠️v2.3.0から設定ファイルの場所が./src/config.jsonから./config.jsonに変更されました。
5) keysecretフィールドを、各取引所から取得したAPIキー、シークレットに置き換える
6) 日本語UIにする場合、languageフィールドを"en"から"ja"に変更する
7) コンソールからnpm startで起動

設定

設定はconfig.jsonファイルで行います。全体に影響する設定と、各取引所(ブローカー)に対する設定があります。
設定項目は、裁定取引システムの先駆者であるBlackbirdを参考にしています。

全体設定

Name Values Description
demoMode true or false デモモード。trueのとき、裁定機会の解析は行うが実際の注文は送らない。
priceMergeSize number 板情報の細かい価格を集約する。100に設定されているとき、100円刻みにクオートの数量を合計したサマリ板を構築し、その板情報に対し裁定機会の計算を行う。
maxSize number 取引所に送る注文数量の最大値。仮にこの値よりも大きい数量で裁定可能であっても、この設定値の注文を送信する。
minSize number 取引所に送るオーダー数量の最小値。裁定機会がこの値より小さい数量の場合、取引を行わない。
minTargetProfitPercent number 最小目標収益割合。期待収益の裁定取引の円換算(取引価格*数量)に対する割合(%)がこれより小さい場合、取引を行わない。
maxTargetProfit number [Optional] 最大目標収益。裁定機会の期待収益がこの値より大きい場合、取引を行わない。取引所が不正な価格を提示した場合の安全弁。
maxTargetProfitPercent number [Optional] 最大目標収益割合。期待収益の裁定取引の円換算(取引価格*数量)に対する割合(%)がこれより大きい場合、取引を行わない。取引所が不正な価格を提示した場合の安全弁。maxTargetProfitとmaxTargetProfitPercentの両方が設定されている場合、両方を下回らない限り取引を行わない。
exitNetProfitRatio number オープン時の収益に対し、クローズによって何%の利益を確定するか指定する。このパーセンテージ分だけオープン時からスプレッドが縮小した時、そのオープンペアをクローズする。例えば、オープン時の収益が200かつexitNetProfitRatio=20とすると、クローズのコストが160を下回ったときにR2はクローズオーダーを送信し、40の利益を確定しようとする。(オープンによる収益200, クローズによる費用160 -> 利益40)
maxTargetVolumePercent number [Optional] 数量不足による約定しない状況を回避する為に、裁定可能数量に対する注文量の割合に制限を設ける。この割合を上回らない限り取引を行わない。例えば、maxTargetVolumePercent=50の場合、裁定可能数量が1.00BTCだと注文量は0.5BTC(50%)以下でなければ取引を行わない。
iterationInterval Millisecond 裁定プロセスのインターバル。この値が3000に設定されている場合、3秒に一回板情報を取得し裁定機会を探る。
positionRefreshInterval Millisecond ポジション更新インターバル。
sleepAfterSend Millisecond 裁定取引完了後、このミリ秒だけ休止する。
maxNetExposure number 最大ネットエクスポージャー*。取引所の合計ネットエクスポージャーの絶対値がこの値を超える場合、取引を行わない。
maxRetryCount number 裁定取引のオーダーを送信後、注文の約定状態をチェックする最大回数。
orderStatusCheckInterval Millisecond 裁定取引の注文を送信後、注文の約定状態をチェックするインターバル。
onSingleLeg - 下記「onSingleLeg設定詳細」参照

*minTargetProfitPercentの例:
minTargetProfitPercent: 0.1%
ベストアスク: 800,000円, 0.3 BTC
ベストビッド: 801,000円, 0.2 BTC
-> MID: 800,500円, 目標数量0.2BTC,期待収益200円となり、収益の割合は 200 / (800500 * 0.2) = 0.0012、つまり0.12%。
この値はminTargetProfitPercentである0.1%を上回っているので、取引を送信します。

*ここでのネットエクスポージャーとは、各取引所のポジションを合計した"BTC数量"です。一般にはエクスポージャーには数量ではなく割合を指しますが、簡略化のため数量としています。例えば、Bitflyerで0.1 BTC, Quoineで0.1 BTC, Coincheckでマイナス0.1 BTC(空売り)のとき、ネットエクスポージャーは 0.1 + 0.1 - 0.1 = 0.1 BTCとなります。仮にMaxNetExposure=0.05と設定されていた場合、0.1 > 0.05のため取引は送信しません。

onSingleLeg設定詳細

onSingleLeg設定で裁定ペアの片側だけ約定したときの動作を指定できます。反対売買で約定を取り消すか、未約定オーダーを再送信するか指定できます。

設定例:

// config.json
...
  "onSingleLeg": {
    "action": "Reverse",
    "actionOnExit": "Proceed",
    "options": {
      "limitMovePercent": 5,
      "ttl": 3000
    }
  },
...
  • action: オープン時に指定回数の約定チェック後、片側だけ約定しなかった場合の動作。 選択肢は以下。
    • Cancel: 未約定のオーダーをキャンセルするのみ。
    • Reverse: 未約定のオーダーをキャンセル後、約定したオーダーに対し指値注文で反対売買する。指値価格はlimitMovePercentに依存する。
    • Proceed: 未約定のオーダーをキャンセル後、同方向に指値注文を再度送信する。指値価格はlimitMovePercentに依存する。
  • actionOnExit: クローズ時に指定回数の約定チェック後、片側だけ約定しなかった場合の動作。選択肢はactionと同様。
  • options
    • limitMovePercent: もとのオーダーよりも指定%だけ不利な方向に指値価格を指定する。
    • ttl: Actionにより作られた指値注文がこのミリ秒後に約定していない場合、キャンセルする。Time to Live。

取引所設定

Name Values Description
broker Bitflyer, Quoine or Coincheck 取引所名
enabled true or false 裁定取引の対象とするかどうかの設定
key string 取引所APIのキーもしくはトークン
secret string 取引所APIのシークレット
maxLongPosition number 最大ロングポジション
maxShortPosition number 最大ショートポジション
cashMarginType Cash, MarginOpen or NetOut オーダータイプ。現金取引、証拠金取引オープン、ネットアウトのどれか。取引所ごとにサポートしているタイプは異なる。下記テーブルを参照。
commissionPercent number 取引手数料割合。裁定プロセスが予想利益を計算する際、取引手数料(目標価格 * 目標数量 * (commissionPercent / 100))を期待収益から差し引いた上で、取引を送信するか判断する。
noTradePeriods ["開始時間", "終了時間"]のリスト 下記noTradePeriods詳細を参照

*取引所は最低2つ有効になっている必要があります。

cashMarginType詳細

取引所 サポートされるcashMarginType
Bitflyer Cash
Quoine Cash, NetOut
Coincheck Cash, MarginOpen, NetOut*

*Coincheckのネットアウトは、取引所APIに存在しない取引タイプのため、アプリケーション内部でどのポジションをクローズするか判断しています。(Quoine APIはネットアウトをネイティブでサポートしています)
CoincheckのcashMarginTypeをNetOutに設定すると、裁定プロセスはオーダーを送信する前に現在のオープンポジションをチェックします。もしほとんど同じサイズのポジションが見つかれば、そのうち最も古いものに対しクローズオーダーを送信します。(FIFO)
ここで「ほとんど同じ」とは、1%以内の差異としています。コインチェックは、0.01 BTCの売注文を出すと、発生するポジションの数量が0.010005 BTCなど微妙に違う値になります。この違いを吸収するために1%の差異を許容しています。

noTradePeriods詳細

取引を禁止する期間を ["開始時間", "終了時間"] の形式で記述します。その期間、その取引所からの価格配信を除外し価格が配信されていないものとみなします。例えば、bitFlyerの定期メンテナンス時間を除外するために利用できます。

  • 例: 一つだけの期間4:00-4:15を禁止したい場合
    {
      "broker": "Bitflyer",
...
      "noTradePeriods": [["04:00", "04:15"]]
    },

- 例: 複数の期間を禁止したい場合

    {
      "broker": "Bitflyer",
...
      "noTradePeriods": [["04:00", "04:15"], ["9:00", "9:30"]]
    },

ログ通知設定 (Slack, LINE通知)

出力されるログの内容に応じて、Slack, LINEに通知を送ることができます。keywordsで指定された複数キーワードのうち一つが含まれると通知します。

// config.json
{
...
  "logging": {
    "slack": {
      "enabled": false,
      "url": "https://hooks.slack.com/services/xxxxxx",
      "channel": "#ch1",
      "username": "abc",
      "keywords": ["error", "profit"]
    },
    "line": {
      "enabled": false,
      "token": "TOKEN",
      "keywords": ["error", "profit"]
    }
  }
}

Slack通知

Name Values Description
enabled true or false 通知の有効、無効
url string Slack Incoming Webフック URL
channel string Slackチャンネル名
username string ユーザー名
keywords string[] キーワードリスト。どれか一つがログメッセージに含まれれば通知される。

LINE通知

Name Values Description
enabled true or false 通知の有効、無効
token string LINE Notifyトークン
keywords string[] キーワードリスト。どれか一つがログメッセージに含まれれば通知される。

Slack WebフックURL、LINE Notifyトークンの取得方法については、以下の投稿を参考ください。

SlackのWebhook URL取得手順

[超簡単]LINE notify を使ってみる

補助スクリプト

R2の裁定プロセスとは別で、単体で動作する補助スクリプトをいくつか用意しています。
config.jsonのキー、シークレットを読み取り取引所APIを実行します。
ソースはtoolsディレクトリ下です。

getBalance - 各取引所のJPY, BTC残高をCSV形式で出力

npm run -s getBalance

出力例:

Exchange, Currency, Type, Amount
bitFlyer, JPY, Cash, 300000
bitFlyer, BTC, Cash, 1.234
Coincheck, JPY, Cash, 300000
Coincheck, BTC, Cash, 0.123
Coincheck, JPY, Margin, 200000
Coincheck, JPY, Free Margin, 123456
Coincheck, BTC, Leverage Position, 3.456
Quoine, JPY, Margin, 300000
Quoine, JPY, Free Margin, 123456
Quoine, BTC, Leverage Position, 0.01

closeCcPosition - Coincheckの全レバレッジポジションを成行注文でクローズ

npm run closeCcPosition

closeBfPosition - bitFlyerの全現物BTCを成行注文で売却

npm run closeBfPosition

closeQuPosition - Quoineの全レバレッジポジションを成行注文でクローズ

npm run closeQuPosition

closeAll - 上記3つのcloseを順に実行する。

npm run closeAll

問題点

取引所の証拠金取引(レバレッジ取引)の可否

取引所によっては、証拠金取引が行えないケースがあります。
例えば、Bitflyerは証拠金取引を「ビットコインFX」として提供していますが、ビットコイン現物とはまったく異なる価格帯で取引が行われています。ビットコインFXに対しては、シンプルな価格比較では裁定取引が行なえません。
そのため、Bitflyerに対しては、この自動裁定取引システムはビットコイン現物のみを裁定対象としています。
レバレッジがかけられないと、ショートポジションが取れない、多額の現金が必要になるなど、継続的な裁定取引の障害となります。

取引所のAPIのスピード、品質

取引所によっては、APIから指値注文を送信後、10秒以上実際の注文が作成されない場合があります。この現象は特にBitflyerで一時的に発生していました。(現在は解消されている模様です。)
これがAPIインフラの遅延なのか、意図的なものなのかは不明ですが、10秒遅れると約定する見込みはほとんどなくなります。

また、取引所によってAPIの品質に非常に大きなばらつきがあります。今回使用した3社だけでなく、7社の口座を開設しAPIの使用感を確認しました。
使いやすさ、統一性という点では、Bitflyer, Quoineが非常に素晴らしいAPIを提供しています。(上記のようにBitflyerにはスピードの問題がありましたが、それはAPI設計ではなくインフラの問題だと思われます)
それに対し、価格取得すら安定的にできないレベルのAPIを提供している取引所も多数あります。
より多くの取引所を対象にすれば、より多くの裁定機会を見つけられるはずですが、実際に裁定取引に耐える品質のAPIを提供している取引所は多くありません。

取引所ごとの価格の偏りから派生するポジションの偏り

取引所ごとに、価格の高低に偏りがあります。例えば、Bitflyerは安値になる傾向があり、Quoineは高値になる傾向があります。このとき、裁定取引はBitflyerに対し買いを出し、Quoineに対し売りをだすことになります。そして、この傾向が長期間続くとBitflyerではロングポジションが増大していき、Quoineではショートポジションが増大していきます。

*12/19追記: この問題に対処するため、minExitTargetProfit設定を追加しました。

各取引所でポジションが偏ると、その分だけ現金・証拠金が多く必要になります。また、偏りをならすために取引所間でBitcoinを移動する必要が出てきます。Bitcoinの移動の手数料は大きくありませんが、移動後の取引所内で売り買いの決済取引のコストが発生します。決済取引のコストは、その取引所のその時点のスプレッドとなり、1BTCあたり1000円以上になることがあります。

結論

自動裁定取引システムである程度の収益を上げることは十分可能です。今後さらに取引所API、取引所システムが洗練されていけば、より効率的に裁定が行えるようになっていくはずです。それに伴い、裁定取引への参入者が増え、各人の裁定収益は減っていくことも予想されます。
品質の悪いAPIを提供している取引所を逆に自分以外への参入障壁とみなし、あえてそこで裁定取引を行うことで独占的利益を得られるかもしれません。

FAQ

Q. Unauthorizedエラーが発生する

以下のようなUnauthorizedエラーが表示される。

HTTP request failed. Response from https://api.bitflyer.jp/v1/me/getbalance. Status Code: 401 (Unauthorized) Content: {"status":-500,"error_message":"Invalid signature","data":null}

A. 取引所のAPIキー/シークレットがconfig.jsonに正しく設定されているか確認してください。APIキー/シークレットの取得方法は、各取引所のWebページを参照してください。

Q. スプレッド解析結果の取得に失敗する

以下のような警告が表示される。

スプレッド解析結果の取得に失敗しました。 ベストビッドが見つかりませんでした。

A. config.jsonの各取引所設定部分のmaxLongPosition, maxShortPositionが十分に大きいか確認してください。
売り試行許可、買い試行許可は、取引所設定のmaxLongPosition、maxShortPositionで決まります。
例えば、Quoineの売りが続くといずれQuoineのmaxShortPosition設定にぶつかり、売り試行許可がFalseに切り替わり、ベストビッドの候補対象から外れます。

Q. 取引所間でBTC保有量のバランスが崩れた際、バランスを判定し、自動で取引所間で送金するというような機能を実装する予定はありますか?

送金は実装予定はありません。理由は、BTC送金に時間がかかることと、日本円送金に大きな手数料がかかるため、自動化してもメリットがないためです。
BTC送金自体は簡単なのですが、BTCを送金した取引所口座は、そのBTCを購入するのにかかった費用の分だけ日本円が減っている状態になります。そのバランスを修正するには、BTCを受け取った取引所口座から日本円を送金するか、銀行から日本円を送金する必要があります。それに伴い、日本円送金の手数料が発生します。
この部分を自動化して細かい単位で送金をしつづけると、手数料がつもり重なって大きなコストになります。

Q. このようなアプリケーションを公開することで、裁定機会がなくなってしまうのではないか?

いいえ。仮に数百人が使ったとしても、このアプリケーションが原因で裁定機会はなくなることはありません。ビットコインの取引高は月間何兆円という単位です。個人にマーケットを動かす力はありません。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.