A Statistical FX Factor Model | Dean Markwick
Factor models attempt to explain asset returns. You can approach this in two ways: define the factors you think are relevant, or use statistical learning to build the relevant factors from the data. My previous post took the first approach. This post will use principal component analysis (PCA) to let the data tell us which factors are most relevant in an FX factor model.
Enjoy these types of posts? Then sign up for my newsletter.
My last post on factor models (A Fundamental FX Factor Model) was about defining the factors we think are relevant to FX returns. These included:
The DXY return (Making Sense of the DXY)
Macro ETF returns to represent stocks, bonds, gold, and oil
Momentum/reversion factors
The final results were okay, and we found four factors were significant: 1-month momentum, 6-month momentum, DXY, and the EM factor.
This time, we will start with the same dataset, but use statistical methods to learn the factors directly rather than presuming what might explain FX returns.
For consistency when comparing to the fundamental model, we will stick with weekly data, but only for the currencies. We do not need the macro ETFs yet. As always, we normalise the log returns by their mean and standard deviation, using a rolling calculation to minimise forward information leakage. Refer to this if the z-score normalisation differs from the fundamental model.
df = df.with_columns(<br>pl.col("close").log().diff().over("ccy").alias("log_return"))
df = df.with_columns(pl.col("log_return").rolling_std(window_size=52).over("ccy").alias("vol_52"),<br>pl.col("log_return").rolling_mean(window_size=52).over("ccy").alias("avg_52")
df = df.with_columns(((pl.col("log_return") - pl.col("avg_52"))/pl.col("vol_52")).alias("log_return_scaled"))
df = df.with_columns(pl.col("log_return_scaled").clip(-2, 2).alias("log_return_scaled_clipped"))
We then need to pivot the data from long to wide to give a matrix with each column as the normalised returns. This is the shape of the data structure we need specifically for a PCA. It might be tempting to say “PCA analysis,” but that would be an example of RAS syndrome.
returns = df.pivot(on="ccy", index = "datetime", values = "log_return_scaled_clipped").drop_nulls().sort("datetime")<br>returns = returns.with_row_index("index")
But before we dive straight into applying PCA, what is it exactly?
Principal Component Analysis
Principal component analysis (PCA) is a technique that breaks down a matrix into vectors that explain most of the variance. It is better to visualise this in two dimensions.
We generate a set of points with a specific correlation matrix. PCA has found the two main components that describe where the variance is. If you rotate by these vectors, you turn the samples into a cloud with unit noise. In this case, we have started with two dimensions and have two principal components, but it does not need to be one-to-one; you can have fewer principal components than dimensions.
In finance, this is a great technique because everything is so correlated. Assets are not moving completely independently; there are key factors . This is why PCA is the tool of choice. We are letting the data tell us the key elements rather than enforcing them ourselves.
PCA and FX
As always, Python, and specifically scikit-learn, has a simple method of calculating PCA on the data. For now, we are assuming five principal components. We have to drop the index and datetime columns so our data is just pure returns.
from sklearn.decomposition import PCA
r = returns.drop(["index", "datetime"])
pca = PCA(n_components=5)<br>pca.fit(r)
F = r @ pca.components_.T # (T, K) — factor realisations<br>W = pca.components_
We are interested in how much variance each component explains.
pca.explained_variance_ratio_
array([0.42833194, 0.09363639, 0.04578692, 0.0385175 , 0.03270084])
This is a normalised measure of how much variation each component describes. So the first component explains 43%, the second factor 9%, and so on. It drops off quite significantly, so you can see why five components are enough for now, and more importantly, the first factor is such a large proportion.
To look at the weights, we can plot a heat map.
All the pairs in PC1 are positive. The first factor is the “market” factor, which in FX is most likely the dollar factor. This makes it the DXY equivalent. You could say it is better than the DXY because it contains more currencies and the weights are more reflective of the actual links between the currencies. The other factors are harder to interpret, but we will come back to this problem.
We can also look at the factor returns.
Factor 1 is very noisy but slowly drifting higher. Factor 3 has been positive since 2022, but all the others are oscillating around 0.
This is a good illustration of what PCA does on a dataset of returns, but it is completely useless for building a factor model. By using the entire dataset, we have...