やること
pythonでローソク足を表示する方法として、mplfinanceというライブラリがあります。
だいたいはこれで事足りるのですが、ローソク足チャートをsubplotとして使いたいときや、カスタマイズしたいときなど、どうすればいいかわからなかったので、自作してみることにしました。
↓こんな感じのチャートをmatplotlibで自作します。
実装
matplotlibのAxesオブジェクトとローソク足データの入ったDataFrameを渡して、ローソク足を描画する関数を作りました。
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import dates as mdates
def ohlcv_plot(ax,df):
"""
matplotlibのAxesオブジェクトにローソク足を描画する.
:param ax:ローソク足を描画するAxesオブジェクト.
:param df:DataFrameオブジェクト. 必要なカラムはtimestamp,open,high,low,close,volume.
"""
# timestampからdatetimeを作りindexにする。datetimeは日本時間を指定。
df["datetime"]=pd.to_datetime(df["timestamp"], unit='s')
df=df.set_index("datetime")
df=df.tz_localize('utc').tz_convert('Asia/Tokyo')
# ローソク足の幅を設定
# matplotlib上でwidth=1->1日となるのでローソク足の時間軸に応じて幅を設定
time_span=df["timestamp"].diff()[1]
w=time_span/(24*60*60)
# ローソク足
# 陽線と陰線で色を変えるため、それぞれのindexを取得
idx1=df.index[df["close"]>=df["open"]]
idx0=df.index[df["close"]<df["open"]]
# 実体
df["body"]=df["close"]-df["open"]
df["body"]=df["body"].abs()
ax.bar(idx1,df.loc[idx1,"body"],width=w * (1-0.2),bottom=df.loc[idx1,"open"],linewidth=1,color="#33b066",zorder=2)
ax.bar(idx0,df.loc[idx0,"body"],width=w * (1-0.2),bottom=df.loc[idx0,"close"],linewidth=1,color="#ff5050",zorder=2)
# ヒゲ
# zorderを指定して実体の後ろになるようにする。
ax.vlines(df.index,df["low"],df["high"],linewidth=1,color="#666666",zorder=1)
# 指標 (SMA,BB)
window=20
# SMA
sma=df["close"].rolling(window).mean()
ax.plot(df.index,sma,linewidth=.5)
# ボリンジャーバンド
window=20
sigma=df["close"].rolling(window).std()
bb_up=sma+2*sigma
bb_low=sma-2*sigma
ax.plot(df.index,bb_up,color="#FF5B11",linewidth=.5)
ax.plot(df.index,bb_low,color="#FF5B11",linewidth=.5)
# 価格目盛調整
# グラフ下部に出来高の棒グラフを描画するので、そのためのスペースを空けるよう目盛を調整する
ymin,ymax=df["low"].min(),df["high"].max()
ticks=ax.get_yticks()
margin=(len(ticks)+4)/5
tick_span=ticks[1]-ticks[0]
min_tick=ticks[0]-tick_span*margin
ax.set_ylim(min_tick, ticks[-1])
ax.set_yticks(ticks[1:])
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, loc: "{:,}".format(int(x))))
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%m-%d %H:%M"))
plt.xticks(rotation=30)
ax.set_axisbelow(True)
ax.grid(True)
# 出来高
axv=ax.twinx()
axv.bar(idx1,df.loc[idx1,"volume"],width=w,color="#33c076",edgecolor="#33b066",linewidth=1)
axv.bar(idx0,df.loc[idx0,"volume"],width=w,color="#ff6060",edgecolor="#ff5050",linewidth=1)
# 出来高目盛が価格目盛にかぶらないよう調整
ymax=df["volume"].max()
axv.set_ylim(0,ymax*5)
ytick=ymax//3
tmp=0
cnt=0
while ytick-tmp>0:
cnt+=1
ytick-=tmp
tmp=ytick%(10**cnt)
axv.set_axisbelow(True)
axv.set_yticks(np.arange(0,ymax,ytick))
axv.tick_params(left=True,labelleft=True,right=False,labelright=False)
axv.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, loc: "{:,}".format(int(x))))
データ準備
実験用にcrypto watchからビットコインのローソク足を取得します。
以下はそのための関数。
import requests
def fetch_ohlcv(symbol="btc",period=60*60):
"""
指定銘柄,指定時間軸の未確定足含む最新1000件のローソク足のDataFrameを返す.
ローソク足はcrypto watchのAPIから取得する.
取引所はliquid.
:param symobl:取得する銘柄
:param period:ローソク足の時間軸. 秒数で指定. 60*60は1時間足. 24*60*60で1日足.
"""
url = f"https://api.cryptowat.ch/markets/liquid/{symbol}jpy/ohlc?period={period}"
work=requests.get(url).json()
rows = []
for t,o,h,l,c,v,_ in work["result"][str(period)]:
ohlcv=[t,o,h,l,c,v]
rows.append(ohlcv)
rows[-1][0] -= period # crypto watchで取得するローソク足はtimestampが足確定の時刻になっている.開始時刻に直す.
rows.sort(key=lambda x:x[0])
df=pd.DataFrame(rows)
df.columns=["timestamp","open","high","low","close","volume"]
return df
df=fetch_ohlcv()
df.head()
"""
[Out]
timestamp open high low close volume
0 1629414000 5367967 5379197 5332839 5348282 90.498291
1 1629417600 5348276 5372000 5337235 5367460 59.795573
2 1629421200 5370233 5372000 5326985 5342500 70.199786
3 1629424800 5342282 5352499 5330417 5350665 45.362724
4 1629428400 5349575 5356597 5332056 5332901 27.142236
"""
実際に描画してみる
df = fetch_ohlcv("btc")
fig = plt.figure(figsize=(7,4))
ax = fig.add_subplot(111)
ohlcv_plot(ax,df[-100:]) # 全部描画すると小さくなって見にくいので一部だけ
plt.show()
ちなみにmplfinanceで描画すると以下のようになります。
df=fetch_ohlcv("btc")
df["datetime"]=pd.to_datetime(df["timestamp"], unit='s')
df=df.set_index("datetime")
df=df.tz_localize('utc').tz_convert('Asia/Tokyo')
dfx=df[["timestamp","open","high","low","close","volume"]]
dfx.columns = ['timestamp','Open', 'High', 'Low', 'Close', 'Volume']
mpf.plot(dfx[-100:], type='candle', figratio=(5,4),volume=True, mav=(5, 25), style='yahoo')
mpf.show()
同じような感じに作ることができました。
ohlcv_plotを使って複数の銘柄を並べて描画してみる
fig=plt.figure(figsize=(14,12))
df=fetch_ohlcv("btc")
ax=plt.subplot2grid((2,2),(0,0))
ohlcv_plot(ax,df[-100:])
ax.set_title("btc",size=10)
df=fetch_ohlcv("eth")
ax=plt.subplot2grid((2,2),(1,0))
ohlcv_plot(ax,df[-100:])
ax.set_title("eth",size=10)
df=fetch_ohlcv("xrp")
ax=plt.subplot2grid((2,2),(0,1))
ohlcv_plot(ax,df[-100:])
ax.set_title("xrp",size=10)
plt.show()
いい感じのグラフをつくる
ローソク足チャートに、ロング/ショートをした場所をマークしたり、損益グラフをサブプロットしたりして、それっぽい感じグラフを作ります。
df = fetch_ohlcv("btc")[-300:]
df["datetime"]=pd.to_datetime(df["timestamp"], unit='s')
df=df.set_index("datetime")
df=df.tz_localize('utc').tz_convert('Asia/Tokyo')
fig = plt.figure(figsize=(16,10))
ax = plt.subplot2grid((2,1),(0,0))
ohlcv_plot(ax,df)
## 適当にロング/ショートした場所をマーク
import numpy as np
df["long"] = np.where(np.random.random(len(df))<0.9,0,1)
df["short"] = np.where(np.random.random(len(df))<0.9,0,1)
ax.scatter(df[df["long"]==1].index,df.loc[df.index[df["long"]==1],"low"]*(1-0.02),marker="^",color="r",label="long")
ax.scatter(df[df["short"]==1].index,df.loc[df.index[df["short"]==1],"high"]*(1+0.02),marker="v",color="b",label="short")
ax.legend()
ax = plt.subplot2grid((2,1),(1,0))
## 適当に損益が発生したとする
df["pnl"] = np.random.random(len(df))-0.45
df["pnl"] *= 100
ax.plot(df.index,df["pnl"].cumsum(),label="pnl")
ax.fill_between(df.index,0,df["pnl"].cumsum(),alpha=.3)
ax.legend()
plt.show()
時間をインデックスに指定して、はじめと最後の時刻を揃えれば上下のグラフで時間軸が揃っていい感じ。
追記
頑張って作りましたがmpl_financeでできるみたいです。
.py
from mpl_finance import candlestick_ohlc
candlestick_ohlc(ax, ohlcv, width=(1/24/60)*0.7,colorup='g', colordown='r')
以下の記事で見つけました。