Tsurumaru Blog

IT技術系ブログ

BTCはアービトラージ余地があるのか(2)

前回、BTC/USDマーケットの取引量上位6取引所の取引状況をリアルタイムに観察し、アービトラージ(最安値の取引所で買い、直後に最高値の取引所で売って差益を得ること)が可能なのかを考察しました。

ところが、世界最大取引高を誇るBinanceが法定通貨(USD, JPYなどの国などが発行する通貨)を扱っていなかったため、ドルベッグ仮想通貨であるUSDTとの通貨ペアであるBTC/USDTをBTC/USDに含めて評価していました。

そんな中、先日このUSDTに対する以下のような報道がなされました。この報道後、USDTは最大3%程度下落したようですが、現在では約1%安程度まで戻している模様です。

ブルームバーグ:仮想通貨急落、時価総額100億ドル消失-テザーの損失隠ぺい疑惑で

今後のUSDT価格がどのように変化するかは不明ですが、3%の乖離が存在するとすれば、完全なドルベッグとして捉えることはできないため、USDTの価格差も反映する必要があると考え調査したところ、KrakenがUSDT/USDのリアルタイムレートを配信していることが判りました。

Kraken:USDT/USDリアルタイムチャート

そこで今回は、BinanceのBTC/USDT価格にUSDT/USDの価格を乗じることで、BTC/USDを生成することにしました。また、これにより他の取引所との価格乖離が解消したように見受けられます。

f:id:tsurumaru:20190430151507g:plain
BTC/USDマーケットリアルタイム比較

結果

  • 2019/04/30 15:00 JST
  • ワークシート下部にKrakenの配信するUSDT/USDのリアルタイム配信項目を追加し、BTC/USDTの最終売買価格と気配値に乗じています。
  • Bitfinexの価格帯が他の取引所と乖離していますが、現在のところ原因は不明です(実際の取引所でも同一価格を提示していました)。
  • 結果として、6%弱のアービトラージ差益が存在していることが表示されています。

各取引所の配信するマーケットレート配信をExcel上でリアルタイムに分析することで、レート変換なども容易に行えるため、各取引所間のゆがみなど、仮想通貨マーケットの側面を様々な視点で観察することが可能になるのではないでしょうか。

補足

  • 全てのリアルタイムマーケットレートは、各取引所のPublic APIチャネルから取得しています。
  • マーケットレート集配信、Excel連携は弊社製品 Feedexを使用しています。興味のある方はこちらよりお問い合わせ下さい。

BTCにアービトラージ余地(裁定機会)はあるのか?

先日、BitwiseがSECにBTC取引所における取引の実体をレポートとしたとの情報がありましたが、BitwiseがSECへ暗号通貨市場の現状を報告、取引量の95%が「フェイク」- BTCN このレポートによれば正しい(と思われる)取引高を公開している取引所は以下の通りでした。

順位 取引所名 取引高
1 Binance $110M
2 Bitfinex $38M
3 Kraken $31M
4 Bitstamp $31M
5 Coinbase $27M
6 bitFlyer $14M
7 GEMINI $8M
8 itBit $6M
9 Bittrex $5M
10 Poloniex $1.4M

となれば、各取引所の取引実体はどのようになっているのか知りたくなります。そこで、Excelで取引所のリアルタイム状況を比較してみました。

f:id:tsurumaru:20190419132652g:plain
BTCRealtimeMarket

  • 取引所はレポートにあった上位6取引所とした。
  • マーケットレートは各取引所から直接リアルタイムで取得している(更新頻度が遅いのはExcelの最短更新期間が2秒のため)。
  • 最も取引高が多いと思われるBTC/USDペアを選択。
  • 1位のBinanceは法定通貨の取り扱いがないので、USDのベッグ仮想通貨であるUSDTを選択。
  • 計測実施日時は2019/04/19 13:00 JST
  • 各取引所のBest Ask/Bidに手数料率を加算

