はじめに
ローソク足などで使われる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()
時間足に変換
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)
-
pd.DataFrame.resample.asfreq().columns
(関数内ではself.asfreq().columns
)でカラム名を取得します。 - dict関数と内包表記でcdictに格納します。
- agdictのキーは引数のカラム名となります
- open, high, low, columnsの
- 全小文字(openなど)
- 全大文字(HIGHなど)
- 一部大文字(Low, cLoSeなど)
が4列ともなければエラーを吐きます。
- volume(大文字小文字関係なし)列があれば、agdictに追加します。
- pd.DataFrame.resampleをagdictに入れたディクショナリでまとめます。
ipython上でいつでも使えるようにする
ipythonのstartupに登録します。
$ ipython profile create
で作成されたディレクトリに以下のファイルを保存します。
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
ipython上でtabを押せば候補としてohlc2()
が出てくるようになります。
参考
- OHLCをOHLCに変換
stackoverflow - Converting OHLC stock data into a different timeframe with python and pandas
- 既存クラスに自作メソッドを追加