Back to Community
Rebalance Algo: 9 Sector ETFs

This is a refined version of the algorithm that Quantopian has been using with real money since January.

The algorithm invests in each of the nine Select Sector SPDR exchange-traded funds (ETFs). The target is equal weight of each sector. The resulting portfolio delivers exposure to every sector of the US stock market and can be characterized as a long-only, large-cap, equity market diversified portfolio. This version of the algorithm automatically rebalances your holdings every 7 calendar days to maintain equal exposure across all sectors.

To connect it to your own brokerage account:

  1. Click the "Clone" button below to make your own copy of the algorithm.
  2. Click the "Run a Full Backtest" button to get it ready.
  3. Click the "Live Trade Algorithm" to kick off and deploy it to your broker

Those are the steps that get you investing using this algorithm.

(Note that you will need to add your broker user name if you haven't already, and you will need to apply to the pilot program if you haven't already)

Clone Algorithm
1034
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 algorithm defines a long-only equal weight portfolio and rebalances it at a user-specified frequency 
    NOTE: This algo is intended to run in minute-mode simulation and is compatible with LIVE TRADING.

'''
# Import the libraries we will use here
import datetime
import pytz
import pandas as pd

def initialize(context):
    # This initialize function sets any data or variables that you'll use in your
    # algorithm. You'll also want to define any parameters or values you're going to use.

    # In our example, we're looking at 9 sector ETFs.  

    context.secs =   [ sid(19662),  # XLY Consumer Discrectionary SPDR Fund   
                       sid(19656),  # XLF Financial SPDR Fund  
                       sid(19658),  # XLK Technology SPDR Fund  
                       sid(19655),  # XLE Energy SPDR Fund  
                       sid(19661),  # XLV Health Care SPRD Fund  
                       sid(19657),  # XLI Industrial SPDR Fund  
                       sid(19659),  # XLP Consumer Staples SPDR Fund   
                       sid(19654),  # XLB Materials SPDR Fund  
                       sid(19660) ] # XLU Utilities SPRD Fund

    # Change this variable if you want to rebalance less frequently
    context.Rebalance_Days = 7

    # These other variables are used in the algorithm for leverage, trade time, etc.
    context.rebalance_date = None
    context.weights = 0.99/len(context.secs)
    context.rebalance_hour_start = 10
    context.rebalance_hour_end = 15

    # These are the default commission and slippage settings.  Change them to fit your
    # brokerage fees. These settings only matter for backtesting.  When you trade this 
    # algorithm, they are moot - the brokerage and real market takes over.
    set_commission(commission.PerTrade(cost=0.03))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

def handle_data(context, data):

    # Get the current exchange time, in the exchange timezone 
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

    # If it's a rebalance day (defined in intialize()) then rebalance:
    if  context.rebalance_date == None or exchange_time > context.rebalance_date + datetime.timedelta(days=context.Rebalance_Days):

        # Do nothing if there are open orders:
        if has_orders(context):
            print('has open orders - doing nothing!')
            return

        rebalance(context, data, exchange_time)  

def rebalance(context, data, exchange_time):  
    # Only rebalance if we are in the user specified rebalance time-of-day window
    if exchange_time.hour < context.rebalance_hour_start or exchange_time.hour > context.rebalance_hour_end:
       return

    # Do the rebalance. Loop through each of the stocks and order to the target
    # percentage.  If already at the target, this command doesn't do anything.
    # A future improvement could be to set rebalance thresholds.
    for sec in context.secs:
        order_target_percent(sec, context.weights, limit_price=None, stop_price=None)

    context.rebalance_date = exchange_time
    log.info("Rebalanced to target portfolio weights at %s" % str(exchange_time))

def has_orders(context):
    # Return true if there are pending orders.
    has_orders = False
    for sec in context.secs:
        orders = get_open_orders(sec)
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=sec)  
                log.info(message)

            has_orders = True
    return has_orders
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.
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.

23 responses

a simplified version of dans algo, using 2x leverage and rebalancing every 5 days

Clone Algorithm
149
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 algorithm defines a long-only equal weight portfolio and rebalances it at a user-specified frequency 
    NOTE: This algo is intended to run in minute-mode simulation and is compatible with LIVE TRADING.

'''
# Import the libraries we will use here
import datetime
import pytz
import pandas as pd