結果

表の最下部に、(売最安気配値 + 手数料) - (買最高気配値 - 手数料) の差のリアルタイム計算結果を表示していますが、常に0.5%ほどのアービトラージ利益が存在するように見えます。実際にはUSDTがドルベッグとはいえ実体はどうなのか。取引所間送金コストやドル円トランスファーコスト、送金時間など、確認しなければならないパラメーターは少なくないですが、少なくともある程度の裁定機会はありそうだという結果が得られました。

補足

Excelでのリアルタイム状況にはFeedexを使用しています。

APIとライブラリとフレームワークは何が違うのか?

要約すると

フレームワーク ⊃ ライブラリ ⊃ API

API (Application Programming Interface)

アプリケーションから呼び出されるインターフェースそのものを指す。

ライブラリ (Library)

非公開部分と公開部分からなる。公開部分はAPIと呼ばれる。

フレームワーク (Framework)

ソフトウェアを開発するための枠組み。ライブラリなどに加え、コンパイラなどの開発ツールなども含む。

解説

API と ライブラリの違い

Web APIに代表されるAPIですが、WindowsのWin32 APILinuxAPIもあります。OSのAPIシステムコールとも呼ばれます。つまり、APIとはアプリケーションを開発する際に提供される「サービス」です。
ライブラリは、クラスや関数、メッセージ文字列などのデータを、ライブラリという単位にまとめた「データ提供の形式」です。ライブラリのうち、外部に公開され、他のアプリケーションからも利用可能な部分はAPIと呼ばれます。

フレームワーク」という用語の定義

フレームワーク」という用語の定義には二つの側面があります。

  • ソフトウェア開発の枠組みとしての「フレームワーク
    ソフトウェア開発の枠組みとしての「フレームワーク」は、ライブラリに加え、コンパイラデバッグツール、ライブラリ(パッケージと呼ばれる場合もある)管理ツールなどを含んだ、大きなまとまりを指します。
  • ライブラリとの対比としての「フレームワーク
    PythonやC言語の標準ライブラリに代表されるライブラリは、それらを呼び出す上位のライブラリを開発することは可能ですが、ライブラリそのものを拡張することを意識して設計されていません。それに対して.NET Frameworkの提供する基本クラスライブラリ(Base Class Library - BCL)は、あたかも基本クラスライブラリが機能拡張されたかのように、機能を追加することができます(hot spot/frozen spot)。

これらのことから「フレームワーク」とは、APIやライブラリを包含する上位概念を示す用語であると言えます。

まとめ

これらの混乱は、.NET Frameworkが「ソフトウェア開発フレームワーク」であり「クラスライブラリ」を提供し「APIを提供」しているが、Angularは「フロントエンドWebアプリケーションフレームワーク」で「クラスライブラリ」を提供するが「APIを提供」しているわけではない、などの状況が招いたものではないかと感じます。APIやライブラリは昔からあり、対象も限定的な用語ですが「フレームワーク」のような比較的新しい用語は非常に大きな包括的概念を含むため、その意味を的確かつ平易に伝えることは難しいなと、この記事を書きながら感じた次第です。

おまけ

変遷する境目と歴史

