Back to Community
RSI UPRO Algorithm

Hello everyone! I have been testing this algorithm and I think I want to go live with it. It buys UPRO when rsi is 32 and sells when rsi is 70. I will use Robinhood, and I tried to add the t+3 trading guards. Are there any bugs with this algorithm? Is it safe to go live with it for robinhood?

Clone Algorithm
55
Loading...
Backtest from to with initial capital
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
# This example algorithm uses the Relative Strength Index indicator as a buy/sell signal.
# When the RSI is over 70, a stock can be seen as overbought and it's time to sell.
# When the RSI is below 30, a stock can be seen as oversold and it's time to buy.

 
import talib


# Setup our variables
def initialize(context):
    
    set_long_only()
    context.last_sale = None
    context.trading_days = 0
    context.stocks = symbols('UPRO')
    context.target_pct_per_stock = 1.0 / len(context.stocks)
    context.LOW_RSI = 32
    context.HIGH_RSI = 70
    set_benchmark(symbol('SPY'))

    schedule_function(rebalance, date_rules.every_day(), time_rules.market_open())

# Rebalance daily.
def rebalance(context, data):
    
    # Load historical data for the stocks
    prices = data.history(context.stocks, 'price', 20, '1d')
    
    rsis = {}
    
    # Loop through our list of stocks
    for stock in context.stocks:
        # Get the rsi of this stock.
        rsi = talib.RSI(prices[stock], timeperiod=14)[-1]
        rsis[stock] = rsi
        
        current_position = context.portfolio.positions[stock].amount
        
        # RSI is above 70 and we own shares, time to sell
  
        if rsi > context.HIGH_RSI and current_position > 0 and data.can_trade(stock):
            order_target(stock, 0)
   
        # RSI is below 30 and we don't have any shares, time to buy
        elif rsi < context.LOW_RSI and current_position == 0 and data.can_trade(stock):
            order_target_percent(stock, context.target_pct_per_stock)
            
def do_unsettled_funds_exist(context):
    """
    For Robinhood users. In order to prevent you from attempting
    to trade on unsettled cash (settlement dates are T+3) from
    sale of proceeds. You can use this snippet of code which
    checks for whether or not you currently have unsettled funds
    
    To only be used for live trading!
    """
    if context.portfolio.cash != context.account.settled_cash:
        return True
    
    
def handle_data(context, data):
    # Because most Robinhood accounts are cash accounts,
    # trades(and most other brokerages) settle 
    # on a T+3 settlement date. This snippet of code prevents
    # a trade from happening when you still have unsettled cash
    # by checking if total cash (settled & unsettled) matches your
    # settled cash amount.

    # [IMPORTANT] During backtests, `settled_cash` will always equal
    # `cash`. In order to simulate a similar check, please also 
    # incorporate `simulate_cash_settlement` in handle data as you will
    # see in this algorithm.
    if do_unsettled_funds_exist(context):
        # For live trading only
        return
There was a runtime error.
13 responses

Absolutely not! You only tested during a recent bull market. Try testing it with a constantly-rebalancing 300% position in SPY (which is equivalent), and you can backtest it to 2001. You'll see you get completely wiped out during 2008.

Um, quite. Herewith a test on SPY with 2x leverage.

Clone Algorithm
2
Loading...
Backtest from to with initial capital
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
# This example algorithm uses the Relative Strength Index indicator as a buy/sell signal.
# When the RSI is over 70, a stock can be seen as overbought and it's time to sell.
# When the RSI is below 30, a stock can be seen as oversold and it's time to buy.

 
import talib


# Setup our variables
def initialize(context):
    
    set_long_only()
    context.last_sale = None
    context.trading_days = 0
    context.stocks = symbols('SPY')
    context.acc_leverage = 2.00 
    context.target_pct_per_stock = context.acc_leverage / len(context.stocks)
    context.LOW_RSI = 32
    context.HIGH_RSI = 70
   
    set_benchmark(symbol('SPY'))

    schedule_function(rebalance, date_rules.every_day(), time_rules.market_open())

