
仮想通貨bot 勉強記録⑭
~赤三兵・黒三兵botをテストネットで動かしてみる~
◆前回までのあらすじ
ロジックを使って注文・決済までするテストコードを作り、バックテストを行いました。
◆今回やること
・Bybitで動く赤三兵・黒三兵ロジックの自動取引botの試作版の作成
こちらを参考に、Bybitで動く自動取引botの試作版を作成します。
書いたコードはこちら
from datetime import datetime,timedelta
import time
from rich import print as pp
import pybybit
apis = [
'プライベートキー',
'シークレットキー'
]
bybit = pybybit.API(*apis, testnet=True)
#最新ローソク足を取得する関数
def get_price(min,i):
t = int((datetime.now() - timedelta(hours=1)).timestamp())#1時間前の時刻取得&変換
d = bybit.rest.inverse.public_kline_list(
symbol = "BTCUSD",
interval=min,
from_=t).json()
data = d['result'][i]
#返り値取得
return { "close_time" : data['open_time'],
"open_price" : float(data['open']),
"high_price" : float(data['high']),
"low_price" : float(data['low']),
"close_price" : float(data['close'])}
#画面出力
def print_price(data):
pp( " 時間: " + datetime.fromtimestamp(data['close_time']).strftime('%Y/%m/%d %H:%M')
+ " 始値: " + str(data['open_price'])
+ " 終値: " + str(data['close_price']))
#ローソク足の条件判別
def check_candle(data,side):
try:
#実体割合の算出
realbody_rate = abs(data["close_price"] - data["open_price"]) / (data["high_price"]-data["low_price"])
except ZeroDivisionError:
increase_rate = 0.1
try:
#実体の大きさの算出
increase_rate = (data["close_price"] / data["open_price"]) - 1
except ZeroDivisionError:
increase_rate = 0.0001
if side == "buy":
if data["close_price"] < data["open_price"] : return False #ローソク足が赤だったらFalse
elif increase_rate < 0.0001 : return False #実体の大きさが現在価格の0.01%未満ならFalse
elif realbody_rate < 0.1 : return False #実体の割合がローソク足の10%未満ならFalse
else : return True #上記すべて条件が当てはまらなければTrue
if side == "sell":
if data["close_price"] > data["open_price"] : return False #ローソク足が緑だったらFalse
elif increase_rate > -0.0001 : return False #実体の大きさが現在価格の0.01%未満ならFalse
elif realbody_rate < 0.1 : return False #実体の割合がローソク足の10%未満ならFalse
else : return True #上記すべて条件が当てはまらなければTrue
#ローソク足の連続上昇の判別
def check_ascend( data,last_data ):
#今回の始値が前回の始値を上回っている且つ今回の終値が前回の終値を上回っていればTure
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 ):
#今回の始値が前回の始値を下回っている且つ今回の終値が前回の終値を下回っていればTure
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" ):#陽線1本目
flag["buy_signal"] = 1
elif flag["buy_signal"] == 1 and check_candle( data,"buy" ) and check_ascend( data,last_data ):#陽線2本目
flag["buy_signal"] = 2
elif flag["buy_signal"] == 2 and check_candle( data,"buy" ) and check_ascend( data,last_data ):#陽線3本目
pp("3本連続で陽線 なので" + str(data["close_price"]) + "で買い指値")
flag["buy_signal"] = 3
#指値買い注文コード
order = bybit.rest.inverse.private_order_create(
side='Buy',
symbol='BTCUSD',
order_type='Limit',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["order"]["exist"] = True
flag["order"]["side"] = "BUY"
#陽線が途切れたらシグナルリセット
else:
flag["buy_signal"] = 0
return flag
#売りサイン・注文を出す関数
def sell_signal( data,last_data,flag ):
if flag["sell_signal"] == 0 and check_candle( data,"sell" ):#陰線1本目
flag["sell_signal"] = 1
elif flag["sell_signal"] == 1 and check_candle( data,"sell" ) and check_descend( data,last_data ):#陰線2本目
flag["sell_signal"] = 2
elif flag["sell_signal"] == 2 and check_candle( data,"sell" ) and check_descend( data,last_data ):#陰線3本目
pp("3本連続で陽線 なので" + str(data["close_price"]) + "で売り指値")
#指値売り注文コード
order = bybit.rest.inverse.private_order_create(
side='Sell',
symbol='BTCUSD',
order_type='Limit',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["order"]["exist"] = True
flag["order"]["side"] = "SELL"
#陰線が途切れたらシグナルリセット
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"]:
pp("前回の終値を下回ったので" + str(data["close_price"]) + "あたりで成行で決済します")
#成行売り注文コード
order = bybit.rest.inverse.private_order_create(
side='Sell',
symbol='BTCUSD',
order_type='Market',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["position"]["exist"] = False
if flag["position"]["side"] == "SELL":
if data["close_price"] > last_data["close_price"]:
pp("前回の終値を上回ったので" + str(data["close_price"]) + "あたりで成行で決済します")
#成行買い注文コード
order = bybit.rest.inverse.private_order_create(
side='Buy',
symbol='BTCUSD',
order_type='Market',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["position"]["exist"] = False
return flag
#注文状況確認用の関数
def check_order( flag ):
position = bybit.rest.inverse.private_position_list(symbol = 'BTCUSD').json()
position = position['result']['side']
orders = bybit.rest.inverse.private_order_list(symbol = 'BTCUSD').json()
orders = orders['result']['data']
if position != 'None':
pp("注文が約定しました!")
flag["order"]["exist"] = False
flag["order"]["count"] = 0
flag["position"]["exist"] = True
flag["position"]["side"] = flag["order"]["side"]
else:
if orders[0]['order_status'] == "New":
pp("まだ未約定の注文があります")
for o in orders:
if o['order_status'] == "New":
pp(o['order_id'])
flag["order"]["count"] += 1
if flag["order"]["count"] > 6:
flag = cancel_order( orders,flag )
else:
pp("注文が遅延しているようです")
return flag
#注文をキャンセルする関数
def cancel_order( orders,flag ):
orders = bybit.rest.inverse.private_order_list(symbol = 'BTCUSD').json()
orders = orders['result']['data']
if orders[0]['order_status'] == "New":
for o in orders:
if o['order_status'] == "New":
bybit.rest.inverse.private_order_cancel(
symbol = 'BTCUSD',
order_id = o['order_id'])
pp("約定していない注文をキャンセルしました")
flag["order"]["count"] = 0
flag["order"]["exist"] = False
time.sleep(20)
position = bybit.rest.inverse.private_position_list(symbol = 'BTCUSD').json()
position = position['result']['side']
if not position == 'None':
pp("現在、未決済の建玉はありません")
else:
pp
("現在、まだ未決済の建玉があります")
flag["position"]["exist"] = True
flag["position"]["side"] = position
return flag
#メイン関数
def main():
last_data = get_price(1,-2)
print_price( last_data )
flag = {
"buy_signal":0,
"sell_signal":0,
"order":{
"exist" : False,
"side" : "",
"count" : 0},
"position":{
"exist" : False,
"side" : ""}}
time.sleep(10)
while True:
if flag["order"]["exist"]:
flag = check_order( flag )
data = get_price(1,-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)
main()
いや~、激しく長くなりました。
めっちゃ苦戦したけど、慣れたらサクサク書けるようになるんだろうか。。。
それじゃ、解説していくぞい!
◆解説
・関数一覧
今回使った関数一覧です。
1つずつ見ていきまっせ~
※サブ関数の中に何度も出てくる変数flagは、main関数で定義しています。
・def get_price(min,i)
def get_price(min,i)
#1時間前の時刻取得&変換
t = int((datetime.now() - timedelta(hours=1)).timestamp())
d = bybit.rest.inverse.public_kline_list(
symbol = "BTCUSD",
interval=min,
from_=t).json()
data = d['result'][i]
#返り値取得
return { "close_time" : data['open_time'],
"open_price" : float(data['open']),
"high_price" : float(data['high']),
"low_price" : float(data['low']),
"close_price" : float(data['close'])}
いつも出てくる関数です。
2つの引数を設定しており、min:何分足か、i:何番目か を指定して呼び出します。
returnでローソク足情報を返します。
勉強記録⑪で解説してます。
・def print_price(data)
def print_price(data):
pp( " 時間: " + datetime.fromtimestamp(data['close_time']).strftime('%Y/%m/%d %H:%M')
+ " 始値: " + str(data['open_price'])
+ " 終値: " + str(data['close_price']))
これも毎回出てくる関数です。
引数dataを指定すると、時間・始値・終値を画面出力します。
勉強記録⑪で解説してます。
・def check_candle(data,side)
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 #ローソク足が赤だったらFalse
elif increase_rate < 0.0001 : return False #実体の大きさが現在価格の0.01%未満ならFalse
elif realbody_rate < 0.1 : return False #実体の割合がローソク足の10%未満ならFalse
else : return True #上記すべて条件が当てはまらなければTrue
if side == "sell":
if data["close_price"] > data["open_price"] : return False #ローソク足が緑だったらFalse
elif increase_rate > -0.0001 : return False #実体の大きさが現在価格の0.01%未満ならFalse
elif realbody_rate < 0.1 : return False #実体の割合がローソク足の10%未満ならFalse
else : return True #上記すべて条件が当てはまらなければTrue
コメントアウトで全部書いてます(笑)
勉強記録⑪で解説してます。
呼び出す際はdata,sideを指定する必要があります。
・def check_ascend( data,last_data )
#ローソク足の連続上昇の判別
def check_ascend( data,last_data ):
#今回の始値が前回の始値を上回っている且つ今回の終値が前回の終値を上回っていればTure
if data["open_price"] > last_data["open_price"] and data["close_price"] > last_data["close_price"]:
return True
else: return False
こちらも勉強記録⑪で解説してます。
呼び出す際はdata,last_dataを指定する必要があります。
・def check_descend( data,last_data )
#ローソク足の連続下降の判別
def check_descend( data,last_data ):
#今回の始値が前回の始値を下回っている且つ今回の終値が前回の終値を下回っていればTure
if data["open_price"] < last_data["open_price"] and data["close_price"] < last_data["close_price"]:
return True
else: return False
def check_ascend( data,last_data )の逆バージョンです。割愛。
・def buy_signal( data,last_data,flag )
#買いサイン・注文を出す関数
def buy_signal( data,last_data,flag ):
if flag["buy_signal"] == 0 and check_candle( data,"buy" ):#陽線1本目
flag["buy_signal"] = 1
elif flag["buy_signal"] == 1 and check_candle( data,"buy" ) and check_ascend( data,last_data ):#陽線2本目
flag["buy_signal"] = 2
elif flag["buy_signal"] == 2 and check_candle( data,"buy" ) and check_ascend( data,last_data ):#陽線3本目
pp("3本連続で陽線 なので" + str(data["close_price"]) + "で買い指値")
flag["buy_signal"] = 3
#指値買い注文コード
order = bybit.rest.inverse.private_order_create(
side='Buy',
symbol='BTCUSD',
order_type='Limit',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["order"]["exist"] = True
flag["order"]["side"] = "BUY"
#陽線が途切れたらシグナルリセット
else:
flag["buy_signal"] = 0
return flag
勉強記録⑬で解説してます。一応買い注文の部分だけ解説↓
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
elif flag["buy_signal"] == 2 and check_candle( data,"buy" ) and check_ascend( data,last_data ):#陽線3本目
pp("3本連続で陽線 なので" + str(data["close_price"]) + "で買い指値")
flag["buy_signal"] = 3
#指値買い注文コード
order = bybit.rest.inverse.private_order_create(
side='Buy',
symbol='BTCUSD',
order_type='Limit',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["order"]["exist"] = True
flag["order"]["side"] = "BUY"
3本連続でcheck_candleの条件を満たしたローソク足が出現すると、flag["buy_signal"] を 3に書き換え、bybitに指値で注文を出します。
注文を出したら、flag["order"]["exist"] を Trueに、flag["order"]["side"] を "BUY"に書き換えます。
買い注文コードは勉強記録⑥で使ったコードと同じです。
ちなみにexistっていうのは存在するっていう意味です。
プログラミングやってると英語の勉強にもなるんだぜ。
・def sell_signal( data,last_data,flag )
#売りサイン・注文を出す関数
def sell_signal( data,last_data,flag ):
if flag["sell_signal"] == 0 and check_candle( data,"sell" ):#陰線1本目
flag["sell_signal"] = 1
elif flag["sell_signal"] == 1 and check_candle( data,"sell" ) and check_descend( data,last_data ):#陰線2本目
flag["sell_signal"] = 2
elif flag["sell_signal"] == 2 and check_candle( data,"sell" ) and check_descend( data,last_data ):#陰線3本目
pp("3本連続で陰線 なので" + str(data["close_price"]) + "で売り指値")
#指値売り注文コード
order = bybit.rest.inverse.private_order_create(
side='Sell',
symbol='BTCUSD',
order_type='Limit',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["order"]["exist"] = True
flag["order"]["side"] = "SELL"
#陰線が途切れたらシグナルリセット
else:
flag["sell_signal"] = 0
return flag
def buy_signal( data,last_data,flag )の売りバージョンです。割愛。
・def close_position( data,last_data,flag )
#決済用の関数
def close_position( data,last_data,flag ):
if flag["position"]["side"] == "BUY":
if data["close_price"] < last_data["close_price"]:
pp("前回の終値を下回ったので" + str(data["close_price"]) + "あたりで成行で決済します")
#成行売り注文コード
order = bybit.rest.inverse.private_order_create(
side='Sell',
symbol='BTCUSD',
order_type='Market',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["position"]["exist"] = False
if flag["position"]["side"] == "SELL":
if data["close_price"] > last_data["close_price"]:
pp("前回の終値を上回ったので" + str(data["close_price"]) + "あたりで成行で決済します")
#成行買い注文コード
order = bybit.rest.inverse.private_order_create(
side='Buy',
symbol='BTCUSD',
order_type='Market',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["position"]["exist"] = False
return flag
決済注文用の関数です。
買いポジションの時は売り注文、売りポジションの時は買い注文を出します。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
if flag["position"]["side"] == "BUY":
if data["close_price"] < last_data["close_price"]:
pp("前回の終値を下回ったので" + str(data["close_price"]) + "あたりで成行で決済します")
#成行売り注文コード
order = bybit.rest.inverse.private_order_create(
side='Sell',
symbol='BTCUSD',
order_type='Market',
qty=1,
price=data["close_price"],
time_in_force='GoodTillCancel')
order
flag["position"]["exist"] = False
※買いポジションの時
flag["position"]["side"] が "BUY"で、今回の終値 < 前回の終値の時、画面出力と成行の売り注文を出します。
注文を出したら、flag["position"]["exist"] を Falseに書き換えます。
※売りポジションの時も同様です。
・def check_order( flag )
#注文状況確認用の関数
def check_order( flag ):
position = bybit.rest.inverse.private_position_list(symbol = 'BTCUSD').json()
position = position['result']['side']
orders = bybit.rest.inverse.private_order_list(symbol = 'BTCUSD').json()
orders = orders['result']['data']
if position != 'None':
pp("注文が約定しました!")
flag["order"]["exist"] = False
flag["order"]["count"] = 0
flag["position"]["exist"] = True
flag["position"]["side"] = flag["order"]["side"]
else:
if orders[0]['order_status'] == "New":
pp("まだ未約定の注文があります")
for o in orders:
if o['order_status'] == "New":
pp(o['order_id'])
flag["order"]["count"] += 1
if flag["order"]["count"] > 6:
flag = cancel_order( orders,flag )
else:
pp("注文が遅延しているようです")
return flag
Bybitに出した注文の状況を確認するための関数です。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
position = bybit.rest.inverse.private_position_list(symbol = 'BTCUSD').json()
position = position['result']['side']
orders = bybit.rest.inverse.private_order_list(symbol = 'BTCUSD').json()
orders = orders['result']['data']
まずBybitからポジションリストを取得し、positionに'Buy'か'Sell'か'None'のどれかの結果を入れておきます。
次にオーダーリストを取得し、ordersに入れておきます。
if position != 'None':
pp("注文が約定しました!")
flag["order"]["exist"] = False
flag["order"]["count"] = 0
flag["position"]["exist"] = True
flag["position"]["side"] = flag["order"]["side"]
positionが'None'ではない場合、BUYかSELLどちらかのポジションを持っているということなので、注文が約定したということになります。
そのため、flagの各変数にオーダー無し・注文ありと判断できるよう値を上書きします。
else:
if orders[0]['order_status'] == "New":
pp("まだ未約定の注文があります")
for o in orders:
if o['order_status'] == "New":
pp(o['order_id'])
flag["order"]["count"] += 1
if flag["order"]["count"] > 6:
flag = cancel_order( orders,flag )
else:
pp("注文が遅延しているようです")
return flag
ポジションが'None'で、注文履歴の最初の注文のorder_statusが'New'の場合、まだ未約定の注文が存在しています。
その場合、order_statusが'New'になっている注文のIDを画面出力します。
また、flag["order"]["count"]の値を+1して、繰り返し処理の中でflag["order"]["count"]が6になったらcancel_order( orders,flag )を呼び出して注文をキャンセルします。
ポジションが'None'で、注文履歴の最初の注文のorder_statusが'New'ではない場合、約定はしているがポジションはまだ解消されていない=遅延が発生しているということになり、"注文が遅延しているようです"と画面出力します。
・def cancel_order( orders,flag )
#注文をキャンセルする関数
def cancel_order( orders,flag ):
orders = bybit.rest.inverse.private_order_list(symbol = 'BTCUSD').json()
orders = orders['result']['data']
if orders[0]['order_status'] == "New":
for o in orders:
if o['order_status'] == "New":
bybit.rest.inverse.private_order_cancel(
symbol = 'BTCUSD',
order_id = o['order_id'])
pp("約定していない注文をキャンセルしました")
flag["order"]["count"] = 0
flag["order"]["exist"] = False
time.sleep(20)
position = bybit.rest.inverse.private_position_list(symbol = 'BTCUSD').json()
position = position['result']['side']
if not position == 'None':
pp("現在、未決済の建玉はありません")
else:
pp
("現在、まだ未決済の建玉があります")
flag["position"]["exist"] = True
flag["position"]["side"] = position[0]["side"]
return flag
最後のサブ関数です!
注文をキャンセルするための関数です。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
orders = bybit.rest.inverse.private_order_list(symbol = 'BTCUSD').json()
orders = orders['result']['data']
if orders[0]['order_status'] == "New":
for o in orders:
if o['order_status'] == "New":
bybit.rest.inverse.private_order_cancel(
symbol = 'BTCUSD',
order_id = o['order_id'])
pp("約定していない注文をキャンセルしました")
flag["order"]["count"] = 0
flag["order"]["exist"] = False
time.sleep(20)
まずBybitからオーダーリストを取得します。
リストの1番目の'order_status'が"New"の場合、リスト中の'order_status'が"New"になっている注文を全てキャンセルします。
その後キャンセルしたことを画面出力し、flag["order"]["count"] を 0、
flag["order"]["exist"] を Falseに書き換え、20秒待機します。
position = bybit.rest.inverse.private_position_list(symbol = 'BTCUSD').json()
position = position['result']['side']
if position == 'None':
pp("現在、未決済の建玉はありません")
else:
pp("現在、まだ未決済の建玉があります")
flag["position"]["exist"] = True
flag["position"]["side"] = position
return flag
次にポジションの確認をします。
Bybitからポジションリストを取得し、
position が 'None'の場合、"現在、未決済の建玉はありません"の画面出力します。
それ以外の場合、"現在、まだ未決済の建玉があります"と画面出力し、flag["position"]["exist"] を True、flag["position"]["side"] を positionの値に書き換えます。
・def main()
やっとメイン関数まで来ました。
#メイン関数
def main():
last_data = get_price(1,-2)
print_price( last_data )
flag = {
"buy_signal":0,
"sell_signal":0,
"order":{
"exist" : False,
"side" : "",
"count" : 0},
"position":{
"exist" : False,
"side" : ""}}
time.sleep(10)
while True:
if flag["order"]["exist"]:
flag = check_order( flag )
data = get_price(1,-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)
main()
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
last_data = get_price(1,-2)
print_price( last_data )
flag = {
"buy_signal":0,
"sell_signal":0,
"order":{
"exist" : False,
"side" : "",
"count" : 0},
"position":{
"exist" : False,
"side" : ""}}
time.sleep(10)
まずlast_dataにget_price(1,-2)から得た返り値を入れて、print_price( last_data )で確定済みの最新ローソク足情報を画面出力します。
次にflagの初期値を定義しておきます。ここ重要です!!
サブ関数で何度もreturn flagと出てきましたが、ここのflagに値を入力するための返り値です。
while True:
if flag["order"]["exist"]:
flag = check_order( flag )
data = get_price(1,-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)
メインのループ部分です。
まず、flag["order"]["exist"]を確認し、Trueなら flag に check_order( flag )からの返り値を入れます。Falseなら何もしません。
次に、data に get_price(1,-2)からの返り値(最新のローソク足)を入れます。
last_dataとdataの"close_time"が違う場合(ローソク足の時間が更新された時)、print_price( data )で最新のローソク足を画面出力します。
更にflag["position"]["exist"]がTrueの場合、flag に close_position( data,last_data,flag )からの返り値を入れます。
flag["position"]["exist"]がFalseの場合、flag に buy_signal( data,last_data,flag )とsell_signal( data,last_data,flag )からの返り値を入れ、last_dataの各値をdataの値に書き換えます。
フローチャート作ってみました↓
解説終わり!!
最後のmain()でメイン関数が実行されます。
実行結果はこんな感じ↓
・・・・・・
エラー出るんかいッ!
一応動くんですが、check_candle関数の計算の部分でZeroDivisionErrorというエラーが出てしまうことがあります。初めて見た。
少し調べたら原因が分かったので、次回このエラー対策を施したコードを書きます。
長くなってしまったので今回はここまで。