Search code examples
pythoncallbackdashboardfinanceplotly-dash

How to properly do a callback and function for Dash web app


Would really appreciate some help regarding how to do a proper callback for my Dash web app. I have the following code below. Its supposed to return a graph with various lines about a stocks' financials. It takes data from an API. But I'm not sure how to define my function for the app callback. I've tried following online tutorials but have had any success. I apologise if its too messy or inefficient, I'm new to this.

'''

#!/usr/bin/env python
import pandas as pd
import requests
import json
import plotly
import chart_studio.plotly as py
import plotly.graph_objs as go
import plotly.express as px
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Output, Input


ticker = input("Insert company ticker: ")
qoy = input("QUARTERLY or YEARLY: ")
if qoy == "Yearly" or qoy == "yearly" or qoy == "YEARLY":
    IS = requests.get("https://financialmodelingprep.com/api/v3/financials/income-statement/" + ticker)
elif qoy == "Quarterly" or qoy == "quarterly" or qoy == "QUARTERLY":
    IS = requests.get(
        "https://financialmodelingprep.com/api/v3/financials/income-statement/" + ticker + "?period=quarter")

IS = IS.json()
IS = IS['financials']
IS = pd.DataFrame.from_dict(IS)
IS = IS.set_index("date")

if qoy == "Yearly" or qoy == "yearly" or qoy == "YEARLY":
    BS = requests.get("https://financialmodelingprep.com/api/v3/financials/balance-sheet-statement/" + ticker)
elif qoy == "Quarterly" or qoy == "quarterly" or qoy == "QUARTERLY":
    BS = requests.get(
        "https://financialmodelingprep.com/api/v3/financials/balance-sheet-statement/" + ticker + "?period=quarter")

BS = BS.json()
BS = BS['financials']
BS = pd.DataFrame.from_dict(BS)
BS = BS.set_index("date")

if qoy == "Yearly" or qoy == "yearly" or qoy == "YEARLY":
    CF = requests.get("https://financialmodelingprep.com/api/v3/financials/cash-flow-statement/" + ticker)
elif qoy == "Quarterly" or qoy == "quarterly" or qoy == "QUARTERLY":
    CF = requests.get(
        "https://financialmodelingprep.com/api/v3/financials/cash-flow-statement/" + ticker + "?period=quarter")

CF = CF.json()
CF = CF['financials']
CF = pd.DataFrame.from_dict(CF)
CF = CF.set_index("date")

df_FS = pd.concat([IS, BS, CF], axis=1, sort=True)
Date = df_FS.index
df_FS.fillna(0, inplace=True)
print(df_FS)

from plotly.subplots import make_subplots

# EARNINGS & REVENUE
fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(go.Scatter(x=Date, y=df_FS['Revenue'],
                         mode='lines+markers',
                         name='Revenue'), secondary_y=False, )
fig.add_trace(go.Bar(x=Date, y=df_FS['Profit Margin'],
                     opacity=0.2,
                     name='Profit Margin'), secondary_y=True, )

fig.add_trace(go.Scatter(x=Date, y=df_FS['Consolidated Income'],
                         mode='lines+markers',
                         name='Earnings'), secondary_y=False, )
fig.add_trace(go.Scatter(x=Date, y=df_FS['Operating Cash Flow'],
                         mode='lines+markers',
                         name='Operating Cash Flow'), secondary_y=False, )
fig.add_trace(go.Scatter(x=Date, y=df_FS['Free Cash Flow'],
                         mode='lines+markers',
                         name='Free Cash Flow'), secondary_y=False, )
fig.add_trace(go.Scatter(x=Date, y=df_FS['Operating Expenses'],
                         mode='lines+markers',
                         name='Operating Expenses'), secondary_y=False, )

fig.update_layout(title="EARNINGS & REVENUE", barmode='group', hovermode='x')
fig.update_yaxes(title_text="in USD", secondary_y=False)
fig.update_yaxes(title_text="Profit Margin", secondary_y=True)
fig.update_xaxes(rangeslider_visible=True)

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    # Input the stock ticker
    html.Div([
        dcc.Input(id="stock-input", value=ticker, type="text"),
        dcc.RadioItems(
            id="quarterlyoryearly",
            options=[
                {'label': 'Quarterly', 'value': 'quarterly'},
                {'label': 'Yearly', 'value': 'yearly'}
            ],
            value=qoy
        )
    ]),


    # Banner of app
    html.Div([
        html.H2("Stock App")
    ], className="banner"),

    # Graphs
    html.Div([

        # Earnings & Revenue Graph
        html.Div([
            dcc.Graph(
                id="Earnings & Revenue",
                figure=fig
            )
        ], className="six columns"),
    ], className="row")

])

app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})


@app.callback(dash.dependencies.Output("Earnings & Revenue","figure"),
              [dash.dependencies.Input("stock-input","value"),
               dash.dependencies.Input("quarterlyoryearly","value")]
              )


#def update_fig(ticker,qoy):


if __name__ == '__main__':
    app.run_server(debug=False)

'''