def initialize(context):
    # This initialize function sets any data or variables that you'll use in your
    # algorithm. You'll also want to define any parameters or values you're going to use.

    # In our example, we're looking at 9 sector ETFs.  

    context.secs =   [ sid(19662),  # XLY Consumer Discrectionary SPDR Fund   
                       sid(19656),  # XLF Financial SPDR Fund  
                       sid(19658),  # XLK Technology SPDR Fund  
                       sid(19655),  # XLE Energy SPDR Fund  
                       sid(19661),  # XLV Health Care SPRD Fund  
                       sid(19657),  # XLI Industrial SPDR Fund  
                       sid(19659),  # XLP Consumer Staples SPDR Fund   
                       sid(19654),  # XLB Materials SPDR Fund  
                       sid(19660) ] # XLU Utilities SPRD Fund

    # Change this variable if you want to rebalance less frequently
    context.rebalance_days=5

    # These other variables are used in the algorithm for leverage, trade time, etc.
    context.today = None
    context.weights = 0.99/len(context.secs)    
    context.leverage=2
    

    # These are the default commission and slippage settings.  Change them to fit your
    # brokerage fees. These settings only matter for backtesting.  When you trade this 
    # algorithm, they are moot - the brokerage and real market takes over.
    set_commission(commission.PerTrade(cost=0.03))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

def handle_data(context, data):

    # Get the current exchange time, in the exchange timezone 
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    if  context.today == None or exchange_time >= context.today + datetime.timedelta(days=context.rebalance_days):
        #new day, open positions
        context.today = exchange_time
        
        for sec in context.secs:
            order_target_percent(sec, context.weights * context.leverage, limit_price=None, stop_price=None)
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Dear sir,

Could give us a fixed period and money investment example that strategy could let user specify a fixed invest money by each month period.
example like following: Initial total invest amount $10000 dollar , and then continue invest $5000 dollar for each month

I try to modify the algorithm to do systematic(periodic) investment plan for every 30 days , I using following redeposit code , but this seems not work, could help ?

context.portfolio.cash = context.portfolio.cash + context.reinvest_cash  
Clone Algorithm
20
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 algorithm defines a long-only equal weight portfolio and rebalances it at a user-specified frequency 
    NOTE: This algo is intended to run in minute-mode simulation and is compatible with LIVE TRADING.

