Search code examples
pythonplotlyplotly-pythoncandlestick-chart

Plotly Python update figure with dropMenu


i am currently working with plotly i have a function called plotChart that takes a dataframe as input and plots a candlestick chart. I am trying to figure out a way to pass a list of dataframes to the function plotChart and use a plotly dropdown menu to show the options on the input list by the stock name. The drop down menu will have the list of dataframe and when an option is clicked on it will update the figure in plotly is there away to do this. below is the code i have to plot a single dataframe

def make_multi_plot(df):
    
    fig = make_subplots(rows=2, cols=2,
                        shared_xaxes=True,
                        vertical_spacing=0.03,
                        subplot_titles=('OHLC', 'Volume Profile'),
                        row_width=[0.2, 0.7])

    for s in df.name.unique():
        
        trace1 = go.Candlestick(
            x=df.loc[df.name.isin([s])].time,
            open=df.loc[df.name.isin([s])].open,
            high=df.loc[df.name.isin([s])].high,
            low=df.loc[df.name.isin([s])].low,
            close=df.loc[df.name.isin([s])].close,
            name = s)
        fig.append_trace(trace1,1,1)
        
        fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].BbandsMid, mode='lines',name='MidBollinger'),1,1)
        fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].BbandsUpp, mode='lines',name='UpperBollinger'),1,1)
        fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].BbandsLow, mode='lines',name='LowerBollinger'),1,1)
        fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].vwap, mode='lines',name='VWAP'),1,1)
        fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].STDEV_1, mode='lines',name='UPPERVWAP'),1,1)
        fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].STDEV_N1, mode='lines',name='LOWERVWAP'),1,1)
        fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].KcMid, mode='lines',name='KcMid'),1,1)
        fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].KcUpper, mode='lines',name='KcUpper'),1,1)
        fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].KcLow, mode='lines',name='KcLow'),1,1)
        

        trace2 = go.Bar(
                x=df.loc[df.name.isin([s])].time,
                y=df.loc[df.name.isin([s])].volume,
                name = s)
        fig.append_trace(trace2,2,1)
        # fig.update_layout(title_text=s)
        
        
        
    graph_cnt=len(fig.data)

        
    tr = 11
    symbol_cnt =len(df.name.unique())
    for g in range(tr, graph_cnt):
        fig.update_traces(visible=False, selector=g)
        #print(g)
    def create_layout_button(k, symbol):
        
        start, end = tr*k, tr*k+2
        visibility = [False]*tr*symbol_cnt
        visibility[start:end] = [True,True,True,True,True,True,True,True,True,True,True]
        return dict(label = symbol,
                    method = 'restyle',
                    args = [{'visible': visibility[:-1],
                             'title': symbol,
                             'showlegend': False}])    
    
    fig.update(layout_xaxis_rangeslider_visible=False)
    fig.update_layout(
        updatemenus=[go.layout.Updatemenu(
            active = 0,
            buttons = [create_layout_button(k, s) for k, s in enumerate(df.name.unique())]
            )
        ])
    
    fig.show()

i am trying to add annotations to the figure it will be different for each chart below is how i had it setup for the single chart df['superTrend'] is a Boolean column

for i in range(df.first_valid_index()+1,len(df.index)):
        prev = i - 1
        if df['superTrend'][i] != df['superTrend'][prev] and not np.isnan(df['superTrend'][i]) :
            #print(i,df['inUptrend'][i])
            fig.add_annotation(x=df['time'][i], y=df['open'][i],
            text= 'Buy' if df['superTrend'][i] else 'Sell',
            showarrow=True,
            arrowhead=6,
            font=dict(
                #family="Courier New, monospace",
                size=20,
                #color="#ffffff"
            ),)

Solution

  • I adapted an example from the plotly community to your example and created the code. The point of creation is to create the data for each subplot and then switch between them by means of buttons. The sample data is created using representative companies of US stocks. one issue is that the title is set but not displayed. We are currently investigating this issue.

    import yfinance as yf
    import plotly.graph_objects as go
    from plotly.subplots import make_subplots
    import pandas as pd
    
    symbols = ['AAPL','GOOG','TSLA']
    stocks = pd.DataFrame()
    for s in symbols:
        data = yf.download(s, start="2021-01-01", end="2021-12-31")
        data['mean'] = data['Close'].rolling(20).mean()
        data['std'] = data['Close'].rolling(20).std()
        data['upperBand'] = data['mean'] + (data['std'] * 2)
        data.reset_index(inplace=True)
        data['symbol'] = s
        stocks = stocks.append(data, ignore_index=True)
    
    def make_multi_plot(df):
        
        fig = make_subplots(rows=2, cols=1,
                            shared_xaxes=True,
                            vertical_spacing=0.03,
                            subplot_titles=('OHLC', 'Volume Profile'),
                            row_width=[0.2, 0.7])
    
        for s in df.symbol.unique():
            trace1 = go.Candlestick(
                x=df.loc[df.symbol.isin([s])].Date,
                open=df.loc[df.symbol.isin([s])].Open,
                high=df.loc[df.symbol.isin([s])].High,
                low=df.loc[df.symbol.isin([s])].Low,
                close=df.loc[df.symbol.isin([s])].Close,
                name=s)
            fig.append_trace(trace1,1,1)
            
            trace2 = go.Scatter(
                x=df.loc[df.symbol.isin([s])].Date,
                y=df.loc[df.symbol.isin([s])].upperBand,
                name=s)
            fig.append_trace(trace2,1,1)
            
            trace3 = go.Bar(
                x=df.loc[df.symbol.isin([s])].Date,
                y=df.loc[df.symbol.isin([s])].Volume,
                name=s)
            fig.append_trace(trace3,2,1)
            # fig.update_layout(title_text=s)
        
        # Calculate the total number of graphs
        graph_cnt=len(fig.data)
        # Number of Symbols
        symbol_cnt =len(df.symbol.unique())
        # Number of graphs per symbol
        tr = 3
        # Hide setting for initial display
        for g in range(tr, graph_cnt): 
            fig.update_traces(visible=False, selector=g)
    
        def create_layout_button(k, symbol):
            start, end = tr*k, tr*k+2
            visibility = [False]*tr*symbol_cnt
            # Number of graphs per symbol, so if you add a graph, add True.
            visibility[start:end] = [True,True,True]
            return dict(label = symbol,
                        method = 'restyle',
                        args = [{'visible': visibility[:-1],
                                 'title': symbol,
                                 'showlegend': True}])    
        
        fig.update(layout_xaxis_rangeslider_visible=False)
        fig.update_layout(
            updatemenus=[go.layout.Updatemenu(
                active = 0,
                buttons = [create_layout_button(k, s) for k, s in enumerate(df.symbol.unique())]
                )
            ])
        
        fig.show()
        return fig.layout
        
    make_multi_plot(stocks)
    

    enter image description here

    enter image description here

    enter image description here