Back to Community
Positive skew in invested assets as proxy for long term outperformance / increasing skewness of momentum trading strategy.

Dear all,

the following presents a model portfolio where the equity selection is done by picking the stocks with the highest positive skew.

Thesis: Equities with positive skew will outperform the market over long time periods and help to shift negatively skewed trading startegies towards more positive skews.

The concept of skew is well know amongst traders and investors, since it can be applied to both trading strategies and individual assets one might choose to invest in.
In this short research, it was explored if it is a viable strategy to hold an assortment of the equities with the most positive skew. The rational behind this thesis is that in theory, statistically speaking, this should reduce the impact of "negative surprises" and increase the impact of "positive surprises", leading to less downside volatility and to the possibility of outperforming a mostly negatively skewed market.
For this, in the first part the constituents of the top 500 US stocks by market cap where sorted based upon skew. Every month, the entire capital was invested into the top 30 companies with the biggest positive skew. This was only done when the SPY happened to be in an uptrend.
If the SPY was in a downtrend, the entire portfolio was liquidated at once.
While the results are only mediocre at best, the data still shows that skewness can indeed provide a certain amount of edge in the market.

In the second part of this research, it was evaluated if adding a skewness criterion to an existing momentum trading strategy could help increase the skewness of the strategy towards 0. It was found that for the particular ruleset employed, considering the skewness of the individual equities did indeed increase the skewness of the strategy by about 1/3 and reduced the kurtosis towards 3.

Best regards, Kristof

Clone Algorithm
11
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import quantopian.algorithm as algo
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.factors import SimpleMovingAverage, CustomFactor
from quantopian.pipeline.classifiers.morningstar import Sector
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume
from quantopian.pipeline.filters.morningstar import Q500US
#from quantopian.pipeline.filters import QTradableStocksUS
import numpy as np
from scipy import stats


def initialize(context):
  
    context.momentum_window = 90
    context.minimum_skew = 0
    context.number_of_stocks = 30
    context.spy = sid(8554)
    context.trends = []
    
    # Monthly trading
    schedule_function(my_rebalance, date_rules.month_start(), time_rules.market_open(hours=1))
     
  
    attach_pipeline(make_pipeline(), 'us_500')
#only trade if SPY is in uptrend.
def get_uptrend(context, data):
    spy_hist = data.history(context.spy, 'close', 200, '1d')
    spy_sma = spy_hist.mean()
    if spy_hist[-1] > spy_sma:
        return True
    return False
    
         
def make_pipeline():

    # Base universe set to the Q500US
    base_universe = Q500US()
    yesterday_close = USEquityPricing.close.latest
     
    pipe = Pipeline(
        screen = base_universe,
        columns = {
            'close': yesterday_close,
        }
    )
    return pipe 
 
def my_rebalance(context,data):
  
    trend = get_uptrend(context, data)
    print(trend)
    context.output = pipeline_output('us_500') # update the current top 500 us stocks
    context.security_list = context.output.index
    
    momentum_list = np.log(data.history(context.security_list, "close", context.momentum_window, "1d")).pct_change()[1:].skew().sort_values(ascending=False)
    
    print(momentum_list)
    
    #ranking_table = momentum_list.sort_values(ascending=False) # Sorted
    
    buy_list = momentum_list[:context.number_of_stocks] # These we want to buy
    print(buy_list)
    
    # Let's trade!
    for security in context.portfolio.positions:
        if security not in buy_list or trend == False:
            order_target(security, 0) # If a stock in the portfolio is not in buy list, sell it!
  
    for security in context.security_list:
        if security in buy_list:
            if buy_list[security] < context.minimum_skew:
                weight = 0.0
            else:
                if trend == True:
                    weight = 1.0 / context.number_of_stocks # Equal size to keep simple
                    order_target_percent(security, weight) # Trade!
There was a runtime error.
2 responses

Good job, Kristof. Very neat and clean code. I think the negative skewness could be used in lower timeframe to detect early stock drop.

Thank you! I was thinking along the same lines when doing the research but I'm not sure if the skewness would be to slow to be used that way. Anyhow, it might be worth trying out a skew based SL. I'll have to code it but maybe the 1st derivative of the skewness versus time could signal an increasing likelyhood for a major stock drop when it crosses the x-axis from top to bottom, thus signaling an inflection of the skewness.