noteのタイトル画像

[Puppeteer] ゆっくりMM(Market Maker)bot 「HAL900」

自作の仮想通貨自動取引bot開発フレームワーク「Puppeteer」上で動作する初の仮想通貨自動取引botのご紹介です。
何か面白い命名をしたくて、次のように名付けました。

ゆっくりMM(Market Maker)bot 「HAL900」

です。
命名の由来は、有名なSF映画に登場する人工知能「HAL9000」から取りました。桁が1つ少ないのは少し控えめにしたためです。
名付けはただの趣味です。
私はプロジェクトの多くをgitlabやbitbucket上で管理しているので、すぐに見分けがつくようにするためです。

もともとHAL900はNeoDuelBot用のストラテジとして作成していました。
しかしNeoDuelBotの運営主体が変更になり無償公開期間が終わり、その後noteにて有償フレームワークとして販売されたことを契機に全く別のbot開発フレームワーク「Puppeteer」を急遽立ち上げ、Puppeteer用のストラテジ(Puppeteerではそれぞれのストラテジのことを「傀儡 (Puppet)」と呼んでいます)として移植しました。

巷で言われているMMbotみたいに秒速で動くことは(まだ)できないので、現状は「ゆっくり動作」です。
それでも30秒程度の実行周期は出せてます。

このbotの特徴は

・対象とする仮想通貨取引所はbitmexで、通過ペアはBTC/USDです。
・Market Makeするように動作します。
・利益は指値約定時にもらえる手数料やスプレッド狙いです。
・実行周期は30秒から指定できます。(これ以上短い周期でも動作可能だと思いますが、環境に左右されると思われます)
・指値は直近のbid/ask価格を中心として、bid側とask側にそれぞれ複数の指値を間隔をあけつつ配置します。(個数をパラメータで指定)
・注文が指値ではなく、成行で入ってしまう価格の注文は自動的にキャンセル扱いになるように発注します。(bitmexの発注APIのオプション機能を利用)
・配置するbid/askに一番近い指値は、bid/ask値から任意の数値(0.5刻み)だけ下駄を履かせて指定できます。
・一つの指値に置くサイズはLOT_SIZEパラメータで指定します(固定ロット)。
・損切りする閾値となる最大ポジションサイズをパラメータで指定します。このパラメータで指定した以上のポジションを保持したら、現在の参入価格で指値損切りを試みます。(現在価格での指値や成行での損切り機能はありませんが、ソースコード中にかなりコメントを書いているので、プログラミングスキルのある方ならば解読して変更は容易だと思います)
・参入価格と現在bid/askの値との乖離がいくらになったら利確するかを指定できます。乖離0での利確(手数料だけ狙う)も可能です。
・置いた指値が順次消費されて、ある一定の閾値を下回ったら指し直します。

です。

色々な設定を試して、必要だと思われるパラメータを定義ファイル(JSONファイル)で指定可能としました。
特に実行周期や指値の数、最初から設定するスプレッド、利確幅などをちょっと変更すると、かなりbotの挙動が変わりました。(もちろん、その時の相場がヨコヨコなのか、かなり大きく動くのかによる影響が一番大きかったですが)

パラメータ化せずに、本botを同じ設定で動かす人が多い場合はbot同士が競合してしまうだろうと予想しました。
なので、パラメータは極力多くして多様性(?)を確保しました。

最小実行周期が30秒程度のMM botなんて遅い?と思われるかもしれませんが、その分指値幅を確保して複数の指値をバラまくことで対応しています。

当然環境によりますが、実行周期が10秒や30秒、1分でも勝つ時は勝つわけですが、相場がトレンドになった時にいかに撤退させてあげるかがMM bot系の課題だと考えています。
片方向に一気に値が雪崩を起こした時の無限ナンピン地獄を回避する方法は、今の所は「参入価格での損切り撤退」だけ実装しています。
(現在価格での指値や成行決済は試していません)

本botのソースコードには試行錯誤した跡がコメントとして大量に書き込まれているので、ソースが読める方は解析してみてください。
コメントを付加しておかないと、1週間後に何故このようなロジックにしたのかを自分で思い出せないので、独り言のように書き込んでいます。
(やたら長い変数名や、どう見ても和製英語を勘違いしたような変数名が多々ありますが、後日に変数名だけを見ても一応は意図が汲み取れるようにしたつもりです)
あーでもないこーでもないと試行錯誤しつつ、ネットのつぶやきや、参加しているdiscordのコミュニティの会話からヒントを得て、なんとか形にできました。

価格は非プレミアムnote会員の最高額とさせていただいています。
申し訳ありません。

また、本botを使って稼げることを保証するものではありませんし、損失を被る可能性のあることをご承知おきください。
本botによって損害が発生したとしても責任は負いかねます。
(基本は売り切りです。継続的なアップデートをご希望の場合は、別途有償にさせていただく場合もあります)

以下のパートから有償です。

