A macro dashboard puts the most important central-bank and economic indicators in one place, so you can scan market conditions in seconds instead of bouncing between data terminals. This guide walks you through building a fully interactive, multi-panel dashboard in Python — fetching live indicator data from the FXMacroData API, reshaping it with pandas, and rendering it with Plotly. Every panel updates from a single API key and a handful of function calls.
What You Will Build
A self-contained Python script that pulls policy rates, inflation, unemployment, and PMI for four major FX currencies (USD, EUR, GBP, AUD), assembles a clean pandas DataFrame for each indicator, and renders a four-panel Plotly dashboard — all in under 150 lines of code.
Prerequisites
You will need the following before starting:
- Python 3.9+
- FXMacroData API key — sign up at /subscribe and copy your key from the dashboard
- Python packages:
requests,pandas,plotly
Install the dependencies in one command:
pip install requests pandas plotly
Store your API key as an environment variable — never hard-code credentials in scripts:
export FXMD_API_KEY="YOUR_API_KEY"
Step 1 — Understand the Indicator Endpoint Shape
Every FXMacroData indicator follows the same REST pattern, which makes it trivial to generalise into a single fetch function. A policy-rate request for USD looks like this:
GET https://fxmacrodata.com/api/v1/announcements/usd/policy_rate?api_key=YOUR_API_KEY&start=2020-01-01
The JSON response is a flat object with a data array:
{
"data": [
{ "date": "2025-03-19", "val": 4.25, "announcement_datetime": "2025-03-19T18:00:00Z" },
{ "date": "2025-01-29", "val": 4.25, "announcement_datetime": "2025-01-29T19:00:00Z" },
{ "date": "2024-12-18", "val": 4.25, "announcement_datetime": "2024-12-18T19:00:00Z" }
]
}
Each record carries a date (YYYY-MM-DD), a numeric val, and — where available — a
second-level UTC announcement_datetime. The consistent shape across all indicators is what lets
one function serve every panel in the dashboard.
Step 2 — Write a Reusable Fetch Function
Create a module called macro_fetch.py. The function below requests any indicator for any currency,
converts the response to a pandas Series, and returns it indexed by date — ready to drop straight into a
DataFrame.
import os
import requests
import pandas as pd
BASE_URL = "https://fxmacrodata.com/api/v1"
API_KEY = os.environ["FXMD_API_KEY"]
def fetch_indicator(currency: str, indicator: str, start: str = "2020-01-01") -> pd.Series:
"""
Fetch a single indicator series from FXMacroData and return it as a
pandas Series with a DatetimeIndex, named '{currency.upper()}_{indicator}'.
"""
url = f"{BASE_URL}/announcements/{currency}/{indicator}"
resp = requests.get(url, params={"api_key": API_KEY, "start": start}, timeout=15)
resp.raise_for_status()
records = resp.json().get("data", [])
if not records:
return pd.Series(name=f"{currency.upper()}_{indicator}", dtype=float)
series = (
pd.DataFrame(records)
.assign(date=lambda df: pd.to_datetime(df["date"]))
.set_index("date")["val"]
.sort_index()
.rename(f"{currency.upper()}_{indicator}")
)
return series
Why a Series per indicator?
Indicators from different currencies are released on different dates, so they never share a perfectly aligned
index. Storing each as a separate Series and joining them with pd.concat(..., axis=1) lets pandas
handle the date alignment automatically, filling gaps with NaN where a currency hasn't yet
reported.
Step 3 — Fetch All Indicators for the Dashboard
With the fetch helper in place, pulling four indicators for four currencies is a concise loop. Add a small retry wrapper to handle transient network hiccups:
import time
CURRENCIES = ["usd", "eur", "gbp", "aud"]
INDICATORS = {
"policy_rate": "Policy Rate (%)",
"inflation": "CPI Inflation (% YoY)",
"unemployment": "Unemployment Rate (%)",
"pmi": "Manufacturing PMI",
}
def fetch_all(start: str = "2020-01-01", retries: int = 3) -> dict[str, pd.DataFrame]:
"""
Return a dict mapping each indicator slug to a wide DataFrame where
each column is one currency (e.g. USD_policy_rate, EUR_policy_rate).
"""
frames: dict[str, list[pd.Series]] = {ind: [] for ind in INDICATORS}
for currency in CURRENCIES:
for indicator in INDICATORS:
for attempt in range(retries):
try:
s = fetch_indicator(currency, indicator, start=start)
frames[indicator].append(s)
break
except requests.HTTPError as exc:
if attempt == retries - 1:
print(f"Warning: could not fetch {currency}/{indicator}: {exc}")
else:
time.sleep(1.5 ** attempt)
return {ind: pd.concat(series, axis=1) for ind, series in frames.items() if series}
You can inspect any DataFrame interactively to verify the data looks right before rendering:
data = fetch_all(start="2021-01-01")
print(data["policy_rate"].tail())
# Output (example):
# USD_policy_rate EUR_policy_rate GBP_policy_rate AUD_policy_rate
# date
# 2025-01-29 4.25 NaN NaN NaN
# 2025-02-06 NaN NaN 4.50 NaN
# 2025-02-18 NaN NaN NaN 4.10
# 2025-03-06 NaN 2.50 NaN NaN
# 2025-03-19 4.25 NaN NaN NaN
Step 4 — Shape the Data for Charting
Plotly works best with forward-filled series for line charts, so the most recently known value carries forward until the next release. Add a forward-fill step before charting:
def prepare_for_chart(df: pd.DataFrame) -> pd.DataFrame:
"""
Forward-fill each column so lines in the chart step at each release date
rather than showing gaps between announcements.
Resample to a monthly frequency for a cleaner visual.
"""
return (
df
.ffill()
.resample("ME") # month-end
.last()
.dropna(how="all")
)
For PMI, which is a monthly indicator, forward-fill is less relevant — but the function handles it gracefully by simply passing through the already-monthly data unchanged.
Step 5 — Build the Plotly Dashboard
Plotly's make_subplots utility lets you arrange multiple charts in a single figure object, which
you can display in a browser, export as HTML, or embed in a Jupyter notebook.
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# Palette aligned with FXMacroData brand colours
COLORS = {
"usd": "#3B82F6", # finance blue
"eur": "#D97706", # gold
"gbp": "#16A34A", # green
"aud": "#7C3AED", # purple
}
SUBPLOT_TITLES = list(INDICATORS.values())
def build_dashboard(data: dict[str, pd.DataFrame]) -> go.Figure:
fig = make_subplots(
rows=2, cols=2,
subplot_titles=SUBPLOT_TITLES,
shared_xaxes=False,
vertical_spacing=0.12,
horizontal_spacing=0.08,
)
positions = [(1, 1), (1, 2), (2, 1), (2, 2)]
for (indicator, label), (row, col) in zip(INDICATORS.items(), positions):
df = prepare_for_chart(data[indicator])
for col_name in df.columns:
currency = col_name.split("_")[0].lower()
fig.add_trace(
go.Scatter(
x=df.index,
y=df[col_name],
mode="lines",
name=currency.upper(),
line=dict(color=COLORS[currency], width=2),
legendgroup=currency,
showlegend=(indicator == "policy_rate"), # one legend entry per currency
hovertemplate=f"%{{x|%b %Y}}: %{{y:.2f}}{currency.upper()} ",
),
row=row, col=col,
)
fig.update_layout(
title=dict(
text="G4 Central Bank Macro Dashboard",
font=dict(size=22, color="#1e3a5f"),
x=0.5,
),
paper_bgcolor="#f8fafc",
plot_bgcolor="#f1f5f9",
font=dict(family="Inter, system-ui, sans-serif", size=12, color="#334155"),
legend=dict(
orientation="h",
yanchor="bottom",
y=1.04,
xanchor="center",
x=0.5,
),
height=700,
margin=dict(t=100, b=50, l=60, r=40),
)
fig.update_xaxes(showgrid=True, gridcolor="#e2e8f0", zeroline=False)
fig.update_yaxes(showgrid=True, gridcolor="#e2e8f0", zeroline=False)
return fig
Step 6 — Run the Dashboard
Wire everything together in a dashboard.py entry-point script. Calling fig.show()
opens the dashboard in your default browser. Calling fig.write_html() saves a self-contained HTML
file you can share or embed anywhere.
if __name__ == "__main__":
print("Fetching macro data …")
data = fetch_all(start="2021-01-01")
print("Building dashboard …")
fig = build_dashboard(data)
# Option A: open in browser
fig.show()
# Option B: save as portable HTML file
fig.write_html("macro_dashboard.html", include_plotlyjs="cdn")
print("Saved macro_dashboard.html")
Run it from the terminal:
python dashboard.py
Plotly will open a browser window showing a two-row, two-column dashboard with four live panels — one for each indicator — colour-coded by currency. Hovering over any line reveals the exact date and value for that release.
Add more indicators in seconds
The dashboard is designed to scale. Add any entry to the INDICATORS dict — for example
"core_inflation": "Core CPI (% YoY)" or "gdp_quarterly": "GDP Growth (% QoQ)" — and
the fetch, shape, and chart steps all pick it up automatically. The full indicator catalogue is available in
the API documentation.
Step 7 — Add Release-Timing Annotations (Optional)
One of the most useful dashboard enhancements is overlaying release markers — vertical lines or dots
that show exactly when each announcement was made. FXMacroData carries second-level
announcement_datetime timestamps, so you can add them without any guesswork:
def fetch_release_datetimes(currency: str, indicator: str, start: str) -> pd.Series:
"""Return a Series of UTC announcement datetimes for a given indicator."""
url = f"{BASE_URL}/announcements/{currency}/{indicator}"
resp = requests.get(url, params={"api_key": API_KEY, "start": start}, timeout=15)
resp.raise_for_status()
records = resp.json().get("data", [])
if not records:
return pd.Series(dtype="datetime64[ns, UTC]")
df = pd.DataFrame(records)
if "announcement_datetime" not in df.columns:
return pd.Series(dtype="datetime64[ns, UTC]")
return pd.to_datetime(df["announcement_datetime"], utc=True)
def add_release_markers(fig: go.Figure, currency: str, indicator: str,
start: str, row: int, col: int) -> None:
"""Overlay vertical dashed lines on a subplot at each release datetime."""
datetimes = fetch_release_datetimes(currency, indicator, start)
for dt in datetimes:
fig.add_vline(
x=dt.timestamp() * 1000, # Plotly uses ms since epoch for datetime axes
line_width=1,
line_dash="dot",
line_color=COLORS[currency],
opacity=0.35,
row=row, col=col,
)
Call add_release_markers after build_dashboard and before fig.show()
to annotate whichever panels are most relevant to your analysis. Release markers are especially useful on the
policy-rate panel, where decision dates are infrequent but high-impact.
Step 8 — Export Panels as Individual Images
If you want to include individual charts in a report or slide deck, Plotly can export each subplot as a PNG via Kaleido:
pip install kaleido
fig.write_image("macro_dashboard.png", width=1400, height=700, scale=2)
For per-panel exports, build each indicator as a standalone go.Figure using the same
go.Scatter traces and call write_image individually. The fetch and shape functions
developed in earlier steps work unchanged for single-panel figures.
Complete Script Reference
Below is the full, self-contained script combining all steps above. Copy it into a file called
dashboard.py, set FXMD_API_KEY, and run it.
"""
macro_dashboard.py — G4 Central Bank Macro Dashboard
Requires: requests, pandas, plotly
Usage: FXMD_API_KEY=your_key python macro_dashboard.py
"""
import os
import time
import requests
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
BASE_URL = "https://fxmacrodata.com/api/v1"
API_KEY = os.environ["FXMD_API_KEY"]
CURRENCIES = ["usd", "eur", "gbp", "aud"]
INDICATORS = {
"policy_rate": "Policy Rate (%)",
"inflation": "CPI Inflation (% YoY)",
"unemployment": "Unemployment Rate (%)",
"pmi": "Manufacturing PMI",
}
COLORS = {"usd": "#3B82F6", "eur": "#D97706", "gbp": "#16A34A", "aud": "#7C3AED"}
def fetch_indicator(currency: str, indicator: str, start: str = "2020-01-01") -> pd.Series:
url = f"{BASE_URL}/announcements/{currency}/{indicator}"
resp = requests.get(url, params={"api_key": API_KEY, "start": start}, timeout=15)
resp.raise_for_status()
records = resp.json().get("data", [])
if not records:
return pd.Series(name=f"{currency.upper()}_{indicator}", dtype=float)
return (
pd.DataFrame(records)
.assign(date=lambda df: pd.to_datetime(df["date"]))
.set_index("date")["val"]
.sort_index()
.rename(f"{currency.upper()}_{indicator}")
)
def fetch_all(start: str = "2020-01-01", retries: int = 3) -> dict[str, pd.DataFrame]:
frames: dict[str, list[pd.Series]] = {ind: [] for ind in INDICATORS}
for currency in CURRENCIES:
for indicator in INDICATORS:
for attempt in range(retries):
try:
frames[indicator].append(fetch_indicator(currency, indicator, start))
break
except requests.HTTPError as exc:
if attempt == retries - 1:
print(f"Warning: {currency}/{indicator}: {exc}")
else:
time.sleep(1.5 ** attempt)
return {ind: pd.concat(series, axis=1) for ind, series in frames.items() if series}
def prepare_for_chart(df: pd.DataFrame) -> pd.DataFrame:
return df.ffill().resample("ME").last().dropna(how="all")
def build_dashboard(data: dict[str, pd.DataFrame]) -> go.Figure:
fig = make_subplots(
rows=2, cols=2,
subplot_titles=list(INDICATORS.values()),
vertical_spacing=0.12,
horizontal_spacing=0.08,
)
for (indicator, _), (row, col) in zip(INDICATORS.items(), [(1,1),(1,2),(2,1),(2,2)]):
df = prepare_for_chart(data[indicator])
for col_name in df.columns:
currency = col_name.split("_")[0].lower()
fig.add_trace(
go.Scatter(
x=df.index, y=df[col_name], mode="lines",
name=currency.upper(),
line=dict(color=COLORS[currency], width=2),
legendgroup=currency,
showlegend=(indicator == "policy_rate"),
hovertemplate=f"%{{x|%b %Y}}: %{{y:.2f}}{currency.upper()} ",
),
row=row, col=col,
)
fig.update_layout(
title=dict(text="G4 Central Bank Macro Dashboard", font=dict(size=22, color="#1e3a5f"), x=0.5),
paper_bgcolor="#f8fafc", plot_bgcolor="#f1f5f9",
font=dict(family="Inter, system-ui, sans-serif", size=12),
legend=dict(orientation="h", yanchor="bottom", y=1.04, xanchor="center", x=0.5),
height=700, margin=dict(t=100, b=50, l=60, r=40),
)
fig.update_xaxes(showgrid=True, gridcolor="#e2e8f0", zeroline=False)
fig.update_yaxes(showgrid=True, gridcolor="#e2e8f0", zeroline=False)
return fig
if __name__ == "__main__":
print("Fetching macro data …")
data = fetch_all(start="2021-01-01")
print("Building dashboard …")
fig = build_dashboard(data)
fig.show()
fig.write_html("macro_dashboard.html", include_plotlyjs="cdn")
print("Saved macro_dashboard.html")
Summary
You now have a working macro dashboard that:
- Fetches policy rates, inflation, unemployment, and PMI for USD, EUR, GBP, and AUD from the FXMacroData announcements endpoint
- Reshapes the data into aligned, forward-filled pandas DataFrames
- Renders a four-panel interactive Plotly dashboard with colour-coded currency lines
- Exports a self-contained HTML file you can share or embed
From here you can extend the dashboard in several directions: add more currencies, overlay unemployment against PMI on dual axes, or wire the data into a scheduled job that refreshes the HTML export every morning. The full indicator catalogue — including trade balance, credit growth, and COT positioning — is available at api-data-docs.