
BQuant [9] - Curves Over Time
Intro
This post shows the CURVEMEMBERS()
universe, showing the rates and tenors for a given security and how they’ve changed on an annual basis.
Also demonstrated here are Plotly animations, which are as simple as replacing a “colors” option with a “animation_frame”.
* I don’t use animations often. It’s a good tool to have. I try to find a static visualization that conveys the additional dimension in one view, but sometimes, there are too many dimensions to visualize.
BQuant Example
Prerequisite
To run this example, follow the steps in BQuant - Opening Blog Examples to create a Custom Environment that includes both nbappinator and iql and the notebook code below.
bqnt_oneshot_curve.ipynb
Intro¶
This is a one-shot (single cell) notebook that shows curves for a single security over multiple years.
This depends on iql
being installed. Run %pip install iql
if not.
What's shown here:
- Showing tenor and rate for a Curve / curvemembers
- Retrieving historical rates
- Displaying an animated Plotly graph
# If iql is not installed, uncomment next line
# %pip install iql
import os
import jinja2
import iql
import plotly.express as px
from functools import cache
import pandas as pd
# {{security}} comes from the jinja2 template rendering.
# pivot=(id, name) just means: put each id on a separate row, and the "name" field (the BQL field name) as the columns
# paramquery is an iql feature to substitute the $OFFSET field with the results of the query
assert "BQUANT_USERNAME" in os.environ, "This must be run in Bquant"
query = """
select *, year(date) as year from bql("
get(
id().id_dates as #date,
id().tenor as #tenor,
id().year_fraction as #year_fraction,
rate(side=mid, dates=$OFFSETY) as #rate
)
for(CURVEMEMBERS('{{security}}', dates=$OFFSETY))
", pivot=(id, name), paramquery=('$OFFSET', 'select * from range(-8, 1)'))
order by year_fraction, year
"""
@cache
def _exec_iql(query: str, **kwargs) -> pd.DataFrame:
try:
query_string = jinja2.Template(query).render(**kwargs)
df = iql.executedf(query_string)
return df
except Exception: # for blogging purposes, just use a dummy df
return pd.DataFrame(
{
"rate": [0.2 * i for i in range(1, 10)],
"tenor": range(1, 10),
"year_fraction": [0.3 * i for i in range(1, 10)],
"year": [2024] * 9,
}
)
curve_df = _exec_iql(query, security="YCGT0001 Index")
curve_df = curve_df.sort_values(by=["year_fraction", "year"])
# animation_frame is the thing that the animation will iterate over
fig = px.bar(
curve_df,
x="year_fraction",
y="rate",
animation_frame="year",
animation_group="year_fraction",
)
# Using the numerical year_fraction to position the X axis, but show the tenor label
fig.update_xaxes(tickvals=curve_df["year_fraction"], ticktext=curve_df["tenor"])
# Set x and y axes ranges, so the chart is stable when animating
fig.update_yaxes(range=[curve_df["rate"].min(), curve_df["rate"].max()])
fig.update_xaxes(
range=[curve_df["year_fraction"].min(), curve_df["year_fraction"].max()]
)
# Set a fixed bar width, so the bars don't change sizes during animation
fig.update_traces(width=0.5)
fig
Alternative: Show as a Gradient¶
Instead of showing as an animation, you could plot them all together. A gradient is one way to show time movement. In this case, I'll use a built-in list of colors: px.colors.sequential.Greys.
Since Greys has 9 values, I cheated a little by setting the range to only have 9 years. You can replace that with any list of Colors.
fig = px.line(
curve_df,
x="year_fraction",
y="rate",
color="year",
color_discrete_sequence=px.colors.sequential.Greys,
)
fig.update_xaxes(tickvals=curve_df["year_fraction"], ticktext=curve_df["tenor"])