Pythonでbitcoinの自動売買システム作ってみた! 〜Zaif編〜

8万を溶かして学んだこと

最近はだいぶ下火ですが、2019年1月くらいまではすごい勢いだった仮想通貨。

私も当時は仕事そっちのけでハマってました笑

FXのような取引(レバレッジきかせて先物、空売りとかができるやつ)で8万ほど溶かして目が覚めましたがww

そこでこんなことを考えるようになりました。

・感情に流されず売買したい。
・短期売買を頻繁かつ簡単に実行したい。

じゃあプログラムで自動化してしまおう!

今回は主要仮想通貨である「bitcoin」を使って、カンタンな自動売買システムをpythonで構築してみたいと思います!

この記事でわかること

・zaifのAPIによる簡単な売買プログラムの作成
・Pythonの基本操作スキル


やりたいことを整理する

仮想通貨の各取引所では、APIを公開していますよね。

それを利用して、プログラムから売買を自動的に実行させたいと思います。

今回は大手取引所の一つである、Zaifの取引所のAPIを参考にしました。

bitflyerでの売買プログラムも今後作成予定。。

Zaif-API Documents — Zaif API document v2.1.0 ドキュメント


どうプログラム化するか?

APIを呼び出すための言語は提供しているものならなんでもいいですが、今回は最近流行りのPythonを使用します。

作成が必要なスクリプトを2つ用意します。

①売り/買い注文、キャンセル等のAPIをメソッド化(関数)した自作モジュール(プログラム)

②自作モジュールから適宜メソッドを呼び出して自動的に取引を行うプログラム

この2つのスクリプトを使った取引の簡単な流れはこんな感じ。


事前準備

・Zaifの口座開設
・Python3の実行環境の準備

ここの詳細は今回の目的ではないので割愛します。お手数ですが、適宜対応下さい。


コード

スクリプト①売り/買い注文、キャンセル等のAPIの自作モジュール

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

### zaif_module.py ###
import hashlib
import hmac
import requests
import datetime
import json
import urllib.parse
class ZaifApi:
 CURRENCY_PAIR= ""
 API_KEY = ""
 API_SECRET = ""
 API_URL = "http://api.zaif.jp/tapi"
 nonce = int((datetime.datetime.today() -datetime.datetime(2017,1,1)).total_seconds())*10
 # コンストラクタ
 def __init__(self, key, secret, currency_pair='btc_jpy'):
    self.API_KEY = key
    self.API_SECRET = secret
    self.CURRENCY_PAIR = currency_pair
    return

 # ZaifのプライベートAPIにリクエストを送信する関数
 def _private_api(self, i_params):
    params = urllib.parse.urlencode(i_params);
    s = hmac.new(bytearray(self.API_SECRET.encode('utf-8')),    digestmod=hashlib.sha512)
    s.update(params.encode('utf-8'))
    headers = {'key':self.API_KEY, 'sign':s.hexdigest()}
    z = requests.post(self.API_URL, data=params, headers=headers)

 # 戻り値のチェック
    if z.status_code != 200:
       raise Exception("HTTP ERROR status={0}".format(z.status_code))
    j = z.json()
    if j["success"]!=1:
       raise Exception("Api ERROR json={0}".format(j))
    return j
 
 # 売買を行うAPIの共通部分
 def _trade_api(self, price, amount, action):
    self.nonce = self.nonce + 1
    j = self._private_api(
         {
          "method":"trade",
          "currency_pair":self.CURRENCY_PAIR,
          "price":price,
          "action":action,
          "amount":amount,

          "nonce":self.nonce
        })
    return j

 # 残高を得る関数
 def balance(self):
    self.nonce = self.nonce + 1
    z = self._private_api({"method":"get_info2", "nonce":self.nonce})
    f = z["return"]["funds"]
    return {"btc":f["btc"], "jpy":f["jpy"], "xem":f["xem"], "mona":f["mona"]}

