【初心者向け】Zaif API×Pythonでプチフィンテック22 仮想通貨自動取引botの作成(メイン処理の実装2)

今回はbotのメイン中のメイン処理である発注処理を実装していきます。

なお、本シリーズは今回が最終回ですので、最後に今まで実装してきたコードを一通り掲載します。これまで断片的に説明してきましたが、全体を見るとより理解が深まると思いますので、ぜひご参照ください。

<スポンサーリンク>

メイン処理の実装(続き)

発注処理の実装

それでは早速発注処理を実装していきます。

ここは、これまで説明してきた内容が理解できていれば、それほど難しくはないものと思います。

なお、今回記述する処理は、whileとtryの中に記述しますので、インデントを入れることを忘れないようご注意ください。

##③-2発注
        for order_price, ordered in position_list.items():
            if order_price < last_price and ordered == False:
                params = {
                    'method': 'trade',
                    'nonce': time(),
                    'currency_pair': CURRENCY_PAIR,
                    'action': 'bid',
                    'price': order_price,
                    'amount': AMOUNT,
                    'limit': order_price + LIMIT
                }

                response_dict = tradeRequester(params, KEY, SECRET)

                if response_dict['success'] == 1:
                    position_list[order_price] = True
                    logger.info('買い発注({})'.format(order_price))
                elif response_dict["success"] == 0:
                    logger.warning('買い発注失敗: {} '.format(response_dict['error']))

        logger.info('ラウンド{}終了'.format(counter))
        logger.info('60秒後に次ラウンド開始\n')

        # ラウンド完了後、60秒待つ
        sleep(60)

内容について1点だけ補足します。

3行目に「if order_price < last_price and ordered == False:」というif文を入れ、発注処理を行う場合の条件を指定しています。

このif文には条件が2つ含まれていますが、後半は良いでしょう。単純にポジションの状態がFalseであれば発注処理を行うということを表しています。

前半部分の「order_price < last_price」ですが、これは現在価格よりポジション価格が低い場合のみ発注処理を行うことを表しています。

なぜこれが必要なのか、例を用いて説明します。

例えば、ポジションリストには価格が\1,000,000と\1,200,000の2つのポジションがあるとし、現在価格は\1,100,000だったとしましょう。

上記の条件が無い場合、\1,000,000と\1,200,000の2つの発注が行われますが、現在価格が\1,100,000であるため、\1,200,000のポジションは即座に\1,100,000で買い約定してしまいます。

予定よりも安い価格で買えているのは良いですが、逆に計画外の購入により資金を圧迫してしまうというデメリットもありますので、作成するbotではこのような意図した発注が行われないように制御しています。

テスト実行

ここまでのコーディングで仮想通貨取引botは完成しましたので、正しく動作するか、プログラムを実行して確認してみましょう。

なお、Zaifサーバの負荷状態によってはメイン処理に入る前に例外が発生してプログラムが終了してしまったり、なかなか発注が行えない場合もあります。場合によっては、API経由/Webからの直接発注を問わず、一見発注が成功したように見えて、Zaif上の注文データには反映されない場合も確認されています

ですので、特にプログラムを実行した直後は、表示されるログやZaifへ直接ログインして見ることができる注文履歴等を参照してみて、正しい処理が行われているか確認してみることをお勧めします。

さて、プログラムを実行してみるとコンソールに処理状況が出力されます。メイン処理に入ってからは、異常が発生してもループが途切れないようにプログラムを作成していますので、念のためにプログラムをしばらく実行して、異常発生時も処理が継続することも確認しておくのが良いと思います。

プログラム全量

冒頭に記載しましたように、プログラムの全量を掲載しておきますのでご参照下さい。

#①前処理
##モジュールインポート
import requests
from urllib.parse import urlencode
import hashlib
import hmac
import json
from time import time, sleep
import logging
import sys

##パラメータ設定
UPPER_PRICE = 2000000
LOWER_PRICE = 1800000
ORDER_INTERVAL = 100000
LIMIT = 200000
AMOUNT = 0.001
CURRENCY_PAIR = 'btc_jpy'
KEY = ""
SECRET = ""

##メソッド定義
###現物取引APIへの共通アクセスメソッド
'''
    概要
        現物取引API利用時の共通処理
    引数
        params: HTTPリクエストに指定するパラメータ
        key: 自アカウントのKey
        secret: 自アカウントのSecret
    戻り値
        HTTPレスポンス(ディクショナリ形式)
'''
def tradeRequester(params, key, secret):
    encoded_params = urlencode(params)
    signature = hmac.new(bytearray(secret.encode('utf-8')), digestmod=hashlib.sha512)
    signature.update(encoded_params.encode('utf-8'))

    headers = {
        'key': key,
        'sign': signature.hexdigest()
    }

    response = requests.post('https://api.zaif.jp/tapi', data=encoded_params, headers=headers)

    if response.status_code == 200:
        response_dict = json.loads(response.text)
        return response_dict
    else:
        raise Exception('status_code is {status_code}, params are {params}'.format(status_code = response.status_code, params = str(params)))

