Bitflyerの自動売買BOTで成行注文を出す場合、その執行価格を把握しておきたい場面があると思います。この記事では成行注文の執行価格を表示するpythonコードを紹介しておきます。
前提
・CCXTライブラリを使う
CCXT経由でBitflyerに成行注文を出す方法は、以前にこちらの記事で解説しているので参考にしてください。
pythonコード
# 成行注文の執行状況を確認する関数 def check_market_order( id,lot ): while True: try: size = [] price = [] executions = bitflyer.private_get_getexecutions( params = { "product_code" : "FX_BTC_JPY" }) for exec in executions: if exec["child_order_acceptance_id"] == id: size.append( exec["size"] ) price.append( exec["price"] ) # 全部約定するまで待つ if round(sum(size),2) != lot: time.sleep(20) print("注文がすべて約定するのを待っています") else: # 平均価格を計算する average_price = round(sum( price[i] * size[i] for i in range(len(price)) ) / sum(size)) print("すべての成行注文が執行されました") print("執行価格は平均 {}円です".format(average_price)) return average_price except ccxt.BaseError as e: print("BitflyerのAPIで問題発生 : ",e) print("20秒待機してやり直します") time.sleep(20)
コードの解説
まずは以下のコードでBitflyerから約定履歴の一覧を取得しています。
executions = bitflyer.private_get_getexecutions( params = { "product_code" : "FX_BTC_JPY" })
BitflyerのAPIでは「 GET /v1/me/getexecutions 」で自分の約定履歴の一覧を取得することができます。取得した約定履歴の一覧からは、[size]で数量、[price]で執行価格を取得することができます。
(参考)
・BitflyerのAPI仕様書の説明ページ
・CCXT経由で個別のAPIを使用する方法
基本的な流れ
成行注文の場合、注文がいくつかに分割されて異なる価格で執行されることも多いです。そのため、上記の関数では以下の2つの情報を必要としています。
(1)成行注文を送ったときに返ってくる注文ID
(2)成行注文を出したときの注文数量
成行注文は、分割されて約定しても全て同一の注文ID(order_acceptance_id)で記録されます。例えば、以下のような感じです。
# 例)0.09BTC の売りの成行注文が、0.08BTC と 0.01BTCに割れて執行された場合 [{'child_order_acceptance_id': 'JRF20180424-060559-396699', 'child_order_id': 'JFX20180424-021850-855859F', 'commission': 0.0, 'exec_date': '2018-04-24T02:18:50.45', 'id': 215522439, 'price': 958028.0, 'side': 'SELL', 'size': 0.08}, {'child_order_acceptance_id': 'JRF20180424-060559-396699', 'child_order_id': 'JFX20180424-021850-855859F', 'commission': 0.0, 'exec_date': '2018-04-24T02:18:50.45', 'id': 215522439, 'price': 958290.0, 'side': 'SELL', 'size': 0.01} ]
そのため、成行注文を出したときに注文ID(order_acceptance_id)を覚えておいて、その注文IDで約定履歴を検索すれば、分割されて執行されたすべての注文を捕捉することができます。
1.注文がすべて執行されたことを確認する
成行注文を送った後に、20秒ごとに約定履歴をAPIで取得して、注文IDが一致している注文の「価格」と「数量」を取得します。それが以下の部分です。
for exec in executions: if exec["child_order_acceptance_id"] == id: size.append( exec["size"] ) price.append( exec["price"] )
そして「数量」の合計が、最初に送った注文数量と一致すれば、すべて約定したと判断します。一致しなければ、一致するまで20秒ごとに確認を繰り返します。(秒数は適当に変更してください)
# 全部約定するまで待つ if round(sum(size),2) != lot: time.sleep(20) print("注文がすべて約定するのを待っています") # ここでwhileループの先頭に戻る
一致したらすべての注文が執行されたものとして次に進みます。
2.執行価格の平均を計算する
すべての注文数量が約定したことを確認できたら、執行価格を計算します。これは以下の式で計算できます。
A)注文1の価格 × 注文1の数量 + 注文2の価格 × 注文2の数量 + 注文3の価格 × 注文3の数量 +….
B)注文数量の合計
執行価格の平均 A ÷ B
これをpythonで1行で書くと以下のようになります。
# 平均価格を計算する average_price = round(sum( price[i] * size[i] for i in range(len(price)) ) / sum(size))
3.注文サイズの計算方法の注意点
もう1つだけ注意点があります。それが小数点の計算です。pythonでは2進法で小数を計算するため、成行注文が細かく割れてしまった場合には、注文サイズが送ったものと一致しなくなることがあります。
具体例
例えば、0.21BTCを成行注文で出した場合を仮定してください。そして注文が以下のように割れて約定したとします。
▽ 0.21BTCの注文
注文1) 0.11634476 BTC
注文2) 0.08175024 BTC
注文3) 0.011905 BTC
合計 0.21BTC
ご自身で確認していただくとわかりますが、これは電卓で計算すると丁度 0.21BTCになります。それではpythonで同じ計算をしてみましょう。
size1 = 0.11634476 size2 = 0.08175024 size3 = 0.011905 print(size1 + size2 + size3) #----- 実行結果 ------ 0.21000000000000002
この計算結果は、なんとpythonでは 0.21000000000000002 になってしまいます。そこでround()を使って、注文したときと同じ桁数に小数点を丸める必要があります。それが以下の箇所です。
# 全部約定するまで待つ if round(sum(size),2) != lot:
これを最初私はよくわかっていなかったのですが、note読者の方に症状を教えていただき発見できました。いつも計算が一致しないわけではなく、細かい単位で注文が割れた場合の一部のケースでこの症状がおこるようです。
参考:「pythonの浮動小数点演算の問題」
成行注文のコードと組み合わせる
最後に成行注文を出すコードと組み合わせたパターンも書いておきます。
以下のコードで成行注文を出せば、約定後にその執行価格を返すところまでセットで実行できます。損益やスリッページの計算、建値の計算、損切価格ラインの決定などに使ってください。
# 成行注文を出す関数 def market_order(side,lot): while True: try: order = bitflyer.create_order( symbol = 'BTC/JPY', type='market', side= side, amount= lot, params = { "product_code" : "FX_BTC_JPY" }) # 注文時のidを記録しておく order_id = order["id"] time.sleep(30) # 執行状況を確認する関数を呼ぶ average_price = check_market_order( order_id, lot ) return average_price except ccxt.BaseError as e: print("Bitflyerの注文APIでエラー発生",e) print("注文が失敗しました") print("30秒待機してやり直します") time.sleep(30) # 成行注文の執行状況を確認する関数 def check_market_order( id,lot ): while True: try: size = [] price = [] executions = bitflyer.private_get_getexecutions( params = { "product_code" : "FX_BTC_JPY" }) for exec in executions: if exec["child_order_acceptance_id"] == id: size.append( exec["size"] ) price.append( exec["price"] ) # 全部約定するまで待つ if round(sum(size),2) != lot: time.sleep(20) print("注文がすべて約定するのを待っています") else: # 平均価格を計算する average_price = round(sum( price[i] * size[i] for i in range(len(price)) ) / sum(size)) print("すべての成行注文が執行されました") print("執行価格は平均 {}円です".format(average_price)) return average_price except ccxt.BaseError as e: print("BitflyerのAPIで問題発生 : ",e) print("20秒待機してやり直します") time.sleep(20)