Bitflyer用サーキットブレイカーbotをGAS(Google Apps Script)で作ってみた


注意事項・免責事項

・2021/04/04時点の情報です。記事内のリンク先の情報や取引所等において、基本的には私が利用した上で信用していますが、保証されている物ではありません。本記事の情報の利用により損失や被害が生じた場合、筆者は一切の責任を負うことはできません。またbotを稼働させて生じる損失にも一切の責任を負うことはできません。投資は自己責任でお願いします。
基本的に質問は回答しません。ノーサポートでお願いします。
・noteご利用規約の「禁止事項」に準拠します。
 https://note.mu/terms
・転載の禁止(本noteに記載された文章、プログラムは許可なく公開、転 載、Discord等での共有を禁止します。)

はじめに

初めましてシステムトレーダのJINです。

仮想通貨の裁量・システムトレードについて呟いています。Twitter IDはこちら

概要

4月1日にbitflyerにXRP, XLM, MONAの板取引が追加されました。とっても板が薄くて当日はアビトラが捗ったそうですね、羨ましい。また、これだけ板が薄いとサーキットブレイカーが発生して板寄せにもなっていたようです?(未確認)

この件をTwitter仲間で話していた所、サーキットブレイカー付近に指値を置き続ければ儲かるんじゃないかという案が生まれました。というより3年前のバブルの頃からサーキットブレイカーは何度か起こっていて、その直後は毎回かなりのリバウンドが発生してるので買っておけばほぼ儲かるのは間違いないとは思います。ただあまりに収益機会が少ないので特に手を出すつもりもありませんでした。

今回、その話の中でGASで実装すれば簡単という話が出てきて、私は使った事なかったので興味が沸いてGASで動くbotを作ってみました。これハムとれとかでも使ってますね。確かにGoogleアカウントはほとんどの人が持っているので新たにサーバを建てる必要がなくて敷居が低そうです。

せっかく作ったので公開する事にしました。今後のbotはGASで作れば環境構築が苦手な人にも手が出しやすそうなので、botの収益性は置いておいて、そういうニーズを持った人(GASでbot作って売りたい・買いたい)には結構有益じゃないかと思います。以下、簡単な手順とbotのソースコードになります。ソース自体はJavaScriptですが、GAS上で動かすと普段とは違う制約があるので注意してください。

手順

1. 取引所の開設。

bitflyerの口座がない人は以下のアフィから開設すると1000円分のビットコインが貰えます。


2. botを動かしたいGoogleアカウントのGoogleドライブを開く

画像1


3.  新規ボタンからGASとGoogleスプレッドシートを作成する

画像2


画像3


名前を付ける。GASの方はなんでもいい。スプレッドシートの方はGASのスクリプトで読み込むので下記の例以外にする場合は、後述するソース内の名前を変更する事。

GASのスクリプト名

画像4


スプレッドシート名


画像5

シート名

画像6


5. 以下のソースコードを貼る



6,7行目のyour keyとyour sercretはあなたのAPIキーを入力してください。APIキーの取得方法は以下の記事を参考にしてください。

1行目のyour idは項番3で作ったスプレッドシートのurlに含まれているIDを入力してください。(/dと/editの間)

10~13行目は対象通貨などの設定です。好きな物を入れても構いませんが、XLM/JPYしかテストしていません。(多分動くと思うけど)

var spreadsheet   = SpreadsheetApp.openById('your id');
var sheet         = spreadsheet.getSheetByName('test');
var last_order_id = sheet.getRange("A1").getValue();

// 自分のAPI情報を入力する
var key    = 'your key;
var secret = 'your secret';

// パラメータを設定する
var coin   = 'XLM_JPY'; // 対象の通貨ペアを入力
var size   = 0.1; //  ロットを入力(最小値以下だとエラーになる)
var keta   = 3;  // 小数点の桁数を入力
var side   = 'BUY';

