Skip to content

candlestick

2 posts with the tag “candlestick”

bokeh로 봉차트(Candlestick Chart) 그리기

파이썬에서 봉차트를 제공하는 라이브러리가 많지 않습니다. 필자는 matplotlib에서 떨어져 나온 mpl_finance와 bokeh 정도로 알고 있습니다. 이미 mpl_finance로 봉차트를 그리는 방법은 이전 포스트 Matplotlib으로 봉차트(Candlestick Chart) 그리기 에서 다루었습니다.

이번 포스트에서 bokeh로 봉차트를 그리는 방법에 대해서 다루겠습니다. bokeh는 Anaconda에서 개발 중인 차트 라이브러리 입니다. matplotlib과 matplotlib 기반인 seaborn과는 다르게 독자 노선을 가지는 라이브러리 입니다. bokeh는 웹에 최적화 되어있는 라이브러리 입니다. matplotlib을 웹브라우저에 띄우려면 차트를 png 등의 그림으로 변환하여 보여줘야 해서 응답형(reponsible) 차트를 웹에 띄우기가 어렵습니다. 그래서 보통 웹에 차트를 띄울 때는 데이터만 서버에서 받고 Chart.jsD3.js 같은 자바스크립트 라이브러리를 사용하여 데이터를 가시화 합니다. bokeh는 파이썬에서 차트를 가시화하고 그 결과를 쉽게 웹에 띄울 수 있는 여러 방법을 제공합니다. 이 포스트에서는 다루지 않겠습니다.

bokeh에서 봉차트를 제공하고 있습니다. bokeh 봉차트 문서에서 상세한 정보를 확인해 보세요.

먼저 Anaconda3가 설치되어 있는 상태라 가정하고 글을 이어나가겠습니다. Jupyter Lab(Notebook)에서 bokeh가 잘 설치되어 있는지 확인합니다.

from bokeh.io import output_notebook, show
from bokeh.plotting import figure, gridplot
output_notebook()

BokehJS 0.12.16 successfully loaded.

이렇게 로딩이 성공되었다는 메시지가 뜨는지 확인합니다. 만약 다음과 같은 메시지가 뜬다면 Jupyter Lab의 Extension을 설치해야 합니다.

Loading BokehJS ...
JavaScript output is disabled in JupyterLab

Anaconda Prompt에서 다음과 같이 jupyterlab_bokeh 확장을 설치합니다.

Terminal window
jupyter labextension install jupyterlab_bokeh

설치가 되지 않는다면 아마도 node.js가 설치되어 있지 않아서일 것입니다. 그렇다면 conda install nodejs를 통해 먼저 node.js를 설치하고 다시 jupyterlab_bokeh 확장을 설치합니다.

bokeh가 잘 설치되어 있다면 이제 봉차트를 그릴 준비가 되었습니다. 차트 데이터는 다음과 같이 ['date', 'open', 'high', 'low', 'close', 'volume']의 리스트로 있습니다.

