Pythonを使ったbitcoinアービトラージの(ほぼ)自動売買プログラムが完成しました!その④

5月 5, 2020

[2020/5/5追記]ここで紹介しているプログラムは最終版ではありません。あくまで参考程度にご参照ください。最終版のプログラムは販売する形で提供させていただくことにいたしました。詳しくはこちら→https://tsurezure.info/arbitrage/index.php/2020/05/05/post-496/


こちらの記事でPythonを使ったbitcoinアービトラージの(ほぼ)自動売買プログラムのバージョン3を公開しましたが、解説記事を出さずにバージョン4まで来てしまいました。今回は主に下記をアップデートしました。解説するする詐欺ですみません。。
・約定頻度に応じてエントリー差額を上げたり下げたりする
・約定履歴をGoogleスプレッドシートに書き出す
→スプレッドシート側に関数組んで自動で損益計算できるようになりました。ただまれに書き出しに失敗してしまうので対策検討中です。→対策しました。
・bitbankでの約定からGMOでの発注までだいたい1秒以内のラグで行えるようになりました

それでは今回のソースコードのご紹介です。 差分を表示してくれるサイトを見つけたのでそちらも参考にしていただければと思います。
https://difff.jp/

import time
from pprint import pprint
import requests
import json
import hmac
import hashlib
from datetime import datetime
from datetime import timedelta
import python_bitbankcc
import winsound
import sys
import os
import csv
from oauth2client.service_account import ServiceAccountCredentials
from httplib2 import Http
import gspread
filepath = os.path.dirname(os.path.abspath(__file__))+'\\'
gfile = ''
def Gspreadsheet():
	global gfile
	scopes = ['https://www.googleapis.com/auth/spreadsheets']
	json_file = filepath+'************************************'
	credentials = ServiceAccountCredentials.from_json_keyfile_name(json_file, scopes=scopes)
	http_auth = credentials.authorize(Http())
	doc_id = '************************************'
	client = gspread.authorize(credentials)
	gfile   = client.open_by_key(doc_id)#読み書きするgoogle spreadsheet
order_amount = 0
active_order = 0 #注文ID
trade_id = 0 #取引ID
order_i = 0
prev_trade_id = 0
current_BTC = 0
bitbank_JPY = 0
prev_BTC = 0
bitbank_limit_order = 0
prev_bitbank_limit_order = 0
pair = 'btc_jpy'
JPYasset=0
BTCasset=1	
max_count = 5
count = max_count-1
Gspreadsheet()
SUM_GMObitbank = 0
margin = 0
margin_min = 0
margin_up = 0
bitbank_amount = 0
#GMO
apiKey    = '************************************'
secretKey = '************************************'		
endPoint  = 'https://api.coin.z.com/private'
def GMOPrvCommon(method, path, text):
	timestamp = '{0}000'.format(int(time.mktime(datetime.now().timetuple())))
	text = timestamp + text
	sign = hmac.new(bytes(secretKey.encode('ascii')), bytes(text.encode('ascii')), hashlib.sha256).hexdigest()		
	headers = {
	    "API-KEY": apiKey,
	    "API-TIMESTAMP": timestamp,
	    "API-SIGN": sign
	}
	return headers
def GMO_order(order_amount,side):
	method    = 'POST'
	path      = '/v1/order'
	reqBody = {
	    "symbol": "BTC",
	    "side": side,
	    "executionType": "MARKET",
	    "size": order_amount
	}			
	text = method + path + json.dumps(reqBody)
	i = 0
	while i == 0:
		headers = GMOPrvCommon(method, path, text)
		res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody))
		json_data = res.json()
		#print (json_data)
		if json_data["status"] == 0:
			print(json_data)
			print('買い注文完了しました')
			GMO_order_id = json_data["data"]
			time.sleep(0.3)
			GsheetGMO_History(GMO_order_id)
			i = 1
		else:
			print('買い注文失敗しました')
			time.sleep(0.3)				
	
def GMO_asset(pair):
    i = 0
    while i == 0:
	    try:
	    	method    = 'GET'
	    	path      = '/v1/account/assets'
	    	text = method + path
	    	sign = hmac.new(bytes(secretKey.encode('ascii')), bytes(text.encode('ascii')), hashlib.sha256).hexdigest()
	    	headers = GMOPrvCommon(method, path, text)    
	    	response = requests.get(endPoint + path, headers=headers)
	    	json_data = response.json()
	    	GMO_asset = json_data['data'][pair]['available']
	    	i = 1
	    except Exception as e:
	    	#print(e)
	    	#CSVwrite([e])
	    	print('データ取得に失敗しました。リトライします')
	    	time.sleep(1)
	    	continue
    return float(GMO_asset)
    
