PythonでCandlestickChartをBokehで描けるようになるまで

# きっかけ
　Botterになるためにまずはチャートを描けるようにならなくてはいけないと思いました。
　最初、Plotlyでやってたんだけど、有料っていうのを見かけたので、Bokehにシフト

# 環境
+ Windows 10 Pro 21H1
+ Python 3.9.7
+ Jupyter Notebook 6.4.5
+ Pandas 1.3.4
+ Bokeh 2.4.1

# コード
```python
from math import pi

import pandas as pd

from bokeh.plotting import figure, show, output_file
from bokeh.sampledata.stocks import MSFT

df = pd.DataFrame(MSFT)
df["date"] = pd.to_datetime(df["date"])

mids = (df.open + df.close)/2
spans = abs(df.close-df.open)

inc = df.close > df.open
dec = ~inc


# 日時表示用のフォーマットを設定（日足と分足に分岐
xaxis_dt_format = '%Y-%m-%d'
if df.iloc[0]['date'].hour > 0:
    xaxis_dt_format = '%Y-%m-%d_%H:%M:%S'


# チャートの初期設定
p = figure(sizing_mode="stretch_both",
           tools=TOOLS, 
           x_axis_type="linear", # linearを指定しないと、土日が空くチャートになる。
           plot_width=1000, 
           toolbar_location="left")


# Colour scheme for increasing and descending candles
# 色はなんとなくTradingViewに合わせた
INCREASING_COLOR = '#26a69a'
DECREASING_COLOR = '#ef5350'

width = 0.5
# 陽線と陰線のデータをまとめちゃう
inc_source = ColumnDataSource(data=dict(
    x1=df.index[inc],
    top1=df.open[inc],
    bottom1=df.close[inc],
    high1=df.high[inc],
    low1=df.low[inc],
    Date1=df.date[inc]
))

dec_source = ColumnDataSource(data=dict(
    x2=df.index[dec],
    top2=df.open[dec],
    bottom2=df.close[dec],
    high2=df.high[dec],
    low2=df.low[dec],
    Date2=df.date[dec]
))

w = 12*60*60*1000 # half day in ms　半日をミリ秒に？

TOOLS = "pan,wheel_zoom,box_zoom,crosshair,reset,save"


p.title = "MSFT Candlestick"
p.xaxis.major_label_orientation = pi/4 # X軸のラベルを開店
p.grid.grid_line_alpha=0.3
p.yaxis[0].formatter = NumeralTickFormatter(format="5.3f") #　y軸のフォーマット

# Plot candles　#ローソク足を描いていく
# High and low
p.segment(x0='x1', y0='high1', x1='x1', y1='low1', source=inc_source, color=INCREASING_COLOR)
p.segment(x0='x2', y0='high2', x1='x2', y1='low2', source=dec_source, color=DECREASING_COLOR)

# Open and close
r1 = p.vbar(x='x1', width=width, top='top1', bottom='bottom1', source=inc_source,
                fill_color=INCREASING_COLOR, line_color=INCREASING_COLOR)
r2 = p.vbar(x='x2', width=width, top='top2', bottom='bottom2', source=dec_source,
                fill_color=DECREASING_COLOR, line_color=DECREASING_COLOR)
# Add date labels to x axis
p.xaxis.major_label_overrides = {
    i: date.strftime(xaxis_dt_format) for i, date in enumerate(pd.to_datetime(df.date))
}

# Set up the hover tooltip to display some useful data
p.add_tools(HoverTool(
    renderers=[r1],
    tooltips=[
        ("Open", "$@top1"),
        ("High", "$@high1"),
        ("Low", "$@low1"),
        ("Close", "$@bottom1"),
        ("Date", "@Date1{" + xaxis_dt_format + "}"),
    ],
    formatters={
        '@Date1': 'datetime',
        
    },
    mode='vline', # 縦線に相当する値を表示
    # mode='mouse',  # マウスポインタを合わせたときに表示
))

p.add_tools(HoverTool(
    renderers=[r2],
    tooltips=[
        ("Open", "$@top2"),
        ("High", "$@high2"),
        ("Low", "$@low2"),
        ("Close", "$@bottom2"),
        ("Date", "@Date2{" + xaxis_dt_format + "}")
    ],
    formatters={
        '@Date2': 'datetime',
    },
    mode='vline', # 縦線に相当する値を表示
    # mode='mouse',  # マウスポインタを合わせたときに表示
))
#output_file("candlestick.html", title="candlestick.py example")

# 出力先をノートブックに設定
output_notebook() 

show(p)  # open a browser
```

# 結果
![スクリーンショット (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531802/523c4d97-2086-e447-163b-d5373e8794ce.png)

# 補足
bokeh.sampledata.stocksは、事前にダウンロードしないといけないです。
下記を事前に実行しておくと、データ落としといてくれます。

```python
import bokeh.sampledata
bokeh.sampledata.download()
```

# 今後の予定
移動平均線とか出してみたいと思っている。


# 参考

https://qiita.com/alchemist/items/544d45480ce9c1ca2c16

https://blog.novonovo.jp/python/bokeh%E3%81%A7%E6%8B%A1%E5%A4%A7%E7%B8%AE%E5%B0%8F%E3%82%84%E3%83%89%E3%83%A9%E3%83%83%E3%82%B0%E3%81%8C%E3%81%A7%E3%81%8D%E3%82%8B%E3%83%AD%E3%83%BC%E3%82%BD%E3%82%AF%E8%B6%B3%E3%81%AE%E3%83%81/

https://www.soliton-cyber.com/blog/colab-bokeh-candlestick

https://github.com/ndepaola/bokeh-candlestick/blob/master/candlestick_plot.py