目次
・Puppeteer(傀儡師)の導入 (環境はAWS Cloud9を想定します)
・必要なモジュールのインストール
・TestNetで
Puppeteerの試運転
・HAL900のソースコード
・パラメータ詳細
・実行
・今後

--------------------------

・Puppeteer(傀儡師)の導入 (環境はAWS Cloud9を想定します)

AWS Cloud9のpython3環境の構築については、すでに多くの方が記述しているので、ここ等を参考にしてpython3系の環境を構築してください。

私自身はiMacで開発しているので、python3が動作するオンプレミス環境でも動くとは思いますが、個々人のPCの環境差が原因で問題が発生してもこちらで再現できないので、対応できない可能性があります。

検証で使用したpython環境は python version 3.6.8 です。

Puppeteer本体をgitlabからcloneします。

aws cloud9のコンソール上で

git clone https://gitlab.com/o-matsuo/puppeteer.git

と入力します。
以下のようなフォルダが作成されます。

 - puppeteer/
   - docs/
   - exchanges/
   - indicators/
   - logs/
   - modules/
   - puppets/
   - CHANGELOG.md
   - LICENSE.txt
   - MEMO.md
   - puppeteer.py
   - README.md

「docs/」のように 最後が「/」(スラッシュ)終わっているのはフォルダを示しています。 
その他はルートに置かれたファイルになります。

・必要なモジュールのインストール

ccxtというモジュールをPuppeteerは利用します。
多くの仮想通貨自動取引botも採用している有名なモジュールです。
また外部ファイルからロジックを呼び出すときに使用するimportlibも導入します。

次のコマンドでモジュールを導入します。

sudo pip install ccxt
sudo pip install importlib

これでPuppeteerの導入は終わりです。
次に、bitmexのテストサイトであるTestNetでapiKey/secretを入手してPuppeteerの試運転をしてみましょう。TestNetの登録がまだの方は、このサイトなどを参考に登録してみてください。
いきなり本番サイトでの運転は避け、TestNetでPuppeteerが正しくインストールされたことを確認することをお勧めします。

・TestNetでPuppeteerの試運転

Puppeteerには、サンプルのストラテジ(以降、Puppetと称す)が付いています。
サンプルは「puppets」フォルダの下の「sample」フォルダの下にあります。

・sample.py : サンプルロジック本体
・sample.json : サンプルロジック用の定義ファイル

sample.py

# -*- coding: utf-8 -*-
# ==========================================
# サンプル Puppet
# ==========================================
import datetime

from puppeteer import Puppeteer

# ==========================================
# Puppet(傀儡) クラス
#   param:
#       puppeteer: Puppeteerオブジェクト
# ==========================================
class Puppet(Puppeteer):
    _exchange = None    # 取引所オブジェクト(ccxt.bitmex)
    _logger = None      # logger
    _config = None      # 定義ファイル

    # ==========================================================
    # 初期化
    #   param:
    #       puppeteer: Puppeteerオブジェクト
    # ==========================================================
    def __init__(self, Puppeteer):
        self._exchange = Puppeteer._exchange
        self._logger = Puppeteer._logger
        self._config = Puppeteer._config
        
    # ==========================================================
    # 売買実行
    #   param:
    #       ticker: Tick情報
    #       orderbook: 板情報
    #       position: ポジション情報
    #       balance: 資産情報
    #       candle: ローソク足
    # ==========================================================
    def run(self, ticker, orderbook, position, balance, candle):
        """
        self._logger.debug('last={}'.format(ticker['last']))
        self._logger.debug('bid={}, ask={}'.format(orderbook['bids'][0][0], orderbook['asks'][0][0]))
        self._logger.debug('position={}, avgPrice={}'.format(position[0]['currentQty'], position[0]['avgEntryPrice']))
        self._logger.debug('balance[walletBalance]={}'.format(balance['info'][0]['walletBalance'] * 0.00000001))
        """
        # --------------------------
        # ここに処理を記述します
        # --------------------------
        # ------------------------------------------------------
        # orderbookから最新のbid/askを取得する
        # ------------------------------------------------------
        bid = orderbook['bids'][0][0]
        ask = orderbook['asks'][0][0]
        # 値チェック
        if bid == 0 or ask == 0 or bid == None or ask == None :
            self._logger.error('orderbook error: bid={}, ask={}'.format(bid, ask))
            return

        # ------------------------------------------------------
        # ポジションサイズ、参入価格
        # ------------------------------------------------------
        pos_qty = position[0]['currentQty'] if position[0]['currentQty'] is not None else 0
        avg_price = position[0]['avgEntryPrice'] if position[0]['avgEntryPrice'] is not None else 0

        # ------------------------------------------------------
        # LOT取得
        # ------------------------------------------------------
        lot = self._config['LOT_SIZE']
        

sample.json

