pythonのお勉強の一環として、ちょこちょこ仮想通貨の自動取引の実装にトライしております。
今回はzaifでBTC/BCH/ETH/MONA/XEM(NEM)を500円分買い注文するプログラムを組んでみます。
作ったスクリプトとcronやsystemdのタイマーと組み合わせることで定期積立を実現できます。
はじめに
zaifを選択する理由は、国内の取引所の中ではAPIで操作できる通貨種類が多いからです。
現在日本円で手に入る主要な仮想通貨としては、
- Bitcoin(BTC)
- Bitcoin Cash(BCH)
- Ethereum(ETH)
- Ethereum classic(ETC)
- XEM(NEM)
- Monacoin(MONA)
- Ripple(XRP)
- Lisk(LSK)
このあたりでしょうか。
Zaifは大体カバーできてますね。
かつ販売ではなく取引ができるのでAPIのpairに指定できるというわけです。
(BitFlyerとCoinCheckはBitcoinしかpairに設定できないはず)
また、zaifには積立のサービスがありますが少額だと手数料の割合がそこそこ高いので自分で実装するとお得です。ただしセキュリティ面については自己責任で。
実装方針
売りの実装は難しい(利益や損切りを考えると面倒)ので、まずは買いの実装だけやってみます。
バグって誤発注するのが怖いので予算は少額に設定しておきます。
- 定期的に一定額(500円で買える分だけ)を購入する(積立)
- 指値はpythonスクリプトを実行した時点の最終取引額
- 最小注文単位の金額が500円を超える場合は注文しない
- 約定判定はなし
実装にあたっては下記のAPIを使用します。
currency_pairs — Zaif api document v1.1.1 ドキュメント
last_price — Zaif api document v1.1.1 ドキュメント
trade — Zaif api document v1.1.1 ドキュメント
APIを利用するには事前にアカウント設定から取引用のキーを取得しておく必要があります。セキュリティ確保のため送信元IPアドレスの制限もしておくと良いでしょう。
ソースコード
zaif_fund.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# -*- coding: utf-8 -*- import sys import json import requests import hmac import hashlib import time from datetime import datetime import urllib.parse import math class zaifApi: def __init__(self, key, key_secret, endpoint): self.key = key self.key_secret = key_secret self.endpoint = endpoint def get(self, path): nonce = int(datetime.now().strftime('%s')) text = nonce + self.endpoint + path signature = hmac.new( bytes(self.key_secret.encode('ascii')), bytes(text.encode('ascii')), hashlib.sha256).hexdigest() return requests.get( self.endpoint + path , headers = self.__get_header(self.key, nonce, signature)) def post(self, method, params): nonce = int(datetime.now().strftime('%s')) payload = { "method": method, "nonce": nonce, } payload.update(params) encoded_payload = urllib.parse.urlencode(payload) return requests.post( self.endpoint, data = payload, headers = self.__get_header(encoded_payload)) def delete(self,path): nonce = str(int(time.time())) text = nonce + self.endpoint + path signature = hmac.new( bytes(self.key_secret.encode('ascii')), bytes(text.encode('ascii')), hashlib.sha256).hexdigest() return requests.delete( self.endpoint+path, headers = self.__get_header(self.key, nonce)) def __get_header(self, params): signature = hmac.new( bytearray(self.key_secret.encode('utf-8')), digestmod=hashlib.sha512) signature.update(params.encode('utf-8')) return { 'key': self.key, 'sign': signature.hexdigest() } def getLastPrice(pair): urlbase = 'https://api.zaif.jp/api/1/last_price/' response = requests.get(urlbase+pair) if response.status_code != 200: return None last_price = response.json() return last_price['last_price'] def getItemUnitMin(pair): urlbase = 'https://api.zaif.jp/api/1/currency_pairs/' response = requests.get(urlbase+pair) if response.status_code != 200: return None currency_pair = response.json() return currency_pair[0]['item_unit_min'] if __name__ == '__main__': argv = sys.argv argc = len(argv) if (argc != 3): print('usage: python %s <key> <secret key>' % argv[0]) quit() key = argv[1] key_secret = argv[2] endpoint = 'https://api.zaif.jp/tapi' # 予算 budget_jpy = 500 # 取引対象の通貨 pairs = [ 'btc_jpy', 'bch_jpy', 'eth_jpy', 'xem_jpy', 'mona_jpy', ] # Instance zaif = zaifApi(key,key_secret,endpoint) for p in pairs: # 認証に使うnonceのための遅延処理 time.sleep(1.1) # 取引通貨の通貨情報を取得 item_unit_min = getItemUnitMin(p) if (item_unit_min == None): # 失敗した場合は中断 continue # 最終取引価格を取得 last_price = getLastPrice(p) if (last_price == None): # 失敗した場合は中断 continue # 注文価格が予算を超えた場合は中断 if ((item_unit_min * last_price) > budget_jpy): continue # 注文量の計算 # buget_jpy / last_price = 123.456789, item_unit_min = 0.01の場合, amount = 123.45になるような計算を行う # floadの場合, 最小桁付近で計算誤差がでるためroundで桁を調整する amount = round((math.floor((budget_jpy / last_price) / item_unit_min) * item_unit_min), int(math.log10(1/item_unit_min))) # 小数点を使わない場合は整数に変換する if (amount.is_integer()): amount = int(amount) if (last_price.is_integer()): last_price = int(last_price) # リクエストのパラメータ設定 method = 'trade' params = { 'currency_pair': p, 'action' : 'bid', 'price' : last_price, 'amount': amount, } print(params) print('total = {0:.2f} yen' .format(amount*last_price)) # 注文の送信 response = zaif.post(method, params) if response.status_code != 200: continue print(response.json()) print('') |
1 |
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ /usr/local/bin/python3 zaif_fund.py usage: python zaif_fund.py <key> <secret key> $ /usr/local/bin/python3 zaif_fund.py 'あなたのAPIキー' 'あなたのプライベートキー' {'amount': 0.0005, 'currency_pair': 'btc_jpy', 'action': 'bid', 'price': 936820} total = 468.41 yen {'return': {'order_id': 0, 'remains': 0.0, 'received': 0.0005, 'funds': {'保有資産の一覧'}}, 'success': 1} {'amount': 0.0031, 'currency_pair': 'bch_jpy', 'action': 'bid', 'price': 159200} total = 493.52 yen {'return': {'order_id': 0, 'remains': 0.0, 'received': 0.0030907, 'funds': {'保有資産の一覧'6}}, 'success': 1} {'amount': 0.0065, 'currency_pair': 'eth_jpy', 'action': 'bid', 'price': 76800} total = 499.20 yen {'return': {'order_id': xxxxxxxxx, 'remains': 0.0065, 'received': 0.0, 'funds': {'保有資産の一覧'}}, 'success': 1} {'amount': 13.6, 'currency_pair': 'xem_jpy', 'action': 'bid', 'price': 36.55} total = 497.08 yen {'return': {'order_id': xxxxxxxxx, 'remains': 13.6, 'received': 0.0, 'funds': {'保有資産の一覧'}}, 'success': 1} {'amount': 1, 'currency_pair': 'mona_jpy', 'action': 'bid', 'price': 442.5} total = 442.50 yen {'return': {'order_id': xxxxxxxxx, 'remains': 1.0, 'received': 0.0, 'funds': {'保有資産の一覧'}}, 'success': 1} |
応答のremainsが0.0以外のものは、板に注文が残っていて、0.0の場合は既に取引が完了しています。
約定の判定処理はありませんが、上げトレンドが続くようなことでもない限り大体翌日ぐらいまでには約定する気がします。
今のところ2ヶ月ほど週1回の頻度で実行してますが、注文が未約定のまま残ったことは無いです。
仮想通貨を取引所に置きっぱなしが怖い人は送金するAPIの組み合わせることで別walletへの退避も自動で出来ますね。