I had the same question / problem quite a while ago. At first I used the talib library as shown in this post. You can use the output of any indicator as input for another one. In the official docs you can see what indicators they provide. There's also a formula for most of them.
However, talib can be very slow since you must iterate over all assets in order to get the values. So I began writing my own indicators with the goals
a) to get the same or at least very similar results as with talib and
b) vectorize the calculations using mostly numpy and scipy to speed things up.
As a starting point I used this tutorial series by Sentdex. Althogh he uses for-loops in each one of them (again, for-loops=slow), it gives you a good insight on how to implement indicators in python. Once you understand the iterative code you can ask yourself "how could I get the same result using numpy arrays?" (you could also use pandas which can be easier, but I often got timeout / memory errors using pandas in pipeline). Sometimes it's just as easy as searching the web for "numpy [some indicator]".
So for the original question, here's a custom factor calculating the 90-sma of the 14-rsi. It uses the functions "my_ema" (needed for rsi), "my_rsi" and "my_sma". Each one of them is completely vectorized and comes pretty close to the corresponding talib indicators (they only differ in the beginning of the time series, the later values are the same). They work for 1D-arrays (one single symbol) as well as for 2D-arrays (more symbols). If anyone is interested in other indicators, I can have a look if I already coded them...
# some imports in addition to the usual ones
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
import numpy as np
from scipy.ndimage import convolve1d
def my_sma(cls, n):
conv1 = np.ones(n) / n
fill = int(n / 2)
result = convolve1d(cls, conv1, axis=0, mode='constant', cval=np.nan, origin=-fill)
return result
def my_ema(cls, n, alpha=None):
'''
Ok, I admit I haven't fully understood these calculations.
The code is from here:
https://stackoverflow.com/a/42926270
with a few modifications.
'''
if alpha is None:
alpha = 2 /(n + 1.0)
alpha_rev = 1-alpha
n = cls.shape[0]
pows = alpha_rev**(np.arange(n+1))
if cls.ndim > 1:
pows = np.tile(pows, (cls.shape[1],1)).T
scale_arr = 1/pows[:-1]
offset = cls[0]*pows[1:]
pw0 = alpha*alpha_rev**(n-1)
mult = cls*pw0*scale_arr
cumsums = np.cumsum(mult, axis=0)
result = offset + cumsums*scale_arr[::-1]
return result
def my_rsi(cls, n):
delta = np.diff(cls, axis=0)
if cls.ndim == 1:
delta = np.append(0, delta)
else:
zeros = np.zeros(cls.shape[1])
delta = np.vstack([zeros, delta])
up, down = delta.copy(), -delta.copy()
up[up<0] = 0
down[down<0] = 0
rs = my_ema(up, n, alpha=1/n) / my_ema(down, n, alpha=1/n)
result = 100 - 100 / (1 + rs)
return result
class SmaRsi(CustomFactor):
inputs = [USEquityPricing.close]
window_length = 500
window_safe = True
def compute(self, today, assets, out, c):
rsi = my_rsi(c, 14)
sma_rsi = my_sma(rsi, 90)
out[:] = sma_rsi[-1]