Python と Jupyter Notebook と pandas と Highcharts を使って Price Channel を計算・表示してみました

Price Channel を計算してみました。 せっかくなので、 python-highcharts を使ってローソク足と一緒に表示してみました。

Price Channel は Donchian Channel や High Low(HL) Bands とも呼ばれるそうです。

環境

  • Ubuntu 16.04 LTS
  • Python 3.5.2
  • jupyter-1.0.0
  • numpy-1.14.3
  • pandas-0.22.0
  • python-highcharts 0.5.2

準備

以前、ヒストリカルデータのリサンプリングをしたときと同じものを使いました。

Jupyter Notebook

Jupyter Notebook を使って 1 セルごとに実行しながら進めました。

モジュールを読み込む

npmpy と pandas と python-highcharts のモジュールを読み込みました。

import numpy as np
import pandas as pd
from highcharts import Highstock

ヒストリカルデータを読み込む

ヒストリカルデータを読み込みました。

次のような CSV の形式になっています。

$ head USDJPY_1\ Min_Bid_2000.01.01_2018.01.01.csv
Time (UTC),Open,High,Low,Close,Volume
2003.05.04 21:00:00,118.94,118.952,118.94,118.952,253
2003.05.04 21:01:00,118.961,118.967,118.958,118.967,154.6
2003.05.04 21:02:00,118.972,118.972,118.955,118.955,219.7
2003.05.04 21:03:00,118.953,118.961,118.949,118.949,309.9
2003.05.04 21:04:00,118.953,118.953,118.946,118.946,229.4
2003.05.04 21:05:00,118.952,118.954,118.944,118.944,112.2
2003.05.04 21:06:00,118.95,118.952,118.945,118.945,170.2
2003.05.04 21:07:00,118.947,118.956,118.947,118.947,124.5
2003.05.04 21:08:00,118.946,118.954,118.934,118.934,355

550 万件程度あるみたいです。

$ wc -l USDJPY_1\ Min_Bid_2000.01.01_2018.01.01.csv
5509561 USDJPY_1 Min_Bid_2000.01.01_2018.01.01.csv

pd.read_csv で読み込んで DataFrame を生成しました。

filepath1 = 'USDJPY_1 Min_Bid_2000.01.01_2018.01.01.csv'
dtype1 = { 'time': str, 'open': float, 'high': float, 'low': float, 'close': float, 'volume': float }
names1 = ['time', 'open', 'high', 'low', 'close', 'volume']
%time df = pd.read_csv(filepath_or_buffer=filepath1, dtype=dtype1, header=0, index_col='time', names=names1, parse_dates=['time'])
df.head()

結果です。

 	open 	high 	low 	close 	volume
time 					
2003-05-04 21:00:00 	118.940 	118.952 	118.940 	118.952 	253.0
2003-05-04 21:01:00 	118.961 	118.967 	118.958 	118.967 	154.6
2003-05-04 21:02:00 	118.972 	118.972 	118.955 	118.955 	219.7
2003-05-04 21:03:00 	118.953 	118.961 	118.949 	118.949 	309.9
2003-05-04 21:04:00 	118.953 	118.953 	118.946 	118.946 	229.4

EEST にする

時刻が UTC なので EEST にします。

data2 = { 'open': df['open'].values, 'high': df['high'].values, 'low': df['low'].values, 'close': df['close'].values, 'volume': df['volume'].values }
columns2 = ['open', 'high', 'low', 'close', 'volume']
index2 = df.index + pd.DateOffset(hours=3)
%time df2 = pd.DataFrame(data=data2, columns=columns2, index=index2)
df2.head()

結果です。

 	open 	high 	low 	close 	volume
time 					
2003-05-05 00:00:00 	118.940 	118.952 	118.940 	118.952 	253.0
2003-05-05 00:01:00 	118.961 	118.967 	118.958 	118.967 	154.6
2003-05-05 00:02:00 	118.972 	118.972 	118.955 	118.955 	219.7
2003-05-05 00:03:00 	118.953 	118.961 	118.949 	118.949 	309.9
2003-05-05 00:04:00 	118.953 	118.953 	118.946 	118.946 	229.4

リサンプリング

1 分足から日足にリサンプリングしました。

%time df3 = df2.resample(rule='D').ohlc()
%time df4 = df2.resample(rule='D').sum()
data5 = { 'open': df3['open']['open'].values, 'high': df3['high']['high'].values, 'low': df3['low']['low'].values, 'close': df3['close']['close'].values, 'volume': df4['volume'].values }
columns5 = ['open', 'high', 'low', 'close', 'volume']
%time df5 = pd.DataFrame(data=data5, columns=columns5, index=df3.index).dropna()
df5.head()

結果です。

 	open 	high 	low 	close 	volume
time 					
2003-05-05 	118.940 	119.046 	118.461 	118.603 	592866.9
2003-05-06 	118.591 	118.751 	117.290 	117.500 	581707.0
2003-05-07 	117.456 	117.830 	116.052 	116.303 	584496.2
2003-05-08 	116.311 	116.969 	115.940 	116.823 	588236.7
2003-05-09 	116.835 	117.612 	116.794 	117.151 	583132.9

