Qiita - u1and0 / Plotlyでぐりぐり動かせる為替チャートを作る(1)の続きです。
指標の追加・削除・初期化ができるようになりました。
下準備
モジュールインポート
必要なモジュールをインポートします。
# ----------General Module----------
import numpy as np
import pandas as pd
# ----------User Module----------
from randomwalk import randomwalk
import stockplot as sp
# ----------Hide General Module----------
import stockstats
import plotly
- General Module, Hide General Moduleは一般に配布されているパッケージなので、condaやpipといったパッケージ管理ソフトなどで追加してください。
- General ModuleはこのJupyter Notebook内で使います。
- Hide General Moduleは
stockplot
内で使用します。
conda install plotly
pip install stockstats
- User Moduleのstockplotについては過去記事も併せてご覧ください。
- random_walkについてはQiita - u1and0 / pythonでローソク足(candle chart)の描画
サンプルデータの作成
# Make sample data
np.random.seed(10)
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日分作成します。
インスタンス化
# Convert DataFrame as StockPlot
fx = sp.StockPlot(df)
StockPlotクラスでインスタンス化します。
ローソク足の描画
fig = sp.StockPlot(sdf)
でインスタンス化されたら時間足を変換します。
変換する際はresample
メソッドを使います。
fx.resample('4H').head()
close | open | high | low | |
---|---|---|---|---|
2017-03-20 00:00:00 | 115.34 | 115.00 | 115.98 | 114.79 |
2017-03-20 04:00:00 | 116.03 | 115.34 | 116.48 | 115.16 |
2017-03-20 08:00:00 | 116.31 | 116.03 | 116.75 | 115.76 |
2017-03-20 12:00:00 | 115.92 | 116.32 | 116.87 | 115.62 |
2017-03-20 16:00:00 | 114.36 | 115.92 | 116.12 | 113.85 |
時間足の設定が済んだらプロットしてみます。
fx.plot(start_view='first', end_view='last')
fx.show('png', filebasename='png1')
ここまでが前回記事の復習です。
指標の操作
指標の追加
指標をプロットしてみます。
最もポピュラーな単純移動平均(Simple Moving Average)をプロットします。
追加するにはappend
メソッドを使います。
fx.append('close_25_sma')
fx.stock_dataframe.head()
close | open | high | low | close_25_sma | |
---|---|---|---|---|---|
2017-03-20 00:00:00 | 115.34 | 115.00 | 115.98 | 114.79 | 115.340000 |
2017-03-20 04:00:00 | 116.03 | 115.34 | 116.48 | 115.16 | 115.685000 |
2017-03-20 08:00:00 | 116.31 | 116.03 | 116.75 | 115.76 | 115.893333 |
2017-03-20 12:00:00 | 115.92 | 116.32 | 116.87 | 115.62 | 115.900000 |
2017-03-20 16:00:00 | 114.36 | 115.92 | 116.12 | 113.85 | 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.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='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, $\sigma$を1に変更してみます。
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')
$\sigma_1$と$\sigma_2$は同時に描けないのが残念です。
BOLL_PERIOD
, BOLL_STD_TIMES
はstockstats
のクラス変数なので、
stockplot.stockstats.BOLL_STD_TIMES = 2
のようにして再定義する必要があります。
しかし、stockstats
が指標を追加するとき、_get
メソッドを使うので、一度追加した指標が上書きされてしまいます。
グラフに描くだけであれば何とかすればできそうですが、今後の課題とします。
サブチャートについて
stockstatsは多くの指標の出力に対応していますが、サブチャートを必要とする指標が多くあります。(MACD, RSI, ADX...)
今回のリリースではサブチャートに手を付けていません。
Cufflinksを使ってみたらサブプロットととかも簡単にいきそうな気がします。
トップのgifファイルについて
最初のgif画像はチャートをipython上からインタラクティブにhtmlとして出力している様子です。
- モジュールのインポートから日足に変更するところまでを実行する
./bin/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('H')
ソースコードはgithubに上げました。
github - u1and0/stockplot