'''
# Import the libraries we will use here
import datetime
import pytz
import pandas as pd

def initialize(context):
    # This initialize function sets any data or variables that you'll use in your
    # algorithm. You'll also want to define any parameters or values you're going to use.

    # In our example, we're looking at 9 sector ETFs.  

    context.secs =   [ sid(19662),  # XLY Consumer Discrectionary SPDR Fund   
                       sid(19656),  # XLF Financial SPDR Fund  
                       sid(19658),  # XLK Technology SPDR Fund  
                       sid(19655),  # XLE Energy SPDR Fund  
                       sid(19661),  # XLV Health Care SPRD Fund  
                       sid(19657),  # XLI Industrial SPDR Fund  
                       sid(19659),  # XLP Consumer Staples SPDR Fund   
                       sid(19654),  # XLB Materials SPDR Fund  
                       sid(19660) ] # XLU Utilities SPRD Fund

    # Change this variable if you want to rebalance less frequently
    context.Rebalance_Days = 7
    
    #Change this variable if you have Periodic Investment Plan
    #context.portfolio.starting_cash = 5000
    context.Reinvest_Days = 30
    context.reinvest_date = None
    context.reinvest_cash = 5000
    # These other variables are used in the algorithm for leverage, trade time, etc.
    context.rebalance_date = None
    context.weights = 0.99/len(context.secs)
    context.rebalance_hour_start = 10
    context.rebalance_hour_end = 15

    # These are the default commission and slippage settings.  Change them to fit your
    # brokerage fees. These settings only matter for backtesting.  When you trade this 
    # algorithm, they are moot - the brokerage and real market takes over.
    set_commission(commission.PerTrade(cost=0.03))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

def handle_data(context, data):

    # Get the current exchange time, in the exchange timezone 
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')

    #If it's a reinvest day (define in initialize()), then reinvest cash
    if context.reinvest_date == None or exchange_time > context.reinvest_date + datetime.timedelta(days=context.Reinvest_Days):
        log.info("reinvest date %s for %s dollar:" % (str(exchange_time),str(context.reinvest_cash)))
        record(current_portfolio_value=context.portfolio.portfolio_value)
        
        context.portfolio.cash = context.portfolio.cash + context.reinvest_cash
        context.reinvest_date = exchange_time
        
        record(after_reinvest_portfolio_value=context.portfolio.portfolio_value)
        
    
    # If it's a rebalance day (defined in intialize()) then rebalance:
    if  context.rebalance_date == None or exchange_time > context.rebalance_date + datetime.timedelta(days=context.Rebalance_Days):

        # Do nothing if there are open orders:
        if has_orders(context):
            print('has open orders - doing nothing!')
            return

        rebalance(context, data, exchange_time)  

    
def rebalance(context, data, exchange_time):  
    # Only rebalance if we are in the user specified rebalance time-of-day window
    if exchange_time.hour < context.rebalance_hour_start or exchange_time.hour > context.rebalance_hour_end:
       return

    # Do the rebalance. Loop through each of the stocks and order to the target
    # percentage.  If already at the target, this command doesn't do anything.
    # A future improvement could be to set rebalance thresholds.
    for sec in context.secs:
        order_target_percent(sec, context.weights, limit_price=None, stop_price=None)

    context.rebalance_date = exchange_time
    log.info("Rebalanced to target portfolio weights at %s" % str(exchange_time))

def has_orders(context):
    # Return true if there are pending orders.
    has_orders = False
    for sec in context.secs:
        orders = get_open_orders(sec)
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=sec)  
                log.info(message)

            has_orders = True
    return has_orders
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.

Hi Devon,

Our backtester doesn't have a great way of simulating in-flows and out-flows currently. Our performance metrics will treat inflows like profits and outflows like losses.

The best example I've seen that sort of does what you're looking for is this algorithm Dan Dunn shared on Dollar Volume Averaging. Note that it does not address the performance and risk measure issues, but it does let you simulate trading with a growing capital base.

I think supporting a more realistic simulation of an account with in/outflows is a great product suggestion and I've added it to our issue queue.

Best, Jess

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.

This is interesting. I'd like to figure out why this algorithm gives (or appears to give) better returns than S&P 500. Here are a few ideas, which might be misguided or based on wrong information etc, so feel free to correct me if you know better (I'm here to learn, not to beat about the bushes):

  1. If the transaction costs in the backtest are set too low (underestimated), then one would expect to be able to beat the S&P500, even by tracking the same portfolio, just by rebalancing more often.

  2. Slippage might be underestimated, and this could be the cause of the additional returns, in other words: if you were to trade this algorithm live, the returns would be diminished from what you see in the backtest.

  3. (I think this is most likely): The weight when rebalancing this portfolio has been chosen to be equal for each sector. However, the S&P500 is market value weighted. This means that each stock in the list of 500 stocks is weighted based on its value at the time of re-balancing. Presumably this does not give equal weights for each sector? If this is true, then consider that it is possible that one (or more) sectors happened to have higher returns over the past 10 years compared with the others (this is guaranteed to happen). If the weight of this sector was lower in the S&P500 (due to a smaller total market capitalization), then the difference in returns between this algo and the S&P500 could be explained by the additional weight (i.e. holdings) for the sector that happened to do better. If this is true, I think it means that the fact that the algo beats the S&P500 in the backtest does not mean that it would necessarily beat it in all future scenarios. It depends if equal weights of sectors beats market cap weighting, and that could change over the years. Perhaps this could be viewed as an additional risk factor that cannot be seen in this backtest.

Hi Gili,

I posted a more in-depth answer just now in a new thread. Short answer is that your #3 is the main driver of outperformance. I'd certainly agree with you that this strategy would not beat the benchmark in "all future scenarios", but actually I think the equal-weighted strategy has a smaller exposure to a size-based risk factor than the SPY does. That's a bit of a semantic point perhaps though :)

Best wishes and thanks for the thoughtful question! Jess

Thanks, Jessica! I read your reply in the other thread. It might be instructive to plot the average return of the overweight sectors vs. the average return of the underweight sectors (as a function of time - daily or minutely). My guess is that you will indeed find that the overweight sectors have a higher return. If so, this is perhaps surprising, since Financials and Technology are in the underweight group, which most people might label is "high return". However, on the other hand, it could be that over this period the returns for Utilities, Materials and Staples were more stable (as one would expect) than Financials and Technology which most likely also suffered more in the crash. Since the holdings for each sector within S&P500 changes over time, it could also be that different sectors are overweight and underweight at different times.

I also wonder if there are significant periods in the backtest where S&P500 actually had higher returns, and in that case it could be that this is difficult to see due to an initial period with higher returns for the equal weighted strategy.

Just to clarify, at each time step, I think one would have to find the market cap percentage for each sector, figure out which are the overweight and underweight sectors (defined as more than 0.5% different from 11%, for example). Then find a separate weighted average for the over and underweighted groups, which are weighted by the difference from 11%. This is complicated by the S&P500's composition changing over time.

Typo guys - and note to those who have cloned this popular algo:

set_commission(commission.PerTrade(cost=0.03))  

should be

set_commission(commission.PerShare(cost=0.03))

If only we could have 3 cents per trade commissions! :) As noted, this does not affect real-time trading, but in backtesting if you rebalance a lot, it is worthy of note.

Hi Ken - thanks for pointing that out.

I think this snuck in because we had the autocomplete set to fill in $0.03 for commissions regardless of whether you chose the PerTrade or PerShare basis - we have since corrected that problem and now autocomplete on PerTrade basis defaults to $1.00.

Since IB's 'flat rate' costs are $1.00 minimum per trade, $0.005 per share thereafter (to a max of 0.5% of the trade value) I think that the $1.00 per trade default is the most realistic option to use unless your strategy will be placing trades of 200 shares or more on a routine basis.

Best wishes, Jess

I agree, in fact allowing you to specifying both parameters and the function pick the max (what IB does) might not be a bad idea :).

Turns out that an equal weighted S&P500 index exists: SPW. It might be worth comparing this algo's performance to it, as well as ETF's that track it, such as Guggenheim Equal Weighted S&P500 ETF. Specifically, you'd have to have higher returns than the ETF for it to be worthwhile to algo trade this. The ETF might have an advantage in lower transaction costs, but perhaps with a smaller portfolio one could snatch some deals that the much bigger ETF cannot (but this might be hard).

    # If it's a rebalance day (defined in intialize()) then rebalance:  
    if  context.rebalance_date == None or exchange_time > context.rebalance_date + datetime.timedelta(days=context.Rebalance_Days):  

Not a big deal but the above line makes the algo weekly reblance to move 1min out every time it rebalances. changing the ">" to ">=" makes sure the rebalance happens on at the same time every week.

    # If it's a rebalance day (defined in intialize()) then rebalance:  
    if  context.rebalance_date == None or exchange_time >= context.rebalance_date + datetime.timedelta(days=context.Rebalance_Days):  

Hi Gili,

Now that we have the ability to swap out the default benchmark (SPY) for any security in the universe you can actually run a backtest of this 9 sector equal ETF strategy and look at returns and risk metrics against something like the Guggenheim Equal Weighted S&P500 ETF you mentioned (ticker: RSP). Note: The cumulative performance of the RSP benchmark doesn't include the 0.4% expense ratio that you'd pay to own that ETF, whereas the algo performance includes $1.00 per trade transaction costs for a fairly aggressive weekly rebalance.

Also, the RSP (or the equal-weighted SP500 index) can have pretty strong sector exposures based on what securities are selected to be included in the SP500 at any given time. So for example if you look at the RSP's sector exposure right now (see table below) you can see that it is overweight financials and consumer discretionary and underweight utilities, materials, and telecom.

RSP sector exposure

Clone Algorithm
126
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 algorithm defines a long-only equal weight portfolio and rebalances it at a user-specified frequency 
    NOTE: This algo is intended to run in minute-mode simulation and is compatible with LIVE TRADING.

'''
# Import the libraries we will use here
import datetime
import pytz
import pandas as pd