# Rebalance daily.
def rebalance(context, data):
    
    # Load historical data for the stocks
    prices = data.history(context.stocks, 'price', 20, '1d')
    
    rsis = {}
    
    # Loop through our list of stocks
    for stock in context.stocks:
        # Get the rsi of this stock.
        rsi = talib.RSI(prices[stock], timeperiod=14)[-1]
        rsis[stock] = rsi
        
        current_position = context.portfolio.positions[stock].amount
        
        # RSI is above 70 and we own shares, time to sell
  
        if rsi > context.HIGH_RSI and current_position > 0 and data.can_trade(stock):
            order_target(stock, 0)
   
        # RSI is below 30 and we don't have any shares, time to buy
        elif rsi < context.LOW_RSI and current_position == 0 and data.can_trade(stock):
            order_target_percent(stock, context.target_pct_per_stock)
            
def do_unsettled_funds_exist(context):
    """
    For Robinhood users. In order to prevent you from attempting
    to trade on unsettled cash (settlement dates are T+3) from
    sale of proceeds. You can use this snippet of code which
    checks for whether or not you currently have unsettled funds
    
    To only be used for live trading!
    """
    if context.portfolio.cash != context.account.settled_cash:
        return True
    
    
def handle_data(context, data):
    # Because most Robinhood accounts are cash accounts,
    # trades(and most other brokerages) settle 
    # on a T+3 settlement date. This snippet of code prevents
    # a trade from happening when you still have unsettled cash
    # by checking if total cash (settled & unsettled) matches your
    # settled cash amount.

    # [IMPORTANT] During backtests, `settled_cash` will always equal
    # `cash`. In order to simulate a similar check, please also 
    # incorporate `simulate_cash_settlement` in handle data as you will
    # see in this algorithm.
    if do_unsettled_funds_exist(context):
        # For live trading only
        return
There was a runtime error.

Not "wiped out" I guess, if it's continuously rebalancing, but a 93% drawdown would be tough to stomach.

Clone Algorithm
4
Loading...
Backtest from to with initial capital
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
# This example algorithm uses the Relative Strength Index indicator as a buy/sell signal.
# When the RSI is over 70, a stock can be seen as overbought and it's time to sell.
# When the RSI is below 30, a stock can be seen as oversold and it's time to buy.

 
import talib


# Setup our variables
def initialize(context):
    
    set_long_only()
    context.last_sale = None
    context.trading_days = 0
    context.stocks = symbols('SPY')
    context.target_pct_per_stock = 3.0 / len(context.stocks)
    context.LOW_RSI = 32
    context.HIGH_RSI = 70
    set_benchmark(symbol('SPY'))

    schedule_function(rebalance, date_rules.every_day(), time_rules.market_open())

# Rebalance daily.
def rebalance(context, data):
    
    # Load historical data for the stocks
    prices = data.history(context.stocks, 'price', 20, '1d')
    
    rsis = {}
    
    # Loop through our list of stocks
    for stock in context.stocks:
        # Get the rsi of this stock.
        rsi = talib.RSI(prices[stock], timeperiod=14)[-1]
        rsis[stock] = rsi
        
        current_position = context.portfolio.positions[stock].amount
 
        if rsi > context.HIGH_RSI and data.can_trade(stock):
            order_target(stock, 0)
   
        elif rsi < context.LOW_RSI and data.can_trade(stock):
            order_target_percent(stock, context.target_pct_per_stock)
            
def do_unsettled_funds_exist(context):
    """
    For Robinhood users. In order to prevent you from attempting
    to trade on unsettled cash (settlement dates are T+3) from
    sale of proceeds. You can use this snippet of code which
    checks for whether or not you currently have unsettled funds
    
    To only be used for live trading!
    """
    if context.portfolio.cash != context.account.settled_cash:
        return True
    
    
def handle_data(context, data):
    # Because most Robinhood accounts are cash accounts,
    # trades(and most other brokerages) settle 
    # on a T+3 settlement date. This snippet of code prevents
    # a trade from happening when you still have unsettled cash
    # by checking if total cash (settled & unsettled) matches your
    # settled cash amount.

    # [IMPORTANT] During backtests, `settled_cash` will always equal
    # `cash`. In order to simulate a similar check, please also 
    # incorporate `simulate_cash_settlement` in handle data as you will
    # see in this algorithm.
    if do_unsettled_funds_exist(context):
        # For live trading only
        return
