noteのタイトル画像

[Puppeteer] <初めてのPuppeteer> pandasを使ってローソク足を加工してみる

<初めてのPuppeteer>シリーズといいつつ何から書いていいやら迷いますね。
おそらくは誰でも使うであろう「ローソク足」の加工に幅広く使えるpandasの使い方でも書いてみましょう。

python使いの皆さまならおなじみの「pandas」です。
pandasの基本的な使い方は既に色々な人が執筆されているので、私がこと更に書くこともないので基本的な部分はQiita等をご覧ください。

puppeteerで提供しているローソク足を取得する機能は大きく2種類です。

・ccxtで提供しているREST APIで取得する。
 タイムフレームは 1分、5分、1時間、1日 の4種類
・puppeteerに搭載したwebsocketでbitmexからリアルタイム通知を受け取り内部的にローソク足を生成する。
 タイムフレームは5秒の1種類

データの形は以下のようになっています。

ccxt版(タイムフレームは1分)は

[1560007560000, 7956.5, 7956.5, 7956, 7956, 5896]
[1560007620000, 7956, 7957.5, 7956, 7957.5, 15967]
[1560007680000, 7957.5, 7957.5, 7957, 7957, 2256]
[1560007740000, 7957, 7957.5, 7954, 7954, 5875]
[1560007800000, 7954, 7954.5, 7940, 7940, 53541]
[1560007860000, 7940, 7950.5, 7940, 7950.5, 17462]
[1560007920000, 7950.5, 7952, 7934.5, 7934.5, 97526]
[1560007980000, 7934.5, 7940.5, 7900, 7917, 153158]
・・・(以下、略)・・・

のように
 timestamp, open, high, low, close, volume
の値が配列となって取得されます。(timestampはUNIXミリ秒)

websocket版は

{'timestamp': 1560013800, 'open': 7872.5, 'high': 7872.5, 'low': 7872.5, 'close': 7872.5, 'volume': 75, 'buy': 0, 'sell': 75}
{'timestamp': 1560013805, 'open': 7872.5, 'high': 7872.5, 'low': 7872.5, 'close': 7872.5, 'volume': 0, 'buy': 0, 'sell': 0}
{'timestamp': 1560013810, 'open': 7872.5, 'high': 7872.5, 'low': 7872.5, 'close': 7872.5, 'volume': 0, 'buy': 0, 'sell': 0}
{'timestamp': 1560013815, 'open': 7872.5, 'high': 7872.5, 'low': 7872.5, 'close': 7872.5, 'volume': 1, 'buy': 0, 'sell': 1}
{'timestamp': 1560013820, 'open': 7872.5, 'high': 7872.5, 'low': 7872.5, 'close': 7872.5, 'volume': 22, 'buy': 0, 'sell': 22}
{'timestamp': 1560013825, 'open': 7872.5, 'high': 7872.5, 'low': 7872.5, 'close': 7872.5, 'volume': 0, 'buy': 0, 'sell': 0}
{'timestamp': 1560013830, 'open': 7872.5, 'high': 7872.5, 'low': 7872.5, 'close': 7872.5, 'volume': 0, 'buy': 0, 'sell': 0}
{'timestamp': 1560013835, 'open': 7872.5, 'high': 7872.5, 'low': 7872.5, 'close': 7872.5, 'volume': 0, 'buy': 0, 'sell': 0}
{'timestamp': 1560013840, 'open': 7872.5, 'high': 7872.5, 'low': 7872.5, 'close': 7872.5, 'volume': 0, 'buy': 0, 'sell': 0}
{'timestamp': 1560013845, 'open': 7872.5, 'high': 7873, 'low': 7872.5, 'close': 7873, 'volume': 25, 'buy': 25, 'sell': 0}
{'timestamp': 1560013850, 'open': 7873, 'high': 7873, 'low': 7873, 'close': 7873, 'volume': 0, 'buy': 0, 'sell': 0}
・・・(以下、略)・・・

のように
 timestamp, open, high, low, close, volume, buy, sell
値がkey-value型となって取得されます。(timestampはUNIX秒)

このままでもデータを扱う上で問題はありませんが、NaN値が含まれていた場合にNaN値を除去したり、volumeの合計を取得したりする場合にpandasを使った方が便利なことが多いです。

そこで、生のローソク足データをpandasに格納することにします。

pandasのDataFrameにローソク足を取り込んだものをprintで出力すると

ccxt版は

                       open    high     low   close  volume
