Bot

仮想通貨botの開発を本格的に始めてみる#13(2023/9/13)

前回の内容に引き続き、仮想通貨botの開発状況をまとめていきます。

前回は「買いシグナルの点灯」ができるようになりました。

今回はこちらの記事を参考にして、過去の価格データを使ってコードが正しく動くかどうかを検証する方法を学んでいきます。

実行するコードもかなり長くなってきたため、新しく分かったことや躓いたこととそれへの対策を中心にまとめていきます。

目的の定義

今回の目的は「bitFlyerの過去データを用いて、赤三兵の買いシグナルを点灯させるコードが正しく動くかどうかを確認すること」です。

検証に使うデータは過去500件分とします。

つまり、「過去500件分の取引データを獲得する」→「赤三兵の売買ロジックのコードに読み込ませる」ことができれば良いということになります。

実行したコードはこちら。

import requests
from datetime import datetime
import time

response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc",params = { "periods" : 60 })
#APIデータを呼ぶのは1回だけ

def get_price(min,i):
	data = response.json()
	last_data = data["result"][str(min)][i]

	return { "close_time" : last_data[0],
		"open_price" : last_data[1],
		"high_price" : last_data[2],
		"low_price" : last_data[3],
		"close_price":last_data[4] }


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 ):
	realbody_rate = abs(data["close_price"] - data["open_price"]) + 1 / (data["high_price"]-data["low_price"] + 1) 
	increase_rate = data["close_price"] / data["open_price"] - 1
        

	if data["close_price"] < data["open_price"] : return False
	elif increase_rate < 0.0005 : 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


last_data = get_price(60,0)
print_price( last_data )
time.sleep(10)

flag = 0
i = 1

while i < 500:	
	data = get_price(60,i)
	
	if data["close_time"] != last_data["close_time"]:
		print_price( data )
		
		if flag == 0 and check_candle( data ):
			flag = 1
		elif flag == 1 and check_candle( data )  and check_ascend( data,last_data ):
			print("2本連続で陽線")
			flag = 2
		elif flag == 2 and check_candle( data )  and check_ascend( data,last_data ):
			print("3本連続で陽線 なので 買い!")
			flag = 3
		else:
			flag = 0
		
		last_data["close_time"] = data["close_time"]
		last_data["open_price"] = data["open_price"]
		last_data["close_price"] = data["close_price"]
	
	i += 1
	time.sleep(0)

0除算への対策

今回も、0除算には悩まされました。

本家の解説記事に掲載されていたコードを実行しても、またしてもZeroDivisionErrorが発生してしまったのです。

そこで、ネットに上がっている記事を参考にして「if文」「例外処理」「ラムダ関数」「デコレータ」などの回避方法を試してみました。

しかし、新たなエラー(TypeError: unsupported operand type(s) for /: 'str' and 'int')が出たり、関数の中でコードの実装ができなかったりと、根本的な解決には至らず,,,

最終的には、計算式に1を足すことで0で割るという状況を回避するという力技にうってでることにしました。

ビットコインのように価格が高いトークンであればこの方法でも良いかもしれませんが、もっと価格が低いトークンの場合、この方法は使えない可能性が高いです。

また、割られる数にも1を足していますが、状況によってはこれも不要かもしれません。

def check_candle( data ):
	realbody_rate = abs(data["close_price"] - data["open_price"]) + 1 / (data["high_price"]-data["low_price"] + 1) 
	increase_rate = data["close_price"] / data["open_price"] - 1

ここまでやって、ようやくコードをまともに実行することができるようになりました。

APIデータを呼ぶのは1回だけ

response = requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc",params = { "periods" : 60 })
#APIデータを呼ぶのは1回だけ

今回使うのは過去のデータだけです。

リアルタイムで情報を獲得する必要がないため、実践のデータと異なり、価格データを呼び出すのは1回だけでOKです。

そのため、APIデータを呼び出すためのコードは、get_price()関数から外して配置しています。

while文の修正

last_data = get_price(60,0)
print_price( last_data )
time.sleep(10)

flag = 0
i = 1

while i < 500:	
	data = get_price(60,i)
               〜中略〜
                i += 1
	time.sleep(0)

過去のデータ500件分を使いたいので、while文を修正しています。

今回は、上記のように記述することで500回目のループで止まるように設定しています。

過去データの検証では、獲得したデータを古いものを一番最初に扱います。

そのため、ループが繰り返される度にAPIデータを一つずつ順番に変えることができるようにします。

今回は、i += 1という記述をすることでループ処理のたびにiを一つずつ増やすように設定しています。

また、データも一括で獲得しているため、ループの待機時間も不要です。

そのため、time.sleep()の中も0を設定しています。(待機時間は0秒でOK)

i = 1と設定しているのは、ループ処理の開始地点を設定するためです。

last_data = get_price(60,0)という設定は、「初めに1番目のデータを参照しなさい」という意味です。

この点については、こちらの記事の「配列値とオブジェクト値」で解説しています。

他の箇所の修正

def get_price(min,i):
	data = response.json()
	last_data = data["result"][str(min)][i]

メインの処理の変更を受けて、get_price()関数にiという引数(ひきすう)を追加しました。

この書き換えによって、指定されたi番目のデータを返す処理をするようにしています。

まとめ

正直、完全に理解したとは言い難い仕上がりです。

細かい部分で理解できていないことはありますが、コードを繰り返し書くことで実践を伴った理解をしていけば良いかな、くらいの感覚です。

今回は、特にZero DivisionErrorについて苦しめられました。

そもそもの処理が少し複雑であるため、関数内部の書き換えを行うのが良いのか、外付けのコードで処理をするのが良いのか分からなかったことが原因です。

また、関数内部の書き換えを行う際も、どんな書き方ならコードが正しく実行されるのかを考えながら作業を進める必要があるため、大元のコードが簡単な構造になっていることが重要であるということも改めて実感しました。

分からないことがあれば、分かっているところまで戻って調べる。

単純な構造のコードを実行してみて、構造と動作を理解する。

そういうものだと思って、とりあえず進めてみる。

どちらも併せて行うことで、コードをいじった実感を持ちながら学習を進めることが大事なのかな、と思います。

そろそろ、学習を始めて2週間が経ちます。

仮想通貨botの開発を宣言してから、まずまずの進み具合ですね。

このままコツコツと実践を積んでいきます。

-Bot