R is one of the most powerful languages for statistical analysis and financial modelling — and it has a mature ecosystem for working with time-series data. This guide walks through everything you need to fetch, clean, and analyse FXMacroData indicator series in R, using the modern httr2 and jsonlite packages. By the end you will have a reusable R workflow that pulls central bank data, builds a tidy tibble, and produces a publication-ready chart — all in fewer than 50 lines of code.
What You Will Build
A fully reproducible R script that authenticates against the FXMacroData REST API, retrieves policy rate and inflation time series for multiple currencies, joins them into a single tibble, and plots them with ggplot2 — ready to embed in a Quarto or R Markdown report.
Prerequisites
- R ≥ 4.2 and RStudio (or any R environment)
- The following packages:
httr2,jsonlite,dplyr,tidyr,lubridate,ggplot2 - A FXMacroData API key — sign up at /subscribe to get one
Install the required packages once if you do not already have them:
install.packages(c("httr2", "jsonlite", "dplyr", "tidyr", "lubridate", "ggplot2"))
Step 1 — Understand the API Shape
Every FXMacroData indicator endpoint follows the same URL pattern:
GET https://fxmacrodata.com/api/v1/announcements/{currency}/{indicator}?api_key=YOUR_API_KEY
The response is a JSON object with a data array where each element has a date
and a val field (and optionally an announcement_datetime for release-time precision).
For example, fetching the US Federal Reserve policy rate:
curl "https://fxmacrodata.com/api/v1/announcements/usd/policy_rate?api_key=YOUR_API_KEY&start=2022-01-01"
{
"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" }
]
}
This clean structure maps perfectly to an R data frame with minimal transformation.
Step 2 — Set Up Your API Key
Store your API key in an environment variable rather than hard-coding it in scripts. Add this line to your
~/.Renviron file:
FXMD_API_KEY=your_actual_api_key_here
Then reload the environment and retrieve the key at the top of every script:
readRenviron("~/.Renviron")
API_KEY <- Sys.getenv("FXMD_API_KEY")
if (nchar(API_KEY) == 0) stop("FXMD_API_KEY is not set in .Renviron")
🔒 Security tip
Never commit API keys to version control. Add .Renviron to your .gitignore if
you keep it in a project directory. For reproducible deployments, use a secrets manager or CI environment variable.
Step 3 — Write a Generic Fetch Helper
With httr2 you build requests declaratively, handle errors explicitly, and decode JSON in a single pipeline. The function below wraps all three steps into one reusable helper:
library(httr2)
library(jsonlite)
library(dplyr)
library(lubridate)
BASE_URL <- "https://fxmacrodata.com/api/v1"
#' Fetch an indicator time series from FXMacroData
#'
#' @param currency Three-letter currency code, e.g. "usd", "eur", "gbp"
#' @param indicator Indicator slug, e.g. "policy_rate", "inflation", "gdp"
#' @param start Optional start date as "YYYY-MM-DD" string
#' @param end Optional end date as "YYYY-MM-DD" string
#' @return A tibble with columns: date (Date), val (numeric), currency (chr), indicator (chr)
fetch_indicator <- function(currency, indicator, start = NULL, end = NULL) {
req <- request(BASE_URL) |>
req_url_path_append("announcements", currency, indicator) |>
req_url_query(api_key = API_KEY) |>
req_error(is_error = \(resp) resp_status(resp) >= 400)
if (!is.null(start)) req <- req |> req_url_query(start = start)
if (!is.null(end)) req <- req |> req_url_query(end = end)
resp <- req |> req_perform()
rows <- resp |> resp_body_json(simplifyVector = TRUE)
as_tibble(rows$data) |>
mutate(
date = as_date(date),
val = as.numeric(val),
currency = toupper(currency),
indicator = indicator
)
}
The key choices here: req_error() ensures HTTP 4xx/5xx responses throw an R condition rather
than silently returning bad data; resp_body_json(simplifyVector = TRUE) coerces nested arrays
directly to a data frame instead of a list; and as_date() from lubridate gives you a proper
Date column immediately.
Step 4 — Fetch Multiple Currencies and Indicators
Now use the helper to pull policy rates for four G4 currencies over a three-year window — exactly the kind of multi-currency comparison that drives divergence trading decisions:
currencies <- c("usd", "eur", "gbp", "jpy")
START <- "2022-01-01"
# Pull policy rates for all four currencies and stack into one tibble
policy_rates <- purrr::map_dfr(
currencies,
\(ccy) fetch_indicator(ccy, "policy_rate", start = START)
)
# Quick check
dplyr::glimpse(policy_rates)
#> Rows: ~80
#> Columns: date <date>, val <dbl>, currency <chr>, indicator <chr>
You can fetch additional indicators just as easily. For instance, pulling headline inflation alongside policy rates lets you compute the real rate spread, a key driver of carry-trade positioning:
inflation <- purrr::map_dfr(
currencies,
\(ccy) fetch_indicator(ccy, "inflation", start = START)
)
# Combine into one tidy frame
macro_data <- bind_rows(policy_rates, inflation)
Indicators you can fetch
The full catalogue is available at fxmacrodata.com/api-data-docs. Key series for FX analysis include policy_rate, inflation, gdp, unemployment, and pmi. Every series uses the same fetch pattern — just change the currency and indicator slug.
Step 5 — Clean and Reshape the Data
For most analyses you want the data in wide format — one column per indicator per currency —
rather than the stacked tidy format returned by the API. The tidyr pivot_wider() call handles
this in one step, and fill() forward-fills the sparse central-bank announcement observations
to a regular monthly grid:
library(tidyr)
# Build a regular monthly date spine
date_spine <- tibble(date = seq.Date(as_date(START), Sys.Date(), by = "month"))
# Pivot to wide: one row per date, columns = currency_indicator
wide_data <- macro_data |>
# Use year-month as join key so quarterly data aligns to month boundaries
mutate(date = floor_date(date, "month")) |>
pivot_wider(
names_from = c(currency, indicator),
values_from = val,
values_fn = \(x) last(x) # take latest reading within each month
)
# Left-join onto the date spine and forward-fill sparse series
full_data <- date_spine |>
left_join(wide_data, by = "date") |>
fill(everything(), .direction = "down")
head(full_data)
Step 6 — Compute Real Rate Spreads
A real rate spread is the policy rate minus inflation — a positive spread means the central bank is in restrictive territory relative to consumer price growth. EUR–USD real rate differentials are among the strongest medium-term predictors of EUR/USD direction:
spread_data <- full_data |>
mutate(
real_rate_usd = USD_policy_rate - USD_inflation,
real_rate_eur = EUR_policy_rate - EUR_inflation,
real_rate_gbp = GBP_policy_rate - GBP_inflation,
real_rate_jpy = JPY_policy_rate - JPY_inflation,
# EUR minus USD spread: positive = EUR relatively less restrictive
eur_usd_spread = real_rate_eur - real_rate_usd
)
Step 7 — Visualise with ggplot2
With the tidy tibble ready, a ggplot2 multi-line chart takes a few lines. Pivot back to long format for
the colour aesthetic:
library(ggplot2)
policy_rates |>
ggplot(aes(x = date, y = val, colour = currency)) +
geom_step(linewidth = 0.9) +
scale_colour_manual(
values = c(USD = "#2563eb", EUR = "#16a34a", GBP = "#7c3aed", JPY = "#dc2626")
) +
scale_y_continuous(labels = scales::label_percent(scale = 1)) +
labs(
title = "G4 Central Bank Policy Rates",
x = NULL,
y = "Policy rate (%)",
colour = "Currency",
caption = "Source: FXMacroData"
) +
theme_minimal(base_size = 13) +
theme(legend.position = "bottom")
Use geom_step() rather than geom_line() for policy rate series — central bank
decisions are discrete staircase changes and a step chart represents that correctly.
Step 8 — Export for a Report
If you are embedding this chart in a Quarto or R Markdown document, save the data frame to a CSV for reproducibility and the plot to a high-resolution PNG for inline rendering:
readr::write_csv(spread_data, "macro_spread_data.csv")
ggsave(
filename = "policy_rates.png",
width = 10,
height = 5.6,
dpi = 150
)
For interactive Shiny dashboards, the same tibble feeds directly into plotly::ggplotly() for
zero-friction interactivity.
Step 9 — Automate with a Scheduled Script
To keep your analysis current without manual re-runs, wrap the fetch logic in a standalone R script and
schedule it with cronR (Linux/macOS) or the Windows Task Scheduler:
# file: refresh_macro.R — run daily at 08:00 UTC
readRenviron("~/.Renviron")
source("fetch_helpers.R")
macro_data <- purrr::map_dfr(
tidyr::crossing(
currency = c("usd", "eur", "gbp", "jpy"),
indicator = c("policy_rate", "inflation", "unemployment")
),
\(row) fetch_indicator(row$currency, row$indicator, start = "2020-01-01")
)
readr::write_csv(macro_data, paste0("data/macro_", Sys.Date(), ".csv"))
message("Refresh complete: ", nrow(macro_data), " observations written.")
Pair this with the release calendar endpoint to trigger refreshes only on days when high-impact data is expected, saving unnecessary API calls on quiet days.
Complete working example
All the snippets above combine into a single ~60-line script.
Set FXMD_API_KEY in your .Renviron, run the file, and you have a
fully refreshable macro dataset ready for modelling in R.
Summary
You have learned how to:
- Authenticate safely with FXMacroData using an environment variable
- Build a reusable
fetch_indicator()helper with httr2 and jsonlite - Pull and stack multi-currency indicator series with
purrr::map_dfr() - Reshape, forward-fill, and compute derived spreads with dplyr and tidyr
- Produce publication-ready step charts with ggplot2
- Automate daily refreshes with a scheduled R script
As a next step, explore the GDP, PMI, and trade balance endpoints to build a fuller macro scorecard across all G10 currencies. The same fetch helper and ggplot2 workflow applies unchanged — only the indicator slug needs to change.