I've attempted to replicate the Pipeline factors used in the ML algo posted here:

https://www.quantopian.com/posts/machine-learning-on-quantopian-part-3-building-an-algorithm

Note also that the ML algo is now on Github:

The end goal is to re-factor the ML algo, so that various alpha combination techniques can be applied easily and compared. This is a first step in that direction.

One specific snag is that I have been unable to use the built-in
`MACDSignal`

in a custom factor. So, if anyone knows how to do it, it would be much appreciated (my attempt is commented out...if you run it, you'll see the error).

Clone Algorithm

20

Loading...

There was an error loading this backtest.
Retry

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.data.builtin import USEquityPricing from quantopian.pipeline.data import Fundamentals, psychsignal from quantopian.pipeline.factors import AnnualizedVolatility, SimpleBeta, Returns, MACDSignal, CustomFactor import quantopian.optimize as opt from quantopian.pipeline.experimental import risk_loading_pipeline from quantopian.pipeline.filters import QTradableStocksUS from sklearn import preprocessing from scipy.stats.mstats import winsorize from zipline.utils.numpy_utils import ( repeat_first_axis, repeat_last_axis, ) import numpy as np MAX_GROSS_EXPOSURE = 1.0 NUM_POSITIONS = 400 # even number MAX_POSITION_SIZE = 2.0/NUM_POSITIONS MIN_BETA_EXPOSURE = -0.3 MAX_BETA_EXPOSURE = 0.3 # Factor preprocessing settings WIN_LIMIT = 0.025 # factor preprocess winsorize limit def preprocess(a): # find inf and -inf and replace with nan inds = np.where(np.isinf(a)) a[inds] = np.nan # demean and replace nans with 0 a = np.nan_to_num((a-np.nanmean(a))) a = winsorize(a,limits=(WIN_LIMIT,WIN_LIMIT)) return preprocessing.scale(a) def make_features(): class MeanReversion1M(CustomFactor): inputs = (Returns(window_length=21),) window_length = 252 def compute(self, today, assets, out, monthly_rets): out[:] = preprocess(np.divide( monthly_rets[-1] - np.nanmean(monthly_rets, axis=0), np.nanstd(monthly_rets, axis=0))) class MoneyflowVolume5d(CustomFactor): inputs = (USEquityPricing.close, USEquityPricing.volume) # we need one more day to get the direction of the price on the first # day of our desired window of 5 days window_length = 6 def compute(self, today, assets, out, close_extra, volume_extra): # slice off the extra row used to get the direction of the close # on the first day close = close_extra[1:] volume = volume_extra[1:] dollar_volume = close * volume denominator = dollar_volume.sum(axis=0) difference = np.diff(close_extra, axis=0) direction = np.where(difference > 0, 1, -1) numerator = (direction * dollar_volume).sum(axis=0) out[:] = preprocess(np.divide(numerator, denominator)) class PriceOscillator(CustomFactor): inputs = (USEquityPricing.close,) window_length = 252 def compute(self, today, assets, out, close): four_week_period = close[-20:] out[:] = preprocess(np.divide( np.nanmean(four_week_period, axis=0), np.nanmean(close, axis=0))-1) class Trendline(CustomFactor): inputs = [USEquityPricing.close] window_length = 252 _x = np.arange(window_length) _x_var = np.var(_x) def compute(self, today, assets, out, close): x_matrix = repeat_last_axis( (self.window_length - 1) / 2 - self._x, len(assets), ) y_bar = np.nanmean(close, axis=0) y_bars = repeat_first_axis(y_bar, self.window_length) y_matrix = close - y_bars out[:] = preprocess(np.divide( (x_matrix * y_matrix).sum(axis=0) / self._x_var, self.window_length) ) class Volatility3M(CustomFactor): inputs = [Returns(window_length=2)] window_length = 63 def compute(self, today, assets, out, rets): out[:] = preprocess(np.nanstd(rets, axis=0)) class AdvancedMomentum(CustomFactor): inputs = [USEquityPricing.close, Returns(window_length=126)] window_length = 252 def compute(self, today, assets, out, prices, returns): out[:] = preprocess(np.divide( ( (prices[-21] - prices[-252]) / prices[-252] - prices[-1] - prices[-21] ) / prices[-21], np.nanstd(returns, axis=0) )) class asset_growth_3m(CustomFactor): inputs = [Returns( window_length=63, )] window_length = 1 def compute(self, today, assets, out, returns): out[:] = preprocess(returns[-1,:]) class asset_to_equity_ratio(CustomFactor): inputs = [Fundamentals.total_assets, Fundamentals.common_stock_equity] window_length = 1 def compute(self, today, assets, out, total_assets, common_stock_equity): out[:] = preprocess(total_assets[-1,:] / common_stock_equity[-1,:]) class capex_to_cashflows(CustomFactor): inputs = [Fundamentals.capital_expenditure, Fundamentals.free_cash_flow] window_length = 1 def compute(self, today, assets, out, capital_expenditure, free_cash_flow): out[:] = preprocess(capital_expenditure[-1,:] / free_cash_flow[-1,:]) class ebitda_yield(CustomFactor): inputs = [Fundamentals.ebitda, USEquityPricing.close] window_length = 1 def compute(self, today, assets, out, ebitda, close): out[:] = preprocess((ebitda[-1,:] * 4) / close[-1,:]) class ebita_to_assets(CustomFactor): inputs = [Fundamentals.ebit, Fundamentals.total_assets] window_length = 1 def compute(self, today, assets, out, ebit, total_assets): out[:] = preprocess((ebit[-1,:] * 4) / total_assets[-1,:]) class return_on_total_invest_capital(CustomFactor): inputs = [Fundamentals.roic] window_length = 1 def compute(self, today, assets, out, roic): out[:] = preprocess(roic[-1,:]) class net_income_margin(CustomFactor): inputs = [Fundamentals.net_margin] window_length = 1 def compute(self, today, assets, out, net_margin): out[:] = preprocess(net_margin[-1,:]) class operating_cashflows_to_assets(CustomFactor): inputs = [Fundamentals.operating_cash_flow, Fundamentals.total_assets] window_length = 1 def compute(self, today, assets, out, operating_cash_flow, total_assets): out[:] = preprocess((operating_cash_flow[-1,:] * 4) / total_assets[-1,:]) class price_momentum_3m(CustomFactor): inputs = [Returns(window_length=63)] window_length = 1 def compute(self, today, assets, out, returns): out[:] = preprocess(returns[-1,:]) class returns_39w(CustomFactor): inputs = [Returns(window_length=215)] window_length = 1 def compute(self, today, assets, out, returns): out[:] = preprocess(returns[-1,:]) # class MACD_Signal(CustomFactor): # inputs = [MACDSignal] # window_length = 1 # def compute(self, today, assets, out, macdsignal): # out[:] = preprocess(macdsignal[-1,:]) return { 'Asset Growth 3M': asset_growth_3m, 'Asset to Equity Ratio': asset_to_equity_ratio, 'Capex to Cashflows': capex_to_cashflows, 'EBIT to Assets': ebita_to_assets, 'EBITDA Yield': ebitda_yield, 'Mean Reversion 1M': MeanReversion1M, 'Moneyflow Volume 5D': MoneyflowVolume5d, 'Net Income Margin': net_income_margin, 'Operating Cashflows to Assets': operating_cashflows_to_assets, 'Price Momentum 3M': price_momentum_3m, 'Price Oscillator': PriceOscillator, 'Return on Invest Capital': return_on_total_invest_capital, '39 Week Returns': returns_39w, 'Trendline': Trendline, 'Volatility 3m': Volatility3M, 'Advanced Momentum': AdvancedMomentum, # 'MACD Signal Line': MACD_Signal, } def make_pipeline(): universe = QTradableStocksUS() beta = SimpleBeta(target=sid(8554),regression_length=260, allowed_missing_percentage=1.0 ) features = make_features() combined_alpha = None for name, f in features.iteritems(): if combined_alpha == None: combined_alpha = f(mask=universe) else: combined_alpha += f(mask=universe) longs = combined_alpha.top(NUM_POSITIONS/2) shorts = combined_alpha.bottom(NUM_POSITIONS/2) long_short_screen = (longs | shorts) pipe = Pipeline(columns = { 'combined_alpha':combined_alpha, 'beta':beta, }, screen = long_short_screen ) return pipe def initialize(context): attach_pipeline(make_pipeline(), 'long_short_equity_template') attach_pipeline(risk_loading_pipeline(), 'risk_loading_pipeline') # 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) # comment out lines below for realistic backtesting 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') context.risk_loading_pipeline = pipeline_output('risk_loading_pipeline') def recording_statements(context, data): record(num_positions=len(context.portfolio.positions)) record(leverage=context.account.leverage) def rebalance(context, data): pipeline_data = context.pipeline_data # demean and normalize combined_alpha = pipeline_data.combined_alpha combined_alpha = combined_alpha - combined_alpha.mean() combined_alpha = combined_alpha/combined_alpha.abs().sum() objective = opt.MaximizeAlpha(combined_alpha) constraints = [] constraints.append(opt.MaxGrossExposure(MAX_GROSS_EXPOSURE)) constraints.append(opt.DollarNeutral()) constraints.append( opt.PositionConcentration.with_equal_bounds( min=-MAX_POSITION_SIZE, max=MAX_POSITION_SIZE )) beta_neutral = opt.FactorExposure( loadings=pipeline_data[['beta']], min_exposures={'beta':MIN_BETA_EXPOSURE}, max_exposures={'beta':MAX_BETA_EXPOSURE} ) constraints.append(beta_neutral) risk_model_exposure = opt.experimental.RiskModelExposure( context.risk_loading_pipeline.dropna(), version=opt.Newest, ) constraints.append(risk_model_exposure) order_optimal_portfolio( objective=objective, constraints=constraints, )