def initialize(context):
    # This initialize function sets any data or variables that you'll use in your
    # algorithm. You'll also want to define any parameters or values you're going to use.

    # In our example, we're looking at 9 sector ETFs.  
    context.secs =   [ sid(19662),  # XLY Consumer Discrectionary SPDR Fund   
                       sid(19656),  # XLF Financial SPDR Fund  
                       sid(19658),  # XLK Technology SPDR Fund  
                       sid(19655),  # XLE Energy SPDR Fund  
                       sid(19661),  # XLV Health Care SPRD Fund  
                       sid(19657),  # XLI Industrial SPDR Fund  
                       sid(19659),  # XLP Consumer Staples SPDR Fund   
                       sid(19654),  # XLB Materials SPDR Fund  
                       sid(19660) ] # XLU Utilities SPRD Fund

    # Change this variable if you want to rebalance less frequently
    context.Rebalance_Days = 7

    # These other variables are used in the algorithm for leverage, trade time, etc.
    context.rebalance_date = None
    
    context.rebalance_hour_start = 10
    context.rebalance_hour_end = 15
    context.percentage = 0.99

    # These are the default commission and slippage settings.  Change them to fit your
    # brokerage fees. These settings only matter for backtesting.  When you trade this 
    # algorithm, they are moot - the brokerage and real market takes over.
    set_commission(commission.PerTrade(cost=1.00))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))
    
    set_benchmark = sid(24744)