Solution

  • You have to perform all the data processing in the callback since they require the input and button values. There are various ways to go about this depending on how you want the app to respond to the inputs. One way is this. Here, the main input is qoy and the callback uses the state of the stock input. So entering the stock input will not update the app until you choose qoy.

    #!/usr/bin/env python
    from plotly.subplots import make_subplots
    import pandas as pd
    import requests
    import json
    import plotly
    import chart_studio.plotly as py
    import plotly.graph_objs as go
    import plotly.express as px
    import dash
    import dash_core_components as dcc
    import dash_html_components as html
    from dash.dependencies import Output, Input, State
    from dash.exceptions import PreventUpdate
    
    external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
    
    app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
    
    app.layout = html.Div([
        # Input the stock ticker
        html.Div([
            dcc.Input(id="stock-input",
                      placeholder='Please insert stock', type="text"),
            dcc.RadioItems(
                id="quarterlyoryearly",
                options=[
                    {'label': 'Quarterly', 'value': 'quarterly'},
                    {'label': 'Yearly', 'value': 'yearly'}
                ]
            )
        ]),
    
    
        # Banner of app
        html.Div([
            html.H2("Stock App")
        ], className="banner"),
    
        # Graphs
        html.Div([
    
            # Earnings & Revenue Graph
            html.Div([
                dcc.Graph(
                    id="Earnings & Revenue",
                    # figure=fig
                )
            ], className="six columns"),
        ], className="row")
    
    ])
    
    @app.callback(Output("quarterlyoryearly", "value"),
                  [Input("stock-input", "n_submit")],
                  [State("quarterlyoryearly", "value")])
    def enter_key(n_sub, qoy):
        if n_sub:
            return qoy
    
    @app.callback(dash.dependencies.Output("Earnings & Revenue", "figure"),
                  [dash.dependencies.Input("quarterlyoryearly", "value")],
                  [dash.dependencies.State("stock-input", "value")]
                  )
    def update_fig(*args):
    
        if not any(args):
            raise PreventUpdate
        else:
            qoy, ticker = args
    
            if qoy.lower() == "yearly":
                IS = requests.get(
                    "https://financialmodelingprep.com/api/v3/financials/income-statement/" + ticker)
            elif qoy.lower() == "quarterly":
                IS = requests.get(
                    "https://financialmodelingprep.com/api/v3/financials/income-statement/" + ticker + "?period=quarter")
    
            IS = IS.json()
            IS = IS['financials']
            IS = pd.DataFrame.from_dict(IS)
            IS = IS.set_index("date")
    
            if qoy == "Yearly" or qoy == "yearly" or qoy == "YEARLY":
                BS = requests.get(
                    "https://financialmodelingprep.com/api/v3/financials/balance-sheet-statement/" + ticker)
            elif qoy == "Quarterly" or qoy == "quarterly" or qoy == "QUARTERLY":
                BS = requests.get(
                    "https://financialmodelingprep.com/api/v3/financials/balance-sheet-statement/" + ticker + "?period=quarter")
    
            BS = BS.json()
            BS = BS['financials']
            BS = pd.DataFrame.from_dict(BS)
            BS = BS.set_index("date")
    
            if qoy == "Yearly" or qoy == "yearly" or qoy == "YEARLY":
                CF = requests.get(
                    "https://financialmodelingprep.com/api/v3/financials/cash-flow-statement/" + ticker)
            elif qoy == "Quarterly" or qoy == "quarterly" or qoy == "QUARTERLY":
                CF = requests.get(
                    "https://financialmodelingprep.com/api/v3/financials/cash-flow-statement/" + ticker + "?period=quarter")
    
            CF = CF.json()
            CF = CF['financials']
            CF = pd.DataFrame.from_dict(CF)
            CF = CF.set_index("date")
    
            df_FS = pd.concat([IS, BS, CF], axis=1, sort=True)
            Date = df_FS.index
            df_FS.fillna(0, inplace=True)
    
            # EARNINGS & REVENUE
            fig = make_subplots(specs=[[{"secondary_y": True}]])
            fig.add_trace(go.Scatter(x=Date, y=df_FS['Revenue'],
                                     mode='lines+markers',
                                     name='Revenue'), secondary_y=False, )
            fig.add_trace(go.Bar(x=Date, y=df_FS['Profit Margin'],
                                 opacity=0.2,
                                 name='Profit Margin'), secondary_y=True, )
    
            fig.add_trace(go.Scatter(x=Date, y=df_FS['Consolidated Income'],
                                     mode='lines+markers',
                                     name='Earnings'), secondary_y=False, )
            fig.add_trace(go.Scatter(x=Date, y=df_FS['Operating Cash Flow'],
                                     mode='lines+markers',
                                     name='Operating Cash Flow'), secondary_y=False, )
            fig.add_trace(go.Scatter(x=Date, y=df_FS['Free Cash Flow'],
                                     mode='lines+markers',
                                     name='Free Cash Flow'), secondary_y=False, )
            fig.add_trace(go.Scatter(x=Date, y=df_FS['Operating Expenses'],
                                     mode='lines+markers',
                                     name='Operating Expenses'), secondary_y=False, )
    
            fig.update_layout(title="EARNINGS & REVENUE",
                              barmode='group', hovermode='x')
            fig.update_yaxes(title_text="in USD", secondary_y=False)
            fig.update_yaxes(title_text="Profit Margin", secondary_y=True)
            fig.update_xaxes(rangeslider_visible=True)
            return fig
    
    
    if __name__ == '__main__':
        app.run_server(debug=False)
    

    You can play around around with the input and state options and see how the app responds. Cheers!

    PS: I added another callback so after the first qoy and ticker values, you can simply change ticker in the input field and hit the enter button (since qoy would have already been selected) to update your app. Hopefully this will give you more insight about how callbacks work.