{
    "//" : "===============================================",
    "//" : " システムで利用",
    "//" : "===============================================",
    "//" : "取引所のapiKey, secretを設定します",
    "APIKEY" : "YOUR_APIKEY",
    "SECRET" : "YOUR_SECRET",

    "//" : "bitmex取引所で対応する通貨ペア等を記述",
    "SYMBOL" : "BTC/USD",
    "INFO_SYMBOL" : "XBTUSD",
    "COIN_BASE" : "BTC",
    "COIN_QUOTE" : "USD",
    "//" : "bitmex取引所の価格の最小幅(0.5ドル)",
    "PRICE_UNIT" : 0.5,

    "//" : "TestNetを使うか?(使う: true, 使わない: false)",
    "USE_TESTNET" : true,

    "//" : "ticker, orderbook, position, balance, candle のどれを利用するかを指定する。Falseを指定した場合はそのデータは取得しない",
    "USE" : {
        "TICKER" : true,
        "ORDERBOOK" : true,
        "POSITION" : true,
        "BALANCE" : true,
        "CANDLE" : true
    },

    "//" : "ローソク足の収集定義。",
    "CANDLE" : {
        "//" : "ローソク足の足幅を設定する。設定値= 1m, 5m, 1h, 1d",
        "TIMEFRAME" : "1m",
        "//" : "データ取得開始時刻(UNIXTIME:1ミリ秒)、使用しない場合 もしくは自動の場合は null(None) を指定",
        "SINCE" : null,
        "//" : "取得件数(未指定:100、MAX:500)",
        "LIMIT" : null,
        "//" : "True(New->Old)、False(Old->New) 未指定時はFlase",
        "REVERSE" : false,
        "//" : "True(最新の未確定足を含む)、False(含まない) 未指定はTrue",
        "PARTIAL" : false
    },

    "//" : "板情報の収集定義。",
    "ORDERBOOK" : {
        "//" : "取得件数(未指定:25、MAX:取引所による?)",
        "LIMIT" : null
    },

    "//" : "インターバル(botの実行周期)を秒で設定",
    "INTERVAL" :30,

    "//" : "discord通知用URL",
    "DISCORD_WEBHOOK_URL" : "",

    "//" : "===============================================",
    "//" : " ユーザで自由に定義",
    "//" : "===============================================",
    "//" : "売買するサイズ",
    "LOT_SIZE" :50
}

このファイルが保管されているsampleフォルダを丸ごとコピーして、そのまま同じ場所にペースとしましょう。
すると「sample.1」というフォルダが作成されるはずです。

その「sample.1」フォルダの中のsample.jsonファイルの以下の部分に、先ほど取得したTestNetのapikey,secretを設定します。

    "//" : "取引所のapiKey, secretを設定します",
    "APIKEY" : "YOUR_APIKEY",
    "SECRET" : "YOUR_SECRET",

変更するのは上記の2つの値です。

さっそく試運転をしてみましょう。
コンソール上で次のコマンドを入力します。

python puppeteer.py puppets/sample.1/sample.py puppets/sample.1/sample.json

実行すると以下のようなログが出ると思います。

2019-04-13 15:28:21, INFO    , [傀儡師] 起動しました。Puppet=puppets/sample.1/sample.py, Config=puppets/sample.1/sample.json, 対象通貨ペア=BTC/USD, RUN周期=30(秒)

起動したことが通知されます。
(上記以外に何も表示されないはずですが、この状態で「例外発生」などのエラー表示がされた場合は何か問題が発生しています。インストールを見直すなどしてください)

次にpuppeteerを終了させてみましょう。
コンソール上で「ctrl+c」を押します。

2019-04-13 15:32:36, INFO , [傀儡師] Ctrl-C検出: 処理を終了します

これでpuppeteerは実行を停止します。

注意点としては、実行を停止してもポジションをクローズしたり、既に発注済みの注文のキャンセルは行いません。
手動で処理する必要があります。

・HAL900のソースコード

お待たせしました。
ここからは bot「HAL900」のソースコードです。
ソースはpythonファイルとJSONファイルの2つから構成されます。

この続きをみるには

この続き:31,138文字/画像1枚
記事を購入する

数量限定で販売中

残り5/ 5

[Puppeteer] ゆっくりMM(Market Maker)bot 「HAL900」

おさむくん

10,000円

この記事が気に入ったら、サポートをしてみませんか?気軽にクリエイターを支援できます。

note.user.nickname || note.user.urlname

ソフトウェア・エンジニアを40年以上やってます。 「Botを作りたいけど敷居が高い」と思われている方にも「わかる」「できる」を感じてもらえるように頑張ります。 よろしくお願い致します。

頑張ります!ありがとうございます。
13
ソフトウェア開発40年以上の実績を活かして、初心者・中級者の方にトレードBotの作り方を丁寧に伝授します。 プログラミング言語はサーバサイドJavaScript (Node.js) 、Pythonを基本に、最新かつ有効な手法を取り入れます。 twitter: @o_matsuo
コメントを投稿するには、 ログイン または 会員登録 をする必要があります。