Dashboard·Dashboards·Intermediate

Plotly Dash Dashboard

Create a production-grade analytical dashboard using Plotly Dash with callbacks, multi-page layout, and live data updates.

dashplotlyinteractive

Dash Trading Dashboard


Overview

This notebook documents a Plotly Dash trading dashboard — a production-grade web application with callback-driven interactivity and automatic data refresh.

Dash vs Streamlit

FeatureStreamlitDash
State modelRe-runs entire scriptCallback graph (reactive)
CustomisationLimitedFull HTML/CSS control
Component libraryStreamlit widgetsDash Core / HTML / Bootstrap
DeploymentStreamlit Cloud / DockerAny WSGI server / Docker
Multi-pagePlugin neededNative multi-page
Real-timest.rerun()dcc.Interval component
[ ]
%pip install dash dash-bootstrap-components plotly pandas jupyter-dash
Requirement already satisfied: dash in /usr/local/lib/python3.12/dist-packages (4.1.0)
Requirement already satisfied: dash-bootstrap-components in /usr/local/lib/python3.12/dist-packages (2.0.4)
Requirement already satisfied: plotly in /usr/local/lib/python3.12/dist-packages (5.24.1)
Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)
Requirement already satisfied: jupyter-dash in /usr/local/lib/python3.12/dist-packages (0.4.2)
Requirement already satisfied: Flask<3.2,>=1.0.4 in /usr/local/lib/python3.12/dist-packages (from dash) (3.1.3)
Requirement already satisfied: Werkzeug<3.2 in /usr/local/lib/python3.12/dist-packages (from dash) (3.1.8)
Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.12/dist-packages (from dash) (8.7.1)
Requirement already satisfied: typing_extensions>=4.1.1 in /usr/local/lib/python3.12/dist-packages (from dash) (4.15.0)
Requirement already satisfied: requests in /usr/local/lib/python3.12/dist-packages (from dash) (2.32.4)
Requirement already satisfied: retrying in /usr/local/lib/python3.12/dist-packages (from dash) (1.4.2)
Requirement already satisfied: nest-asyncio in /usr/local/lib/python3.12/dist-packages (from dash) (1.6.0)
Requirement already satisfied: setuptools in /usr/local/lib/python3.12/dist-packages (from dash) (75.2.0)
Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.12/dist-packages (from plotly) (9.1.4)
Requirement already satisfied: packaging in /usr/local/lib/python3.12/dist-packages (from plotly) (26.1)
Requirement already satisfied: numpy>=1.26.0 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.0.2)
Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2026.1)
Requirement already satisfied: ipython in /usr/local/lib/python3.12/dist-packages (from jupyter-dash) (7.34.0)
Requirement already satisfied: ipykernel in /usr/local/lib/python3.12/dist-packages (from jupyter-dash) (6.17.1)
Requirement already satisfied: ansi2html in /usr/local/lib/python3.12/dist-packages (from jupyter-dash) (1.9.2)
Requirement already satisfied: blinker>=1.9.0 in /usr/local/lib/python3.12/dist-packages (from Flask<3.2,>=1.0.4->dash) (1.9.0)
Requirement already satisfied: click>=8.1.3 in /usr/local/lib/python3.12/dist-packages (from Flask<3.2,>=1.0.4->dash) (8.3.3)
Requirement already satisfied: itsdangerous>=2.2.0 in /usr/local/lib/python3.12/dist-packages (from Flask<3.2,>=1.0.4->dash) (2.2.0)
Requirement already satisfied: jinja2>=3.1.2 in /usr/local/lib/python3.12/dist-packages (from Flask<3.2,>=1.0.4->dash) (3.1.6)
Requirement already satisfied: markupsafe>=2.1.1 in /usr/local/lib/python3.12/dist-packages (from Flask<3.2,>=1.0.4->dash) (3.0.3)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)
Requirement already satisfied: zipp>=3.20 in /usr/local/lib/python3.12/dist-packages (from importlib-metadata->dash) (3.23.1)
Requirement already satisfied: debugpy>=1.0 in /usr/local/lib/python3.12/dist-packages (from ipykernel->jupyter-dash) (1.8.15)
Requirement already satisfied: jupyter-client>=6.1.12 in /usr/local/lib/python3.12/dist-packages (from ipykernel->jupyter-dash) (7.4.9)
Requirement already satisfied: matplotlib-inline>=0.1 in /usr/local/lib/python3.12/dist-packages (from ipykernel->jupyter-dash) (0.2.1)
Requirement already satisfied: psutil in /usr/local/lib/python3.12/dist-packages (from ipykernel->jupyter-dash) (5.9.5)
Requirement already satisfied: pyzmq>=17 in /usr/local/lib/python3.12/dist-packages (from ipykernel->jupyter-dash) (26.2.1)
Requirement already satisfied: tornado>=6.1 in /usr/local/lib/python3.12/dist-packages (from ipykernel->jupyter-dash) (6.5.1)
Requirement already satisfied: traitlets>=5.1.0 in /usr/local/lib/python3.12/dist-packages (from ipykernel->jupyter-dash) (5.7.1)
Requirement already satisfied: jedi>=0.16 in /usr/local/lib/python3.12/dist-packages (from ipython->jupyter-dash) (0.20.0)
Requirement already satisfied: decorator in /usr/local/lib/python3.12/dist-packages (from ipython->jupyter-dash) (4.4.2)
Requirement already satisfied: pickleshare in /usr/local/lib/python3.12/dist-packages (from ipython->jupyter-dash) (0.7.5)
Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in /usr/local/lib/python3.12/dist-packages (from ipython->jupyter-dash) (3.0.52)
Requirement already satisfied: pygments in /usr/local/lib/python3.12/dist-packages (from ipython->jupyter-dash) (2.20.0)
Requirement already satisfied: backcall in /usr/local/lib/python3.12/dist-packages (from ipython->jupyter-dash) (0.2.0)
Requirement already satisfied: pexpect>4.3 in /usr/local/lib/python3.12/dist-packages (from ipython->jupyter-dash) (4.9.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.12/dist-packages (from requests->dash) (3.4.7)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.12/dist-packages (from requests->dash) (3.13)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.12/dist-packages (from requests->dash) (2.5.0)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.12/dist-packages (from requests->dash) (2026.4.22)
Requirement already satisfied: parso<0.9.0,>=0.8.6 in /usr/local/lib/python3.12/dist-packages (from jedi>=0.16->ipython->jupyter-dash) (0.8.6)
Requirement already satisfied: entrypoints in /usr/local/lib/python3.12/dist-packages (from jupyter-client>=6.1.12->ipykernel->jupyter-dash) (0.4)
Requirement already satisfied: jupyter-core>=4.9.2 in /usr/local/lib/python3.12/dist-packages (from jupyter-client>=6.1.12->ipykernel->jupyter-dash) (5.9.1)
Requirement already satisfied: ptyprocess>=0.5 in /usr/local/lib/python3.12/dist-packages (from pexpect>4.3->ipython->jupyter-dash) (0.7.0)
Requirement already satisfied: wcwidth in /usr/local/lib/python3.12/dist-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython->jupyter-dash) (0.6.0)
Requirement already satisfied: platformdirs>=2.5 in /usr/local/lib/python3.12/dist-packages (from jupyter-core>=4.9.2->jupyter-client>=6.1.12->ipykernel->jupyter-dash) (4.9.6)
[ ]
from dash import dcc, html, Input, Output, dash_table
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

