Bitflyerのメンテナンス時間中はBOTの稼働を停止させたい場合

Bitflyerでは毎日午前4時00分~午前4時10分にかけて定期メンテナンスが実施されます。このメンテナンスの時間中は新規の注文ができないだけでなく、事前に受け付けられた注文も約定しないまま消滅します。

参考リンク:Bitflyer公式「メンテナンスについて教えてください」

問題

1時間足を基準にトレードするBOTの場合、午前4時ちょうどにエントリーや決済のシグナルが出ることもあります。

このときBOTの新規注文が拒否されれば通常の例外処理で対応できますが、ギリギリで注文が通ってしまった場合、サーバー側ではそのまま約定することなく消滅するため、BOT側は永遠に約定を待つことになり、トラブルの原因になります。

そのため、この記事ではメンテナンス時間中にBOTを停止する方法を解説します。

解決策

以下のようなコードをメインのwhile文の中の先頭の部分に追加しましょう。


wait = 60
maintenance_start = "03:59:30"
maintenance_end   = "04:10:30"


while True:
	
	# メンテナンス時間は稼働しない
	st = datetime.strptime(maintenance_start,"%H:%M:%S").strftime("%H:%M:%S")
	et = datetime.strptime(maintenance_end, "%H:%M:%S").strftime("%H:%M:%S")
	nt = datetime.now().strftime("%H:%M:%S")
		
	if nt>st and nt<et:
		print("メンテナンス時間中のため待機しています")
		time.sleep(wait)
		continue

現在の時刻が、メンテナンスの開始時刻(maintenance_start)と終了時刻(maintenance_end)の間に入っていれば、if文の中身が実行されます。continueは、それ以降の記述を実行せずに while文の先頭に戻る、という指示です。

このコード例では周期を60秒(wait=60)にしていますが、ここは各BOTに合わせて調整してください。

BitflyerFXの成行注文のスリッページのコストの影響を測定しよう

BOTの運用を成行注文で行っている場合、バックテスト上の成績と実際の成績が乖離する原因の1つにスリッページがあります。

成行注文や逆指値注文は、必ずしも理想の価格で約定するわけではありません。例えば、バックテスト上では「100万円で買いエントリーして98万円で損切りした」はずでも、成行注文が1%不利な価格で約定した場合、実際の運用は「101万円で買って97万円で売却する」ことになります。

この場合、1回の取引の損失は-2%(バックテスト)から-約4%(実際の運用)まで悪化します。

実際にどのくらいのスリッページが発生するかは、おそらく売買ロジックやボラティリティ、成行注文の数量によって異なるので、一概に何%と言うことはできません。この記事では、測定方法などを解説しますが、実際の数値は参考程度にしてください。

スリッページの計算方法

実際のスリッページの影響は、自分のロジックでBOTを一定の期間や回数動かしてみて、シグナル価格と約定価格の乖離を記録するしかないと思います。

例えば、以下は実際にログから過去300回程度の取引を抽出して、シグナル価格と約定価格の乖離をまとめたものの一部です。こちらの記事の方法を参考に、普段からBOTでシグナル価格や約定価格をログファイルに出力しておけば、簡単に集計できます。


「滑り」の欄でプラスの数値になっているのは、想定していた価格よりも不利な価格で約定したときの乖離率(スリッページ)です。マイナスの数値になっているものは、想定していた価格よりも有利な価格で約定したときの乖離率です。

売買ロジックにもよると思いますが、実際に集計してみると「予想よりも有利な価格」で約定するケースも結構あります。後ほど、このヒストグラムを紹介します。

1)スリッページの計算式

この記事では、スリッページの定義を「シグナル価格に対して何%不利な価格で約定したか?」のコストを表す数値とします。そのままバックテストで使えるようにするためです。

計算式は以下になります。

スリッページの計算式では、注文がエントリーなのか手仕舞いなのかを区別する必要はありません。買い注文は想定より高く約定したら不利で、売り注文は想定より安く約定したら不利です。このことは常に成り立ちます。

2)ログから欲しい数値を集計する方法

ログなどのテキストファイルから、欲しい数値(ここではシグナル価格と約定価格)を抜き出す方法を紹介します。どのようなログを出力するかは人によって違うので、ここでは具体的なコードというより、考え方だけ紹介しておきます。

▽ 例)ログのフォーマット


時間: 2019/04/26 06:00 高値: 622124 安値: 618640 終値: 622000
時間: 2019/04/26 07:00 高値: 622423 安値: 609400 終値: 610253
時間: 2019/04/26 08:00 高値: 610597 安値: 555556 終値: 580936
過去**足の最安値609350円を、直近の価格が580936円でブレイクしました
現在のアカウント残高は**円です
現在の**期間の平均ボラティリティは**円です
許容リスクから購入できる枚数は最大**BTCまでです
**回に分けて**BTCずつ注文します
580936円あたりに**BTCの売りの成行注文を出します
--------------------
{'info': {'child_order_acceptance_id':***}
--------------------
すべての成行注文が執行されました
執行価格は平均 579700円です
 

このテキストから抽出したい情報を以下の2つとします。

1)シグナル価格(バックテスト上で用いている価格) 580936円
2)実際の約定価格 579700円
3)スリッページ(乖離率) +0.21%

これを抽出するには、正規表現 というものを使うと便利です。例えば、以下のようなコードを作成すれば、ログファイルを1行ずつ読み込んで、欲しい数字だけを抽出することができます。

▽ コードの例


# 正規表現を扱うライブラリ
import re

# テキストファイルを読み込む
f = open("./logfile.txt", "r", encoding="UTF-8")
line = f.readline()

# 抽出したい情報
signal_price    = []
execution_price = []

# 1行ずつ読み込んで処理
while line:
	line = f.readline()
	if "ブレイクしました" in line:
		price = re.match(".*価格が?(\d+)円でブレイクしました", line).group(1)
		price = int(price) # 数値にする
		signal_price.append( price )
	
	if "執行価格は" in line:
		price = re.match(".*執行価格は.*?(\d+)円です", line).group(1)
		price = int(price)
		execution_price.append( price )

f.close()
print(signal_price)
print(execution_price)

re.match() の関数の箇所が「正規表現」です。

正規表現とは、毎回、出現する文字や数字の内容が違っている場合でも、その出現パターンさえ同じであれば、()で括られた箇所だけを抜き出すことができる便利な記述ルールです。以下の記事で、詳しい正規表現のルールが記述されているので参考にしてください。

参考:Qiita「わかりやすいpythonの正規表現の例」

実際のログファイルには、BOTの停止や手動での決済が混じってる場合も多く、欲しい情報を正しくセットで抜き出すためには、もう少し複雑なコードを考える必要があります。が、基本的には上記の方法の組み合わせで実現できます。

欲しい数字を全て抽出したら、以前の記事で紹介したようにpandasでデータフレームに変換して、csvなどの形式で出力すれば準備完了です。

3)スリッページの集計

全ての取引のスリッページ(シグナル価格と約定価格との乖離率)をcsvにまとめることができたら、次にそれを分析してみましょう。
今回は以下のような分析結果を作る方法を紹介します。


私の運用BOTの場合は上記のような集計結果になりました。

全取引のうち、およそ6割が不利な価格で約定し、約3割が有利な価格で約定しています。また順張りのロジックのため、明らかにエントリー時に偏って約定価格の滑りが発生しています。複数回に分けてエントリーする場合、後半になるほどバックテストで想定するより不利な価格で約定しています。一方、損切りや利確時にはほとんど滑りは発生していませんでした。

また全取引のうち95%は-0.3%~0.6%の乖離で約定していて、平均としては 0.1%程度の滑りを考慮すれば足りそうだとわかりました。たまに±3%前後で大きく滑っていますが、異常値は必ずしも不利な方向に発生するわけではないようです。

