DEV Community

KartikJha-prod
KartikJha-prod

Posted on Edited on

📊 How to build a Financial Dashboard on Python step-by-step

Have you ever wanted to look up a stock's performance on a specific date, only to find out the market was closed that day because of a weekend or a holiday? In this tutorial, we will build a program that solves this problem using Python, Plotly and Dash.

Instead of only telling us the price for one date, this program starts a local web server and fetches historical prices from Yahoo Finance using the yfinance module. It takes the ticker symbol and date chosen by the user, opens a window of dates around the target date, handles market closures by showing the nearest trading days, and draws an interactive candlestick chart.

Note: yfinance is an unofficial library and this project is for learning purposes only. It is not financial advice.


💼 Project features and result

By the end of this guide, you will be able to build a program that does the following:

  • ➔ Historical data extraction: It connects to Yahoo Finance through the yfinance module to fetch the Open, High, Low and Close prices.
  • ➔ Relevant window: It creates a window of dates (10 days before and 5 days after your target date), which is useful for studying trends.
  • ➔ Market closure alerts: It checks whether the stock exchange was open on your target date. If the market was closed, it alerts the user and shows the nearest trading days instead.
  • ➔ Runs in different environments: It runs from a normal terminal (you open the dashboard in your browser) and also inline inside Jupyter and Google Colab notebooks.

📦 Step 1: All the imports and dependencies

First, install the libraries by running this line in your terminal (in a Jupyter or Colab notebook, put ! in front of it):

pip install dash pandas plotly yfinance
Enter fullscreen mode Exit fullscreen mode

Then create a new file named app.py and add these imports at the top. They bring in the libraries that manage our layout, data and graph.

import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State
import plotly.graph_objects as go
from datetime import datetime, timedelta
import yfinance as yf
import pandas as pd
Enter fullscreen mode Exit fullscreen mode

Why these modules?

  • yfinance ➔ pulls the stock prices.
  • dash ➔ builds the dashboard and its user interface.
  • plotly.graph_objects ➔ makes the chart.
  • datetime and timedelta ➔ work with dates.
  • pandas ➔ structures the data in a more manageable form.

🌐 Step 2: Starting the server

Afterwards, type in the following line:

app = dash.Dash(__name__)
Enter fullscreen mode Exit fullscreen mode

What it does: This single line sets up our app. The __name__ part tells Dash where the root folder of the project is, so it can find assets like styles and images.


✍️ Step 3: Designing the interface

Now we choose how we want the dashboard to look. I have provided my own style as an example, but feel free to change it and style the interface to your liking. Below the code block are explanations of what the components do.

app.layout = html.Div(
    style={
        'fontFamily': '"Segoe UI", Helvetica, Arial, sans-serif',
        'padding': '30px',
        'maxWidth': '900px',
        'margin': 'auto',
        'backgroundColor': '#ffffff'
    },
    children=[
        html.H2(
            "Historical Stock Analysis Dashboard 📊",
            style={'textAlign': 'center', 'color': '#2c3e50', 'marginBottom': '10px'}
        ),
        html.P(
            "Enter a stock ticker and target date to analyze historical market performance.",
            style={'textAlign': 'center', 'color': '#7f8c8d', 'marginBottom': '30px'}
        ),

        html.Div(
            style={
                'backgroundColor': '#f8f9fa',
                'padding': '25px',
                'borderRadius': '8px',
                'marginBottom': '25px',
                'boxShadow': '0 4px 6px rgba(0,0,0,0.05)'
            },
            children=[
                html.Label(
                    "Stock Ticker Symbol:",
                    style={'fontWeight': 'bold', 'display': 'block', 'marginBottom': '8px', 'color': '#34495e'}
                ),
                dcc.Input(
                    id='ticker-input',
                    type='text',
                    value='AAPL',
                    style={
                        'width': '100%', 'padding': '10px', 'fontSize': '14px',
                        'marginBottom': '20px', 'borderRadius': '4px',
                        'border': '1px solid #ccc', 'boxSizing': 'border-box'
                    }
                ),

                html.Label(
                    "Select Date (YYYY-MM-DD):",
                    style={'fontWeight': 'bold', 'display': 'block', 'marginBottom': '8px', 'color': '#34495e'}
                ),
                dcc.Input(
                    id='date-input',
                    type='text',
                    value='2026-01-15',
                    style={
                        'width': '100%', 'padding': '10px', 'fontSize': '14px',
                        'marginBottom': '25px', 'borderRadius': '4px',
                        'border': '1px solid #ccc', 'boxSizing': 'border-box'
                    }
                ),

                html.Button(
                    "Get Market Summary",
                    id='submit-btn',
                    n_clicks=0,
                    style={
                        'width': '100%', 'padding': '12px', 'backgroundColor': '#007bff',
                        'color': 'white', 'border': 'none', 'borderRadius': '4px',
                        'fontSize': '16px', 'cursor': 'pointer', 'fontWeight': 'bold'
                    }
                )
            ]
        ),

        html.Div(
            id='status-output',
            style={'textAlign': 'center', 'fontWeight': 'bold', 'marginBottom': '20px', 'fontSize': '16px'}
        ),
        dcc.Graph(id='price-graph', config={'displayModeBar': False})
    ]
)
Enter fullscreen mode Exit fullscreen mode

