Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Struggling with Relative Strength

Hi Everyone,

Long term I'm attempting to build a multi-factor Quality/Value/Momentum based approach.
I've been using pipelines and CustomFactors.

My initial Value based version is sorted and I'm currently working on the Momentum version.

So far I've managed to use Custom Factors to calculate and rank based on:

  • Price to 50d/200MA
  • Price to 52w high (for longs)
  • Price to 52w low (for shorts)

but I'm really struggling to implement 1 year Relative Strength and 6m Relative Strength.

The formula is ...

relative strength = ( (% change in price in stock/% change in benchmark) - 1) * 100

I'm a former software developer but I have to say the finer details of Python have floored me.

Any advice or pointers to other similar solutions are gratefully received.

Thanks in advance
Phil

13 responses

Hi Phil,
There is a built in factor to calculate the relative strength index. I believe that RSI is relative strength normalized to a 1-100 scale but perhaps the source for this will help you out. You can see it here: http://www.zipline.io/_modules/zipline/pipeline/factors/technical.html#RSI

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Hi Karen,

Thanks for the reply.
RSI is a technical indicator whereas "relative strength" is a comparison against a benchmark.
Similar names but different indicators I'm afraid!

Thanks
Phil

May be somebody can do it in more elegant way, but at least this works.

# Relative_Strength

def initialize(context):  
    context.stock = symbol('AMZN')  
    context.index = symbol('SPY')   

def handle_data(context,data):  
    period = 252  
    prices = history(period,  '1d',  'price')  
    stk_price_series = prices[context.stock]  
    ind_price_series = prices[context.index]  
    pct_change_stk = stk_price_series.iloc[[0, -1]].pct_change()  
    pct_change_ind = ind_price_series.iloc[[0, -1]].pct_change()  
    RS =(pct_change_stk[-1]+1.0) / (pct_change_ind[-1] + 1.0)  
    record(Relative_Strength=RS)  

Is it subtract or divide and + 1.0 or - 1.0 as in the original post? RS1 and RS2 here are the same, RS3 is different with some extremes.
Could be some confusion here between ratio vs percent also.

# Relative_Strength

def initialize(context):  
    context.amz = symbol('AMZN')  
    context.spy = symbol('SPY')

def handle_data(context,data):  
    prices = history(252,  '1d',  'price')  # 252 is one year

    # VY  
    stk_price_series = prices[context.amz]  
    ind_price_series = prices[context.spy]  
    pct_change_stk = stk_price_series.iloc[[0, -1]].pct_change()  
    pct_change_ind = ind_price_series.iloc[[0, -1]].pct_change()  
    RS =pct_change_stk[-1]- pct_change_ind[-1] + 1.0  
    record(RS1=RS)

    pct_changes = prices.iloc[[0, -1]].pct_change().dropna() # all stocks simultaneously  
    RS = pct_changes[context.amz][-1] - pct_changes[context.spy][-1] + 1.0 # based on VY  
    record(RS2 = RS)

    # Now based on formula in the original post  
    #  ( (% change in price in stock/% change in benchmark) - 1) * 100  
    RS = ( (pct_changes[context.amz][-1] / pct_changes[context.spy][-1]) - 1.0) * 100  
    record(RS3 = RS)  

Gary,
Relative strengs, sometimes called price relative, actually is the ratio of stock price to benchmark price.{ price(amzn)/price(spy)}
It is good to normalize each price series to the beginnig price.
In other words it is better to use not prices but equities for the period of stock and benchmark.
In this case beginning equity both stock and benchmark will be 1.
At the end of period equity of stock will be 1+pct_change(amzn,period) and equity of benchmark will be 1+pct_change(spy,period).
So Normalized Relative Strength (amzn) will be (pct_change(amzn,period)+1)/(pct_change(spy,period)+1)

Thank you for tip which make the code more elegant:

# Relative_Strength  ( price relative)

def initialize(context):  
    context.stock = symbol('AMZN')  
    context.index = symbol('SPY')  
