frank,
For some reason, known only to Quantopian staff, I could not attach my backtests.
Here is my code.
# Momentum, Value, and a little bit of mean reverting by Giuseppe Moriconi, modified by Vladimir
# https://www.quantopian.com/posts/momentum-value-and-a-little-bit-of-mean-reverting
from quantopian.pipeline.factors import SimpleMovingAverage, Returns
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline.classifiers.morningstar import Sector
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline import CustomFactor
from quantopian.pipeline import Pipeline
import pandas as pd
import numpy as np
import math
def initialize(context):
context.equity_allocation = 0
context.hedge_allocation = 0
context.sectors_weigth = {}
context.market_trend = 0
context.sectors_to_buy = []
context.sectors_weight = {}
context.sectors_number_to_buy = []
context.sectors_dict = {
symbol('XLB'): 101, # XLB Materials
symbol('XLY'): 102, # XLY Consumer Cyclical
# symbol('XLF'): 103, # XLF Financials
symbol('XLP'): 205, # XLP Consumer Defensive
symbol('XLV'): 206, # XLV Healthcare
symbol('XLU'): 207, # XLU Utilities
symbol('XLE'): 309, # XLE Energy
symbol('XLI'): 310, # XLI Industrials
symbol('XLK'): 311, # XLK Tech
}
context.sectors_list = context.sectors_dict.keys()
context.bonds = [symbol('TLT'), # TLT
symbol('TIP'), # TIP, IEF
]
schedule_function(rebalance, date_rules.month_start(), time_rules.market_open(hours=1),)
schedule_function(record_vars, date_rules.month_start(), time_rules.market_open(hours=1),)
base_universe = QTradableStocksUS()
sector = Sector() # sector code
ev_to_ebitda = Fundamentals.ev_to_ebitda.latest # ev_ebitda
returns = Returns(window_length = 120, mask=base_universe) # returns
volatility = Volatility_Daily_Annual() # std
market_cap = Fundamentals.market_cap.latest # market_cap
ev_screen = 0.0 < ev_to_ebitda < 10.0 # fundamental_screen
# ev_screen = (0.0 < ev_to_ebitda) & (ev_to_ebitda < 10.0) # fundamental_screen
profit = ROA() + ROAChange() + CashFlow() + CashFlowFromOps()
leverage = LongTermDebtRatioChange() + CurrentDebtRatioChange() + SharesOutstandingChange()
operating = GrossMarginChange() + AssetsTurnoverChange()
piotroski = profit + leverage + operating
piotroski_screen = ((piotroski >= 7) | (piotroski <= 3))
market_cap_screen = market_cap > 1e9
columns = {'sector': sector, 'piotroski': piotroski, 'ret': returns, 'vol' : volatility}
screen = (base_universe & ev_screen & piotroski_screen & market_cap_screen)
pipe = Pipeline(columns, screen)
attach_pipeline(pipe, 'pipeline')
def rebalance(context, data):
stocks_to_buy = []
sectors_to_buy = []
sectors_number_to_buy = []
########## ETF SELECTION #############
p = data.history(context.sectors_list, 'close', 252,'1d')
mean_20= p.rolling(20).mean().iloc[-1]
mean_240 = p.rolling(240).mean().iloc[-1]
ratio = mean_20/mean_240
uptrending_sectors = ratio.where(ratio > 1.0).dropna()
last_month_performance = p.pct_change(periods= 30).iloc[-1]
#ascending = false significa in alto i maggiori valori
last_month_sorted = last_month_performance.sort_values(ascending = False)
last_month_sorted = last_month_sorted[last_month_sorted.index.isin(uptrending_sectors.index)]
uptrending_sectors_list = uptrending_sectors.index.tolist()
######### EQUITY ALLOCATION BASED ON TREND ################
n = len(uptrending_sectors_list)
# ----------- MOST OF THE SECTORS ARE UPTRENDING ------------- #
if n >= 8:
market_trend = 2
equity_allocation = 0.8
#peggiori rendimenti ultimo mese
sectors_to_buy = last_month_sorted[-2:].index.tolist()
# ----------- MORE THAN HALF OF THE SECTORS ARE UPTRENDING ------------- #
elif 5 < n < 8:
market_trend = 1
equity_allocation = 0.6
sectors_to_buy = last_month_sorted[-2:].index.tolist()
# ----------- LESS THAN HALF OF THE SECTORS ARE UPTRENDING ------------- #
elif 2 < n <= 5:
market_trend = 0
equity_allocation = 0.4
sectors_to_buy = last_month_sorted[:2].index.tolist()
# ----------- FEW OF THE SECTORS ARE UPTRENDING ------------- #
elif 1 < n <= 2:
market_trend = -1
equity_allocation = 0.2
sectors_to_buy = last_month_sorted.index.tolist()
# ----------- NO SECTOR UPTREND ------------- #
elif n <= 1:
market_trend = -2
equity_allocation = 0
sectors_to_buy = []
hedge_allocation = 1.0 - equity_allocation
for k, v in context.sectors_dict.iteritems():
if k in sectors_to_buy:
sectors_number_to_buy.append(v)
########## PIPELINE FILTER BASED ON PREVIOUSLY SELECTED SECTORS #########
pipe = pipeline_output('pipeline')
grouped = pipe.groupby('sector')
for sector, group in grouped:
if sector in sectors_number_to_buy:
group['ranked_vol'] = group['vol'].rank(ascending = False) #low volatility
group['ranked_returns'] = group['ret'].rank(ascending = True) # high returns
group['ranked_pio'] = group['piotroski'].rank(ascending = True) #high piotroski score
group['total_rank'] = group['ranked_vol'] + group['ranked_returns'] + group['ranked_pio']
stocks_to_buy_for_this_sector =group['total_rank'].dropna().nlargest(3).index.tolist()
stocks_to_buy = stocks_to_buy + stocks_to_buy_for_this_sector
# Make global variables to plot lines etc
context.stocks_to_buy = stocks_to_buy
context.hedge_allocation = hedge_allocation
context.market_trend = market_trend
context.equity_allocation = equity_allocation
context.sectors_to_buy = sectors_to_buy
'''
print '-------------REBALANCE-------------'
print 'equity allocation %s' %(context.equity_allocation)
print 'bonds allocation %s' %(context.hedge_allocation)
print 'sectors to buy %s' %( context.sectors_to_buy)
print 'portfolio positions %s' %(context.portfolio.positions.keys())
print 'stocks to buy: %s' %(context.stocks_to_buy)
'''
for bond in context.bonds:
order_target_percent(bond, context.hedge_allocation/len(context.bonds))
for stock in context.portfolio.positions :
if stock not in context.stocks_to_buy and stock not in context.bonds:
order_target_percent(stock, 0)
for stock in context.stocks_to_buy :
if get_open_orders(stock): continue
else:
order_target_percent(stock, context.equity_allocation / len(context.stocks_to_buy))
def record_vars(context, data):
record(leverage=context.account.leverage, trend = context.market_trend, equity_allocation = context.equity_allocation, n_sectors = len(context.sectors_to_buy))
class Piotroski(CustomFactor):
inputs = [
Fundamentals.roa,
Fundamentals.operating_cash_flow,
Fundamentals.cash_flow_from_continuing_operating_activities,
Fundamentals.long_term_debt_equity_ratio,
Fundamentals.current_ratio,
Fundamentals.shares_outstanding,
Fundamentals.gross_margin,
Fundamentals.assets_turnover,
]
window_length = 22
def compute(self, today, assets, out,
roa, cash_flow, cash_flow_from_ops,
long_term_debt_ratio, current_ratio, shares_outstanding,
gross_margin, assets_turnover):
profit = (
(roa[-1] > 0).astype(int) +
(cash_flow[-1] > 0).astype(int) +
(roa[-1] > roa[0]).astype(int) +
(cash_flow_from_ops[-1] > roa[-1]).astype(int)
)
leverage = (
(long_term_debt_ratio[-1] < long_term_debt_ratio[0]).astype(int) +
(current_ratio[-1] > current_ratio[0]).astype(int) +
(shares_outstanding[-1] <= shares_outstanding[0]).astype(int)
)
operating = (
(gross_margin[-1] > gross_margin[0]).astype(int) +
(assets_turnover[-1] > assets_turnover[0]).astype(int)
)
out[:] = profit + leverage + operating
class ROA(CustomFactor):
window_length = 1
inputs = [Fundamentals.roa]
def compute(self, today, assets, out, roa):
out[:] = (roa[-1] > 0).astype(int)
class ROAChange(CustomFactor):
window_length = 22
inputs = [Fundamentals.roa]
def compute(self, today, assets, out, roa):
out[:] = (roa[-1] > roa[0]).astype(int)
class CashFlow(CustomFactor):
window_length = 1
inputs = [Fundamentals.operating_cash_flow]
def compute(self, today, assets, out, cash_flow):
out[:] = (cash_flow[-1] > 0).astype(int)
class CashFlowFromOps(CustomFactor):
window_length = 1
inputs = [Fundamentals.cash_flow_from_continuing_operating_activities, Fundamentals.roa]
def compute(self, today, assets, out, cash_flow_from_ops, roa):
out[:] = (cash_flow_from_ops[-1] > roa[-1]).astype(int)
class LongTermDebtRatioChange(CustomFactor):
window_length = 22
inputs = [Fundamentals.long_term_debt_equity_ratio]
def compute(self, today, assets, out, long_term_debt_ratio):
out[:] = (long_term_debt_ratio[-1] < long_term_debt_ratio[0]).astype(int)
class CurrentDebtRatioChange(CustomFactor):
window_length = 22
inputs = [Fundamentals.current_ratio]
def compute(self, today, assets, out, current_ratio):
out[:] = (current_ratio[-1] > current_ratio[0]).astype(int)
class SharesOutstandingChange(CustomFactor):
window_length = 22
inputs = [Fundamentals.shares_outstanding]
def compute(self, today, assets, out, shares_outstanding):
out[:] = (shares_outstanding[-1] <= shares_outstanding[0]).astype(int)
class GrossMarginChange(CustomFactor):
window_length = 22
inputs = [Fundamentals.gross_margin]
def compute(self, today, assets, out, gross_margin):
out[:] = (gross_margin[-1] > gross_margin[0]).astype(int)
class AssetsTurnoverChange(CustomFactor):
window_length = 22
inputs = [Fundamentals.assets_turnover]
def compute(self, today, assets, out, assets_turnover):
out[:] = (assets_turnover[-1] > assets_turnover[0]).astype(int)
class Volatility_Daily_Annual(CustomFactor):
inputs = [USEquityPricing.close]
window_length = 120
def compute(self, today, assets, out, close):
# [0:-1] is needed to remove last close since diff is one element shorter
daily_returns = np.diff(close, axis = 0) / close[0:-1]
out[:] = daily_returns.std(axis = 0) * math.sqrt(252)