def GMO_CannotBuy():
	current_BTC_price = int(python_bitbankcc.public().get_ticker('btc_jpy')["sell"])
	GMO_JPYasset=GMO_asset(JPYasset)
	###print('JPY資金=',GMO_JPYasset)
	GsheetUpdate('B12',str(GMO_JPYasset))
	if bitbank_amount * current_BTC_price * 2 > GMO_JPYasset:
		print('JPY資金不足です')
		#CSVwrite(['JPY資金不足です'])
		prv_set.cancel_order(pair, active_order)
		print('注文をキャンセルしました')
		return True
	return False
def PriceOrder(order_amount, side):
	endPoint = 'https://api.coin.z.com/public'
	path     = '/v1/orderbooks?symbol=BTC'
	response = requests.get(endPoint + path)
	json_data = response.json()
	i = 0
	size_sum = 0
	price_sum = 0
	taker_price = 0
	while size_sum < order_amount:
		j = 0
		while j == 0:
			try:
				size_sum = size_sum + float(json_data["data"][side][i]["size"])
				j = 1
			except Exception as e:
				#print(e)
				#CSVwrite([e])
				print('データ取得に失敗しました。リトライします')
				time.sleep(1)
				response = requests.get(endPoint + path)
				json_data = response.json()
		price_sum = price_sum + float(json_data["data"][side][i]["price"])
		taker_price = taker_price + float(json_data["data"][side][i]["price"]) * (float(json_data["data"][side][i]["size"]))
		i = i + 1
	delta = size_sum - order_amount
	return taker_price - float(json_data["data"][side][i-1]["price"]) * delta
def GMO_OrderCheck():
	global prev_trade_id
	global margin
	global margin_up
	global done_order
	global order_i
	global bb_history
	done_order = prv_set.get_trade_history(pair,'20')['trades']	
	i = 0
	#print('done_trade_id=',done_order[i]['trade_id'])
	while prev_trade_id != done_order[i]['trade_id']:
		order_i = i
		order_amount = done_order[i]['amount']
		#print('done_order_id[',i,']=',done_order[i])
		#CSVwrite(['done_order_id[',i,']=',done_order[i]])
		print(order_amount,'BTC','売り注文が約定しました!',)
		print('約定した分だけGMOコインで成行で買います')
		#CSVwrite(['GMOで購入', order_amount])
		bb_history = done_order[i]
		GMO_order(order_amount,"BUY")
		i = i + 1
	if i > 0:
		Sound2()
		GsheetUpdate('B9',str(dt))
		GsheetUpdate('B3',str(margin + margin_up))
		print('条件少し厳しくします(+',margin_up,'円)')
		margin = margin + margin_up
		time.sleep(5)
	prev_trade_id = done_order[0]['trade_id']
	#print('prev_trade_id=',prev_trade_id)	
	
def GMOStatus():
	i = 0
	while i == 0:
		try:
			endPoint = 'https://api.coin.z.com/public'
			path     = '/v1/status'
			response = requests.get(endPoint + path)
			json_data = response.json()["data"]["status"]
			if json_data == 'OPEN':
				i = 1
			else:	
				print('GMOコインメンテナンス中です')
				print('')
				print('')
				prv_set.cancel_order(pair, active_order)
				#print('order canceled. order_id =', active_order)		
				time.sleep(10)	
				dt = datetime.now()
				print(dt)
		except Exception as e:
			print('GMOコインメンテナンス中です')
			print('')
			print('')
			#print(e)
			#CSVwrite([e])
			#print('retry json_data')
			time.sleep(10)
			dt = datetime.now()
			print(dt)
		
	return
	
#bitbank
class BitBankPubAPI:
	def get_ticker(pair):
		i = 0
		while i == 0:
			pub = python_bitbankcc.public()
			try:
				value = pub.get_ticker(pair)
				i = 1
			except Exception as e:
				print('データ取得に失敗しました。リトライします')
				#print(e)
				#CSVwrite([e])
				#return None
		return value
