오늘의 예제: 캔들스틱 그림 그리기
이번에는 저번 시간에 읽은 가격 자료를 이용하여 Candle Stick 차트를 그려보도록 하겠습니다.
인터넷에서 검색해보니, 파이쏜의 그림그리는 모듈인 Matplotlib에서 Candle Stick을 그릴 수 있는 하부 모듈이 있다고 하는데, 문제는 그 모듈이 관리도 안되고 곧 없어진다 그래서 그거 안쓰고 그냥 제가 직접 그렸어요 ^^
Matplotlib에 대한 기본적인 사항은 예전에 제가 작성했던 글들 (목록은 밑에 있어요)을 참조하시기 바랍니다.
일단 코드부터 보시죠.
read_txt+plot_candle.py3.py
import sys
import numpy as np
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.ticker import AutoMinorLocator
def conv2date(mon_str,day_str,year_str):
"""
mon_str: Month Name Abbre. (3-letters)
day_str: Need to remove the last comma
year_str: 4-digit number
"""
return datetime.strptime(mon_str+day_str+year_str,'%b%d,%Y')
def draw_candlesticks(ax1,dates,prices):
### Increasing Price
pinc_idx= prices[3,:]>=prices[0,:]
pinc_prices=prices[:,pinc_idx]
pinc_dates=dates[pinc_idx]
#print(pinc_prices[0,:],pinc_prices[1,:])
pic1 = ax1.bar(pinc_dates,pinc_prices[3,:]-pinc_prices[0,:],width=width,bottom=pinc_prices[0,:],color='w',edgecolor=cc[0],linewidth=max(0.1,1/max(nt/80,0.8)))
pic1a= ax1.bar(pinc_dates,pinc_prices[1,:]-pinc_prices[3,:],width=width2,bottom=pinc_prices[3,:],color=cc[0],edgecolor=None)
pic1b= ax1.bar(pinc_dates,pinc_prices[0,:]-pinc_prices[2,:],width=width2,bottom=pinc_prices[2,:],color=cc[0],edgecolor=None)
### Decreasing Price
pdec_idx= np.logical_not(pinc_idx)
pdec_dates=dates[pdec_idx]; pdec_prices=prices[:,pdec_idx]
#print(pdec_prices[0,:],pdec_prices[1,:])
pic2 = ax1.bar(pdec_dates,pdec_prices[0,:]-pdec_prices[3,:],width=width,bottom=pdec_prices[3,:],color=cc[1],edgecolor=None)
pic2a= ax1.bar(pdec_dates,pdec_prices[1,:]-pdec_prices[0,:],width=width2,bottom=pdec_prices[0,:],color=cc[1],edgecolor=None)
pic2b= ax1.bar(pdec_dates,pdec_prices[3,:]-pdec_prices[2,:],width=width2,bottom=pdec_prices[2,:],color=cc[1],edgecolor=None)
def draw_candlesticks_misc(ax1,dates,datebuffer0=-5,datebuffer1=30):
### Decoration
ax1.xaxis_date()
myFmt = mdates.DateFormatter('%b\n%Y')
ax1.xaxis.set_major_formatter(myFmt)
ax1.set_xlim(dates[0]+timedelta(days=datebuffer0),dates[-1]+timedelta(days=datebuffer1))
ax1.set_ylim(pmin*0.8,pmax*1.1)
ax1.yaxis.set_minor_locator(AutoMinorLocator(2))
ax1.yaxis.set_ticks_position('both')
ax1.grid(which='major',axis='y',color='silver',lw=0.5,ls=':')
###--- Main ---###
## Parameters
c_name="BTC"
## Input File
in_dir='./'
in_txt_fn=in_dir+c_name+'_daily_20130428-20190715.txt'
## Read Text File
dates=[]; prices=[]
with open(in_txt_fn,'r') as f:
for i,line in enumerate(f):
if i==0:
print(line)
else:
words=line.strip().split()
if i==1:
num=len(words)
print(num,words)
elif num!=len(words):
print(num,len(words),words)
sys.exit()
dates.append(conv2date(*words[:3]))
price_tmp=[]
for ww in words[3:7]:
if "," in ww:
ww=ww.replace(",","")
price_tmp.append(float(ww))
prices.append(price_tmp)
print(dates[-1],prices[-1])
## Convert list to numpy array
prices=np.asarray(prices).T
print(prices.shape) ### Now should be [4,time_steps]
## Make time reversed (old to new)
prices=prices[:,::-1]
dates=dates[::-1]
print(prices[:,0],dates[0])
ini_date= dates[0]
tgt_dates= [datetime(2018,10,1),dates[-1]] ### Target Dates to draw chart
it= (tgt_dates[0]-ini_date).days
nt= (tgt_dates[1]-tgt_dates[0]).days+1
prices= prices[:,it:it+nt]
dates= np.array(dates[it:it+nt])
pmin,pmax= prices.min(),prices.max()
print(pmin,pmax)
###--- Plotting
##-- Page Setup --##
fig = plt.figure()
fig.set_size_inches(8.5,5) # Physical page size in inches, (lx,ly)
fig.subplots_adjust(left=0.05,right=0.95,top=0.92,bottom=0.05,hspace=0.35,wspace=0.15) ### Margins, etc.
##-- Title for the page --##
suptit="{} Price Timeseries".format(c_name)
fig.suptitle(suptit,fontsize=15) #,ha='left',x=0.,y=0.98,stretch='semi-condensed')
width=0.8; width2=width/10
cc=['g', 'darkorange']
##-- Set up an axis --##
ax1 = fig.add_subplot(1,1,1) # (# of rows, # of columns, indicater from 1)
draw_candlesticks(ax1,dates,prices)
draw_candlesticks_misc(ax1,dates)
##-- Seeing or Saving Pic --##
#- If want to see on screen -#
#plt.show()
#- If want to save to file
outdir = "./Pics/"
outfnm = outdir+"{}_price_ex1.png".format(c_name)
#fig.savefig(outfnm,dpi=100) # dpi: pixels per inch
fig.savefig(outfnm,dpi=180,bbox_inches='tight') # dpi: pixels per inch
벌써 코드가 상당히 길어졌는데요, 사실 절반 정도는 저번 시간에 다룬, 텍스트 파일에서 데이타를 읽어오는 부분입니다. 이번에 추가된 부분은,
- 읽어들인 데이타를 Numpy Array로 변환
- 시가와 종가를 비교하여 상승하는 날과 하락하는 날로 구분
- 상승과 하락 각각에 맞는 그래프를 그림 (색이 다르니까요)
이렇게 되어있습니다. 별로 어렵지 않죠? ^^
몇 가지 설명
- 저번 시간에 다뤘던 함수 "conv2date"에 작은 수정이 있었습니다. 생각해보니 굳이 ","를 제거할 필요 없겠더라구요.
- Numpy Array의 경우, 크기가 [nn,mm,ll]로 정의되었을 경우 가장 오른쪽 축(axis), 즉 ll 부분이 가장 먼저 저장되고, ll이 한바퀴 돌면 그 다음에 mm이 바뀌고 하는 순으로 자료가 저장됩니다.
- Numpy Array의 경우 다양한 Slicing이 가능합니다. 예를 들어 어떤 한 축에 대하여 [10:20:3]라고 되어있으면, 10번 원소부터 20번 전 원소까지 3개마다 하나씩이라는 뜻입니다. [::3]이라고 숫자가 생략되면 처음부터 끝까지 3개당 하나씩이겠죠. 그리고 저 위에서 [::-1]이라는 표현이 나오는데, 이건 순서를 뒤집는다는 뜻입니다.
- Numpy Array에서 많이 쓰는 방법 중 하나가 "조건부 인덱싱"입니다. 상승날과 하락날을 구분하는 부분 (함수 draw_candlesticks)을 보시면, "C= A>=B" 이런 부분이 보이는데, 이것은 각각 원소에 대하여 A의 원소가 B의 원소보다 크면 True 아니면 False가 저장된다는 뜻입니다. 당연히 A와 B의 크기가 같아야겠죠.
- True와 False는 각각이 사실 1과 0으로 저장됩니다. 그래서 위의 C에 대하여 C.sum()을 구하면 True가 몇 개 인지 셀 수가 있죠.
- T/F로 구성된 C가 준비되면, D=A[C] 이런 형태가 가능합니다. 이렇게 되면 D에는 C가 True일 때의 A값만 들어가게 되죠.
- 그림을 그릴 때, "fig"를 정의하고 그 다음에 "ax"를 정의합니다. 쉽게 생각해 "fig" 정의는 종이를 준비하는 거고, "ax"의 정의는 종이 위해 실제 그림 그릴 부분을 설정한다고 생각하시면 됩니다.
- Candle Stick 차트를 위해 저는 "Bar plot (막대그래프)"를 이용했습니다. 막대그래프는 각 막대 상자의 바닥값과 그 높이, 그리고 주어진 넓이 값으로 정해집니다.
- 막대 상자 안이 차 있는 경우에는 상관 없었는데, 빈 경우 테두리의 선 굵기가 문제였어요. 50일 그리는 경우와 300일 그리는 경우 막대 상자의 폭이 실제로 보이기에 차이가 큰데, 선 굵기가 일정하면 나중에는 선 굵기가 막대 상자 넓이보다 더 커져버리는 일이 발생했거든요.
- 현재 그림 그리는 기간은 "tgt_dates"라는 변수를 이용해 통제하고 있습니다.
일단은 이정도면 되려나요.
제가 너무 익숙해서 지나가버렸지만, 초보자에겐 어려운 부분이 있을거에요. 댓글로 질문 주시길!
다음 시간에는 이동 평균 Moving Average를 계산해서 그림에 추가하도록 하겠습니다.

관련 글들
Matplotlib List
[Matplotlib] 00. Intro + 01. Page Setup
[Matplotlib] 02. Axes Setup: Subplots
[Matplotlib] 03. Axes Setup: Text, Label, and Annotation
[Matplotlib] 04. Axes Setup: Ticks and Tick Labels
[Matplotlib] 05. Plot Accessories: Grid and Supporting Lines
[Matplotlib] 06. Plot Accessories: Legend
[Matplotlib] 07. Plot Main: Plot
[Matplotlib] 08. Plot Main: Imshow
[Matplotlib] 09. Plot Accessary: Color Map (part1)
[Matplotlib] 10. Plot Accessary: Color Map (part2) + Color Bar
F2PY List
[F2PY] 01. Basic Example: Simple Gaussian 2D Filter
[F2PY] 02. Basic Example: Moving Average
[F2PY] 03. Advanced Example: Using OpenMP
Scipy+Numpy List
[SciPy] 1. Linear Regression (Application to Scatter Plot)
[SciPy] 2. Density Estimation (Application to Scatter Plot)
[Scipy+Numpy] 3. 2D Histogram + [Matplotlib] 11. Plot Main: Pcolormesh
Road to Finance
#00 Read Text File
#01 Draw CandleSticks