| Overall Statistics |
|
Total Trades 59 Average Win 0.38% Average Loss -0.43% Compounding Annual Return 6.958% Drawdown 2.500% Expectancy 0.249 Net Profit 2.878% Sharpe Ratio 1.414 Probabilistic Sharpe Ratio 60.556% Loss Rate 33% Win Rate 67% Profit-Loss Ratio 0.87 Alpha 0.067 Beta -0.038 Annual Standard Deviation 0.04 Annual Variance 0.002 Information Ratio -1.545 Tracking Error 0.137 Treynor Ratio -1.482 Total Fees $0.00 Estimated Strategy Capacity $1400000.00 Lowest Capacity Asset AUDUSD 8G |
# Reference: Risk Premia in Forex Markets
# https://www.quantconnect.com/tutorials/strategy-library/risk-premia-in-forex-markets
class RiskPremiaForexAlgorithm(QCAlgorithm):
'''
Asymmetric Tail Risks and Excess Returns in Forex Markets
Paper: https://arxiv.org/pdf/1409.7720.pdf
'''
def Initialize(self):
# Set Date Range (Frame 1)
# self.SetStartDate(2016, 1, 1)
# self.SetEndDate(2020, 12, 31)
# Set Date Range (Frame 2)
# self.SetStartDate(2011, 1, 1)
# self.SetEndDate(2015, 12, 31)
# Set Date Range (Frame 3)
# self.SetStartDate(2006, 1, 1)
# self.SetEndDate(2010, 12, 31)
# Set Date Range (Frame 4)
# self.SetStartDate(2001, 1, 1)
# self.SetEndDate(2005, 12, 31)
# self.SetCash(100000) # Set Strategy Cash
self.SetStartDate(2021, 1, 1)
self.SetEndDate(2021, 6, 4)
self.SetCash(11840) # Set Strategy Cash
# Add forex data of the following symbols
for pair in ['EURUSD', 'GBPUSD', 'USDCHF', 'AUDUSD', 'USDCAD', 'USDJPY']:
self.AddForex(pair, Resolution.Hour, Market.Oanda)
self.lookback = 30 # Length(days) of historical data
self.nextRebalance = self.Time # Next time to rebalance
self.rebalanceDays = 7 # Rebalance every 7 days (weekly)
self.longSkewLevel = -0.6 # If the skewness of a pair is less than this level, enter a long postion
self.shortSkewLevel = 0.6 # If the skewness of a pair is larger than this level, enter a short position
def OnData(self, data):
'''
Rebalance weekly for each forex pair
'''
# Do nothing until next rebalance
if self.Time < self.nextRebalance:
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.GetSkewness(history)
longSymbols = [k for k,v in skewness.items() if v < self.longSkewLevel]
shortSymbols = [k for k,v in skewness.items() if v > self.shortSkewLevel]
# 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.SetHoldings(pair, 1/count)
for pair in shortSymbols:
self.SetHoldings(pair, -1/count)
# Set next rebalance time
self.nextRebalance += timedelta(self.rebalanceDays)
def GetSkewness(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()