class BitBankPrvAPI:
	def __init__(self):
		API_KEY = '************************************'
		API_SECRET = '************************************'
		self.prv = python_bitbankcc.private(API_KEY, API_SECRET)
	def get_asset(self):
		i = 0
		while i == 0:
			try:
				value = self.prv.get_asset()
				i = 1
			except Exception as e:
				print('bitbankのデータ取得に失敗しました。リトライします')
				time.sleep(1)
		return value
	def order(self, pair, price, amount, side, order_type):
		i = 0
		while i == 0:
			try:
				value = self.prv.order(pair, price, amount, side, order_type)
				i = 1
			except Exception as e:
				print('bitbankの発注に失敗しました。リトライします')
				time.sleep(1)
		return value
	def get_active_order(self, pair):
		try:
			value = self.prv.get_active_orders(pair)
			value = value['orders'][0]["order_id"]
			return value
		except Exception as e:
			#print(e)
			#CSVwrite([e])
			return 0
	def cancel_order(self, pair, active_order):
		try:
			value = self.prv.cancel_order(pair, active_order)
			return value
		except Exception as e:
			#print(e)
			#CSVwrite([e])
			return None
	def get_trade_history(self, pair, order_count):
		i = 0
		while i == 0:
			try:
				value = self.prv.get_trade_history(pair, order_count)
				i = 1
			except Exception as e:
				print('約定履歴の取得に失敗しました。リトライします')
				time.sleep(1)
		return value
prv_set = BitBankPrvAPI()
pub_set = BitBankPubAPI()
def bitbank_Order(price, side):
	global prev_BTC
	global current_BTC
	global prev_bitbank_limit_order
	current_BTC = prv_set.get_asset()['assets'][1]['onhand_amount']
	prev_BTC = current_BTC
	#print('BTC=',current_BTC)
	prev_bitbank_limit_order = bitbank_limit_order
	amount = bitbank_amount
	order_type = 'limit'
	prv_set.order(pair, price, amount, side, order_type)
	#print(pair, price, amount, side, order_type)
	print('条件成立したので', price,'円で')
	print(amount,'BTC 売り注文出します')
	#CSVwrite(['price-amount-side-order_type', price, amount, side, order_type])
def Sound():
	winsound.Beep(800, 300)
	time.sleep(0.3)	
	winsound.Beep(800, 300)
	time.sleep(0.3)
	winsound.Beep(800, 300)
	time.sleep(0.3)
def Sound2():	
	winsound.Beep(1000, 1500)
def Sound3():
	winsound.Beep(900, 500)
	winsound.Beep(1000, 500)
def GsheetUpdate(cell, data):
	#print('GsheetUpdate',' cell=',cell,' data=',data)
	i = 0
	while i == 0:
	    try:
	    	worksheet.update_acell(cell,data)
	    	i = 1
	    except Exception as e:
	    	#print(e)
	    	#CSVwrite([e])
	    	#print('retry update_acel')
	    	Gspreadsheet()
	    	time.sleep(1)
	    	continue
def GsheetCellGet(cell):
	i = 0
	while i == 0:
	    try:
	    	data = worksheet.acell(cell).value
	    	i = 1
	    except Exception as e:
	    	Gspreadsheet()
	    	time.sleep(1)
	    	continue
	return data
def GMO_TradeHistory():
    i = 0
    while i == 0:
	    try:
	    	method    = 'GET'
	    	path      = '/v1/latestExecutions'
	    	text = method + path
	    	sign = hmac.new(bytes(secretKey.encode('ascii')), bytes(text.encode('ascii')), hashlib.sha256).hexdigest()
	    	headers = GMOPrvCommon(method, path, text)
	    	parameters = {"symbol": "BTC","page": 1,"count": 10}
	    	response = requests.get(endPoint + path, headers=headers, params=parameters)
	    	json_data = response.json()
	    	i = 1
	    except Exception as e:
	    	#print(e)
	    	#CSVwrite([e])
	    	print('データ取得に失敗しました。リトライします')
	    	time.sleep(1)
	    	continue
    return json_data
