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

#13 Python×ビットコイン自動売買 | クラスを作成してコードを読みやすくしよう!

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

今回の講義では、自動売買で使うためのクラスを作成していきたいと思います。

 

前回の記事はこちら

keep-getting-ticker-by-coincheck
#12 Python×ビットコイン自動売買 | リアルタイムでビットコイン価格を取得し続けよう!こんにちは、はやたす(@hayatasuuu )です。 この記事では、リアルタイムでビットコインの最新価格を取得し続ける方法を紹介...

 

今までの講義では、以下のようにすべてのコードを1つのファイルに書いていました。

import hmac
import hashlib
import json
import time
from pprint import pprint
import configparser

import requests

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

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

BASE_URL = 'https://coincheck.com'
url = BASE_URL + '/api/exchange/orders'

nonce = str(int(time.time()))

params = {
    'pair': 'btc_jpy',
    'order_type': 'market_sell',
    'amount': '0.00500736'
}

body = json.dumps(params)

# body = ''
message = nonce + url + body
signature = hmac.new(SECRET_KEY.encode(),
                     message.encode(),
                     hashlib.sha256).hexdigest()
headers = {
    'ACCESS-KEY': ACCESS_KEY,
    'ACCESS-NONCE': nonce,
    'ACCESS-SIGNATURE': signature,
    'Content-Type': 'application/json'
}

# r = requests.get(url, headers=headers)
r = requests.post(url, headers=headers, data=body)
r = r.json()

pprint(r)

 

ただ、このようなコードを作成しておくと、非常にコードが読みづらいです。また、毎回APIのアクセス先を編集する必要があります。

そこで当記事では、ビットコインの最新価格取得や、購入のためのクラスを作成することで、コインチェックAPIを扱いやすくしたいと思います。

この記事で紹介する内容まで学習していただけば、あとはビットコイン自動売買のロジックを組むだけです。

クラスを苦手としている人は多いですが、これを機に慣れていきましょう!

 

クラスを作成してコードを読みやすくしよう!

 

ここからは、以前のコードを使いながら、新しいファイルcoincheck.pyにクラスを作成していきます。

順を追って解説していきますね。

STEP① : ライブラリのインポート

 

まずはmain.pyで使っていたライブラリをインポートします。

import hmac
import hashlib
import json
import time

import requests

 

上記ライブラリは、PrivateAPIの利用で必要になるんでしたね。

STEP② : __init__()の設定

ライブラリのインポートが完了したら、さっそくクラスを作成していきます。今回はCoincheckという名前で、クラスを作成していきましょう。

class Coincheck(object):
    pass

 

そうしたら、まず書いていきたいのが__init__()です。ここには、インスタンスの生成をしたとき、どのメソッドを使うにも共通で持たせておきたい変数を設定しておきます。

今回クラスを作成する上で、各APIごとにメソッドを作成していきたいです。

そんなとき、どのメソッドでも共通で使う変数を考えてあげると、以下のような__init__()を作成できるかと思います。

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'

 

アクセスキーとシークレットキーについては、各種APIで必要になるので問題ないと思います。

あとはURLの設定ですが、こちらは元々BASE_URLで設定していたものです。

クラスになっても、今までと同じようにBASE_URL + それぞれのAPIにアクセスできるURLの形で書いていきます。

STEP③ : 共通で使う内部メソッドの作成

今まで色々なAPIにアクセスして分かったと思いますが、基本的に変更されるのはURLとパラメータだけです。

反対に、以下の内容は毎回同じものを使っています。

  • 署名
  • ヘッダーの作成
  • リクエスト部分

 

こういった共通部分は、できれば一回で済ませておきたいです。

ゆえに、以下のようなリクエスト用のメソッドを追加してあげましょう。

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'
        }

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

        return r.json()

 

上記のように_request()を準備しておくと、どのURLにアクセスするときでも、同じメソッドを使えば認証からリクエスト結果の取得までできるようになります。

また_request()のように、アンダースコアを1つ付けているのは、このメソッドを内部的に使うことを想定しているからです。

言い方を換えると、インスタンス.メソッド名()の形で使うことを想定していません。あくまでもクラス内で使っていくメソッドになります。

※とはいっても、もちろん外からアクセスすることは可能です!

STEP④ : 各APIにアクセスするためのメソッドを作成

 

これまでの講義で、以下5つのAPIを紹介しました。

  • /api/ticker
  • /api/trades
  • /api/order_books
  • /api/exchange/orders
  • /api/accounts/balance

上記の各APIごとに、メソッドを作成していきましょう。

tickerメソッド

 

クラスに対して、以下のメソッドを追加します。

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

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

 

これで、先ほど作成しておいた_request()を活用しつつ、メソッドの作成が完了しました。

ここまで書いたら、実際にticker()を使ってみたいと思います。main.pyを開いて、コードを以下のように編集しましょう。

import configparser

from coincheck import Coincheck

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

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

coincheck = Coincheck(ACCESS_KEY, SECRET_KEY)
ticker = coincheck.ticker()
print(ticker)

 

クラスを使うときは「①インスタンスの生成」をして「②インスタンス名.メソッド名()」で呼び出すんでしたね。

この状態でmain.pyを実行してみましょう。

create-coincheck-class-object1

そうすると、今までどおりにティッカー情報を取得できました。ビットコインの最新価格が5,867,997円ということで、かなり高い位置にいますね^^;

 

lastプロパティ

 

いま使っていた/api/tickerでは、lastしか使っていませんでした。つまり、他のKeyは今回の自動売買では不要ということになります。

今回はlastだけ取得できるようにしたいので、以下のようなプロパティを作成しましょう。

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

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

 

これで、lastプロパティを使えば、ビットコインの最新価格だけを取得できるようになりました。

まずは以下のようにmain.pyを編集していきましょう。

import configparser

from coincheck import Coincheck

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

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

coincheck = Coincheck(ACCESS_KEY, SECRET_KEY)
# ticker = coincheck.ticker()
# print(ticker)

print(coincheck.last)

 

これでmain.pyを実行してみたいと思います。

create-coincheck-class-object2

そうすると、ビットコインの最新価格だけが取得されました。基本的にはticker()メソッドではなくlastプロパティを使っていけば良いですね。

 

その他メソッドの作成

 

残りのAPIについても、同じ要領で作成していけばOKです。

つまり、コード全体としては以下のようになります。

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'
        }

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

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

 

balance()メソッドは、各通貨ごとに自分の持っているポジションが表示されます。

それゆえ、今回の自動売買と無縁なものだらけになってしまうので、ticker()と同じ発想でpotisionプロパティを追加しました。

 

これでクラスの作成が完了しました。

 

まとめ : クラスを作成してコードを読みやすくしよう!

というわけで、ビットコインの自動売買で使うクラスを作成していきました。

このようにクラスを作成しておくと、かなりコードの見通しが良くなります。

これから自動売買のロジックを組んでいきますが、色々なコードが錯綜していると自分で書いていても訳の分からないことになってしまいます。

この講義に限らず、うまくクラスや関数を活用して、同じような処理をまとめていきましょう。

 

次回の講義では、実際に自動売買ロジックを組んでみたいと思います。

いよいよ本格的になっていくので、難しいと感じる部分が出てきたら、復習しながら学習を進めていきましょう!