공부/퀀트

파이썬 캔들 차트 구현

민수 2022. 1. 7. 17:44

주가 분석 연구를 하게 되면 차트를 통해 시각화해야하는 경우가 많다.  

 

그러나 캔들 차트는 구현이 까다로워서 대중적으로 사용되지는 않았었는데, FinaceDataReader 라이브러리에

차트 기능이 추가되어 간편하게 차트 시각화가 가능해졌다. 

 

FinanceDataReader를 통해 시각화한 차트 이미지 1

 

FinanceDataReader를 통해 시각화한 차트 이미지 2

직접 사용해 본 결과, 코드 한줄로도 간단한 캔들 차트가 완성되어 무척 신기했다. 그러나 각 캔들의 정확한 날짜를

 

알기 위해서는 줌 인 기능을 통해 엄청 크게 확대한 후 눈으로 짐작해야해서 매우 불편했다.  

 

따라서 가격 캔들과 거래량 봉에 마우스를 갖다대면 날짜와 가격을 알 수 있도록 코드를 커스터마이징 했다. 

 

코드는 다음과 같으며 깃허브에도 공유할 예정이다.   

 

사용하기 위해서는 FDR의 chart.plot 함수를 재정의 하거나 아래 코드의 함수를 자신만의 이름으로 바꿔서 사용하면 된다.  

 

def get_width(df):
    mindate = min(df.index)
    maxdate = max(df.index) 
    width = 0.8 #vbar의 가로폭 0.8 로 설정
    return width * (maxdate-mindate).total_seconds()*1000 / len(df.index) 

def chart_plot(df,start=None,end=None,volume=False,ma_lines=[5,20,60]): 
     try:
        from bokeh.plotting import figure, gridplot
        from bokeh.models import NumeralTickFormatter, DatetimeTickFormatter, Span
        from bokeh.io import output_notebook, show, export_png
        from bokeh.palettes import d3
    except ModuleNotFoundError as e:
        raise ModuleNotFoundError(bokeh_install_msg)
        
    for n in ma_lines: # 이동평균 계산
        df[f'MA_{n}'] = df.Close.rolling(window=n).mean() 
    inc = df.Close > df.Open
    dec = df.Open > df.Close 
    ############## 
    df['date_'] = df.index
    # plot price OHLC candles
    x = np.arange(len(df)) 
    width = 940
    height = 680 # 그래프 전체 높이 설정 
    if volume == True:
        height = int(height - height * 0.3) # 거래량 비중 0.3
    TOOLTIPS = [
        ("index", "$index"),
        ("y", "$y{0.0})"),
        ("date", "$x{%F}"),
    ]
    pp = figure(plot_width=width, 
                plot_height=height,
                x_range=Range1d(start=df.index[0], end=df.index[-1]),
                y_range=(df.Low.min(), df.High.max()),
                title='',
                y_axis_label='',
                tools=[PanTool(), WheelZoomTool(), BoxZoomTool(),SaveTool(), ResetTool(), HelpTool(), 
                       HoverTool(formatters={'$x':'datetime'})],
                tooltips=TOOLTIPS)                 
    
    
    pp.segment(df.index[inc], df.High[inc], df.index[inc], df.Low[inc], color='red')
    pp.segment(df.index[dec], df.High[dec], df.index[dec], df.Low[dec], color='blue')
    pp.vbar(df.index[inc], get_width(df), df.Open[inc], df.Close[inc], fill_color='red', line_color='red')
    pp.vbar(df.index[dec], get_width(df), df.Open[dec], df.Close[dec], fill_color='blue', line_color='blue')
    pp.yaxis[0].formatter = NumeralTickFormatter(format='0,0')
        
    if volume == True:
        pp.xaxis.visible = False
    else:
        x_labels = {i: dt.strftime('%Y-%m-%d') for i,dt in enumerate(df.index)}
        x_labels.update({len(df): ''})
        pp.xaxis.major_label_overrides = x_labels
        pp.xaxis.formatter=DatetimeTickFormatter(hours=["%H:%M"], days=["%Y-%m-%d"])
        pp.xaxis.major_label_orientation = np.pi / 5
    
    source = ColumnDataSource(data = {i:df[i] for i in df.columns})
    for ix,n in enumerate(ma_lines):
        pal = d3['Category10'][10]
        pp.line(df.index, df[f'MA_{n}'], line_color=pal[ix % len(pal)],legend_label=f'MA_{n}') 
    # plot volume
    if volume == True:
        inc = df.Volume.diff() >= 0
        dec = df.Volume.diff() < 0
        height = int(height * 0.3)
        pv = figure(plot_width= width, plot_height=height, x_range = pp.x_range,
                   tools= [HoverTool(formatters={'$x':'datetime'})],tooltips=TOOLTIPS)

        pv.vbar(df.index[inc], get_width(df), df.Volume[inc], fill_color='red', line_color="black")
        pv.vbar(df.index[dec], get_width(df), df.Volume[dec], fill_color='blue', line_color="black")

        pv.yaxis[0].formatter = NumeralTickFormatter(format='0,0')
        x_labels = {i: dt.strftime('%Y-%m-%d') for i,dt in enumerate(df.index)}
        x_labels.update({len(df): ''})
        pv.xaxis.major_label_overrides = x_labels
        pv.xaxis.formatter=DatetimeTickFormatter(hours=["%H:%M"], days=["%Y-%m-%d"])
        pv.xaxis.major_label_orientation = np.pi / 5
        pv.y_range.range_padding = 0

        # 가격(pp)과 거래량(pv) 함께 그리기
        p = gridplot([[pp], [pv]])
    else:
        p = gridplot([[pp]])
    
    output_notebook()
    show(p)

 

코드 수정 후 차트 이미지

수정한 코드를 통해서 함수를 실행하면 캔들에 마우스를 갖다 댈 경우 해당 캔들의 날짜가 보이도록 하였고, 

 

차트에 거래량을 함께 볼지 말지, 이동평균으로 쓰고싶은 값을 한번에 입력받아 표시하도록 수정하였다.