def GsheetGMO_History(GMO_order_id):
	GMO_order_id = int(GMO_order_id)
	j = 0
	k = 0
	GMOtradehistory = GMO_TradeHistory()
	while j == 0:
		CSVwrite(['while j == ',j])
		try:						
			current_order_id = int(GMOtradehistory['data']['list'][k]['orderId'])
			CSVwrite(['current_order_id = ',current_order_id])
			CSVwrite(['k1 = ',k])
			history_sheet = gfile.worksheet("trade_history")
			cell = history_sheet.find("last")
			GMO_list = []
			while GMO_order_id == current_order_id:
				CSVwrite(['k2 = ',k])
				GMOhistory = GMOtradehistory['data']['list'][k]
				CSVwrite(['GMOhistory = ',GMOhistory])
				timestamp = GMOhistory["timestamp"]
				timestamp = datetime.fromisoformat(timestamp[:-1])
				timestamp = timestamp + timedelta(hours=9)			
				GMOsize = round(float(GMOhistory["size"]),4)
				GMOprice = int(GMOhistory["price"])
				GMOfee = 0.0005*GMOsize*GMOprice
				GMO_list = GMO_list + ['GMO', GMOhistory["executionId"], GMOhistory["orderId"], GMOhistory["side"], GMOsize, GMOprice, 'NA', GMOfee, str(timestamp)[:19]]
				k = k+1
				current_order_id = int(GMOtradehistory['data']['list'][k]['orderId'])
			CSVwrite(['bb_history = ',bb_history])
			BBsize = round(float(bb_history['amount']),4)
			BBfee = float(bb_history['fee_amount_quote'])
			BBprice = int(bb_history['price'])
			BBtimestamp = bb_history['executed_at']
			BBtimestamp = round(BBtimestamp/1000)
			BBtimestamp = datetime.fromtimestamp(BBtimestamp)
			BB_list = ['bitbank',bb_history['trade_id'],bb_history['order_id'],bb_history['side'],BBsize,BBprice,bb_history['maker_taker'],BBfee,str(BBtimestamp)]
			GMO_list = GMO_list + BB_list + ["last","","","","","","","","2222-22-22 22:22:22"]
			CSVwrite(['GMO_list = ',GMO_list])
			cell_list = history_sheet.range(cell.row, 1, cell.row+k+1, 9)
			CSVwrite(['cell_list = ',cell_list])
			for i,cell in enumerate(cell_list):
			    cell.value = GMO_list[i]
			history_sheet.update_cells(cell_list)
			j = 1
		except Exception as e:
			print(e)
			print('約定結果記録に失敗しました。リトライします')
			CSVwrite([e])
			CSVwrite(['約定結果記録に失敗しました。リトライします'])
			time.sleep(1)
			continue
def CSVwrite(csvdata):
    i = 0
    while i == 0:
    	try:
    		with open(filepath+'debug_log.csv', 'a', newline="") as f:		
    			writer = csv.writer(f)									
    			writer.writerow(csvdata)
    			i = 1		
    	except Exception as e:
        	#print(e)
        	#CSVwrite([e])
        	#print('retry CSV write')
        	time.sleep(1)
	        continue			
