BETA

【Python】bitflyer の API を使って約定一覧を取得してみる

投稿日:2018-12-11
最終更新:2018-12-14
※この記事は外部サイト(https://www.doraxdora.com/blog/2018/03/16/...)からのクロス投稿です

今回は約定一覧を取得して表示してみます。

プログラムは前回のものを流用します。

【Python】bitflyer の Private API を使って資産情報を取得

プロジェクトのリファクタリング

少しごちゃってきたので整理します。

BfTool
│ BfApi.py
│ BfTool.py(Sampl.py)
├─common
│ Constants.py
├─static
│ ├─css
│ │ style.css
│ └─js
│ script.js
└─templates
Main.html(index.html)

画面の修正

Main.html
<!DOCTYPE html>  
<html>  
   <head>  
      <title>{{ title }}</title>  
      <link rel="stylesheet" href="{{ static_url('css/style.css') }}"/>  
      <script type="text/javascript" src="{{ static_url('js/script.js') }}"></script>  
      <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>  
   </head>  
   <body onload="initialize();">  
      <div id="container">  
         <div style="clear:both; padding-top:10px;">  
            <div class="entry_title">  
               <div class="pull_left">資産情報</div>  
               <div class="pull_right"><input type="button" value="更新" onclick="updateBalance();" /></div>  
            </div>  
            <table id="balanceTable">  
               <tr><th>円</th><td id="jpy"></td><th>イーサクラシック</th><td id="etc"></td></tr>  
               <tr><th>ビットコイン</th><td id="btc"></td><th>ライトコイン</th><td id="ltc"></td></tr>  
               <tr><th>ビットコインキャッシュ</th><td id="bch"></td><th>モナコイン</th><td id="mona"></td></tr>  
               <tr><th>イーサ</th><td id="eth"></td><th>リスク</th><td id="lsk"></td></tr>  
            </table>  
         </div>  
         <div style="clear:both; padding-top:10px;">  
            <div class="entry_title">ティッカー情報</div>  
            <table id="tickerTable">  
               <tr class="header">  
                  <th style="width:5%">種別</th>  
                  <th style="width:10%">時刻</th>  
                  <th style="width:5%">ID</th>  
                  <th style="width:5%">売値</th>  
                  <th style="width:5%">買値</th>  
                  <th style="width:10%">売り数量</th>  
                  <th style="width:10%">買い数量</th>  
                  <th style="width:10%">売り注文総数</th>  
                  <th style="width:10%">買い注文総数</th>  
                  <th style="width:10%">最終取引価格</th>  
                  <th style="width:10%">出来高</th>  
                  <th style="width:10%">価格単位出来高</th>  
               </tr>  
            </table>  
         </div>  
         <div style="clear:both; padding-top:10px;">  
            <div class="entry_title">  
               <div class="pull_left">約定一覧</div>  
               <div class="pull_right"><input type="button" value="更新" onclick="updateExecution();" /></div>  
            </div>  
            <div class="table_container">  
               <table id="executionTable">  
                  <tr class="header">  
                     <th style="width:10%">ID</th>  
                     <th style="width:10%">売買</th>  
                     <th style="width:10%">値段</th>  
                     <th style="width:10%">数量</th>  
                     <th style="width:10%">約定日時</th>  
                     <th style="width:20%">注文ID</th>  
                     <th style="width:10%">委任</th>  
                     <th style="width:20%">受付ID</th>  
                  </tr>  
               </table>  
            </div>  
         </div>  
      </div>  
   </body>  
</html>
style.css
body {  
   font-family:"MS Pゴシック","MS PGothic",sans-serif;  
   width: 100%;  
   margin: 0 auto;  
}  
div {  
   margin:20px;  
}  
div#container{  
   width: 100%;  
}  
table {  
   width:95%;  
   border: 1px solid #ccc;  
   border-collapse:collapse;  
}  
th {  
   text-align:center;  
   background-color:#404040;  
   color:#ffffff;  
   width: 100px;  
   height: 25px;  
   border: 1px solid #ccc;  
}  
td {  
   padding-left:5px;  
   width: 200px;  
   height: 20px;  
   border: 1px solid #ccc;  
}  
#balanceTable th {  
   padding-left:5px;  
    text-align: left;  
}  

