Volatility 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[2000-01-01]
# Stock market turnover ratio series IDs
volatility_series = "VIXCLS"
:ok
# Fetch metadata on the unemployment series and output it
{:ok, %{"seriess" => [volatility_data | _]}} = Fred.Series.get(volatility_series)
# Print out some of the metadata from the series
IO.puts("""
Title: #{volatility_data["title"]}
Frequency: #{volatility_data["frequency"]}
Units: #{volatility_data["units"]}
Seasonal: #{volatility_data["seasonal_adjustment"]}
Last Update: #{volatility_data["last_updated"]}
""")
data_frame =
Fred.Series.observations_as_data_frame(
volatility_series,
observation_start: observation_start,
frequency: :d,
rename: %{volatility_series => "volatility"}
)
min_date = Series.min(data_frame["date"])
max_date = Series.max(data_frame["date"])
Kino.DataTable.new(data_frame)
volitility_bands =
data_frame
|> DataFrame.mutate(
condition:
cond do
volatility < 15.0 -> "low"
volatility >= 15.0 and volatility < 30.0 -> "normal"
volatility >= 30.0 and volatility < 40.0 -> "high"
volatility >= 40.0 -> "extremely_high"
end
)
|> DataFrame.to_rows()
|> Enum.chunk_by(fn %{"condition" => condition} ->
condition
end)
|> Enum.map(fn consecutive_dates ->
condition =
consecutive_dates
|> List.first()
|> Map.fetch!("condition")
{start, stop} =
consecutive_dates
|> Enum.map(&Map.fetch!(&1, "date"))
|> Enum.min_max_by(
fn date ->
date
end,
Date
)
%{condition: condition, start: start, stop: stop}
end)
conditions = [
{"low", "#fde047"},
{"normal", "#d97706"},
{"high", "#c2410c"},
{"extremely_high", "#991b1b"}
]
# Plot the two separate series
[
width: 1000,
height: 400,
title: "Volatility Index (#{min_date} - #{max_date})"
]
|> Vl.new()
|> Vl.layers([
Vl.new()
|> Vl.data_from_values(volitility_bands)
|> Vl.mark(:rect, opacity: 0.25)
|> Vl.encode_field(:x, "start", type: :temporal)
|> Vl.encode_field(:x2, "stop")
|> Vl.encode_field(:color, "condition",
type: :nominal,
scale: %{
domain: Enum.map(conditions, fn {condition, _color} -> condition end),
range: Enum.map(conditions, fn {_condition, color} -> color end)
}
),
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, "volatility",
type: :quantitative,
title: "Volatility"
)
])