Back to Community
Trading strategy worthy of Q fund ?

Hi,
Could this pairs trading stat arb market neutral strategy be something worthy of Q fund ? Not particularly this algo, but this kind of long-short pairs strategy ?
I have a separate screener that I use for finding correlated stocks ( using also PearsonR = numpy.corrcoef(xA,yB)[-1,1] , but not only) that tend to mean reverse when a major price gap (%) appears between them or that tend to keep wideing the gap / momentum, after a initial gap in price.
The idea for the future is to integrate the research tool / screener in the algo and use also machine learning model in order to minimise risk and make better decisions.

Regards,
Adrian

Clone Algorithm
110
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import datetime
import pytz
import numpy
import pandas


def initialize(context):
    
    context.stocks = [sid(5061), # MSFT
                      sid(3766), # IBM
                      sid(24), # AAPL
                      sid(26578), # GOOGL
                      sid(8347), # XOM
                      sid(23112) # CVX
                      ]
    context.s1 = sid(5061)
    context.s2 = sid(3766)
    context.s3 = sid(24)
    context.s4 = sid(26578)
    context.s5 = sid(8347)
    context.s6 = sid(23112)
    
def A(context,data):
    for stock in context.stocks:
        if stock == context.s1:
            A_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_A = A_history_open[stock][-3]
            A_history = history(bar_count=2, frequency='1d', field='price')
            curr_A = A_history[stock][-1]
            A_move = curr_A / open_A
            return A_move  
    
def B(context,data):    
    for stock in context.stocks:
        if stock == context.s2:
            B_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_B = B_history_open[stock][-3]
            B_history = history(bar_count=2, frequency='1d', field='price')
            curr_B = B_history[stock][-1]
            B_move = curr_B / open_B
            return B_move
        
def C(context,data):    
    for stock in context.stocks:
        if stock == context.s3:
            C_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_C = C_history_open[stock][-3]
            C_history = history(bar_count=2, frequency='1d', field='price')
            curr_C = C_history[stock][-1]
            C_move = curr_C / open_C
            return C_move
        
def D(context,data):    
    for stock in context.stocks:
        if stock == context.s4:
            D_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_D = D_history_open[stock][-3]
            D_history = history(bar_count=2, frequency='1d', field='price')
            curr_D = D_history[stock][-1]
            D_move = curr_D / open_D
            return D_move
        
def E(context,data):    
    for stock in context.stocks:
        if stock == context.s5:
            E_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_E = E_history_open[stock][-3]
            E_history = history(bar_count=2, frequency='1d', field='price')
            curr_E = E_history[stock][-1]
            E_move = curr_E / open_E
            return E_move
        
def F(context,data):    
    for stock in context.stocks:
        if stock == context.s6:
            F_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_F = F_history_open[stock][-3]
            F_history = history(bar_count=2, frequency='1d', field='price')
            curr_F = F_history[stock][-1]
            F_move = curr_F / open_F
            return F_move
    
def CloseAllPositions(context,data):
    
    has_orders = get_open_orders()
    has_positions = context.portfolio.positions
    
    if has_orders:
            pass
    else:
        if has_positions:
            A(context,data)
            A_move = A(context,data)
            B(context,data)
            B_move = B(context,data)
            AvsB = A_move / B_move
            if (0.997 < AvsB < 1.003) or (AvsB==None):
                order_target_value(context.s1,0)
                order_target_value(context.s2,0)
                
            C(context,data)
            C_move = C(context,data)
            D(context,data)
            D_move = D(context,data)
            CvsD = C_move / D_move
            if (0.997 < CvsD < 1.003) or (CvsD==None):
                order_target_value(context.s3,0)
                order_target_value(context.s4,0)
                
            E(context,data)
            E_move = E(context,data)
            F(context,data)
            F_move = F(context,data)
            EvsF = E_move / F_move
            if (0.997 < EvsF < 1.003) or (EvsF==None):
                order_target_value(context.s5,0)
                order_target_value(context.s6,0)
                