# 板情報を得る関数
 def orderbook(self):
    z = requests.get('https://api.zaif.jp/api/1/depth/{0}'.format(self.CURRENCY_PAIR))
    if z.status_code != 200:
       raise Exception("HTTP ERROR status={0}".format(z.status_code))
    j = z.json()
    return {"asks":[tuple(i) for i in j["asks"]], "bids":[tuple(i) for i in j["bids"]]}

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

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

 # 注文をキャンセルする関数
 def cancel(self, oid):
    self.nonce = self.nonce + 1
    return self._private_api({"method":"cancel_order", "order_id":oid, "nonce":self.nonce})

 # 注文の状態を調べる関数
 def is_active_order(self, oid):
    self.nonce = self.nonce+1
    j = self._private_api({"method":"active_orders", "currrency_pair":self.CURRENCY_PAIR, "nonce":self.nonce})
    return str(oid) in j["return"]

スクリプト②自作モジュールから適宜メソッドを呼び出して自動取引を行うプログラム

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

### zaif_trade.py ###
from zaif_module import ZaifApi
imoort time

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

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

# 1回の取引量を指定
buy_unit = 0.0014 # 購入価格
profit = 100 # 価格差
api = ZaifApi(API_KEY, API_SECRET)
while True:
   print(" ### Zaif ### ")
   print("BTC/JPYの自動売買取引を開始します")
   time.sleep(5)

   # 板情報の取得
   print("板情報を取得しています")
   ob = api.orderbook()

   # 購入予定価格の決定(bidの先頭)
   buy_price = ob["bids"][0][0]
   print("購入予定価格は{0}円です".format(int(buy_price)))
  
   # 購入数量を計算(購入数量 = 数量*(1+fee*2) - BTC残高)
   balance = api.balance()

   # 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)
      
      # JPY残高の確認
      if balance["jpy"] < buy_amount*buy_price:
         print("十分な日本円残高({0}円)がありません".format(balance["jpy"]))
         break

      # 買い注文の開始
      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("サーバ処理中(5秒間処理待ち)")
       time.sleep(5)

       # さらに最大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(5)

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

      # 売却するBTCが既にあるなら何もしない
      print("売却可能なBTCがあるので売り注文に移ります")
      # 売り注文開始
      print("売り注文開始します")
 
      # BTC残高を調べる
      print("売却可能なBTC残高を確認します")
      balance = api.balance()
      print("現在の所有しているBTC残高は {0} です".format(balance["btc"]))
      # 売却数量は購入数量と一緒にする
      sell_amount = buy_unit
      print("BTCの売却数量は {0} です".format(sell_amount))
      if sell_amount < order_min_size:
         # 部分的な約定などで最小売却単位に届かないのならもう一度購入に戻る
         price("十分なBTCが存在しません")
         continue
      else:

         # 利益をのせて注文。BTCの場合はpriceを整数にする
         print("{0}円の利益を上乗せして売り注文をします".format(profit))
         print("売却価格+利鞘 x 売却数量={0} x {1}".format(int(buy_price+profit),sell_amount))
         oid = api.sell(int(buy_price+profit), sell_amount)
         print("注文ID={0}".format(oid))
         time.sleep(5)

         # 最大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の先頭)
            ob02 = api.orderbook()
            buy_price02 = ob02["bids"][0][0]
            profit02 = 200

            # 再売り注文
            print("再売り注文をしています")
            oid02 = api.sell(int(buy_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("売り注文が約定されました。引き続き買い注文へ移ります")
            print("注文ID={0}".format(oid))


最後に

問題なく動くと思うのですが、APIによる通信の影響か、たまに通信エラーで処理が止まることがあります。

その場合は、処理に時間がかかる部分の前後を一定時間ポーズさせたりすることでより安定して処理が行われるはずです。

正直に申し上げると、これは完全に私のオリジナルというわけはありません(かといって完全丸パクリではありませんが)。

日経ソフトウェアの2017年9月号及び11月号を参考に今回作成してみました。

そちらには、詳細な説明が載っていますので、興味のある方は是非確認してみてください。

多少Pythonが読める方であれば、上記のコメントですぐ実装できるはず。インベントリはご注意下さい。是非自分の環境でも動かして楽しんでください。

最後まで読んで頂きありがとうございました!