def handle_data(context,data):  
    period = 252  
    prices = history(period,  '1d',  'price')  
    pct_changes = prices.iloc[[0, -1]].pct_change().dropna()  
    RS =(pct_changes[context.stock][-1]+1.0) / (pct_changes[context.index][-1] + 1.0)  
    record(Relative_Strength=RS)  

Thank you so much Gary & Vladimir.
As well as the solution, your code helps to increase my understanding of the API

I'm very grateful
Phil

My follow on question is ...

How do I translate that into an algorithm?

I've previously used Custom Factors but in this case to calculate the RS for each stock I also need access to the history for the comparative benchmark as well. Is there way to achieve this? Can I pass the history of the benchmark in as a parameter to the CustomFactor?

Apologies if these are basic questions

Thanks in advance
Phil

I didn't test the code, but something like this should work:

class RelativeStrength(CustomFactor):  
    """  
    Compute the following:  
    relative strength = ( (% change in price in stock / % change in benchmark) - 1) * 100  
    """  
    params = ('market_sid',)  
    inputs = [USEquityPricing.close]

    def compute(self, today, assets, out, close, market_sid):  
        returns = (close[-1] - close[0]) / close[0]  
        market_idx = assets.get_loc(market_sid)  
        out[:] = ( (returns / returns[market_idx]) - 1) * 100


#You can use the factor like this:  
market = symbol('SPY').sid  
rs12months = RelativeStrength(window_length=252, market_sid=market)  
rs6months   = RelativeStrength(window_length=(252/2), market_sid=market)  

Thank you so much Luca.
That has really helped me and I've been able to integrate it into a developing blended Momentum Rank approach.

I am glad it helped. There is one shortcoming though: if you set up the pipeline filters so that the market sid is not part of the universe handled by the pipeline, this code fails. It would been nice if we had the possibility to specifically add certain sid to the pipeline universe, but it's not possible.

Phil, Luca

The formula for Relative_Strength is:

 RS =(pct_change_stk[-1]+1.0) / (pct_change_ind[-1] + 1.0)  

Not as in yours posts:
RS =pct_change_stk[-1] / (pct_change_ind[-1] - 1.0

Make sure that the formulas and code in yours posts are correct.

@Luca, yes I thought that might be an issue. I also wasn't sure if the benchmark 'returns' were calculated prior to their use in all rows

@Vladimir thanks for the reminder, my code looks like this ...


class RelativeStrength(CustomFactor):  
    """  
    Compute the following:  
    relative strength = ( (% change in price in stock / % change in benchmark) - 1) * 100  
    """  
    params = ('market_sid',)  
    inputs = [USEquityPricing.close]

    def compute(self, today, assets, out, close, market_sid):  
        rsRankTable = pd.DataFrame(index=assets)

        returns = (close[-1] - close[0]) / close[0]  
        market_idx = assets.get_loc(market_sid)  
        rsRankTable["RS"] = (((returns + 1)/(returns[market_idx] + 1)) - 1) * 100

        out[:] = rsRankTable["RS"].rank(ascending=False)  

Phil,

I do not mind if you want to use this indicator as oscillator, but then it will be good to give it another name.
Like "Normalized Relative Strength Oscillator" or NRSO and change the code accordingly.

class NormalizedRelativeStrengthOscillator(CustomFactor):  
    """  
    Compute the following:  
    Normalized Relative Strength Oscillator = ((% change in stock + 1) / (% change in benchmark + 1) - 1) * 100  
    """  
    params = ('market_sid',)  
    inputs = [USEquityPricing.close]

    def compute(self, today, assets, out, close, market_sid):  
        nrsoRankTable = pd.DataFrame(index=assets)

        returns = (close[-1] - close[0]) / close[0]  
        market_idx = assets.get_loc(market_sid)  
        nrsoRankTable["NRSO"] = (((returns + 1)/(returns[market_idx] + 1)) - 1) * 100

        out[:] = nrsoRankTable["NRSO"].rank(ascending=False)