3. Application Initialization

This section initializes the Dash application. The external_stylesheets parameter is configured to use dbc.themes.DARKLY, providing a dark-themed user interface. The title attribute sets the title displayed in the browser tab, and update_title is set to None to prevent the default 'Updating...' text during callbacks.

[ ]
# Application Initialisation
app = dash.Dash(
    __name__,
    external_stylesheets=[dbc.themes.DARKLY],
    title="Trading Dashboard",
    update_title=None,
)

4. Data Retrieval Functions

This section defines functions responsible for simulating or fetching financial data required for the dashboard. These functions are designed to abstract the data source, allowing for easy integration with various backend systems (e.g., databases, APIs).

4.1. get_equity_data Function

Purpose: Retrieves historical equity curve data for a specified trading strategy.

Parameters:

  • strategy (str): The identifier for the trading strategy.

Return Value:

  • pd.DataFrame: A Pandas DataFrame containing 'datetime' and 'balance' columns, representing the equity curve over time.

Implementation Details:

  • For demonstration purposes, this function simulates equity data using a random walk model.
  • In a production environment, this function would query a financial ledger or database (e.g., ledger_{strategy} from PostgreSQL).
[ ]
def get_equity_data(strategy: str) -> pd.DataFrame:
    """Fetch equity curve for the selected strategy."""
    np.random.seed(hash(strategy) % 1000)
    n = 120
    returns = np.random.normal(0.002, 0.015, n)
    equity = [10_000.0]
    for r in returns:
        equity.append(equity[-1] * (1 + r))
    dates = pd.date_range(end=datetime.utcnow(), periods=n + 1, freq="1h")
    return pd.DataFrame({"datetime": dates.astype(str), "balance": equity})

