pandasで時系列データのOHLC(四本値)を算出・ダウンサンプリング

Modified: | Tags: Python, pandas, 時系列データ

pandasで株価や為替などの時系列データから特定の期間のOHLC(四本値: 始値、高値、安値、終値)やOHLCV(OHLC + 出来高)を算出したりダウンサンプリングしたりする方法について、以下の内容を説明する。

  • OHLCとOHLCV
  • 終値・始値や歩み値(ティック)からOHLC, OHLCVを算出
  • OHLC, OHLCVデータをダウンサンプリング

OHLCデータからローソク足チャートを作成する方法は以下の記事を参照。

OHLCとOHLCV

OHLCはある期間(日、週、月など)における以下の4つの値の総称。

  • Open: 始値
  • High: 高値
  • Low: 安値
  • Close: 終値

四本値とも呼ばれる。

OHLCに出来高(Volume)を加えたものをOHLCVと呼ぶ。

終値・始値や歩み値(ティック)からOHLC, OHLCVを算出

株価などの終値・始値や歩み値(ティック)データからOHLC, OHLCVを算出するにはresample()およびohlc(), sum()を使う。

ohlc(), sum()pandas.DataFrameからではなく、resample()の返り値から更に呼び出す。

以下の簡単な日次データを例とする。

import pandas as pd

dates = pd.date_range('2018-08-01', '2018-08-31', freq='B')

df = pd.DataFrame({'price': dates.day, 'volume': dates.day * 10}, index=dates)

print(df)
#             price  volume
# 2018-08-01      1      10
# 2018-08-02      2      20
# 2018-08-03      3      30
# 2018-08-06      6      60
# 2018-08-07      7      70
# 2018-08-08      8      80
# 2018-08-09      9      90
# 2018-08-10     10     100
# 2018-08-13     13     130
# 2018-08-14     14     140
# 2018-08-15     15     150
# 2018-08-16     16     160
# 2018-08-17     17     170
# 2018-08-20     20     200
# 2018-08-21     21     210
# 2018-08-22     22     220
# 2018-08-23     23     230
# 2018-08-24     24     240
# 2018-08-27     27     270
# 2018-08-28     28     280
# 2018-08-29     29     290
# 2018-08-30     30     300
# 2018-08-31     31     310

print(df.index)
# DatetimeIndex(['2018-08-01', '2018-08-02', '2018-08-03', '2018-08-06',
#                '2018-08-07', '2018-08-08', '2018-08-09', '2018-08-10',
#                '2018-08-13', '2018-08-14', '2018-08-15', '2018-08-16',
#                '2018-08-17', '2018-08-20', '2018-08-21', '2018-08-22',
#                '2018-08-23', '2018-08-24', '2018-08-27', '2018-08-28',
#                '2018-08-29', '2018-08-30', '2018-08-31'],
#               dtype='datetime64[ns]', freq='B')

インデックス列がDatetimeIndexの時系列データとなっている必要がある。例では新規にpandas.DataFrameを作成しているが、既存のpandas.DataFrameを時系列データとして扱う方法については以下の記事を参照。

freq='B'は営業日(月 - 金)を表している。

この日次データから週次のOHLC, OHLCVデータを算出する。

時系列データをresample()でリサンプリングしohlc()を適用する。resample()の引数freqに渡す頻度コードによってOHLCを算出する期間を指定できる。

ここでは価格を表す列に対して処理を行う。

print(df['price'].resample('W').ohlc())
#             open  high  low  close
# 2018-08-05     1     3    1      3
# 2018-08-12     6    10    6     10
# 2018-08-19    13    17   13     17
# 2018-08-26    20    24   20     24
# 2018-09-02    27    31   27     31

週次の期間コードWは結果のインデックス列(ラベル)がデフォルトで期間の最終日になる。引数label, closed'left'とすると期間の開始日に変更できる。月曜始まりにする例は以下の通り。

print(df['price'].resample('W-MON', label='left', closed='left').ohlc())
#             open  high  low  close
# 2018-07-30     1     3    1      3
# 2018-08-06     6    10    6     10
# 2018-08-13    13    17   13     17
# 2018-08-20    20    24   20     24
# 2018-08-27    27    31   27     31

OHLCVのV(出来高)は出来高を表す列の期間ごとの合計をsum()で算出する。

print(df['volume'].resample('W').sum())
# 2018-08-05      60
# 2018-08-12     400
# 2018-08-19     750
# 2018-08-26    1100
# 2018-09-02    1450
# Freq: W-SUN, Name: volume, dtype: int64

print(df['volume'].resample('W-MON', label='left', closed='left').sum())
# 2018-07-30      60
# 2018-08-06     400
# 2018-08-13     750
# 2018-08-20    1100
# 2018-08-27    1450
# Freq: W-MON, Name: volume, dtype: int64

OHLCVをまとめて取得するにはpd.concat()での連結やassign()での新規列追加を行う。

