Overall Statistics
Total Trades
102
Average Win
0.31%
Average Loss
-0.47%
Compounding Annual Return
-5.581%
Drawdown
5.500%
Expectancy
-0.166
Net Profit
-3.766%
Sharpe Ratio
-0.763
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
0.67
Alpha
-0.027
Beta
-0.063
Annual Standard Deviation
0.05
Annual Variance
0.003
Information Ratio
-1.641
Tracking Error
0.132
Treynor Ratio
0.607
Total Fees
$0.00
class RiskPremiaForexAlgorithm(QCAlgorithm):
    '''
    Asymmetric Tail Risks and Excess Returns in Forex Market
    Paper: https://arxiv.org/pdf/1409.7720.pdf
    '''
    def Initialize(self):
        self.SetStartDate(2019, 1, 1)   # Set Start Date
        self.SetEndDate(2019, 9, 1)     # Set End Date
        self.SetCash(100000)            # Set Strategy Cash
        
        # Add forex data of the following symbols
        forexs = ["EURUSD", "AUDUSD", "USDCAD", "USDJPY"]
        self.symbols = [self.AddForex(forex, Resolution.Hour, Market.FXCM).Symbol for forex in forexs]
        
        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 forex is less than this level, enter a long postion
        self.shortSkewLevel = 0.6       # If the skewness of a froex 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
        
        # Liquidate the holdings when the rebalance day comes
        for holding in self.Portfolio.Values:
            if holding.Invested:
                self.Liquidate(holding.Symbol,"Liquidate")
        
        # Get historical close data for the symbols
        history = self.History(self.symbols, self.lookback, Resolution.Daily).close.unstack(level=0)
        
        # The lists containing symbols for long & short
        longSymbols = []
        shortSymbols = []

        # Get skewness for each symbol
        for forex in self.symbols:
            skewness = self.GetSkewness(history[str(forex)].values)
            
            # If the skewness of a forex is less than the longSkew level, 
            # put this symbol into the longSymbols list
            if skewness < self.longSkewLevel:
                longSymbols.append(forex)
                
            # If the skewness of a forex is larger than the shortSkew level, 
            # put this symbol into the shortSymbols list
            if skewness > self.shortSkewLevel:
                shortSymbols.append(forex)
            
        # Open positions for the symbols with equal weights
        count = len(longSymbols) + len(shortSymbols)
        
        # If no suitable symbols, return
        if count == 0:
            return
        
        # Long positions
        for forex in longSymbols:
            self.SetHoldings(forex, 1/count)
            
        # Short postions
        for forex in shortSymbols:
            self.SetHoldings(forex, -1/count)
            
        # Set next rebalance time
        self.nextRebalance += timedelta(self.rebalanceDays)
    
    def GetSkewness(self, values):
        '''
        Get the skewness for a forex symbol based on its historical data
        Ref: https://www.itl.nist.gov/div898/handbook/eda/section3/eda35b.htm
        '''
        # Get standard deviation of the values
        sd = values.std()
        
        # Get the numerator of the skewness 
        numer = ((values - values.mean()) ** 3).sum()
        
        # Get the denominator of the skewness
        denom = self.lookback * sd ** 3
        
        # Return the skewness
        return numer/denom