note.nkmk.me

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

Date: 2018-08-29 / 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()を使う。

以下のデータを例とする。

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()の引数に渡す頻度コードによって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で2017年のApple(AAPL)の株価データを取得して例とする。

2018年8月29日現在、pandasとpandas-datareaderのバージョンによってはインポートエラーが発生するので注意。その対処法のほか、pandas-datareaderについての詳細は以下の記事を参照。

pandas-datareaderでは日次のOHLCVデータが取得できる。インデックスは文字列なのでpd.to_datetime()で変換する。

import pandas as pd
import pandas_datareader.data as web

df = web.DataReader('AAPL', 'iex', '2017-01-01', '2017-12-31')

df.index = pd.to_datetime(df.index)

print(df.shape)
# (251, 5)

print(df.head())
#                 open      high       low     close    volume
# date                                                        
# 2017-01-03  112.6732  113.1889  111.6613  113.0138  28781865
# 2017-01-04  112.7219  113.3641  112.6246  112.8873  21118116
# 2017-01-05  112.7900  113.7087  112.6830  113.4614  22193587
# 2017-01-06  113.6268  114.9695  113.3251  114.7263  31751900
# 2017-01-09  114.7652  116.2052  114.7554  115.7771  33561948

print(df.tail())
#                 open      high       low     close    volume
# date                                                        
# 2017-12-22  172.6967  173.4323  172.5188  173.0230  16349444
# 2017-12-26  168.8608  169.5232  167.7525  168.6334  33185536
# 2017-12-27  168.1687  168.8410  167.7831  168.6630  21498213
# 2017-12-28  169.0585  169.8988  168.5444  169.1376  16480187
# 2017-12-29  168.5839  168.6531  167.2987  167.3086  25999922

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

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

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

頻度コードによって様々な期間にダウンサンプリング可能。

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

print(df.resample('MS').agg(d_ohlc))
#                 open      high       low     close
# date                                              
# 2017-01-01  112.6732  119.1339  111.6613  118.0734
# 2017-02-01  123.6000  134.3478  123.5805  133.8690
# 2017-03-01  134.7485  141.2079  133.9276  140.3870
# 2017-04-01  140.4359  142.1460  136.8690  140.3772
# 2017-05-01  141.7942  153.7130  140.9831  149.8959
# 2017-06-01  150.2982  153.0555  139.5339  141.3198
# 2017-07-01  142.1637  151.1029  139.7400  145.9415
# 2017-08-01  154.1153  162.0694  152.1135  161.5572
# 2017-09-01  162.3453  162.4832  146.9382  151.8244
# 2017-10-01  151.9623  167.1229  150.1891  166.5221
# 2017-11-01  167.3398  173.6149  162.8181  169.8988
# 2017-12-01  168.0204  175.1881  164.5700  167.3086

print(df.resample('QS').agg(d_ohlc))
#                 open      high       low     close
# date                                              
# 2017-01-01  112.6732  141.2079  111.6613  140.3870
# 2017-04-01  140.4359  153.7130  136.8690  141.3198
# 2017-07-01  142.1637  162.4832  139.7400  151.8244
# 2017-10-01  151.9623  175.1881  150.1891  167.3086

print(df.resample('2W-MON', closed='left', label='left').agg(d_ohlc))
#                 open      high       low     close
# date                                              
# 2017-01-02  112.6732  116.6917  111.6613  115.8257
# 2017-01-16  115.1446  119.1339  115.0279  118.6572
# 2017-01-30  117.6647  129.9113  117.3631  129.1099
# 2017-02-13  130.0481  134.3478  129.7256  133.5465
# 2017-02-27  134.0156  137.0827  133.1752  135.9700
# 2017-03-13  135.6866  139.5466  135.6573  137.4358
# 2017-03-27  136.2143  142.1460  135.4618  140.0743
# 2017-04-10  140.3284  140.6012  136.8690  139.0287
# 2017-04-24  140.2307  145.5858  139.9180  145.5663
# 2017-05-08  145.6347  153.7130  145.6347  150.1903
# 2017-05-22  151.1127  152.5355  149.3660  152.5355
# 2017-06-05  151.4463  153.0555  139.5339  139.6026
# 2017-06-19  140.9665  145.4999  139.6124  141.3198
# 2017-07-03  142.1637  146.5302  139.7400  146.2457
# 2017-07-17  146.0298  151.1029  144.5383  146.6970
# 2017-07-31  147.0895  158.7959  145.3527  155.1343
# 2017-08-14  156.9469  160.0894  152.7997  157.4789
# 2017-08-28  157.7547  162.4832  156.1687  156.2672
# 2017-09-11  158.1093  161.5178  148.3174  149.6276
# 2017-09-25  147.7559  153.1739  146.9382  152.9868
# 2017-10-09  153.4892  158.4738  152.7109  153.9226
# 2017-10-23  154.5531  171.6644  152.9572  169.9306
# 2017-11-06  169.7976  173.6149  166.4682  168.2181
# 2017-11-20  168.3566  173.5074  165.2621  169.1079
# 2017-12-04  170.5217  172.1925  164.5700  171.9948
# 2017-12-18  172.8944  175.1881  167.2987  167.3086

なお、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
# date                                                         
# 2017-01-01  112.6732  119.1339  111.6613  118.0734  563331160
# 2017-02-01  123.6000  134.3478  123.5805  133.8690  574968547
# 2017-03-01  134.7485  141.2079  133.9276  140.3870  562091214
# 2017-04-01  140.4359  142.1460  136.8690  140.3772  373290435
# 2017-05-01  141.7942  153.7130  140.9831  149.8959  654022901
# 2017-06-01  150.2982  153.0555  139.5339  141.3198  684178036
# 2017-07-01  142.1637  151.1029  139.7400  145.9415  422011831
# 2017-08-01  154.1153  162.0694  152.1135  161.5572  646894310
# 2017-09-01  162.3453  162.4832  146.9382  151.8244  680442092
# 2017-10-01  151.9623  167.1229  150.1891  166.5221  504291118
# 2017-11-01  167.3398  173.6149  162.8181  169.8988  600366443
# 2017-12-01  168.0204  175.1881  164.5700  167.3086  531184058
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事