div.table_container {
margin: 0px;
width:95%;
height: 280px;
overflow-y: scroll;
}

#executionTable {
width: 100%;
}

div.entry_title {
font-size: 18px;
width: 93%;
height: 25px;
padding: 10px;
margin-left: 0px;
border-bottom: 1px solid #316d9c;
border-left: 15px solid #316d9c;
}

div.pull_left {
float:left;
margin: 0px;
}
div.pull_right {
float:right;
margin: 0px;
}

プログラムの修正

定数クラスの作成

Constants.py

Pythonでは、名前付きタプル(namedtuple)というモジュールを使うことによって擬似的にクラスのような使い方ができるようです。

# -*- coding: utf-8 -*-  
"""  
Created on 2018/03/14  
@author: doraxdora  
"""  
from collections import namedtuple  

# 名前付きタプルの定義  
HTTP_TUPLE = namedtuple("HTTP", ("GET", "POST"))  


class Constants:  

    HTTP = HTTP_TUPLE(GET="GET", POST="POST")

APIクラスの作成

Bitflyer の API を利用する処理を別クラスに抜き出して整理しました。

bitApi.py

# -*- coding: utf-8 -*-  
"""  
Created on 2018/03/14  
@author: doraxdora  
"""  

import hashlib  
import hmac  
import json  
import logging  
import requests  
import time  
import urllib  

from common.Constants import Constants  
from pubnub.pnconfiguration import PNConfiguration  
from pubnub.pubnub import PubNub, SubscribeListener  


class BfApi:  
    """  
    Bitflyer API を利用するためのツールクラス  
    """  

    def __init__(self, access_key=None, secret_key=None):  
        self.access_key = access_key  
        self.secret_key = secret_key  
        self.api_url = "https://api.bitflyer.jp"  
        self.pb_config = PNConfiguration()  
        self.pb_config.subscribe_key = "sub-c-52a9ab50-291b-11e5-baaa-0619f8945a4f"  
        self.pb_config.ssl = False  

    def call_pub_nub(self, channels):  
        """  
        pubnub を利用して指定したチャネルからデータを取得  

        :param channels: 接続チャネル  
        :return: リアルタイム配信データ  
        """  

        # pubnubの生成  
        pub_nub = PubNub(self.pb_config)  
        listener = SubscribeListener()  
        pub_nub.add_listener(listener)  

        # チャンネルへ接続要求し接続を待機する  
        pub_nub.subscribe().channels(channels).execute()  
        listener.wait_for_connect()  

        # リアルタイム配信からデータを取得  
        result = listener.wait_for_message_on(channels)  
        data = result.message  

        # チャンネルの接続解除を要求し切断を待機する  
        pub_nub.unsubscribe().channels(channels).execute()  
        listener.wait_for_disconnect()  

        return data  

    def send_req(self, api_path, http_method="GET", timeout=None, **send_params):  
        """  
        Bitflyer Private API を利用してリクエストを送信し  
        取得したデータを JSON 形式で返却します  

        :param api_path: 呼び出す Private API のパス  
        :param http_method: GET/POST  
        :param timeout: 接続タイムアウト時間  
        :param send_params: APIに送信するパラメータ  
        :return: 取得したデータのJSON  
        """  

        url = self.api_url + api_path  
        body = ""  
        auth_header = None  

        if http_method == Constants.HTTP.POST:  
            body = json.dumps(send_params)  
        else:  
            if send_params:  
                body = "?" + urllib.parse.urlencode(send_params)  

        if self.access_key and self.secret_key:  
            access_time = str(time.time())  
            encode_secret_key = str.encode(self.secret_key)  
            encode_text = str.encode(access_time + http_method + api_path + body)  
            access_sign = hmac.new(encode_secret_key, encode_text, hashlib.sha256).hexdigest()  

            auth_header = {  
                'ACCESS-KEY': self.access_key,  
                'ACCESS-TIMESTAMP': access_time,  
                'ACCESS-SIGN': access_sign,  
                'Content-Type': 'application/json'  
            }  

        try:  
            with requests.Session() as s:  
                if auth_header:  
                    s.headers.update(auth_header)  

                if http_method == Constants.HTTP.GET:  
                    response = s.get(url, params=send_params, timeout=timeout)  
                else:  
                    response = s.post(url, data=json.dumps(send_params), timeout=timeout)  
        except requests.RequestException as e:  
            logging.error(e)  
            raise e  

        content = ""  
        if len(response.content) > 0:  
            content = json.loads(response.content.decode("utf-8"))  

        return content

