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

DMM FXで2万円をGET♪

ステップ1:DMM FXで口座開設

ステップ2:1lot(約5万円)取引

<< 2万 GET >>

具体的な取引方法は下記の動画にて。

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

最近はだいぶ下火ですが、今年1月くらいまではすごい勢いだった仮想通貨。
私も昨年の今頃は仕事そっちのけでハマってました。

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

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

  • 感情に流されず売買(トレード)したい。
  • 短期売買をもっと頻繁かつ簡単にしたい。

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

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


やりたいこと

仮想通貨の取引所では、APIを公開していますよね。
それを利用して、プログラムから売買を自動的に実行させたいと思います!
仮想通貨は安定のbitcoin(ビットコイン)にします!

今回は大手取引所の一つである、Zaifの取引所のAPIを参考にしました。
bitflyerでの売買プログラムも今後作成予定。。

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

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

APIを呼び出すための言語はなんでもいいですが、今回は最近流行りのPythonを使用。自動売買プログラムに必要なスクリプトは2つ。

  • 売り/買い注文、キャンセル等のAPIをメソッド化(関数)した自作モジュール
  • 自作モジュールから適宜メソッドを呼び出して自動的に取引を行うプログラム

簡単な流れとしては、

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

の繰り返しです。

事前準備

  • Zaifの口座開設
  • Python3の実行環境

ここの詳細は割愛します。適宜対応ください。

コード

まずは売り/買い注文、キャンセル等の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月号を参考に今回作成してみました。そこに詳細な説明が載っていますので、興味のある方は是非購入してみてください!

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

多少Pythonが読める人であれば、上記のコメントですぐ実装できるはず。
是非自分の環境でも動かしてみてください!

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