しらべっぱなし帽子

調べっぱなしにしないためのメモ。

Pythonで仮想通貨の自動売買システムを作ってみた! 〜bitflyer編〜

はじめに

前回はZaifAPIを利用して、自動売買プログラムを作成。今回はその延長でbitflyerAPIを利用して、自動売買プログラムを作成。

Zaif編の記事はこちら。 

dokusho-now.hatenadiary.jp

やりたいこと

Zaif編と同様、各取引所で公開しているAPIを利用して、プログラムから注文を自動的に実行させる。今回は大手取引所の一つである、bitflyerの取引所(API)で実験。 

bitflyerAPIはこちら。

lightning.bitflyer.com

自動売買プログラムに必要なスクリプトは2つ。

・売り/買い注文、キャンセル等のAPIをメソッド化した自作モジュール

・自作モジュールから適宜メソッド(API)を呼び出して、自動取引を行うプログラム

 

事前準備

bitflyerの口座開設

・Python3の実行環境準備

 

コード

まずは売り/買い注文、キャンセル等のAPIをメソッド化した自作モジュールを作成。

### bf_module.py ###

import hashlib
import hmac
import requests
import datetime
import json
import urllib

# クラスの定義
class BFApi():
CURRENCY_PAIR=""
API_KEY = ""
API_SECRET = ""
API_URL = "https://api.bitflyer.jp"
nonce = int((datetime.datetime.today()-datetime.datetime(2017,1,1)).total_seconds())*10

# コンストラクタ
def __init__(self, key, secret, product_code="FX_BTC_JPY"):
self.API_KEY = key
self.API_SECRET = secret
self.CURRENCY_PAIR = product_code
return

# bitFlyerのプライベートAPIリクエストを送信する関数
def _private_api(self, i_path, i_params=None):
timestamp = str(datetime.datetime.today())
headers = {"ACCESS-KEY":self.API_KEY,"ACCESS-TIMESTAMP":timestamp,"Content-Type":"application/json"}
s = hmac.new(bytearray(self.API_SECRET.encode("utf-8")), digestmod=hashlib.sha256)
b = None
if i_params is None:
w = timestamp + "GET" + i_path
s.update(w.encode("utf-8"))
headers["ACCESS-SIGN"] = s.hexdigest()
b = requests.get(self.API_URL + i_path, headers=headers)
else:
body = json.dumps(i_params);
w = timestamp + "POST" + i_path + body
s.update(w.encode("utf-8"))
headers["ACCESS-SIGN"] = s.hexdigest()
b = requests.post(self.API_URL+i_path, data=body, headers=headers)

# 戻り値のチェック
if b.status_code != 200:
raise Exception("HTTP ERROR status={0},{1}".format(b.status_code,b.json()))
return b

# 売買を行うAPIの共通部分
def _trade_api(self, price, amount, side):
j = self._private_api("/v1/me/sendchildorder",
{
"product_code":self.CURRENCY_PAIR,
"child_order_type": "LIMIT",
"side":side,
"price":price,
"size":amount,
"time_in_force":"GTC"}).json()
return j

# 最終取引価格を得る関数
def board(self):
j = requests.get("https://api.bitflyer.jp/v1/board? product_code=FX_BTC_JPY").json()
x = j["mid_price"]
return x

# 残高を得る関数
def balance(self):
j = self._private_api("/v1/me/getbalance").json()
jpy = [i for i in j if i["currency_code"]=="JPY"][0]
btc = [i for i in j if i["currency_code"]=="BTC"][0]
eth = [i for i in j if i["currency_code"]=="ETH"][0]
return {"btc":btc["available"],"jpy":jpy["available"],"eth":eth["available"]}

# 証拠金残高を得る関数
def collateral(self):
j = self._private_api("/v1/me/getcollateral").json()
return {"証拠金 : {0} 円".format(j["collateral"])}

# 売り注文を実行する関数
def sell(self, price, amount):
j = self._trade_api(price, amount, "SELL")
return j["child_order_acceptance_id"]

# 買い注文を実行する関数
def buy(self, price, amount):
j = self._trade_api(price, amount, "BUY")
return j["child_order_acceptance_id"]