Explaining the UI components

  • html.Div :- Makes a box containing other elements. You choose what is inside the box using children=[...] and change how it looks using style={...} (color, font, size, bold and so on).
  • dcc.Input :- Creates a box where the user types data, in this case the ticker symbol and the target date.
  • dcc.Graph :- Creates the graph that shows the data. The setting config={'displayModeBar': False} removes the extra button tray for a clean, minimalist look.

Feel free to play around and experiment a lot with these components so you get the hang of them.


🔗 Step 4: The reactive callback

To give life to our interface, we use the Dash @app.callback() decorator. This part of the script listens for user input and updates the page.

First, add this small helper function. It builds the style of the status message, so the text stays centered and bold whatever color we choose:

def status_style(color):
    return {
        'textAlign': 'center',
        'fontWeight': 'bold',
        'marginBottom': '20px',
        'fontSize': '16px',
        'color': color
    }
Enter fullscreen mode Exit fullscreen mode

Now add the callback and the start of the function it controls:

@app.callback(
    [Output('price-graph', 'figure'),
     Output('status-output', 'children'),
     Output('status-output', 'style')],
    [Input('submit-btn', 'n_clicks')],
    [State('ticker-input', 'value'),
     State('date-input', 'value')]
)
def update_dashboard(n_clicks, ticker, date_str):
    # This empty figure is a clean placeholder canvas
    empty_fig = go.Figure(layout=go.Layout(
        paper_bgcolor='rgba(0,0,0,0)',
        plot_bgcolor='rgba(0,0,0,0)',
        xaxis={'visible': False},
        yaxis={'visible': False}
    ))
Enter fullscreen mode Exit fullscreen mode
  1. Output points to the exact properties on the page that we want to change. Here we target the graph's figure, plus the children (text) and style of our status message.
  2. Input tracks interactions. Whenever the button is clicked, n_clicks changes and the function runs. (Dash also runs it once when the page first loads, so you will see the default stock right away.)
  3. State gives the function the current values of the input boxes without triggering it. For example, if the user is typing AAPL, the function won't run when they have only typed A. It waits until the button is clicked.

🧮 Step 5: The financial logic

