| Overall Statistics |
|
Total Orders 864 Average Win 0.80% Average Loss -0.72% Compounding Annual Return -0.329% Drawdown 23.100% Expectancy -0.001 Start Equity 100000.00 End Equity 96544.44 Net Profit -3.456% Sharpe Ratio -0.208 Sortino Ratio -0.233 Probabilistic Sharpe Ratio 0.004% Loss Rate 52% Win Rate 48% Profit-Loss Ratio 1.10 Alpha -0.011 Beta -0.007 Annual Standard Deviation 0.057 Annual Variance 0.003 Information Ratio -0.707 Tracking Error 0.148 Treynor Ratio 1.622 Total Fees $0.00 Estimated Strategy Capacity $490000.00 Lowest Capacity Asset AUDUSD 5O Portfolio Turnover 15.39% |
#region imports
from AlgorithmImports import *
#endregion
class RiskPremiaForexAlgorithm(QCAlgorithm):
'''
Asymmetric Tail Risks and Excess Returns in Forex Markets
Paper: https://arxiv.org/pdf/1409.7720.pdf
'''
def initialize(self):
self.set_start_date(2009, 1, 1) # Set Start Date
self.set_end_date(2019, 9, 1) # Set End Date
self.set_cash(100000) # Set Strategy Cash
# Add forex data of the following symbols
for pair in ['EURUSD', 'AUDUSD', 'USDCAD', 'USDJPY']:
self.add_forex(pair, Resolution.HOUR, Market.FXCM)
self._lookback = 30 # Length(days) of historical data
self._next_rebalance = self.time # Next time to rebalance
self._rebalance_days = 7 # Rebalance every 7 days (weekly)
self._long_skew_level = -0.6 # If the skewness of a pair is less than this level, enter a long postion
self._short_skew_level = 0.6 # If the skewness of a pair is larger than this level, enter a short position
def on_data(self, data):
'''
Rebalance weekly for each forex pair
'''
# Do nothing until next rebalance
if self.time < self._next_rebalance:
return
# 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)
long_symbols = [k for k,v in skewness.items() if v < self._long_skew_level]
short_symbols = [k for k,v in skewness.items() if v > self._short_skew_level]
# 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 long_symbols + short_symbols:
self.liquidate(symbol, 'Not selected pair')
# Open positions for the symbols with equal weights
count = len(long_symbols) + len(short_symbols)
for pair in long_symbols:
self.set_holdings(pair, 1/count)
for pair in short_symbols:
self.set_holdings(pair, -1/count)
# Set next rebalance time
self._next_rebalance += timedelta(self._rebalance_days)
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()