# From https://www.quantopian.com/posts/relevant-fundamental-factors#5b9563549ad4a0004e03850d
# Working Capital to Assets
# Retained Earnings to Assets
# EBITA to Assets
# Sales to Assets
# Mkt Cap to Liabilities
# Basically, valuation ratios are most commonly used when 'pricing' securities.
# - Present value models (Dividens / FCFE / FCFF), require to compute CAPM required return on equity and the growth rate
# - Multipliers models (P/E, P/S, P/B...)
# Other models to consider:
# - Altman z-score (credit risk)
# - Beinish M-score (reporting quality & account manipulations)
# - ROE decomposition
import numpy as np
import pandas as pd
from quantopian.research import run_pipeline
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Returns, AnnualizedVolatility
from quantopian.pipeline.classifiers.fundamentals import Sector
from quantopian.pipeline.filters import QTradableStocksUS
from time import time
from quantopian.pipeline.data.psychsignal import stocktwits
from sklearn import preprocessing
import alphalens as al
MORNINGSTAR_SECTOR_CODES = {
-1: 'Misc',
101: 'Basic Materials',
102: 'Consumer Cyclical',
103: 'Financial Services',
104: 'Real Estate',
205: 'Consumer Defensive',
206: 'Healthcare',
207: 'Utilities',
308: 'Communication Services',
309: 'Energy',
310: 'Industrials',
311: 'Technology' ,
}
def make_factors():
class MessageSum(CustomFactor):
inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close, stocktwits.bull_scored_messages, stocktwits.bear_scored_messages, stocktwits.total_scanned_messages]
window_length = 5
def compute(self, today, assets, out, high, low, close, bull, bear, total):
v = np.nansum((high-low)/close, axis=0)
out[:] = preprocess(v*np.nansum(total*(bear-bull), axis=0))
class fcf(CustomFactor):
inputs = [Fundamentals.fcf_yield]
window_length = 1
def compute(self, today, assets, out, fcf_yield):
out[:] = preprocess(np.nan_to_num(fcf_yield[-1,:]))
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 mean_rev(CustomFactor):
inputs = [USEquityPricing.high,USEquityPricing.low,USEquityPricing.close]
window_length = 30
def compute(self, today, assets, out, high, low, close):
p = (high+low+close)/3
m = len(close[0,:])
n = len(close[:,0])
b = np.zeros(m)
a = np.zeros(m)
for k in range(10,n+1):
price_rel = np.nanmean(p[-k:,:],axis=0)/p[-1,:]
wt = np.nansum(price_rel)
b += wt*price_rel
price_rel = 1.0/price_rel
wt = np.nansum(price_rel)
a += wt*price_rel
out[:] = preprocess(b-a)
class volatility(CustomFactor):
inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close, USEquityPricing.volume]
window_length = 5
def compute(self, today, assets, out, high, low, close, volume):
vol = np.nansum(volume,axis=0)*np.nansum(np.absolute((high-low)/close),axis=0)
out[:] = preprocess(-vol)
class growthscore(CustomFactor):
inputs = [Fundamentals.growth_score]
window_length = 1
def compute(self, today, assets, out, growth_score):
out[:] = preprocess(growth_score[-1,:])
class peg_ratio(CustomFactor):
inputs = [Fundamentals.peg_ratio]
window_length = 1
def compute(self, today, assets, out, peg_ratio):
out[:] = preprocess(-1.0/peg_ratio[-1,:])
return {
'MessageSum': MessageSum,
'FCF': fcf,
'Direction': Direction,
'mean_rev': mean_rev,
'volatility': volatility,
'GrowthScore': growthscore,
'PegRatio': peg_ratio,
}
def preprocess(a):
a = np.nan_to_num(a - np.nanmean(a))
return preprocessing.scale(a)
universe = QTradableStocksUS()
factors = make_factors()
combined_alpha = None
for name, f in factors.iteritems():
if combined_alpha == None:
combined_alpha = f(mask=universe)
else:
combined_alpha = combined_alpha+f(mask=universe)
pipe = Pipeline(
columns = {
'CombinedAlpha' : combined_alpha,
'Sector' : Sector()
},
screen=universe
)
start_timer = time()
results = run_pipeline(pipe, '2016-08-29', '2018-08-29')
end_timer = time()
results.fillna(value=0);
print "Time to run pipeline %.2f secs" % (end_timer - start_timer)
my_factor = results['CombinedAlpha']
sectors = results['Sector']
asset_list = results.index.levels[1].unique()
prices = get_pricing(asset_list, start_date='2016-08-29', end_date='2018-09-07', fields='open_price')
periods = (1,3,5,10,21)
factor_data = al.utils.get_clean_factor_and_forward_returns(factor=my_factor,
prices=prices,
groupby=sectors,
groupby_labels=MORNINGSTAR_SECTOR_CODES,
periods=periods,
quantiles = 5)
mean_return_by_q_daily, std_err_by_q_daily = al.performance.mean_return_by_quantile(factor_data,
by_date=True)
mean_return_by_q, std_err_by_q = al.performance.mean_return_by_quantile(factor_data,
by_group=False)
ic = al.performance.factor_information_coefficient(factor_data)
al.tears.create_information_tear_sheet(factor_data)
al.tears.create_full_tear_sheet(factor_data, by_group=True);