#14 Python×ビットコイン自動売買 | 条件分岐を追加して売買ロジックを完成させよう!

  • URLをコピーしました!

こんにちは、はやたす(@hayatasuuu )です。

第14回目の本記事では、条件分岐を追加して売買ロジックを完成させます。

前回の記事 : #13 Python×ビットコイン自動売買 | 自動売買に必要なロジックを作成してみよう!

クラスを少しだけ編集して、ビットコインを売買するためのコードを追加しましょう。

今回の内容に取り組めば、最低限の自動売買プログラムは完了します。残りわずかなので、ぜひ頑張っていきましょう!

目次

条件分岐に応じてビットコインを自動売買しよう!

自動売買プログラムを完成させるにあたり、今回は以下の手順で紹介していきます。

  • STEP① : Coincheckクラスの編集
  • STEP② : 購入ロジックの作成
  • STEP③ : 売却ロジックの作成
  • STEP④ : コードの微調整

それでは順番に見ていきましょう!

STEP① : Coincheckクラスの編集

まずはCoincheckクラスを編集していきます。いまcoincheck.pyの中身は、以下のようになっていました。

import hmac
import hashlib
import json
import time

import requests


class Coincheck(object):
    def __init__(self, access_key, secret_key):
        self.access_key = access_key
        self.secret_key = secret_key
        self.url = 'https://coincheck.com'

    def _request(self, endpoint, params=None, method='GET'):
        nonce = str(int(time.time()))
        body = json.dumps(params) if params else ''

        message = nonce + endpoint + body
        signature = hmac.new(self.secret_key.encode(),
                             message.encode(),
                             hashlib.sha256).hexdigest()

        headers = {
            'ACCESS-KEY': self.access_key,
            'ACCESS-NONCE': nonce,
            'ACCESS-SIGNATURE': signature,
            'Content-Type': 'application/json'
        }

        if method == 'GET':
            r = requests.get(endpoint, headers=headers, params=params)
        else:
            r = requests.post(endpoint, headers=headers, data=body)

        return r.json()

    def ticker(self):
        endpoint = self.url + '/api/ticker'
        return self._request(endpoint=endpoint)

    @property
    def last(self):
        return self.ticker()['last']

    def trades(self, params):
        endpoint = self.url + '/api/trades'
        return self._request(endpoint=endpoint, params=params)

    def order_books(self, params=None):
        endpoint = self.url + '/api/order_books'
        return self._request(endpoint=endpoint, params=params)

    def balance(self):
        endpoint = self.url + '/api/accounts/balance'
        return self._request(endpoint=endpoint)

    @property
    def position(self):
        balance = self.balance()
        return {k: v for k, v in balance.items()
                if isinstance(v, str) and float(v)}

    def order(self, params):
        endpoint = self.url + '/api/exchange/orders'
        return self._request(endpoint=endpoint, params=params, method='POST')

このCoincheckクラスに対して、2つのメソッドと1つのプロパティを追加していきます。

  • メソッドtransaction() : 自分の最近の取引内容を確認する
  • メソッドrate() : ビットコインの枚数に応じた価格を確認する
  • プロパティask_rate : 自分が購入したビットコイン価格を取得する

それぞれの使い方は、後ほど売買プログラムを組むときに紹介します。

とりあえず、今は上記3つのメソッドとプロパティを追加していきましょう。

メソッドの追加に関しては、第12回目と同じ要領で作成できます。

CoincheckAPIドキュメントで確認すると、各メソッドでアクセスしたいURLが以下であることが分かります。

  • メソッドtransaction : /api/exchange/orders/transactions
  • メソッドrate : /api/exchange/orders/rate

パラメータ付与などを気をつけると、今回は次のようにコードを編集すれば良さそうです。

"""
中略
"""

class Coincheck(object):
    def __init__(self, access_key, secret_key):
        self.access_key = access_key
        self.secret_key = secret_key
        self.url = 'https://coincheck.com'

    """
    中略
    """

    def order(self, params):
        endpoint = self.url + '/api/exchange/orders'
        return self._request(endpoint=endpoint, params=params, method='POST')

    def transaction(self):
        endpoint = self.url + '/api/exchange/orders/transactions'
        return self._request(endpoint=endpoint)

    def rate(self, params):
        endpoint = self.url + '/api/exchange/orders/rate'
        return self._request(endpoint=endpoint, params=params)