def handle_data(context, data):
    
    # Get the current exchange time, in the exchange timezone 
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    # If it's a rebalance day (defined in intialize()) then rebalance:
    if  context.rebalance_date == None or exchange_time > context.rebalance_date + datetime.timedelta(days=context.Rebalance_Days):

        # Do nothing if there are open orders:
        if has_orders(context):
            print('has open orders - doing nothing!')
            return
        
        calculate_target_weights(context)
        rebalance(context, data, exchange_time)  

def calculate_target_weights(context):
       
    
    def pos_val(stock):
        pos = context.portfolio.positions[stock]
        return pos.amount * pos.last_sale_price
    
    total = context.portfolio.cash + \
            sum([pos_val(s) for s in context.secs])
    context.weights = (context.percentage * total / context.portfolio.portfolio_value) / len(context.secs)
    
def rebalance(context, data, exchange_time):  
    # Only rebalance if we are in the user specified rebalance time-of-day window
    if exchange_time.hour < context.rebalance_hour_start or exchange_time.hour > context.rebalance_hour_end:
       return

    # Do the rebalance. Loop through each of the stocks and order to the target
    # percentage.  If already at the target, this command doesn't do anything.
    # A future improvement could be to set rebalance thresholds.
    for sec in context.secs:
        order_target_percent(sec, context.weights, limit_price=None, stop_price=None)

    context.rebalance_date = exchange_time
    log.info("Rebalanced to target portfolio weights at %s" % str(exchange_time))

def has_orders(context):
    # Return true if there are pending orders.
    has_orders = False
    for sec in context.secs:
        orders = get_open_orders(sec)
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=sec)  
                log.info(message)

            has_orders = True
    return has_orders
There was a runtime error.

Interesting, I didn't realize that RSP is not actually equal weight in sectors. Presumably it is equal weight within the sectors, otherwise nothing about it would be equal weighted...

It's nice that we can now benchmark against other indices, stocks, etc, but could this information somehow appear in the plot? (or maybe it does, but I don't see it?). For example, instead of the word benchmark, it could say SPY or RSP or whatever the benchmark was, or maybe better "Benchmark (SPY)". Otherwise I don't see an easy way to see against what asset the algo was benchmarked against. Even in the code it is not that transparent, since if I understand correctly, the only change (for changing the benchmark), is changing the sid in "set_benchmark = sid(24744)".

Ken,

