「 アービトラージをやって見たいけどJavaScript(nodeJS)でサクッとできないのかなあ 」 という人へ向けてまずは各取引所の板から価格を取得して、そこから価格差を導き出すプログラムをアービトラージの基礎として解説しました。
「Pythonの方がいいなあ」という人は【Python】 仮想通貨アービトラージ自動売買botの基本 【価格差の取得】へどうぞ。
こちらの記事から始めても大丈夫ですが、この記事で利用するccxtライブラリなどについて分からなければ、前回の記事【 JavaScript(node.js) 】 bitFlyerからAPIで売買注文を入れる方法 【コピペ】を参考にしてみてください。
この記事はアービトラージについての解説であるのと同時に、初心者向けが足掛かりとなるための解説にもなっています。
実践的なアービトラージができるプログラムが欲しい人はnoteで買える仮想通貨の自動売買botや関連noteをまとめてみたを参考にして見てください。
この記事を読めば JavaScript(node.js)を使った仮想通貨アービトラージ(裁定取引)のやり方 の基礎を身に付けることが出来ます。
草猫店長の目次ノート

nodeJSでのアービトラージのロジック
いうまでもないですが「安い価格で買い、高い価格で売る」これを取引所に置き換えるだけですね。
転売と全く変わりません。
しかし、物と違ってリアルタイムで価格が変わっていきます。
したがって、リアルタイムで取得していき、安い価格と高い価格の取引所を見つけて売買するロジックとします。
今回の記事で考慮しないポイント
・手数料(送金手数料や売買手数料)
・送金時間
・空売りを交えた売買(クロス取引)
・ビットコイン以外の通貨
・買い数量の調整
他にも精度を高めるための視点は色々ありますが、上記5つのポイントなどを考慮しつつ利益を出せるようにする実践的な部分は話が分岐して細かくなってしまうためここでは無視しています。
実際に利益を出していこうとするといくらくらいの価格差から実行するべきかどうかは、過去データを使って仮の送金時間などを考慮に入れてバックテストした方が良いでしょうね。
ですがその前にまずは動かせるようになりたいところです。
ターゲットとなる取引所の板情報を取得しよう
今回はテストとして5つの国内取引所(bitbank・bitflyer・coincheck・liquid・zaif)を対象にしました。
ですがまずは一つの取引所の板情報を取得してみましょう。
(UdemyのNode.jsの基礎から学ぶ、ビットコイン自動売買プログラムで紹介されているとても分かりやすいコードを途中まで参考にさせてもらいました。この記事読んでも微妙だったという人はぜひ講座を購入してみてください。0から分かります。
また、後半は自分用のカスタマイズしたコードを載せています)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
'use strict'; const ccxt = require ('ccxt'); const bitflyer = new ccxt.bitflyer (); const interval = 2000 const stopTerm = (timer) => { return new Promise((resolve, reject) => { setTimeout( () => { resolve () }, timer) }) } (async function() { while(true) { const results = await Promise.all([ bitflyer.fetchTicker ('BTC/JPY'), ]) const bitflyerTicker = results[0] console.log ('bitflyerの買いは' + bitflyerTicker.ask + '円') console.log ('bitflyerの売りは' + bitflyerTicker.bid + '円') await stopTerm (interval) } })(); |
上記のコードを実行すると以下の結果を得ることができます。
1 2 3 4 5 |
$ node abitrageSample.js bitflyerの買いは1020990円 bitflyerの売りは1019249円 bitflyerの買いは1021029円 bitflyerの売りは1019279円 |
およそ2秒毎にbitflyerの買いと売りを取得して出力されていきます。
ccxtライブラリのインストールがまだ終わってないという人は以下のコードをVScodeなどのターミナル(コマンドプロンプト)に入力しましょう
1 |
npm install ccxt |
もしインストールできない場合は、npmパッケージをインストールしていない場合も考えられます。npm入門 – Qiitaの記事通りにインストールしてみてください。
コード解説
1 |
const bitflyer = new ccxt.bitflyer (); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
'use strict'; const ccxt = require ('ccxt'); const bitflyer = new ccxt.bitflyer (); const interval = 2000 const stopTerm = (timer) => { return new Promise((resolve, reject) => { setTimeout( () => { resolve () }, timer) }) } (async function() { while(true) { const results = await Promise.all([ bitflyer.fetchTicker ('BTC/JPY'), ]) const bitflyerTicker = results[0] console.log ('bitflyerの買いは' + bitflyerTicker.ask + '円') console.log ('bitflyerの売りは' + bitflyerTicker.bid + '円') await stopTerm (interval) } })(); |
results配列の[0]の中にTickerデータが入っているのでconsole.logでbitflyerTicker.askのコマンドで買い気配値を取得しています。
2秒間隔で実行されるコード
1 |
await getAgain (interval) |
1 2 3 4 5 6 7 |
const stopTerm = (timer) => { return new Promise((resolve, reject) => { setTimeout( () => { resolve () }, timer) }) } |
1 |
while(true) |
while(true)というのは処理が真である間は処理を延々と続けるという意味になります。
偽になる条件を処理の中に入れていないためwhile(true)はずっと適用され続けることになります。
しかし繰り返しのインターバルは決まっていないため、interval = 2000(2000ミリ秒つまり2秒)でストップ期間を入れています。
データを受け取るタイミング調整を行うための非同期処理(async / await)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
'use strict'; const ccxt = require ('ccxt'); const bitflyer = new ccxt.bitflyer (); const interval = 2000 const stopTerm = (timer) => { return new Promise((resolve, reject) => { setTimeout( () => { resolve () }, timer) }) } (async function() { while(true) { const results = await Promise.all([ bitflyer.fetchTicker ('BTC/JPY'), ]) const bitflyerTicker = results[0] console.log ('bitflyerの買いは' + bitflyerTicker.ask + '円') console.log ('bitflyerの売りは' + bitflyerTicker.bid + '円') await stopTerm (interval) } })(); |
ccxtライブラリで各取引所にあるAPIサーバーへ「データが欲しい」と伝える訳ですが、APIサーバーによって反応スピードが異なります。
そのため、取得できるタイミングがバラバラになり出力もバラバラになります。
そこで、バラバラになるタイミングだけ非同期処理(asyncのawait)を挟み込み、非同期処理の処理が終わり次第、次の処理を実行できるようにしています。
順番に処理が進むことは同期処理と言います。

