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)

1034
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_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

149
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_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

20
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_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.

126
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_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,

"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:

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 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_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

# -------------------------------------------------------
# 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.