# 注文をキャンセルする関数
def cancel(self, oid):
j = self._private_api("/v1/me/cancelchildorder", {
"product_code":self.CURRENCY_PAIR, "child_order_acceptance_id": oid})
return

# 注文状態を調べる関数
def is_active_order(self, oid):
j = self._private_api("/v1/me/getchildorders?"+urllib.parse.urlencode({
"product_code":self.CURRENCY_PAIR})).json()
t = [i for i in j if i["child_order_acceptance_id"]==oid]
if len(t)>0:
if t[0]["child_order_state"]=="ACTIVE":
return True
return False

 

次に自作モジュールから適宜メソッド(API)を呼び出して、自動取引を行うプログラムを作成。

### bf_trade.py ###
from bf_module import BFApi
from time import sleep

# API_KEY API_SERCRET のキーを入力する。取扱注意! 
API_KEY = ""
API_SECRET = ""

# bitflyer取引所のパラメータ
order_min_size = 0.01 # BTCの数量最小値
order_digit = 3 # BTC数量の桁数
fee_rate = 0.0 # 取引手数料のレート(%)

# 1回の取引量
buy_unit = 0.01 # 購入価格
profit = 200 # 利益

api = BFApi(API_KEY, API_SECRET)

while True:
print(" ### bitflyer ### ")
print("BTC/JPYの自動売買取引を開始します")
time.sleep(5)

# 最終取引価格の取得
print("最終取引価格を取得しています")
buy_price = api.board()
print("購入予定価格は {0} 円です".format(int(buy_price)))

# BTCの購入数量の決定
buy_amount = round(buy_unit,order_digit)
print("BTCの購入数量は {0} です".format(buy_amount))

if buy_amount > 0:
# BTC残高が不十分なら終了
buy_amount = max(order_min_size, buy_amount)

# 残高を計算
balance = api.balance()

# JPY残高の確認
if balance["jpy"] < buy_amount*buy_price:
print("現在のBTC残高は {0} です".format(balance["btc"]))

# 買い注文の開始
print("買い注文を開始します")
print("購入価格 x 購入数量={0} x {1}".format(int(buy_price),buy_amount))
oid = api.buy(int(buy_price), buy_amount)
print("注文ID={0}".format(oid))

# 買い注文がサーバで処理されるまで少し待つ
print("サーバ処理中(3秒間処理待ち)")
time.sleep(3)

# さらに最大30秒間買い注文が約定するのを待つ
for i in range(0,10):
if api.is_active_order(oid) == False:
oid = None
break
print("まだ約定に時間がかかりそうです({0}回目/10回中)".format(i))
print("少々お待ちを")
time.sleep(3)

# 注文が残っていたらキャンセルする
if oid != None:
api.cancel(oid)
print("約定できなかったため注文をキャンセルしています")
print("注文ID={0}".format(oid))
time.sleep(5)

# 購入予定価格の決定(bidの先頭)
sell_price02 = api.board()
profit02 = 200

# 再売り注文
print("再売り注文をしています")
oid02 = api.sell(int(sell_price02+profit02), sell_amount)
print("売却数量+利鞘 x 売却数量={0} x {1}".format(int(buy_price02),sell_amount))
print("注文ID={0}".format(oid02))
time.sleep(5)

# 注文が成立するまで永遠に待つ
while api.is_active_order(oid02):
print("少々お待ちを")
time.sleep(3)
print("売り注文が約定されました。")
print("注文ID={0}".format(oid02))
else:
print("売り注文が約定されました。引き続き買い注文へ移ります")

こちらもZaif編と同様です。

簡単な流れとしては、

残高確認 → 買い注文 → 約定待ち → 約定完了 → 売り注文 → 約定待ち→ 約定完了 → 残高確認 ・・・

の繰り返しです。

 

最後に

他の言語(rubyとか)でも同様にプログラムを作っていきたい。あと、今回は「買う → 売る」の単純なプログラムだったので、「売る → 買う」バージョンも実装させたい。

 

参考資料

日経ソフトウエア 2017年 9月号 [雑誌]

日経ソフトウエア 2017年 9月号 [雑誌]

 
日経ソフトウエア 2017年 11 月号

日経ソフトウエア 2017年 11 月号