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

5月 5, 2020

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


こちらの記事でPythonを使ったbitcoinアービトラージの(ほぼ)自動売買プログラムを公開しましたが、いろいろと不備があり売った数量に対して買う数量が少なくなってしまう等のバグがありました。今回プログラムの見直しを行い確実に売買処理を行えるように修正しましたので再度ソースコードを公開します。今回もとりあえず貼り付けるだけで中身の説明はまた別途させてください。。

修正内容をざっくりご紹介すると、前回のプログラムではGMOでのBTC購入数量をbitbankでのBTC残高の増減を見て決めていたのですが、今回はちゃんとbitbankでの売り注文の約定履歴を確認し、約定した取引の数量から購入数量を決めるようにしました。また、Googleスプレッドシートを活用することで外出先からスマホでプログラムの稼働状況を確認したりアービトラージ条件(発注単位や発注を出す差額の条件等)を変更できるようにしました。それでは下記にソースコードをご紹介します。

こちらの記事で紹介している通りGMOで買ってbitbankで売るのが効率的ですが、前回の記事作成時点ではbitbankの方が安かったのでbitbankで買ってGMOで買うプログラムになっていました。今回は本来やりたかったGMOで買ってbitbankで売るプログラムにしています。bitbankで指値で売り注文を出し、約定した瞬間にGMOで成行買い注文をします。

それでは今回作成したプログラムのソースコードはこちらです。いつものことですが、 '*********’となっている部分はご自身のものに修正していただく必要があります。

import time
from pprint import pprint
import requests
import json
import hmac
import hashlib
from datetime import datetime
import python_bitbankcc
import winsound
import sys
import csv
from oauth2client.service_account import ServiceAccountCredentials
from httplib2 import Http
import gspread
filepath = '******************'
gfile = ''
def Gspreadsheet():
	global gfile
	scopes = ['https://spreadsheets.google.com/feeds','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
prev_trade_id = 0
current_BTC = 0
prev_BTC = 0
bitbank_limit_order = 0
prev_bitbank_limit_order = 0
pair = 'btc_jpy'
JPYasset=0
BTCasset=1	
#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)
	headers = GMOPrvCommon(method, path, text)
	res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody))
	print (json.dumps(res.json(), indent=2))
	CSVwrite([json.dumps(res.json(), indent=2)])		
def GMO_asset(pair):
    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)    
    i = 0
    while i == 0:
	    try:
	    	response = requests.get(endPoint + path, headers=headers)
	    	json_data = response.json()
	    	GMO_BTC = json_data['data'][pair]['available']
	    	print('json_data=',GMO_BTC)
	    	i = 1
	    except KeyError:
	    	print('retry')
	    	time.sleep(5)
	    	continue
    return float(GMO_BTC)
def CannotBuy():
	current_BTC_price = int(python_bitbankcc.public().get_ticker('btc_jpy')["sell"])
	GMO_JPYasset=GMO_asset(JPYasset)
	print('JPY資金=',GMO_JPYasset)
	if bitbank_amount * current_BTC_price * 2 > GMO_JPYasset:
		print('JPY資金不足です')
		CSVwrite(['JPY資金不足です'])
		prv_set.cancel_order(pair, active_order)
		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:
		try:
			size_sum = size_sum + float(json_data["data"][side][i]["size"])
		except KeyError:
			print('retry')
			time.sleep(5)
			response = requests.get(endPoint + path)
			json_data = response.json()
			size_sum = size_sum + float(json_data["data"][side][i]["size"])
		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
#bitbank
class BitBankPubAPI:
    def get_ticker(pair):
        pub = python_bitbankcc.public()
        try:
            value = pub.get_ticker(pair)
            return value
        except Exception as e:
            print(e)
            return None