import pandas as pd
data = [['20190219', 125000.0, 127500.0, 123500.0, 126000.0, 57757], ['20190220', 125000.0, 127000.0, 124500.0, 126000.0, 68453], ['20190221', 125500.0, 126000.0, 124000.0, 125000.0, 43961], ['20190222', 125000.0, 125000.0, 123500.0, 125000.0, 31065], ['20190225', 125500.0, 126000.0, 124000.0, 125500.0, 45852], ['20190226', 125000.0, 127000.0, 124500.0, 126500.0, 37404], ['20190227', 126500.0, 127000.0, 124500.0, 126000.0, 36131], ['20190228', 126500.0, 127000.0, 124500.0, 125000.0, 69474], ['20190304', 124500.0, 125500.0, 122500.0, 123500.0, 65517], ['20190305', 123000.0, 125000.0, 122000.0, 124500.0, 35186], ['20190306', 124000.0, 124500.0, 122500.0, 123500.0, 35449], ['20190307', 123500.0, 124000.0, 122000.0, 123500.0, 34768], ['20190308', 122500.0, 122500.0, 120500.0, 121500.0, 35118], ['20190311', 121500.0, 122500.0, 120000.0, 122500.0, 39576], ['20190312', 123000.0, 124000.0, 122000.0, 123500.0, 24117], ['20190313', 123000.0, 123500.0, 121500.0, 123500.0, 37649], ['20190314', 123000.0, 124000.0, 122000.0, 123500.0, 95132], ['20190315', 123000.0, 128000.0, 123000.0, 126500.0, 107246], ['20190318', 127000.0, 131000.0, 126500.0, 131000.0, 74644], ['20190319', 130000.0, 134000.0, 129500.0, 133000.0, 68348], ['20190320', 132000.0, 133000.0, 129500.0, 131000.0, 42697], ['20190321', 130000.0, 132000.0, 127500.0, 128500.0, 54018], ['20190322', 127500.0, 129500.0, 126500.0, 127000.0, 32380], ['20190325', 125500.0, 126500.0, 124000.0, 124500.0, 37185], ['20190326', 124500.0, 125500.0, 123500.0, 124000.0, 45161], ['20190327', 124000.0, 125000.0, 123500.0, 124000.0, 34336], ['20190328', 124000.0, 124500.0, 120500.0, 121500.0, 43518], ['20190329', 122500.0, 125000.0, 122000.0, 124500.0, 39035], ['20190401', 124000.0, 126500.0, 124000.0, 126500.0, 22463], ['20190402', 125500.0, 126500.0, 123500.0, 126000.0, 31754], ['20190403', 124500.0, 128000.0, 124500.0, 128000.0, 36250], ['20190404', 128500.0, 128500.0, 126000.0, 128000.0, 34854], ['20190405', 127500.0, 129000.0, 126000.0, 127500.0, 33513], ['20190408', 127500.0, 128000.0, 126000.0, 128000.0, 39005], ['20190409', 128000.0, 129000.0, 127500.0, 128500.0, 33266], ['20190410', 128000.0, 129000.0, 126500.0, 128000.0, 64476], ['20190411', 128500.0, 129000.0, 125000.0, 125000.0, 84802], ['20190412', 126000.0, 127500.0, 125500.0, 127000.0, 39663], ['20190415', 126500.0, 128500.0, 126000.0, 127000.0, 61140], ['20190416', 127000.0, 129000.0, 126500.0, 128500.0, 40123], ['20190417', 128500.0, 129000.0, 127000.0, 128000.0, 30846], ['20190418', 128000.0, 128500.0, 124000.0, 124500.0, 55346], ['20190419', 124500.0, 125000.0, 122000.0, 123000.0, 53439], ['20190422', 123000.0, 123500.0, 121000.0, 122500.0, 30421], ['20190423', 122500.0, 123500.0, 120500.0, 121500.0, 54997], ['20190424', 122500.0, 122500.0, 119500.0, 120000.0, 63486], ['20190425', 121000.0, 121000.0, 118500.0, 119000.0, 36046], ['20190426', 118500.0, 119500.0, 117000.0, 119000.0, 43749], ['20190429', 119000.0, 119500.0, 117000.0, 119500.0, 33516], ['20190430', 122000.0, 123000.0, 119000.0, 119500.0, 94118], ['20190502', 118500.0, 122000.0, 118000.0, 121000.0, 56723], ['20190503', 121000.0, 122000.0, 119500.0, 120000.0, 35240], ['20190507', 118500.0, 119500.0, 117000.0, 117500.0, 44453], ['20190508', 116500.0, 117000.0, 115000.0, 116500.0, 52805], ['20190509', 117000.0, 117000.0, 113000.0, 113000.0, 116012], ['20190510', 113000.0, 114500.0, 110000.0, 111500.0, 86072], ['20190513', 110500.0, 110500.0, 107500.0, 108500.0, 70847], ['20190514', 107500.0, 108000.0, 105000.0, 106500.0, 92820], ['20190515', 107000.0, 107500.0, 105000.0, 107000.0, 68937], ['20190516', 107000.0, 107500.0, 104000.0, 105000.0, 64047]]
df = pd.DataFrame(data, columns=['date', 'open', 'high', 'low', 'close', 'volume'])
dateopenhighlowclosevolume
20190219125000.0127500.0123500.0126000.057757
20190220125000.0127000.0124500.0126000.068453
20190221125500.0126000.0124000.0125000.043961
20190222125000.0125000.0123500.0125000.031065
20190225125500.0126000.0124000.0125500.045852
20190226125000.0127000.0124500.0126500.037404
20190227126500.0127000.0124500.0126000.036131
20190228126500.0127000.0124500.0125000.069474
20190304124500.0125500.0122500.0123500.065517
20190305123000.0125000.0122000.0124500.035186
20190306124000.0124500.0122500.0123500.035449
20190307123500.0124000.0122000.0123500.034768
20190308122500.0122500.0120500.0121500.035118
20190311121500.0122500.0120000.0122500.039576
20190312123000.0124000.0122000.0123500.024117
20190313123000.0123500.0121500.0123500.037649
20190314123000.0124000.0122000.0123500.095132
20190315123000.0128000.0123000.0126500.0107246
20190318127000.0131000.0126500.0131000.074644
20190319130000.0134000.0129500.0133000.068348
20190320132000.0133000.0129500.0131000.042697
20190321130000.0132000.0127500.0128500.054018
20190322127500.0129500.0126500.0127000.032380
20190325125500.0126500.0124000.0124500.037185
20190326124500.0125500.0123500.0124000.045161
20190327124000.0125000.0123500.0124000.034336
20190328124000.0124500.0120500.0121500.043518
20190329122500.0125000.0122000.0124500.039035
20190401124000.0126500.0124000.0126500.022463
20190402125500.0126500.0123500.0126000.031754
20190403124500.0128000.0124500.0128000.036250
20190404128500.0128500.0126000.0128000.034854
20190405127500.0129000.0126000.0127500.033513
20190408127500.0128000.0126000.0128000.039005
20190409128000.0129000.0127500.0128500.033266
20190410128000.0129000.0126500.0128000.064476
20190411128500.0129000.0125000.0125000.084802
20190412126000.0127500.0125500.0127000.039663
20190415126500.0128500.0126000.0127000.061140
20190416127000.0129000.0126500.0128500.040123
20190417128500.0129000.0127000.0128000.030846
20190418128000.0128500.0124000.0124500.055346
20190419124500.0125000.0122000.0123000.053439
20190422123000.0123500.0121000.0122500.030421
20190423122500.0123500.0120500.0121500.054997
20190424122500.0122500.0119500.0120000.063486
20190425121000.0121000.0118500.0119000.036046
20190426118500.0119500.0117000.0119000.043749
20190429119000.0119500.0117000.0119500.033516
20190430122000.0123000.0119000.0119500.094118
20190502118500.0122000.0118000.0121000.056723
20190503121000.0122000.0119500.0120000.035240
20190507118500.0119500.0117000.0117500.044453
20190508116500.0117000.0115000.0116500.052805
20190509117000.0117000.0113000.0113000.0116012
20190510113000.0114500.0110000.0111500.086072
20190513110500.0110500.0107500.0108500.070847
20190514107500.0108000.0105000.0106500.092820
20190515107000.0107500.0105000.0107000.068937
20190516107000.0107500.0104000.0105000.064047

