First thing first, we need to download data from Yahoo Finance


# import libraries
import yfinance as yf
import pandas as pd
import datetime

# Download all stock price historical data from yahoo finance
spy_data = yf.download("SPY")
aapl_data = yf.download("AAPL")
goog_data = yf.download("GOOG")
tsla_data = yf.download("TSLA")
amzn_data = yf.download("AMZN")
meta_data = yf.download("META")
nvda_data = yf.download("NVDA")

# Reset index to turn date to a separate column
spy_data.reset_index(inplace=True)
aapl_data.reset_index(inplace=True)
goog_data.reset_index(inplace=True)
tsla_data.reset_index(inplace=True)
amzn_data.reset_index(inplace=True)
meta_data.reset_index(inplace=True)
nvda_data.reset_index(inplace=True)

# Add a column for ticker symbol
spy_data['Ticker']  = 'SPY'
aapl_data['Ticker'] = 'AAPL'
goog_data['Ticker'] = 'GOOG'
tsla_data['Ticker'] = 'TSLA'
amzn_data['Ticker'] = 'AMZN'
meta_data['Ticker'] = 'META'
nvda_data['Ticker'] = 'NVDA'

# Put all stocks into a consolidated data frame
stock_all = pd.concat([spy_data, aapl_data, goog_data, tsla_data, amzn_data, meta_data, nvda_data])

# Restrict data to work with to only dates after specific year
stock_all['Date'] = pd.to_datetime(stock_all['Date'])
df = stock_all[stock_all['Date'] >= '2023-01-01']

# preview data structure
df.info()

First, let’s import libraries


from bokeh.layouts import layout, column, row
from bokeh.models import ColumnDataSource, RangeSlider, DateRangeSlider, HoverTool, CDSView, BooleanFilter, CustomJS, Dropdown, Select, Div
from bokeh.plotting import figure, output_notebook, show
from bokeh.io import output_notebook, show
output_notebook()

1. Create candle stick chart with Toolbox


# Create Candle Stick Chart for one ticker
ticker = 'TSLA'
df_in_use = df[df['Ticker'] == ticker]

# Create candle chart
increase_logic = df_in_use['Close'] > df_in_use['Open']
decrease_logic = df_in_use['Open'] > df_in_use['Close']

# Width is 1 day
w = 24*60*60*1000 # 1 day in ms

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

p = figure(x_axis_type="datetime", tools=TOOLS, width=1000, title = f'Candlestick {ticker}', sizing_mode = "stretch_width")

p.grid.grid_line_alpha=0.3

p.segment(x0 = df_in_use['Date'], y0 = df_in_use['High'], x1 = df_in_use['Date'], y1 = df_in_use['Low'], color="black")

p.vbar(x = df_in_use['Date'][increase_logic],  
       top = df_in_use['Open'][increase_logic], 
       bottom = df_in_use['Close'][increase_logic], 
       width = w, fill_color="green", line_color ="green")

p.vbar(x = df_in_use['Date'][decrease_logic],  
       top = df_in_use['Open'][decrease_logic], 
       bottom = df_in_use['Close'][decrease_logic], 
       width = w, fill_color="red", line_color="red")

show(p)

Candlestick chart for TSLA shows up as expected

Now the chart has 2 extra interactive features:

  • Timeline can be adjusted using Time Slider at the bottom

  • Different stock ticker can be selected from the dropdown list in the upper left corner

  • Here we can see that the chart automatically updates when I choose GOOG instead of TSLA in the Stock Ticker dropdown list

  • Timeline range is narrowed to June 2023 - Jan 2024

  • When hovering the mouse over the specific candlestick, specific details of stock price on that specific date are shwon

2. Create Interactive Candle Stick Charts with Dropdown Selection


# Define date range for graph
start_date = pd.to_datetime('2023-01-01')
end_date   = datetime.datetime.now()
date_rangeX = pd.date_range(start = start_date, end = end_date)

# Create lists for ticker
ticker_list = ['SPY', 'AAPL', 'AMZN', 'GOOG', 'META', 'NVDA', 'TSLA']

# Start with one ticker
ticker = 'TSLA'
df_in_use = df[df['Ticker'] == ticker]
df_in_use_green = df_in_use[df_in_use['Close'] >= df_in_use['Open']]
df_in_use_red = df_in_use[df_in_use['Close'] < df_in_use['Open']]

# Create a ColumnDataSource containing only the current ticker
#source = ColumnDataSource(df_in_use)
source_green = ColumnDataSource(df_in_use_green)
source_red = ColumnDataSource(df_in_use_red)

# Create a ColumnDataSource containing all data for filtering
source_all = ColumnDataSource(df)

# Width is 1 day
w = 24*60*60*1000 # 1 day in ms