OS API(System Call)は、そもそも異なるハードウェアで同一のソフトウェアを動作させるために導入された仕組みですが、CP/MMS-DOSは機能の呼び出し、入出力が機械語ベースで定義されていたため、ライブラリは不要でした(API≠ライブラリ)。また、PCのBIOSx86ベースのインターフェースであるため、API≠ライブラリであると言えます。 一方、Unix/LinuxWindowsに関してはAPI呼び出しのためには、システムライブラリをリンクする必要ががあるため、現在一般的に使用されているOSではAPI=ライブラリであると言えるでしょう。
一方、フレームワークですが、同じような時代にもApple ][Wizardlyの開発に使用されたUCSD Pascalのような、フレームワークと呼べるようなシステムは存在しましたが、Wikipediaでは「処理系」として記述されています。Web全盛以前は、ライブラリ=比較的小さなもの、フレームワーク=巨大なライブラリの集まりのような印象だったと記憶していますが、Web全盛以降は、アプリケーション開発のエコシステムをフレームワークと呼ぶように言葉の定義が変遷していったと感じます。
これらのことから、それぞれの用語は、歴史と共に変遷してきたと言えるのではないでしょうか。

ソフトウェアフレームワークとアプリケーションフレームワーク

Wikipediaを見ていて、これら二つの表記に「揺れ」があると感じました。用語本来の定義であればソフトウェア⊃アプリケーションなので、ソフトウェアフレームワークはアプリケーションフレームワークを包含する用語として用いられているべきとも思われますが、どうやらそうでもないようです。詳しい方がいらっしゃいましたら、教えて頂きたいなと思います。

.NET Frameworkランタイムは「フレームワーク」なのか

Windowsには.NET Frameworkのランタイムが標準でインストールされていますが、実はランタイムと言いながらコンパイラも含まれていたりするので、Visual Studioが無くてもアプリケーションのビルドができたりするので、フレームワークと呼べる気がします。

BitFlyerDotNetで約定通知をLINEに送信してみる

BitFlyerDotNetNuGetに公開

BitFlyerDotNet.LightningAPIをNuGetに公開したので、LINE Notifyと連携してみたいと思います。なお、サンプル作成にあたっては、以下のページを参考にさせて頂きました。ありがとうございます。
Web備忘録 LINE Notifyで通知を送信する方法

注文が執行されたらLINEに通知する

ブラウザの注文画面から行った注文が約定したら、LINE Notifyに通知します。 大まかな処理の流れは以下のようになります。

  • 1分毎にポーリングしながらアクティブな注文一覧を取得する。
  • 前回取得した注文一覧と比較して、非アクティブになった注文を抽出する。
  • 抽出された注文毎に約定情報を取得する。
    • 注文がキャンセルされた場合や有効期間が切れた注文は注文一覧に残らず削除されるため、約定情報を取得して実際に約定したかを確かめる必要があります。
  • 約定情報が存在した場合には、メッセージを生成し LINE Notify に通知する。

サンプルコードの使い方

  • Visual Studio 等でコンソールアプリケーションプロジェクトを作成する。
    • .NET Core 2.0/2.1 推奨
    • .NET Framework 4.7x や Mono でも動作すると思われます。
  • NuGet から BitFlyerDotNet.LightningApi を追加する。
  • サンプルコードを貼り付ける。
    • namespace は適宜変更して下さい。
  • ビルドして実行する。
  • Enterキーを押すと終了します。

注意点

  • あくまでサンプルコードです。動作は保証しません。またサンプルコードを利用することで万が一損害が発生しても、当方は一切の責任を負いません。
  • LINE Notify Token, bitFlyer API Key, API Secret の管理はくれぐれも厳重に。
    • サンプルではソースコード上に保存しないよう、あえて毎回入力するようにしています。
  • 親注文(特殊注文)には対応していません。
  • ポーリング周期内(設定は1分)に、注文と約定が行われた場合、通知されない可能性があります。
  • LINE Notify API呼び出しには数秒かかるため、ポーリング周期をあまり短くしすぎると、複数処理が平行して動作してしまう可能性があります。

サンプルコード

//==============================================================================
// Copyright (c) 2017-2018 Fiats Inc. All rights reserved.
// https://www.fiats.asia/
//

using System;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using BitFlyerDotNet.LightningApi; 

namespace LineNotifySample
{
    class Program
    {
        const string LineNotifyUri = "https://notify-api.line.me/api/notify";
        const BfProductCode ProductCode = BfProductCode.FXBTCJPY;
        static readonly TimeSpan PollingInterval = TimeSpan.FromMinutes(1);

        static HttpClient _lnClient;
        static BitFlyerClient _bfClient;
        static Timer _pollingTimer;
        static BfChildOrder[] _lastActiveOrders = new BfChildOrder[0];

        static void Main(string[] args)
        {
            // Create LINE Notify client
            Console.Write("LINE Notify access Token :"); var token = Console.ReadLine();
            _lnClient = new HttpClient();
            _lnClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

            // Create bitFlyer Client in private mode
            Console.Write("bitFlyer API Key :"); var apiKey = Console.ReadLine();
            Console.Write("bitFlyer API Secret :"); var apiSecret = Console.ReadLine();
            _bfClient = new BitFlyerClient(apiKey, apiSecret);

            // Create monitor
            _pollingTimer = new Timer(OnTimerElapsed, null, TimeSpan.Zero, PollingInterval);

            // Waiting threads
            Console.WriteLine("Hit enter key to exit.");
            Console.ReadLine();

            _lnClient.Dispose();
        }

        static void OnTimerElapsed(object _)
        {
            // Get active child orders
            var resp = _bfClient.GetChildOrders(ProductCode, BfOrderState.Active);
            if (resp.IsErrorOrEmpty)
            {
                if (resp.IsError)
                {
                    Console.WriteLine("{0} {1}", DateTime.Now, resp.ErrorMessage);
                }
                return;
            }

            // Get not activated child orders
            var activeOrders = resp.GetResult();
            var closedOrders = _lastActiveOrders.Except(activeOrders);
            try
            {
                if (closedOrders.Count() == 0)
                {
                    return;
                }

                var messages = new List<string>();
                foreach (var order in closedOrders)
                {
                    // Get child order execution results
                    var execResp = _bfClient.GetPrivateExecutionsByAcceptanceId(ProductCode, order.ChildOrderAcceptanceId);
                    if (execResp.IsErrorOrEmpty)
                    {
                        continue;
                    }

                    // Create notify message
                    foreach (var exec in execResp.GetResult())
                    {
                        messages.Add(string.Format("{0} {1} {2} {3}", exec.ExecutedTime.ToLocalTime(), exec.Side, exec.Size, exec.Price));
                    }
                }
                if (messages.Count == 0)
                {
                    return;
                }

                // Request LINE Notify API
                var content = new FormUrlEncodedContent(new Dictionary<string, string> { { "message", string.Join("\n", messages) } });
                var result = _lnClient.PostAsync(LineNotifyUri, content).Result;
                if (result.StatusCode != System.Net.HttpStatusCode.OK)
                {
                    Console.WriteLine("{0} LINE Notify returned = {1}", DateTime.Now, result.StatusCode);
                    return;
                }
            }
            finally
            {
                _lastActiveOrders = activeOrders;
            }
        }
    }
}

最後に

.NET Standard対応なので、.NET CoreやWindows10 IoT/UWPなどが動作する、Raspberry Piなどで常時稼働させたら面白いかもしれません。また、質問や機能の追加など要望がありましたら、本ブログにコメントをお寄せ下さい。

BitFlyerDotNet.Historicalリリース

GitHubにBitFlyerDotNet.Hitstoricalをリリースしました。

  • bitFlyerから取得したリアルタイムデータからチャートを生成するためのクラスライブラリです。
  • リアルタイムAPI(WebSocket)からは最新の執行データをリアルタイムで取得します。
  • 4本値(ローソクなどで使用)開始時は、現在から直近過去の期間開始(たとえば、13:21:15に1分足を開始した場合には、現在から13:21:00)までの執行データをREST APIで取得します。
  • それ以前のデータは(この場合は、13:20:00から過去)、Cryptowatchから取得します。
  • 取得したデータは、次回起動時に利用するため、データベースに保存されます。
  • 最新の4本値は更新の度にイベントを発生させるため、リアルタイムチャートに利用することができます。

デモ動画

以下のサンプルアプリケーションは、BitFlyerDotNet.Historicalを使用し実装したものです。FXBTC/JPY 1分足をリアルタイムで描画していますが、UIを除けば、200ステップ以下で実装することができます。


BitFlyerDotNet Charting Demo

SFDTickerサンプルを更新しました。

BitFlyerDotNet.Tradingリリース

BitFlyerDotNet.Tradingとは

bitFlyer Lightning APIの.NETラッパーである BitFlyerDotNet.LighningAPI は、 REST API を単純に .NETから利用可能にしたものですが、取引アプリケーションを作成しようとした場合、API 呼び出し以外にも以下のような様々な処理を追加する必要があるため、それらを追加した BitFlyerDotNet.Trading をリリースしました。

  • 二重発注防止と注文送信時のエラー処理

    • 注文は送信完了の時点で確定し、確定後にサーバーからの注文情報を取得することで確認することができますが、サーバービジー時には、注文送信に失敗したり、注文の確定から注文情報を取得可能になるまでに相応の時間がかかったりする場合があります。BitFlyerDotNet.Trading では、非同期処理とエラー処理、リトライ処理を適切に行うことにより、注文を確実に送信するとともに、注文状態を適切に把握することで、二重注文を未然に防止することができます。
  • 注文約定確認の高速化とサーバー負荷の軽減

    • 注文が約定したことを確認するためには、getchildorders API を利用して注文の状態を取得する必要がありますが、可能な限り迅速に状態を把握するためには、REST API を連続してアクセス(ポーリング)する必要があるため、サーバーに過大な負荷をかけてしまう可能性があります。BitFlyerDotNet.Trading では、Realtime API で約定した取引を監視することで、サーバーに高負荷を与えることなく、実際の約定とほぼ同時に約定を確認することができます。
  • 確実なキャンセル処理と状態管理

    • 注文をキャンセルする場合には、キャンセル API を使用してキャンセル注文を行いますが、サーバービジーの場合など、リトライを行っても数秒〜数十秒にわたって注文を受け付けられない場合があります。このような場合には、キャンセル注文を送信するのではなく、注文送信自身を中止する必要があります。また、未約定の注文をキャンセルした場合には、注文一覧に注文記録が残らないため、自前で状態管理を行って、確実にキャンセルが行われたかを管理する必要があります。
BitFlyerDotNet.Trading での注文コード例
// FX_BTC_JPY で成行買い注文を実行する。
var account = new TradingAccount(BfProductCode.FXBTCJPY);
account.Login(apiKey, apiSecret);

var order = account.CreateMarketPriceOrder(BfTradeSide.Buy, 0.01);
account.PlaceOrder(order);
リアルタイム情報を利用した注文のコード例
  • リアルタイム API の統合と特殊注文
    • リアルタイムティッカー情報やリアルタイム約定情報を統合しているため、現在価格での注文などを簡単に行うことができます。
// 現在の買気配価格で買注文を出し、約定した時点で ¥5,000 幅で逆指値売(利益確定)注文を発注する。
// 注文には親注文(IFD)を使用する。
var order = account.CreateIFD(
    new LimitPriceOrder(BfProductCode.FXBTCJPY, BfTradeSide.Buy, 0.01, account.BidPrice),
    new StopLimitOrder(BfProductCode.FXBTCJPY, BfTradeSide.Sell, 0.01, account.BidPrice + 5000.0)
);
account.PlaceOrder(order);

BitFlyerDotNetからLightning/Realtime APIを使ってみる

BitFlyerDotNetのRealtime API

BitFlyerDotNetでは、Reactive Extensionsを使用してRealtime APIをラップしています。Reactive Extensions (通称Rx)は、マイクロソフト謹製のC#/Linqなライブラリで、ちょっとクセがあり、なれるまでに若干時間がかかりますが、リアルタイム配信と非常に相性が良いです。

コード例

// FX_BTC_JPYのリアルタイム約定情報を取得して表示する。
var factory = new BitFlyerRealtimeSourceFactory();
factory.GetExecutionSource(BfProductCode.FXBTCJPY).Subscribe(exec =>
{
    Console.WriteLine("{0} {1} {2} {3} {4} {5}",
        exec.ExecutionId,
        exec.Side,
        exec.Price,
        exec.Size,
        exec.ExecutedTime.ToLocalTime(),
        exec.ChildOrderAcceptanceId);
 });
factory.StartAllExecutionSources();

実行結果

563172040 Sell 646805 0.01 2018/11/16 12:09:16 JRF20181116-030916-521674
563172041 Sell 646805 0.01 2018/11/16 12:09:16 JRF20181116-030913-895733
563172042 Sell 646805 0.1 2018/11/16 12:09:16 JRF20181116-030916-160381
563172043 Sell 646805 0.11 2018/11/16 12:09:16 JRF20181116-030916-160381
563172044 Sell 646803 0.01 2018/11/16 12:09:16 JRF20181116-030916-160381
563172045 Sell 646803 0.01 2018/11/16 12:09:16 JRF20181116-030916-160381
563172046 Sell 646801 0.081 2018/11/16 12:09:16 JRF20181116-030916-160381
563172047 Buy 646814 0.17714894 2018/11/16 12:09:16 JRF20181116-030916-095133

コード例

// FX_BTC_JPYのリアルタイムTikcer情報を取得して表示する。
factory.GetTickerSource(BfProductCode.FXBTCJPY).Subscribe(ticker =>
{
    Console.WriteLine("{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11}",
        ticker.ProductCode,
        ticker.Timestamp.ToLocalTime(),
        ticker.TickId,
        ticker.BestBid,
        ticker.BestAsk,
        ticker.BestBidSize,
        ticker.BestAskSize,
        ticker.TotalBidDepth,
        ticker.TotalAskDepth,
        ticker.LastTradedPrice,
        ticker.Last24HoursVolume,
        ticker.VolumeByProduct);
});

実行結果

FX_BTC_JPY 2018/11/16 12:21:02 153098950 647342 647350 0.0032 0.47 9721.84809004 10288.60099151 647342 689153.31360529 689153.31360529
FX_BTC_JPY 2018/11/16 12:21:02 153098971 647322 647389 0.1032 0.09000012 9720.92809004 10287.08947385 647389 689154.51360529 689154.5136
0529
FX_BTC_JPY 2018/11/16 12:21:02 153099021 647535 647538 1.63255782 0.05 9725.33496188 10286.7555318 647535 689156.58104747 689156.5810474
7
FX_BTC_JPY 2018/11/16 12:21:02 153099075 647535 647538 1.14455781 0.05 9726.71026174 10276.68630791 647535 689155.88904748 689155.889047
48
FX_BTC_JPY 2018/11/16 12:21:03 153099133 647375 647381 0.29 0.07644219 9726.89110026 10280.46026099 647375 689157.31360529 689157.313605
29
FX_BTC_JPY 2018/11/16 12:21:03 153099148 647399 647538 1.04355781 0.108 9729.03465807 10280.5838188 647381 689157.21004748 689157.210047
48
FX_BTC_JPY 2018/11/16 12:21:03 153099153 647399 647549 1.04355781 0.0565129 9728.98461868 10280.3938188 647549 689157.41004748 689157.41
004748
FX_BTC_JPY 2018/11/16 12:21:03 153099168 647399 647571 0.92873781 0.00354524 9728.86479868 10280.13658906 647571 689157.82486748 689157.
82486748

これらのリアルタイムAPIを利用することで、ストリーミング注文(成行注文ではなく、現在の価格で指値注文する注文方式)を行ったり、自動取引を行ったりすることができるようになります。