def OpenAvsB(context,data):    
    
    has_orders = get_open_orders()
    has_positions = context.portfolio.positions
       
    A(context,data)
    A_move = A(context,data)
    B(context,data)
    B_move = B(context,data)
    
    AvsB = A_move / B_move
    #record(AvsB = AvsB)
    
    if has_orders:
        pass
    else: 
        if context.s1 not in has_positions:
                if AvsB > 1.01:
                    order_target_value(context.s1,35000)
                    order_target_value(context.s2,-35000)
                if AvsB < 0.98:
                    order_target_value(context.s1,-35000)
                    order_target_value(context.s2,35000)

def OpenCvsD(context,data):
    
    has_orders = get_open_orders()
    has_positions = context.portfolio.positions
    
    C(context,data)
    C_move = C(context,data)
    D(context,data)
    D_move = D(context,data)
    
    CvsD = C_move / D_move     
    #record(CvsD = CvsD)
    
    if has_orders:
        pass
    else: 
        if context.s3 not in has_positions:
          # it avoids a price data error/bug for GOOGLE split
          # Dan Dunn, May 13, 2015 : I'm going to write a bug on this one, but I don't plan on putting the "urgent" tag on it. Just to set expectations =) 
          # https://www.quantopian.com/posts/mishandled-google-split
          if not ((get_datetime().year == 2014 and get_datetime().month == 4 and get_datetime().day <= 7) or (get_datetime().year == 2014 and get_datetime().month == 3 and get_datetime().day >= 24)):
                    if CvsD > 1.01:
                        order_target_value(context.s3,35000)
                        order_target_value(context.s4,-35000)
                    if CvsD < 0.98:
                        order_target_value(context.s3,-35000)
                        order_target_value(context.s4,35000)
                        
def OpenEvsF(context,data):    
    
    has_orders = get_open_orders()
    has_positions = context.portfolio.positions
       
    E(context,data)
    E_move = E(context,data)
    F(context,data)
    F_move = F(context,data)
    
    EvsF = E_move / F_move
    #record(EvsF = EvsF)
    
    if has_orders:
        pass
    else: 
        if context.s5 not in has_positions:
                if EvsF > 1.01:
                    order_target_value(context.s5,35000)
                    order_target_value(context.s6,-35000)
                if EvsF < 0.98:
                    order_target_value(context.s5,-35000)
                    order_target_value(context.s6,35000)
                        
def handle_data(context,data):
    if get_datetime().hour == 15 and get_datetime().minute == 35:
        CloseAllPositions(context,data)
    if get_datetime().hour == 15:
        if get_datetime().minute == 45:
            OpenAvsB(context,data)
        if get_datetime().minute == 52:
            OpenCvsD(context,data)
        if get_datetime().minute == 58:
            OpenEvsF(context,data)
There was a runtime error.
12 responses

It has very low beta.
Is it worthy?
Some times yes, but mostly no.

Clone Algorithm
7
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
#  Adrian Boca pairs trading stat arb market neutral 
#  https://www.quantopian.com/posts/trading-strategy-worthy-of-q-fund

import datetime
import pytz
import numpy
import pandas


def initialize(context):
    
    context.stocks = [sid(5061), # MSFT
                      sid(3766), # IBM
                      sid(24), # AAPL
                      sid(26578), # GOOGL
                      sid(8347), # XOM
                      sid(23112) # CVX
                      ]
    context.s1 = sid(5061)
    context.s2 = sid(3766)
    context.s3 = sid(24)
    context.s4 = sid(26578)
    context.s5 = sid(8347)
    context.s6 = sid(23112)
    
def A(context,data):
    for stock in context.stocks:
        if stock == context.s1:
            A_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_A = A_history_open[stock][-3]
            A_history = history(bar_count=2, frequency='1d', field='price')
            curr_A = A_history[stock][-1]
            A_move = curr_A / open_A
            return A_move  
    
def B(context,data):    
    for stock in context.stocks:
        if stock == context.s2:
            B_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_B = B_history_open[stock][-3]
            B_history = history(bar_count=2, frequency='1d', field='price')
            curr_B = B_history[stock][-1]
            B_move = curr_B / open_B
            return B_move
        
def C(context,data):    
    for stock in context.stocks:
        if stock == context.s3:
            C_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_C = C_history_open[stock][-3]
            C_history = history(bar_count=2, frequency='1d', field='price')
            curr_C = C_history[stock][-1]
            C_move = curr_C / open_C
            return C_move
        