Now we add our data, logic and graph code. This goes inside the update_dashboard function, directly under the empty_fig block, indented once (4 spaces) like the code above.

    # 1. Check for empty fields
    if not ticker or not date_str:
        return empty_fig, "Please input a ticker symbol and target date.", status_style('#e74c3c')

    try:
        # 2. Turn the input text into a date object (raises ValueError if the format is wrong)
        target_date = datetime.strptime(date_str.strip(), "%Y-%m-%d").date()
        target_str = target_date.strftime("%Y-%m-%d")

        # 3. Build the context window (10 days before and 5 days after the target date)
        start_date = target_date - timedelta(days=10)
        end_date = target_date + timedelta(days=5)

        # 4. Clean and normalize the ticker text
        ticker_clean = ticker.strip().upper()
        stock = yf.Ticker(ticker_clean)

        # 5. Get the historical prices for the window
        history = stock.history(
            start=start_date.strftime("%Y-%m-%d"),
            end=(end_date + timedelta(days=1)).strftime("%Y-%m-%d")
        )

        if history.empty:
            return empty_fig, f"No trading data found for symbol '{ticker_clean}' around {target_str}.", status_style('#e74c3c')

        # Turn the dates into plain text so we can look them up easily
        history.index = history.index.strftime('%Y-%m-%d')

        # 6. Build the candlestick chart
        fig = go.Figure(data=[go.Candlestick(
            x=history.index,
            open=history['Open'],
            high=history['High'],
            low=history['Low'],
            close=history['Close'],
            increasing_line_color='#2ecc71',  # soft green for gains
            decreasing_line_color='#e74c3c'   # clean red for losses
        )])

        fig.update_layout(
            title=f"{ticker_clean} Performance around Target Date: {target_str}",
            yaxis_title="Stock Value (USD)",
            xaxis_title="Trading Session Date",
            template="plotly_white",
            xaxis_rangeslider_visible=False,
            margin=dict(l=40, r=40, t=60, b=40)
        )

        # 7. Check whether the market was open on the target date
        if target_str in history.index:
            t_open = history.loc[target_str, 'Open']
            t_close = history.loc[target_str, 'Close']
            msg = f"➔ Success! Target date metrics found ({target_str}) -> Open: ${t_open:,.2f} | Close: ${t_close:,.2f}"
            return fig, msg, status_style('#27ae60')
        else:
            msg = f"⚠️ Target date ({target_str}) was a market closure day. Showing the nearest open sessions."
            return fig, msg, status_style('#f39c12')

    except ValueError:
        return empty_fig, "Invalid date format. Please use YYYY-MM-DD.", status_style('#e74c3c')
    except Exception as e:
        return empty_fig, f"Could not fetch data: {str(e)}", status_style('#e74c3c')
Enter fullscreen mode Exit fullscreen mode

Main logic

  • Polishing input: We use .strip() and .upper() on the ticker so the user's input is accepted in any form. For example, to check Tesla shares (ticker TSLA), the user can write tsla or Tsla without the program rejecting it.
  • Pandas: With history.index.strftime('%Y-%m-%d'), we turn the dates into plain YYYY-MM-DD text. This lets us check whether the target date exists in the data with a simple if/else. If it isn't there, the market was closed.
  • API boundary: When we request the window, we add one extra day to the end date with timedelta(days=1). Yahoo Finance treats the end date as exclusive, so without this the last day would be missing.
  • Error handling: A wrong date format raises a ValueError, which we catch to show a friendly message. Any other problem (for example a network error) is caught by the last except.

🌐 Step 6: Running in different environments

To complete our script, add this at the very end of app.py (at the left edge, outside any function):

if __name__ == '__main__':
    app.run(
        debug=True,
        jupyter_mode='inline',
        jupyter_height=750
    )
Enter fullscreen mode Exit fullscreen mode

This block starts the app. Here is how it behaves in each environment:

  • In a terminal: It starts a local server. Open http://127.0.0.1:8050 in your browser to see the dashboard.
  • In Jupyter or Google Colab: jupyter_mode='inline' shows the dashboard directly below the cell, and jupyter_height=750 sets its height.

jupyter_mode needs a recent version of Dash (2.11 or later). If you are not in a notebook, the setting is simply ignored.


🎉 Conclusion and summary of achievements

Congratulations! You have built an interactive, cross-environment financial dashboard using Python, Dash and Plotly. You have learned how to take user input, fetch historical stock data, handle market closure days, and draw a candlestick chart.

To take your dashboard to the next level, try these upgrades:

  • ➔ Add a toggle-able dark mode directly in the program.
  • ➔ Expand your callback to accept a second ticker, so you can compare two stocks in one window.

At the end of this tutorial, your dashboard should look something like this:

Screenshot of the finished stock dashboard showing a candlestick chart

Top comments (0)