ローソク足OHLCの時間足を変える

More than 1 year has passed since last update.


はじめに

ローソク足などで使われるOHLC形式(Open High Low Closeの頭文字)で集計されたデータフレームの時間足を変えて(=ダウンサンプリング)、再度OHLC形式のデータフレーム(以下、ohlcデータ)を出力します。

以下の例では時間足を日足に変換しています。

df

open high low close
2017-11-25 00:00:00 1 2 1 2
2017-11-25 01:00:00 1 3 1 3
2017-11-25 02:00:00 4 5 4 5
2017-11-25 03:00:00 6 8 6 7
2017-11-25 04:00:00 7 8 7 7
...
# ↑これが

df.resample('D').ohlc2()

# ↓こう
open high low close
2017-11-25 1 13 1 12
2017-11-26 12 16 9 13
2017-11-27 12 16 7 14
2017-11-28 13 14 3 10
2017-11-29 11 21 11 12
...

ohlc2()メソッドを作成し、入力をシンプルにしたいと思います。


準備


ランダムウォークデータを用意

ランダムに数値が上下するデータ(ランダムウォーク)を作ります。

import numpy as np

import pandas as pd

def randomwalk(periods, start=pd.datetime.today().date(), index=None, name=None, tick=1, freq='B'):
"""periods日分だけランダムウォークを返す"""
if not index:
index = pd.date_range(start=start, periods=periods, freq=freq) # 今日の日付からperiod日分の平日
bullbear = pd.Series(tick * np.random.randint(-1, 2, periods),
index=index, name=name) # tick * (-1,0,1のどれか)を吐き出すSeries
price = bullbear.cumsum() # 累積和
return price

df = randomwalk(periods=10000, freq='15T') # 15分刻みのランダムウォーク

2017-11-25 00:00:00 1
2017-11-25 00:15:00 0
2017-11-25 00:30:00 1
2017-11-25 00:45:00 2
2017-11-25 01:00:00 1
...

グラフ化するとこんな感じです。

df.plot()

image.png


時間足に変換

pandas.DataFrameでohlcデータを用意します。

df = df.resample('H').ohlc()  # 1時間足としてダウンサンプリング

open high low close
2017-11-25 00:00:00 1 2 1 2
2017-11-25 01:00:00 1 3 1 3
2017-11-25 02:00:00 4 5 4 5
2017-11-25 03:00:00 6 8 6 7
2017-11-25 04:00:00 7 8 7 7
...


ohlcデータの時間足変換


日足に変換(間違った方法)

このohlcデータdfは時間足です。これを日足に変換したいとき、df.resample('D').ohlc()とやりがちですが、open high low closeそれぞれに対してopen high low closeを分けようとするため思ったように変換してくれません。

df.resample('D').ohlc()  # やりがちなohlcデータを再度resampleしてohlcで集計

open high low close \
open high low close open high low close open high low close open
2017-11-25 1 13 1 13 2 13 2 13 1 12 1 12 2
2017-11-26 12 15 10 13 14 16 10 13 12 14 9 13 14
2017-11-27 12 15 7 15 12 16 9 15 10 15 7 14 10

high low close
2017-11-25 10 0 8
2017-11-26 15 6 9
2017-11-27 14 6 8
2017-11-28 6 -10 -9
2017-11-29 -6 -17 -9

...


日足に変換(正しい方法)

これを正しく日足に変換するにはresampleメソッドで日足にしたあとに、agg(=aggregate, 集計)メソッドを使わなければなりません。

df.resample('D').agg({'open': 'first',

'high': 'max',
'low': 'min',
'close': 'last'}) # ohlcを再度ohlcに集計するにはaggメソッド

open high low close
2017-11-25 1 11 -1 8
2017-11-26 8 15 5 9
2017-11-27 10 15 5 8
2017-11-28 8 8 -10 -9
2017-11-29 -10 -5 -17 -9
...


日足に変換(メソッドに追加)

毎回agg({'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last'})を打つのも大変なので、簡単に打てるようにメソッドとして使えるようにしたいと思います。

from pandas.core import resample

def ohlc2(self): # 関数でaggを定義
return self.agg({'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last'})

resample.DatetimeIndexResampler.ohlc2 = ohlc2 # resampleすると生成されるクラスにohlc2メソッドを追加

このようにすればdf.resample('D')のクラスpd.core.resample.DatetimeIndexResamplerにohlc2メソッドが追加されます。

df.resample('4H').ohlc2()  # 先ほどのdfをやっぱり4時間足に変えたいとき

open high low close
2017-11-25 00:00:00 -1 1 -4 1
2017-11-25 04:00:00 0 0 -6 -5
2017-11-25 08:00:00 -4 -3 -8 -5
2017-11-25 12:00:00 -6 -1 -6 -2
2017-11-25 16:00:00 -1 -1 -7 -7
...


