FX volatility risk premia #
The category group contains simple estimates of FX volatility risk premia, calculated as the difference between implied and expected realized volatility divided by expected realized volatility. A positive volatility risk premium means that market participants demand a surcharge for exposure to volatility. Expected realized volatility is simply estimated using recent realized volatility, which is assumed to be an unbiased estimator over the short term. See Appendix 1 for further details.
FX volatility risk premia against dominant base currency #
Ticker : FXVRP7D_NSA / FXVRP2W_NSA / FXVRP1M_NSA / FXVRP2M_NSA / FXVRP3M_NSA
Label : FX volatility risk premia against main benchmark: 7 days / 2 weeks / 1 month / 2 months / 3 months
Definition : FX volatility risk premia based on exchange rate to the main benchmark currency: 7 days / 2 weeks / 1 month / 2 months / 3 months
Notes :
-
For most currencies the dominant benchmark is the dollar. For some European currencies (CHF, CZK, HUF, NOK, PLN, RON, SEK) the benchmark is the euro. And for GBP, TRY, and RUB an equally weighted basked of dollar and euro has been used.
-
Implied volatility is calculated as Black-Scholes volatility for at-the-money options with the expiries of 7 days to 3 months. The source of the underlying quotes is J.P. Morgan/ Dataquery.
-
Realised volatility is the exponentially weighted moving average (EWMA) of squared returns. The half-life has been set at 11 days, a convention frequently used for risk management.
FX volatility risk premia against USD #
Ticker : FXVRP7DUSD_NSA / FXVRP2WUSD_NSA / FXVRP1MUSD_NSA / FXVRP2MUSD_NSA / FXVRP3MUSD_NSA
Label : FX volatility risk premia against USD: 7 days / 2 weeks / 1 month / 2 months / 3 months
Definition : FX volatility risk premia based on exchange rate to the U.S. dollar: 7 days / 2 weeks / 1 month / 2 months / 3 months
Notes :
-
Implied volatility is calculated as Black-Scholes volatility for at-the-money options with the expiries of 7 days to 3 months. The source of the underlying quotes is J.P. Morgan/ Dataquery.
-
Realised volatility is the exponentially weighted moving average (EWMA) of squared returns. The half-life has been set at 11 days, a convention frequently used for risk management.
Imports #
Only the standard Python data science packages and the specialized
macrosynergy
package are needed.
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import math
import json
import yaml
import macrosynergy.management as msm
import macrosynergy.panel as msp
import macrosynergy.signal as mss
import macrosynergy.pnl as msn
from macrosynergy.download import JPMaQSDownload
from timeit import default_timer as timer
from datetime import timedelta, date, datetime
import warnings
warnings.simplefilter("ignore")
The nb_black extension is already loaded. To reload it, use:
%reload_ext nb_black
The
JPMaQS
indicators we consider are downloaded using the J.P. Morgan Dataquery API interface within the
macrosynergy
package. This is done by specifying
ticker strings
, formed by appending an indicator category code
<category>
to a currency area code
<cross_section>
. These constitute the main part of a full quantamental indicator ticker, taking the form
DB(JPMAQS,<cross_section>_<category>,<info>)
, where
<info>
denotes the time series of information for the given cross-section and category. The following types of information are available:
-
value
giving the latest available values for the indicator -
eop_lag
referring to days elapsed since the end of the observation period -
mop_lag
referring to the number of days elapsed since the mean observation period -
grade
denoting a grade of the observation, giving a metric of real time information quality.
After instantiating the
JPMaQSDownload
class within the
macrosynergy.download
module, one can use the
download(tickers,start_date,metrics)
method to easily download the necessary data, where
tickers
is an array of ticker strings,
start_date
is the first collection date to be considered and
metrics
is an array comprising the times series information to be downloaded.
cids_dm = ["GBP", "EUR", "JPY", "AUD", "CAD", "CHF", "NZD", "NOK", "SEK"]
cids_em = [
"CNY",
"KRW",
"SGD",
"MXN",
"INR",
"RUB",
"ZAR",
"TRY",
"BRL",
"TWD",
"PLN",
"THB",
"IDR",
"HUF",
"CZK",
"ILS",
"CLP",
"PHP",
"COP",
"MYR",
"RON",
"PEN",
]
cids = cids_dm + cids_em
vdic = {
"EUR": ["NOK", "SEK", "CHF", "CZK", "HUF", "PLN", "RON", "GBP", "TRY", "RUB"],
"USD": [
"CAD",
"CHF",
"GBP",
"EUR",
"NZD",
"AUD",
"SEK",
"NOK",
"JPY",
"ISK",
"DKK",
"CZK",
"RUB",
"RON",
"TRY",
"ZAR",
"ILS",
"HUF",
"PLN",
"PHP",
"SGD",
"THB",
"HKD",
"ARS",
"BRL",
"CLP",
"COP",
"PEN",
"MXN",
"MYR",
"TWD",
"INR",
"IDR",
"KRW",
"CNY",
],
}
main = [
"FXVRP7D_NSA",
"FXVRP2W_NSA",
"FXVRP1M_NSA",
"FXVRP2M_NSA",
"FXVRP3M_NSA",
"FXVRP7DUSD_NSA",
"FXVRP2WUSD_NSA",
"FXVRP1MUSD_NSA",
"FXVRP2MUSD_NSA",
"FXVRP3MUSD_NSA",
]
econ = ["FXXRxEASD_NSA", "BXBGDPRATIO_NSA_12MMA"]
mark = ["EQXR_NSA", "EQXR_VT10", "FXXR_NSA", "FXXR_VT10"] # market links
xcats = main + econ + mark
# Download series from J.P. Morgan DataQuery by tickers
start_date = "2000-01-01"
tickers = [cid + "_" + xcat for cid in cids for xcat in xcats]
print(f"Maximum number of tickers is {len(tickers)}")
# Retrieve credentials
client_id: str = os.getenv("DQ_CLIENT_ID")
client_secret: str = os.getenv("DQ_CLIENT_SECRET")
# Download from DataQuery
with JPMaQSDownload(client_id=client_id, client_secret=client_secret) as downloader:
start = timer()
df = downloader.download(
tickers=tickers,
start_date=start_date,
metrics=["value", "eop_lag", "mop_lag", "grading"],
suppress_warning=True,
show_progress=True,
)
end = timer()
dfd = df
print("Download time from DQ: " + str(timedelta(seconds=end - start)))
Maximum number of tickers is 496
Downloading data from JPMaQS.
Timestamp UTC: 2023-06-02 23:52:22
Connection successful!
Number of expressions requested: 1984
Requesting data: 100%|████████████████████████| 100/100 [00:30<00:00, 3.23it/s]
Downloading data: 100%|███████████████████████| 100/100 [01:41<00:00, 1.02s/it]
Download time from DQ: 0:02:32.275674
Availability #
cids_exp = cids # cids expected in category panels
msm.missing_in_df(dfd, xcats=main, cids=cids_exp)
Missing xcats across df: set()
Missing cids for FXVRP1MUSD_NSA: set()
Missing cids for FXVRP1M_NSA: set()
Missing cids for FXVRP2MUSD_NSA: set()
Missing cids for FXVRP2M_NSA: set()
Missing cids for FXVRP2WUSD_NSA: set()
Missing cids for FXVRP2W_NSA: set()
Missing cids for FXVRP3MUSD_NSA: set()
Missing cids for FXVRP3M_NSA: set()
Missing cids for FXVRP7DUSD_NSA: set()
Missing cids for FXVRP7D_NSA: set()
Most indicators are available by 2000. In some emerging markets, most notably Taiwan, data is only available from the mid-2000s onwards.
xcatx = main
cidx = cids_exp
dfx = msm.reduce_df(dfd, xcats=xcatx, cids=cidx)
dfs = msm.check_startyears(
dfx,
)
msm.visual_paneldates(dfs, size=(20, 3))
print("Last updated:", date.today())
Last updated: 2023-06-03
plot = msm.check_availability(
dfd, xcats=main, cids=cids_exp, start_size=(20, 2), start_years=False
)
msp.heatmap_grades(dfd, xcats=main, cids=cids_exp, start="2000-01-10", size=(18, 6))
xcatx = main[0:2]
cidx = cids_exp
msp.view_ranges(
dfd,
xcats=xcatx,
cids=cidx,
val="eop_lag",
title="End of observation period lags (ranges of time elapsed since end of observation period in days)",
start="2000-01-01",
kind="box",
size=(16, 4),
)
msp.view_ranges(
dfd,
xcats=xcatx,
cids=cidx,
val="mop_lag",
title="Median of observation period lags (ranges of time elapsed since middle of observation period in days)",
start="2000-01-01",
kind="box",
size=(16, 4),
)
History #
Shorter-dated premia (7 days - 2 weeks) #
Average premia have been positive for all currencies, but much more so for currencies of emerging markets than those of developed markets. Partially or temporarily managed currencies showed particularly high premia, plausibly because of repressed historic volatility.
Estimated premia display much volatility, which may largely reflect that historic return volatility is a very crude metric of future short-term volatility. The latter can depend a lot on calendar effects, forthcoming central bank meetings or political events, for example.
xcatx = ["FXVRP7D_NSA", "FXVRP2W_NSA"]
msp.view_ranges(
dfd,
xcats=xcatx,
cids=cidx,
sort_cids_by="mean",
start="2000-01-01",
title="Means and standard deviations of short-dated volatility risk premia, since 2000",
xcat_labels=["7 days", "2 weeks"],
kind="bar",
size=(16, 8),
)
xcats_sel = ["FXVRP7D_NSA", "FXVRP2W_NSA"]
msp.view_timelines(
dfd,
xcats=xcats_sel,
cids=cids_exp,
start="2000-01-01",
title="Shorter-dated FX volatility risk premia",
title_adj=0.95,
xcat_labels=None,
ncol=4,
same_y=False,
label_adj=0.05,
aspect=1.7,
all_xticks=False,
)
Using weekly averages, volatility risk premia have been predominantly positively correlated, with low and negative correlations focused on those currencies that trade against the euro.
dfd_vrp7 = dfd[dfd["xcat"] == "FXVRP7D_NSA"][
["real_date", "cid", "xcat", "value"]
].set_index("real_date")
dfw_vrp7 = dfd_vrp7.groupby(["cid", "xcat"]).resample("W").mean().reset_index()
msp.correl_matrix(dfw_vrp7, xcats="FXVRP7D_NSA", cids=cids_exp, size=(20, 14))
Importance #
Relevant research #
“Volatility risk premia – differences between implied and realized volatility – are plausible and empirically validated predictors of directional foreign exchange returns, particularly for EM currencies. The intuition is that excess implied volatility typically results from elevated risk aversion, which should be indicative of undershooting.” sr-sv
“Variance risk premiums mark the difference between implied (future) and past volatility. They indicate changes in risk aversion or uncertainty. As these changes may differ or have different implications across countries, they may cause FX overshooting and payback. The effect complements the simpler argument that rising currency volatility predicts lower FX carry returns. Academic papers support both effects empirically.” sr-sv
“The variance risk premium manifests as a long-term difference between option-implied and expected realized asset price volatility. It compensates investors for taking short volatility risk, which typically comes with a positive correlation with the equity market and occasional outsized drawdowns…Evidence since the mid-1990s suggests that variance is an attractive factor for the long run, particularly when positions take steady equal convexity exposure. Unlike other factor strategies, variance exposure has earned premia fairly consistently and typically recovered well from its intermittent large drawdowns.” sr-sv
Empirical clues #
As suggested by theory, volatility risk premia have bee significantly positively correlated with subsequent quarterly, monthly or weekly FX forward returns.
cr = msp.CategoryRelations(
dfd,
xcats=["FXVRP1M_NSA", "FXXR_VT10"],
cids=cids_exp,
freq="Q",
lag=1,
xcat_aggs=["last", "sum"],
fwin=1,
xcat_trims=[6, 40],
start="2002-01-01",
)
cr.reg_scatter(
title="Volatility risk premia and subsequent FX forward returns (all currencies since 2002)",
labels=False,
coef_box="upper right",
xlab="Volatility risk premium (1 month horizon), end of quarter",
ylab="Vol-targeted FX forward return, next quarter",
)
cr = msp.CategoryRelations(
dfd,
xcats=["FXVRP1M_NSA", "FXXR_VT10"],
cids=cids_exp,
freq="W",
lag=1,
xcat_aggs=["last", "sum"],
fwin=1,
xcat_trims=[6, 40],
start="2002-01-01",
)
cr.reg_scatter(
title="Volatility risk premia and subsequent weekly FX forward returns (all currencies since 2002)",
labels=False,
coef_box="upper right",
xlab="Volatility risk premium (1 month horizon), end of week",
ylab="Vol-targeted FX forward return, next week",
)
Appendices #
Appendix 1: Calculation details #
The category group contains estimates of the volatility risk premium , defined for our purposes as the ratio
where \(\sigma_i(\tau)\) is the volatility implied by the market for at-the-money options with time to maturity \(\tau\) , and \(\sigma_r(H)\) is the realised (historical) volatility, extracted using an exponentially weighted moving average filter of half-life \(H\) . The VRP is unitless and refers to the fractional deviation of implied volatility from realised volatility.
Notes:
-
Implied volatility is the paramter \(\sigma_i\) which when substitued into the Black-Scholes formula yields the market price for FX options. It codifies the standard deviation of returns of the underlying currency-pair, and will generally be a function of both the strike of the option ( \(K\) ) and the time to maturity of the option ( \(\tau\) ).
-
Realised volatility is here defined as the running standard deviation of (daily) FX spot returns, exponentially weighted through time: \begin{equation} \sigma_{r,t} = \sqrt{261 \cdot \sum_{k=0}^\infty \lambda^k r_{t-k}^2}, \end{equation} where \(\lambda\) is a decay parameter. Following the RiskMetrics convention we set \(\lambda = 0.94\) , or identically, the half-life \(H\) equal to ~11 days (the value for \(k\) which renders \(\lambda^k \approx 0.5\) ). The \(\sqrt{261}\) is an annualisation factor reflecting the fact that currencies typically have 261 observation periods in a year. Assuming that returns are i.i.d.: \(\text{Var}[r_{\text{yearly}}]=\text{Var}[r_1+r_2+\cdots+r_{261}]=\text{Var}[r_1]+\text{Var}[r_2]+\cdots+\text{Var}[r_{261}]=261 \cdot\text{Var}[r_{\text{daily}}]\) .
-
Implied volatility codifies the market’s expectations of future uncertainty. Meanwhile realised volatility looks backwards in time.
-
Ample empirical evidence suggests implied volatility tends to exceed realised volatility (the so-called premium).
The image above depicts the historical 1M implied volatility of USDCAD alongside the exponentially weighted realised volatility. Note that implied generally exceeds realised volatility, except for particularly turbulent periods.