###サーバ時刻取得メソッド
'''
    概要
        Zaifサーバ上の現在時刻を取得する
    引数
        key: 自アカウントのKey
        secret: 自アカウントのSecret
    戻り値
        サーバ上の現在時刻
'''
def getTimestamp(key, secret):
    params = {
        'method': 'get_info2',
        'nonce': time()
    }
    response_dict = tradeRequester(params, key, secret)
    timestamp = response_dict['return']['server_time']
    return timestamp

###現在価格取得メソッド
'''
    概要
        取引対象通貨ペアの現在価格を取得する
    引数
        なし
    戻り値
        取引対象通貨ペアの現在価格
'''
def getLastPrice():
    response = requests.get('https://api.zaif.jp/api/1/last_price/' + CURRENCY_PAIR)
    if response.status_code == 200:
        response_json = response.json()
        last_price = response_json['last_price']
        return last_price
    else:
        raise Exception('status_code is {status_code}, params are {params}', status_code = response.status_code, params = 'last_price')

##ログ取得設定
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

formatter = logging.Formatter('[%(levelname)s] %(asctime)s %(message)s')
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)

logger.addHandler(stream_handler)

##時刻変数定義
current_timestamp = None
previous_timestamp = None

##ループカウンタ初期化
counter = 0

#②ポジションリストの生成と初期化
logger.info('準備開始')

try:
    #ポジションリストの生成と、全ポジションをFalse(未発注)で初期化
    position_list = {}
    for i in range(LOWER_PRICE, UPPER_PRICE+1, ORDER_INTERVAL):
        position_list[i] = False

    #注文データを参照し、発注済みのポジションをTrue(発注済み)に初期化
    params = {
        'method': 'active_orders',
        'nonce': time(),
        'currency_pair': CURRENCY_PAIR
    }

    response_dict = tradeRequester(params, KEY, SECRET)

    #全注文を参照し、ポジションリストを更新する必要があるか確認
    order_numbers = response_dict['return'].keys()
    for order_number in order_numbers:
        price = int(response_dict['return'][order_number]['price'])

        #買い注文がある場合の処理
        if response_dict['return'][order_number]['action'] == 'bid':
            if price in position_list.keys():
                position_list[price] = True

        #売り注文がある場合の処理
        if response_dict['return'][order_number]['action'] == 'ask':
            if price - LIMIT in position_list.keys():
                position_list[price - LIMIT] = True

except Exception as e:
    logger.exception('準備失敗: %r' % e)
    sys.exit()

logger.info('準備完了')
#③メイン処理(ループ)
while True:
    try:
        #ループ開始処理
        counter += 1
        logger.info('ラウンド{}開始'.format(counter))
        previous_timestamp = current_timestamp
        current_timestamp = getTimestamp(KEY, SECRET)
        last_price = getLastPrice()
        logger.info('現在価格: {}'.format(last_price))

##③-1約定/決済状態の確認(2回目以降のループ処理時のみ)
        if counter >= 2:
            params = {
                'method': 'trade_history',
                'nonce': time(),
                'since': previous_timestamp,
                'currency_pair': CURRENCY_PAIR
            }

            response_dict = tradeRequester(params, KEY, SECRET)

            #前回ループ処理実行時以降に発生した約定/決済データを1つ1つ確認
            order_numbers = response_dict['return'].keys()
            for order_number in order_numbers:
                price = int(response_dict['return'][order_number]['price'])

                #買い約定がある場合の処理
                if response_dict['return'][order_number]['your_action'] == 'bid':
                    logger.info('買い約定({})'.format(price))
                    logger.info('売り注文({})'.format(price + LIMIT))

                #決済がある場合の処理
                elif response_dict['return'][order_number]['your_action'] == 'ask':
                    logger.info('売り約定({})'.format(price))
                    position_list[price - LIMIT] = False

##③-2発注
        for order_price, ordered in position_list.items():
            if order_price < last_price and ordered == False:
                params = {
                    'method': 'trade',
                    'nonce': time(),
                    'currency_pair': CURRENCY_PAIR,
                    'action': 'bid',
                    'price': order_price,
                    'amount': AMOUNT,
                    'limit': order_price + LIMIT
                }

                response_dict = tradeRequester(params, KEY, SECRET)

                if response_dict['success'] == 1:
                    position_list[order_price] = True
                    logger.info('買い発注({})'.format(order_price))
                elif response_dict["success"] == 0:
                    logger.warning('買い発注失敗: {} '.format(response_dict['error']))

        logger.info('ラウンド{}終了'.format(counter))
        logger.info('60秒後に次ラウンド開始\n')

        # ラウンド完了後、60秒待つ
        sleep(60)

    except Exception as e:
        logger.warning('取引ループ中にエラー発生、60秒待機:  %r\n' %e)
        sleep(60)

終わりに

多少長くなりましたが、今回の記事で、仮想通貨取引bot作成シリーズは完結です。

作成したbotは、もちろんこのままでも十分動かすことができますが、本シリーズで紹介した手法を応用して、ご自身にあったトレードスタイルをプログラムとして実装してみるのも面白いと思います。興味がある方はぜひ試してみて下さい。

カテゴリ:フィンテック
<スポンサーリンク>

シェアする

フォローする