Notebook

Using the NetExposure constraint to limit short

In [1]:
# Pipeline imports
from quantopian.pipeline import CustomFactor, Pipeline
from quantopian.pipeline.filters import QTradableStocksUS, StaticAssets
from quantopian.pipeline.factors import DailyReturns

# Import run_pipeline method
from quantopian.research import run_pipeline

# Import optimize
import quantopian.experimental.optimize as opt
In [2]:
# Pipeline definition 
# Make a pipeline with defined percent of longs and shorts
def  make_pipeline(universe_size=100, short_percent=50): 
    factor_universe = QTradableStocksUS()
    
    # Some random data. Universe with highest returns.
    my_universe = DailyReturns(mask=factor_universe).top(universe_size)
    
    # Filter for specific assets to pass to CompareAssets factor
    # Could be the result of some calculation but just using a static filter for testing
    shorts = DailyReturns(mask=my_universe).percentile_between(0, short_percent)
    longs = my_universe & ~shorts
        
    return Pipeline(
        columns={
            'shorts': shorts,
            'longs': longs,
                },
        screen=my_universe
    )
In [4]:
def get_weights(universe_size=100, short_percent=50, date='2019-01-02'):
    df = run_pipeline(make_pipeline(universe_size=universe_size, 
                                    short_percent=short_percent), date, date)
    
    # Create a column for weight - equal weight longs and shorts
    weight = 1.0 / df.index.size
    df.loc[df.longs, 'weights'] = weight
    df.loc[df.shorts, 'weights'] = -weight    
    
    return df.xs(date, level=0).weights
In [35]:
# Lets run optimize to limit our short exposure
# Create a set of weights with 90% short
orig_weights = get_weights(universe_size=100, short_percent=90)

# One issue is that the optimize constraint defines 'net exposure' and not 'short weight'
# So, we need to do a bit of conversion. Start with our max desired short weight
max_percent_shorts = .8

# This implies the following max short exposure 
max_short_exposure = (max_percent_shorts * -1.0) + (1.0-max_percent_shorts)

weights = orig_weights
loop = 0
while True:
    # Now calculate an optimized portfolio using a net_exposure_constraint
    objective = opt.TargetWeights(weights)
    net_exposure_constraint = opt.NetExposure(max_short_exposure, 1.0)
    opt_weights = opt.calculate_optimal_portfolio(objective, constraints=[net_exposure_constraint])
    gross_exposure = opt_weights.abs().sum()
    weights = opt_weights / gross_exposure
    loop += 1
    if gross_exposure > .99:
        break


# Let's get some information about our results
short_percent = opt_weights.where(opt_weights<0).sum()
long_percent = opt_weights.where(opt_weights>0).sum()
net_exposure = short_percent + long_percent
gross_exposure = abs(short_percent) + abs(long_percent)

display('number of iterations: {}'.format(loop))
display('gross exposure: {}'.format(gross_exposure))
display('net exposure: {}'.format(net_exposure))
display('short percent: {}'.format(short_percent))
display(opt_weights)

Pipeline Execution Time: 0.49 Seconds
'number of iterations: 6'
'gross exposure: 0.994177152131'
'net exposure: -0.599999998959'
'short percent: -0.797088575545'
Equity(301 [ALKS])     -0.008857
Equity(351 [AMD])      -0.008857
Equity(2385 [DY])      -0.008857
Equity(3436 [HAE])     -0.008857
Equity(3885 [IMGN])    -0.008857
Equity(4031 [IONS])    -0.008857
Equity(4413 [AXGN])    -0.008857
Equity(5847 [PDLI])    -0.008857
Equity(6924 [SKY])     -0.008857
Equity(8910 [ANIK])    -0.008857
Equity(9693 [BKS])      0.019709
Equity(10417 [ARWR])   -0.008857
Equity(13601 [THG])    -0.008857
Equity(14379 [GEF])    -0.008857
Equity(14927 [FCN])    -0.008857
Equity(14972 [NBIX])   -0.008857
Equity(15622 [ANF])    -0.008857
Equity(17908 [PGNX])   -0.008857
Equity(19336 [WFT])     0.019709
Equity(19973 [EXTR])   -0.008857
Equity(20306 [UTHR])   -0.008857
Equity(21413 [LXRX])   -0.008857
Equity(21447 [SGMO])   -0.008857
Equity(21666 [MRVL])   -0.008857
Equity(22091 [ATTU])   -0.008857
Equity(22418 [ATRS])   -0.008857
Equity(22651 [HRTX])    0.019709
Equity(23709 [NFLX])   -0.008857
Equity(25660 [TRQ])    -0.008857
Equity(25972 [DVAX])   -0.008857
                          ...   
Equity(47432 [LOXO])   -0.008857
Equity(47845 [DERM])    0.019709
Equity(48015 [VRAY])    0.019709
Equity(48029 [UPLD])   -0.008857
Equity(48077 [PRAH])   -0.008857
Equity(48254 [WK])     -0.008857
Equity(48543 [SHAK])   -0.008857
Equity(48628 [NVTA])   -0.008857
Equity(49000 [BPMC])   -0.008857
Equity(49016 [COLL])   -0.008857
Equity(49060 [SHOP])   -0.008857
Equity(49189 [LNTH])   -0.008857
Equity(49236 [NTRA])   -0.008857
Equity(49572 [KURA])   -0.008857
Equity(49736 [EDIT])    0.019709
Equity(49934 [NTLA])   -0.008857
Equity(49995 [RETA])   -0.008857
Equity(50077 [TWLO])   -0.008857
Equity(50169 [TCMD])   -0.008857
Equity(50264 [EVBG])   -0.008857
Equity(50400 [CRSP])   -0.008857
Equity(50713 [GOOS])   -0.008857
Equity(50758 [OKTA])   -0.008857
Equity(50839 [BHVN])   -0.008857
Equity(50927 [SSTI])   -0.008857
Equity(50978 [MGY])    -0.008857
Equity(51124 [KL])     -0.008857
Equity(51314 [MDB])    -0.008857
Equity(51428 [BAND])   -0.008857
Equity(51542 [DNLI])   -0.008857
dtype: float64
In [36]:
# IT WORKS!
# But notice one had to iterate 6 times to get leverage above .99
In [ ]: