from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline
from quantopian.pipeline.factors import SimpleMovingAverage,Latest,RSI, MACDSignal, SimpleBeta
from quantopian.pipeline.filters import Q1500US, QTradableStocksUS, Q500US, Q3000US
from quantopian.research import get_pricing
from quantopian.pipeline.data import USEquityPricing
from quantopian.pipeline.factors import CustomFactor
from alphalens.utils import get_clean_factor_and_forward_returns
from alphalens.tears import create_full_tear_sheet
from quantopian.pipeline import CustomFilter
import pandas as pd
import numpy as np
import talib
#This class is to create the price action pattern of engulging,
class engulf(CustomFilter):
inputs = [USEquityPricing.close, USEquityPricing.low, USEquityPricing.open, USEquityPricing.high]
window_length = 2
def compute(self, today, assets, out, close, low, open1, high):
condition_1 = (high[-1] > high[-2]) & (low[-1]<low[-2])
condition_2 = (close[-1] > close[-2]) & (open1[-1]<open1[-2])
condition_3 = close[-1] > open1[-1]
condition_4 = (close[-1] > open1[-2]) & (open1[-1]<close[-2])
result = condition_1 & condition_2 & condition_3 & condition_4
out[:] = result
class volume(CustomFilter):
inputs = [USEquityPricing.volume]
window_length = 6
def compute(self, today, assets, out, vol):
low_condition_1 = vol[-1] / np.mean(vol, axis = 0)>0.29
low_condition_2 = (vol[-1] / np.mean(vol, axis = 0))<1.54
out[:] = low_condition_1
def filtro():
sector = Fundamentals.morningstar_sector_code.latest
industry = Fundamentals.morningstar_industry_code.latest
#universe= Q3000US() & sector.element_of([206])
universe = Q3000US() & QTradableStocksUS()
sma20 = SimpleMovingAverage(inputs = [USEquityPricing.close], window_length=20 )
sma50 = SimpleMovingAverage(inputs = [USEquityPricing.close], window_length=50, mask = universe )
sma200 = SimpleMovingAverage(inputs = [USEquityPricing.close], window_length=200 )
price = SimpleMovingAverage(inputs = [USEquityPricing.close], window_length=1, mask = universe )
high = SimpleMovingAverage(inputs = [USEquityPricing.high], window_length=1, mask = universe )
low = SimpleMovingAverage(inputs = [USEquityPricing.low], window_length=1, mask = universe )
close = SimpleMovingAverage(inputs = [USEquityPricing.close], window_length=1, mask = universe )
open1 = SimpleMovingAverage(inputs = [USEquityPricing.open], window_length=1, mask = universe )
betaytd = SimpleBeta(target=symbols(8554) , regression_length=100)
betayty = SimpleBeta(target=symbols(8554),regression_length = 253)
rsi = RSI()
macd = MACDSignal()
condition_1 = rsi < 40
condition_2 = macd < -1.5
technical_condition = condition_2 & condition_1
condition_1 = price > sma20
condition_2 = price < sma50
condition_3 = price < sma200
condition_4 = 5 < price <120
sma_condition = condition_1 & condition_4 & condition_3 & condition_2
mid_price = (high+low)/2
box = (close-open1).abs()
limit_box = (high-low)*0.25
condition_5 = close > mid_price
condition_6 = open1 > mid_price
condition_7 = box <= limit_box
hammer = condition_5 & condition_6 & condition_7
price_action = (hammer & volume()) | engulf()
filter1= technical_condition | sma_condition
return Pipeline(columns={"filter":filter1} ,screen = universe & price_action )
factor = run_pipeline(filtro(), start_date="2019-05-5", end_date="2020-06-11")
asset_list = factor.index.levels[1].unique()
pricing = get_pricing(asset_list,start_date="2019-4-5", end_date="2020-6-10", fields= "close_price" )
merged_data = get_clean_factor_and_forward_returns(factor, pricing, periods=[1,2,3,4,5], quantiles=None, bins=[-1, 0.5, 2])
Create a full tear sheet to give us some statistics about this factor.
Lets specifically look at the Mean Period Wise Return Top Quantile (bps)
.
create_full_tear_sheet(merged_data)
Let's see if we can get the same numbers for Mean Period Wise Return Top Quantile (bps)
is we calculate it manually. This is calculated as the mean of daily means. So, we need to do two mean calculations. First group by day to get the mean of each day. Second, group by factor quantile to get the mean of those means for each quantile.
# First create a 'grouper' to group by date
day_grouper = ['factor_quantile', merged_data.index.get_level_values('date')]
# Now use that grouper and get the mean of each returns column
daily_means = merged_data.groupby(day_grouper)['1D','2D','3D','4D','5D'].mean()
# Print to see what we have
daily_means
# Next, create a 'grouper' to group by our factor quantiles
quantile_grouper = [daily_means.index.get_level_values('factor_quantile')]
# Now use that grouper and get the mean of the daily means by factor quantile
quantile_means = daily_means.groupby(quantile_grouper).mean()
# Print to see what we have
quantile_means
Hmm, these don't match the returns in the tear sheet output. Why is that? The create_full_tear_sheet
method, by default, assumes a long short portfolio. It assumes one will short stocks in the lower half of the quantiles and long the stocks in the upper half. The mean returns we calculated above just took the returns and didn't account for long and short. The short returns would need to be reversed.
We won't do that here. Rather, one can use the long_short
parameter to force create_full_tear_sheet
to assume an all long portfolio. The returns should then be the returns we calculated. Let's check that.
display(quantile_means)
create_full_tear_sheet(merged_data, long_short=False)
First it seems the numbers are off by a factor of 1000. This makes sense since create_full_tear_sheet
displays results in basis points (bps). However, the 1D values are correct but the others seem to be off.
The create_full_tear_sheet
method displays all the returns normalized to the 1 day compounded rate. In other words, the returns shown in create_full_tear_sheet
, if compounded, will equal the total returns we calculated. Another way to look at it is the nth root of the day n returns will equal the create_full_tear_sheet
returns.
Lets chek that out.
# Let's just look at quantile 2
quantile_2_total_returns = quantile_means.query('factor_quantile==2')
quantile_2_total_returns
# Let's also drop the multi-index on the columns (to make indexing easier)
#quantile_2_total_returns.columns = quantile_2_total_returns.columns.droplevel(level=1)
#quantile_2_total_returns
# Make a new dataframe for our daily compounded returns
quantile_2_daily_returns = pd.DataFrame()
quantile_2_daily_returns['1D'] = quantile_2_total_returns['1D'].mul(10000).round(3)
quantile_2_daily_returns['2D'] = quantile_2_total_returns['2D'].add(1).pow(1/2).sub(1).mul(10000).round(3)
quantile_2_daily_returns['3D'] = quantile_2_total_returns['3D'].add(1).pow(1/3).sub(1).mul(10000).round(3)
quantile_2_daily_returns['4D'] = quantile_2_total_returns['4D'].add(1).pow(1/4).sub(1).mul(10000).round(3)
quantile_2_daily_returns['5D'] = quantile_2_total_returns['5D'].add(1).pow(1/5).sub(1).mul(10000).round(3)
quantile_2_daily_returns
Finally, let's compare this calculated value with those returned by create_full_tear_sheet
.
display(quantile_2_daily_returns)
create_full_tear_sheet(merged_data, long_short=False)
That's it. The tear sheet's mean period wise returns for the top quantile match our calculated returns.