transaction()メソッドは、トレード履歴を取得するだけなのでパラメータが必要ありません。

それに対してrate()メソッドでは、通貨ペアとその枚数が必要になります。

これでメソッドを追加できたので、試しにtransaction()を使ってみたいと思います。

皆さんは実行しなくても大丈夫ですが、もし手元でも確認するなら対話シェルを開いて、以下のコードを書いてください。

>>> import configparser
>>> from coincheck import Coincheck
>>> conf = configparser.ConfigParser()
>>> conf.read('config.ini')
['config.ini']
>>> ACCEES_KEY = conf['coincheck']['access_key']
>>> SECRET_KEY = conf['coincheck']['secret_key']
>>> coincheck = Coincheck(access_key=ACCEES_KEY, secret_key=SECRET_KEY)
>>> coincheck.transaction()

そうすると膨大な取引履歴を取得できるはずです。

今回、取引履歴を使う理由は「直近のビットコイン購入価格を把握するため」です。

ゆえにすべての取引情報は必要ありません。

そこでask_rateプロパティを作成することで、直近のビットコイン購入価格だけ取得できるようにしましょう。

transaction()メソッドの取得結果を確認しつつ、直近の購入価格だけ取得するには、以下のようにコードを作成すれば良さそうですね!

class Coincheck(object):
    """
    中略
    """

    def transaction(self):
        endpoint = self.url + '/api/exchange/orders/transactions'
        return self._request(endpoint=endpoint)

    @property
    def ask_rate(self):
        transaction = self.transaction()
        ask_transaction = [d for d in transaction['transactions']
                           if d['side'] == 'buy']
        return float(ask_transaction[0]['rate'])

    def rate(self, params):
        endpoint = self.url + '/api/exchange/orders/rate'
        return self._request(endpoint=endpoint, params=params)


これでask_rateを使えば、直近のビットコイン購入価格だけ取得できるはずです。

対話シェルの続きでask_rateプロパティを使ってみましょう。

>>> coincheck.ask_rate
6553604.0

出力を確認すると”ビットコインの価格らしき値”を取得できました。

TraderViewを使って、これが直近の購入価格なのか確認してみましょう。

ビットコイン 購入価格

どうやら直近の価格だったようです!これでメソッドとプロパティの作成が完了しました。

STEP② : 購入ロジックの作成

それではmain.pyを編集して、購入ロジックを完成させていきましょう。

現状だと最新価格の取得とボリンジャーバンドの計算までコードを作成してあったはずです。

"""
中略
"""
interval = 1
duration = 20

df = pd.DataFrame()
while True:
    time.sleep(interval)
    df = df.append(
        {'price': coincheck.last}, ignore_index=True
    )

    if len(df) < duration:
        continue

    df['SMA'] = df['price'].rolling(window=duration).mean()
    df['std'] = df['price'].rolling(window=duration).std()

    df['-2σ'] = df['SMA'] - 2*df['std']
    df['+2σ'] = df['SMA'] + 2*df['std']

    # もし-2σを割ったら
    if df['price'].iloc[-1] < df['-2σ'].iloc[-1]:
        print('buy!!!')

    # もし+2σを割ったら
    if df['+2σ'].iloc[-1] < df['price'].iloc[-1]:
        print('sell!!!')


このコードで書かれている条件分岐の中で、クラス内で定義したメソッドを使って新規発注できるようにします。

ビットコインを購入するには、coincheck.order(params)を使います。このときparamsの中には、以下3つの引数が必要になります。

  • pair : 通貨ペア(今回はbtc_jpy)
  • order_type : 取引方法(今は購入なのでmarket_buy)
  • market_buy_amount : 購入で使用する日本円

pairorder_typeは問題ないですよね。通貨ペアはBTC/JPYだし、取引方法は成行の買いです。

ここで問題になるのは、購入で使用する日本円です。