bokeh를 이용해 봉차트를 그려봅니다. 먼저 데이터에서 양봉과 음봉에 대한 mask를 저장합니다.

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

inc는 양봉, dec는 음봉에 해당합니다. 이제 봉들을 그려줍니다. 하나의 봉은 bokeh에서 segmentvbar로 구성되어 있습니다. segment는 high와 low까지 선으로 그은 봉의 꼬리를 그리는데 사용됩니다. vbar로는 봉의 몸통을 그립니다.

p_candlechart = figure(plot_width=1050, plot_height=200, x_range=(-1, len(df)), tools="crosshair")
p_candlechart.segment(df.index[inc], df.high[inc], df.index[inc], df.low[inc], color="red")
p_candlechart.segment(df.index[dec], df.high[dec], df.index[dec], df.low[dec], color="blue")
p_candlechart.vbar(df.index[inc], 0.5, df.open[inc], df.close[inc], fill_color="red", line_color="red")
p_candlechart.vbar(df.index[dec], 0.5, df.open[dec], df.close[dec], fill_color="blue", line_color="blue")

봉차트 밑에 거래량 막대차트(Bar Chart)까지 그려 줍니다. 이 때도 vbar를 사용하면 됩니다.

p_volumechart = figure(plot_width=1050, plot_height=100, x_range=p_candlechart.x_range, tools="crosshair")
p_volumechart.vbar(df.index, 0.5, df.volume, fill_color="black", line_color="black")

그리고 마지막으로 위 차트들을 gridplot으로 배치하여 가시화합니다.

p = gridplot([[p_candlechart], [p_volumechart]], toolbar_location=None)
show(p)

이 코드들을 모아서 다음과 같은 봉차트를 그릴 수 있습니다.

