noteのタイトル画像

[Puppeteer] <初めてのPuppeteer> ドテン君を作ろう♪(3回目)

前回までのおさらい

前回までで、1時間のローソク足データを取得することができました。

1回目で言及したドテン君のロジックをちょっと思い出してください。

ドテン君の基本は「チャネルブレイクアウト」なので、まずはチャネルを計算しなくてはなりません。

チャネルは

直近の足を除く、指定個数の”高値ー安値” の平均

とありますので、その指定個数を「RangeMean計算数(単位:整数)」として変数化しておきます。
JSONファイルに次のような変数を用意します。

   "//" : "RangeMean計算数(単位:整数)",
   "RANGE_MEAN_NUM" : 18,

値はとりあえず18で良いですね。
また、RangeMeanに乗算するK値も変数化します。

   "//" : "ドテンK値(単位:実数)",
   "DOTEN_K" : 1.6

ここは実数で1.6としておきましょう。

ということで、doten.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" : "1h",
       "//" : "データ取得開始時刻(UNIXTIME:1ミリ秒)、使用しない場合 もしくは自動の場合は null(None) を指定",
       "SINCE" : null,
       "//" : "取得件数(未指定:100、MAX:500)",
       "LIMIT" : null,
       "//" : "True(New->Old)、False(Old->New) 未指定時はFlase",
       "REVERSE" : false,
       "//" : "True(最新の未確定足を含む)、False(含まない) 未指定はTrue",
       "PARTIAL" : true
   },

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

   "//" : "websocketを使用するかどうかを指定",
   "USE_WEBSOCKET" : false,

   "//" : "ログレベルを指定。('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG')",
   "LOG_LEVEL" : "INFO",

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

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

   "//" : "資産状況通知をするか",
   "USE_SEND_BALANCE" : false,

   "//" : "===============================================",
   "//" : " ユーザで自由に定義",
   "//" : "===============================================",

   "//" : "===============================================",
   "//" : " ドテン設定",
   "//" : "===============================================",
   "//" : "RangeMean計算数(単位:整数)",
   "RANGE_MEAN_NUM" : 18,
   "//" : "ドテンK値(単位:実数)",
   "DOTEN_K" : 1.6
}

RangeMean計算

変数を用意してので、RangeMeanを計算する関数とドテン判定する関数を用意しましょう。

RangeMean計算は

   # ==========================================================
   # RANGE計算
   # ==========================================================
   def __calc_range_mean(self, df, range_mean_num):
       diff = 0
       for index, row in df[-range_mean_num:].iterrows():
           diff += row["high"] - row["low"]
       return diff / range_mean_num

で計算できます。

__calc_range_mean関数は指定個数(range_mean_num)だけのローソク足(直近の足以外)の「高値ー安値」を合計して、さらにrange_mean_numで割って平均を出しています。

チャネルレンジを計算するんだったら、range_mean_num期間の最高値と最安値を出せば良いという考えもできますが、今回はこの計算でやってみましょう。

ドテン判定

ドテン判定は

   # ==========================================================
   # ドテン計算
   # ==========================================================
   def __calc_doten(self, last, range_mean, k):
       ret = 'none'
       if last['high'] > (last['open'] + range_mean * k):
           ret = 'buy'
       elif last['low'] < (last['open'] - range_mean * k):
           ret = 'sell'
       return ret

としました。

__calc_doten関数は先程の__calc_range_mean関数で計算したRangeMean値を使って、直近の足(未確定足)の始値、高値、安値を用いてブレイクしたかどうかを判定しています。

判定条件は

直近高値 > (直近始値 + RangeMean値 * K値) の場合は「買い」
直近安値 < (直近始値 ー RangeMean値 * K値) の場合は「売り」

という判定です。

doten.pyプログラム全体

doten.pyは次のようになりました。

# -*- coding: utf-8 -*-
# ==========================================
# ドテン君 サンプル
# ==========================================
import time
from datetime import datetime as dt, timezone as tz, timedelta as delta