Per your post:
"I agree, in fact allowing you to specifying both parameters and the function pick the max (what IB does) might not be a bad idea :)."

Note that this functionality already exists. Try the following:
set_commission(commission.PerShare(cost=0.013, min_trade_cost=1.3))

As an aside, note that IB's 'flat rate' costs are actually $1.30 minimum for US API directed orders (always read the fine print (-;). Be sure to click on the arrow for Exceptions (API, Global X ETFs, ...) to view the fees for US API Directed Orders.

In addition, the per share cost is $0.013 for transactions with 500 or fewer shares and $0.008 for more than 500 shares.

All.

I'm really taken by the powerful simplicity of this algo. I've been modifying and live testing it for some time now. I decided it was time to give back to the community and more importantly get the benefit of your review. The most significant change is probably the added ability to weight each sector differently. This allows you to take advantage of short term imbalances among sectors. Doing so also diminishes the mean reversion characteristics of equal sector weighting as noted by Jessica S. in her 4/21/14 post above.

I look forward to your feedback.

Best,
Tom

Clone Algorithm
93
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
'''
Algo Rebalance Sector ETFs Rev. 0.9

# --------------------------------------------------------------------------------
source: https://www.quantopian.com/posts/rebalance-algo-9-sector-etfs (accessed 11/2014)
reference: http://www.sectorspdr.com/sectorspdr/tools/sector-tracker

This algorithm is based on Dan Dunn's shared algorithm 'Rebalance Algo: 9 Sector ETFs' which is
shared at the 'source' link above. It defines a long-only equal OR UN-EQUaLLY weighted portfolio 
and rebalances it at a specified frequency. It also incorporates various other changes from the 
'source' thread above and others of my own doing. For details see the summary below.

NOTE: This algo is intended to run in minute-mode simulation and is compatible with LIVE TRADING.

# ---------------------------------------------------------
11/8/2014, Added raging bear trap. It can be switched on/off with a single line to facilitate deeper
backtesting. It protects against catastrophic drops due to unforeseen events by tracking SPY for a drop 
below early 2014 levels. Changes marked by `ts.

11/16/2014, Added total initial investment. Changes marked by `ts.

11/17/2014, Limit cash available to this algorithm to 10% ($1000) of IB account total ($10k) until 
I'm comforatable with live trading. Removed yesterday's ineffective change and replaced it with a 
fix from Alisa D.. Changes marked by `ts.

11/20/2014, Added closing of all positions just before the end of day. Aside from the bear trap 
this is useful when it's necessary to leave IB in that state when modifiying the algo. Changes marked by `ts.

11/27/2014. Numerous unmarked changes, adds, deletes. 

    1) Simplified the code by switching from update_data() to schedule_function(). 

    2) Added ability to weight each sector differently. This allows one to take advantage of 
    short term inbalances among sectors. It also diminishes the mean reversion characteristics
    of equal sector weighting as noted by Jessica S. in her 4/15/14 post in the 'source' 
    thread above. Regularly monitor the 'reference' link above for any needed changes.

    3) Added some simple logic to avoid margin (i.e. negative cash) and excessive draw down.

    4) Limited, informal testing indicates that weekly rebalancing works as well as shorter intervals. It also
       indicates that approx. $10k of inital cash is needed to outperform SPY.

    5) Eliminated code obviated by the changes for the sake of read-ability.

# --------------------------------------------------------------------------------
'''

import datetime
import pytz
import pandas as pd

