The strategy trades 39 markets, going long on the most negatively skewed quintile and short on the most positively skewed, equally weighting and rebalancing monthly based on 24-month skewness.

I. STRATEGY IN A NUTSHELL

This strategy trades 39 MSCI USD-denominated markets using 24-month historical skewness. Each month, the most negatively skewed quintile is bought and the most positively skewed quintile is sold, with markets equally weighted and the portfolio rebalanced monthly to exploit skewness-driven inefficiencies.

II. ECONOMIC RATIONALE

Investors overvalue small-probability, high-payoff outcomes, favoring positively skewed markets. This effect is stronger in globally integrated, liquid markets with international investors, while illiquid or local markets show a weaker skewness influence, confirming skewness as a driver of returns.

III. SOURCE PAPER

Skewness Preference Across Countries [Click to Open PDF]

Zaremba, Nowak

<Abstract>

The prospect theory implies that that assets with positively skewed returns should be traded at premium to assets with negative skewness. We hypothesize that in integrated financial markets this concept should also hold for entire country equity portfolios. The article examines the linkages between country-level expected returns and past skewness. We document a robust negative relationship between skewness and future returns. The phenomenon is strongest for large, liquid and developed stock markets and negligible for small, illiquid and undeveloped ones. Furthermore, additional sorts on skewness can improve performance of cross-country value and momentum strategies. The study is based on sorting and cross-sectional tests conducted within a sample 78 country equity markets for years 1999-2014.

IV. BACKTEST PERFORMANCE

Annualised Return13.76%
Volatility25.66%
Beta-0.014
Sharpe Ratio0.54
Sortino Ratio0.372
Maximum DrawdownN/A
Win Rate52%

V. FULL PYTHON CODE

import numpy as np
from AlgorithmImports import *
from scipy.stats import skew
class SkewnessEffectEquities(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.symbols = ["EWJ",  # iShares MSCI Japan Index ETF
                        "EZU",  # iShares MSCI Eurozone ETF
                        "EFNL", # iShares MSCI Finland Capped Investable Market Index ETF
                        "EWW",  # iShares MSCI Mexico Inv. Mt. Idx
                        "ERUS", # iShares MSCI Russia ETF
                        "IVV",  # iShares S&P 500 Index
                        "ICOL", # Consumer Discretionary Select Sector SPDR Fund
                        "AAXJ", # iShares MSCI All Country Asia ex Japan Index ETF
                        "AUD",  # Australia Bond Index Fund
                        "EWQ",  # iShares MSCI France Index ETF
                        "BUND", # Pimco Germany Bond Index Fund
                        "EWH",  # iShares MSCI Hong Kong Index ETF
                        "EPI",  # WisdomTree India Earnings ETF
                        "EIDO"  # iShares MSCI Indonesia Investable Market Index ETF
                        "EWI",  # iShares MSCI Italy Index ETF
                        "GAF",  # SPDR S&P Emerging Middle East & Africa ETF
                        "ENZL", # iShares MSCI New Zealand Investable Market Index Fund
                        "NORW"  # Global X FTSE Norway 30 ETF
                        "EWY",  # iShares MSCI South Korea Index ETF
                        "EWP",  # iShares MSCI Spain Index ETF
                        "EWD",  # iShares MSCI Sweden Index ETF
                        "EWL",  # iShares MSCI Switzerland Index ETF
                        "GXC",  # SPDR S&P China ETF
                        "EWC",  # iShares MSCI Canada Index ETF
                        "EWZ",  # iShares MSCI Brazil Index ETF
                        "ARGT", # Global X FTSE Argentina 20 ETF
                        "AND",  # Global X FTSE Andean 40 ETF
                        "AIA",  # iShares S&P Asia 50 Index ETF
                        "EWO",  # iShares MSCI Austria Investable Mkt Index ETF
                        "EWK",  # iShares MSCI Belgium Investable Market Index ETF
                        "BRAQ", # Global X Brazil Consumer ETF
                        "ECH",  # iShares MSCI Chile Investable Market Index ETF
                        "CHIB", # Global X China Technology ETF
                        "EGPT", # Market Vectors Egypt Index ETF
                        "ADRU"  # BLDRS Europe 100 ADR Index ETF
                        ]
        
        self.lookup_period = 24 * 21
        self.SetWarmup(self.lookup_period)
        self.data = {}
        
        for symbol in self.symbols:
            data = self.AddEquity(symbol, Resolution.Daily)
            data.SetLeverage(10)
            self.data[symbol] = RollingWindow[float](self.lookup_period)
            
        self.Schedule.On(self.DateRules.MonthStart(self.symbols[0]), self.TimeRules.AfterMarketOpen(self.symbols[0]), self.Rebalance)
    
    def OnData(self, data):
        for symbol in self.data:
            if symbol in data and data[symbol]:
                price = data[symbol].Value
                if price != 0:
                    self.data[symbol].Add(price)
                
    def Rebalance(self):
        if self.IsWarmingUp: return
    
        # Skewness calculation
        skewness_data = {}
        for symbol in self.symbols:
            if self.data[symbol].IsReady and self.Securities[symbol].IsTradable and (self.Time.date() - self.Securities[symbol].GetLastData().Time.date()).days < 5:
                prices = np.array([x for x in self.data[symbol]])
                returns = (prices[:-1]-prices[1:])/prices[1:]
                if len(returns) == self.lookup_period-1:
                    # NOTE: Manual skewness calculation example
                    # avg = np.average(returns)
                    # std = np.std(returns)
                    # skewness = (sum(np.power((x - avg), 3) for x in returns)) / ((self.return_history[symbol].maxlen-1) * np.power(std, 3))
                    skewness_data[symbol] = skew(returns)
                
        # Skewness sorting
        sorted_by_skewness = sorted(skewness_data.items(), key = lambda x: x[1], reverse = True)
        quintile = int(len(sorted_by_skewness)/5)
        long = [x[0] for x in sorted_by_skewness[-quintile:]]
        short = [x[0] for x in sorted_by_skewness[:quintile]]
        
        # Trade execution
        invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in long + short:
                self.Liquidate(symbol)
        long_count = len(long)
        short_count = len(short)
        
        for symbol in long:
            self.SetHoldings(symbol, 1 / long_count)
        for symbol in short:
            self.SetHoldings(symbol, -1 / short_count)

VI. Backtest Performance

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading