US Dollar Index
Mix.install([
{:fred, "~> 0.5.0"},
{:vega_lite, "~> 0.1.11"},
{:kino_vega_lite, "~> 0.1.13"}
])
Introuction
<- Back to index
require Explorer.DataFrame
alias VegaLite, as: Vl
alias Explorer.DataFrame
alias Explorer.Series
# API key pulled from Livebook secrets
Application.put_env(:fred, :api_key, System.fetch_env!("LB_FRED_API_KEY"))
# Attach the default logger to keep an eye on requests
Fred.Telemetry.Logger.attach(level: :info)
# Set the start date
observation_start = ~D[2005-01-01]
# Stock market turnover ratio series IDs
recession_series = "USREC"
dollar_index_series = "RTWEXBGS"
:ok
{:ok, %{"seriess" => [metadata | _]}} = Fred.Series.get(dollar_index_series)
# Print out some of the metadata from the series
IO.puts("""
Title: #{metadata["title"]}
Frequency: #{metadata["frequency"]}
Units: #{metadata["units"]}
Seasonal: #{metadata["seasonal_adjustment"]}
Last Update: #{metadata["last_updated"]}
""")
:ok
# Fetch the time series for the stock market turnover as a DataFrame
dollar_index_data_frame =
dollar_index_series
|> Fred.Series.observations_as_data_frame(
observation_start: observation_start,
frequency: :m,
rename: %{dollar_index_series => "dollar_index"}
)
dollar_index_percent_change_data_frame =
dollar_index_series
|> Fred.Series.observations_as_data_frame(
observation_start: observation_start,
frequency: :m,
units: :pch,
rename: %{dollar_index_series => "dollar_index_percent_change"}
)
|> DataFrame.mutate(
condition:
if dollar_index_percent_change > 0 do
"increase"
else
"decrease"
end
)
data_frame =
dollar_index_data_frame
|> DataFrame.join(dollar_index_percent_change_data_frame, on: [:date], how: :left)
|> DataFrame.sort_by(asc: date)
min_date = Series.min(data_frame["date"])
max_date = Series.max(data_frame["date"])
Kino.DataTable.new(data_frame)
# Fetch the recession indicator for the same date range
{:ok, %{"observations" => recession_data}} =
Fred.Series.observations(recession_series,
observation_start: observation_start,
frequency: :m
)
recession_periods =
recession_data
|> Enum.flat_map(fn
%{"value" => "."} ->
[]
%{"value" => value, "date" => date} ->
[{Date.from_iso8601!(date), value}]
end)
|> Enum.chunk_by(fn {_date, value} -> value end)
|> Enum.flat_map(fn
[{_date, "0"} | _] ->
[]
data ->
[Enum.map(data, fn {date, _value} -> date end)]
end)
|> Enum.map(fn chunk ->
{start, stop} =
Enum.min_max_by(chunk, fn date -> date end, Date)
%{start: start, stop: stop}
end)
:ok
# Plot the two separate series
[
width: 1_000,
height: 400,
title: "#{metadata["title"]} (#{min_date} - #{max_date})"
]
|> Vl.new()
|> Vl.layers([
# Gray recession bands
Vl.new()
|> Vl.data_from_values(recession_periods)
|> Vl.mark(:rect, color: "#3f3f46", opacity: 0.10)
|> Vl.encode_field(:x, "start", type: :temporal)
|> Vl.encode_field(:x2, "stop", type: :temporal),
Vl.new()
|> Vl.data_from_values(data_frame)
|> Vl.mark(:line, tooltip: true, color: "#2563eb")
|> Vl.encode_field(:x, "date",
type: :temporal,
title: "Date",
axis: [format: "%Y"]
)
|> Vl.encode_field(:y, "dollar_index",
type: :quantitative,
title: "US Dollar Index (%)",
scale: [zero: false]
),
Vl.new()
|> Vl.data_from_values(data_frame)
|> Vl.mark(:bar, tooltip: true, opacity: 0.5, width: 1.25)
|> Vl.encode_field(:x, "date",
type: :temporal,
title: "Date",
axis: [format: "%Y"]
)
|> Vl.encode_field(:y, "dollar_index_percent_change",
type: :quantitative,
title: "Percent change"
)
|> Vl.encode_field(:color, "condition",
type: :nominal,
scale: [domain: ["increase", "decrease"], range: ["#22c55e", "#ef4444"]],
legend: nil
)
])
|> Vl.resolve(:scale, y: :independent)