# ---------------------------------------------------------
def initialize(context):
        
    # --------------------------------
    # run on on sched. instead of on price change event (i.e. handle_data())

    # every day 3 hrs. after market opens 
    # schedule_function(algorithm, date_rules.every_day(), time_rules.market_open(hours = 3, minutes = 0))
 
    # every Wed., 3 hrs. after market opens 
    schedule_function(algorithm, date_rules.week_start(days_offset = 2), time_rules.market_open(hours = 3, minutes = 0))

    #`ts +1, close all postions just before end of day to facilitate algo. mod.'s
    # schedule_function(close_all_postions, date_rules.every_day(), time_rules.market_close(minutes=15))

    # --------------------------------
    # list of dictionaries containing the 9 sector ETFs and a weight specifc to each
    # it's been tweaked to take advantage of recent inbalances in performance
    # regularly monitor the 'reference' link above for any needed changes 
    context.sector_weighted_secs = [ 
        {'sec': symbol('XLY'), 'weight': 1.00,},  # XLY Consumer Discrectionary SPDR Fund   
        {'sec': symbol('XLF'), 'weight': 1.00,},  # XLF Financial SPDR Fund  
        {'sec': symbol('XLK'), 'weight': 1.50,},  # XLK Technology SPDR Fund  
        {'sec': symbol('XLE'), 'weight': 0.01,},  # XLE Energy SPDR Fund  
        {'sec': symbol('XLV'), 'weight': 1.70,},  # XLV Health Care SPRD Fund  
        {'sec': symbol('XLI'), 'weight': 1.00,},  # XLI Industrial SPDR Fund  
        {'sec': symbol('XLP'), 'weight': 1.00,},  # XLP Consumer Staples SPDR Fund   
        {'sec': symbol('XLB'), 'weight': 1.00,},  # XLB Materials SPDR Fund  
        {'sec': symbol('XLU'), 'weight': 1.00,},  # XLU Utilities SPRD Fund
    ]
    #`ts +3, for chacking the data structure above
    # log.info('length: %d' % (len(context.sector_weighted_secs),))
    # for sec in context.sector_weighted_secs:
    #     log.info('sid: %d' % (sec['sec'].sid,))
    
    # --------------------------------
    #'ts -1+2, limit cash available to this algorithm to 10% ($1000) of IB account total ($10k)
    # context.weights = 0.99/len(context.secs)
    percent_of_account = 1.0  # change this to 1.0 if you want to beat SPY or less as needed  
    context.weights = percent_of_account * 0.99 / len(context.sector_weighted_secs)

    # --------------------------------
    #`ts +3, raging bear trap
    context.bear_trapped = False
    context.bear_untrap = 180
    context.bear_trap = 175

    # --------------------------------
    # avoid margin and excessive draw down
    context.cash_initial = 10000 # in dollars
    context.position_value_min = 0.80 * context.cash_initial # in dollars
    context.cash_min = 0.05 * context.cash_initial # in dollars
    context.start_up = None
    
    # --------------------------------
    # these settings only only apply backtesting
    #`ts -1+1, as suggested in thread
    # set_commission(commission.PerTrade(cost=0.03))
    set_commission(commission.PerShare(cost=0.013, min_trade_cost=1.3))
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

# ---------------------------------------------------------
def handle_data(context, data):
    # run algorithm at start up rather than waiting up to a week
    if context.start_up == None:
        context.start_up = True
        algorithm(context, data)

# ---------------------------------------------------------
def algorithm(context, data):
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    #`ts +1, 11/18/2014
    record(cash = context.portfolio.cash, positions = context.portfolio.positions_value)

    # -------------------------------------------------------
    # avoid margin and excessive draw down
    if context.portfolio.cash < context.cash_min:
        return
        if context.portfolio.positions_value < context.position_value_min:
            return

    # -------------------------------------------------------
    #`ts begin add, raging bear trap, such a hack but seems to work
    # todo: attempt to unify all this algo.'s logic with a finite state machine
    if True:  # change False to True to enable bear trap and vice versa
        price_current = data[symbol('SPY')].price
        if (price_current > context.bear_untrap):
            context.bear_trapped = None
        elif (price_current < context.bear_trap):
            context.bear_trapped = True
            log.info('%s: bear trapped' % 
                (str(exchange_time))
            )
            close_all_postions(context, data)
        if (context.bear_trapped == True):
            return
    #`ts end add

    # -------------------------------------------------------
    # do nothing if there are open orders else rebalance
    if has_orders(context):
        # print('has open orders - doing nothing!')
        return
    rebalance(context, data, exchange_time)  