There was a runtime error.

Yes I tested it with SSO (2x leveraged SPY) and saw very high drawdown. However I think this can still beat any strategy that I use manually. Generally I believe that SP 500 will continue to go up. Is it possible to create a stop loss if max draw down > 0.6?

Max draw down stops are possible to make, but pretty annoying. You need to keep track of your own account equity history in context, calculate your own current drawdown metric, and then compare that to 0.6. It's possible, I've done it, but I gave up because I don't like algos which depend on storing vital state in context anymore, given how frequently one must restart them and lose all of context (e.g. Q2).

Ok is it possible to make a 50% trailing stop loss? I want to record the price i bought it at, and set a stop loss if current price is 50% less than the price i bought it at.
I don't need to use 50% exactly, I just want to know where I can find code for a trailing stop loss. I'm not a programmer ,it would be hard for me to make it.

Here is an alternative approach using code nicked from elsewhere. At the end of the day, the signals may not protect you and the leverage may well kill you.

Clone Algorithm
48
Loading...
Backtest from to with initial capital
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 THE LIBRARIES USED IN THE ALGORITHM ####################################
import datetime
import pytz
import pandas as pd
from zipline.utils.tradingcalendar import get_early_closes
import talib

########### INITIALZE() IS RUN ONCE (OR IN LIVE TRADING ONCE EACH DAY BEFORE TRADING) #####
def initialize(context):
    set_long_only()
    
    # Define the instruments in the portfolio:
    context.SPY      = symbol('SPY')
    context.TLT      = symbol('TLT')
    context.IEF      = symbol('IEF')
    context.IWV      = symbol('IWV')
    context.QQQ      = symbol('QQQ')
    context.EFA      = symbol('EFA')
    context.SHY      = symbol('SHY')
    context.GLD      = symbol('GLD')

    # Define the benchmark (used to get early close dates for reference).
    #context.spy           = sid(8554)
    context.canary = context.SPY
    context.risk_free = context.IEF
    
    start_date = context.SPY.security_start_date
    end_date   = context.SPY.security_end_date
    
    # Initialize context variables the define rebalance logic:
    context.rebalance_date = None
    context.next_rebalance_Date = None
    context.rebalance_days = 1
    #context.rebalance_window_start = 10
    #context.rebalance_window_stop  = 15
    
    # Get the dates when the market closes early:
    context.early_closes = get_early_closes(start_date,end_date).date
    
    
########### HANDLE_DATA() IS RUN ONCE PER MINUTE #######################
def handle_data(context, data):
    
    # Get the current exchange time, in local timezone: 
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    # If it is rebalance day, rebalance:
    if  context.rebalance_date == None or exchange_time >= context.next_rebalance_date:
        
       # If EOD, cancel all open orders, maintains consistency with live trading.
       cancel_orders_EOD(context)
       
       # If it's 10am proceed with rebalancing, otherwise skip this minute of trading.
       if exchange_time.hour != 10:
        return 
       
       # Check if there are any existing open orders. has_orders() defined below.
       has_orders = has_open_orders(data,context)
        
       # If we are in rebalance window but there are open orders, wait til next minute
       if has_orders == True:
            log.info('Has open orders')
            return
        
       # If there are no open orders we can rebalance.
       elif has_orders == False:
           
           rebalance(context, data, exchange_time) 
           log.info('Rebalanced portfolio to target weights at %s' % exchange_time)

           # Update the current and next rebalance dates
           context.rebalance_date = exchange_time 
           context.next_rebalance_date = context.rebalance_date + \
           datetime.timedelta(days=context.rebalance_days)      
    else:
        return
    
########### CORE REBALANCE LOGIC #########################################
## THIS FUNCTION IS RUN ONLY AT REBALANCE (DAY/TIME) #####################
def rebalance(context,data,exchange_time):
    
    #Rebalance the portfolio to the predetermined target weights:
    #
    #  61%   - MarketAxess Holdings Inc
    #  16%   - BGC Partners, Inc
    #  05%   - Interactive Brokers Group, Inc
    #  12%   - Nasdaq Inc
    #  06%  - CBOE Holdings, Inc
     Canary = data.history(context.canary, 'price', 80, '1d')
