前回の記事の続きです。
前回までで、Bitflyerで自動的に売買シグナルを判定してエントリーし、さらに決済(利確・損切)まで自動で行うBOTを作ることができました。しかしこの自動売買BOTを実践で長時間、安定的に動かし続けるためには、「例外処理」が必要になります。
例外処理とは
簡単にいうと「サーバーエラー」の対策のことです。
自動売買BOTでは、pythonのプログラミングコード(構文や文法)自体に問題がなくても、外部のサーバーが原因で止まってしまうことがありえます。例えば、Bitflyerに買い注文を出したものの、Bitflyerのサーバーが混雑していてエラーになってしまったり、一時的にダウンしていて応答がなくなるような場合です。
このような場合に何も対策をしていないと、自動売買BOT自体もエラーを吐いて止まってしまいます。例えば、以下のような感じです。

↑ pythonで買い注文を出したところ、Bitflyerのサーバーから「500 Internal Server Error」(サーバーエラー)が返ってきたため、そこでプログラムの実行が停止されてしまった画面です。
キチンと例外処理をしていれば、外部サーバーから思わぬエラーが返ってきても、そこで自動売買BOTが停止することはなくなります。自動売買BOTがポジションを持ったままエラーを吐いて止まってしまうと、知らない間に損失が膨らみ続ける危険もあります。
特にBitflyerはサーバーから頻繁に500エラーが返ってくるため、きちんと例外処理を書くことが大切です。
例外処理の基本的な書き方
例外処理の基本的な構文は以下です。
try: #リクエスト通信 except 例外名 as e: #エラーが返ってきたときの処理
エラー処理が発生する可能性があるのは、リクエスト通信が必要な場面です。具体的にはAPIを使用する場面ですね。前回の記事で作成した自動売買BOTの例でいえば、以下のような場面です。
・Cryptowatchから価格データを取得する
・Bitflyerに買い・売り注文を出す
・Bitflyerから未約定の注文一覧を取得する
・Bitflyerから建玉の一覧を取得する
上記の場面では、外部サーバーとの通信が発生するため、すべて例外処理を書かなければなりません。逆にいえば、通信が発生する箇所にすべて例外処理を書けば、通信エラーで自動売買BOTが停止することはなくなります。
具体的なコード例
例えば、前回のCryptowatchから価格データを取得する関数に例外処理を入れてみましょう。前回までのコードは以下でした。
修正前
# Cryptowatchから価格を取得する関数
def get_price(min,i):
response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc", params = { "periods" : 60 })
data = response.json()
return { "close_time" : data["result"][str(min)][i][0],
"open_price" : data["result"][str(min)][i][1],
"high_price" : data["result"][str(min)][i][2],
"low_price" : data["result"][str(min)][i][3],
"close_price": data["result"][str(min)][i][4] }
ここに例外処理を入れてみましょう。
例外処理
# Cryptowatchから価格を取得する関数
def get_price(min,i):
while True:
try:
response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc", params = { "periods" : 60 }, timeout = 5)
response.raise_for_status()
data = response.json()
return { "close_time" : data["result"][str(min)][i][0],
"open_price" : data["result"][str(min)][i][1],
"high_price" : data["result"][str(min)][i][2],
"low_price" : data["result"][str(min)][i][3],
"close_price": data["result"][str(min)][i][4] }
except requests.exceptions.RequestException as e:
print("Cryptowatchの価格取得でエラー発生 : ",e)
print("10秒待機してやり直します")
time.sleep(10)
上記のコードでは、正常にデータを取得できれば価格データを返し、もし通信エラーなどの異常が発生した場合は、10秒間待機した後で、データの取得をやり直すようにプログラムしています。そのため、全体をwhile文で囲んでいます。
「try:」の部分で、requests()というリクエスト通信用の関数を使い、「except requests.exceptions.RequestException as e:」の部分で、通信エラーがあった場合の処理を記述しています。エラーがあった場合は、print(“Cryptowatchの価格取得でエラー発生”, e)でエラーの内容も表示するようにしています。
実行結果の例
これを実行すると、Cryptowatchのサーバーから通信エラーが返ってきても、BOTは停止することなく自動的に10秒待機して通信をやり直してくれます。