4)集計コードの作り方

最初に示したようなフォーマットのcsvファイルを無事に作成できたと仮定して、話を進めましょう。
具体的な分析のコードが以下です。

▽ 分析用のコード


import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import csv

#------------------------------------
import matplotlib as mpl
font = {"family":"Noto Sans CJK JP"}
mpl.rc("font",**font)
#-------------------------------------

# 集計データを読み込み
data = pd.read_csv("./data.csv", encoding="UTF-8", sep=",")

# 全取引の95%が収まる範囲を計算
percentile_5  = round( np.percentile( data["滑り"].dropna(),5 ) *100,3) # 下位5%
percentile_95 = round( np.percentile( data["滑り"].dropna(),95) *100,3) # 上位5%

print("-----------------------------------")
print(" 1BTC辺りのスリッページの測定")
print("-----------------------------------")

print(" 取引回数 : {}回".format( len(data["滑り"]) ))
print(" 平均値   : {}%".format( round(data["滑り"].mean() * 100,3)  ))
print(" 標準偏差 : {}%".format( round(data["滑り"].std() * 100,3 )  ))
print(" 最大値   : {}%".format( data["滑り"].max() * 100 ))
print(" 最小値   : {}%".format( data["滑り"].min() * 100 ))
print(" 95%区間  : {}%~{}%".format( percentile_5, percentile_95 ))
print(" 不利な約定回数 :  {}回 / {}回".format( len( data[ data["滑り"]>0 ]), len(data) ))
print(" 有利な約定回数 :  {}回 / {}回".format( len( data[ data["滑り"]<0 ]), len(data) ))
print("")

print("-----------------------------------")

print("")
print("成行買いの滑り          :   平均{}%".format( round( data[ data["売買方向"] == "BUY" ]["滑り"].mean(),4) * 100 ))
print("成行売りの滑り          :   平均{}%".format( round( data[ data["売買方向"] == "SELL" ]["滑り"].mean(),4) * 100 ))
print("エントリー1回目の滑り  :   平均{}%".format( round( data[ data["内容"] == "entry1" ]["滑り"].mean(),4) * 100 ))
print("エントリー2回目の滑り  :   平均{}%".format( round( data[ data["内容"] == "entry2" ]["滑り"].mean(),4) * 100 ))
print("損切や手仕舞い時の滑り  :   平均{}%".format( round( data[ data["内容"] == "close" ]["滑り"].mean(),6) * 100 ))
#print("ボラティリティとの相関係数  :   {}".format( data["滑り"].corr( data["ボラティリティ"] ) ))

print("----------------------------------")

#------------------------------------------
# グラフの描画
#------------------------------------------

n1, n2 = np.histogram( data["滑り"].dropna()*100, bins=200 ) 
# numpy の histogram() を使って n1(binの境界)とn2(度数)を取得する
# dropna() は空白行を落とすために使用

# ヒストグラムを表示
y = n1/float(n1.sum())         # 頻度(度数)をもとに確率を計算する(縦軸)
x = (n2[1:] + n2[:-1])/2       # 境界[0番目,1番目,2番目,...5番目]と[1番目,2番目,3番目,...6番目]からbinの中央値を計算(横軸)
x_width  = n2[1] - n2[0]       # binの幅を取得
plt.bar( x, y, width=x_width ) # 棒グラフの描画

# 平均値をグラフに表示
plt.axvline( data["滑り"].mean()*100, color="red",linewidth=1, label="平均値 {}%".format( round(data["滑り"].mean()*100,2 ) ))

# 95パーセンタイル区間を塗りつぶし
plt.axvspan( percentile_5, percentile_95, color="blue",alpha=0.1, label="95パーセンタイル区間") 

plt.grid(True)
plt.xlabel("シグナル価格と執行価格の乖離(スリッページ)%")
plt.ylabel("頻度(確率)")
plt.legend()
plt.show()


 

csvファイルを読み込んで、pandasを使って集計しています。
pandasの使い方はこちらの記事で解説しているので参考にしてください。

またmatplotlibのhist()関数では、縦軸の目盛りを確率にすることが難しいようだったので、numpyでヒストグラムにしました。

スリッページの影響

スリッページコストが最終的な成績にどの程度の影響を及ぼすかは、BOTの売買頻度によって異なります。

期待リターン2%の取引を年間200回するBOTと、期待リターン0.2%の取引を年間2000回するBOTでは、理論上の期待リターンは同じです。しかしここに0.1%のスリッページコストを考慮すると、前者の利回りは年間380%である一方、後者は期待リターンが100%になり、最終成績は4倍近い差となります。

BitflyerFXでCCXTを使って成行注文を出しその執行価格を取得する方法

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)

BitflyerFXで注文が消えたり、約定拒否や二重注文になる場合の対策コード

PythonでBOTを作ってBitflyerFXで運用していると、以下のようなトラブルにときどき遭遇します。いずれも「対策をしないと気付かないうちに意図しない建玉が残る」という種類のトラブルです。

(1)注文反映の遅延
(2)キャンセルとスレ違いでの約定
(3)注文が消える・約定拒否
(4)二重注文(ダブル約定)

定義

これらの用語はよく聞くものの、それぞれの具体的な定義がなかなか調べてもわからなかったので、以下、私が理解している範囲のそれぞれの定義を記載しておきます。

問題 意味
注文遅延 API経由で指値注文を出し、それが正常にサーバー側に受け取られたが、その情報が「未約定の注文一覧」「建玉の一覧」のどちらにも反映されず、数十秒~数分間、宙に浮いている状態
スレ違い API経由で指値注文のキャンセルを出し、それが正常にサーバー側に受け取られたが、実際はキャンセルに成功しておらず、スレ違いで注文が約定して建玉に残っている状態
注文が消える API経由で注文を出し、それが正常にサーバー側に受け取られたにも関わらず、出したはずの注文がどこかに消えてしまう状態
二重注文 API経由で注文を出したものの、それがタイムアウト等の通信エラーになり、例外処理でリトライをしたところ、実はさっきの注文が正常に受理されていたため、二重に注文が通ってしまう状態

「約定拒否」というのが何かよくわからず、個人的には「注文が消える」と類似の問題と理解しているのですが、もし違ったら教えてください。

問題点

これらの共通の問題は、通信エラーが判定条件にならないことです。

通信エラーが出てくれれば、単に例外処理をすればいいだけですが、通信エラーが出ていないのに注文が反映されない場合(または通信エラーが出たのに注文が執行された場合)、BOTの挙動がおかしくなる原因になります。

遅延の対策

注文の遅延とは、正常に指値注文のリクエストが受理されたのに、「注文一覧」か「建玉一覧」に注文が反映されない状態です。

これはもし「信じて待っていればいつか必ず反映される」という前提であれば、ただひたすら待てばいいことになります。大抵の場合は実際それで問題ありません。基本的な遅延の対策コードは以下に紹介しています。

参考:Bitflyerで自動売買の試作BOTを作ろう!

消えた場合の対策

しかしごく稀に「消えてしまう」という厄介なケースがあります。この場合、信じて待っていると永久に「反映待ち」の状態が続きます。そのため、少し気持ち悪いですが、一定の間隔でカウンターを回してどこかで諦めて「消えた」と判定しなければなりません。

例えば、以下のような感じです。