4.2. get_positions Function

Purpose: Retrieves a list of currently open trading positions.

Parameters: None.

Return Value:

  • list[dict]: A list of dictionaries, where each dictionary represents an open position with keys such as 'Exchange', 'Symbol', 'Direction', 'Entry', 'Current', 'PnL%', and 'Size'.

Implementation Details:

  • This function returns a hardcoded list of sample open positions for demonstration.
  • In a production environment, this function would query a live execution or position management system (e.g., execution_live table).
[ ]
def get_positions() -> list[dict]:
    """Fetch open positions from execution_live table."""
    return [
        {"Exchange": "Binance", "Symbol": "BTCUSDT", "Direction": "LONG",
         "Entry": 68_000, "Current": 69_200, "PnL%": 1.76, "Size": 0.15},
        {"Exchange": "Bybit",   "Symbol": "ETHUSDT", "Direction": "SHORT",
         "Entry": 3_500,  "Current": 3_460,  "PnL%": 1.14, "Size": 2.0},
    ]

4.3. get_strategy_summary Function

Purpose: Retrieves a summary of performance metrics for various trading strategies.

Parameters: None.

Return Value:

  • list[dict]: A list of dictionaries, each containing summary statistics for a strategy, including 'Strategy', 'Trades', 'WinRate%', and 'PnL%'.

Implementation Details:

  • This function provides hardcoded sample strategy summaries.
  • In a production environment, this function would aggregate performance data from backtesting results or live strategy monitoring.
[ ]
def get_strategy_summary() -> list[dict]:
    """Fetch strategy execution summary."""
    return [
        {"Strategy": "btc_1h", "Trades": 42, "WinRate%": 61.9, "PnL%": 8.3},
        {"Strategy": "eth_4h", "Trades": 18, "WinRate%": 55.6, "PnL%": 3.1},
        {"Strategy": "sol_1d", "Trades": 9,  "WinRate%": 44.4, "PnL%": -1.2},
        {"Strategy": "btc_4h", "Trades": 31, "WinRate%": 67.7, "PnL%": 12.4},
    ]

5. Layout Definition

This section constructs the user interface (UI) for the trading dashboard using dash_bootstrap_components (dbc) for styling and layout, and dash_core_components (dcc) for interactive elements. The layout is structured as a fluid container with rows and columns to ensure responsiveness. Each major component (header, controls, KPI cards, equity chart, tables) is defined within this structure.

5.1. Strategy List

Defines a list of available trading strategies. This list populates the dropdown selector in the dashboard, allowing users to choose which strategy's performance to display.

[ ]
STRATEGIES = ["btc_1h", "eth_4h", "sol_1d", "btc_4h"]

5.2. Dashboard Layout Structure

The app.layout property defines the entire visual structure of the dashboard. It uses dbc.Container as the top-level element, providing a responsive layout. The layout is organized into several key rows:

  • Header: Displays the dashboard title and a dynamic 'Last Updated' timestamp.
  • Controls: Contains interactive elements such as the strategy selector dropdown and a refresh interval slider.
  • KPI Cards: A row dedicated to displaying key performance indicator (KPI) cards, which will be populated dynamically.
  • Equity Chart: A card containing a dcc.Graph component to visualize the equity curve.
  • Positions & Summary Tables: Two columns side-by-side, displaying open trading positions and a strategy summary table.
  • Auto-refresh Interval: A hidden dcc.Interval component that triggers data updates at a specified frequency.
