LoginSignup
16
19

More than 1 year has passed since last update.

matplotlibでローソク足を自作する(mplfinanceを使わない)

Last updated at Posted at 2021-10-01

やること

pythonでローソク足を表示する方法として、mplfinanceというライブラリがあります。
だいたいはこれで事足りるのですが、ローソク足チャートをsubplotとして使いたいときや、カスタマイズしたいときなど、どうすればいいかわからなかったので、自作してみることにしました。
↓こんな感じのチャートをmatplotlibで自作します。

スクリーンショット 2021-10-05 23.33.20.png

実装

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()

スクリーンショット 2021-10-05 23.33.20.png

ちなみに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()

スクリーンショット 2021-10-05 23.33.37.png

同じような感じに作ることができました。

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()

スクリーンショット 2021-10-05 23.33.49.png

いい感じのグラフをつくる

ローソク足チャートに、ロング/ショートをした場所をマークしたり、損益グラフをサブプロットしたりして、それっぽい感じグラフを作ります。


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()

スクリーンショット 2021-10-05 23.34.49.png

時間をインデックスに指定して、はじめと最後の時刻を揃えれば上下のグラフで時間軸が揃っていい感じ。

追記

頑張って作りましたがmpl_financeでできるみたいです。

from mpl_finance import candlestick_ohlc
candlestick_ohlc(ax, ohlcv, width=(1/24/60)*0.7,colorup='g', colordown='r')

以下の記事で見つけました。

16
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
19