購入したいビットコインの量は0.005BTCと決まっているけど、日本円の金額は[latex]-2\sigma[/latex]を下回ったときなので、毎回同じとは限りません。

つまり「購入したいビットコインのレートに応じた日本円の金額」を分かっている必要があります。

この計算を行うには、先ほどCoincheckクラス内に追加しておいたrateメソッドを使いましょう。

具体的には、購入したいビットコインの数量と、通貨ペア・取引方法をパラメータで渡します。

対話シェルで試してみたのが以下です。

>>> coincheck.rate({'order_type': 'buy', 'pair': 'btc_jpy', 'amount': 0.005})
{'success': True, 'rate': '6559853.0', 'amount': '0.005', 'price': '32799.265'}

このように、coincheck.rate()で取引したいビットコインの数量を渡すと、日本円換算したpriceを取得できます。

というわけで、最終的なビットコインの購入ロジックは、以下のようになります。

coincheck = Coincheck(access_key=ACCEES_KEY, secret_key=SECRET_KEY)

interval = 1
duration = 20
AMOUNT = 0.005 # 追加

df = pd.DataFrame()
while True:
    time.sleep(interval)
    df = df.append(
        {'price': coincheck.last}, ignore_index=True
    )
    """
    中略
    """
    # もし-2σを割ったら
    if df['price'].iloc[-1] < df['-2σ'].iloc[-1]:
        market_buy_amount = coincheck.rate({'order_type': 'buy',
                                            'pair': 'btc_jpy',
                                            'amount': AMOUNT})
        print('使用する日本円', market_buy_amount['price'])
        params = {
            'pair': 'btc_jpy',
            'order_type': 'market_buy',
            'market_buy_amount': market_buy_amount['price']
        }

        r = coincheck.order(params)
        print('entry', r)

取引する数量は、あらかじめAMOUNTで設定しておきました!

STEP③ : 売却ロジックの作成

次に売却ロジックです。

いま作成してある条件分岐では、「ビットコイン価格が[latex]+2\sigma[/latex]より上だったら」売却することになっています。

今回はそれだけでなく、購入した価格を上振れたら売却するようにしましょう。つまり作成するコードとしては、以下のようになります。

"""
中略
"""
while True:
    time.sleep(interval)
    positions = coincheck.position # 追加

    """
    中略
    """

    # もし+2σを割ったら
    if df['+2σ'].iloc[-1] < df['price'].iloc[-1] \
            and coincheck.ask_rate < df['price'].iloc[-1]:
        params = {
            'pair': 'btc_jpy',
            'order_type': 'market_sell',
            'amount': positions['btc']
        }
        r = coincheck.order(params)
        print('exit', r)


これで売却ロジックも完成しました。

[latex]+2\sigma[/latex]を超えて、かつ購入レートより高い位置にいる場合だけ、売却するようにしています。

STEP④ : コードの微調整

これで自動売買ロジックも完成…と言いたいところですが、もう少しだけやるべきことがあります。

具体的には以下の3つです。

  • 日本円の残高確認
  • 売買ロジックの条件追加
  • _request()メソッドの修正

文だけ見てもよく分からないので、順番にコードを書きながら修正していきましょう。

日本円の残高確認

ビットコインの自動売買を行うには、口座内に日本円を入金しておく必要があります。

言い方を換えると、残高が入っていないなら、価格の取得を開始しないようにしたいです。

そのためには、価格情報をDataFrameに追加し始める前に、残高確認の判定をおこなっていきましょう。

ポジションの確認後に、以下のコードを書きます。

while True:
    time.sleep(interval)
    positions = coincheck.position

    # 以下を追加する
    if not positions.get('jpy'):
        raise

    df = df.append(
        {'price': coincheck.last}, ignore_index=True
    )

    """
    中略
    """

これで残高が入っていなかったら、エラーを発生させるように変更できました。

売買ロジックの条件追加

いま書いてある売買ロジックだと、ポジションを持っていなくても、[latex]+2\sigma[/latex]を上振れたらビットコインを売却するようになっています。

Coincheckでは現物取引しか扱っていないので空売りできません。ポジションがないのに売りをかけるとエラーが発生します。