print(pd.concat([df['price'].resample('W-MON', label='left', closed='left').ohlc(),
                 df['volume'].resample('W-MON', label='left', closed='left').sum()], axis=1))
#             open  high  low  close  volume
# 2018-07-30     1     3    1      3      60
# 2018-08-06     6    10    6     10     400
# 2018-08-13    13    17   13     17     750
# 2018-08-20    20    24   20     24    1100
# 2018-08-27    27    31   27     31    1450

print(df['price'].resample('W-MON', label='left', closed='left').ohlc()
      .assign(volume=df['volume'].resample('W-MON', label='left', closed='left').sum()))
#             open  high  low  close  volume
# 2018-07-30     1     3    1      3      60
# 2018-08-06     6    10    6     10     400
# 2018-08-13    13    17   13     17     750
# 2018-08-20    20    24   20     24    1100
# 2018-08-27    27    31   27     31    1450

例ではprint()の括弧の中なので.assign()の前で改行しているが、括弧の外で実行する場合は.assign()の前で改行するとエラーになるので注意(前の行の末尾にバックスラッシュ\を入れれば改行可能)。

pd.concat()で連結する場合はインデックスが揃っていないとダメだが、assign()で新規の列を追加する場合は要素数が合っていればリストやNumPy配列ndarrayでもOK。インデックスが異なっていてもvalues属性でnumpy.ndarrayを取得して追加できる。

print(pd.concat([df['price'].resample('W-MON', label='left', closed='left').ohlc(),
                 df['volume'].resample('W').sum()], axis=1))
#             open  high   low  close  volume
# 2018-07-30   1.0   3.0   1.0    3.0     NaN
# 2018-08-05   NaN   NaN   NaN    NaN    60.0
# 2018-08-06   6.0  10.0   6.0   10.0     NaN
# 2018-08-12   NaN   NaN   NaN    NaN   400.0
# 2018-08-13  13.0  17.0  13.0   17.0     NaN
# 2018-08-19   NaN   NaN   NaN    NaN   750.0
# 2018-08-20  20.0  24.0  20.0   24.0     NaN
# 2018-08-26   NaN   NaN   NaN    NaN  1100.0
# 2018-08-27  27.0  31.0  27.0   31.0     NaN
# 2018-09-02   NaN   NaN   NaN    NaN  1450.0

print(df['price'].resample('W-MON', label='left', closed='left').ohlc()
      .assign(volume=df['volume'].resample('W').sum().values))
#             open  high  low  close  volume
# 2018-07-30     1     3    1      3      60
# 2018-08-06     6    10    6     10     400
# 2018-08-13    13    17   13     17     750
# 2018-08-20    20    24   20     24    1100
# 2018-08-27    27    31   27     31    1450

歩み値(ティック)データの場合

上の例のような日次データではなく、取引時刻と取引数量からなる歩み値(ティック)データの場合もやり方は同じ。

時刻のデータをpd.to_datetime()などで変換しDatetimeIndexとしてインデックスに指定し、resample()の頻度コードをH(時間)、T(分)、S(秒)などで指定する。例えば5分足のOHLCを算出したい場合は'5T'とすればよい。

OHLC, OHLCVデータをダウンサンプリング

既存のOHLC, OHLCVデータをより長い期間にダウンサンプリングするには注意が必要。簡易的には終値や始値に対してOHLC, OHLCVを算出してもよいが、その場合はザラ場高値やザラ場安値の情報が失われてしまう。

ここではpandas-datareaderで取得して保存したApple(AAPL)の株価データを例とする。

pd.read_csv()parse_dates=Trueとして時系列データとして読み込み、さらに['2017']で2017年のデータを抽出する。

import pandas as pd

df = pd.read_csv('data/src/aapl_2015_2019.csv', index_col=0, parse_dates=True)['2017']
print(df)
#               open      high      low   close    volume
# 2017-01-03  115.80  116.3300  114.760  116.15  28781865
# 2017-01-04  115.85  116.5100  115.750  116.02  21118116
# 2017-01-05  115.92  116.8642  115.810  116.61  22193587
# 2017-01-06  116.78  118.1600  116.470  117.91  31751900
# 2017-01-09  117.95  119.4300  117.940  118.99  33561948
# ...            ...       ...      ...     ...       ...
# 2017-12-22  174.68  175.4240  174.500  175.01  16052615
# 2017-12-26  170.80  171.4700  169.679  170.57  32968167
# 2017-12-27  170.10  170.7800  169.710  170.60  21672062
# 2017-12-28  171.00  171.8500  170.480  171.08  15997739
# 2017-12-29  170.52  170.5900  169.220  169.23  25643711
# 
# [251 rows x 5 columns]

resample()のあとohlc()ではなくagg()メソッドに{列名: 関数}の辞書を指定することで各列にそれぞれ異なる処理を適用できる。

既存のOHLCをダウンサンプリングするにはそれぞれ以下のような関数を適用すればよい。

  • open: first(期間中の最初の値を返す)
  • high: max(期間中の最大値を返す)
  • low: min(期間中の最小値を返す)
  • close: last(期間中の最後の値を返す)