# サーバーの注文を確認する関数
def bitflyer_check_order( flag ):
	try:
		position = bitflyer.private_get_getpositions( params = { "product_code" : "FX_BTC_JPY" })
		orders = bitflyer.fetch_open_orders(
			symbol = "BTC/JPY",
			params = { "product_code" : "FX_BTC_JPY" })
	except ccxt.BaseError as e:
		print("BitflyerのAPIで問題発生 : ",e)
	else:
		# 注文が約定していた場合
		if position:
			print("注文が約定しました!")
			flag["order"]["exist"] = False
			flag["order"]["count"] = 0
			flag["order"]["latency"] = 0
			
			# 平均建値やサイズを取得
			time.sleep(5)
			price,size,side = check_bf_positions()
			
			flag["position"]["exist"] = True
			flag["position"]["side"] = side
			flag["position"]["price"] = price
			flag["position"]["lot"] = size
			return flag
		else:
			# 注文一覧に残っている場合
			if orders:
				print("まだ未約定の注文があります")
				for o in orders:
					print( o["id"] )
				flag["order"]["count"] += 1
				
				# 約定しない注文をキャンセルする
				if flag["order"]["count"] > 12:
					flag = bitflyer_cancel_order( orders,flag )
			else:
				# 遅延対策
				print("注文が遅延しているようです")
				flag["order"]["latency"] += 1
				
				# ずっと遅延が続いて消えた疑いがある場合
				if flag["order"]["latency"] > 12:
					print("注文が消えてしまったようです")
					flag["order"]["exist"] = False
					flag["order"]["count"] = 0
					flag["order"]["latency"] = 0
	return flag

通常は多くても5~10行、遅延が続いたあとに注文一覧に反映されます。しかし4月後半に注文が消えるという問題が何度か出たことがあったと思います。

記事にしようと思いつつ時間が経ってしまい、少し前のテストなのでうろ覚えですが、たしかこのテストのときは注文が消えたと思います。「注文が消える」ケースはそう頻繁に再現できないので、なかなか対策の方法が難しいです。

▽ 注文が消えた場合

二重注文の対策

二重注文は、タイムアウトやサーバーエラーなどの通信エラーが出たにも関わらず、実際には注文が正常に受理されていたパターンです。それに気づかずに、例外処理をして注文を再送すると、ダブルで約定してしまいます。

これは注文を連打せずに、ある程度、しっかり間隔をあけて間にポジションチェックを入れれば回避できる気もします。しかし前述のように、遅延が酷いときは1~2分待ったくらいでは確信を持って判断できないときもあります。サーバーが混雑していないときまで毎回1~2分待つのも非効率です。

タイムアウトエラーの対策

1つの対策としては、接続のタイムアウトの判定時間を長めに設定しておくという方法があります。

CCXTライブラリ経由でBitflyerに注文を出す場合、初期設定でのタイムアウトの判定は10秒に設定されています。つまり10秒待って応答がなければエラーとして扱われるわけですが、以下のように記述することで、その待機時間を伸ばすことができます。


bitflyer = ccxt.bitflyer()
bitflyer.apiKey = ''
bitflyer.secret = ''
bitflyer.timeout = 30000     # 通信のタイムアウト時間の設定

上記の例では、タイムアウトエラーの判定時間を30秒にしています。抜本的な解決方法ではありませんが、個人的には、上記の設定でかなりエラー頻度が減ったように思います。

定期的に建玉をチェックする

結局のところ、あまりスマートではないですが、定期的に「意図しない建玉」が残っていないかをチェックするしかないかもしれません。現在の建玉を取得する関数の作り方は以下を参考にしてください。

参考:BitflyerFXの平均建値とサイズを取得する

例えば、以下のような関数を全体ループの中に入れることを考えます。

def find_unexpected_pos( flag ):
	
	if flag["position"]["exist"] == True:
		return flag
	count = 0
	while True:
		price,size,side = bitflyer_check_positions()
		if size == 0:
			return flag
		
		print("把握していないポジションが見つかりました")
		print("反映の遅延でないことを確認するため様子を見ています")
		count += 1
		
		if count > 5:
			# ポジションの復活
			print("把握していないポジションが見つかったためポジションを復活させます")
			
			flag["position"]["exist"] = True
			flag["position"]["side"] = side
			flag["position"]["lot"] = size
			flag["position"]["price"] = price
			return flag
		time.sleep(30)

※ 例外処理は check_bf_position() の中に入っています。

このような処理をする場合は、今度は逆に「成行注文で決済したはずなのに遅延で建玉が残っている場合」に騙されないように注意しなければなりません。そのため、成行注文を出したときに、確実にすべて執行されたところまで見届けることが重要です。

またすべての成行注文が執行されたとしても、決済後も「建玉一覧」にポジションが解消されたことへの反映が遅延する可能性もあるため、上記のコードでは5回ほどカウンターを回して確認しています。

もっと良い方法も模索中です。

CCXT経由でBitflyerFXの現在のポジションの平均建値とサイズを取得する

BitflyerFXからAPIで現在のポジションの建値やサイズを取得する方法を解説します。具体的な使い道として以下のような場面を想定しています。

(1)BOTを再稼働させたときにポジション情報を取得する
(2)注文拒否や二重約定などでBOTがポジションを見失った場合
(3)ループのたびにポジション情報を取得する仕様にする場合

前提

CCXTライブラリを使います。

CCXTライブラリのGithubページ
CCXTライブラリって何?を解説
CCXTライブラリで取引所のAPIを直接使う方法

Pythonコード


import ccxt

bitflyer = ccxt.bitflyer()
bitflyer.apiKey = ''
bitflyer.secret = ''

def check_bf_positions():
	while True:
		try:
			size = []
			price = []
			positions = bitflyer.private_get_getpositions( params = { "product_code" : "FX_BTC_JPY" })
			if not positions:
				print("現在ポジションは存在しません")
				return 0,0,None
			for pos in positions:
				size.append( pos["size"] )
				price.append( pos["price"] )
				side = pos["side"]
			
			# 平均建値を計算する
			average_price = round(sum( price[i] * size[i] for i in range(len(price)) ) / sum(size))
			sum_size = round(sum(size),2)
			print("保有中の建玉:合計{}つ\n平均建値:{}円\n合計サイズ:{}BTC\n方向:{}".format(len(price),average_price,sum_size,side))
			
			# 価格・サイズ・方向を返す
			return average_price,sum_size,side
				
		except ccxt.BaseError as e:
			print("BitflyerのAPIで問題発生 : ",e)
			print("20秒待機してやり直します")
			time.sleep(20)

# 実行処理
price,size,side = check_bf_positions()

保有中の建玉の価格とサイズ、エントリーの方向の3つをセットで返す関数です。複数のポジションを保有している場合は、平均建値と合計サイズを計算して返します。

BitflyerFXでは両建てはできないので、方向(BUYかSELLか)は、複数ポジションのうち1つを参照すれば大丈夫です。そのため、最後に取得したポジションの方向を参照しています。

ポジションが存在しない場合

もしポジションが存在しなければ、price=0, size=0, side=None を返します。これらのいずれかを使えば、外部から以下のように「ポジション無し」を判定できます。

▽ ポジションの有無を判定したい場合


price,size,side = check_bf_positions()
if size == 0:
	print("ポジションは存在しません")

BitflyerのAPI

BitflyerのAPIには、建玉の一覧を取得するAPI「GET /v1/me/getpositions」を、CCXTライブラリ経由で使用しています。

BitflyerAPIの公式ページ

このAPIを使用すると、建玉の情報が配列で返ってきます。そのため、以下のような形式で情報にアクセスできます。

▽ 返り値を positions に入れた場合


positions[0]    # 最初に取得したポジションの情報
positions[-1]   # 最後に取得したポジションの情報

positions[0]["price"]  # エントリー価格
positions[0]["side"]   # エントリーの方向
positions[0]["size"]   # ポジションのサイズ


いくつポジションを保有しているかわからない場合は、for文で回して全ての情報を取得します。

サイズ計算の注意点