高値と安値を計算する

ここから Price Channel の計算になります。

n 期間の高値と安値を計算します。 pandas の関数があるようで、最初、 pd.rolling_maxpd.rolling_min を使ったのですが、 pandas 0.22.0 では deprecated になっていました。 DataFrame.rolling(...).max()DataFrame.rolling(...).min() を使うようです。

period = 20
%time high = df5['high'].rolling(window=period).max()
%time low = df5['low'].rolling(window=period).min()
%time pd.DataFrame(data={ 'high': high, 'low': low }).tail()

期間は 20 にしてみました。

結果です。

CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 4.02 ms
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 1.34 ms

 	high 	low
time 		
2017-12-28 	113.75 	111.993
2017-12-29 	113.75 	112.030
2017-12-30 	113.75 	112.030
2018-01-01 	113.75 	112.030
2018-01-02 	113.75 	112.030

日足にリサンプリングしたデータは 4,100 件程度ありましたが、わたしの環境では 1 ミリ秒から 5 ミリ秒程度で計算できました。

Highstock 向けの DataFrame を作る

python-highcharts に渡すデータの形式は 2 次元配列なので、それに合わせて DataFrame を作ります。

time6 = (df5.index.values // pd.Timedelta('1ms')).astype(np.int64)
data6 = { 'time': time6, 'open': df5['open'].values, 'high': df5['high'].values, 'low': df5['low'].values, 'close': df5['close'].values }
columns6 = ['time', 'open', 'high', 'low', 'close']
df6 = pd.DataFrame(data=data6, columns=columns6)
data7 = { 'time': time6, 'hlhigh': high.values }
columns7 = ['time', 'hlhigh']
df7 = pd.DataFrame(data=data7, columns=columns7)
data8 = { 'time': time6, 'hllow': low.values }
columns8 = ['time', 'hllow']
df8 = pd.DataFrame(data=data8, columns=columns8)
df6.merge(df7).merge(df8).tail()

df5 がローソク足のデータです。 df6 が Price Channel の高値のデータです。 df7 が Price Channel の安値のデータです。

DataFrame を index なしの行と列の形式にして、 df.values.tolist() のようにすると 2 次元配列の形式で渡すことができるからこのようにしています。 index ありの DataFrame でも簡単に列の順番を考慮した 2 次元配列の形式にすることができるのかな。 df.values が ndarray だから、 DataFrame にしなくても ndarray にできれば十分そうだけど、 pandas と NumPy の行き来がまだ不慣れです。

結果です。

 	time 	open 	high 	low 	close 	hlhigh 	hllow
4098 	1514419200000 	113.279 	113.351 	112.664 	112.895 	113.75 	111.993
4099 	1514505600000 	112.894 	112.969 	112.472 	112.678 	113.75 	112.030
4100 	1514592000000 	112.680 	112.714 	112.647 	112.658 	113.75 	112.030
4101 	1514764800000 	112.658 	112.658 	112.658 	112.658 	113.75 	112.030
4102 	1514851200000 	112.658 	112.789 	112.570 	112.773 	113.75 	112.030

Highstock のローソク足と Price Channel を表示する

H = Highstock()

H.add_data_set(df6.values.tolist(), 'candlestick', '米ドル/円')
H.add_data_set(df7.values.tolist(), 'line', 'High')
H.add_data_set(df8.values.tolist(), 'line', 'Low')

options = {
    'plotOptions': {
        'line': {
            'lineWidth': 1
        }
    },
    'rangeSelector': {
        'selected': 1
    },
    'title': {
        'text': '米ドル/円 daily'
    }
}
H.set_dict_options(options)

H

ローソク足のデータは H.add_data_set(df6.values.tolist(), 'candlestick', '米ドル/円') のようにして渡しています。

Price Channel のデータは H.add_data_set(df7.values.tolist(), 'line', 'High')H.add_data_set(df8.values.tolist(), 'line', 'Low') のように 2 つの折れ線グラフのデータとして渡しています。

オプションは Price Channel の折れ線グラフの線が太かったので細くしました。 それから、チャートを表示する範囲の初期値を 3m にしました。

結果は Jupyter Notebook Viewer から見ることができます。

Highstock の SMA (単純移動平均線)

Highstock のチャートのタイプに sma があったので、 python-highcharts でも使えるか試してみました。

が、 #OptionTypeError: Not An Accepted Option Type: sma のようなエラーになってしまいました。 sma は使えないようでした。

pandas で計算すればいいや。

Gist のソース

Gist にアップしました。

Jupyter Notebook Viewer からチャートを見ることができます。

終わり

今はインディケーターは使わなくなったのですが、 Price Channel はタートルズも使っていたブレイクアウトの戦略を、自分でも検証してみたかったので計算してみました。

計算したデータで検証してみたいと思います。