Python×ビットコイン自動売買

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

buy-and-sell-bitcoin-automatically

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

今回の講義では、途中まで作成していた自動売買ロジックを、完成させていきたいと思います。

前回の記事はこちら

create-automatic-trading-system
#13 Python×ビットコイン自動売買 | 自動売買に必要なロジックを作成してみよう!こんにちは、はやたす(@hayatasuuu )です。 今回の講義では、自動売買に必要なロジックを作成していきたいと思います。 ...

とはいえ自動売買ロジックは、ほぼ完成しています。
あとは少しだけクラスを編集しつつ、売買ロジックを書いていくだけです。

今回の内容まで取り組めば、最小限のコード作成は完了します。
残りもわずかなので、ぜひ頑張っていきましょう!

前回のコードを確認する

import configparser
import time

import pandas as pd

from coincheck import Coincheck

conf = configparser.ConfigParser()
conf.read('config.ini')

ACCEES_KEY = conf['coincheck']['access_key']
SECRET_KEY = conf['coincheck']['secret_key']

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

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!!!')

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

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

  • 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つ追加していきます。

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

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

それぞれの使い方は、後ほど売買プログラムを組むときに紹介していきます。
とりあえず今は、これらのメソッドとプロパティを追加していきましょう。

とはいえ、メソッドの追加に関しては、第12回目と同じ要領で作成できます。
各メソッドのアクセス先をCoincheckAPIドキュメントで確認すると、以下のようになっています。

  • メソッド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プロパティの出力を確認してみたいと思います。

>>> 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と決まっているけど、日本円の金額は\(-2\sigma\)を下回ったときなので、毎回同じとは限りません。

つまり、購入したいビットコインのレートに応じた、日本円の金額が分かっている必要があります。
この計算を行うには、先ほど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③ : 売却ロジックの作成

次に売却ロジックです。
いま作成してある条件分岐は「ビットコイン価格が\(+2\sigma\)より上だったら」になっています。

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

"""
中略
"""
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)

 

これで売却側のロジックも完成しました。
\(+2\sigma\)を超えて、かつ購入レートより高い位置にいる場合だけ、売却するようにしています。

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
    )

    """
    中略
    """

 

これで残高が入っていない場合に、エラーを発生させるような処理を書けました。

売買ロジックの条件追加

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

ただ、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.spleep(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()

 

おそらく、先ほどまで書いていたmain.pyを実行すると、リクエスト同士の間隔が狭すぎてエラーになるはずです。

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

ここまで書ければ、今回使っていきたい自動売買ロジックの骨組みは作成完了です。

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

というわけで、条件分岐の追加もできたので、自動売買ロジックも完成しました。

おそらく自動売買の作成は「思っていたより難しくない」と感じますよね。
確かに、できるだけ難しいコードにしないよう心がけていますが、それでもやるべきことはシンプルです。

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

ただ、自動売買する上で、もう少し追加したい部分があります。
具体的には、売買結果がリアルタイムで分かると良いので、LINEに通知したいと思います。

また、いま作成したコードを自分のPCで動かし続けるのは、あまり良い判断とは言えません。

なので次回以降は、もう少しだけコードの追加をおこない、さらに自動売買プログラムを24時間365日動かすための設定をおこなっていきます。

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

でも、もっと自動売買を進化させたいなら、次回以降の内容も間違いなく重要なので、続けて学習することをおすすめします。

ということで、次回も学習する場合は、もう少しだけお付き合い頂けると嬉しいです!

第15回目の記事はこちら

notify-trade-results-by-line
#15 Python×ビットコイン自動売買 | 自動売買の結果をLINEに通知しよう!こんにちは、はやたす(@hayatasuuu )です。 今回の講義では、ビットコインの売買結果を、LINEに通知するプログラムを作...

トップページはこちら

python-bitcoin-auto-trade
Python×ビットコイン自動売買 | 初心者でも理解できる入門講義『Pythonを使ったビットコインの自動売買って、どうやって実装するんだろう...。分かりやすく書かれている入門記事はないかな〜。』このような悩みを解決する記事になっています。Pythonを使ったビットコインの自動売買を学んでみたい人は必見です。...
ABOUT ME
はやたす
たくさんPythonを紹介するYouTuberです(登録者1.94万人) | フリーランスで機械学習/分析案件も請けています(経験業界 : 金融, 情報通信, サービス) | 元プログラミングスクール講師