合計サイズは、APIで取得した各建玉のサイズの合計値を計算しています。しかしpythonは足し算などの演算を2進法で計算するため、四捨五入しないと最終的な値がおかしくなることがあります。

具体例

例えば、以下のような3つの建玉がある場合、合計サイズは普通に計算すれば 0.21BTC になります。しかしpythonが2進法で足し算をすると、これは0.21 にならず、0.21000000000002 になってしまいます。


size1 = 0.11634476
size2 = 0.08175024
size3 = 0.011905

print(size1 + size2 + size3)

#----- 実行結果 ------
0.21000000000000002

そのため、合計サイズは四捨五入して規定の小数点以下に丸めておく必要があります。例えば、ご自身のBOTがすべての注文を小数点以下2桁で管理しているなら、小数点以下2桁で丸めれば大丈夫です。

これが上記コードの以下の箇所です。

sum_size = round(sum(size),2)

Bitflyerの未約定の全注文をCCXTで一括でキャンセルする方法

前回の記事「CCXTでBitflyerに出した注文を管理・キャンセルする方法」の最後の練習問題の解答です。

解答


import ccxt

bitflyer = ccxt.bitflyer()
bitflyer.apiKey = '**********'
bitflyer.secret = '**********'

orders = bitflyer.fetch_open_orders(
	symbol = "BTC/JPY",
	params = { "product_code" : "FX_BTC_JPY" })

for o in orders:
	bitflyer.cancel_order(
		symbol = "BTC/JPY",
		id = o["id"],
		params = { "product_code" : "FX_BTC_JPY" })


fetch_open_orders()で未約定のすべての注文を取得し、それをfor文を使って1つずつキャンセルしていきます。キャンセルする注文IDを id = o[“id”] と指定するのが、今回のポイントでした。なお、Bitflyerの場合は、product_codeを指定するのを忘れないようにしてください。

(for文の解説)

orders[0][“id”]
orders[1][“id”]
orders[2][“id”]
orders[3][“id”]
orders[4][“id”]



の順番で処理したい要素がある場合は、for o in orders : と書くことで、orders[?]までの部分を o という変数に入れて順番に処理することができます。このとき、orders[?][“id”]は、for文の中では、o[“id”]で指定できます。

CCXTでBitflyerに出した注文を管理・キャンセルする方法

前回の記事では、CCXTライブラリを使ってBitflyerの最終取引価格を取得し、それに基づいて「買い注文」をpythonのプログラムから出す方法を説明しました。

今回はその続きです。
送信した注文が約定したかどうかをチェックしたり、未約定の注文をキャンセルする方法を解説します。

事前準備

今回の説明にあたり、複数の注文があった方がいいので、前回作ったプログラムを使って3つほど注文を出してみましょう。pythonプログラムを3回実行するだけです。

以下のように、3つの注文が置かれました。

まずはこの注文情報をpythonのプログラムで取得してみましょう。

注文情報を取得する

サーバーに送ったもののまだ約定していない注文のことを(Open Order)といいます。この未約定の注文は、「fetch_open_orders()」という共通の関数で取得できます。

エディタに以下のコードを記述してください。


import ccxt
from pprint import pprint

bitflyer = ccxt.bitflyer()
bitflyer.apiKey = '**********'
bitflyer.secret = '**********'


orders = bitflyer.fetch_open_orders(
	symbol = "BTC/JPY",
	params = { "product_code" : "FX_BTC_JPY" })
pprint( orders )


これを実行すると、以下のような結果が取得できます。

前回までの記事を読んでいれば、特に目新しいことはないと思うので、コードの解説は省略します。paramsに「FX_BTC_JPY」を指定するのを忘れないようにしてください。

実行結果

先ほど出した未約定の注文が3つとも取得できているのが確認できます。fetch_open_orders()で取得した注文情報には、以下の情報が含まれます。

・注文ID(例:JRF20180317-022611-288893)
・注文した日時
・指値の価格
・Buy/Sellの方向
・未約定の残高

注文をキャンセルする場合には、「注文ID」を使います。ここでは、シンプルに「注文ID」だけを表示したいので、以下のようにコードを修正しましょう。


import ccxt
from pprint import pprint

bitflyer = ccxt.bitflyer()
bitflyer.apiKey = '**********'
bitflyer.secret = '**********'


orders = bitflyer.fetch_open_orders(
	symbol = "BTC/JPY",
	params = { "product_code" : "FX_BTC_JPY" })

for o in orders:
	pprint( o["id"] )


変更したのは最後の「for文」のところだけですね。

コードの解説

上記のfor文は、CryptowatchのAPIを取得する練習記事で解説したのと全く同じパターンです。

JSON形式の構造が[ {},{},{}… ] になっていることと、{}の中身が「 id : 注文ID 」になっていることを把握できれば、同じやり方でデータを取り出せます。詳しくは「API編」の記事を参考にしてください。

プログラミング未経験者の方にとって、for文は慣れるまで少しコツが必要です。最低限、for文/if文/while文だけは知らないと自動売買BOTが作れません。これらの構文を全く知らない方は、以下の記事を読んでおいてください。

最低限必要なプログラミング構文(for文/if文/while文)

実行結果

上記のコードを実行すると、以下のように注文IDだけを取得することができました。

応用:すべての注文を取得する

先ほど書いたpythonプログラムの fetch_open_orders()の部分を、fetch_orders()に変更すれば、すべての注文(約定した注文/未約定の注文)をまとめて取得できます。

何も指定しなければ直近100件のすべての注文が取得されますが、「count」というパラメーターを付ければ取得する件数を調整することができます。

また取得した注文情報の「status」を調べれば、約定したかどうかがわかります。「status : open」のままであれば未約定ですし、「status : closed」になっていれば約定しています。

これを応用して、直近10件の注文が約定しているかどうかを調べるコードを書いてみましょう。以下のようになります。

▽ 直近10件の注文と、その約定の有無を調べるコード例


import ccxt
from pprint import pprint

bitflyer = ccxt.bitflyer()
bitflyer.apiKey = '**********'
bitflyer.secret = '**********'

orders = bitflyer.fetch_orders(
	symbol = "BTC/JPY",
	params = { "product_code" : "FX_BTC_JPY",
		"count" : 10})

for o in orders:
	print( o["id"] + " : 注文状況 " + o["status"] )


コードの解説

print()の関数は、print( 変数 + 変数 )で2つの変数の中身を繋いで表示することができます。見やすく表示するために、間に文字を入れたければ、print( 変数 + “好きな文字” + 変数 ) で、間に空白や文字を入れることができます。

実行結果

上記のコードを実行すると以下のように表示されます。

さて、次はこの注文IDをもとにして、注文をキャンセルしてみましょう。

注文をキャンセルする

注文をキャンセルする場合は、cancel_order()という共通の関数を使います。注文IDがすでにわかっている場合は、以下のように書くだけでOKです。


bitflyer.cancel_order(
	symbol = "BTC/JPY",
	id = "JRF20180316-172859-799705",
	params = { "product_code" : "FX_BTC_JPY" })


注) id=””の部分に注文IDを入れてください。
またキャンセルの関数は、orders ではなく order なので間違えないようにしてください。

Bitflyerでは、注文をキャンセルした際に、サーバーからは何のデータも返ってきません。なのでキャンセルできたかどうかを確認したければ、もう1度、fetch_orders()を実行する必要があります。

上記のコードを実行した後、もう1度、fetch_orders()を実行してみましょう。

実行結果

cancelで指定した注文が消えていますね。
管理画面でも確認してみましょう。

無事、注文が無くなっています。

応用:一番新しい注文を取得してキャンセルする

せっかくなので、手動で注文IDを指定するのではなく、fetch_open_orders()で取得した注文をそのままプログラムでキャンセルしてみましょう。今回は応用として「注文のうち一番新しいもの」を自動的にキャンセルします。

