zaif apiとpythonの勉強。一定間隔でリミット注文を出し続ける自動取引botを作った

前振り

zaif apiとpythonでなんやかやしています。

なんか前回参考にしたプログラム、動きがよくわからない&ちゃんと動いてる感じがしなかったです。

で、さもこれから「他のプログラムをいろいろ試してみます!」と言わんばかりでしたけど。

ロジックを思いついちゃったんで結局自分で作りました。
プログラマってそういうところあるよね

書き方はだいたい似たり寄ったりなので、もはやpythonの勉強にはならないなあとか思いつつ。

一定間隔で注文を出し続けるbot

作ったのはそんな感じのbotです。

フフーフフフフ社のフフッフフフーフフフフンによく似てます。

あくまで一定間隔でリミット付きの注文を出し続けるだけなんでセーフじゃないかな……伏せてるあたりでやましさが満ち満ちてますけど……。

価格だけしか見ていないんで、注文の返り値のidを保存しておいて……とかやりたいですね。
apiでエラーが返ってきたのに注文通ってたとかあるらしいんで確実性に欠けそうですけどね。
実際そんなことあるのか知りませんけど。

ちなみに先日作ったカスタムキャンセルをしれっと使っています。
もしご利用される方は合わせてご利用ください。

ソース

from datetime import datetime
import time
import json
from zaifapi import *
from decimal import (Decimal)
import logging
from pprint import pprint
import zaif_custom_cancel as cancel
import sys

FILE_ID='nffn'
args = sys.argv

########################################
# ログ関連
########################################
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(message)s')

handler = logging.StreamHandler()
handler.setFormatter(formatter)
handler.setLevel(logging.INFO)

fileHandler = logging.FileHandler('./log/'+FILE_ID+".log")
fileHandler.setFormatter(formatter)

logger.addHandler(handler)
logger.addHandler(fileHandler)

########################################
# ZAIFAPI
########################################
zaif_keys_json = open('config/zaifkeys.json', 'r')
zaif_keys = json.load(zaif_keys_json)
KEY = zaif_keys["key"]
SECRET = zaif_keys["secret"]
zaif = ZaifPublicApi()
trade = ZaifTradeApi(KEY, SECRET)

########################################
# ユーザ定義用変数
########################################
### 覚悟
KAKUGO = False

### 購入上限と下限(jpy)
### 購入上限と現在価格の低い方から購入加減と現在価格の1/2の高い方まで買い注文を出し揃える
### 切りの良い価格でない方がmakerになりやすい(?)かもしれない
buy_max = 850345
buy_min = 800000

### 1件の注文に利用する金額(jpy)
### 注文の最低単位は0.0001btcなので、それ以下だと注文が不発する
### あと1000円未満の注文だとマイナス手数料が入らない
buy_jpy = 1000

### 利確値幅(jpy)
### 注文価格+これがリミット価格になる
margin = 5000

### 注文値幅(jpy)
### この価格刻みで注文を出す
separate = 2000

########################################
# 関数
########################################
def exists_active_order(orders, action, price, comment):
    for order_id in orders:
        if (action == orders[order_id]['action']
        and price == orders[order_id]['price']
        and comment == orders[order_id]['comment']):
            return True

    return False

def get_search_order_ids(orders, action='all', price=-1, comment='1'):
    search_order_ids = []
    for order_id in orders:
        if (action in ['all', orders[order_id]['action']]
        and price in [-1, orders[order_id]['price']]
        and comment in ['*', orders[order_id]['comment']]):
            search_order_ids.append(order_id)

    return search_order_ids


########################################
# 処理開始
########################################

# 実行時、引数に1を指定すると同じIDの買い注文をすべてキャンセルする
if len(args) > 1:
    if args[1] == '1':
        logger.info('このプログラムで発注されている既存の注文をキャンセルします')
        time.sleep(1)
        cancel.custom_cancel_orders('bid',FILE_ID)
        logger.info('キャンセル完了')
        time.sleep(1)