timestamp                                                  
2019-06-08 15:38:00  7913.0  7914.0  7913.0  7914.0    5832
2019-06-08 15:39:00  7914.0  7914.0  7911.0  7911.0   16181
2019-06-08 15:40:00  7911.0  7913.0  7913.0  7913.0     285
2019-06-08 15:41:00  7913.0  7913.5  7898.0  7898.0    9006
2019-06-08 15:42:00  7898.0  7913.5  7898.5  7912.0   26836
2019-06-08 15:43:00  7912.0  7912.0  7911.5  7911.5    5005
2019-06-08 15:44:00  7911.5  7912.0  7910.5  7911.5   24145
2019-06-08 15:45:00  7911.5  7911.5  7911.0  7911.0     755
2019-06-08 15:46:00  7911.0  7911.0  7911.0  7911.0    3677
2019-06-08 15:47:00  7911.0  7911.0  7910.5  7911.0    4426
2019-06-08 15:48:00  7911.0  7910.5  7886.5  7910.5   12694
2019-06-08 15:49:00  7910.5  7911.0  7910.5  7910.5    3007
2019-06-08 15:50:00  7910.5  7911.0  7910.5  7910.5    1446
2019-06-08 15:51:00  7910.5  7911.0  7910.5  7910.5    1132
・・・(以下略)・・・

のように。

websocket版は

                       open    high     low   close  volume  buy  sell
timestamp                                                             
2019-06-08 17:23:00  7873.5  7873.5  7873.5  7873.5       9    9     0
2019-06-08 17:23:05  7873.5  7873.5  7873.5  7873.5       0    0     0
2019-06-08 17:23:10  7873.5  7873.5  7873.5  7873.5       0    0     0
2019-06-08 17:23:15  7873.5  7873.5  7873.5  7873.5       0    0     0
2019-06-08 17:23:20  7873.5  7873.5  7873.5  7873.5       0    0     0
2019-06-08 17:23:25  7873.5  7873.5  7873.0  7873.0       1    0     1
2019-06-08 17:23:30  7873.0  7873.0  7873.0  7873.0       5    0     5
2019-06-08 17:23:35  7873.0  7873.0  7873.0  7873.0       0    0     0
2019-06-08 17:23:40  7873.0  7873.0  7873.0  7873.0       0    0     0
2019-06-08 17:23:45  7873.0  7873.5  7873.0  7873.5       9    9     0
2019-06-08 17:23:50  7873.5  7873.5  7873.5  7873.5       0    0     0
2019-06-08 17:23:55  7873.5  7873.5  7873.5  7873.5       0    0     0
・・・(以下略)・・・​

のように、綺麗にわかりやすく格納されています。

pandasにデータを取り込んだことで、例えば、
 最後から3個分のデータのvolumeを合計したい
と思った時には次のようにプログラムすれば簡単に取得できます。
(df変数のDataFrameが格納されているとします)

df.tail(3)['volume'].sum()

簡単ですね。

それでは最後にccxt版とwebsocket版のpuppetsのソースコードを載せておきます。
みなさんもぜひpuppeteerで試してみてください。

ccxt版
- pupptes/
  - sample/
    - sample.py
    - sample.json

sample.py

# -*- coding: utf-8 -*-
# ==========================================
# サンプル Puppet
# ==========================================
import datetime
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):

       # -----------------------------------------------
       # Pandasのデータフレームに
       # -----------------------------------------------
       df = pd.DataFrame(candle,
               columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
       # -----------------------------------------------
       # 日時データをDataFrameのインデックスにする
       # -----------------------------------------------
       df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
       df = df.set_index('timestamp')

       print(df)

       print(df.tail(3)['volume'].sum())

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
   },

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

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

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

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

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

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

websocket版
- pupptes/
  - sample1/
    - sample1.py
    - sample1.json

sample1.py

# -*- coding: utf-8 -*-
# ==========================================
# サンプル Puppet (websocket)
# ==========================================
import datetime
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        # 定義ファイル
       self._ws = Puppeteer._ws                # websocket
       self._bitmex = Puppeteer._bitmex        # ccxt.bimexラッパーオブジェクト
       
   # ==========================================================
   # 売買実行
   #   param:
   #       ticker: Tick情報
   #       orderbook: 板情報
   #       position: ポジション情報
   #       balance: 資産情報
   #       candle: ローソク足
   # ==========================================================
   def run(self, ticker, orderbook, position, balance, candle):
       
       # Socketの接続が活きている and 強制終了フラグがOFF の限り処理を続ける
       if not self._ws.ws.sock and not self._ws.ws.sock.connected and self._ws.is_force_exit():
           self._logger.warning('websocket not running / force exit')
           return

       ws_candle = self._ws.candle()

       # ------------------------------------------------------
       # DataFrame作成
       # ------------------------------------------------------
       df = pd.DataFrame(ws_candle,
               columns=['timestamp', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell']
           )
       # ------------------------------------------------------
       # 日時データをDataFrameのインデックスにする
       #   candleのtimestampデータがUNIXTIME(秒)なので、unit='s'を指定する。(ミリ秒なら 'ms'を指定する)
       # ------------------------------------------------------
       df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s')
       df = df.set_index('timestamp')

       print(df)

       print(df.tail(3)['volume'].sum())

sample1.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" : false,
       "ORDERBOOK" : false,
       "POSITION" : false,
       "BALANCE" : false,
       "CANDLE" : false
   },

   "//" : "ローソク足の収集定義。",
   "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
   },

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

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

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

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

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

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

楽しいbotライフを!


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