# From: https://www.quantopian.com/posts/long-only-non-day-trading-algorithm-for-live
# PYLIVETRADER
# from quantopian.algorithm import attach_pipeline, pipeline_output
# Quantopian
from zipline.api import attach_pipeline, pipeline_output
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume, RSI, CustomFactor
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
from quantopian.pipeline.domain import US_EQUITIES
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume
import pandas as pd
import numpy as np # needed for NaN handling
import math # ceil and floor are useful for rounding
from itertools import cycle
class ADX(CustomFactor):
inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]
params = {'adx_len' : 14}
window_length = 140 # use window_length = 10 * adx_len
def compute(self, today, assets, out, highs, lows, closes, adx_len):
THs = np.maximum(highs[1:], closes[:-1]) # max of current high or previous close
TLs = np.minimum(lows[1:], closes[:-1]) # min of current low or previous close
TRs = THs - TLs
# This comented out code below is an efficient way to calculate ATR without a for loop
# However it produces a single ATR value and not the array of ATR values needed later on
#count = len(TRs)
#decay_rate = (1.0 - (1.0 / (adx_len)))
#weights = np.full(count, decay_rate, float) ** np.arange(count + 1, 1, -1)
#ATR = np.average(TRs, axis=0, weights=weights)
ATRs = np.empty(TRs.shape)
ATRs.fill(np.nan)
for j in range(0, TRs.shape[1]):
for i in range(adx_len, TRs.shape[0]):
if i == adx_len:
ATRs[i,j] = np.average(TRs[1:adx_len,j],axis=0)
else:
ATRs[i,j] = (ATRs[i-1,j]*(adx_len-1)+TRs[i,j])/adx_len
high_diffs = highs[1:]-highs[:-1] # current high - previous high
low_diffs = lows[:-1]-lows[1:] # previous low - current low
pDIs = np.where(((high_diffs > low_diffs) & (high_diffs > 0)), high_diffs, 0.)
nDIs = np.where(((low_diffs > high_diffs) & (low_diffs > 0)), low_diffs, 0.)
# This comented out code below is an efficient way to calculate ApDI and AnDI without a for loop
# However it produces single values and not the array of values needed later on
#ApDI = np.average(pDIs, axis=0, weights=weights)
#AnDI = np.average(nDIs, axis=0, weights=weights)
ApDIs = np.empty(pDIs.shape)
ApDIs.fill(np.nan)
for j in range(0, pDIs.shape[1]):
for i in range(adx_len, pDIs.shape[0]):
if i == adx_len:
ApDIs[i,j] = np.average(pDIs[1:adx_len,j],axis=0)
else:
ApDIs[i,j] = (ApDIs[i-1,j]*(adx_len-1)+pDIs[i,j])/adx_len
AnDIs = np.empty(nDIs.shape)
AnDIs.fill(np.nan)
for j in range(0, nDIs.shape[1]):
for i in range(adx_len, nDIs.shape[0]):
if i == adx_len:
AnDIs[i,j] = np.average(nDIs[1:adx_len,j],axis=0)
else:
AnDIs[i,j] = (AnDIs[i-1,j]*(adx_len-1)+nDIs[i,j])/adx_len
pDMs = 100 * (ApDIs / ATRs)
nDMs = 100 * (AnDIs / ATRs)
DXs = 100 * np.abs(pDMs - nDMs) / (pDMs + nDMs)
# This commented out code below seems like it should have worked
# However it just produce NaN values.
#count = len(DXs)
#decay_rate = (1.0 - (1.0 / (adx_len)))
#weights = np.full(count, decay_rate, float) ** np.arange(count + 1, 1, -1)
#ADX = np.average(DXs, axis=0, weights=weights)
#out[:] = ADX
ADXs = np.empty(DXs.shape)
ADXs.fill(np.nan)
for j in range(0, DXs.shape[1]):
for i in range(2*adx_len, DXs.shape[0]):
if i == 2*adx_len:
ADXs[i,j] = np.average(DXs[adx_len+1:2*adx_len,j],axis=0)
else:
ADXs[i,j] = (ADXs[i-1,j]*(adx_len-1)+DXs[i,j])/adx_len
out[:] = ADXs[-1:]
MaxCandidates = 15
MaxBuyOrdersAtOnce = 5
MyLeastPrice = 3.0
MyFireSalePrice = MyLeastPrice
MyFireSaleAge = 4
def make_pipeline():
"""
Create our pipeline.
"""
# Filter for primary share equities. IsPrimaryShare is a built-in filter.
primary_share = IsPrimaryShare()
# Not when-issued equities.
not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')
# Equities without LP in their name, .matches does a match using a regular
# expression
not_lp_name = ~morningstar.company_reference.standard_name.latest.matches(
'.* L[. ]?P.?$')
# Equities whose most recent Morningstar market cap is not null have
# fundamental data and therefore are not ETFs.
#have_market_cap = morningstar.valuation.market_cap.latest.notnull()
have_market_cap = morningstar.valuation.market_cap.latest >= 1
# At least a certain price
price = USEquityPricing.close.latest
AtLeastPrice = (price >= MyLeastPrice)
filter_volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],
window_length=30) > 500000
filter_dollar_volume = AverageDollarVolume(window_length=30) > 2500000
rsi = RSI(inputs=[USEquityPricing.close], window_length=3)
filter_overbought = rsi < 30
# Filter for stocks that pass all of our previous filters.
tradeable_stocks = (
primary_share
& not_wi
& not_lp_name
& have_market_cap
& AtLeastPrice
& filter_volume
& filter_dollar_volume
& filter_overbought
)
adx = ADX(mask=tradeable_stocks,window_length=70,adx_len=7)
tradable_with_adx_stocks = adx > 20
# Short close price average.
ShortAvg = SimpleMovingAverage(
inputs=[USEquityPricing.close],
window_length=3,
mask=tradable_with_adx_stocks
)
# Long close price average.
LongAvg = SimpleMovingAverage(
inputs=[USEquityPricing.close],
window_length=45,
mask=tradable_with_adx_stocks
)
percent_difference = (ShortAvg - LongAvg) / LongAvg
# Filter to select securities to long.
stocks_worst = percent_difference.bottom(MaxCandidates)
# securities_to_trade = (stocks_worst)
return Pipeline(
columns={
'percent_difference': -percent_difference,
},
screen=tradable_with_adx_stocks,
)
pipeline = make_pipeline()
# Run the pipeline from 2016 to 2019 and display the first few rows of output.
from quantopian.research import run_pipeline
factor_data = run_pipeline(pipeline, '2016-05-01', '2020-05-08')
print("Result contains {} rows of output.".format(len(factor_data)))
factor_data.head()
pricing_data = get_pricing(
symbols=factor_data.index.levels[1], # Finds all assets that appear at least once in "factor_data"
start_date='2016-05-01',
end_date='2020-06-08', # must be after run_pipeline()'s end date. Explained more in lesson 4
fields='open_price' # Generally, you should use open pricing. Explained more in lesson 4
)
# Show the first 5 rows of pricing_data
pricing_data.head()
factor_data.head()
from alphalens.utils import get_clean_factor_and_forward_returns
merged_data = get_clean_factor_and_forward_returns(
factor=factor_data,
prices=pricing_data
)
# Show the first 5 rows of merged_data
merged_data.head(5)
from alphalens.tears import create_full_tear_sheet
create_full_tear_sheet(merged_data)