Bitflyerから fetch_open_orders() で取得した注文は、以下のように古い順番に並んでいます。

[{注文1},{注文2},{注文3},….]

この配列の最後の要素(一番新しい注文)を指定したければ、orders[-1]と指定すればOKです。コードを書いてみましょう。


import ccxt
from pprint import pprint

bitflyer = ccxt.bitflyer()
bitflyer.apiKey = '**********'
bitflyer.secret = '**********'

orders = bitflyer.fetch_open_orders(
	symbol = "BTC/JPY",
	params = { "product_code" : "FX_BTC_JPY"})

bitflyer.cancel_order(
	symbol = "BTC/JPY",
	id = orders[-1]["id"], #11行目
	params = { "product_code" : "FX_BTC_JPY" })


コードの解説

今まで学習したことの組み合わせなのでわかると思いますが、6行目の fetch_open_orders() で未約定の注文を取得して、cancel_order() で注文をキャンセルしています。

11行目の id = orders[-1][“id”] で、取得した未約定の注文のうち、一番後ろの注文(一番最新の注文)の注文IDを指定しています。

実行結果

実行してもう1度、注文状況を取得してみましょう。

無事、新しい方の注文が消えましたね。
管理画面でも確認しておきましょう。

新しい方の注文がキャンセルされています。

以上が、Bitflyerにpythonで出した注文の状況を管理して、注文IDを取得し、未約定の注文をキャンセルする方法でした。

練習問題

未約定の注文の一覧を取得して、それらを「すべて」一括でキャンセルするプログラムを書いてみましょう。ヒントとしては、最初の「注文IDだけを表示する」のプログラムコードを修正して、for文の中に注文キャンセルのコードを入れるとうまくいきます。

正解はこちら

CCXTライブラリでBitflyerに注文を出す方法をマスターする

さて、前々回の記事ではBitflyerのAPIを使って買い注文を出す方法を紹介しました。このとき注文を出すだけでも結構長いコードを書かなければならなかったのを覚えているでしょうか?

CCXTライブラリを使えば、これを1行で書くことができます。実際にやってみましょう!

事前準備

bitFlyer lightning の最低注文単位は0.01BTCからです。
現在(2018年4月)のレートでいうと9000円くらいですね。そのため、口座には最低1万円以上の資金が必要です。

今回はレバレッジ無しで最低単位の注文をします。一応テストなので、約定しないように注意しながら、現在価格から10%以上離れた場所に指値を入れます。そして、それをキャンセルするところまでプログラムしてみます。

bitFlyerのアカウント登録がまだの方は、ぜひこちらから登録してください。

(追記) 2018年4月から最低注文単位が0.01BTCに引き上げられました。この記事はそれ以前に作成したものなので、画像が0.001BTCのままになっています。文字部分は修正しています。

現在のBTC-FX価格を取得する

前々回の記事では、BTC-FXの画面にログインして目視で価格を確認しました。ですが、今回はより本格的に、価格を調べるところもすべてPythonのプログラムでやりましょう。

エディタを起動して以下のように記載してください。


import ccxt
from pprint import pprint

bitflyer = ccxt.bitflyer()
ticker = bitflyer.fetch_ticker('BTC/JPY', params = { "product_code" : "FX_BTC_JPY" })
pprint(ticker)


コードの解説

1行目と2行目でccxtライブラリとpprintライブラリをimportします。これは前回の記事と同じですね。pprintライブラリは配列を綺麗に表示するために使います。

3行目の「bitflyer = ccxt.bitflyer()」は、ビットフライヤーで関数を使うために必ず最初に書かなければならないコードです。準備のようなものだと思ってください。

4行目でティッカー情報を取得しています。すでに学んだように、ティッカー情報の取得はパブリックAPIなので、この時点ではAPIキーやシークレットは必要ありません。Bitflyerの場合は、FXでは「product_code」を「FX_BTC_JPY」に指定するのを忘れないようにしてください。

5行目のpprint()で、取得した ticker を黒い画面に表示しています。

実行結果

それでは、上記のコードをpythonファイルで保存して、Anacondaプロンプトで実行してみましょう。以下のように表示されました。

{'ask': 902954.0,
 'average': None,
 'baseVolume': 274827.01507782,
 'bid': 902900.0,
 'change': None,
 'close': None,
 'datetime': '2018-03-16T00:13:01.147Z',
 'first': None,
 'high': None,
 'info': {'best_ask': 902954.0,
          'best_ask_size': 0.2,
          'best_bid': 902900.0,
          'best_bid_size': 1.2837385,
          'ltp': 903111.0,
          'product_code': 'FX_BTC_JPY',
          'tick_id': 14952020,
          'timestamp': '2018-03-16T00:13:01.147',
          'total_ask_depth': 7224.20363872,
          'total_bid_depth': 5425.62384916,
          'volume': 306014.57114597,
          'volume_by_product': 274827.01507782},
 'last': 903111.0,
 'low': None,
 'open': None,
 'percentage': None,
 'quoteVolume': None,
 'symbol': 'BTC/JPY',
 'timestamp': 1521159181147,
 'vwap': None}

この中で、最終取引価格(=現在価格)だけ知りたければ、「last」の部分を見ればOKです。この記事の執筆時点では、903111円(90万3111円)ですね。

あるいは、bid(買い板の中の最高価格)を見てもいいです。売買BOTのアルゴリズムで買いシグナルが出た場合、指値注文を入れるのであれば、この「bid」か「last」のどちらかの価格で指値を入れることが多いと思います。

なお、この取得したJSONデータから最終取引価格(last)だけ取り出したければ、先ほどのpythonのプログラムの5行目を以下のように書けばOKです。


pprint( ticker["last"] )


これは「JSON形式のデータから欲しい数字だけを取り出そう!」の復習なので、わからない方はこちらを読んでください。

FXの証拠金残高を確認する

次にポジション管理のために、FXの証拠金残高などを取得してみましょう。エディタに以下のコードを書いてください。


import ccxt
from pprint import pprint

bitflyer = ccxt.bitflyer()
bitflyer.apiKey = '**********'
bitflyer.secret = '**********'

collateral = bitflyer.private_get_getcollateral()
pprint( collateral )


コードの解説

今回はプライベートAPIを使うので、APIキーとAPIシークレットを準備する必要があります。4行目・5行目の’**********’の部分に各自のAPIキー・シークレットをそれぞれ入力してください。

またCCXTライブラリには、Bitflyerで BTC-FX の証拠金残高を取得する共通の関数がありません。そのため、ここではBitflyerの「v1/me/getcollateral」というAPIを使っています。(collateralは英語で「担保」という意味です)。

BitflyerのAPIドキュメント

このようにCCXTライブラリでは、共通のメソッドが見つからない場合に、各取引所で個別に提供されているAPIを、直接使うこともできます。具体的な方法については、以下の記事で紹介しています。他の場面でも困ったら参考にしてみてください。

・Bitflyerや各取引所の個別のAPIをCCXT経由で利用する方法

実行結果

上記のコードをAnacondaプロンプトで実行してみましょう。以下のような結果が表示されます。


{
  "collateral": 100000,
  "open_position_pnl": -715,
  "require_collateral": 19857,
  "keep_rate": 5.000
}


上から順番に、

・預入証拠金の評価額(collateral)
・ポジションの評価損益(open_position_pnl)
・現在の必要証拠金(require_collateral)
・現在の証拠金維持率(keep_rate)

です。

自動売買BOTでは、証拠金の残高から取得可能なポジションのサイズを自動計算させて、可変ロットで注文を出すこともできます。この方法は第7回の「資金管理編」で詳しく解説するので楽しみにしていてください。

初心者の方は、まずは固定ロット(0.1BTCなど)でちゃんと売買できるようになりましょう!

買い注文を出す

今回はテストなので、最終取引価格が90万円のところ、10%以上離して80万円で買い指値を入れてみます。数量は最低単位の0.01BTCにします。

注文には、「create_order()」というCCXTの共通の関数を使います。エディタを起動して以下のコードを書いてください。


import ccxt
from pprint import pprint

bitflyer = ccxt.bitflyer()
bitflyer.apiKey = '**********'
bitflyer.secret = '**********'


order = bitflyer.create_order(
	symbol = 'BTC/JPY',
	type='limit',
	side='buy',
	price='800000',
	amount='0.01',
	params = { "product_code" : "FX_BTC_JPY" })

pprint( order )


これで終わりです!

実際に注文を送るために書いたのは、order = bitflyer.create_order() の1行だけです! 見やすいように改行してるだけなので、「1行じゃねぇじゃねーか!」とか言わないでください><

コードの解説

CCXTには指値注文、成行注文を出すための関数、買い注文・売り注文を出すための関数もありますが、上記のように、create_order()で詳細を指定すれば、全パターンの注文を出せますので、この1つだけ書き方を覚えたほうが簡単です。

以下だけ覚えてください。

symbol=””の部分 ⇒ 通貨ペア
type=”” の部分 ⇒ limitが指値/marketが成行
side=”” の部分 ⇒ buyが買い/sellが売り
price=”” の部分 ⇒ 指値を入れる場合の価格
amount=””の部分 ⇒ 注文数量
params={} の部分 ⇒ 各取引所のAPIに渡せるパラメーター

Bitflyerでは、「product_code」を「FX_BTC_JPY」に指定するのを忘れないようにしてください。これを忘れると現物注文になります。

6行目で、Bitflyerのサーバーに注文を出すと、サーバーから注文IDが返ってきます。この注文IDは、今後、注文状況を確認したりキャンセルするときに使います。そのため、返ってきた値は、orderという変数で受け取り、それをpprint()で画面に表示しています。

実行結果

それでは、上記のコードをpythonファイルで保存して、Anacondaプロンプトで実行してみましょう。以下のように表示されれば成功です。

実際にBitflyerのFX画面でも、注文が通っていることが確認できます。

今回の記事はここまでです。
次回の記事では、注文状況を確認したり、まだ約定していない注文をキャンセルする方法を解説していきます!

CCXTが便利な理由!CCXTライブラリをインストールしよう

前回の記事では、BitflyerのAPIを使って実際にBTC-FXの買い注文を出す方法を解説しました。

少し長い記事だったので、疲れた方も多いかもしれませんね(笑)。でもキチンと読んでくださった方は、いきなりライブラリの利用からから入る方よりも、間違いなく「本質的な部分」の理解が深まっているはずなので自信を持ってください!

さて、今回からはCCXTという外部のライブラリを使って、全く同じ「bitFlyerに買い注文を出す」コードを、もっと簡単に実装する方法を紹介します。

CCXTライブラリのgithubページ

なぜCCXTライブラリを使うのか?

1つ目の理由は、自分で長いコードを書かなくて済むからです。

前回の記事では、「sendchildorder」というBitflyerのAPIを利用して、pythonプログラムからBitflyerのサーバーに注文を出すことができました。しかし実際には、このAPIを利用するためには、以下のような手順を踏まなければなりませんでした。

1)現在時刻のタイムスタンプを作る
2)注文価格や数量などのパラーメータを設定する
3)パラメーターをJSON形式に変換する
4)メッセージ認証コード(暗号署名)を作る
5)通信用のヘッダーを作成する
6)リクエストを送る

このうち実質的に(2)の部分以外は、すべてのAPIリクエストで共通の処理です。なので毎回、自分で書かなくても、自動的に実行してくれたら楽ですよね。

例えば、買い注文を出す場合には、「指値か/成行か」「買いか/売りか」「注文ロット数は?」「指値の価格は?」といった随時、変更したい部分だけを指定し、それ以外の部分は1つの関数にまとめておいて、いつでも呼び出せるようにしておけば、プログラムを書くのが遥かに楽になります。

これをやってくれるのがライブラリです。

取引所ごとのAPIの違いを気にしなくていい!

2つ目の理由は、取引所ごとのAPIの仕様の違いを無くすためです。

APIというのは、各取引所が独自に開発しているものなので、取引所によって仕様が違います。例えば、bitFlyerでは「sendchildorder」というAPIで注文を出しましたが、Zaifの場合は「trade」というAPIを使いますし、bitbankであれば、「order」というAPIを使います。

通信に含めるパラメーターのルールも、取引所ごとに違います。例えば、Bitflyerは「size」で注文ロット数を指定しますが、Zaifは「amount」で注文ロット数を指定しなければなりません。

このような取引所ごとのAPIの違いを気にしながら、自動売買BOTを作るのはかなり面倒です。

1つの売買ロジックを複数の取引所で試したい場合でも、取引所ごとにpythonコードを書き直さなければなりません。また、裁定取引のように取引所間の価格差を利用した売買BOTを作りたい場合、取引所ごとに注文の方法が違うと非常に厄介です。

CCXTライブラリで出来ること

CCXTなどのライブラリは、このような「各取引所ごとのAPIによる注文の違い」を、見えない裏側でまとめて処理してくれます。そのため、先頭の1行目でCCXTライブラリを読み込めば、あとはすべての取引所で共通の関数を使うことができます。

例えば、以下のような関数(メソッド)がすべての取引所で「共通」で使えます。

create_order() ・・・ 注文を出す(成行/指値)
cancel_order() ・・・ 注文をキャンセルする
fetch_ticker() ・・・ ティッカー情報を取得
fetch_balance() ・・・ 口座残高を取得
fetch_order_book() ・・・ 板情報の取得

また各取引所ごとのAPIをCCXTライブラリを経由して直接使うこともできます。例えば、bitFlyerにしか存在しないAPIを使いたい場合は、上記の共通のメソッドを使わずに、直接、BitflyerのAPIを利用することもできます。

詳しい使い方は今後の記事で説明していきます!

CCXTをインストールしよう

まずはCCXTライブラリをPythonにインストールしていきましょう。Anacondaプロンプトを起動して以下の1行を打ち込むだけです。

pip install ccxt

すると以下のようにインストールが開始されます。

「Successfully installed … 」と出て、また文字が打てる状態になれば、インストールは完了です。

Bitflyerで出来ることを確認しておく

早速、CCXTライブラリを使ってpythonプログラムを1つ書いてみましょう。今回は、BitFlyerでできることを把握するために、対応している共通の関数(メソッド)の一覧を確認してみます。

エディタを起動して以下のコードを書いてみてください。

import ccxt
from pprint import pprint

bitflyer = ccxt.bitflyer()
pprint( bitflyer.has )

コードの解説

最初の「import ccxt」は、CCXTライブラリを利用するための1文です。これは今後、CCXTを使うときには必ず記述します。

2行目の「from pprint import pprint」は、pprint()を使ってデータを綺麗に表示するためにインポートしてみました。今までの記事では、すべてprint()だけで表示していましたが、改行がないと見にくいデータは、pprint()で表示した方が綺麗です。

3行目の「bitflyer = ccxt.bitflyer()」も、Bitflyerの取引所を使う場合には最初に必ず書かなければならないコードです。準備のようなものだと思ってください。

4行目の「bitflyer.has」は、CCXTライブラリにあるBitflyerのプログラムが、どの共通関数に対応しているかを確認するためのコードです。これをpprint()を使って黒い画面に表示しています。

実行結果

それでは実行してみましょう!
以下のように表示されました。

「True」と記載されているものは、Bitflyerでも使えます。
以下のような共通関数は使えるということですね。