そこで売買判定するときポジションの有無も、あわせて確認するようにしましょう。

以下のように条件判定を変更します。

while True:
    """
    中略
    """
    df['-2σ'] = df['SMA'] - 2*df['std']
    df['+2σ'] = df['SMA'] + 2*df['std']

    if 'btc' in positions.keys():
        if df['+2σ'].iloc[-1] < df['price'].iloc[-1] \
                and coincheck.ask_rate < df['price'].iloc[-1]:
            params = {
                'pair': 'btc_jpy',
                'order_type': 'market_sell',
                'amount': positions['btc']
            }
            r = coincheck.order(params)
            print('exit', r)
    else:
        if df['price'].iloc[-1] < df['-2σ'].iloc[-1]:
            market_buy_amount = coincheck.rate({'order_type': 'buy',
                                                'pair': 'btc_jpy',
                                                'amount': AMOUNT})
            print('使用する日本円', market_buy_amount['price'])
            params = {
                'pair': 'btc_jpy',
                'order_type': 'market_buy',
                'market_buy_amount': market_buy_amount['price']
            }

            r = coincheck.order(params)
            print('entry', r)


このように、先にポジションの有無を確認してから、売買を判定するようにコードを書き直しました。

これで売買ロジックの修正は完了です。

_request()メソッドの修正

最後にクラス内で使っていた_request()メソッドを少し修正しましょう。

具体的には_request()の先頭で、time.sleep(1)を追加します。

class Coincheck(object):
    def _request(self, endpoint, params=None, method='GET'):
        time.sleep(1) # 追加
        nonce = str(int(time.time()))
        body = json.dumps(params) if params else ''

        message = nonce + endpoint + body
        signature = hmac.new(self.secret_key.encode(),
                             message.encode(),
                             hashlib.sha256).hexdigest()

        headers = {
            'ACCESS-KEY': self.access_key,
            'ACCESS-NONCE': nonce,
            'ACCESS-SIGNATURE': signature,
            'Content-Type': 'application/json'
        }

        if method == 'GET':
            r = requests.get(endpoint, headers=headers, params=params)
        else:
            r = requests.post(endpoint, headers=headers, data=body)

        return r.json()

time.sleep(1)を入れないと、リクエスト間隔が狭すぎてエラーになるはずです。(main.pyを実行してみると分かります。)

そのため_request()の内部で1秒だけ待機することで、余計なエラーが発生しないようにしました。

ここまで書ければ、自動売買ロジックの骨組みは作成できています!

まとめ : 条件分岐に応じてビットコインを自動売買しよう!

というわけで、この記事では条件分岐を追加して、自動売買ロジックを修正しました。

自動売買のコード作成自体は「思っていたより難しくないな…」と感じますよね。

できるだけ難しいコードにしないよう心がけているのもありますが、それを加味しても自動売買の実装は意外とシンプルです。

あとは作成したプログラムを実行するだけで、自動売買の取引を開始できます。

でもプログラムを動かす前に、もう少し追加したいコードがあります。

具体的には、売買結果がリアルタイムで分かるようにLINEに通知するプログラムを追加したいです。

そこで次の記事では、もう少しだけコードの追加をおこない、取引があればLINEに通知できるようにしていきましょう。売買結果をLINEに通知できるようになったら、最後に作成したプログラムを24時間365日動かせるようにしていきます。

もしLINEへの通知に興味がなかったり、自動売買プログラムを自分のPCで動かし続けるのであれば、ここまでの記事で十分です。

でも、もっと自動売買を進化させたいなら、次回以降の内容も間違いなく重要になります。

せっかくここまで学習してきたので、もう少しだけお付き合い頂けると嬉しいです!

あわせて読みたい
#15 Python×ビットコイン自動売買 | 自動売買の結果をLINEに通知しよう! こんにちは、はやたす(@hayatasuuu )です。 第15回目の本記事では、自動売買の結果をLINEに通知できるようにしていきます。 前回の記事 : #14 Python×ビットコイン自動...
よかったらシェアしてね!
  • URLをコピーしました!
目次
閉じる