active_order = prv_set.get_active_order(pair)
prv_set.cancel_order(pair, active_order)
prev_trade_id = prv_set.get_trade_history(pair,'1')['trades'][0]['trade_id']
#print('prev_trade_id=',prev_trade_id)
while 1:
	print('')
	dt = datetime.now()
	print(dt)
	GMOStatus()
	GMO_OrderCheck()
	active_order = prv_set.get_active_order(pair)
	#print('active_order = ',active_order)
	if active_order == 0:
		print('現在発注中の注文はありません')
		print(margin,'円以上の差額が開いたら発注します')	
		print('発注数量=', bitbank_amount,'BTC')	
	else:
		print('現在',prev_bitbank_limit_order,'円で売り注文発注中')
		print('発注条件=',margin,'円以上の差額')
		print('発注数量=', bitbank_amount,'BTC')
	#CSVwrite([dt])
	
	#Googleスプレッドシートでパラメータ取得
	#print('count=',count)
	GMO_OrderCheck()
	count = count + 1
	if count == max_count:
		Gspreadsheet()
		count = 0
		i = 0
		while i == 0:
			try:
				worksheet  = gfile.sheet1
				i = 1
			except Exception as e:
				#print(e)
				#CSVwrite([e])
				#print('retry gfile.sheet1')
				Gspreadsheet()
				continue
		#print('Loading OK')
		cell = GsheetCellGet('B1')
		if cell == '0':
			#print('cell=0')
			prv_set.cancel_order(pair, active_order)
			#print('order canceled. order_id =', active_order)
			time.sleep(10)
			count=max_count-1
			continue
		bitbank_amount = float(GsheetCellGet('B2'))       #発注数量
		#print('発注数量=', bitbank_amount,'BTC')
		margin_change = float(GsheetCellGet('B16'))
		margin = int(GsheetCellGet('B3'))	#差額計算時の調整用
		margin_min = int(GsheetCellGet('B17'))	#差額計算時の調整用
		margin_up = int(GsheetCellGet('B18'))	#差額計算時の調整用
		margin_down = int(GsheetCellGet('B19'))	#差額計算時の調整用
		if margin_change > 0.02 and margin > margin_min:
			GsheetUpdate('B3',str(margin - margin_down))
			print('なかなか約定しないので条件緩和します')
			print('(-',margin_down,'円)')
			print('')
			print(margin - margin_down,'円以上の差額が開いたら発注します')
			margin = margin - margin_down
		margin = int(GsheetCellGet('B3'))	#差額計算時の調整用
		buffer = int(GsheetCellGet('B4'))	#指値注文時の価格調整用
		GMO_OrderCheck()
		GsheetUpdate('B5',str(dt))
		GsheetUpdate('B6',str(SUM_GMObitbank))
		GMO_OrderCheck()
		GsheetUpdate('B7',str(current_BTC))
		GsheetUpdate('B11',str(bitbank_JPY))
		GsheetUpdate('B13',str(prev_bitbank_limit_order))
		
	#売却余力確認
	GMO_OrderCheck()
	current_BTC = prv_set.get_asset()['assets'][1]['onhand_amount']	
	bitbank_JPY = prv_set.get_asset()['assets'][0]['onhand_amount']	
	G_current_BTC = GMO_asset(BTCasset)
	time.sleep(0.1)
	SUM_GMObitbank = float(current_BTC) + float(G_current_BTC)	
	#print('SUM GMO and bitbank ',SUM_GMObitbank)
	if bitbank_amount > float(current_BTC):
		prv_set.cancel_order(pair, active_order)
		#print('order canceled. order_id =', active_order)
		print('売却BTC不足です')
		#CSVwrite(['売却BTC不足です'])
		GMO_OrderCheck()
		time.sleep(10)
		continue
	
	#購入余力確認
	if GMO_CannotBuy():
		GMO_OrderCheck()
		count = max_count-1
		time.sleep(10)
		continue
	
	#差額計算
	GMO_OrderCheck()
	price_order = PriceOrder(1, 'asks')
	bitbank_limit_order = int(python_bitbankcc.public().get_ticker('btc_jpy')["sell"])    #※指値でMaker取引をするためbuyではなくsell(売り注文)の値を取得
	GsheetUpdate('B13',str(bitbank_limit_order))
	#print('bitbank_limit_order=',bitbank_limit_order)
	#print('prev_bitbank_limit_order=',prev_bitbank_limit_order)
	price_fee = price_order * 0.0005 #GMO taker fee
	limit_fee = bitbank_limit_order * -0.0002 #bitbank maker fee
	print('bitbankでのメイカー手数料は',round(limit_fee,1),'円')
	print('GMOでのテイカー手数料は',round(price_fee,1),'円')
	print('bitbankで',round(bitbank_limit_order),'円で売って(メイカー)')
	print('GMOで',round(price_order),'円で買えば(テイカー)')
	profit = bitbank_limit_order - price_order - price_fee - limit_fee
	print(round(profit),'円の利益!')
	print('')
	transfer_fee = 0
	
	#bitbankで売却
	GMO_OrderCheck()
	if profit > transfer_fee + margin:
		if active_order == 0:			
			bitbank_Order(bitbank_limit_order + buffer, 'sell')
		elif prev_bitbank_limit_order - bitbank_limit_order > 0 :
			prv_set.cancel_order(pair, active_order)
			print('価格が動いたので注文出しなおします')
			bitbank_Order(bitbank_limit_order + buffer, 'sell')
		Sound()			
	else:
		if active_order != 0:
			prv_set.cancel_order(pair, active_order)
			print('差額が狭まったので注文キャンセルします')
	#GMOでの購入処理
	t1 = time.time()
	t2 = time.time()
	while t2-t1 < 5:
		GMO_OrderCheck()
		t2 = time.time()
	print('')

ゼロ送金時間アービトラージ手法の紹介はこちら↓
https://tsurezure.info/arbitrage/index.php/2020/01/26/post-326/


bitbankの口座開設はこちらからどうぞ!

bitbank

Liquidの口座開設はこちらからどうぞ!

QUOINEX

Coincheckの口座開設はこちらからどうぞ!

コインチェック