class BitBankPrvAPI:
    def __init__(self):
        API_KEY = '******************'
        API_SECRET = '******************'
        self.prv = python_bitbankcc.private(API_KEY, API_SECRET)
    def get_asset(self):
        try:
            value = self.prv.get_asset()
            return value
        except Exception as e:
            print('retry get_asset')
            CSVwrite(['retry get_asset'])
            time.sleep(5)
            value = self.prv.get_asset()
            return value
    def order(self, pair, price, amount, side, order_type):
        try:
            value = self.prv.order(pair, price, amount, side, order_type)
            return value
        except Exception as e:
            return None
    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:
            return 0
    def cancel_order(self, pair, active_order):
        try:
            value = self.prv.cancel_order(pair, active_order)
            return value
        except Exception as e:
            return None
    def get_trade_history(self, pair, order_count):
        try:
            value = self.prv.get_trade_history(pair, order_count)
            return value
        except Exception as e:
            return None
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)
	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 CSVwrite(csvdata):
	with open(filepath+'GMO買いbitbank売りarbitrage-20200224.csv', 'a', newline="") as f:		
		writer = csv.writer(f)									
		writer.writerow(csvdata)		
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)
max_count = 5
count = max_count-1
Gspreadsheet()
while 1:
	dt = datetime.now()
	print(dt)
	active_order = prv_set.get_active_order(pair)
	print('active_order = ',active_order)
	CSVwrite([dt])
	
	#Googleスプレッドシートでパラメータ取得
	print('count=',count)
	count = count + 1
	if count == max_count:
		count = 0
		try:
			worksheet  = gfile.sheet1
		except gspread.exceptions.APIError as e:
			print(e)
			CSVwrite([e])
			Gspreadsheet()
			continue
		print('Loading OK')
		cell = worksheet.acell('B1').value
		print(cell)	
		if cell == '0':
			print('cell=0')
			prv_set.cancel_order(pair, active_order)
			print('canceled,', active_order	)
			time.sleep(10)
			count=max_count-1
			continue
		bitbank_amount = float(worksheet.acell('B2').value)       #発注数量
		print('order_unit=', bitbank_amount)
		margin = int(worksheet.acell('B3').value)	#差額計算時の調整用
		print('margin=',margin)
		buffer = int(worksheet.acell('B4').value)	#指値注文時の価格調整用
		print('buffer=',buffer)
		try:
			worksheet.update_acell('B5',str(dt))
		except gspread.exceptions.APIError as e:
			print(e)
			CSVwrite([e])
			Gspreadsheet()
			continue
	
	#売却余力確認
	current_BTC = prv_set.get_asset()['assets'][1]['onhand_amount']	
	G_current_BTC = GMO_asset(BTCasset)
	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('売却BTC不足です')
		CSVwrite(['売却BTC不足です'])
		time.sleep(10)
		continue
	
	#購入余力確認
	GMO_JPYasset=GMO_asset(JPYasset)
	print('JPY資金=',GMO_JPYasset)
	if CannotBuy():
		count = max_count-1
		time.sleep(10)
		continue
	
	#差額計算
	price_order = PriceOrder(1, 'asks')
	bitbank_limit_order = int(python_bitbankcc.public().get_ticker('btc_jpy')["sell"])    #※指値でMaker取引をするためbuyではなくsell(売り注文)の値を取得
	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('テイカー手数料は',price_fee,'円')
	print('メイカー手数料は',limit_fee,'円')
	print(price_order,'円で買って(テイカー)')
	print(bitbank_limit_order,'円で売れば(メイカー)')
	profit = bitbank_limit_order - price_order - price_fee - limit_fee
	print(profit,'円の利益!')
	transfer_fee = 0			#GMOの場合送金手数料無料
	#bitbankで売却
	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('canceled,', active_order	)
			bitbank_Order(bitbank_limit_order + buffer, 'sell')
		Sound()			
	else:
		if active_order != 0:
			prv_set.cancel_order(pair, active_order)
			print('canceled,', active_order	)
	#GMOでの購入処理
	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_amount = done_order[i]['amount']
		print('done_order_id[',i,']=',done_order[i])
		CSVwrite(['done_order_id[',i,']=',done_order[i]])
		print('order_amount=',order_amount)
		print('GMOで購入', order_amount)
		CSVwrite(['GMOで購入', order_amount])
		GMO_order(order_amount,"BUY")
		Sound2()
		i = i + 1
	prev_trade_id = done_order[0]['trade_id']
	print('prev_trade_id=',prev_trade_id)	

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