Here's an example of using an RSI factor to reduce the
`short_term_reversal`

risk. Will post tear sheets next.

Clone Algorithm

136

Loading...

There was an error loading this backtest.

Backtest from
to
with
initial capital

Cumulative performance:

Algorithm
Benchmark

Custom data:

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 |

from quantopian.algorithm import attach_pipeline, pipeline_output, order_optimal_portfolio from quantopian.pipeline import Pipeline from quantopian.pipeline.factors import CustomFactor, RollingLinearRegressionOfReturns from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.data import Fundamentals, psychsignal from quantopian.pipeline.classifiers.fundamentals import Sector from quantopian.pipeline.factors import Latest, Returns, AverageDollarVolume import quantopian.optimize as opt from sklearn import preprocessing from quantopian.pipeline.experimental import QTradableStocksUS from scipy.stats.mstats import winsorize import numpy as np import pandas as pd # Liquidity screen parameters LIQUIDITY_LOOKBACK_LENGTH = 100 UNIVERSE_SIZE = 1200 # Constraint Parameters MAX_GROSS_EXPOSURE = 1.0 NUM_TOTAL_POSITIONS = 600 NUM_LONG_POSITIONS = NUM_TOTAL_POSITIONS/2 NUM_SHORT_POSITIONS = NUM_LONG_POSITIONS MAX_LONG_POSITION_SIZE = 2.0/NUM_TOTAL_POSITIONS MAX_SHORT_POSITION_SIZE = MAX_LONG_POSITION_SIZE # Risk Exposures MAX_SECTOR_EXPOSURE = 0.005 MAX_BETA_EXPOSURE = 0.05 # Factor preprocessing settings WIN_LIMIT = 0.01 # factor preprocess winsorize limit def make_factors(): class mean_rev(CustomFactor): inputs = [USEquityPricing.open,USEquityPricing.high,USEquityPricing.low,USEquityPricing.close] window_length = 5 def compute(self, today, assets, out, open, high, low, close): p = (open+high+low+close)/4 rng = (high-low)/close m = len(p) a = np.zeros(m) for k in range(1,m+1): a = preprocess(np.mean(p[-k:,:],axis=0)/close[-1,:]) w = np.nanmean(rng[-k:,:],axis=0) a += w*a out[:] = preprocess(a) class fcf(CustomFactor): inputs = [Fundamentals.fcf_yield] window_length = 1 def compute(self, today, assets, out, fcf_yield): out[:] = preprocess(fcf_yield) class earn_yield(CustomFactor): inputs = [Fundamentals.earning_yield] window_length = 1 def compute(self, today, assets, out, earn_yield): out[:] = preprocess(earn_yield) class sentiment(CustomFactor): inputs = [psychsignal.stocktwits.bull_minus_bear] window_length = 1 def compute(self, today, assets, out, sentiment): out[:] = preprocess(sentiment) class MessageSum(CustomFactor): inputs = [psychsignal.stocktwits.bull_scored_messages, psychsignal.stocktwits.bear_scored_messages] window_length = 5 def compute(self, today, assets, out, bull, bear): out[:] = preprocess(np.nansum(bear-bull, axis=0)) class Volatility(CustomFactor): inputs = [USEquityPricing.high,USEquityPricing.low,USEquityPricing.close] window_length = 21 def compute(self, today, assets, out, high, low, close): p = (high-low)/close out[:] = preprocess(-np.nansum(p,axis=0)) class Direction(CustomFactor): inputs = [USEquityPricing.open, USEquityPricing.close] window_length = 21 def compute(self, today, assets, out, open, close): p = (close-open)/close out[:] = preprocess(-np.nansum(p,axis=0)) class RSI(CustomFactor): window_length = 15 inputs = (USEquityPricing.close,) def compute(self, today, assets, out, closes): diffs = np.diff(closes, axis=0) ups = np.nanmean(np.clip(diffs, 0, np.inf), axis=0) downs = abs(np.nanmean(np.clip(diffs, -np.inf, 0), axis=0)) rs = ups/downs rsi = 100.0-100.0/(1.0+rs) out[:] = preprocess(rsi) return { 'MeanRev': mean_rev, 'FCF': fcf, 'Yield': earn_yield, 'Sentiment': sentiment, 'MessageSum': MessageSum, # 'Volatility': Volatility, 'Direction': Direction, 'RSI': RSI, } def make_pipeline(): pricing = USEquityPricing.close.latest base_universe = QTradableStocksUS() & (pricing > 5) ev = Latest(inputs=[Fundamentals.enterprise_value], mask=base_universe) ev_positive = ev > 0 ebitda = Latest(inputs=[Fundamentals.ebitda], mask=ev_positive) ebitda_positive = ebitda > 0 market_cap = Latest(inputs=[Fundamentals.market_cap]) market_cap_top = market_cap.top(UNIVERSE_SIZE, mask=ebitda_positive) universe = ( AverageDollarVolume(window_length=LIQUIDITY_LOOKBACK_LENGTH) .top(NUM_TOTAL_POSITIONS, mask=market_cap_top) ) sector = Sector(mask=universe) # sector needed to construct portfolio # =============================================== factors = make_factors() combined_alpha = None for name, f in factors.iteritems(): if combined_alpha == None: combined_alpha = f(mask=universe) else: combined_alpha += f(mask=universe) longs = combined_alpha.top(NUM_LONG_POSITIONS) shorts = combined_alpha.bottom(NUM_SHORT_POSITIONS) long_short_screen = (longs | shorts) beta = 0.66*RollingLinearRegressionOfReturns( target=sid(8554), returns_length=5, regression_length=260, mask=long_short_screen ).beta + 0.33*1.0 # Create pipeline pipe = Pipeline(columns = { 'combined_alpha':combined_alpha, 'sector':sector, 'market_beta':beta }, screen = long_short_screen) return pipe def initialize(context): context.spy = sid(8554) attach_pipeline(make_pipeline(), 'long_short_equity_template') # Schedule my rebalance function schedule_function(func=rebalance, date_rule=date_rules.every_day(), time_rule=time_rules.market_open(minutes=60), half_days=True) # record my portfolio variables at the end of day schedule_function(func=recording_statements, date_rule=date_rules.every_day(), time_rule=time_rules.market_close(), half_days=True) # set_commission(commission.PerShare(cost=0, min_trade_cost=0)) # set_slippage(slippage.FixedSlippage(spread=0)) def before_trading_start(context, data): context.pipeline_data = pipeline_output('long_short_equity_template') def recording_statements(context, data): record(num_positions=len(context.portfolio.positions)) def rebalance(context, data): pipeline_data = context.pipeline_data risk_factor_exposures = pd.DataFrame({ 'market_beta':pipeline_data.market_beta.fillna(1.0) }) denom = np.nansum(np.absolute(pipeline_data.combined_alpha.values)) objective = opt.MaximizeAlpha(pipeline_data.combined_alpha/denom) constraints = [] constraints.append(opt.MaxGrossExposure(MAX_GROSS_EXPOSURE)) constraints.append(opt.DollarNeutral()) constraints.append( opt.NetGroupExposure.with_equal_bounds( labels=pipeline_data.sector, min=-MAX_SECTOR_EXPOSURE, max=MAX_SECTOR_EXPOSURE, )) neutralize_risk_factors = opt.FactorExposure( loadings=risk_factor_exposures, min_exposures={'market_beta':-MAX_BETA_EXPOSURE}, max_exposures={'market_beta':MAX_BETA_EXPOSURE} ) constraints.append(neutralize_risk_factors) constraints.append( opt.PositionConcentration.with_equal_bounds( min=-MAX_SHORT_POSITION_SIZE, max=MAX_LONG_POSITION_SIZE )) try: order_optimal_portfolio( objective=objective, constraints=constraints, ) except: return def preprocess(a): a = np.nan_to_num(a - np.nanmean(a)) a = winsorize(a,limits=(WIN_LIMIT,WIN_LIMIT)) a = a/np.sum(np.absolute(a)) return preprocessing.scale(a)