cancelOrder() ・・・ 注文をキャンセルする関数
createOrder() ・・・ 注文を出す(買い/売り、指値/成行)関数
fetchBalance() ・・・ 残高を確認する関数
fetchTickers() ・・・ ティッカー情報を取得
fetchOrderBook() ・・・ 板情報を取得

一方、見てのとおり、結構、使えない(Flaseになっている)関数もあります。

前回の記事の意味

前回の記事を読んでくださった方ならわかると思いますが、CCXTライブラリは結局のところ、各取引所のAPIを裏側で叩いているだけのものが多いです。そのため、各取引所が該当するAPIを提供してくれていなければ使えません。

また上記の共通メソッドにはパラメーターを渡すこともできますが、どのようなパラメーターを渡すことができるかは、各取引所のAPIの仕様によって異なります。例えば、Bitflyerで BTC-FX のデータを取得するためには、”FX_BTC_JPY”というパラメーターを渡さなければなりません。渡さないとBTC現物のデータが返ってきます。

このようなルールも、裏で各取引所のAPIが動いていることを知っていなければ、なかなか自分で気づいたり調べることが難しいと思います。そのため、前回の記事ではわざわざ1度、APIだけで注文を出す方法を学びました。

さて、次の記事では、CCXTライブラリを使って、実際に「BitflyerFXの現在価格と口座の残高を取得して、購入額と数量を決め、買い注文を出す」という多くの売買BOTで必要にな一連の流れをまとめて勉強します!

(追記)

※ 最新版 Anaconda3.7 ではCCXTのインストールができず、3.6にダウングレードするとできたという報告があるようです。もし出来ない方は参考にしてください。

bifFlyerのAPI経由でPythonで「買い注文」を出してみよう

さあ! ようやくここまで来ました!
いよいよPythonでプログラムを書いて、実際にBitflyerに注文を出してみましょう! まずはシンプルに「買い」の注文を出してみることにします。

今回はあくまでテストなので、成約しないように、最低単位のBTCを指値で注文します。注文後は、いったん自分で手動で注文をキャンセルしてください。

(bitFlyerのアカウント登録がまだの方は、ぜひこちらから登録してください)

事前準備(必要な口座残高)

bitFlyer lightning の最低注文単位は0.01BTCからです。
現在(2018年4月)のレートでいうと1万円くらいですね。

bitFlyerFXで注文するための必要証拠金は、レバレッジの倍率によって違います。例えば、レバレッジ2倍で100万円分(=約1BTC)のポジションを取るには、最低50万円の残高が必要です。一方、レバレッジ10倍であれば、最低10万円の残高が必要です。

ちなみに追証ラインは80%、ロスカットライン(証拠金維持率)は50%です。レバレッジ2倍の例だと、90万円を切った時点で追証がかかり、75万円を切ると強制決済されます。レバレッジ10倍の場合だと、98万円を切った時点で追証がかかり、95万円を切ると強制決済されます。

Bitflyerの必要証拠金について
Bitflyerのロスカット(証拠金維持率)について

この記事では0.01BTCの注文しか出しませんので、残高は数万円もあれば十分です。

(追記) 2018年4月から最低注文単位が0.01BTCに引き上げられました。この記事はそれ以前に作成したものなので、文字部分は修正していますが、一部の画像が0.001BTCのままになっています。

bitFlyerの買い注文のAPI

bitFlyer(FX)の買い注文のAPIは以下のような形式になります。

https://api.bitflyer.jp/v1/me/sendchildorder

今まで勉強したパブリックAPIに似ていますね。ただしこちらのURLは今までのようにブラウザで見ることはできません。

理由は「POSTメソッドという通信方式で送らないといけないから」ですが、意味がわからなくても構いません。単に、何となく以下のようなパラメーターをボディに含めて送る方式だと理解してください。

必要なパラメーター

1)product_code
取引する通貨種類のこと。現物なら「BTC_JPY」。今回はFXなので「FX_BTC_JPY」

2)child_order_type
指値注文か成行注文か、という意味。今回は指値注文なので「LIMIT」と入れる。

3)side
買い(ロング)か売り(ショート)か。今回は買いエントリーするので「BUY」と入れる。

4)price
指値注文を入れる場合には、価格の指定が必須になる。今回は練習なので、bitFlyerFXの管理画面にログインして現在価格を確認して、そこから10~20%乖離した価格(間違って成約しない価格)を入れる。

5)size
注文数量(ロット数)。今回は、最低単位の0.01BTCだけ買いたいので、「0.01」を入れる。

他にも設定できるパラメーターはありますが、今回、指定する必須のパラメーターはこれだけです。

なお、どのようなパラーメーターを設定できるのかは、BitflyerのAPI仕様書を見ればわかります。前の記事でも言いましたが、なるべく自分でAPI仕様書を読むことに対して、抵抗感が無くなるようにしておきましょう。

買い注文のAPI仕様書(公式ドキュメント)

買い注文の価格について

もう少しレベルの高い自動売買BOTだと、指値の注文価格は当然、自動で設定します。例えば、API経由で現在の板情報を取得して、買い板の中で一番高い価格(買い気配値)を設定することが多いです。すでに当ブログでAPIの使い方を学んでくださった方は、大雑把なやり方のイメージは付くと思います。

ですが今回の記事では、BTC-FXの管理画面にログインして、自分の目で現在価格を確かめ、少し離したところに指値を入れることにします。この記事の執筆時点では、最終取引価格が90万500円なので、今回は10%以上離して、80万円に買い指値を入れてみます。

具体的なPythonコード

あまり前置きが長くなっても良くないので、いったん具体的なソースコードを全部見ておきましょう。少し長くなりますが、怖がらないで、まずはザっと意味を想像しながら、読んでみてください。


import hashlib
import hmac
import requests
import datetime
import json


api_key = "APIキーを入力"
api_secret = "APIシークレットを入力"

base_url = "https://api.bitflyer.jp"
path_url = "/v1/me/sendchildorder"
method = "POST"

timestamp = str(datetime.datetime.today())

param = {
	"product_code" : "FX_BTC_JPY",
	"child_order_type" : "LIMIT",
	"side" : "BUY",
	"price" : 指値価格を入力,
	"size" : 0.01,
}
body = json.dumps(param)

message = timestamp + method + path_url + body
signature = hmac.new(bytearray(api_secret.encode('utf-8')), message.encode('utf-8') , digestmod = hashlib.sha256 ).hexdigest()

headers = {
	'ACCESS-KEY' : api_key,
	'ACCESS-TIMESTAMP' : timestamp,
	'ACCESS-SIGN' : signature,
	'Content-Type' : 'application/json'
}

response = requests.post( base_url + path_url , data = body , headers = headers)
print( response.status_code )
print( response.json() )


今まで当ブログで紹介した中では、一番長いpythonコードが出てきました。「ほら…やっぱり、プログラムは難しいじゃないか…騙された….」と思った方もいるかもしれません。

ですが、上記をすべて自分で書く必要は全くありません。実は、「買い注文をするコード」を1行書くだけで実行してくれる便利な「ライブラリ」が存在するからです。

「なんだよ!だったら最初からそれを教えろよ!」と思うかもしれません。ちゃんと次の記事からはその方法を紹介します。しかし、1度だけでいいので、「自分でAPI経由で注文してみる」ということをやってみることをお勧めします。

応用力をつけて欲しい

CCXTなどの便利なライブラリは、結局のところ、内側でBitflyerのAPIを呼んでいるだけにすぎません。なので、内側で「どのようなことが行われているのか?」のイメージを掴んでおくことは、今後、自分で自動売買BOTを作る上で絶対に必要です。