[ ]
app.layout = dbc.Container([

    # Header
    dbc.Row([
        dbc.Col(html.H2("📈 Algorithmic Trading Dashboard",
                         className="text-white mt-3 mb-1")),
        dbc.Col(html.Div(id="last-updated", className="text-muted text-end mt-4"),
                width=3),
    ]),
    html.Hr(className="border-secondary"),

    # Controls
    dbc.Row([
        dbc.Col([
            html.Label("Strategy", className="text-white"),
            dcc.Dropdown(
                id="strategy-selector",
                options=[{"label": s, "value": s} for s in STRATEGIES],
                value="btc_1h",
                clearable=False,
                style={"color": "#333"},
            ),
        ], width=3),
        dbc.Col([
            html.Label("Refresh Interval", className="text-white"),
            dcc.Slider(id="refresh-slider", min=5, max=60, step=5, value=15,
                       marks={5: "5s", 15: "15s", 30: "30s", 60: "60s"}),
        ], width=5),
    ], className="mb-3"),

    # KPI Cards
    dbc.Row(id="kpi-row", className="mb-3"),

    # Equity Chart
    dbc.Row([
        dbc.Col(dbc.Card([
            dbc.CardHeader("Equity Curve", className="text-white"),
            dbc.CardBody(dcc.Graph(id="equity-chart", style={"height": "320px"})),
        ], className="bg-dark border-secondary"))
    ], className="mb-3"),

    # Positions & Summary
    dbc.Row([
        dbc.Col([
            dbc.Card([
                dbc.CardHeader("Open Positions", className="text-white"),
                dbc.CardBody(html.Div(id="positions-table")),
            ], className="bg-dark border-secondary"),
        ], width=6),
        dbc.Col([
            dbc.Card([
                dbc.CardHeader("Strategy Summary", className="text-white"),
                dbc.CardBody(html.Div(id="summary-table")),
            ], className="bg-dark border-secondary"),
        ], width=6),
    ]),

    # Auto-refresh interval
    dcc.Interval(id="interval", interval=15_000, n_intervals=0),

], fluid=True, style={"backgroundColor": "#1a1a2e", "minHeight": "100vh"})

6. Callbacks

Dash callbacks enable interactivity and dynamic updates within the dashboard. These functions are triggered by changes in input components and update specified output components. This section defines the callback mechanisms that refresh data and modify the dashboard's visual elements.

6.1. update_interval Callback

Purpose: Synchronizes the data refresh interval of the dcc.Interval component with the user-selected value from the refresh slider.

Inputs:

  • refresh-slider (value property): The selected refresh interval in seconds.

Outputs:

  • interval (interval property): The interval in milliseconds for the dcc.Interval component.

Logic: Converts the slider's second-based value to milliseconds to set the dcc.Interval component's interval property.

[ ]
@app.callback(
    Output("interval", "interval"),
    Input("refresh-slider", "value"),
)
def update_interval(seconds):
    """Sync the auto-refresh interval with the slider value."""
    return seconds * 1000

6.2. refresh_dashboard Callback

Purpose: This is the primary callback function responsible for updating all dynamic components of the dashboard. It triggers whenever the dcc.Interval component ticks or when the selected trading strategy changes.

Inputs:

  • interval (n_intervals property): Increments with each tick of the dcc.Interval component, serving as a trigger.
  • strategy-selector (value property): The currently selected trading strategy.

Outputs:

  • equity-chart (figure property): Updates the Plotly graph displaying the equity curve.
  • kpi-row (children property): Updates the key performance indicator cards.
  • positions-table (children property): Updates the table showing open trading positions.
  • summary-table (children property): Updates the table summarizing strategy performance.
  • last-updated (children property): Displays the timestamp of the last data refresh.