volume(出来高)列の扱い

列目にvolume(出来高)が入っている場合、上記の関数では自動的にvolumeが省かれておりましたので、volumeを含む場合、含まない場合に場合分けして動作するように改造しました。

all関数に内包表記を組み込んで、列名にopen high low close volumeのすべてを含んでいない場合はエラーを送出するようにしました。

volumeだけ入っていない場合は改造前と同様に4本値で計算します。

def ohlc2(self):

if all(i in ['open', 'high', 'low', 'close'] for i in self.asfreq().columns): # データに出来高が含まれないとき
return self.agg({'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last'})
elif all(i in ['open', 'high', 'low', 'close', 'volume'] for i in self.asfreq().columns): # データに出来高が含まれるとき
return self.agg({'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'})
else: # `open high low close (volume)`の列名で構成されていない場合はエラー
raise KeyError("columns must have ['open', 'high', 'low', 'close'(, 'volume')]")

より短く書くなら、columnsをまとめて、aggに渡す辞書にvolumeを追加する形にして以下のように書きます。

# ↑と同様

def ohlc2(self):
agdict = {'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last'}
columns = list(agdict.keys()) # agdictのキー['open', 'high', 'low', 'close']
if all(i in columns for i in self.asfreq().columns):
pass # ['open', 'high', 'low', 'close']のときは何もせずreturnへ
elif all(i in columns + ['volume'] for i in self.asfreq().columns):
agdict['volume'] = 'sum' # columnsに加えてvolumeが入る場合はagdict追加
else:
raise KeyError("columns must have ['open', 'high', 'low', 'close'(, 'volume')]")
return self.agg(agdict)


columns名が大文字である場合に対応する

pandas_readerなどで取得したデータのカラム名は頭文字が大文字('Open', 'High', 'Low', 'Close', 'Volume')なので、前のバージョンではエラーを吐いていました。

KeyError("columns must have ['open', 'high', 'low', 'close'(, 'volume')]")

openとOpenを区別してしますためですね。

そこで、次のようにして引数となるカラム名を使ってaggに渡す辞書を作ります。

def ohlc2(self, *args, **kwargs):

"""`pd.DataFrame.resample(<TimeFrame>).ohlc2()`
Resample method converting OHLC to OHLC
"""

cdict = dict([(v.lower(), v) for v in self.asfreq().columns]) # (1),(2)
try:
agdict = {cdict['open']: 'first',
cdict['high']: 'max',
cdict['low']: 'min',
cdict['close']: 'last'} # (3)
except KeyError as e:
raise KeyError('Columns not enough {}'.format(*e.args)) # (4)
if 'volume' in map(lambda x: x.lower(), self.asfreq().columns):
agdict[cdict['volume']] = 'sum' # (5)
return self.agg(agdict) # (6)



  1. pd.DataFrame.resample.asfreq().columns(関数内ではself.asfreq().columns)でカラム名を取得します。

  2. dict関数と内包表記でcdictに格納します。

  3. agdictのキーは引数のカラム名となります

  4. open, high, low, columnsの


    • 全小文字(openなど)

    • 全大文字(HIGHなど)

    • 一部大文字(Low, cLoSeなど)
      が4列ともなければエラーを吐きます。



  5. volume(大文字小文字関係なし)列があれば、agdictに追加します。

  6. pd.DataFrame.resampleをagdictに入れたディクショナリでまとめます。


ipython上でいつでも使えるようにする

ipythonのstartupに登録します。

$ ipython profile create

で作成されたディレクトリに以下のファイルを保存します。


~/.ipython/profile_default/startup/ohlc2.ipy

from pandas.core import resample

def ohlc2(self, *args, **kwargs):
"""`pd.DataFrame.resample(<TimeFrame>).ohlc2()`
Resample method converting OHLC to OHLC
"""

cdict = dict([(v.lower(), v) for v in self.asfreq().columns])
try:
agdict = {cdict['open']: 'first',
cdict['high']: 'max',
cdict['low']: 'min',
cdict['close']: 'last'}
except KeyError as e:
raise KeyError('Columns not enough {}'.format(*e.args))
if 'volume' in map(lambda x: x.lower(), self.asfreq().columns):
agdict[cdict['volume']] = 'sum'
return self.agg(agdict)

# Add instance as `pd.DataFrame.resample('<TimeFrame>').ohlc2()`
resample.DatetimeIndexResampler.ohlc2 = ohlc2


image.png

ipython上でtabを押せば候補としてohlc2()が出てくるようになります。


参考


  • OHLCをOHLCに変換


stackoverflow - Converting OHLC stock data into a different timeframe with python and pandas



  • 既存クラスに自作メソッドを追加


Python Tips:既存のクラスにインスタンスメソッドを追加したい