そのイメージがないと、「ライブラリでどんなことが可能なのか?」「どのようなカスタマイズが可能なのか?」「なぜ自分のコードが動かないのか?」「エラーが出た場合、どこに原因がありそうか?」が全く理解できなくなり、応用ができなくなるからです。

内側で各取引所のAPIを呼んでいることを理解していれば、よりライブラリを便利に使いこなすことができます。BitflyerのAPIを直で使って注文を出すのは、今回の記事の1回限りなので、1度だけ勉強だと思って付き合ってみてください。

ソースコードの意味を解説

繰り返しになりますが、プログラム初心者の方は、最初から自分でソースコードを書く必要はありません。最初に目指すべきなのは、「ソースコードを自力で読めるようになること」「読めば、大体、何をやっているのか理解できるようになること」です。

読めるようになれば、ネットに落ちているソースコードを拾って、どの部分をカスタマイズすれば「やりたいこと」ができるのか、のアタリを付けることができます。

ここでも上記のソースコードの意味を上から純に説明していきます。

importするライブラリ

import hashlib
import hmac
import requests
import datetime
import json

まず最初にこのプログラムで必要なライブラリをインポートします。

hashlibとhmacは、APIシークレットを暗号化して送るために使うライブラリです。requestsはAPIリクエスト(https://api.bitflyer.jp/~)を送るためのライブラリです。

datetimeは現在時刻を取得するためのライブラリです。どの取引所でもAPIで注文を出すときには、現在時刻(タイムスタンプ)をリクエストに含めるのが普通です。またjsonは、指値価格や注文量などのパラーメータをJSON形式に変換するために使います。

必要な情報を揃える

api_key = "APIキーを入力"
api_secret = "APIシークレットを入力"

base_url = "https://api.bitflyer.jp"
path_url = "/v1/me/sendchildorder"
method = "POST"

ここは見れば意味がわかると思います。
今回のプログラムで必要なURLやAPIキーなどの情報を最初に変数に入れておきます。また今回はPOSTメソッドで通信するので、methodという変数も用意しておきます。

timestamp = str(datetime.datetime.today())

現在時刻を、datetime.datetime.today()で取得します。
この日付データを「文字」に変換するために、str()で囲っています。文字に変換しないとhttps通信で送れないからです。変換した結果を、「timestamp」という変数に入れておきます。これも後で使います。

注文に必要なパラメーターをセット

param = {
	"product_code" : "FX_BTC_JPY",
	"child_order_type" : "LIMIT",
	"side" : "BUY",
	"price" : 指値価格を入力,
	"size" : 0.01,
}
body = json.dumps(param)

これは、先ほど確認したように、bitFlyerのAPI注文で必要になるパラメーター(通貨の種類、指値/成行、ロング/ショート、指値価格、注文ロット数)です。

これをparamという変数にセットして、それをjson.dumps()でJSON形式に変換します。JSON形式にしないと、HTTPS通信のデータとして送れないからです。JSON形式に変換したデータは、bodyという変数に入れておきます。

これらのbodyのデータは、通信データの「ボディ」という部分に入れて送ります。メールの本文に該当するイメージです。

認証のための暗号文(署名)を作る

message = timestamp + method + path_url + body
signature = hmac.new(bytearray(api_secret.encode('utf-8')), message.encode('utf-8') , digestmod = hashlib.sha256 ).hexdigest()

最初に読んだとき、この部分が一番、「難しい…わからない…」と思った方が多いかもしれません。

この部分では、「この注文メッセージを作って送っているのが、本当にあなた本人である」「成りすましや通信途中での改竄がされていない」ということを証明するために、APIシークレットを鍵にして、本文を暗号化(署名)しています。

いわゆるメッセージ認証コード(HMAC)という認証のための暗号アルゴリズムですが、暗号分野に興味のある方以外は、深く理解する必要はありません。単に何となく「こう書くものなんだ」と考えておいてください。興味がある方は、「hmac() python」で検索すればわかります。

通信データのヘッダーを作る

headers = {
	'ACCESS-KEY' : api_key,
	'ACCESS-TIMESTAMP' : timestamp,
	'ACCESS-SIGN' : signature,
	'Content-Type' : 'application/json'
}

ここでは、通信データのヘッダーを作っています。
メールのヘッダーのようなものですね。ここに、「APIキー」「現在時刻」と、さきほどの「暗号署名」をセットします。これで通信文の準備はできたので、あとはbitFlyerのサーバーに送るだけです。

APIリクエストを送る

response = requests.post( base_url + path_url , data = body , headers = headers)
print( response.status_code )
print( response.json() )

requests()関数を使って、指定したURL(https://api.bitflyer.jp/~)宛に、さっき作った通信データ(bodyが注文情報、headersがAPIキーや署名)を、postメソッドで送っています。そしてサーバーから返ってきた情報を、responseという変数に入れています。

このrequests()は以前の記事でも何度も使いましたね。Pythonのプログラミングではめちゃくちゃよく使う関数なので、何となく慣れておきましょう。最後の2行は、bitFlyerのサーバーから返ってきた情報を、プロンプト(黒い画面)に表示するためのコードです。

実行してみよう!

上記のコードをエディタに打ち込んでください。
丸々コピペしてもいいですが、意味を考えながら自分の手で書き写した方が、プログラミングの上達は早くなります。

できたら、いつものように「test.py」というファイル名で保存して、Anacondaプロンプトで実行してみましょう。以下のように表示されれば成功です!

200
{'child_order_acceptance_id': 'JRF20180314-181041-090404'}

実際にbitFlyerのトレード画面にログインして注文状況を見てみましょう。ちゃんと注文が入っているのが確認できました。

今回はテストなので間違って刺さらないように、現在価格が90万円のところ、80万円で買い指値を入れました。普通に「×」ボタンを押せば、注文をキャンセルできます。

もし動かなかった場合

長いpythonのソースコードを自分で手打ちして動かした場合、1発では動かない方が普通なので安心してください。プログラミングはエラーを試行錯誤しながら修正する過程でしか上達しません。1発でエラーのないコードを書ける人はいません。

大抵は何かしらの打ち間違いなので、エラーをよく読んでください。

何行目のエラーかを確認する

「line 33」とあるので、33行目が何かしら間違ってるとわかります。
「name ‘signatuer’ is not defined」と書いてあります。名前がおかしいようですね。ここでは、「signature」のスペルミスでした。

ステータスコードを確認する

先頭に「404」と書かれています。
これはpythonのプログラム自体は正しく書けていて、通信データ自体は送れたものの、bitFlyerのサーバーから「404」エラーが返ってきていることを意味しています。

「404」はサーバー側で、リクエストURLが見つからなかった場合のステータスコードです。ここでは、指定したAPIのURLを書き間違ってました。ステータスコードを表示することで、エラーの原因を絞りこむことができます。

HTTPステータスコード一覧

最後に

前述のように実際に自動売買BOTを作る際には、外部の方が作ってくれている便利な「ライブラリ」を使います。例えば、次の記事からはCCXTというライブラリを使います。それを使えば、今回のような買い注文を出すコードは1行で書けます。

ただし知っておいて欲しいのは、ライブラリはbitFlyerの公式が提供しているAPIではないということです。そのため、利用は自己責任になります。皆が使っているようなライブラリで、かつgithubなどで全コードが公開されているものは、ある程度は安全だと思いますが、一応、危険性は理解しておいてください。

また便利なライブラリとはいっても、基本的には、内側で各取引所のAPIを叩いているだけです。なので、ライブラリを使っていて何か行き詰った場合は、「裏側でどのAPIを使ってるのか?」を考えたり調べたりしてみると、解決のヒントになります。

(練習問題)

自分で上記のコードをカスタマイズして、売り(ショート)注文を出してみてください!^^ ※ 指値価格を変え忘れるとすぐに約定してしまうので注意してね!