def D(context,data):    
    for stock in context.stocks:
        if stock == context.s4:
            D_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_D = D_history_open[stock][-3]
            D_history = history(bar_count=2, frequency='1d', field='price')
            curr_D = D_history[stock][-1]
            D_move = curr_D / open_D
            return D_move
        
def E(context,data):    
    for stock in context.stocks:
        if stock == context.s5:
            E_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_E = E_history_open[stock][-3]
            E_history = history(bar_count=2, frequency='1d', field='price')
            curr_E = E_history[stock][-1]
            E_move = curr_E / open_E
            return E_move
        
def F(context,data):    
    for stock in context.stocks:
        if stock == context.s6:
            F_history_open = history(bar_count=3, frequency='1d', field='open_price')
            open_F = F_history_open[stock][-3]
            F_history = history(bar_count=2, frequency='1d', field='price')
            curr_F = F_history[stock][-1]
            F_move = curr_F / open_F
            return F_move
    
def CloseAllPositions(context,data):
    
    has_orders = get_open_orders()
    has_positions = context.portfolio.positions
    
    if has_orders:
            pass
    else:
        if has_positions:
            A(context,data)
            A_move = A(context,data)
            B(context,data)
            B_move = B(context,data)
            AvsB = A_move / B_move
            if (0.997 < AvsB < 1.003) or (AvsB==None):
                order_target_value(context.s1,0)
                order_target_value(context.s2,0)
                
            C(context,data)
            C_move = C(context,data)
            D(context,data)
            D_move = D(context,data)
            CvsD = C_move / D_move
            if (0.997 < CvsD < 1.003) or (CvsD==None):
                order_target_value(context.s3,0)
                order_target_value(context.s4,0)
                
            E(context,data)
            E_move = E(context,data)
            F(context,data)
            F_move = F(context,data)
            EvsF = E_move / F_move
            if (0.997 < EvsF < 1.003) or (EvsF==None):
                order_target_value(context.s5,0)
                order_target_value(context.s6,0)
                
def OpenAvsB(context,data):    
    
    has_orders = get_open_orders()
    has_positions = context.portfolio.positions
       
    A(context,data)
    A_move = A(context,data)
    B(context,data)
    B_move = B(context,data)
    
    AvsB = A_move / B_move
    #record(AvsB = AvsB)
    
    if has_orders:
        pass
    else: 
        if context.s1 not in has_positions:
                if AvsB > 1.01:
                    order_target_value(context.s1,35000)
                    order_target_value(context.s2,-35000)
                if AvsB < 0.98:
                    order_target_value(context.s1,-35000)
                    order_target_value(context.s2,35000)

def OpenCvsD(context,data):
    
    has_orders = get_open_orders()
    has_positions = context.portfolio.positions
    
    C(context,data)
    C_move = C(context,data)
    D(context,data)
    D_move = D(context,data)
    
    CvsD = C_move / D_move     
    #record(CvsD = CvsD)
    
    if has_orders:
        pass
    else: 
        if context.s3 not in has_positions:
          # it avoids a price data error/bug for GOOGLE split
          # Dan Dunn, May 13, 2015 : I'm going to write a bug on this one, but I don't plan on putting the "urgent" tag on it. Just to set expectations =) 
          # https://www.quantopian.com/posts/mishandled-google-split
          if not ((get_datetime().year == 2014 and get_datetime().month == 4 and get_datetime().day <= 7) or (get_datetime().year == 2014 and get_datetime().month == 3 and get_datetime().day >= 24)):
                    if CvsD > 1.01:
                        order_target_value(context.s3,35000)
                        order_target_value(context.s4,-35000)
                    if CvsD < 0.98:
                        order_target_value(context.s3,-35000)
                        order_target_value(context.s4,35000)
                        