Logic:

  1. Equity Chart Update: Calls get_equity_data for the selected strategy and generates a Plotly go.Figure object.
  2. KPI Cards Update: Retrieves strategy summary data using get_strategy_summary, filters for the selected strategy, and constructs dbc.Col elements for each KPI.
  3. Positions Table Update: Fetches open positions via get_positions and renders them in a dash_table.DataTable.
  4. Summary Table Update: Fetches all strategy summaries via get_strategy_summary and displays them in a dash_table.DataTable.
  5. Timestamp Update: Generates a string indicating the Universal Coordinated Time (UTC) of the last update.
[ ]
@app.callback(
    Output("equity-chart",  "figure"),
    Output("kpi-row",       "children"),
    Output("positions-table","children"),
    Output("summary-table", "children"),
    Output("last-updated",  "children"),
    Input("interval",           "n_intervals"),
    Input("strategy-selector",  "value"),
)
def refresh_dashboard(n, strategy):
    """
    Master callback — refreshes all dashboard components on each interval tick
    or when the strategy selector changes.
    """

    # Equity chart
    eq = get_equity_data(strategy)
    fig = go.Figure(go.Scatter(
        x=eq["datetime"], y=eq["balance"],
        mode="lines", fill="tozeroy",
        line=dict(color="#2ecc71", width=2),
        fillcolor="rgba(46,204,113,0.08)",
    ))
    fig.update_layout(
        margin=dict(l=0,r=0,t=10,b=0),
        plot_bgcolor="#1a1a2e", paper_bgcolor="#1a1a2e",
        font=dict(color="#ccc"),
        xaxis=dict(showgrid=False), yaxis=dict(showgrid=True, gridcolor="#333"),
    )

    # KPI cards
    summary = get_strategy_summary()
    row_data = next((r for r in summary if r["Strategy"] == strategy), {})
    kpi_cards = [
        dbc.Col(dbc.Card([
            dbc.CardBody([
                html.H6(k, className="text-muted small"),
                html.H4(v, className="text-white"),
            ])
        ], className="bg-dark border-secondary text-center"))
        for k, v in [
            ("Trades",   row_data.get("Trades", 0)),
            ("Win Rate", f"{row_data.get('WinRate%', 0):.1f}%"),
            ("PnL",      f"{row_data.get('PnL%', 0):.2f}%"),
        ]
    ]

    # Positions table
    pos_df = pd.DataFrame(get_positions())
    pos_table = dash_table.DataTable(
        data=pos_df.to_dict("records"),
        columns=[{"name": c, "id": c} for c in pos_df.columns],
        style_table={"overflowX": "auto"},
        style_cell={"backgroundColor": "#1a1a2e", "color": "#ccc", "border": "1px solid #333"},
        style_header={"backgroundColor": "#2d2d44", "color": "#fff", "fontWeight": "bold"},
        style_data_conditional=[
            {"if": {"filter_query": "{PnL%} > 0", "column_id": "PnL%"},
             "color": "#2ecc71"},
            {"if": {"filter_query": "{PnL%} < 0", "column_id": "PnL%"},
             "color": "#e74c3c"},
        ],
    )

    # Summary table
    sum_df = pd.DataFrame(summary)
    sum_table = dash_table.DataTable(
        data=sum_df.to_dict("records"),
        columns=[{"name": c, "id": c} for c in sum_df.columns],
        style_table={"overflowX": "auto"},
        style_cell={"backgroundColor": "#1a1a2e", "color": "#ccc", "border": "1px solid #333"},
        style_header={"backgroundColor": "#2d2d44", "color": "#fff", "fontWeight": "bold"},
    )

    updated = f"Updated: {datetime.utcnow().strftime('%H:%M:%S')} UTC"
    return fig, kpi_cards, pos_table, sum_table, updated

7. Application Entry Point

This section defines the entry point for running the Dash application. When the script is executed directly, app.run_server() is called to start the Dash development server. For production deployments, a WSGI server would typically be used.

Configuration:

  • debug=True: Enables hot-reloading and debug tools, suitable for development.
  • port=8050: Specifies the port on which the application will be accessible.
[ ]
if __name__ == "__main__":
    app.run(debug=True, port=8050)
Dash is running on http://127.0.0.1:8050/

INFO:dash.dash:Dash is running on http://127.0.0.1:8050/

 * Serving Flask app '__main__'
 * Debug mode: on