import pandas as pd

from puppeteer import Puppeteer

# ==========================================
# Puppet(傀儡) クラス
#   param:
#       puppeteer: Puppeteerオブジェクト
# ==========================================
class Puppet(Puppeteer):

   # ==========================================================
   # 初期化
   #   param:
   #       puppeteer: Puppeteerオブジェクト
   # ==========================================================
   def __init__(self, Puppeteer):
       self._exchange = Puppeteer._exchange    # 取引所オブジェクト(ccxt.bitmex)
       self._logger = Puppeteer._logger        # logger
       self._config = Puppeteer._config        # 定義ファイル
       
   # ==========================================================
   # 売買実行
   #   param:
   #       ticker: Tick情報
   #       orderbook: 板情報
   #       position: ポジション情報
   #       balance: 資産情報
   #       candle: ローソク足
   # ==========================================================
   def run(self, ticker, orderbook, position, balance, candle):
       # --------------------------
       # ここに処理を記述します
       # --------------------------

       # ------------------------------------------------------
       # ローソク足
       # ------------------------------------------------------
       df = self.__get_candleDF(candle)

       range_mean = self.__calc_range_mean(df[:-1], self._config['RANGE_MEAN_NUM'])    # 直近の足は未確定足だから計算に渡さない

       doten = self.__calc_doten(df.iloc[-1], range_mean, self._config['DOTEN_K'])     # 直近の足からopen, high, lowを取得する

       self._logger.info('doten: {}'.format(doten))

   # ==========================================================
   # ローソク足 DataFrame 取得
   # ==========================================================
   def __get_candleDF(self, candle):
       # -----------------------------------------------
       # Pandasのデータフレームに
       # -----------------------------------------------
       df = pd.DataFrame(candle,
               columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
       # -----------------------------------------------
       # 日時データをDataFrameのインデックスにする
       # -----------------------------------------------
       df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms', utc=True, infer_datetime_format=True) # UNIX時間(ミリ秒)を変換, UTC=TrueでタイムゾーンがUTCに設定される, infer_datetime_format=Trueは高速化に寄与するとのこと。
       df = df.set_index('timestamp')

       return df

   # ==========================================================
   # RANGE計算
   # ==========================================================
   def __calc_range_mean(self, df, range_mean_num):
       diff = 0
       for index, row in df[-range_mean_num:].iterrows():
           diff += row["high"] - row["low"]
       return diff / range_mean_num

   # ==========================================================
   # ドテン計算
   # ==========================================================
   def __calc_doten(self, last, range_mean, k):
       ret = 'none'
       if last['high'] > (last['open'] + range_mean * k):
           ret = 'buy'
       elif last['low'] < (last['open'] - range_mean * k):
           ret = 'sell'
       return ret

dfにはローソク足のデータフレームが入っているので、直近の足を除外して__calc_range_mean関数に渡すために、引数に渡すdfは

df[:-1]

としています。これで直近の足(未確定足)を除外したローソク足のデータフレームが__calc_range_mean関数に渡されます。

同じように、__calc_doten関数には直近の足(未確定足)を渡してあげるので、

df.iloc[-1]

を引数に設定してあげています。

このあたりのデータフレームの使い方はpythonのpandasの使い方をググってみてください。
(良い時代になりました。。)

実行

次のコマンドで実行してみましょう。

python3 puppeteer.py puppets/doten/doten.py puppets/doten/doten.json

すると、

2019-07-14 02:54:48, INFO    , class BitMEX initialized
2019-07-14 02:54:48, INFO    , [傀儡師] 起動しました。Puppet=puppets/doten/doten.py, Config=puppets/doten/doten.json, 対象通貨ペア=BTC/USD, RUN周期=60(秒)
2019-07-14 02:54:52, INFO    , doten: none

・・・(以下略)・・・

のように判定結果をログに出力してくれます。

今日はここまでです。

楽しいbotライフを!



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