def OpenEvsF(context,data):    
    
    has_orders = get_open_orders()
    has_positions = context.portfolio.positions
       
    E(context,data)
    E_move = E(context,data)
    F(context,data)
    F_move = F(context,data)
    
    EvsF = E_move / F_move
    #record(EvsF = EvsF)
    
    if has_orders:
        pass
    else: 
        if context.s5 not in has_positions:
                if EvsF > 1.01:
                    order_target_value(context.s5,35000)
                    order_target_value(context.s6,-35000)
                if EvsF < 0.98:
                    order_target_value(context.s5,-35000)
                    order_target_value(context.s6,35000)
                        
def handle_data(context,data):
    if get_datetime().hour == 15 and get_datetime().minute == 35:
        CloseAllPositions(context,data)
    if get_datetime().hour == 15:
        if get_datetime().minute == 45:
            OpenAvsB(context,data)
        if get_datetime().minute == 52:
            OpenCvsD(context,data)
        if get_datetime().minute == 58:
            OpenEvsF(context,data)
There was a runtime error.

Hi Vadimir,
Thank you for your reply and making the backtest begin in 2007:). No disrespect. Maybe I was misunderstood, but I specified earlier that I was curious to know the point of view of the Q team & community regarding the type of strategy used, not if this particular algo was worthy of the fund. The algo for live trading is using more pairs of stocks and refreshing the stock list on a monthly basis. These are only some pairs that behaved well in the last 2 years, but nothing lasts forever.

For experimenting, condensed and sped up.

Clone Algorithm
64
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
'''
Pairs trading. https://www.quantopian.com/posts/trading-strategy-worthy-of-q-fund

Poor when started from 2007.
'''
import datetime
import pytz
import numpy
import pandas

window = 3    # for opens

def initialize(context):
    context.spy      = sid(8554)    # SPY
    context.spy_list = []
    context.pairs    = [
        [
            sid(5061),    # MSFT
            sid(3766),    # IBM
        ],
        [
            sid(24),      # AAPL
            sid(26578),   # GOOGL
        ],
        [
            sid(8347),    # XOM
            sid(23112),   # CVX
        ],
    ]

def hist(context, data, sec):
    opn  = context.opens_list[sec][-window]
    cur  = context.close_list[sec][-1]
    move = cur / opn
    return move

def CompareOpens(context, data, pair):
    if get_open_orders(pair[0]): return
    if get_open_orders(pair[1]): return
    if pair[1] in context.portfolio.positions and context.portfolio.positions[pair[1]] != 0:
        return

    # Experiment to only trade when spy up/dn
    switch = 0
    if switch == 1:
        context.spy_list.append(context.opens_list[context.spy][-1])
        wndw = 6
        context.spy_list = context.spy_list[-wndw:]    # trim
        if len(context.spy_list) >= wndw and context.spy_list[-1] < context.spy_list[-wndw]:
            CloseSomePositions(context, data)
            return

    move1 = hist(context, data, pair[0])
    move2 = hist(context, data, pair[1])
    moves = move1 / move2
    record(moves = moves)

    shrs = 35000
    if moves > 1.01:
        order_target_value(pair[0],  shrs)
        order_target_value(pair[1], -shrs)
    elif moves < 0.98:
        order_target_value(pair[0], -shrs)
        order_target_value(pair[1],  shrs)

def handle_data(context,data):
    context.opens_list = history(bar_count=window, frequency='1d', field='open_price')
    context.close_list = history(bar_count=2,      frequency='1d', field='close_price')

    if get_datetime().hour == 15:
        minute = get_datetime().minute
        if minute == 35:
            if get_open_orders(): return
            if not context.portfolio.positions:   return
            if CloseSomePositions(context, data): return
        elif minute == 45:
            CompareOpens(context, data, context.pairs[0])
        elif minute == 52:
            # This avoids a price data error/bug for GOOGLE split
            # Dan Dunn, May 13, 2015 : I'm going to write a bug on this one,
            #   but I don't plan on putting the "urgent" tag on it. Just to set expectations =)
            # https://www.quantopian.com/posts/mishandled-google-split
            dt = get_datetime()
            if   (dt.year == 2014 and dt.month == 4 and dt.day <= 7)\
              or (dt.year == 2014 and dt.month == 3 and dt.day >= 24):
                return
            CompareOpens(context, data, context.pairs[1])
        elif minute == 58:
            CompareOpens(context, data, context.pairs[2])