1 2 3 4 5 6 7 8 9 10 11 12 13 |
(async function() { while(true) { const results = await Promise.all([ bitflyer.fetchTicker ('BTC/JPY'), ]) const bitflyerTicker = results[0] console.log ('bitflyerの買いは' + bitflyerTicker.ask + '円') console.log ('bitflyerの売りは' + bitflyerTicker.bid + '円') await stopTerm (interval) } })(); |
全体をasync function()とasync関数にします。async関数の中でのみawaitを使うことができます。
awaitで処理される内容は、処理が完了するまで次の処理へ進みません。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(async function() { while(true) { const results = await Promise.all([ bitflyer.fetchTicker ('BTC/JPY'), ]) const bitflyerTicker = results[0] console.log ('bitflyerの買いは' + bitflyerTicker.ask + '円') console.log ('bitflyerの売りは' + bitflyerTicker.bid + '円') await stopTerm (interval) } })(); |
次に通常通りの、いつ処理が終了するのか関係なく処理していきます。同期処理です。
1 |
await stopTerm (interval) |
最後にawait sleep (interval)でstopTerm関数の処理が終了するまで待ちます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
'use strict'; const ccxt = require ('ccxt'); const bitflyer = new ccxt.bitflyer (); const interval = 2000 const stopTerm = (timer) => { return new Promise((resolve, reject) => { setTimeout( () => { resolve () }, timer) }) } (async function() { while(true) { const results = await Promise.all([ bitflyer.fetchTicker ('BTC/JPY'), ]) const bitflyerTicker = results[0] console.log ('bitflyerの買いは' + bitflyerTicker.ask + '円') console.log ('bitflyerの売りは' + bitflyerTicker.bid + '円') await stopTerm (interval) } })(); |
asyncの外ではawaitは使えません。await stopTerm()で処理をasyncの外側で行うためにnew Promise()と呼ばれる別の非同期処理メソッドを使っています。
resolve()とは処理が成功した際に呼び出します。
reject()とは処理が失敗した際に呼び出します。(このコードでは書いてません)
もっともお得な組み合わせで価格差を出力して見よう
ここまでで処理の基本が理解できたと思うので、実際に先ほどのコードに少し手を加えて最高値の買い価格と、最安値の売り価格を見つけて差益を出してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
'use strict'; const ccxt = require ('ccxt'); const bitbank = new ccxt.bitbank (); const bitflyer = new ccxt.bitflyer (); const coincheck = new ccxt.coincheck (); const liquid = new ccxt.liquid (); const zaif = new ccxt.zaif (); const interval = 2000 const stopTerm = (timer) => { return new Promise((resolve, reject) => { setTimeout( () => { resolve () }, timer) }) } (async function() { while(true) { const results = await Promise.all([ bitbank.fetchTicker ('BTC/JPY'), bitflyer.fetchTicker ('BTC/JPY'), coincheck.fetchTicker ('BTC/JPY'), liquid.fetchTicker ('BTC/JPY'), zaif.fetchTicker ('BTC/JPY'), ]) let askBest = 0 let bidBest = 1000000000 let askIndex let bidIndex for( let i in results ) { if( results[i].ask > askBest ){ askBest = results[i].ask, askIndex = i } if( results[i].bid < bidBest ){ bidBest = results[i].bid, bidIndex = i } } results[0].name = 'bitbank' results[1].name = 'bitflyer' results[2].name = 'coincheck' results[3].name = 'liquid' results[4].name = 'zaif' console.log ('買いの最高値は' + results[askIndex].name + 'の' + askBest + '円') console.log ('売りの最高値は' + results[bidIndex].name + 'の' + bidBest + '円') console.log ( results[bidIndex].name + 'で買い、' + results[askIndex].name + 'で売った場合の粗利益は' + ( askBest - bidBest ) + '円です' ) await stopTerm (interval) } })(); |
このコードでは、単純にccxtで呼び出す取引所の数を5種類にまで増やしました。
fetchTicker(板情報の取得)もそれぞれ5回を行います。
しかし、5回ともデータを取得できるタイミングは異なりますのでawait Promise.all()メソッド内で非同期処理を待ち、それから価格を出力しています。以下が結果です。
1 2 3 4 |
$ node abitrageSample2.js 買いの最高値はzaifの1018595円 売りの最高値はbitbankの1016489円 bitbankで買い、zaifで売った場合の粗利益は2106円です |
およそ2秒毎に呼び出されるようになっています。
for文で最高値と最安値を取得しつつresults配列の何番目が該当するのかaskIndex、bidIndexで格納しておきます。
1 2 3 4 |
for( let i in results ) { if( results[i].ask > askBest ){ askBest = results[i].ask, askIndex = i } if( results[i].bid < bidBest ){ bidBest = results[i].bid, bidIndex = i } } |
fetchTickerの各取引所のデータを保有しているresults配列に、{ name: 取引所の名前 }のプロパティを追加します。
1 2 3 4 5 |
results[0].name = 'bitbank' results[1].name = 'bitflyer' results[2].name = 'coincheck' results[3].name = 'liquid' results[4].name = 'zaif' |
例えばresults[0]の中身は{ fetchTickerデータ, name: bitbank }のようになっており、nameキーと取引所の名前が追加されています。
そして結果を出力しました。
1 |
console.log ( results[bidIndex].name + 'で買い、' + results[askIndex].name + 'で売った場合の粗利益は' + ( askBest - bidBest ) + '円です' ) |
もう少し実践向けに拡張性のあるコード例
※あくまで初心者層を想定して公開しています。より良いコードは知り合いのエンジニアか質問サービスか何かでコードのレビューを尋ねて見てください。
先ほどのコードでもアービトラージの仕組みを作っていくこともできますが、比較する取引所が増えると面倒です。
先の項では足し算式にccxtライブラリを増やして、処理をしています。
しかし、実際に注文などを交える場合は、apiキーを取得して引っ張ってきたり、取引所名を色々な場所から引っ張ってきたり、データの流れがごちゃごちゃしていきます。
ですので一つにデータをまとめる形で処理した場合のあくまで一例として下のコードを作ってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
'use strict'; const ccxt = require ('ccxt'); // 実際に発注をかける際に必要なapiキーですが、apiキーが必要な場合はconfigファイルを作成してから読み込もう // const config = require('./config'); const interval = 2000 //targetsにccxtに対応している取引所名のプロパティを増やせば比較対象も増やせます const targets = [ {'name': 'bitbank'}, {'name': 'bitflyer'}, {'name': 'coincheck'}, {'name': 'liquid'}, {'name': 'zaif'} ] for( let i in targets ){ //evalはtargets.namaeの取引所名が文字列として格納してあるためそのままではスクリプトとして使えないためのeval targets[i]['ccxt'] = eval('new ccxt.' + targets[i].name + '()') } const stopTerm = (timer) => { return new Promise( (resolve, reject) => { setTimeout( () => { resolve() }, timer) }) } (async function () { while (true) { await Promise.all( targets.map(async target => { //配列の中身を順番に取り出して非同期処理を行うためにmapを採用 target['ticker'] = await target['ccxt'].fetchTicker('BTC/JPY') })) let askPrice = [] let bidPrice = [] let askBest = 0 let askExchange let bidBest = 10000000 let bidExchange for( let i in targets ) { askPrice[i] = targets[i].ticker.ask bidPrice[i] = targets[i].ticker.bid if(askPrice[i] > askBest){ askBest = askPrice[i] askExchange = targets[i].name } if(bidPrice[i] < bidBest){ bidBest = bidPrice[i] bidExchange = targets[i].name } } console.log ('最高値は' + askExchange + 'の' + askBest + '円') console.log ('最安値は' + bidExchange + 'の' + bidBest + '円') console.log ( bidExchange + 'で買って' + askExchange + 'で売れば' + (askBest - bidBest) + '円の粗利になります') console.log ('-------------------*') await stopTerm (interval) } }) (); |
そのままコピペして出力すると以下のような結果が帰ってきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ node abitrage.js 最高値はzaifの1011850円 最安値はliquidの1008661.692円 liquidで買ってzaifで売れば3188.307999999961円の粗利になります -------------------* 最高値はzaifの1011820円 最安値はbitbankの1008796円 bitbankで買ってzaifで売れば3024円の粗利になります -------------------* 最高値はzaifの1011875円 最安値はbitbankの1008796円 bitbankで買ってzaifで売れば3079円の粗利になります -------------------* 最高値はzaifの1011860円 最安値はbitbankの1008944円 bitbankで買ってzaifで売れば2916円の粗利になります -------------------* |
targets配列の中にオブジェクトを作り、targetsをバケツリレーのようにデータを追加しながら回していきました。
targets以降の処理では実際にtargetsのオブジェクトのプロパティに、キーと値を追加していきどこからでもデータを引っ張りやすくしています。
コードではccxtキーとその値、tickerキーをその値を追加していって必要に応じて引っ張っています。
また、targetsに別の取引所を追加するだけで比較対象を増やせるので先のコードよりも拡張性は高いと言えます。
JavaScript(node.js)を使った仮想通貨アービトラージ(裁定取引)のやり方 まとめ

非同期処理ならnodeJSが得意とすることです。この点においては他の自動売買で人気なPythonにも引けを取らないかなと思います。
この記事を読んでccxtのことがよく分からなかった人はまずはccxtにフォーカスしてAPIを使って注文を行うことを解説した前記事である【 JavaScript(node.js) 】 bitFlyerからAPIで売買注文を入れる方法 【コピペ】を読んでみてください。
最近は自動売買botをプログラミング知識なしでもできるWebサービスのQUOREAなんかがあります。
QUOREA内で自作botを作って人気になれば十分なお小遣いは得られるので興味があればQUOREA(クオレア)とは 実際に使って理解したメリットとデメリットを読んでみてください。
良かったらシェアしてね♪ /