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

5月 24, 2020

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


ここ2か月間、Pythonを使ったbitcoinアービトラージの自動売買プログラムの作成に取り組んできましたがついに(ほぼ)自動売買と呼べるまでのプログラムが完成しました!アービトラージ取引に必要な送金処理が入っていないので全自動ではないのですが、手動での差額計算および売買注文は一切不要になりました。売買ロジックは以前もご紹介した通りこちらのフローチャートの通りです。

こちらの記事で紹介している通り本当はGMOで買ってbitbankで売りたいのですが、ちょうど昨日あたりからbitbankの方が安くなってしまったので今回はbitbankで買ってGMOで売る場合のプログラムになっています。bitbankの送金手数料は0.001かかってしまうので、それを差し引いても利益が出ると計算されたときに買い注文が出るようにしました。

それでは今回作成したプログラムのソースコードはこちらです。
注意点ですが、これまでと比べて規模が大きくなってしまったのと全然可読性を考慮していない(継ぎ接ぎ状態)ので、あくまで同様なプログラムを作成される際の「ヒント」としていただければ幸いです。可読性改善の為のコード修正や各処理の説明は次回以降の記事でまとめたいと思います。

import ccxt
import time
from pprint import pprint
import requests
import json
import hmac
import hashlib
from datetime import datetime
import python_bitbankcc
import winsound
order_amount = 0 #注文数量
order_id = 0 #注文ID
current_BTC = 0
prev_BTC = 0
prev_buy_limit_order = 0
#GMO
def Sell(order_amount):
	apiKey    = '******************'
	secretKey = '******************'
	timestamp = '{0}000'.format(int(time.mktime(datetime.now().timetuple())))
	method    = 'POST'
	endPoint  = 'https://api.coin.z.com/private'
	path      = '/v1/order'
	reqBody = {
	    "symbol": "BTC",
	    "side": "SELL",
	    "executionType": "MARKET",
	    "size": order_amount
	}
	text = timestamp + method + path + json.dumps(reqBody)
	sign = hmac.new(bytes(secretKey.encode('ascii')), bytes(text.encode('ascii')), hashlib.sha256).hexdigest()
	headers = {
	    "API-KEY": apiKey,
	    "API-TIMESTAMP": timestamp,
	    "API-SIGN": sign
	}
	res = requests.post(endPoint + path, headers=headers, data=json.dumps(reqBody))
	print (json.dumps(res.json(), indent=2))
#bitbank
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:
            return None
    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_orders(self, pair):
        try:
            value = self.prv.get_active_orders(pair)
            return value
        except Exception as e:
            return None
    def get_orders_info(self, pair):
        try:
            value = self.prv.get_orders_info(pair)
            return value
        except Exception as e:
            return None
    def cancel_order(self, pair, order_id):
        try:
            value = self.prv.cancel_order(pair, order_id)
            return value
        except Exception as e:
            return None
prv_set = BitBankPrvAPI()
pair = 'btc_jpy'
buy_amount = 0.001       #発注数量
sell_amount = 0
def Buy(price):
	#price = price +100000	
	amount = buy_amount
	side = 'buy'
	order_type = 'limit'
	prv_set.order(pair, price, amount, side, order_type)
	print(pair, price, amount, side, order_type)
#GMO
def SellPriceOrder(order_amount):
	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:
		size_sum = size_sum + float(json_data["data"]["bids"][i]["size"])
		price_sum = price_sum + float(json_data["data"]["bids"][i]["price"])
		taker_price = taker_price + float(json_data["data"]["bids"][i]["price"]) * (float(json_data["data"]["bids"][i]["size"]))
		i = i + 1
	delta = size_sum - order_amount
	return taker_price - float(json_data["data"]["bids"][i-1]["price"]) * delta
#bitbank
def BuyLimitOrder(order_amount):
	exchange = eval('ccxt.' + 'bitbank' + '()')
	orderbook = exchange.fetch_order_book ('BTC/JPY')
	return order_amount * orderbook['bids'][0][0]     #※指値でMaker取引をするためaskではなくbid(買い注文)の値を取得
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)
while 1:
	sell_price_order = SellPriceOrder(1)
	buy_limit_order = BuyLimitOrder(1)
	print('buy_limit_order=',buy_limit_order)
	print('prev_buy_limit_order=',prev_buy_limit_order)
	price_fee = sell_price_order * 0.0005 #GMO taker fee
	limit_fee = buy_limit_order * -0.0002 #bitbank maker fee
	print('テイカー手数料は',price_fee,'円')
	print('メイカー手数料は',limit_fee,'円')
	print(buy_limit_order,'円で買って(メイカー)')
	print(sell_price_order,'円で売れば(テイカー)')
	profit = sell_price_order - buy_limit_order - price_fee - limit_fee
	print(profit,'円の利益!')
	transfer_fee = buy_limit_order * 0.001
	if profit > transfer_fee:
	#if profit > 0:
		if order_id == 0:
			current_BTC = prv_set.get_asset()['assets'][1]['onhand_amount']
			prev_BTC = current_BTC
			print('BTC=',current_BTC)
			Buy(buy_limit_order - 10)
			prev_buy_limit_order = buy_limit_order
			value = prv_set.get_active_orders(pair)
			order_id = value['orders'][0]["order_id"]
			print('order_id', order_id)
		elif buy_limit_order - prev_buy_limit_order > 0 :
			prv_set.cancel_order(pair, order_id)
			current_BTC = prv_set.get_asset()['assets'][1]['onhand_amount']
			prev_BTC = current_BTC
			Buy(buy_limit_order - 10)
			prev_buy_limit_order = buy_limit_order
			value = prv_set.get_active_orders(pair)
			order_id = value['orders'][0]["order_id"]
			print('order_id', order_id)
		Sound()
		current_BTC = prv_set.get_asset()['assets'][1]['onhand_amount']
		order_amount = float(prev_BTC) - float(current_BTC)
		order_amount = abs(round(order_amount, 4))
		print('order_amount=',order_amount)
		if order_amount > 0:
			print('Sell ', order_amount)
			Sell(order_amount)
			Sound2()
			prev_BTC = current_BTC
			sell_amount = sell_amount + order_amount
			if buy_amount == sell_amount:
				order_id = 0
				sell_amount = 0
				time.sleep(5)
	else:
		if order_id != 0:
			prv_set.cancel_order(pair, order_id)
			order_id = 0
			prev_BTC = current_BTC		
	time.sleep(1)

実際にプログラムを実行しているときのbitbankの取引画面がこちらです。

GMOコインとの差額を元に買い注文を入れたりキャンセルしたりしていることがわかります。

始めは最小取引単位の0.0001BTCで試運転していたのですが、良い感じに動いてきたので発注数量を増やしてみたときの取引履歴がこちらです。上がbitbankで下がGMOコインです。bitbankで出した買い注文が一部約定したら約定した分だけ成行でGMOコインで売り注文しています。


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