【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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | <!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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | body { font-family:"MS Pゴシック","MSPGothic",sans-serif; width:100%; margin:0auto; } div { margin:20px; } div#container{ width:100%; } table { width:95%; border:1pxsolid#ccc; border-collapse:collapse; } th { text-align:center; background-color:#404040; color:#ffffff; width:100px; height:25px; border:1pxsolid#ccc; } td { padding-left:5px; width:200px; height:20px; border:1pxsolid#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:1pxsolid#316d9c; border-left:15pxsolid#316d9c; } div.pull_left { float:left; margin:0px; } div.pull_right { float:right; margin:0px; } |
プログラムの修正
定数クラスの作成
Constants.py
Pythonでは、名前付きタプル(namedtuple)というモジュールを使うことによって擬似的にクラスのような使い方ができるようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # -*- coding: utf-8 -*- """ Created on 2018/03/14 @author: doraxdora """ fromcollectionsimportnamedtuple # 名前付きタプルの定義 HTTP_TUPLE=namedtuple("HTTP",("GET","POST")) classConstants: HTTP=HTTP_TUPLE(GET="GET",POST="POST") |
APIクラスの作成
Bitflyer の API を利用する処理を別クラスに抜き出して整理しました。
bitApi.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | # -*- coding: utf-8 -*- """ Created on 2018/03/14 @author: doraxdora """ importhashlib importhmac importjson importlogging importrequests importtime importurllib fromcommon.Constants importConstants frompubnub.pnconfiguration importPNConfiguration frompubnub.pubnub importPubNub,SubscribeListener classBfApi: """ 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 defcall_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() returndata defsend_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 ifhttp_method==Constants.HTTP.POST: body=json.dumps(send_params) else: ifsend_params: body="?"+urllib.parse.urlencode(send_params) ifself.access_key andself.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: withrequests.Session()ass: ifauth_header: s.headers.update(auth_header) ifhttp_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) exceptrequests.RequestException ase: logging.error(e) raisee content="" iflen(response.content)>0: content=json.loads(response.content.decode("utf-8")) returncontent |
リクエスト処理クラス
メインとなる、画面からのリクエストを受付け、処理をハンドリングするクラス
BfTool.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | # -*- coding: utf-8 -*- """ Created on 2018/03/14 @author: doraxdora """ importjson importlogging importos importtime importtornado.ioloop fromtornado.web importRequestHandler fromtornado.websocket importWebSocketHandler fromtornado.options importoptions fromBfApi importBfApi classMainHandler(RequestHandler): """ メイン処理 初期画面を表示します. """ definitialize(self): logging.info("MainHandler [initialize]") defget(self): logging.info("MainHandler [get]") self.render("Main.html",title="BfTool") classSendWebSocket(WebSocketHandler): """ WEBソケット通信 1秒毎にティッカー情報を画面に配信します """ defopen(self): logging.info('SendWebSocket [open] IP :'+self.request.remote_ip) self.ioloop=tornado.ioloop.IOLoop.instance() self.send_ticker() defon_close(self): logging.info("SendWebSocket [on_closed]") defcheck_origin(self,origin): logging.info("SendWebSocket [check_origin]") returnTrue defsend_ticker(self): self.ioloop.add_timeout(time.time()+1,self.send_ticker) ifself.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) classGetBalanceHandler(RequestHandler): """ 資産情報を取得 """ definitialize(self): logging.info("GetBalanceHandler [initialize]") defpost(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)) classGetExecutionHandler(RequestHandler): """ 約定一覧を取得 """ definitialize(self): logging.info("GetExecutionHandler [initialize]") defpost(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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /** * 画面読み込み時の処理 */ functioninitialize(){ // 各テーブルに空行を追加しておく addEmptyRow("tickerTable",10,12); addEmptyRow("executionTable",10,8); // 初期表示時に一度データを取得 updateBalance(); updateExecution(); varconnection=newWebSocket('ws://127.0.0.1:8888/ticker'); connection.onmessage=function(e){ vardata=JSON.parse(e.data.replace(/\\/g,"")); vartable=$("#tickerTable"); // 日付け変換 vardate=newDate(data.timestamp); data.timestamp=date.toLocaleString(); // テーブルに追加 vartr=document.createElement("tr"); $.each(data,function(i,cell){ vartd=document.createElement("td"); td.innerHTML=cell; tr.appendChild(td); }); varrows=table.find("tr"); if(rows.length>10){ $("#tickerTable tr:last").remove(); } $(tr).insertAfter("#tickerTable tr.header"); }; } /** * 空行をテーブルに追加します */ functionaddEmptyRow(tableId,rowCount,colCount){ for(i=0;i<rowCount;i++){ vartr=document.createElement("tr"); for(j=0;j<colCount;j++){ vartd=document.createElement("td"); tr.appendChild(td); } $(tr).insertAfter("#"+tableId+" tr.header"); } } /** * 資産情報を更新します. */ functionupdateBalance(){ $.ajax({ url:"http://localhost:8888/balance", type:"POST", success:function(jsonResponse){ jsonResponse=jsonResponse.replace(/\\/g,""); vardata=JSON.parse(jsonResponse); for(row indata){ 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(){ } }); } /** * 約定一覧を更新します. */ functionupdateExecution(){ $.ajax({ url:"http://localhost:8888/execution", type:"POST", success:function(jsonResponse){ jsonResponse=jsonResponse.replace(/\\/g,""); vardata=JSON.parse(jsonResponse); vartable=$("#executionTable"); varrows=table.find("tr"); for(index inrows){ if(rows[index].className==""){ rows[index].remove(); } } // テーブルに追加 $.each(data,function(i,row){ vartr=document.createElement("tr"); $.each(row,function(i,cell){ vartd=document.createElement("td"); td.innerHTML=cell; tr.appendChild(td); }); table.append(tr); }); }, error:function(){ } }); } |
起動してみる
結構すっきりしたんじゃないでしょうか。
まとめ
次回は注文の一覧を取得し、Tornadoのテンプレートで JSON形式のデータからうまいことテーブルに変換したいと思います。
ではでは。
ディスカッション
コメント一覧
まだ、コメントがありません