from bokeh.io import show, output_file
from bokeh.plotting import figure
from bokeh.layouts import gridplot
inc = df.close >= df.open
dec = df.open > df.close
p_candlechart = figure(plot_width=1050, plot_height=200, x_range=(-1, len(df)), tools="crosshair")
p_candlechart.segment(df.index[inc], df.high[inc], df.index[inc], df.low[inc], color="red")
p_candlechart.segment(df.index[dec], df.high[dec], df.index[dec], df.low[dec], color="blue")
p_candlechart.vbar(df.index[inc], 0.5, df.open[inc], df.close[inc], fill_color="red", line_color="red")
p_candlechart.vbar(df.index[dec], 0.5, df.open[dec], df.close[dec], fill_color="blue", line_color="blue")
p_volumechart = figure(plot_width=1050, plot_height=100, x_range=p_candlechart.x_range, tools="crosshair")
p_volumechart.vbar(df.index, 0.5, df.volume, fill_color="black", line_color="black")
p = gridplot([[p_candlechart], [p_volumechart]], toolbar_location=None)
show(p)

chart1

그러나 여기서 각 axis에 표시된 레이블들이 유용한 정보를 주지 못하고 있습니다. 좀 더 보기 좋게 레이블들을 포메팅 해보겠습니다.

p_candlechart.yaxis[0].formatter = NumeralTickFormatter(format='0,0')
p_candlechart.xaxis.visible = False

p_candlechart의 y 레이블을 다음과 같이 천단위로 콤마를 붙인 숫자로 표현합니다.

여기서 x 레이블은 거래량 차트에만 표시하기 위해서 숨기도록 합니다.

p_volumechart의 x 레이블을 날짜로 표현하고 y 레이블을 숫자로 표현합니다.

major_label = {
i: date.strftime('%Y%m%d') for i, date in enumerate(pd.to_datetime(df["date"]))
}
major_label.update({len(df): ''})
p_volumechart.xaxis.major_label_overrides = major_label
p_volumechart.yaxis[0].formatter = NumeralTickFormatter(format='0,0')

x 레이블의 마지막 값은 잘려서 나와서 숨겼습니다. 이제 이 코드들을 추가해서 다시 봉차트를 그려봅니다.

from bokeh.io import show, output_file
from bokeh.plotting import figure
from bokeh.layouts import gridplot
from bokeh.models.formatters import NumeralTickFormatter
inc = df.close >= df.open
dec = df.open > df.close
p_candlechart = figure(plot_width=1050, plot_height=200, x_range=(-1, len(df)), tools="crosshair")
p_candlechart.segment(df.index[inc], df.high[inc], df.index[inc], df.low[inc], color="red")
p_candlechart.segment(df.index[dec], df.high[dec], df.index[dec], df.low[dec], color="blue")
p_candlechart.vbar(df.index[inc], 0.5, df.open[inc], df.close[inc], fill_color="red", line_color="red")
p_candlechart.vbar(df.index[dec], 0.5, df.open[dec], df.close[dec], fill_color="blue", line_color="blue")
p_candlechart.yaxis[0].formatter = NumeralTickFormatter(format='0,0')
p_candlechart.xaxis.visible = False
p_volumechart = figure(plot_width=1050, plot_height=100, x_range=p_candlechart.x_range, tools="crosshair")
p_volumechart.vbar(df.index, 0.5, df.volume, fill_color="black", line_color="black")
major_label = {
i: date.strftime('%Y%m%d') for i, date in enumerate(pd.to_datetime(df["date"]))
}
major_label.update({len(df): ''})
p_volumechart.xaxis.major_label_overrides = major_label
p_volumechart.yaxis[0].formatter = NumeralTickFormatter(format='0,0')
p = gridplot([[p_candlechart], [p_volumechart]], toolbar_location=None)
show(p)

chart2

이렇게 x축은 날짜가, y축은 숫자가 보기좋게 나오게 됩니다. 이 차트를 html, json 등으로 웹에 넣어줄 수 있습니다. 자세한 사항은 bokeh 문서 Embedding Plots and Apps를 확인해 주세요.

Matplotlib으로 봉차트(Candlestick Chart) 그리기

