[Python3] [CCXT] UKI さんの元祖ドテン君を実装してみた

元祖ドテン君とは、AKAGAMI さんのドテン君の名前の元になった bot で、こちらの記事で公開されてるやつのことです。

ここで紹介されている本来のロジックは「ブレイクした次の足でエントリー」ですが、あえて逆指値でレンジブレイク直後にエントリーするようにしました。バックテストした感じそっちのほうがパフォーマンス良かったので。
5/14追記:
バックテストのスクリプトが間違ってました。元祖のほうがパフォーマンス良かったです。修正版はこちら。

あまり自信はありませんが、それ以外は同じに作っているつもりです。

import json
import time
from datetime import datetime

import ccxt
import numpy as np
import requests

API_KEY = ''
API_SECRET = ''
POSITION_SIZE_USD = 50
# 元祖ドテン君
RESOLUTION_M = 120
WINDOW_SIZE = 5
K = 1.6

BTCUSD = 'BTC/USD'
XBTUSD = 'XBTUSD'
BUY = 'Buy'
SELL = 'Sell'

mex = ccxt.bitmex()
mex.apiKey = API_KEY
mex.secret = API_SECRET


class BitMEXChart:
    def __init__(self, resolution_m):
        """
        :param resolution_m: 1, 3, 5, 15, 30, 60, 60*2, 60*4, 60*6, 60*12, 60*24, 60*24*3, 60*24*7
        """
        self._chart = self._fetch_chart(resolution_m=resolution_m)

    def _fetch_chart(self, resolution_m):
        chart = requests.get(
            'https://api.cryptowat.ch/markets/bitmex/btcusd-perpetual-futures/ohlc?periods={:d}'.format(resolution_m * 60)).json()
        return chart['result']['{}'.format(resolution_m * 60)]

    def timestamps(self):
        return [int(c[0]) for c in self._chart]

    def open_values(self):
        return [float(c[1]) for c in self._chart]

    def high_values(self):
        return [float(c[2]) for c in self._chart]

    def low_values(self):
        return [float(c[3]) for c in self._chart]

    def close_values(self):
        return [float(c[4]) for c in self._chart]

    def volumes(self):
        return [float(c[5]) for c in self._chart]


def stop(side, price):
    positions_size = fetch_positions_size()
    if side == BUY and positions_size > 0 or side == SELL and positions_size < 0:
        # 同じ方向には注文しない
        return

    # 約定後 POSITION_SIZE_USD のポジションを持つ
    order_size = POSITION_SIZE_USD + abs(positions_size)

    orders = mex.fetch_orders(BTCUSD, params={
        'filter': {
            'open': True,
            'ordType': 'Stop',
            'side': side,
        }
    })
    if len(orders) == 0:
        # 新規注文
        mex.create_order(BTCUSD, type='Stop', side=side, amount=order_size, params={
            'stopPx': price,
            'execInst': 'LastPrice',
        })
    else:
        # price と amount をアップデートするだけ
        order = orders[0]
        if order['info'].get('stopPx') == price and order['amount'] == order_size:
            return
        mex.edit_order(order['id'], order['symbol'], order['type'], order['side'], order_size, params={
            'stopPx': price,
            'execInst': 'LastPrice',
        })


def fetch_positions_size():
    positions = mex.private_get_position({
        'columns': json.dumps(['currentQty']),
        'filter': json.dumps({'symbol': XBTUSD})
    })
    return sum([p['currentQty'] for p in positions])


def cancel_orders(side):
    return mex.private_delete_order_all({
        'filter': json.dumps({'side': side})
    })


def main():
    chart = BitMEXChart(resolution_m=RESOLUTION_M)
    range_mean = np.mean([h - L for h, L in zip(chart.high_values(), chart.low_values())][-WINDOW_SIZE-1:-1])
    open_value = chart.open_values()[-1]
    up_bound = (open_value + range_mean * K) // 0.5 * 0.5
    down_bound = (open_value - range_mean * K) // 0.5 * 0.5

    positions_size = fetch_positions_size()
    if positions_size == 0:
        stop(BUY, up_bound)
        stop(SELL, down_bound)
    if positions_size > 0:
        # 買いポジション持ち
        stop(SELL, down_bound)
        cancel_orders(BUY)
    if positions_size < 0:
        # 売りポジション持ち
        stop(BUY, up_bound)
        cancel_orders(SELL)


if __name__ == '__main__':
    print('--- {} ---'.format(datetime.now()))
    try:
        while True:
            main()
            time.sleep(10)
    except Exception as e:
        time.sleep(10)
        raise

動かすには API キーの設定と依存ライブラリのインストールが必要です。

pip install ccxt
pip install requests
pip install numpy

もしよくわからないところがあれば分かる範囲でお答えしますので、Twitter (@ycrypthon) かコメントでお知らせください。もしよろしければ♡やシェアをしてくださると喜びます\( 'ω')/

4/30 追記:
動かない場合は Python のバージョンを上げてみてください。Python 3.5.2 で動作確認できています。

よろしければこちらの note もどうぞ\( 'ω')/

ご覧いただきありがとうございます!