【Python】bitflyer の API を使って約定一覧を取得してみる
おはようございます。
注文をする予定だったのですが、
その前に約定や建玉、注文の一覧が必要だろうということで、
今回は約定一覧を取得して表示してみます。
プログラムは前回のものを流用します。
【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> <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="更新" /></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="更新" /></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形式のデータからうまいことテーブルに変換したいと思います。
ではでは。
ディスカッション
コメント一覧
まだ、コメントがありません