Bitflyerへの注文の例外処理
同じように、Bitflyerのサーバーと通信する箇所にも例外処理を入れてみましょう。この記事の自動売買BOTは、すべてCCXTライブラリ経由でBitflyerと通信しています。そのため、例外処理はCCXTライブラリで用意されている「ccxt.BaseError」を使います。
ここでは、手仕舞いの成行注文を出す箇所のコードを使って説明します。
修正前
order = bitflyer.create_order(
symbol = 'BTC/JPY',
type='market',
side='sell',
amount='0.01',
params = { "product_code" : "FX_BTC_JPY" })
flag["position"]["exist"] = False
time.sleep(30)
ここに同じように例外処理を入れると、以下のようになります。
例外処理
while True:
try:
order = bitflyer.create_order(
symbol = 'BTC/JPY',
type='market',
side='sell',
amount='0.01',
params = { "product_code" : "FX_BTC_JPY" })
flag["position"]["exist"] = False
time.sleep(30)
break
except ccxt.BaseError as e:
print("BitflyerのAPIでエラー発生",e)
print("注文の通信が失敗しました。30秒後に再トライします")
time.sleep(30)
通信に失敗した場合に、何度も自動でやり直しをさせたい場合は、このように「While文」で囲みます。While文で書くときは、「break」を書くのを忘れないようにしてください。breakは、成功したらwhile文を抜ける、という意味です。
やり直しをさせる必要がない場合は、While文で囲む必要はありません。
実行結果の例
同じように、すべての通信箇所に例外処理を書いておくとBitflyerの注文が通りにくい時間帯でも、BOTがエラーで停止することはなくなります。

