Plotly Dash Dashboard
Create a production-grade analytical dashboard using Plotly Dash with callbacks, multi-page layout, and live data updates.
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
| Feature | Streamlit | Dash |
|---|---|---|
| State model | Re-runs entire script | Callback graph (reactive) |
| Customisation | Limited | Full HTML/CSS control |
| Component library | Streamlit widgets | Dash Core / HTML / Bootstrap |
| Deployment | Streamlit Cloud / Docker | Any WSGI server / Docker |
| Multi-page | Plugin needed | Native multi-page |
| Real-time | st.rerun() | dcc.Interval component |
%pip install dash dash-bootstrap-components plotly pandas jupyter-dashRequirement 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, timedelta3. 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_livetable).
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.Graphcomponent 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.Intervalcomponent 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(valueproperty): The selected refresh interval in seconds.
Outputs:
interval(intervalproperty): The interval in milliseconds for thedcc.Intervalcomponent.
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 * 10006.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_intervalsproperty): Increments with each tick of thedcc.Intervalcomponent, serving as a trigger.strategy-selector(valueproperty): The currently selected trading strategy.
Outputs:
equity-chart(figureproperty): Updates the Plotly graph displaying the equity curve.kpi-row(childrenproperty): Updates the key performance indicator cards.positions-table(childrenproperty): Updates the table showing open trading positions.summary-table(childrenproperty): Updates the table summarizing strategy performance.last-updated(childrenproperty): Displays the timestamp of the last data refresh.
Logic:
- Equity Chart Update: Calls
get_equity_datafor the selected strategy and generates a Plotlygo.Figureobject. - KPI Cards Update: Retrieves strategy summary data using
get_strategy_summary, filters for the selected strategy, and constructsdbc.Colelements for each KPI. - Positions Table Update: Fetches open positions via
get_positionsand renders them in adash_table.DataTable. - Summary Table Update: Fetches all strategy summaries via
get_strategy_summaryand displays them in adash_table.DataTable. - 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, updated7. 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