// POSTを使ったリクエストを出す関数(requestType: cancelchildorder/sendchildorder)
function sendPost(requestType,buy_price){

 var timestamp = Date.now().toString();
 var method = 'POST';
 var path = '/v1/me/' + requestType;

 if (requestType == 'cancelchildorder')
 {
   var body = JSON.stringify({
     product_code: coin,
     child_order_acceptance_id: last_order_id,
   });
 } else {
   var body = JSON.stringify({
     product_code: coin,
     child_order_type: "LIMIT",
     side: side,
     price: buy_price,
     size: size
   });

 } 

 var text = timestamp + method + path + body;
 var signature = Utilities.computeHmacSha256Signature(text, secret);
 var sign = signature.reduce(function(str,chr){
   chr = (chr < 0 ? chr + 256 : chr).toString(16);
   return str + (chr.length==1?'0':'') + chr;
 },'');

 var url = 'https://api.bitflyer.jp' + path;
 var options = {
     method: method,
     payload: body, // ← GASの場合、payloadで送る
     headers: {
       'ACCESS-KEY': key,
       'ACCESS-TIMESTAMP': timestamp,
       'ACCESS-SIGN': sign,
       'Content-Type': 'application/json'
     }
 };

 //送信してレスポンス取得
 const response = UrlFetchApp.fetch(url, options)
 if (requestType == 'cancelchildorder'){
   return 0;
 }

 if( response != null ){
   var obj = JSON.parse(response.getContentText());
   Logger.log("child_order_acceptance_id(APIの受付ID): "+ obj.child_order_acceptance_id);
   sheet.getRange("A1").setValue(obj.child_order_acceptance_id);
 }
 
}

function cirutOrder(){

 // スプレッドシートに注文IDが書いてあったらキャンセルする
 if (last_order_id != ''){
   sendPost('cancelchildorder');
 }

 // coinの価格を取得する
 let url_coin     = 'https://api.bitflyer.jp/v1/ticker?product_code=' + coin;
 let res_coin     = UrlFetchApp.fetch(url_coin).getContentText("UTF-8");
 let coin_info    = JSON.parse(res_coin);
 let http_status1 = coin_info['status'];
 let coin_price   = coin_info['ltp'];

 // 取得出来なかったら処理を抜ける
 if (0 > coin_price) {
   return 1;
 }

 // サーキットブレイカーの価格を取得する
 let url_circut_break  = 'https://lightning.bitflyer.com/api/trade/getCircuitBreakInfo?productCode=' + coin + '&v=1';
 let res_circut        = UrlFetchApp.fetch(url_circut_break).getContentText("UTF-8");
 let circut_break_info = JSON.parse(res_circut);
 let http_status       = circut_break_info['status'];
 let circut_sell_price = circut_break_info['data']['upper_limit'];
 let circut_buy_price  = circut_break_info['data']['lower_limit'];

 // 取得出来なかったら処理を抜ける
 if (http_status != 0) {
   return 1;
 }

 // サーキットブレイカーの取得価格の小数点部分を注文用の桁数に修正する
 let ceil_circut_sell_price = Math.ceil(circut_sell_price * Math.pow(10, keta)) / Math.pow(10, keta);
 let ceil_circut_buy_price = Math.ceil(circut_buy_price * Math.pow(10, keta)) / Math.pow(10, keta);

 // サーキットブレイカーの価格で指値注文を出す
 sendPost('sendchildorder',ceil_circut_buy_price);

}

※突貫で作ったのでかなり冗長なコードになってますwでも動くので良し!

※バグがあったらご報告頂ければ修正します!

※XLM/JPYでしかテストしていません!

6.  デプロイする

右上にあるデプロイボタンを押してウェブアプリでデプロイします

権限などの細かい設定はご自分で確認して設定してください。

7. トリガーを追加する

GASには〇〇分間隔でスクリプトを実行するトリガーという機能(LInuxのcronやWindowsのタスクスケジューラーみたいなもの)があります。今回はこれを5分に設定します。つまり5分間の間に価格が変わっても反応しませんし注文も約定してしまいます。無料でスクリプトを実行できる時間が1日1時間と決まっているので、もし途中で使い切った場合、その日はスクリプトは実行できなくなる事に注意してください。このスクリプトは大体2~4secで完了しますので5分でも許容範囲ですが、遅延が激しい場合はもっと大きめにするといいかもしれません。

画像7

トリガー追加ボタンを押す

分ベースに変更して保存ボタンを押す

画像8


8.  実行ログを確認

以下のメニューでログが確認できます。完了になっていればOKです。

画像9


9. bitflyerで約定したら通知するなどの設定をする。

このスクリプトでは約定したかどうか分からないのでbitflyerの方で設定しておきましょう。

おわりに

これで終了です。特に難しいところはないですが、GASは色々と使い方を覚える必要があったり、JavaScriptで書くとは色々と制約がありそうです。(例えばset intervalが使えないとかnodo moduleをimportどうやってやるのか、とかccxtとかも入れられるかどうかよく分からない。)

ただ、AWSのインスタンスを立てたり環境構築が必要ないのはかなり敷居が低くなるんじゃないでしょうか。高頻度じゃないbotなら無料枠でも充分動かせると思います。


以上です。最後まで読んでいただきありがとうございました。


この記事が気に入ったらサポートをしてみませんか?