TOC
- stockplot.py
- 下準備
- インストール
- モジュールインポート
- サンプルデータの作成
- インスタンス化
- チャートの作成
- 時間足の変更
- ローソク足の描画
- チャートの表示
- 再度時間足の変更
- 平均足の描画
- 描画の範囲
- plot範囲の指定
- view範囲の指定
- 右側に空白を作る
- data範囲、plot範囲, view範囲、shiftまとめ
- チャート描画までのまとめ
- メソッド一覧
- 使用メソッドフローチャート
- 指標の操作
- 指標の追加
- 指標の削除
- 指標の初期化
- 指標操作のまとめと補足
- 使用メソッドフローチャート
- ボリンジャーバンドについて
- サブチャートについて
- トップのgifファイルについて
- 追記
- 下準備
- read_hst.py
- 何をするためのスクリプト?
- インストール
- データのダウンロード
- 使用方法
- jupyter notebook や ipython上で使うとき
- bashなどのshell上で使うとき
- 参考
githubにソースコードを置きました。
git clone https://github.com/u1and0/stockplot.git ~/the_path_of_your_repository
パスを追加します。
import sys
sys.path.append('~/the_path_of_your_repository/stockplot/bin/')
必要なモジュールをインポートします。
from randomwalk import randomwalk
import stockplot as sp
必要に応じて一般に配布されているモジュールをインストールしてきて下さい。
stockplot.py
は以下のパッケージに依存しているので、環境になければImportError
が吐かれます。
import numpy as np
import pandas as pd
import stockstats
import plotly
インストールするときは
conda install numpy pandas plotly
pip install stockstats
# Make sample data
np.random.seed(1)
df = randomwalk(60 * 60 * 24 * 90, freq='S', tick=0.01, start=pd.datetime(2017, 3, 20))\
.resample('T').ohlc() + 115 # 90日分の1分足, 初期値が115
ランダムな為替チャートを作成します。 randomwalk関数で2017/3/20からの1分足を90日分作成します。
ランダムなOHLCを使う代わりに、MT4で使うhstファイルなどからOHCLデータを読み込んで使うこともできます。 最後に記述する
bin/read_hst.py
を使うとzipの展開からhstの読み込み、データフレームの読み込みまで自動で行ってくれます。 参考: MT4ヒストリカルデータをpython上で扱えるようにしたりcsvに保存するimport read_hst as h df = h.read_hst('data/USDJPY.zip') # zipファイルの相対/絶対パスshellからcsv, pickle形式への変換を行うこともできます。
$ cd ~/python/stockplot $ bin/read_hst.py -c ~/Data/USDJPY.zip # Convert .hst to .csv
StockPlotクラスでインスタンス化します。
# Convert DataFrame as StockPlot
fx = sp.StockPlot(df)
fx = sp.StockPlot(df)
でインスタンス化されたらまずは時間足を指定します。
変換する際はresample
メソッドを使います。
fx.resample('D').head()
low | open | close | high | |
---|---|---|---|---|
2017-03-20 | 112.71 | 115.00 | 114.22 | 116.80 |
2017-03-21 | 113.67 | 114.23 | 115.52 | 116.23 |
2017-03-22 | 112.23 | 115.51 | 112.29 | 117.44 |
2017-03-23 | 111.88 | 112.28 | 116.02 | 116.08 |
2017-03-24 | 114.76 | 116.03 | 118.60 | 119.10 |
1分足として入力したデータを日足に変換したデータが返されました。
変換されたデータはstock_dataframe
というインスタンス変数に格納されます。
fx.stock_dataframe.head(), fx.stock_dataframe.tail()
( low open close high
2017-03-20 112.71 115.00 114.22 116.80
2017-03-21 113.67 114.23 115.52 116.23
2017-03-22 112.23 115.51 112.29 117.44
2017-03-23 111.88 112.28 116.02 116.08
2017-03-24 114.76 116.03 118.60 119.10,
low open close high
2017-06-13 103.18 106.19 106.12 106.28
2017-06-14 104.59 106.13 108.07 108.51
2017-06-15 103.97 108.06 105.66 108.86
2017-06-16 104.94 105.66 108.25 108.59
2017-06-17 107.31 108.24 109.22 110.73)
2017/3/20-2017/6/17の日足ができたことを確認しました。
resampleメソッドは'open', 'high', 'low', 'close'
のカラムを持ったデータフレーム(OHLC)の時間足変換を行います。
def resample(self, freq: str):
"""Convert ohlc time span
Usage: `fx.resample('D') # 日足に変換`
* Args: 変更したい期間 M(onth) | W(eek) | D(ay) | H(our) | T(Minute) | S(econd)
* Return: スパン変更後のデータフレーム
"""
self.freq = freq
df = self._init_stock_dataframe.resample(freq).ohlc2().dropna()
self.stock_dataframe = ss.StockDataFrame(df)
for indicator in self._indicators.keys():
self.append(indicator) # Re-append indicator in dataframe
return self.stock_dataframe
OHLCの時間足を変えたいとき、fx.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
...
そこで、OHLC->OHLCの変換をメソッドでできるようにohlc2()
メソッドを作成しました。
from pandas.core import resample
def ohlc2(self):
"""`pd.DataFrame.resample(<TimeFrame>).ohlc2()`
Resample method converting OHLC to OHLC
"""
agdict = {'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last'}
columns = list(agdict.keys())
if all(i in columns for i in self.columns):
pass
elif all(i in columns + ['volume'] for i in self.columns):
agdict['volume'] = 'sum'
else:
raise KeyError("columns must have ['open', 'high', 'low', 'close'(, 'volume')]")
return self.agg(agdict)
# Add instance as `pd.DataFrame.resample('<TimeFrame>').ohlc2()`
resample.DatetimeIndexResampler.ohlc2 = ohlc2
# 使い方
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
...
freq
はdf.resample
で使える時間であれば自由なので、例えばfreq="1D4H2T24S"とすると"1日と4時間2分24秒足"といった変な時間足を作れます。
fx.resample('1D4H2T24S').head()
low | open | close | high | |
---|---|---|---|---|
2017-03-20 00:00:00 | 112.71 | 115.00 | 114.28 | 116.80 |
2017-03-21 04:02:24 | 113.67 | 114.28 | 116.92 | 117.44 |
2017-03-22 08:04:48 | 111.93 | 116.91 | 112.38 | 117.26 |
2017-03-23 12:07:12 | 111.88 | 112.38 | 117.79 | 118.01 |
2017-03-24 16:09:36 | 116.71 | 117.80 | 120.74 | 121.27 |
時間足の変換が済むと、プロットが可能です。
プロットするときはplot
メソッドを使います。
fx.plot()
fx.plot()
でplotly
で出力する形式plotly.graph_objs.graph_objs.Figure
(data
とlayout
がキーとなった辞書)が返されます。
def plot(self, bar='candle', start_view=None, end_view=None, periods_view=None, shift=None,
start_plot=None, end_plot=None, periods_plot=None,
showgrid=True, validate=False, **kwargs):
"""Retrun plotly candle chart graph
Usage: `fx.plot()`
* Args:
* bar: 'candle', 'c' -> candle_plot / 'heikin', 'h' -> heikin_ahi plot
* start, end: 最初と最後のdatetime, 'first'でindexの最初、'last'でindexの最後
* periods: 足の本数
> **start, end, periods合わせて2つの引数が必要**
* shift: shiftの本数の足だけ右側に空白
* Return: グラフデータとレイアウト(plotly.graph_objs.graph_objs.Figure)
"""
# ---------Set "plot_dataframe"----------
# Default Args
if com._count_not_none(start_plot,
end_plot, periods_plot) == 0:
end_plot = 'last'
periods_plot = 300
try:
# first/last
start_plot = self.stock_dataframe.index[0] if start_plot == 'first' else start_plot
end_plot = self.stock_dataframe.index[-1] if end_plot == 'last' else end_plot
except AttributeError:
raise AttributeError('{} Use `fx.resample(<TimeFrame>)` at first'
.format(type(self.stock_dataframe)))
# Set "plot_dataframe"
start_plot, end_plot = set_span(start_plot, end_plot, periods_plot, self.freq)
if bar in ('candle', 'c'):
plot_dataframe = self.stock_dataframe.loc[start_plot:end_plot]
self._fig = FF.create_candlestick(plot_dataframe.open,
plot_dataframe.high,
plot_dataframe.low,
plot_dataframe.close,
dates=plot_dataframe.index)
elif bar in ('heikin', 'h'):
self.stock_dataframe.heikin_ashi()
plot_dataframe = self.stock_dataframe.loc[start_plot:end_plot]
self._fig = FF.create_candlestick(plot_dataframe.hopen,
plot_dataframe.hhigh,
plot_dataframe.hlow,
plot_dataframe.hclose,
dates=plot_dataframe.index)
else:
raise KeyError('Use bar = "[c]andle" or "[h]eikin"')
# ---------Append indicators----------
for indicator in self._indicators.keys():
self._append_graph(indicator, start_plot, end_plot) # Re-append indicator in graph
# ---------Set "view"----------
# Default Args
if com._count_not_none(start_view,
end_view, periods_view) == 0:
end_view = 'last'
periods_view = 50
# first/last
start_view = plot_dataframe.index[0] if start_view == 'first' else start_view
end_view = plot_dataframe.index[-1] if end_view == 'last' else end_view
# Set "view"
start_view, end_view = set_span(start_view, end_view, periods_view, self.freq)
end_view = set_span(start=end_view, periods=shift,
freq=self.freq)[-1] if shift else end_view
view = list(to_unix_time(start_view, end_view))
# ---------Plot graph----------
self._fig['layout'].update(xaxis={'showgrid': showgrid, 'range': view},
yaxis={"autorange": True})
return self._fig
チャートを見るにはmatplotlib.pyplot
のようにshow
メソッドを使います。
show
メソッドの第一引数how
のデフォルト引数はhtml
です。
引数なしでshow
するとブラウザの新しいタブが立ち上がってそこに表示されます。
今はJupyter Notebook上で描きたいので、how=jupyter
、または単にjupyter
を引数にします。
def show(self, how='html', filebasename='candlestick_and_trace'):
"""Export file type"""
if how == 'html':
ax = pyo.plot(self._fig, filename=filebasename + '.html',
validate=False) # for HTML
elif how == 'jupyter':
ax = pyo.iplot(self._fig, filename=filebasename + '.html',
validate=False) # for Jupyter Notebook
elif how in ('png', 'jpeg', 'webp', 'svg'):
ax = pyo.plot(self._fig, image=how, image_filename=filebasename,
validate=False) # for file exporting
else:
raise KeyError(how)
return ax
fx.show(how='jupyter')
2017/3/20-2017/6/17の日足が描かれました。
plotlyの操作は
- グラフ上のマウスオーバーで値の表示
- グラフ上のドラッグでズームイン
- 軸上(真ん中)のドラッグでスクロール
- 軸上(端)のドラッグでズームアウト
- ダブルクリックで元のビューに戻る
- トリプルクリックで全体表示
日足だけじゃなくて別の時間足も見たいです。
そういうときは再度resample
メソッドを使って時間幅を変更します。
fx.resample('H') # 1時間足に変更
fx.plot() # ローソク足プロット
fx.show('jupyter') # プロットの表示をJupyter Notebookで開く
1時間足がプロットされました。 あえて時間をかけてマウスオーバーしているのですが、1時間ごとにプロットされていることがわかりましたでしょうか。
ここで再度stock_dataframe
を確認してみますと、1時間足に変わっていることがわかります。
fx.stock_dataframe.head(), fx.stock_dataframe.tail()
( low open close high
2017-03-20 00:00:00 114.76 115.00 115.26 115.49
2017-03-20 01:00:00 115.27 115.27 116.11 116.47
2017-03-20 02:00:00 115.69 116.10 115.69 116.53
2017-03-20 03:00:00 115.62 115.68 116.02 116.19
2017-03-20 04:00:00 115.74 116.01 116.00 116.31,
low open close high
2017-06-17 19:00:00 108.50 108.65 109.91 109.93
2017-06-17 20:00:00 109.56 109.90 109.76 110.03
2017-06-17 21:00:00 109.47 109.76 109.77 110.06
2017-06-17 22:00:00 109.27 109.77 109.31 110.10
2017-06-17 23:00:00 108.96 109.30 109.22 109.70)
平均足の計算は以下のとおりです。
始値 = (一本前の始値(平均足) + 一本前の終値(平均足))÷2
高値 = 高値(ローソク足)
安値 = 安値(ローソク足)
終値 = (始値(ローソク足) + 高値(ローソク足) + 安値(ローソク足) + 終値(ローソク足) )÷4
pythonで書くと次のようになります。
def heikin_ashi(self):
"""Return HEIKIN ASHI columns"""
self['hopen'] = (self.open.shift() + self.close.shift()) / 2
self['hclose'] = (self[['open', 'high', 'low', 'close']]).mean(1)
self['hhigh'] = self[['high', 'hopen', 'hclose']].max(1)
self['hlow'] = self[['low', 'hopen', 'hclose']].min(1)
return self[['hopen', 'hhigh', 'hlow', 'hclose']]
pd.DataFrame.heikin_ashi = heikin_ashi
plot
メソッドで描画方法bar
(第一引数なので省略可)をheikin
(またはh
)と指定するとデフォルトのローソク足(candle
)から平均足の描画に切り替わります。
fx.plot('h')
fx.show(how='png', filebasename = 'heikin')
plot
メソッドはstock_dataframe
の中身をすべてグラフ化しません。
デフォルトの場合、最後の足から数えて300本足がグラフ化されます。
例として、5分足のチャートを描きます。
fx.resample('5T') # 5分足に変換
fx.plot()
fx.show('jupyter')
# stock_dataframeは2017/3/20から
fx.stock_dataframe.index
DatetimeIndex(['2017-03-20 00:00:00', '2017-03-20 00:05:00',
'2017-03-20 00:10:00', '2017-03-20 00:15:00',
'2017-03-20 00:20:00', '2017-03-20 00:25:00',
'2017-03-20 00:30:00', '2017-03-20 00:35:00',
'2017-03-20 00:40:00', '2017-03-20 00:45:00',
...
'2017-06-17 23:10:00', '2017-06-17 23:15:00',
'2017-06-17 23:20:00', '2017-06-17 23:25:00',
'2017-06-17 23:30:00', '2017-06-17 23:35:00',
'2017-06-17 23:40:00', '2017-06-17 23:45:00',
'2017-06-17 23:50:00', '2017-06-17 23:55:00'],
dtype='datetime64[ns]', length=25920, freq='5T')
2017/3/20-2017/6/17ののデータフレームを5分足に変換してローソク足を描きました。 最初の足が2017/3/20ではなく2017/6/16で途切れています。 これはグラフ化される範囲が5分足の300本足で切られているためです。
描画されるデータが大きいとshow
メソッド時に大変リソースを食います。
グラフとして見る範囲は限定的だろうとの考えから、plot
メソッドはstock_dataframe
から一部切り出した形をグラフ化(plot)します。
グラフ化する範囲は、plot
メソッドの引数として与えることができます。
plot
メソッドのプロット範囲を決める引数start_plot
: グラフ化する最初の日付・時間end_plot
: グラフ化する最後の日付・時間periods_plot
: グラフ化する足の数(int型)
- start, end, periodsのうち二つが指定されている必要がある。
- 何も指定しなければ、デフォルトとして最後の足から数えて300本が描画される。
# Default Args
if com._count_not_none(start_plot,
end_plot, periods_plot) == 0: # すべてNoneのままだったら
end_plot = 'last' # 最後の足から
periods_plot = 300 # 300本足で切る
# first/last
start_plot = self.stock_dataframe.index[0] if start_plot == 'first' else start_plot
end_plot = self.stock_dataframe.index[-1] if end_plot == 'last' else end_plot # 'last'=最後の足とはindexの最後
start_plot, end_plot
を指定して描画してみます。
# fx.resample('5T') # 既に5分足に変換されているので必要ない
start = pd.datetime(2017,6,17,9,0,0) # 2017/6/17 09:00
end = pd.datetime(2017,6,17,23,0,0) # 2017/6/17 23:00
fx.plot(start_plot=start, end_plot=end) # 2017/6/17 09:00-23:00までをプロットする
fx.show('jupyter')
2017/6/17 09:00 - 2017/6/17 23:00の5分足が描かれました。
plotlyのズームイン / アウト、スクロールを使えば表示範囲外のところも見れます。 しかし、見たい期間が最初から決まっているのにもかかわらず、グラフ化してからスクロールするのはメンドウです。
そこで、plot
メソッドではグラフ化して最初に見えるビュー範囲(view)を指定できます。
例えば2017/5/8から2017/6/5の4時間足が見たいとしましょう。
fx.resample('4H') # 4時間足に変換
start = pd.datetime(2017,5,8) # 2017/5/8
end = pd.Timestamp('20170605') # 2017/6/5(Timestampでも指定可能)
fx.plot(start_view=start, end_view=end) # 2017/5/8 - 2017/6/5を表示する
fx.show('jupyter')
次はstart_view, end_view
の指定ではなく、end_view, periods_view
を使って表示してみます。
fx.resample('D') # 日足に変換
fx.plot(periods_view=20, end_view='last')
# `end_view`を'last' 最後の足に設定する
# `periods_view`で20本足表示する
fx.show('html') # html形式で表示
plot
メソッドのビュー範囲を決める引数start_view
: 表示する最初の日付・時間end_view
: 表示する最後の日付・時間periods_view
: 表示する足の数(int型)
- start, end, periodsのうち二つが指定されている必要がある。
- 何も指定しなければ、デフォルト値が入力される。
# Default Args
if com._count_not_none(start_view,
end_view, periods_view) == 0: # すべてNoneのままだったら
end_view = 'last' # 最後の足から
periods_view = 50 # 50本足までを表示する
# first/last
start_view = plot_dataframe.index[0] if start_view == 'first' else start_view
end_view = plot_dataframe.index[-1] if end_view == 'last' else end_view # 'last'はindexの最後
periods
の指定はend
が指定された場合はstart
、start
が指定された場合はend
を計算します。
計算する関数は次のようにしました。
from pandas.core import common as com
def set_span(start=None, end=None, periods=None, freq='D'):
""" 引数のstart, end, periodsに対して
startとendの時間を返す。
* start, end, periods合わせて2つの引数が指定されていなければエラー
* start, endが指定されていたらそのまま返す
* start, periodsが指定されていたら、endを計算する
* end, periodsが指定されていたら、startを計算する
"""
if com._count_not_none(start, end, periods) != 2: # 引数が2個以外であればエラー
raise ValueError('Must specify two of start, end, or periods')
# `start`が指定されていれば`start`をそのまま返し、そうでなければ`end`から`periods`引いた時間を`start`とする。
start = start if start else (pd.Period(end, freq) - periods).start_time
# `end`が指定されていれば`end`をそのまま返し、そうでなければ`start`から`periods`足した時間を`end`とする。
end = end if end else (pd.Period(start, freq) + periods).start_time
return start, end
呼び出すときは次のようにします。
start_view, end_view = set_span(start_view, end_view, periods_view, self.freq)
説明は省きましたが、グラフ化する時間足も
view
と同様にperiods_plot
引数として指定できます。
view
はself._fig
のlayout
において、xaxisの範囲(range)を変更するのに使います。
変更する際、unix時間に変換する必要があるので、to_unix_time
関数に通します。
def to_unix_time(*dt: pd.datetime)->iter:
"""datetimeをunix秒に変換
引数: datetime(複数指定可能)
戻り値: unix秒に直されたイテレータ"""
epoch = pd.datetime.utcfromtimestamp(0)
return ((i - epoch).total_seconds() * 1000 for i in dt)
view = list(to_unix_time(start_view, end_view))
# ---------Plot graph----------
self._fig['layout'].update(xaxis={'showgrid': showgrid, 'range': view},
yaxis={"autorange": True})
引数shift
に指定した足の本数だけ、右側に空白を作ります。
時間足が短いとうまくいきません。原因究明中です。 想定より多めに足の数を設定することでとりあえず回避しています。
予測線を引いたり一目均衡表を使うとき必要になる機能だと思います。
fx.plot()
fx.show('jupyter')
fx.plot(shift=30)
fx.show('jupyter')
plot
メソッドのfix
引数を30とし、30本の足だけの空白を右側(時間の遅い側)に作ることができました。
処理としては、先ほど出てきたset_span
関数を使って、end_view
に30本足分の時間足を足してあげます。
end_view = set_span(start=end_view, periods=shift,
freq=self.freq)[-1] if shift else end_view
図示すると以下のような感じです。
__init__
- pandas.Dataframeをインスタンス化
- open, high, low, closeのカラムを持たないとエラー
- indexがDatetimeIndexでなければエラー
resample
メソッドfreq
引数で時間足を決める。stock_dataframe
を決める。
plot
メソッド- plot範囲(
plot_dataframe
)を決める。start_plot
end_plot
periods_plot
- view範囲を決める。
start_view
end_view
periods_view
- グラフの右側の空白(shift)を決める。
shift
- plot範囲(
show
メソッド- 出力形式を決める。
how='jupyter', 'html', 'png', 'jpeg', 'webp', 'svg'
- ファイル名を決める。
filebasename
- 出力形式を決める。
各メソッドの呼び出しに使う引数と戻り値、プロットに使うフローは以下の図の通りです。
指標をプロットしてみます。
最もポピュラーな単純移動平均(Simple Moving Average)をプロットします。
追加するにはappend
メソッドを使います。
fx.append('close_25_sma')
fx.stock_dataframe.head()
high | low | open | close | close_25_sma | |
---|---|---|---|---|---|
2017-03-20 00:00:00 | 115.98 | 114.79 | 115.00 | 115.34 | 115.340000 |
2017-03-20 04:00:00 | 116.48 | 115.16 | 115.34 | 116.03 | 115.685000 |
2017-03-20 08:00:00 | 116.75 | 115.76 | 116.03 | 116.31 | 115.893333 |
2017-03-20 12:00:00 | 116.87 | 115.62 | 116.32 | 115.92 | 115.900000 |
2017-03-20 16:00:00 | 116.12 | 113.85 | 115.92 | 114.36 | 115.592000 |
fx.plot(start_view='first', end_view='last')
fx.show('png', filebasename='png2')
close_25_sma(25本足単純移動平均線)が追加されました。
なお、append
メソッド単体をJupyter NotebookやIpython上で実行するとclose_25_smaの値が戻り値として表示されます。
追加された指標は時間足を変えても、その時間足に合わせて値を変更してくれます。
fx.resample('15T')
fx.plot(start_view='first', end_view='last')
fx.show('png', filebasename='png3')
resample
メソッドで15分足に変えた後、append
メソッドを使わなくともclose_25_sma
が追加されたままです。
これはappend
メソッドを実行した際ではなく、plot
メソッドを実行した際にグラフに指標を追加するようにしたためです。
append
メソッドが行うのはself._indicators
に値を格納するだけです。
# ========self._indicatorに指標を蓄える==========
def append(self, indicator):
indicator_value = self.stock_dataframe[indicator]
self._indicators[indicator] = indicator_value # self._indicatorsに辞書形式で
return indicator_value
# =======plotメソッド実行時にself._indicatorに蓄えられている指標を_append_graphに渡す==========
def plot(self, (略)):
# (中略)
# ---------Append indicators----------
for indicator in self._indicators.keys():
self._append_graph(indicator, start_plot, end_plot) # Re-append indicator in graph
# (中略)
return self._fig
# =======self._indicatorに蓄えられている指標をself._figのデータ部分に追加する==========
def _append_graph(self, indicator, start, end):
graph_value = self._indicators[indicator].loc[start:end]
plotter = go.Scatter(x=graph_value.index, y=graph_value,
name=indicator.upper().replace('_', ' ')) # グラフに追加する形式変換
self._fig['data'].append(plotter)
指標の削除にはpop
メソッドを使用します。
fx.pop('close_25_sma')
fx.stock_dataframe.head()
open | high | low | close | |
---|---|---|---|---|
2017-03-20 00:00:00 | 115.00 | 115.98 | 114.79 | 115.34 |
2017-03-20 04:00:00 | 115.34 | 116.48 | 115.16 | 116.03 |
2017-03-20 08:00:00 | 116.03 | 116.75 | 115.76 | 116.31 |
2017-03-20 12:00:00 | 116.32 | 116.87 | 115.62 | 115.92 |
2017-03-20 16:00:00 | 115.92 | 116.12 | 113.85 | 114.36 |
fx.plot(start_view='first', end_view='last')
fx.show('png', filebasename='png3_1')
close_25_smaが削除されました。
単純移動平均以外の指標も描いてみます。
fx.append('close_20_ema') # 終値の指数移動平均線
fx.append('boll') # ボリンジャーバンド真ん中(close_20_smaと同じ)
fx.append('boll_ub') # ボリンジャーバンド上
fx.append('boll_lb') # ボリンジャーバンド下
fx.append('high_0~20_max') # 20足前の移動最高値
fx.append('low_0~20_min') # 20足前の移動最安値
fx.plot(start_view='first', end_view='last')
fx.show('png', filebasename='png4')
- 20本足ボリンジャーバンド
- 20本足移動最高値
- 20本足最安値
がプロットされました。
追加した指標名がわからなくなったらインスタンス変数からアクセスできます。
fx._indicators.keys()
dict_keys(['low_0~20_min', 'boll', 'high_0~20_max', 'boll_ub', 'close_20_ema', 'boll_lb'])
append
メソッドを使ったときの引数がkey、戻り値がvalueとして、_indicators
にディクショナリ形式で保存されます。
そのため、keys
メソッドで追加した指標名を呼び出すことができます。
fx.stock_dataframe.columns
でも表示できますが、推奨できません。stockstats.StockDataFrame
は指標の生成時に補助的なカラムも発生させます。 そのため、補助指標(グラフにプロットされていないデータ)も混在していて、どれがプロットされているのか見分けづらいためです。
fx.stock_dataframe.columns
Index(['open', 'high', 'low', 'close', 'close_20_ema', 'close_20_sma',
'close_20_mstd', 'boll', 'boll_ub', 'boll_lb', 'high_0_s', 'high_1_s',
'high_2_s', 'high_3_s', 'high_4_s', 'high_5_s', 'high_6_s', 'high_7_s',
'high_8_s', 'high_9_s', 'high_10_s', 'high_11_s', 'high_12_s',
'high_13_s', 'high_14_s', 'high_15_s', 'high_16_s', 'high_17_s',
'high_18_s', 'high_19_s', 'high_20_s', 'high_0~20_max', 'low_0_s',
'low_1_s', 'low_2_s', 'low_3_s', 'low_4_s', 'low_5_s', 'low_6_s',
'low_7_s', 'low_8_s', 'low_9_s', 'low_10_s', 'low_11_s', 'low_12_s',
'low_13_s', 'low_14_s', 'low_15_s', 'low_16_s', 'low_17_s', 'low_18_s',
'low_19_s', 'low_20_s', 'low_0~20_min'],
dtype='object')
fx.stock_dataframe.columns
による指標の表示は、追加していない指標名も表示されます。
ごちゃごちゃしてきたのでhigh_20_max
, low_20_min
を削除します。
fx.pop('high_0~20_max')
fx.pop('low_0~20_min')
fx.plot(start_view='first', end_view='last')
fx.show('png', filebasename='png5')
high_20_max
, low_20_min
だけがグラフから削除されました。
pop
メソッドは以下の手順で進みます。
self._indicator
の中からindicatorで指定された値を削除します。self.stock_dataframe
からopen, high, low, close
だけ抜き出します。self._indicators
に残っている指標を再度プロットします。
def pop(self, indicator):
popper = self._indicators.pop(indicator) # (1)
self.stock_dataframe = reset_dataframe(self.stock_dataframe) # (2)
for reindicator in self._indicators.keys():
self.stock_dataframe.get(reindicator) # (3)
return popper
self.stock_dataframe
に入っている指標は、追加した指標によっては補助的に作られたカラムなどが混在します。
そのため、「ある指標によって作られたカラムだけ」を特定し、self.stock_dataframe
から削除するのが困難です。
よって、一度self.stock_dataframe
をresample
がかかった状態まで戻し(2)、再度指標を追加しています(3)。
(3)はappend
メソッドとほとんど同じことですが、self._indicators
に追加しません。
(1)の段階でself._indicators
からは余計な指標を取り除いていないため、self._indicators
に再度追加する必要がありません。
追加した指標をすべて消すときはclear
メソッドを使います。
fx.clear()
fx.stock_dataframe.head()
open | high | low | close | |
---|---|---|---|---|
2017-03-20 00:00:00 | 115.00 | 115.26 | 114.87 | 115.11 |
2017-03-20 00:15:00 | 115.11 | 115.21 | 114.85 | 115.01 |
2017-03-20 00:30:00 | 115.01 | 115.49 | 114.90 | 115.47 |
2017-03-20 00:45:00 | 115.47 | 115.50 | 115.24 | 115.26 |
2017-03-20 01:00:00 | 115.25 | 115.49 | 115.10 | 115.27 |
fx.plot(start_view='first', end_view='last')
fx.show('png', filebasename='png6')
- データフレーム(
self.stock_dataframe
)を初期化します。 - グラフ(
self._fig
)を初期化します。 - 指標(
self._indicators
)を初期化します。 - 時間足は初期化しません。
hardオプションをTrueにする(
fx.clear(hard=True)
として実行する)ことで時間足も初期化できます(ハードリセット)。self.stock_dataframe
はNone
に戻ります。 ハードリセットをかけた後に再度プロットしたいときはresample
メソッドから実行してください。
def clear(self, hard=False):
self._fig = None # <-- plotly.graph_objs
self._indicators = {}
if hard:
self.stock_dataframe = None
self.freq = None # 足の時間幅
else:
self.stock_dataframe = reset_dataframe(self.stock_dataframe)
clear
メソッドはほとんど__init__
メソッドと同じですが、
- データとしての引数が必要ないこと
- デフォルトでは時間足を変更しないこと
すなわち再度プロットするときに
resample
メソッドを使う必要がないこと
の点が__init__
と異なります。
左側が追加と表示、右側が削除とリセットを表しています。
stockstats
ではボリンジャーバンドで使う移動区間と$\sigma$がクラス変数として定義されています。
BOLL_PERIOD = 20
BOLL_STD_TIMES = 2
ここで移動区間を5,
sp.ss.StockDataFrame.BOLL_PERIOD = 5 # ボリンジャーバンド移動区間の設定
sp.ss.StockDataFrame.BOLL_STD_TIMES = 1 # ボリンジャーバンドσの設定
boll = sp.StockPlot(df)
boll.resample('4H')
boll.append('boll') # ボリンジャーバンド真ん中(close_5_smaと同じ)
boll.append('boll_ub') # ボリンジャーバンド上
boll.append('boll_lb') # ボリンジャーバンド下
boll.plot(start_view='first', end_view='last')
boll.show('png', filebasename='png7')
BOLL_PERIOD
, BOLL_STD_TIMES
はstockstats
のクラス変数なので、
stockplot.stockstats.BOLL_STD_TIMES = 2
のようにして再定義する必要があります。
しかし、stockstats
が指標を追加するとき、_get
メソッドを使うので、一度追加した指標が上書きされてしまいます。
グラフに描くだけであれば何とかすればできそうですが、今後の課題とします。
stockstats
は多くの指標の出力に対応していますが、サブチャートを必要とする指標が多くあります。(MACD, RSI, ADX...)
今回のリリースではサブチャートに手を付けていません。
Cufflinksを使ってみたらサブプロットととかも簡単にいきそうな気がします。
最初のgif画像はチャートをipython上からインタラクティブにhtmlとして出力している様子です。
- モジュールのインポートから日足に変更するところまでを実行する
./test/stockplot_quickset.py
を実行します。 - 'close_25_sma'を追加します。
- 時間足を15分足に変えます。
- 'close_75_sma'を追加します。
# ----------General Module----------
import numpy as np
import pandas as pd
# ----------User Module----------
from randomwalk import randomwalk
import stockplot as sp
# ----------Plotly Module----------
import plotly.offline as pyo
pyo.init_notebook_mode(connected=True)
# Make sample data
np.random.seed(1)
# 90日分の1秒tickを1分足に直す
df = randomwalk(60 * 60 * 24 * 90, freq='S', tick=0.01, start=pd.datetime(2017, 3, 20)).resample('T').ohlc() + 115
# Convert StockDataFrame as StockPlot
fx = sp.StockPlot(df)
# Resample as Day OHLC
fx.resample('D')
2017/4/21 Qiitaに投稿しました。README.mdと内容はほぼ同じです。 Plotlyでぐりぐり動かせる為替チャートを作る
2017/4/22 Qiitaデイリーランキング4位に入りました。ありがとうございます^^
ヒストリカルデータをpythonを使用してpandas DataFrameとして読み出したり、csvやpickleに書き込みを行います。
github - u1and0/stockplotからcloneしてください。
git clone https://github.com/u1and0/stockplot.git
binディレクトリ下のread_hst.pyを使用してください。
FXDD Trading などからヒストリカルデータ(一分足のティックデータ)の圧縮ファイルをダウンロードしてください。
wget, aria2などのコマンドが使える環境にあれば
$ wget http://tools.fxdd.com/tools/M1Data/USDJPY.zip
などとしてヒストリカルデータの圧縮ファイルをダウンロードできます。 容量は50MB程度です。
- read_hstモジュールをインポートします。
- read_hst()関数にダウンロードしたzipファイルのパス、または解凍したhstファイルのパスを入れます。
- 結果はpandas DataFrameとして返されます。
import read_hst as h
df = h.read_hst('data/USDJPY.zip') # zipファイルの相対/絶対パス
# hstファイル以外の拡張子が与えられると、展開したhstファイルは削除されます。
df = h.read_hst('data/USDJPY.hst') # hstファイルの相対/絶対パス
# zipを解凍してhstファイルを引数に与えたらファイルを削除しません。
df.tail
open high low close volume
time
2017-11-17 08:32:00 112.573 112.584 112.573 112.581 50.0
2017-11-17 08:33:00 112.581 112.583 112.578 112.580 38.0
2017-11-17 08:34:00 112.580 112.583 112.578 112.580 51.0
2017-11-17 08:35:00 112.580 112.580 112.572 112.572 44.0
2017-11-17 08:36:00 112.572 112.574 112.572 112.572 24.0
以下のコマンドは~/Data/USDJPY.zipを~/Data/USDJPY.csvとして保存します。
-p
とすればpickleファイル(拡張子はpkl)としても保存できます。
$ cd ~/python/stockplot
$ bin/read_hst.py -c ~/Data/USDJPY.zip # Convert .hst to .csv
$ bin/read_hst.py -h
usage: bin/read_hst.py [-h] [-c] [-p] filenames [filenames ...]
Convering historical file (.hst) to csv or pickle file.
positional arguments:
filenames
optional arguments:
-h, --help show this help message and exit
-c, --csv Convert to csv file
-p, --pickle Convert to pickle file
- stockplot使い方
- 旧バージョン: Qiita - u1and0 / plotlyでキャンドルチャートプロット
- random_walkについて: Qiita - u1and0 / pythonでローソク足(candle chart)の描画
- stockplot使い方1: Plotlyでぐりぐり動かせる為替チャートを作る(1)
- stockplot使い方2: Plotlyでぐりぐり動かせる為替チャートを作る(2)
- numpyを使用して高速にバイナリ→テキスト変換 >> (´・ω・`;)ヒィィッ すいません - pythonでMT4のヒストリファイルを読み込む
- 引数読み込み >> Converting MT4 binary history files: hst to csv using a python script