# 最終的には処理全体をループさせる。1分周期くらいで
while True:
    logger.info('確認開始')

    # 各種情報取得。成功するまで続ける
    while True:
        try:
            last_price = zaif.last_price('btc_jpy')['last_price']
            funds_jpy = trade.get_info2()['funds']['jpy']
            break
        except Exception as ex:
            logger.debug(ex)
            logger.info('get info error')
            time.sleep(5)

    # 初期化。
    order_price = buy_max

    # 注文のチェック。
    # 現在価格から下方無限に、separateずつ注文の有無を確認する。
    # ループは以下の条件で終了する
    # 1. funds_jpyがbuy_jpy以下になる(注文できないため)
    # 2. order_priceがlast_price/2かbuy_min以下になる(注文範囲を超えているため)
    while (funds_jpy > buy_jpy and order_price > last_price / 2 and order_price > buy_min):

        # 覚悟が無いとlast_place以下になるまで注文しない
        if order_price > last_price and not KAKUGO:
            logger.info("注文価格: %s ... 覚悟がないのでスルーします" % str(order_price))
            time.sleep(1)
        else:
            logger.info("注文価格: %s ... 注文開始" % str(order_price))

            # なんか注文がエラーになっても通ってたりすることがあるらしいので都度active_ordersを確認する
            while True:
                try:
                    # 注文を探す
                    active_orders = trade.active_orders(currency_pair='btc_jpy')
                    # 買いと売りの両方を探す。売りはコメントが付かないので誤認する可能性がある。
                    # どうしようもないので気にしない。
                    # 注文が有る場合、その価格はなにもしない。
                    if (exists_active_order(orders=active_orders, price=order_price, action='bid', comment=FILE_ID)
                    or exists_active_order(orders=active_orders, price=order_price + margin, action='ask', comment='')):

                        logger.info("まだ注文が残っているのでスルーします。")
                    else:
                        # 注文が無い場合は(1) リミット売りが成立している (2) エラーなどで注文が通らなかった
                        # のいずれかなので、注文し直す。
                        order_amount = Decimal(buy_jpy / order_price).quantize(Decimal('0.0001'))
                        trade_result = trade.trade(
                            currency_pair = 'btc_jpy',
                            action = 'bid',
                            price = order_price,
                            amount = order_amount,
                            limit = order_price + margin,
                            comment = FILE_ID
                            )

                        # 残高の更新
                        funds_jpy = trade_result['funds']['jpy']

                        logger.info("%sJPYに%sBTCの注文完了。残JPYは%sです。" % (str(order_price), str(order_amount), str(funds_jpy)))

                    break
                except Exception as ex:
                    logger.debug(ex)
                    logger.info('注文が通らなかったっぽいので10秒後に注文し直します。')
                    time.sleep(10)

        # 注文価格を引き下げて次のループへ
        order_price -= separate

    if funds_jpy < buy_jpy:
        logger.info('日本円がないので終了します。')
    if order_price < last_price / 2:
        logger.info('注文可能価格を割ったので処理を終了します。')
    if order_price < buy_min:
        logger.info('注文価格帯を割ったので処理を終了します。')
    logger.info('確認終了')

    # 定期的に注文の状態を確認する
    time.sleep(60)

使い方

logディレクトリとconfig/zaifkeys.jsonを用意したのち、コマンドでpython ~~~~.pyとか叩いてください。

引数に1をつけると、処理の冒頭で既存注文をキャンセルします。

ユーザー設定項目

KAKUGO

覚悟です。
覚悟を決める(Trueにする)と、設定した注文価格帯において現在価格より高い価格についても発注します。

当然ながら即座に注文が通り、リミット注文に切り替わります。

buy_max, buy_min

等間隔での注文を出す価格帯です。
buy_maxを起点として、buy_minを下回るまで、後述するseparateずつ下に注文を出していきます。

覚悟を決めていない場合は、現在価格より下にしか注文を出しません。

buy_jpy

注文価格です。
この価格を注文価格で割った値が発注するBTCの量になります。
現在価格の10000分の1以下だと、最低発注量に引っかかるため注文が不発して無限ループに陥ります。

1000円くらい言っておけばとりあえず大丈夫でしょう。

margin

リミットを決める値です。
注文価格+この値がリミットの価格になります。
このため、あまり小さいと往復注文になってしまいます。
ただしいくらから往復とみなされなくなるかは情報がないのでわかりません。

ちなみにリミット注文にはコメントが入らないため、この価格に一致する注文をリミット注文と決めつけます。
なのでこの値を変更して再実行すると、それまでのリミット注文には対応できなくなります。

separate

注文を出す値幅です。
2000なら2000円ごとに注文を出します。

880000, 878000, 876000, 874000, … という感じです。

まとめ

というわけで取引botを組んでみました。
利益が出るのかは知りません。
出るんじゃないかな。
出るといいな。

現物取引なので絶対にロスカットされないというのは利点といえば利点ですが、利益の出るペースが遅すぎて使い物にならないかもしれません。

それにしても当初妄想していた、価格がガクッと下がったら買って……みたいなのはいろいろ面倒だなあと感じました。

これもロジックの大部分をリミット注文という機能に頼ってますからね。

それがなかったら注文ごとに状態を監視して、買いが通ったら売りを出して、売りが通ったら買いを出して……とかやらないといけないですし。

0.0001BTCずつ売りを下げることで、徐々に所有するBTCを増やして……とかやりたい気もするんですが、リミット注文が楽すぎるので断念してます。

コメント

  1. 覚悟があるとないのとではどれ程の違いが出てくるものなのでしょうか?

    返信削除
  2. 値段が上がったときの利益は増えますが、値段が下がったときの含み損が増えます。
    具体的な数字については設定によるのでなんとも言い難いです。

    返信削除

コメントを投稿

こんにちは