組み込み関数やNumPyの関数などを呼び出し可能オブジェクトとして指定することもできるが、ここではそれぞれの関数名を文字列で指定する。

openhighなどは元のpandas.DataFrameの列名に合わせる。

頻度コードによって様々な期間にダウンサンプリング可能。月、四半期、2週間の例を示す。

d_ohlc = {'open': 'first',
          'high': 'max',
          'low': 'min',
          'close': 'last'}

print(df.resample('MS').agg(d_ohlc))
#               open      high     low   close
# 2017-01-01  115.80  122.4400  114.76  121.35
# 2017-02-01  127.03  137.4800  127.01  136.99
# 2017-03-01  137.89  144.5000  137.05  143.66
# 2017-04-01  143.71  145.4600  140.06  143.65
# 2017-05-01  145.10  156.6500  144.27  152.76
# 2017-06-01  153.17  155.9800  142.20  144.02
# 2017-07-01  144.88  153.9900  142.41  148.73
# 2017-08-01  149.10  164.5200  148.41  164.00
# 2017-09-01  164.80  164.9400  149.16  154.12
# 2017-10-01  154.26  169.6499  152.46  169.04
# 2017-11-01  169.87  176.2400  165.28  171.85
# 2017-12-01  169.95  177.2000  166.46  169.23

print(df.resample('QS').agg(d_ohlc))
#               open    high     low   close
# 2017-01-01  115.80  144.50  114.76  143.66
# 2017-04-01  143.71  156.65  140.06  144.02
# 2017-07-01  144.88  164.94  142.41  154.12
# 2017-10-01  154.26  177.20  152.46  169.23

print(df.resample('2W-MON', closed='left', label='left').agg(d_ohlc))
#                open      high       low   close
# 2017-01-02  115.800  119.9300  114.7600  119.04
# 2017-01-16  118.340  122.4400  118.2200  121.95
# 2017-01-30  120.930  132.9400  120.6200  132.12
# 2017-02-13  133.080  137.4800  132.7500  136.66
# 2017-02-27  137.140  140.2786  136.2800  139.14
# 2017-03-13  138.850  142.8000  138.8200  140.64
# 2017-03-27  139.390  145.4600  138.6200  143.34
# 2017-04-10  143.600  143.8792  140.0600  142.27
# 2017-04-24  143.500  148.9800  143.1800  148.96
# 2017-05-08  149.030  156.6500  149.0300  153.06
# 2017-05-22  154.000  155.4500  152.2200  155.45
# 2017-06-05  154.340  155.9800  142.2000  142.27
# 2017-06-19  143.660  148.2800  142.2800  144.02
# 2017-07-03  144.880  149.3300  142.4100  149.04
# 2017-07-17  148.820  153.9900  147.3000  149.50
# 2017-07-31  149.900  161.8300  148.1300  157.48
# 2017-08-14  159.320  162.5100  155.1101  159.86
# 2017-08-28  160.140  164.9400  158.5300  158.63
# 2017-09-11  160.500  163.9600  150.5600  151.89
# 2017-09-25  149.990  155.4900  149.1600  155.30
# 2017-10-09  155.810  160.8700  155.0200  156.25
# 2017-10-23  156.890  174.2600  155.2700  172.50
# 2017-11-06  172.365  176.2400  168.3800  170.15
# 2017-11-20  170.290  175.5000  167.1600  171.05
# 2017-12-04  172.480  174.1700  166.4600  173.97
# 2017-12-18  174.880  177.2000  169.2200  169.23

なお、agg()aggregate(), apply()は等価。どれでも同じ。

OHLCVの場合は、

  • volume: sum(期間中の合計値を返す)

を追加すればOK。

d_ohlcv = {'open': 'first',
           'high': 'max',
           'low': 'min',
           'close': 'last',
           'volume': 'sum'}

print(df.resample('MS').agg(d_ohlcv))
#               open      high     low   close     volume
# 2017-01-01  115.80  122.4400  114.76  121.35  563331160
# 2017-02-01  127.03  137.4800  127.01  136.99  574968547
# 2017-03-01  137.89  144.5000  137.05  143.66  562091214
# 2017-04-01  143.71  145.4600  140.06  143.65  371280180
# 2017-05-01  145.10  156.6500  144.27  152.76  635292989
# 2017-06-01  153.17  155.9800  142.20  144.02  664986406
# 2017-07-01  144.88  153.9900  142.41  148.73  411377229
# 2017-08-01  149.10  164.5200  148.41  164.00  638221161
# 2017-09-01  164.80  164.9400  149.16  154.12  669594016
# 2017-10-01  154.26  169.6499  152.46  169.04  496135305
# 2017-11-01  169.87  176.2400  165.28  171.85  581876496
# 2017-12-01  169.95  177.2000  166.46  169.23  518560008

関連カテゴリー

関連記事