Overall Statistics
Total Trades
686
Average Win
0.84%
Average Loss
-0.72%
Compounding Annual Return
-0.716%
Drawdown
24.000%
Expectancy
-0.021
Net Profit
-7.386%
Sharpe Ratio
-0.061
Loss Rate
55%
Win Rate
45%
Profit-Loss Ratio
1.16
Alpha
-0.003
Beta
-0.004
Annual Standard Deviation
0.056
Annual Variance
0.003
Information Ratio
-0.701
Tracking Error
0.148
Treynor Ratio
0.96
Total Fees
$0.00
class RiskPremiaForexAlgorithm(QCAlgorithm):
    '''
    Asymmetric Tail Risks and Excess Returns in Forex Markets
    Paper: https://arxiv.org/pdf/1409.7720.pdf
    '''
    def Initialize(self):
        self.SetStartDate(2009, 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
        for pair in ['EURUSD', 'AUDUSD', 'USDCAD', 'USDJPY']:
            self.AddForex(pair, Resolution.Hour, Market.FXCM)

        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()