リクエスト処理クラス

メインとなる、画面からのリクエストを受付け、処理をハンドリングするクラス

BfTool.py

# -*- coding: utf-8 -*-  
"""  
Created on 2018/03/14  
@author: doraxdora  
"""  

import json  
import logging  
import os  
import time  
import tornado.ioloop  

from tornado.web import RequestHandler  
from tornado.websocket import WebSocketHandler  
from tornado.options import options  
from BfApi import BfApi  


class MainHandler(RequestHandler):  
    """  
    メイン処理  
    初期画面を表示します.  
    """  

    def initialize(self):  
        logging.info("MainHandler [initialize]")  

    def get(self):  
        logging.info("MainHandler [get]")  

        self.render("Main.html", title="BfTool")  


class SendWebSocket(WebSocketHandler):  
    """  
    WEBソケット通信  
    1秒毎にティッカー情報を画面に配信します  
    """  

    def open(self):  
        logging.info('SendWebSocket [open] IP :' + self.request.remote_ip)  
        self.ioloop = tornado.ioloop.IOLoop.instance()  
        self.send_ticker()  

    def on_close(self):  
        logging.info("SendWebSocket [on_closed]")  

    def check_origin(self, origin):  
        logging.info("SendWebSocket [check_origin]")  

        return True  

    def send_ticker(self):  

        self.ioloop.add_timeout(time.time() + 1, self.send_ticker)  
        if self.ws_connection:  
            api = BfApi(access_key="APIキー", secret_key="Private API キー")  
            data = api.call_pub_nub('lightning_ticker_FX_BTC_JPY')  
            message = json.dumps(data)  
            self.write_message(message)  


class GetBalanceHandler(RequestHandler):  
    """  
    資産情報を取得  
    """  

    def initialize(self):  
        logging.info("GetBalanceHandler [initialize]")  

    def post(self):  
        logging.info("GetBalanceHandler [post]")  

        api = BfApi(access_key="APIキー", secret_key="Private API キー")  
        data = api.send_req(api_path="/v1/me/getbalance")  
        self.write(json.dumps(data, ensure_ascii=False))  


class GetExecutionHandler(RequestHandler):  
    """  
    約定一覧を取得  
    """  

    def initialize(self):  
        logging.info("GetExecutionHandler [initialize]")  

    def post(self):  
        logging.info("GetExecutionHandler [post]")  

        api = BfApi()  
        data = api.send_req(api_path="/v1/me/getexecutions", product_code="FX_BTC_JPY")  
        self.write(json.dumps(data, ensure_ascii=False))  

app = tornado.web.Application([  
    (r"/", MainHandler),  
    (r"/ticker", SendWebSocket),  
    (r"/balance", GetBalanceHandler),  
    (r"/execution", GetExecutionHandler)  
    ],  
    template_path=os.path.join(os.getcwd(), "templates"),  
    static_path=os.path.join(os.getcwd(), "static"),  
    js_path=os.path.join(os.getcwd(), "js"),  
)  

if __name__ == "__main__":  
    options.parse_command_line()  
    app.listen(8888)  
    logging.info("server started")  
    tornado.ioloop.IOLoop.instance().start()  

画面処理スクリプト

script.js
  