# ---------------------------------------------------------
def rebalance(context, data, exchange_time):  
    # iterate over each stocks and order to the target percentage 
    # if already at target, do nothing
    # todo: set rebalance thresholds.
    for sec in context.sector_weighted_secs:
       order_target_percent(sec['sec'], context.weights * sec['weight'], limit_price=None, stop_price=None)
    log.info('%s: rebalanced, cash: %f, value of open positions: %f, bench mark price: %f' % 
        (
            str(exchange_time), context.portfolio.cash, 
            context.portfolio.positions_value, data[symbol('SPY')].price,
        )
    )

# ---------------------------------------------------------
def has_orders(context):
    # return True if pending orders else False
    has_orders = False
    for sec in context.sector_weighted_secs:
        orders = get_open_orders(sec['sec'])
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=sec['sec'])  
                log.info(message)
            has_orders = True
    return has_orders

#'ts begin add, close all postions just before end of day
# ---------------------------------------------------------
def close_all_postions(context, data):
    # sell everything
    # iterate over each possible position and ensure it's closed
    # if already cloed, nothing happens
    log.info('closing all positions')
    for sec in context.sector_weighted_secs:
        security = sec['sec']
        if(context.portfolio.positions[security].amount > 0):
            order_target_percent(sec['sec'], 0)
#'ts end add, close all postions just before end of day
There was a runtime error.

Jessica, Alisa,

Is there an explanation of virtual machine boot resets/restarts/schedules? This is in relation to production preparation of a strategy that may set various state variables during initialization. I read about the system reset on outages in the Help, and the fact that there won't be a system restart until the next day on an outage event, but what about scheduled down times and restarts?

This applies to the strategy above that the Q is running in production mode as an example. Within this strategy a global state variable "context.rebalance_date = None" is cleared on initialize. As each minute is processed, and the scheduled exchange time finally comes into view a rebalance will occur. If a strategy is restarted then this state variable will be cleared again forcing another rebalance on the next exchange trading window. Restarting this strategy daily would result in a rebalance, daily. (This is obviously not an issue for back test where only one initialize is called.) A rebalance after a reset may not be avoidable, but still, what awareness was considered for this eventuality?

In brief, in production (live trading), when are strategies VM's reset, or the strategies themselves restarted? And what other conditions or issues should a production system be aware of with regards to trading platform constraints, contingencies and such?

@Jessica: you say "The cumulative performance of the RSP benchmark doesn't include the 0.4% expense ratio that you'd pay to own that ETF" (4-21-14). My understanding is that the expense ratio is not a direct charge to the investor but taken out of net assets and reflected in the lower share price or dividend rate. So the RSP ETF DOES include the 0.4% expense ratio, and we don't have to model this expense. If I am wrong let me know so we can model what would be a substantial additional drag to the portfolio !

@Market Tech,

Live trading algorithms are kept logged in every day, and automatically get ready in the morning for the trading day. We run the algorithms on servers through Amazon Web Services, and if one of their servers is scheduled for decommission, we will notify you. We'll schedule a day after market close to move your algorithm to a new server - which we handle in our back end.

All you need to do is choose which day you want to relogin to your IB algorithm, and login after receiving an automated email from us. It's easy!

When this happens, your algorithm is not "restarted", it will pick up from the place it left off. There are a couple rough edges here to be aware of. I would recommend to use "context" on your variables to save state. And also avoid hard-coding a specific day or time. What happens if the algorithm got disconnected (server, login, infrastructure issues to name a few options) during the moment it was supposed to execute a trade? Instead, you should use "smart" logic to trade at or at the next available specific date or time. In the example we posted, you can see this on line 59

exchange_time.hour < context.rebalance_hour_start or exchange_time.hour > context.rebalance_hour_end:  

And now there's the new schedule_function which makes it even easier.

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, thanks for reply.

Is is safe to say then that the Q's "context" is effectively a state object that is persisted for the life of an algo, after initialization, until at what time a quant is notified that the algo may be moved and restarted?

@Paul Yes, I believe you are correct. If you are using the actual ETF, such as RSP, as the benchmark you specify in your backtest then the expense fee is already rolled into the price of the ETF (If I recall how ETF fees work is that a little bit is taken each day of which sums up to the total annual fee over the course of a year).

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.

@Market Tech, yes that's exactly right.