#    RiskFree = data.history(context.risk_free, 'price', 80, '1d')
#    Canary_Mom = Canary[-1]/Canary[-20]
     Canary_fast = Canary[-15:].mean()
     Canary_slow = Canary.mean()
#    RiskFree_Mom = RiskFree[-1]/RiskFree[-20]
    
     if Canary_fast > Canary_slow:    
#   if Canary_Mom > RiskFree_Mom:
        #order_target_percent(context.GLD,0.25)
        order_target_percent(context.SPY,3.00)
        #order_target_percent(context.SHY,0.25)
        #order_target_percent(context.IEF,0.25)
        #order_target_percent(context.IOO,0.25)
     else: 
        #order_target_percent(context.GLD,0.25)
        order_target_percent(context.SPY,0.00)
        #order_target_percent(context.SHY,0.25)
        #order_target_percent(context.IEF,0.25)
        #order_target_percent(context.IOO,0.25)
        pass
        

########### HELPER FUNCTIONS ##############################################
        
## IN LIVE TRADE ALL OPEN ORDERS ARE CANCELLED EOD. HANDLE EXPLICITLY WITH THIS
## HELPER FUNCTION SO THAT ALL OPEN ORDERS ARE ALSO CANCELLED AT EOD IN BACKTEST.
def cancel_orders_EOD(context):

    date = get_datetime().date()
    
    # set the closing hour, based on get_early_closes (assumes that all early closes are at 13)
    if date in context.early_closes:
        close = 13 # early closing time
    else:
        close = 16 # normal closing time
    
    loc_dt = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    # if it is EOD, find open orders and cancel them, otherwise return
    if loc_dt.hour == close and loc_dt.minute == 0:
        pass
    else:
        return
    
    all_open_orders = get_open_orders()
    if all_open_orders:
        for security, oo_for_sid in all_open_orders.iteritems():
            for order_obj in oo_for_sid:
                log.info("%s: Cancelling order for %s of %s created on %s" % 
                         (get_datetime(), order_obj.amount,
                          security.symbol, order_obj.created))
                cancel_order(order_obj)    
                
## RETURNS TRUE IF THERE ARE PENDING OPEN ORDERS, OTHERWISE RETURNS FALSE
def has_open_orders(data,context):               
# Only rebalance when we have zero pending orders.
    has_orders = False
    for stk in data:
        orders = get_open_orders(stk)
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=stk)  
            has_orders = True
    return has_orders           
    if has_orders:
        return  
There was a runtime error.

Aren't these other strategies essentially completely different, and variations on the same-old moving average crossover TLT/SPY stuff?

Thank you so much everyone!! You've helped me alot!

The real questions are:

  • does market timing work (or is it something we create with back
    testing); and
  • will long bonds continue to provide protection in the
    future.

Any answer to the first question is guesswork, the second question is perhaps more interesting.

Take a look at bond futures prices: 1) unadjusted and 2) back adjusted. Take a look at a bond index: 1) price only 2) with coupons reinvested. Take a look at a bond mutual fund: 1) price only and 2) with coupons reinvested.

This will tell you where bond returns come from when you operate a constant maturity scheme.

Ask yourself: where do bond returns come from given the operation of a constant maturity scheme?

Do bond returns come mostly from price movement? Do bond returns come mostly from coupon? If the former then investment in a bond fund is a disaster in an era of rising rates. If the latter then you may come to a different conclusion.

The overwhelming majority of the return from bonds is from the coupon, therefore my own thoughts are that while there will inevitably be a downwards price movement in bonds funds as interest rates rise this should be outweighed by rising coupons.

Hey,
Have your gone with this algo at last? is it recommended?

Is it finely suited for Interactive Brokers?

anyone ever actually go live with this?

You can clone the algos and run a back-test on the out-of-sample time since they were posted. The OP's original algo has continued to work. Beware though that it offers no hedging or protection during a crash. I also checked one of the "improved" versions and it failed miserably on the out-of-sample timeframe.