/**  
 * 画面読み込み時の処理  
 */  
function initialize() {  

    // 各テーブルに空行を追加しておく  
    addEmptyRow("tickerTable", 10, 12);  
    addEmptyRow("executionTable", 10, 8);  

    // 初期表示時に一度データを取得  
    updateBalance();  
    updateExecution();  

    var connection = new WebSocket('ws://127.0.0.1:8888/ticker');  
    connection.onmessage = function (e) {  
        var data = JSON.parse(e.data.replace( /\\/g , "" ));  

        var table = $("#tickerTable");  

        // 日付け変換  
        var date = new Date(data.timestamp);  
        data.timestamp = date.toLocaleString();  

        // テーブルに追加  
        var tr = document.createElement("tr");  
        $.each(data, function(i, cell){  
            var td = document.createElement("td");  
            td.innerHTML = cell;  
            tr.appendChild(td);  
        });  
        var rows = table.find("tr");  
        if (rows.length > 10) {  
            $("#tickerTable tr:last").remove();  
        }  
        $(tr).insertAfter("#tickerTable tr.header");  
    };  
}  

/**  
 * 空行をテーブルに追加します  
 */  
function addEmptyRow(tableId, rowCount, colCount) {  

    for (i = 0; i < rowCount; i++) {  
        var tr = document.createElement("tr");  
        for (j = 0; j < colCount; j++) {  
            var td = document.createElement("td");  
            tr.appendChild(td);  
        }  
        $(tr).insertAfter("#" + tableId + " tr.header");  
    }  
}  

/**  
 * 資産情報を更新します.  
 */  
function updateBalance() {  

   $.ajax({  
      url: "http://localhost:8888/balance",  
      type: "POST",  
      success: function(jsonResponse) {  
         jsonResponse = jsonResponse.replace( /\\/g , "" );  
         var data = JSON.parse(jsonResponse);  
         for(row in data) {  
             switch (data[row].currency_code) {  
                 case "JPY":  
                     $("#jpy").text(data[row].amount);  
                     break;  
                 case "BTC":  
                     $("#btc").text(data[row].amount);  
                     break;  
                 case "BCH":  
                     $("#bch").text(data[row].amount);  
                     break;  
                 case "ETH":  
                     $("#eth").text(data[row].amount);  
                     break;  
                 case "ETC":  
                     $("#etc").text(data[row].amount);  
                     break;  
                 case "LTC":  
                     $("#ltc").text(data[row].amount);  
                     break;  
                 case "MONA":  
                     $("#mona").text(data[row].amount);  
                     break;  
                 case "LSK":  
                     $("#lsk").text(data[row].amount);  
                     break;  
                 default:  
                     console.log("該当なし");  
                     break;  
             }  
         }  
      },  
      error: function() {  
      }  
   });  
}  

/**  
 * 約定一覧を更新します.  
 */  
function updateExecution() {  
   $.ajax({  
      url: "http://localhost:8888/execution",  
      type: "POST",  
      success: function(jsonResponse) {  
         jsonResponse = jsonResponse.replace( /\\/g , "" );  
         var data = JSON.parse(jsonResponse);  

            var table = $("#executionTable");  
            var rows = table.find("tr");  
            for (index in rows) {  
                if (rows[index].className == ""){  
                    rows[index].remove();  
                }  
            }  

            // テーブルに追加  
            $.each(data, function(i, row){  
                var tr = document.createElement("tr");  
                $.each(row, function(i, cell){  
                    var td = document.createElement("td");  
                    td.innerHTML = cell;  
                    tr.appendChild(td);  
                });  

                table.append(tr);  
            });  

      },  
      error: function() {  
      }  
   });  
}  

起動してみる

結構すっきりしたんじゃないでしょうか。

まとめ

次回は注文の一覧を取得し、Tornadoのテンプレートで JSON形式のデータからうまいことテーブルに変換したいと思います。

ではでは。

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@doraxdoraの技術ブログ 主に Java, C#, Python, Javascript の記事を載せていく予定。

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!