Introduction
This tutorial implements a risk premia strategy that enters long-short positions in the Forex market based on signals from a skewness indicator. The strategy is derived from the paper “Risk Premia: Asymmetric Tail Risks and Excess Returns” by Lemperiere, Deremble, Nguyen, Seager, Potters, and Bouchaud.
One of the pillars in modern finance theory is the concept of risk premium, which states the riskier an investment is today the more profitable it should be in the long run. Risk premia strategies aim to profit from risk premiums. Lemperiere et al. describe a positive linear relationship between the Sharpe ratio of risk premia strategies and their negative skewness. It provides extensive evidence that risk premium is indeed strongly correlated with the skewness of a strategy, not only in the Equity world but also in currencies, Options, credit, etc.
Method
Step 1: Select our forex universe
In this algorithm, we use a fixed Forex universe which contains four symbols: "EURUSD", "AUDUSD", "USDCAD" and "USDJPY". By using self.AddForex(), we add the requested Forex data into the data feed.
# Add forex data of the following symbols
for pair in ['EURUSD', 'AUDUSD', 'USDCAD', 'USDJPY']:
self.add_forex(pair, Resolution.HOUR, Market.FXCM)
Step 2: Calculate the skewness indicator
In statistics, skewness is a measure of the asymmetry of the probability distribution of a real-valued random variable about its mean. Lemperiere et al. suggest there is a positive relationship between risk premia strategies and their negative skewness. We will use this relationship in our trading logic. Our implementation goes long for a Forex pair when the skewness indicator is lower than a minimum threshold (-0.6) and short the pair when the indicator exceeds a maximum threshold (0.6). For each Forex pair in the universe, we will calculate the skewness indicator with historical close prices and select the symbols as follows:
### In OnData()
# Get historical close data for the symbols
history = self.history(self.securities.keys, self.lookback, Resolution.DAILY)
history = history.drop_duplicates().close.unstack(level=0)
# Get the skewness of the historical data
skewness = self.get_skewness(history)
longSymbols = [k for k,v in skewness.items() if v < self.long_skew_level]
shortSymbols = [k for k,v in skewness.items() if v > self.short_skew_level]
def get_skewness(self, values):
'''
Get the skewness for all forex symbols based on its historical data
Ref: https://www.itl.nist.gov/div898/handbook/eda/section3/eda35b.htm
'''
# Get the numerator of the skewness
numer = ((values - values.mean()) ** 3).sum()
# Get the denominator of the skewness
denom = self.lookback * values.std() ** 3
# Return the skewness
return (numer/denom).to_dict()
Step 3: Rebalance weekly
We rebalance every week, liquidate the forex pairs not on the trading list, then repeat steps 1-2. We use equal weights for the long and short positions of securities in our portfolio.
# Liquidate the holdings for pairs that will not trade
for holding in self.portfolio.values:
symbol = holding.symbol
if holding.invested and symbol.value not in longSymbols + shortSymbols:
self.liquidate(symbol, 'Not selected pair')
# Open positions for the symbols with equal weights
count = len(longSymbols) + len(shortSymbols)
for pair in longSymbols:
self.set_holdings(pair, 1/count)
for pair in shortSymbols:
self.set_holdings(pair, -1/count)
# Set next rebalance time
self.next_rebalance += timedelta(self.rebalance_days)
Results
In this case our backtest results in a low annual return of approximately -0.330% over a decade. The poor performance may be due to several reasons:
- The fixed Forex universe chosen is not large enough to properly diversify market risk
- The thresholds for entering long and short positions (0.6, -0.6) may need adjustment
- The length of historical data might be not large enough for this weekly-rebalanced strategy.
We encourage the community to further develop this strategy by testing out different symbols, thresholds, and historical data lengths.
Derek Melchin
See the attached backtest for an updated version of the algorithm in PEP8 style.
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Daniel Chen
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!