봉차트(Candlestick Chart)는 한국 금융권에서 아주 친숙하게 사용되고 있는 차트 입니다. 하나의 봉(Candle)은 특정 기간 동안에서의 시가, 고가, 저가, 종가 (Open, High, Low, Close; OHLC)를 한번에 표현해 줍니다. 보통 일봉, 즉 하루 동안의 OHLC를 표현한 봉으로 차트를 그립니다.

파이썬에서 봉차트를 그릴 수 있는 도구는 다양하겠지만 대표적인 파이썬 차트 라이브러리인 (Matplotlib)[https://matplotlib.org/index.html]을 활용하여 봉차트를 그리는 법을 이 포스트에서 다루겠습니다.

Matplotlib 1.X 버전에서는 matplotlib.finance 모듈에서 candlestick_ohlc() 함수를 제공하고 있었으나 이 모듈은 2.0 버전부터 Deprecated 되었고, 이 모듈은 따로 (Gihub Repository)[https://github.com/matplotlib/mpl_finance]로 유지되고 있습니다. 설치는 다음 명령으로 할 수 있습니다.

pip install https://github.com/matplotlib/mpl_finance/archive/master.zip

이제 가시화할 데이터를 준비합니다. 다음과 같은 20일치 OHLC와 거래량 데이터를 봉차트로 그려보겠습니다.

dateopenhighlowclosevolume
02018080714250014550014200014500063103
12018080814400014500014150014350049191
22018080914350014400014200014200063800
32018081014200014650014200014600057283
42018081314500014600014100014200051095
52018081414150014600014150014250042579
62018081614150014200013850014100066262
720180817140500141000137000138500100717
82018082013850014000013750013750053507
92018082113750014000013750014000040399
102018082213850014000013850013900034023
112018082313950014000013500013600095824
1220180824135500137000132000134500118459
132018082713500013650013400013500067489
142018082813550013600013300013500066626
152018082913500013600013400013550064548
1620180830135500136000131500132500141496
1720180831132500133500131000132000100410
182018090313250013350013200013200037159
192018090413250013350013150013350035215

이렇게 데이터가 준비되었으니, 차트 레이아웃을 구성하겠습니다.

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(8, 5))
fig.set_facecolor('w')
gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1])
axes = []
axes.append(plt.subplot(gs[0]))
axes.append(plt.subplot(gs[1], sharex=axes[0]))
axes[0].get_xaxis().set_visible(False)

결과는 다음과 두 개의 차트 영역이 위아래에 구성됩니다. 위에는 봉차트를 아래에는 거래량 바차트(Bar Chart)를 그리겠습니다. 위 아래 차트 모두 x 축은 같기 때문에 아래쪽 차트에만 x 축의 틱 레이블(Tick Label)을 표시합니다.

layout

이제 여기에 봉차트와 거래량 차트를 그리면 됩니다. mpl_finance 모듈에서 candlestick_ohlc() 함수를 임포트하고 x 축 데이터를 준비한 다음 봉차트와 거래량 차트를 그립니다.

from mpl_finance import candlestick_ohlc
x = np.arange(len(df.index))
ohlc = df[['open', 'high', 'low', 'close']].astype(int).values
dohlc = np.hstack((np.reshape(x, (-1, 1)), ohlc))
# 봉차트
candlestick_ohlc(axes[0], dohlc, width=0.5, colorup='r', colordown='b')
# 거래량 차트
axes[1].bar(x, df.volume, color='k', width=0.6, align='center')
plt.tight_layout()
plt.show()

이 파이썬 코드의 결과는 다음과 같습니다.

layout

여기서 x 축의 틱 레이블이 0에서 59까지 index 값으로 표시되는데, 일자로 표시되도록 해보겠습니다. 아래와 같이 plt.show() 전에 틱 레이블을 추가합니다. 여기서는 월요일에 해당하는 날짜만 표시하도록 했습니다.

# xlabels에 요일 추가
import datetime
_xticks = []
_xlabels = []
_wd_prev = 0
for _x, d in zip(x, df.date.values):
weekday = datetime.datetime.strptime(str(d), '%Y%m%d').weekday()
if weekday <= _wd_prev:
_xticks.append(_x)
_xlabels.append(datetime.datetime.strptime(str(d), '%Y%m%d').strftime('%m/%d'))
_wd_prev = weekday
axes[1].set_xticks(_xticks)
axes[1].set_xticklabels(_xlabels, rotation=45, minor=False)

이렇게 해주면 아래와 같이 index 값 대신 날짜가 x축 틱의 레이블로 표시됩니다.

layout