「例外名」を自分で調べる方法
さきほどの構文の「except 例外名 as e:」の部分を、なぜ「except requests.exceptions.RequestException」と書くのか?疑問に思った方もいるかもしれません。これは、requests()ライブラリの仕様でそう決まっているからです。
つまりrequests()ライブラリを開発してくれた人たちが、そういう名前のエラー(例外)をあらかじめ用意してくれている、ということです。
例外の名前の部分は使用するライブラリによって違います。例えば、CCXTライブラリで例外処理を書くときは、「except ccxt.BaseError as e:」と書きます。大抵の場合は、「ライブラリ名 error handling」「ライブラリ名 例外処理」などで検索すると、何らかのヒントが見つかります。
例)
requests 例外処理
CCXT error handling
例外処理を入れた完成版のpythonコード
さて、それでは前回の記事のコードに例外処理を入れたバージョンを作ってみましょう! 以下がpythonコードの全文です。
import requests
from datetime import datetime
import time
import ccxt
bitflyer = ccxt.bitflyer()
bitflyer.apiKey = '**********'
bitflyer.secret = '**********'
# Cryptowatchから価格を取得する関数
def get_price(min,i):
while True:
try:
response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc", params = { "periods" : 60 }, timeout = 5)
response.raise_for_status()
data = response.json()
return { "close_time" : data["result"][str(min)][i][0],
"open_price" : data["result"][str(min)][i][1],
"high_price" : data["result"][str(min)][i][2],
"low_price" : data["result"][str(min)][i][3],
"close_price": data["result"][str(min)][i][4] }
except requests.exceptions.RequestException as e:
print("Cryptowatchの価格取得でエラー発生 : ",e)
print("10秒待機してやり直します")
time.sleep(10)
# 時間と始値・終値を表示する関数
def print_price( data ):
print( "時間: " + datetime.fromtimestamp(data["close_time"]).strftime('%Y/%m/%d %H:%M') + " 始値: " + str(data["open_price"]) + " 終値: " + str(data["close_price"]) )
# 各ローソク足が陽線・陰線の基準を満たしているか確認する関数
def check_candle( data,side ):
realbody_rate = abs(data["close_price"] - data["open_price"]) / (data["high_price"]-data["low_price"])
increase_rate = data["close_price"] / data["open_price"] - 1
if side == "buy":
if data["close_price"] < data["open_price"] : return False
elif increase_rate < 0.0003 : return False
elif realbody_rate < 0.5 : return False
else : return True
if side == "sell":
if data["close_price"] > data["open_price"] : return False
elif increase_rate > -0.0003 : return False
elif realbody_rate < 0.5 : return False
else : return True
# ローソク足が連続で上昇しているか確認する関数
def check_ascend( data,last_data ):
if data["open_price"] > last_data["open_price"] and data["close_price"] > last_data["close_price"]:
return True
else:
return False
# ローソク足が連続で下落しているか確認する関数
def check_descend( data,last_data ):
if data["open_price"] < last_data["open_price"] and data["close_price"] < last_data["close_price"]:
return True
else:
return False
# 買いシグナルが出たら指値で買い注文を出す関数
def buy_signal( data,last_data,flag ):
if flag["buy_signal"] == 0 and check_candle( data,"buy" ):
flag["buy_signal"] = 1
elif flag["buy_signal"] == 1 and check_candle( data,"buy" ) and check_ascend( data,last_data ):
flag["buy_signal"] = 2
elif flag["buy_signal"] == 2 and check_candle( data,"buy" ) and check_ascend( data,last_data ):
print("3本連続で陽線 なので" + str(data["close_price"]) + "で買い指値を入れます")
flag["buy_signal"] = 3
try:
order = bitflyer.create_order(
symbol = 'BTC/JPY',
type='limit',
side='buy',
price= data["close_price"],
amount='0.01',
params = { "product_code" : "FX_BTC_JPY" })
flag["order"]["exist"] = True
flag["order"]["side"] = "BUY"
time.sleep(30)
except ccxt.BaseError as e:
print("Bitflyerの注文APIでエラー発生",e)
print("注文が失敗しました")
else:
flag["buy_signal"] = 0
return flag
# 売りシグナルが出たら指値で売り注文を出す関数
def sell_signal( data,last_data,flag ):
if flag["sell_signal"] == 0 and check_candle( data,"sell" ):
flag["sell_signal"] = 1
elif flag["sell_signal"] == 1 and check_candle( data,"sell" ) and check_descend( data,last_data ):
flag["sell_signal"] = 2
elif flag["sell_signal"] == 2 and check_candle( data,"sell" ) and check_descend( data,last_data ):
print("3本連続で陰線 なので" + str(data["close_price"]) + "で売り指値を入れます")
flag["sell_signal"] = 3
try:
order = bitflyer.create_order(
symbol = 'BTC/JPY',
type='limit',
side='sell',
price= data["close_price"],
amount='0.01',
params = { "product_code" : "FX_BTC_JPY" })
flag["order"]["exist"] = True
flag["order"]["side"] = "SELL"
time.sleep(30)
except ccxt.BaseError as e:
print("BitflyerのAPIでエラー発生",e)
print("注文が失敗しました")
else:
flag["sell_signal"] = 0
return flag
# 手仕舞いのシグナルが出たら決済の成行注文を出す関数
def close_position( data,last_data,flag ):
if flag["position"]["side"] == "BUY":
if data["close_price"] < last_data["close_price"]:
print("前回の終値を下回ったので" + str(data["close_price"]) + "あたりで成行で決済します")
while True:
try:
order = bitflyer.create_order(
symbol = 'BTC/JPY',
type='market',
side='sell',
amount='0.01',
params = { "product_code" : "FX_BTC_JPY" })
flag["position"]["exist"] = False
time.sleep(30)
break
except ccxt.BaseError as e:
print("BitflyerのAPIでエラー発生",e)
print("注文の通信が失敗しました。30秒後に再トライします")
time.sleep(30)
if flag["position"]["side"] == "SELL":
if data["close_price"] > last_data["close_price"]:
print("前回の終値を上回ったので" + str(data["close_price"]) + "あたりで成行で決済します")
while True:
try:
order = bitflyer.create_order(
symbol = 'BTC/JPY',
type='market',
side='buy',
amount='0.01',
params = { "product_code" : "FX_BTC_JPY" })
flag["position"]["exist"] = False
time.sleep(30)
break
except ccxt.BaseError as e:
print("BitflyerのAPIでエラー発生",e)
print("注文の通信が失敗しました。30秒後に再トライします")
time.sleep(30)
return flag
# サーバーに出した注文が約定したかどうかチェックする関数
def 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["position"]["exist"] = True
flag["position"]["side"] = flag["order"]["side"]
else:
if orders:
print("まだ未約定の注文があります")
for o in orders:
print( o["id"] )
flag["order"]["count"] += 1
if flag["order"]["count"] > 6:
flag = cancel_order( orders,flag )
else:
print("注文が遅延しているようです")
return flag
# 注文をキャンセルする関数
def cancel_order( orders,flag ):
try:
for o in orders:
bitflyer.cancel_order(
symbol = "BTC/JPY",
id = o["id"],
params = { "product_code" : "FX_BTC_JPY" })
print("約定していない注文をキャンセルしました")
flag["order"]["count"] = 0
flag["order"]["exist"] = False
time.sleep(20)
position = bitflyer.private_get_getpositions( params = { "product_code" : "FX_BTC_JPY" })
if not position:
print("現在、未決済の建玉はありません")
else:
print("現在、まだ未決済の建玉があります")
flag["position"]["exist"] = True
flag["position"]["side"] = position[0]["side"]
except ccxt.BaseError as e:
print("BitflyerのAPIで問題発生 : ", e)
finally:
return flag
# ここからメイン
last_data = get_price(60,-2)
print_price( last_data )
time.sleep(10)
flag = {
"buy_signal":0,
"sell_signal":0,
"order":{
"exist" : False,
"side" : "",
"count" : 0
},
"position":{
"exist" : False,
"side" : ""
}
}
while True:
if flag["order"]["exist"]:
flag = check_order( flag )
data = get_price(60,-2)
if data["close_time"] != last_data["close_time"]:
print_price( data )
if flag["position"]["exist"]:
flag = close_position( data,last_data,flag )
else:
flag = buy_signal( data,last_data,flag )
flag = sell_signal( data,last_data,flag )
last_data["close_time"] = data["close_time"]
last_data["open_price"] = data["open_price"]
last_data["close_price"] = data["close_price"]
time.sleep(10)
Bitflyerのサーバーと通信する部分(買い注文・売り注文・手仕舞いの成行注文・キャンセル・注文一覧の取得・建玉一覧の取得)には、すべて例外処理を書いています。面倒ですが、これがBOTを安定的に動かす上では重要になります。
完成!
これで「最初の自動売買BOTを作ってみる!」の章は完成です。
この章を読んだ方は、以下のことができるようになったはずです。
1.数字で定義できる売買ロジックを考える
2.ローソク足の実体やヒゲの長さをシグナルにする
3.ローソク足のパターン(三兵)をシグナルにする
4.過去の価格データを使ってシグナルをテストする
5.売買シグナルで自動的にエントリーし、手仕舞いするBOTを作る
6.サーバーエラーで停止しないように例外処理を書く
ここまで勉強したことを応用してカスタマイズすれば、「3本以上連続で陰線だが、徐々に実体が短くなって出来高も減っている」という局面で、「実体が短くて長い下ヒゲの付いた陽線(ハンマー)が現れたら買いでエントリーする」というような、より実践的な売買ロジックも作れるでしょう。
※ ちなみに、これは「出来高・価格分析の完全ガイド」という本で紹介されている売買ロジックです。興味がある方は練習で実装してみてください。
次回以降の記事
次以降の章では、もう少し高度な「移動平均線」「MACD」などのトレンドフォロー指標、「RSI」「ストキャスティクス」などのオシレーターをpythonで実装する方法を解説します。
自動売買BOTなどのシステムトレードは、もともとチャートパターン(例えば、Wボトムやエリオット波動などの視覚的なもの)を認識するよりも、MACDヒストグラムやRSIなどの数値指標で売買シグナルを判断する方が得意です。そのため、自動売買BOTの精度を上げるためには、テクニカル指標を理解して実装できるようになることが必須です。
次の記事では、自動売買BOTと相性のいいテクニカル指標とその実装方法を解説していきます。


