# Create candlelight chart for df_in_use
graph = figure(x_axis_type  = "datetime", 
               title        = "Candlestick Price Chart (Open-High-Low-Close)",
               x_axis_label = 'Date', 
               y_axis_label = 'Price (USD)',
               sizing_mode  = "stretch_width",
               height = 500)

# Make grid line more transparent
graph.grid.grid_line_alpha=0.3

# Add segment and vertical bars for each data row
graph.segment(source=source_green, x0 ='Date', y0='High', x1='Date', y1='Low', color="green")
graph.segment(source=source_red, x0 ='Date', y0='High', x1='Date', y1='Low', color="red")

graph.vbar(source=source_green,
           x = 'Date',  
           top = 'Open', 
           bottom = 'Close', 
           width = w, fill_color="green", line_color ="green")

graph.vbar(source=source_red,
           x = 'Date',  
           top = 'Open', 
           bottom = 'Close',
           width = w, fill_color="red", line_color="red")

# Make font size of title bigger
graph.title.text_font_size = '20pt'
graph.title.align = "center"

# Set up RangeSlider for timeline
timeline_slider = DateRangeSlider(  title = "Adjust timeline (x-axis) range",
                                    start = date_rangeX.min(),
                                    end   = date_rangeX.max(),
                                    step  = 1,
                                    value = (date_rangeX.min(), date_rangeX.max()),
                                    sizing_mode = "stretch_width")

# Add callback code
timeline_slider.js_link("value", graph.x_range, "start", attr_selector=0)
timeline_slider.js_link("value", graph.x_range, "end", attr_selector=1)

# Define Hover Tool
hover_tool = HoverTool( tooltips=[   #('Ticker', '@Ticker'),  
                                     ('Date',   '@Date{%F}'),
                                     ('Open',  '@Open{$0.00}'),
                                     ('High',  '@High{$0.00}'),
                                     ('Low',   '@Low{$0.00}'),
                                     ('Close', '@Close{$0.00}') ],
                        formatters = {'@Date': 'datetime',    }    )

# Add hover_tool to graph
graph.add_tools(hover_tool)

# Create single Selection widget
select = Select(title = 'Stock Ticker', 
                value = 'TSLA',
                options = ticker_list,
                width = 100,
                #margin = (5, 5, 5, 80)
               )

# Create a callback function to kick through changes when a new ticker is chosen
callback = CustomJS(
    args = dict(source_green = source_green, source_red = source_red, source_all = source_all, select = select),
    code ="""
        // get the value from the dropdown
        var ticker_name = select.value;
        
        // filter the full data set to include only those from that ticker
        var data_all = source_all.data;
        var data_green = source_green.data;
        var data_red = source_red.data;

        data_green['Date'] = [];
        data_green['Open'] = [];
        data_green['High'] = [];
        data_green['Low'] = [];
        data_green['Close'] = [];
        
        data_red['Date'] = [];
        data_red['Open'] = [];
        data_red['High'] = [];
        data_red['Low'] = [];
        data_red['Close'] = [];

        for (var i = 0; i < data_all['Ticker'].length; i++) {
            if (data_all['Ticker'][i] == ticker_name) {
                if (data_all['Close'][i] >= data_all['Open'][i]) {
                    data_green['Date'].push(data_all['Date'][i]);
                    data_green['Open'].push(data_all['Open'][i]);
                    data_green['High'].push(data_all['High'][i]);
                    data_green['Low'].push(data_all['Low'][i]);
                    data_green['Close'].push(data_all['Close'][i]);
                } else {
                    data_red['Date'].push(data_all['Date'][i]);
                    data_red['Open'].push(data_all['Open'][i]);
                    data_red['High'].push(data_all['High'][i]);
                    data_red['Low'].push(data_all['Low'][i]);
                    data_red['Close'].push(data_all['Close'][i]);
                }
            }
        }
        
        // commit changes
        source_green.change.emit();
        source_red.change.emit();
    """)

# Call function on change of selection in drop down list                
select.js_on_change("value", callback)

# Create a separate callback to update the Hover Tool
hover_callback = CustomJS(
    args=dict(hover_tool=hover_tool, select=select),
    code="""
        // get the value from the dropdown
        var ticker_name = select.value;

        // Update the ticker name in the Hover Tool
        hover_tool.tooltips[0][1] = ticker_name;
        hover_tool.update();
    """
)

# Attach the hover_callback to the graph
graph.js_on_change("renderers", hover_callback)

# create layout
layout = column([select,  graph,   timeline_slider],
                sizing_mode = 'stretch_width')

# show result
show(layout)

Notebook in my github repository:

https://github.com/ExcellentBee/Learning-Everyday/blob/main/Candlestick%20Stock%20Chart%20Using%20Bokeh.ipynb

Previous
Previous

Dynamic Selection Charts using Bokeh