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