Tsurumaru Blog

IT技術系ブログ

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などで常時稼働させたら面白いかもしれません。また、質問や機能の追加など要望がありましたら、本ブログにコメントをお寄せ下さい。