def CloseSomePositions(context, data):
    for p in context.pairs:
        move1 = hist(context, data, p[0])
        move2 = hist(context, data, p[1])
        moves = move1 / move2
        if 0.997 < moves < 1.003 or moves == None:
            order_target_value(p[0], 0)
            order_target_value(p[1], 0)
            return 1
    return 0
There was a runtime error.

Groovy! Nice code optimization, garyha!:)

Thanks and hope it helps folks find improvements.
I need to make one correction, letting any be closed rather than just the first one:

def CloseSomePositions(context, data):  
    sales = 0  
    for p in context.pairs:  
        moves = MovesCalc(context, p)  
        offset = .003  
        if 1 - offset < moves < 1 + offset or moves == None:  
            order_target_value(p[0], 0)  
            order_target_value(p[1], 0)  
            sales = 1  
    if sales:  
        return 1  
    return 0  

Hi Adrian,

It's great to see you thinking about strategies for the fund. I cloned your post and ran it through the tearsheet to analyze the behavior. At first glance, the 2 year backtest from 2013-2015 looked interesting. However, as I ran longer backtests (5 yrs, 10 yrs) it appears the algo was overfit to the last 2 year period.

Attached is the tearsheet from the last 10 years. The returns could be improved, and the algo reaches a 50% drawdown. As you continue to test your ideas and create strategies, consider the bigger picture - Why does your algorithm perform well? What is the economic rationale? How does it perform in different market regimes? Feel free to share any other ideas for feedback! And if you find something you like, start paper trading it on Quantopian. Then you'll begin to collect out of sample data and track the returns.

Alisa

Loading notebook preview...
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 Adrian,

Per your asking whether "this style of strategy is interesting to Q":
Yes, Definitely -- Everything you describe here sounds like a strong economic rationale to attempt to exploit with an algo. Specifically, as you mentioned: "correlated stocks (but not only) that tend to mean reverse when a major price gap (%) appears between them or that tend to keep wideing the gap / momentum, after a initial gap in price."

I think if you can extrapolate this idea into general logic for screening over a large universe of stocks, and possibly have, say, 10 - 20 pairs on at all times, the diversification benefit of many pairs should help mitigate risk in the overall portfolio.

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.

Alisa,
What do you mean by market "regimes"? Also, how do you determine it was "overfit" for the past 2 years?

Thanks for your replies! You can find a similar shared strategy of mine, that trades only ETFs, here: https://www.quantopian.com/posts/october-prize-number-1-algo-disqualified-worthy-of-the-q-fund . Please share your code or trading strategy ideas on what can be improved to make it mode robust.
Adrian

@Adam, I'd be happy to explain. For the fund, we'll see how the algorithm performs in different market conditions, not only the performance in recent years. We'd like to see a backtest over 10 years (not an exact number) to see more data. As you're writing the algo, test how it performs in 2008, 2011 and other turbulent times. If the algorithm does well in the last 2 years, but loses money when running a 5,7, or 10 year backtest then it's very likely it was overfit to the recent data.

To prevent from overfitting, I encourage you to first think about your economic rationale. Why does this strategy work? What would cause this strategy to stop working? Then begin coding the details and testing over various periods. If you find something promising, enter it in the contest or start paper trading to see out of sample data. The more data it accumulates the better; paper trading is the true test.

The tearsheet is a very powerful tool to visualize the data. You'll see the length of drawdown periods, cumulative return trends, position concentration, and more. We held a webinar showing how to read the tearsheet plots and interpret the data; if you missed it check out this video: https://www.youtube.com/watch?v=-VmZAlBWUko

I think if you can extrapolate this idea into general logic for screening over a large universe of stocks, and possibly have, say, 10 - 20 pairs on at all times, the diversification benefit of many pairs should help mitigate risk in the overall portfolio.

This is a pre-appeal to give us enough computation time in the upcoming Modeling API to do the above! :) EDIT: woo, five minutes, thanks!

@Adrian,
I think Simon and Justin are giving good advice and pointing you in the direction of the new Pipeline API. We built it with the express goal of making it easier to extend algorithms like this to a larger number of securities. I hope it proves useful!

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.