Quantopian Tutorial with Sample Momentum Algorithm - Lesson 1: The basics of the IDE

2/16/2016 This tutorial is out-dated, please view the new versions here: https://www.quantopian.com/posts/quantopian-tutorials

Hey guys,

We just finished up the first lesson in our Quantopian new user webinar series so thanks to those who joined and for those who didn't, we'll have more in the future and will be posting up a recording to this one shortly so you can see it as well.

We covered the basics of the IDE, what you can and cannot do in Python versus IDE, and a walkthrough of a simple momentum trading strategy

The rest of the tutorial series:

Some notes that you guys will find helpful:

Further Work

For those who are looking for more, take the algorithm I've attached to this post and play around with the moving average periods to create an algorithm with highest % returns

- From 1/1/2007-11/1/2014
- Convert it to use schedule_function instead of get_datetime()
- Use no leverage.

I encourage you to post your answers here and let's see what you get!

Thanks to @David Edwards for the algorithm

Seong

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
from datetime import datetime, timedelta
import numpy as np

def initialize(context):

#: Settinng our universe to be between the top 99.5% and the top 100% of stocks by Dollar Volume
set_universe(universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))
#: Setting the number of stocks that we want to long and the number of stocks that we want to short
context.stocks_to_long = 5
context.stocks_to_short = 5

schedule_function(func=rebalance,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open())
schedule_function(func=day_counter,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_close())

context.rebalance_days = 10
context.current_days_counted = 0

def day_counter(context, data):
"""
Increments our day counter at the end of day, every day
"""
context.current_days_counted += 1

def rebalance(context, data):
"""
The logic for rebalancing our algorithm
"""
if get_open_orders():
return
#: A quick check to see that we're only rebalancing every X days, defined by
#: context.rebalance_days
if context.current_days_counted % context.rebalance_days != 0:
return

#: Getting 200 days worth of historical data
#: If you wanted an intraday strategy based of minutely data you could change '1d' to '1m'
historical_data = history(200, '1d', 'price')

#: Getting the difference between the 50 day mean and the 200 day mean
past_50_day_mean = historical_data.tail(50).mean()
past_200_day_mean = historical_data.mean()
diff = past_50_day_mean / past_200_day_mean - 1

#: Cleaning up our diffs by removing any NaNs and sorting it in ascending order
diff = diff.dropna()
diff.sort()

#: Recording the stocks that we want to buy and the stocks that we want to sell
#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]

#: Create weights for our securities
short_length = min(context.stocks_to_short, len(sells))
short_weight = -1.0/short_length if short_length != 0 else 0

#: Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

#: Define a 2% stoploss for each security
stops =  historical_data.iloc[-1] * 0.02

#: Iterate through each security in data
for sym in data:

#: If the security exists in our sells.index then sell
if sells is not None and sym in sells.index:
log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
stop_price=data[sym].price - stops[sym])

log.info('LONG: %s'%sym.symbol)
stop_price=data[sym].price + stops[sym])

#: If the security is in neither list, exit any positions we might have in that security
else:
order_target(sym, 0)

def handle_data(context, data):
pass


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.

24 responses

Hi Seong, looking forward for the recording. Many thanks!

The recording has been uploaded here: http://bit.ly/1xAHAsp!

Hi, could you please give a hint how not to use leverage? Cannot find a solution. Thank you in advance!

Mikhail,
The attached backtest records the leverage and has it adjusted down to roughly 1. The leverage used is the sum of the absolute values of the numerators in lines 53 & 54, so to constrain the leverage I changed those numerators to +-0.5 instead of +-1.0.

David

69
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
from datetime import datetime, timedelta
import numpy as np

def initialize(context):

#: Settinng our universe to be between the top 99.5% and the top 100% of stocks by Dollar Volume
set_universe(universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))
#: Setting the number of stocks that we want to long and the number of stocks that we want to short
context.stocks_to_long = 5
context.stocks_to_short = 5

context.rebalance_date = None
context.rebalance_days = 10

def handle_data(context, data):
record_leverage(context, data)

if context.rebalance_date != None:
next_date = context.rebalance_date + timedelta(days=context.rebalance_days)

if context.rebalance_date == None or get_datetime() == next_date:
context.rebalance_date = get_datetime()
else:
return

#: Getting 200 days worth of historical data
#: If you wanted an intraday strategy based of minutely data you could change '1d' to '1m'
historical_data = history(200, '1d', 'price')

#: Getting the difference between the 50 day mean and the 200 day mean
past_50_day_mean = historical_data.tail(50).mean()
past_200_day_mean = historical_data.mean()
diff = past_50_day_mean / past_200_day_mean - 1

#: Cleaning up our diffs by removing any NaNs and sorting it in ascending order
diff = diff.dropna()
diff.sort()

#: Recording the stocks that we want to buy and the stocks that we want to sell
#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]

#: Create weights for our securities
short_length = min(context.stocks_to_short, len(sells))
short_weight = -0.5 / short_length if short_length != 0 else 0

#: Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

#: Define a 2% stoploss for each security
stops =  historical_data.iloc[-1] * 0.02

#: Iterate through each security in data
for sym in data:

#: If the security exists in our sells.index then sell
if sells is not None and sym in sells.index:
log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
stop_price=data[sym].price + stops[sym])

log.info('LONG: %s'%sym.symbol)
stop_price=data[sym].price - stops[sym])

#: If the security is in neither list, exit any positions we might have in that security
else:
order_target(sym, 0)

def record_leverage(context, data):
P = context.portfolio
market_value = sum(data[i].price * abs(P.positions[i].amount) for i in data)
record(leverage=market_value / max(P.portfolio_value, 1))


There was a runtime error.

David, many thanks for your reply and source version provided! If I correctly understood, but control of long/short ratio we are able to tune our risk/reward parametres?

That is the basic idea Mihail, you can vary the market exposure and leverage by adjusting the numerators in line 53/54. If you want to be more long, give the positive weight a larger absolute value than the negative weight. Keep in mind that rounding error does creep in, but you are correct about what is going on there.

I know you are defining rebalance days and date and you are checking it within handle_Data and reassigning but wasn't clear where you are rebalancing after 10 days in algo and how you are restricting it to trade once per day.I know you can use the rebalance by using a counter as well but wanted to understand your logic.thanks

Hi Amit,

I've created a new algorithm that uses schedule_function() which we cover in-depth in the follow up tutorial to this one: Quantopian Tutorial Series Lesson 2.

This should make the rebalance more clear where I have two methods: one to increment the number of days everyday and another that actually does the rebalancing. But here, the rebalancing algorithm checks at the beginning that

context.current_days_counted % 10 (context.rebalance_days) == 0


before rebalancing. Let me know if you have any questions on that.

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
from datetime import datetime, timedelta
import numpy as np

def initialize(context):

#: Settinng our universe to be between the top 99.5% and the top 100% of stocks by Dollar Volume
set_universe(universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))
#: Setting the number of stocks that we want to long and the number of stocks that we want to short
context.stocks_to_long = 5
context.stocks_to_short = 5

schedule_function(func=rebalance,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open())
schedule_function(func=day_counter,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_close())

context.rebalance_days = 10
context.current_days_counted = 0

def day_counter(context, data):
"""
Increments our day counter at the end of day, every day
"""
context.current_days_counted += 1

def rebalance(context, data):
"""
The logic for rebalancing our algorithm
"""
#: A quick check to see that we're only rebalancing every X days, defined by
#: context.rebalance_days
if context.current_days_counted % context.rebalance_days != 0:
return

#: Getting 200 days worth of historical data
#: If you wanted an intraday strategy based of minutely data you could change '1d' to '1m'
historical_data = history(200, '1d', 'price')

#: Getting the difference between the 50 day mean and the 200 day mean
past_50_day_mean = historical_data.tail(50).mean()
past_200_day_mean = historical_data.mean()
diff = past_50_day_mean / past_200_day_mean - 1

#: Cleaning up our diffs by removing any NaNs and sorting it in ascending order
diff = diff.dropna()
diff.sort()

#: Recording the stocks that we want to buy and the stocks that we want to sell
#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]

#: Create weights for our securities
short_length = min(context.stocks_to_short, len(sells))
short_weight = -1.0/short_length if short_length != 0 else 0

#: Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

#: Define a 2% stoploss for each security
stops =  historical_data.iloc[-1] * 0.02

#: Iterate through each security in data
for sym in data:

#: If the security exists in our sells.index then sell
if sells is not None and sym in sells.index:
log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
stop_price=data[sym].price + stops[sym])

log.info('LONG: %s'%sym.symbol)
stop_price=data[sym].price - stops[sym])

#: If the security is in neither list, exit any positions we might have in that security
else:
order_target(sym, 0)

def handle_data(context, data):
pass


There was a runtime error.

In the D.E. code two backtests upward, the return shows 3.7%.
However since it only utilized about 10% of initial capital, your algo is actually better than it appears to be.
In my opinion that worse representation is not good, others would say that is just fine: https://www.quantopian.com/posts/returns. Any thoughts about that? Feel free to post them there.

I bumped up your buy_weight from .5 to 1.38 to approach full use of initial capital and the output went from 3.7% to almost four times higher, 14.4%.
You can also nearly double that to 28% with these two lines:

def handle_data(context, data):
if get_open_orders():
return


I was able to see what was going on because of Run Summary, it is added to this backtest.
Also I see you calculated the leverage manually, recently Q added context.account.leverage for that to be easier.
Mikhail, I'm not aware of any way to prohibit leverage, it is being considered.
I would like to know what happens on IB if one tries to order more than cash available without a margin account, reject or partial fill. Best for this environment to match that in my opinion.

109
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
from datetime import datetime, timedelta
import numpy as np

def initialize(context):
# Setting universe between top 99.5% and 100% of stocks by Dollar Volume
set_universe(
universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))
# Setting the number of stocks to long and to short
context.stocks_to_long  = 5
context.stocks_to_short = 5
context.rebalance_date  = None
context.rebalance_days  = 10

schedule_function(summary, date_rules.every_day(), time_rules.market_close())

def handle_data(context, data):
if get_open_orders():
return

record_leverage(context, data)

if context.rebalance_date != None:
next_date = context.rebalance_date + timedelta(days=context.rebalance_days)

if context.rebalance_date == None or get_datetime() == next_date:
context.rebalance_date = get_datetime()
else:
return

# Getting 200 days worth of historical data
# For an intraday strategy based off minutely data, change '1d' to '1m'
historical_data = history(200, '1d', 'price')

# Difference between the 50-day mean and 200-day mean
past_50_day_mean  = historical_data.tail(50).mean()
past_200_day_mean = historical_data.mean()
diff = past_50_day_mean / past_200_day_mean - 1

# Cleaning up diffs by removing any NaNs and sorting in ascending order
diff = diff.dropna()
diff.sort()

# Recording the stocks to buy and to sell
# If the 50-day mean is greater than the 200-day mean, add it to the buy
# Vice versa for shorts
sells = diff[diff < 0]

# Weights for securities
short_length = min(context.stocks_to_short, len(sells))
short_weight = -0.5 / short_length if short_length != 0 else 0

# Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

# Define a 2% stoploss for each security
stops = historical_data.iloc[-1] * 0.02

# Iterate through each security in data
for sym in data:

# If the security exists in sells.index then sell
if sells is not None and sym in sells.index:
#log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
stop_price=data[sym].price + stops[sym])

#log.info('LONG: %s'%sym.symbol)
stop_price=data[sym].price - stops[sym])

# If the security is in neither list, exit any positions in that security
else:
order_target(sym, 0)

def record_leverage(context, data):
P = context.portfolio
market_value = sum(data[i].price * abs(P.positions[i].amount) for i in data)
record(leverage = market_value / max(P.portfolio_value, 1))
record(leverage2 = context.account.leverage)

def summary(context, data):
'''
Summary processing

https://www.quantopian.com/posts/run-summary
'''
#  - - - - - - - -  Options  - - - - - - - -
cancel_partials  = 1  # Cancel orders after being partially filled.
daily_live       = 1  # Log summary at end of each day when live.
drawdown_returns = 1  # Custom chart returns based on profit/drawdown.
#  At 1/2015 Q returns are profit/init_cash.
#  Custom chart only accepts 5 items so watch for that.
filter_zeros     = 0  # 0 or 1 to filter out those with no buy and no sell.
# In get_fundamentals for example there can be over
#   a thousand stocks processed with many not traded.
leverage_alert   = 1  # Log new lowest cash points reached.
percent_results  = 0  # Express results like 270.1% instead of x2.701

if 'books' not in context:
'''
Preparation. Initialize one time.
'''
cash = context.portfolio.starting_cash
context.books = {   # Starting cash value from GUI or live restart...
'cash_low'      : cash,
'shares'        : 0,       # Overall number of shares owned.
'count_sell'    : 0,       # Overall sell count.
'cnt_sel_evnts' : 0,
'summary_print' : 0,       # Use to force print when you like.
'commissions'   : 0,       # Commissions.
'sids_seen'     : [],      # For set_universe since dynamic.
'orders'        : {},      # Keep orders for accounting,
}                              #   orders not completely filled yet.
b = context.books

# Environment   First/last dates and
#   Arena: backtest or live.  Mode: daily or minute.
env = get_environment('*')
b['arena'] = env['arena']
b['mode']  = env['data_frequency']

if b['arena'] == 'live':
b['arena'] = 'paper'
elif b['arena'] != 'backtest': # ie like 'IB'
b['arena'] = 'live'

# Show environment at the beginning of the run
prep_prnt = ' {}\n  {}  {} to {}  {}  {}\n'.format(
b['arena'],
b['mode'],
'   \$' + '%.0f' % context.portfolio.starting_cash,
'  First bar stocks ({}) ...'.format(len(data)),
)

# Show current universe once
for sec in data:
if isinstance(sec, basestring):
continue   # Skip any injected fetcher string keys.
prep_prnt += (sec.symbol + ' ')
log.info(prep_prnt)

'''
Prepare individual securities dictionaries
with dynamic set_universe, fetcher, IPO's appearing etc.
'''
b = context.books   # For brevity.
for sec in data:
if isinstance(sec, basestring):
continue   # Skip any injected fetcher string keys.
sym = sec.symbol
if sym in b:
continue
if sec not in b['sids_seen']:
# Scenarios with price missing ...
price = data[sec].price if 'price' in data[sec] else 0
b['sids_seen'].append(sec)
b[sym] = {
'init_price'    : price,  # Save for summary.
'price'         : price,  # Most recent price.
'cash_low'      : 0,      # Lowest level of cash.
'balance'       : 0,      # For individual 'x' return.
'shares'        : 0,
'count_sell'    : 0,
'cnt_sel_evnts' : 0,
'return'        : 0,      # Return calculated.
'analog'        : 0,      # Analog relative return ratio.
}
cash_now = context.portfolio.cash
if cash_now < b['cash_low']:
b['cash_low'] = cash_now
# An alert for negative cash unless you like "leverage"
if leverage_alert and cash_now < 0.:
log.info('cash low ' + str(b['cash_low']))
'''
Custom chart of drawdown returns, profit/drawdown
'''
if drawdown_returns:
cash_now  = context.portfolio.cash
cash_strt = context.portfolio.starting_cash
portfolio = context.portfolio.portfolio_value
drawdown  = cash_strt - b['cash_low']
profit    = portfolio - cash_strt
dreturns  = 0 if not drawdown else profit / drawdown
qreturns  = profit / cash_strt    # Can be useful for easy visual compare,
record(QReturn = 100 * qreturns)  #   overlay, same as standard chart.
record(MaxSpentReturn = 100 * dreturns)
'''
Accounting. Update the numbers, manage orders if any.
'''
accounting = {}  # Local, any orders ready to be counted.

for security, oo_for_sid in get_open_orders().iteritems():
for order_obj in oo_for_sid:
# If an order not seen before, add for tracking
if order_obj.id not in b['orders']:
b['orders'][order_obj.id] = order_obj.filled

for id in b['orders']:  # Take a look at current orders saved.
o = get_order(id)   # Current order, might have been updated.

# If filled is not zero, account for it
if o.filled != 0:
accounting[id] = o    # Set to account for filled.

# On partial fills, a new order is automatically
#   generated for the remainder.
# Bugbug: The only way I could make sense of things so far ...
# If filled is not amount (shares), that's a partial fill,
#   cancelling remainder to simplify life. Unsure.
if o.filled != o.amount and cancel_partials:
cancel_order(id)

for id in accounting:    # Do any accounting, into books{}.
sec = accounting[id]['sid']
sym = sec.symbol
if sec in data and 'price' in data[sec]: # Update price if available.
b[sym]['price'] = data[sec].price
commission          = accounting[id]['commission']
filled              = accounting[id]['filled']  # Number filled, sell neg.
# ToDo: Don't know the official actual fill prices.
transaction         = filled * b[sym]['price']  # Last known price.
b[sym]['shares']   += filled      # The transaction on sell is negative
b[sym]['balance']  -= transaction #   so this line adds to balance then.
b[sym]['balance']  -= commission
b['commissions']   += commission

if filled > 0:                          # Buy
elif filled < 0:                        # Sell
b[sym]['cnt_sel_evnts'] += 1
b[sym]['count_sell']    += abs(filled)

del b['orders'][id]    # Remove from the list, accounting done.

# Keep track of lowest cash per symbol
if b[sym]['balance'] < b[sym]['cash_low']:
b[sym]['cash_low'] = b[sym]['balance']
'''
Show summary if last bar
'''
last_bar_now = 0
if not b['summary_print']:
if context.books['arena'] in ['paper', 'live'] and daily_live:
# When paper or live log summary every day end of day.
# Assumes schedule is set to every_day().
last_bar_now = 1
elif context.books['arena'] == 'backtest':
# Flag for summary output if last bar now
bar = get_datetime()
if b['mode'] == 'daily':
last_bar_now = 1
elif b['mode'] == 'minute':
# Not ideal.
# How to print in minute mode only on last bar simply?
log.info('Algo time: ' + str(bar.time()))
last_bar_now = 1
'''
Summary output to the logging window
'''
if last_bar_now or b['summary_print']:
# Independent copy of context.books using dict() in case summary print
#   is set to happen more than once in a run, due to concats below (+=)
b    = dict(context.books)
done = {}   # Protect against any listed twice.

# Some overall values by adding individual values
for sec in b['sids_seen']:
if sec in done:
continue

# There's a problem with a dynamic run where a security can have
#   dropped out of the picture, all sold, not in current universe,
#   and its price is no longer accessible. Need help from Q.
if sec in data and 'price' in data[sec]:
b[sec.symbol]['price'] = data[sec].price
sym = sec.symbol
b['count_sell']    += b[sym]['count_sell']
b['cnt_sel_evnts'] += b[sym]['cnt_sel_evnts']
b['shares']        += b[sym]['shares']
done[sec] = 1

portfolio    = context.portfolio.portfolio_value
init_cash    = context.portfolio.starting_cash
cash_now     = context.portfolio.cash
cash_low     = b['cash_low']
cash_profit  = cash_now - init_cash
shares_value = portfolio - cash_now
spent        = 0
if init_cash > cash_low:
spent    = init_cash - cash_low
else:
spent    = cash_now - init_cash  # ??  case of short-selling
spent_prcnt  = ' ({}%)'.format(int(100 * (spent / init_cash)))
tot_profit   = cash_profit + shares_value
qntp_return  = (portfolio - init_cash) / init_cash
draw_return  = 0.
draw_return = tot_profit / spent
draw_float   = draw_return
rel_word     = ' Prcnt' if percent_results else ' Ratio'
if percent_results:
if qntp_return < 10.:
qntp_return = '{}%'.format(float('%.2f' % (100 * qntp_return)))
draw_return = '{}%'.format(float('%.2f' % (100 * draw_return)))
draw_float  = float('%.3f' % (100 * draw_float))
elif qntp_return < 100.:
qntp_return = '{}%'.format(float('%.1f' % (100 * qntp_return)))
draw_return = '{}%'.format(float('%.1f' % (100 * draw_return)))
draw_float  = float('%.1f' % (100 * draw_float))
else:
qntp_return = '{}%'.format(int(100 * qntp_return))
draw_return = '{}%'.format(int(100 * draw_return))
draw_float  = int(100 * draw_float)
else:
if qntp_return < 10.:
qntp_return = 'x' + '%.2f' % qntp_return
draw_return = 'x' + '%.2f' % draw_return
elif qntp_return < 100.:
qntp_return = 'x' + '%.1f' % qntp_return
draw_return = 'x' + '%.1f' % draw_return
else:
qntp_return = 'x' + '%.0f' % qntp_return
draw_return = 'x' + '%.0f' % draw_return
if qntp_return == '0.00%' or qntp_return == 'x0.00':
qntp_return = '0'
if draw_return == '0.00%' or draw_return == 'x0.00':
draw_return = '0'

v1 = {  # values
'pflo': '%.0f' % portfolio,
'icsh': str(int(init_cash)),
'untd': '0' if int(cash_low) <= 0 else str(int(cash_low)),
'ncsh': '0' if int(cash_low) >= 0 else str(int(cash_low)),
'down': str(int(spent)),
'cshp': str(int(cash_profit)),
'totp': '%.0f' % tot_profit,
'qret': qntp_return,
'dret': draw_return,
}
v2 = {
'csel': str(b['count_sell']),
'shnw': str(b['shares']),
'shvl': '%.0f' % shares_value,
'cmsn': '%.0f' % b['commissions'],
'cshn': '%.0f' % cash_now,
}
# Widths of the longest for columns
w1 = 0; w2 = 0
for v in v1:
len_v_str = len(str(v1[v]))
if len_v_str > w1:
w1 = len_v_str
for v in v2:
len_v_str = len(str(v2[v]))
if len_v_str > w2:
w2 = len_v_str
for v in v1:  # Padding
v1[v] = v1[v].rjust(w1)
for v in v2:
v2[v] = v2[v].rjust(w2)
'''
Portfolio: 342690
Initial Cash:   1000                          Buys: 217225 (147 trades)     
Unused Cash:      0                         Sells: 77559 (11 trades)
Neg Cash:    -21                   Commissions: 1523                    
Drawdown:   1021 (102%)             Shares Now: 139666
Cash Profit:   -991                  Shares Value: 342681                  
Total Profit: 341690  w/ shares               Cash: 9
QReturn:   x342  Profit/InitCash                                       
Return:   x335  Profit/Drawdown
2015-01-02 summary:616 INFO      200 average initial cash, 5 securities      
Relativ Buy| By|Sl By|Sl Price Draw Cash Shrs Shrs
Symbol Ratio Hold Count Evnts Strt|Now Down Now Now Value                   
RDNT x32.0  2.2 16849|16849  20|2     3|9     -39502   39768       0       0
NVAX x9.71  2.0 23042|23009  18|3     2|6     -34756   10439      33     190
EDAP x21.2  0.5 143855|4419  34|1     2|2    -205336 -205336  139436  341618
ACHN x61.8  0.6 22133|22133  38|2     8|13    -49470   96132       0       0
HGSH x1548 10.8 11346|11149  37|3     0|4      -1019   58273     197     872

'''
pflo   = '{m1:>15} {m2}'.format(
m1 = 'Portfolio:',     m2 = v1['pflo'] )
icsh   = '{m1:>15} {m2:<34}{m3}{m4}'.format(
m1 = 'Initial Cash:',  m2 = v1['icsh'],
ucsh   = '{m1:>15} {m2:<33}{m3}{m4}'.format(
m1 = 'Unused Cash:',   m2 = v1['untd'],
m3 = 'Sells: ' + v2['csel'],
m4 = ' (' + str(b['cnt_sel_evnts']) + ' trades)' )
ncsh   = '{m1:>15} {m2:<27}{m3}'.format(
m1 = 'Neg Cash:',      m2 = v1['ncsh'],
m3 = 'Commissions: ' + v2['cmsn'] )
dcsh   = '{m1:>15} {m2:<28}{m3}'.format(
m1 = 'Drawdown:',      m2 = v1['down'] + spent_prcnt,
m3 = 'Shares Now: ' + v2['shnw'] )
pcsh   = '{m1:>15} {m2:<26}{m3}'.format(
m1 = 'Cash Profit:',   m2 = v1['cshp'],
m3 = 'Shares Value: ' + v2['shvl'] )
ttlp   = '{m1:>15} {m2:<34}{m3}'.format(
m1 = 'Total Profit:',  m2 = v1['totp'] + '   w/ shares',
m3 = 'Cash: ' + v2['cshn'] )
qret   = '{m1:>15} {m2}'.format(
m1 = 'QReturn:',
m2 = v1['qret'] + '   Profit/InitCash' )
dret   = '{m1:>15} {m2}'.format(            # drawdown return
m1 = 'Return:',
m2 = v1['dret'] + '   Profit/Drawdown')
outs        = [pflo, icsh, ucsh, ncsh, dcsh, pcsh, ttlp, qret, dret]
out_summary = '_\r\n'
line_len    = 80      # Length
for o in outs:
out_summary += (o + ' ' * (line_len - len(o)) + '\r\n')

# -------------------------------
# Individual securities detail
# -------------------------------
out_content_collections = []
count_sids    = len(b['sids_seen'])
avg_init_cash = init_cash / len(b['sids_seen'])
sec_word      = ' security' if count_sids == 1 else ' securities'
sec_strng     = '  ' + '%.0f' % int(avg_init_cash) \
+ ' average initial cash, ' + str(count_sids) + sec_word
out_content   = (sec_strng + '    \r\n').rjust(line_len - 26)
lines_out     = 11    # Log in clumps to stay under logging limits.
count_lines   = 0
if filter_zeros:
count_lines += 1
out_content += '.\r\n\tZero buy/sell filtered out \r\n\r\n.'
'Symbol',rel_word,'Hold','Count','Evnts','Strt|Now','Down',' Now',' Now','Value']
contents_list = [header1, header2]    # To be lines per sym as a list of lists.

# The list to process
sids_to_process = []
for sec in sorted(b['sids_seen']):
sym = sec.symbol
if filter_zeros and not b[sym]['count_buy'] and not b[sym]['count_sell']:
continue
sids_to_process.append(sec)

# Individual return
return_list = []
for sec in sids_to_process:
sym = sec.symbol
# There's a problem with balance, it is tracked based on
#   last known price, and when filled, no current way to obtain
#   the actual fill price.
# For that reason, there can be discrepancies, sometimes major.
# To Q, request made to provide us with fill_price in the object id.
cash_now   = b[sym]['balance']      # Balance started at zero
cash_low   = b[sym]['cash_low']     # Maximum expended
shares_val = b[sym]['shares'] * b[sym]['price']
outputs    = shares_val + cash_now
cash_pnl   = 0   # Cash profit and loss
if avg_init_cash > cash_low:             # Typical trading
cash_pnl = avg_init_cash - cash_low
else:
cash_pnl = cash_now - avg_init_cash  # ?? Case of short-selling, unsure
if (b[sym]['count_buy'] or b[sym]['count_sell']) and avg_init_cash:
b[sym]['return'] = outputs / cash_pnl
return_list.append(b[sym]['return'])

if not return_list:
if not avg_init_cash:
log.info('Odd, no avg_init_cash, aborting summary')
else:
schedule = '        ' + \
'schedule_function( \n\t\tsummary, date_rules.every_day()'
minute_note = ',\n    especially when in minute mode, '
if b['mode'] == 'daily':
schedule   += '\n        )'
minute_note = '.\n    '
else:
schedule += ', time_rules.market_close()\n        )'
log.info(
'.\n  No buys and no sells. If unexpected, check placement' + \
' of calls to summary,\n    after any orders and before any' + \
' returns and/or the scheduling for it' + \
minute_note + 'like summary(context, data) or\n' + \
schedule    + '\n\tAborting summary()'
)
return

# Multiplication factor
mult_factor  = 0
shift        = 0    # Up/dn to move value to line up with overall.
avg_of_list  = 0    # Taking avg as an analog of draw_float.
return_list  = sorted(return_list)
lowest       = return_list[0]
list_shifted = []
if lowest < 0:
# Shift upward to avoid zero-division,
#   in case some are negative, then each back down.
shift = 0 - lowest
for r in return_list:
list_shifted.append(r + shift)
avg_of_list = sum(list_shifted) / len(list_shifted)
mult_factor = draw_float / avg_of_list
else:
avg_of_list = sum(return_list) / len(return_list)
mult_factor = draw_float / avg_of_list

# Normalize x values proportionally compared to overall x value
for sec in sids_to_process:
sym    = sec.symbol
analog = 0.
value  = (b[sym]['return'] * mult_factor) - shift
if value == 0:   # like 0.00
analog = '0'
continue
if percent_results:
if value < 10.:
analog = '{}%'.format(float('%.2f' % value))
elif value < 100.:
analog = '{}%'.format(float('%.1f' % value))
else:
analog = '{}%'.format(int(value))
else:
if value < 10.:
analog = 'x' + '%.2f' % value
elif value < 100.:
analog = 'x' + '%.1f' % value
else:
analog = 'x' + '%.0f' % value

b[sym]['analog'] = analog

# Set values
for sec in sids_to_process:
sym = sec.symbol
init_price = b[sym]['init_price']
if init_price:
buy_hold = '%.1f' % ((b[sym]['price'] - init_price) / init_price)
content = [
sym,
' ' + str(b[sym]['analog']),
+ str(b[sym]['count_sell']),
+ str(b[sym]['cnt_sel_evnts']),
'%.0f' % init_price + '|' + '%.0f' % b[sym]['price'],
int(b[sym]['cash_low']),
int(b[sym]['balance']),
b[sym]['shares'],
int(b[sym]['shares'] * b[sym]['price'])
]
# Collect lines per sym as a list of lists
contents_list.append(content)

# Set widths
col_widths = {}
for i in range(len(contents_list[0])):
col_widths[i + 1] = 7       # Defaults
col_widths[1] = 6               # Symbol
for line_list in contents_list:
ec = 1  # element count
for element in line_list:
if len(str(element)) > col_widths[ec]:
col_widths[ec] = len(str(element)) # Set width to largest seen.
ec += 1

# Piece together the output lines formatted.
line_c = 0
for line in contents_list:
out_line = ''
line_c  += 1  # Line count
cc       = 1  # Column count
for column in line:
if cc in [4, 5, 6] or line_c in [1, 2]:
out_line += str(column).center(col_widths[cc] + 1)
else:
column = str(column) + ' ' if cc == 3 else column
out_line += str(column).rjust(col_widths[cc] + 1)
cc += 1

out_content += (out_line + ' ' * (line_len- len(out_line)) + '\r\n')
count_lines += 1

# Backticks at the end of line are for replace-all in an editor
#   later after copy/paste, since new lines are gone at least on Windows.
#   Unfortunate to not be able to copy and paste results easily.

# Decide when to tuck a group away for later and start a new group,
#   due to logging limits, using modulus (remainder).
if count_lines % lines_out == 0:
out_content_collections.append(out_content)
out_content = '_\r\n'       # Restart a group.

if count_lines % lines_out != 0:    # A few remaining lines.
out_content_collections.append(out_content)

# Log output
log.info(out_summary)   # The top, general overall output first

# Log stored groups
for occ in out_content_collections:
log.info(occ)

out_content = '(symbol ratios adjusted proportionally to overall)'.rjust(20)

# Add any other content you want ---------------------------
#out_content += '_\n' # Underscore to a new line for left alignment,
#   '\n' by itself would be ignored/dropped.
# Some variables or whatever you might want to add ...
out_content += ''

log.info(out_content)


There was a runtime error.

Dear all

Appreciate some advice. Whats does the following code do? Does it Sort and buy only stocks where its price is just above the slow moving average which in this case is above 200MA? Will it buy stock when current price is below its 200MA but 50MA is still above 200MA?

Or does it sort and pick strong stocks that has a very strong 50 MA compare to a stock with weak 50MA? Bec the 50 mean is much greater than the 200 day resulting in a bigger difference.

Many thks

#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]


#: Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

In these lines, you are deciding what stocks to buy and sell. If the difference is positive, it buys. If it's negative, the stock will be sold.

#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]


The difference is calculated on line 38 as the ratio between the 50-day moving average and the 200-day moving average
 diff = past_50_day_mean / past_200_day_mean - 1 

So the algorithm will buy if the short moving average (in this case 50 days) is greater than the longer moving average (200 days), suggesting the stock price is trending upward and you ride the wave.

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.

Thk you very much

I did a test on the code from 2011 to date and why is the nos of transaction so low? I tried increasing the nos of stock short and long to total 20 and increasing the universe to be between the top 90% and the top 100% of stocks by Dollar Volume

But there is only 8 transaction and i am puzzled?

Thank you

5
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
from datetime import datetime, timedelta
import numpy as np

def initialize(context):

#: Settinng our universe to be between the top 99.5% and the top 100% of stocks by Dollar Volume
set_universe(universe.DollarVolumeUniverse(floor_percentile=90, ceiling_percentile=100))
#: Setting the number of stocks that we want to long and the number of stocks that we want to short
context.stocks_to_long = 10
context.stocks_to_short = 10

context.rebalance_date = None
context.rebalance_days = 10

def handle_data(context, data):
record_leverage(context, data)

if context.rebalance_date != None:
next_date = context.rebalance_date + timedelta(days=context.rebalance_days)

if context.rebalance_date == None or get_datetime() == next_date:
context.rebalance_date = get_datetime()
else:
return

#: Getting 200 days worth of historical data
#: If you wanted an intraday strategy based of minutely data you could change '1d' to '1m'
historical_data = history(200, '1d', 'price')

#: Getting the difference between the 50 day mean and the 200 day mean
past_50_day_mean = historical_data.tail(50).mean()
past_200_day_mean = historical_data.mean()
diff = past_50_day_mean / past_200_day_mean - 1

#: Cleaning up our diffs by removing any NaNs and sorting it in ascending order
diff = diff.dropna()
diff.sort()

#: Recording the stocks that we want to buy and the stocks that we want to sell
#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]

#: Create weights for our securities
short_length = min(context.stocks_to_short, len(sells))
short_weight = -0.5 / short_length if short_length != 0 else 0

#: Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

#: Define a 2% stoploss for each security
stops =  historical_data.iloc[-1] * 0.02

#: Iterate through each security in data
for sym in data:

#: If the security exists in our sells.index then sell
if sells is not None and sym in sells.index:
log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
stop_price=data[sym].price + stops[sym])

log.info('LONG: %s'%sym.symbol)
stop_price=data[sym].price - stops[sym])

#: If the security is in neither list, exit any positions we might have in that security
else:
order_target(sym, 0)

def record_leverage(context, data):
P = context.portfolio
market_value = sum(data[i].price * abs(P.positions[i].amount) for i in data)
record(leverage=market_value / max(P.portfolio_value, 1))


There was a runtime error.

Maybe code in the example did work correctly previously but now I get transactions only for the first few weeks or so. to fix I changed  if context.rebalance_date == None or get_datetime()== next_date:  to
 if context.rebalance_date == None or get_datetime()>= next_date: 

Is it user error on my part, or does this algorithm only trade in the very beginning? The rebalance doesn't seem to be happening for me.

Alx,

Are you running backtests directly from the clone?

Bogdan Kasp solution is good, because the original algo is stopped running on the first next_day which was set on Saturday or Sunday (or stock exchange holiday).
From such date get_datetime() == next_date condition was false permanently because next_date was skipped and never set new date again.

Here's the newest backtest that uses schedule_function to clear up any rebalance date confusion

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
from datetime import datetime, timedelta
import numpy as np

def initialize(context):

#: Settinng our universe to be between the top 99.5% and the top 100% of stocks by Dollar Volume
set_universe(universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))
#: Setting the number of stocks that we want to long and the number of stocks that we want to short
context.stocks_to_long = 5
context.stocks_to_short = 5

schedule_function(func=rebalance,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open())
schedule_function(func=day_counter,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_close())

context.rebalance_days = 10
context.current_days_counted = 0

def day_counter(context, data):
"""
Increments our day counter at the end of day, every day
"""
context.current_days_counted += 1

def rebalance(context, data):
"""
The logic for rebalancing our algorithm
"""
if get_open_orders():
return
#: A quick check to see that we're only rebalancing every X days, defined by
#: context.rebalance_days
if context.current_days_counted % context.rebalance_days != 0:
return

#: Getting 200 days worth of historical data
#: If you wanted an intraday strategy based of minutely data you could change '1d' to '1m'
historical_data = history(200, '1d', 'price')

#: Getting the difference between the 50 day mean and the 200 day mean
past_50_day_mean = historical_data.tail(50).mean()
past_200_day_mean = historical_data.mean()
diff = past_50_day_mean / past_200_day_mean - 1

#: Cleaning up our diffs by removing any NaNs and sorting it in ascending order
diff = diff.dropna()
diff.sort()

#: Recording the stocks that we want to buy and the stocks that we want to sell
#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]

#: Create weights for our securities
short_length = min(context.stocks_to_short, len(sells))
short_weight = -1.0/short_length if short_length != 0 else 0

#: Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

#: Define a 2% stoploss for each security
stops =  historical_data.iloc[-1] * 0.02

#: Iterate through each security in data
for sym in data:

#: If the security exists in our sells.index then sell
if sells is not None and sym in sells.index:
log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
stop_price=data[sym].price - stops[sym])

log.info('LONG: %s'%sym.symbol)
stop_price=data[sym].price + stops[sym])

#: If the security is in neither list, exit any positions we might have in that security
else:
order_target(sym, 0)

def handle_data(context, data):
pass


There was a runtime error.

Here's the newest version that uses schedule_function to clear up any date confusion.

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
from datetime import datetime, timedelta
import numpy as np

def initialize(context):

#: Settinng our universe to be between the top 99.5% and the top 100% of stocks by Dollar Volume
set_universe(universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))
#: Setting the number of stocks that we want to long and the number of stocks that we want to short
context.stocks_to_long = 5
context.stocks_to_short = 5

schedule_function(func=rebalance,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open())
schedule_function(func=day_counter,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_close())

context.rebalance_days = 10
context.current_days_counted = 0

def day_counter(context, data):
"""
Increments our day counter at the end of day, every day
"""
context.current_days_counted += 1

def rebalance(context, data):
"""
The logic for rebalancing our algorithm
"""
if get_open_orders():
return
#: A quick check to see that we're only rebalancing every X days, defined by
#: context.rebalance_days
if context.current_days_counted % context.rebalance_days != 0:
return

#: Getting 200 days worth of historical data
#: If you wanted an intraday strategy based of minutely data you could change '1d' to '1m'
historical_data = history(200, '1d', 'price')

#: Getting the difference between the 50 day mean and the 200 day mean
past_50_day_mean = historical_data.tail(50).mean()
past_200_day_mean = historical_data.mean()
diff = past_50_day_mean / past_200_day_mean - 1

#: Cleaning up our diffs by removing any NaNs and sorting it in ascending order
diff = diff.dropna()
diff.sort()

#: Recording the stocks that we want to buy and the stocks that we want to sell
#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]

#: Create weights for our securities
short_length = min(context.stocks_to_short, len(sells))
short_weight = -1.0/short_length if short_length != 0 else 0

#: Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

#: Define a 2% stoploss for each security
stops =  historical_data.iloc[-1] * 0.02

#: Iterate through each security in data
for sym in data:

#: If the security exists in our sells.index then sell
if sells is not None and sym in sells.index:
log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
stop_price=data[sym].price - stops[sym])

log.info('LONG: %s'%sym.symbol)
stop_price=data[sym].price + stops[sym])

#: If the security is in neither list, exit any positions we might have in that security
else:
order_target(sym, 0)

def handle_data(context, data):
pass


There was a runtime error.

The latest version of this algorithm is bailing out on lines 33-34:

if get_open_orders():
return


There are orders that stay open and never close. This algorithm will execute a set of trades in the first tick and then early-out for the rest of the run.

Jack,

To prevent that from happening, you can cancel all orders at the end of the day like:


from datetime import datetime, timedelta
import numpy as np

def initialize(context):
#: Settinng our universe to be between the top 99.5% and the top 100% of stocks by Dollar Volume
set_universe(universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))
#: Setting the number of stocks that we want to long and the number of stocks that we want to short
context.stocks_to_long = 5
context.stocks_to_short = 5
schedule_function(func=rebalance,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open())
schedule_function(func=day_counter,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_close())
schedule_function(func=close_orders,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_close(minutes=15))
context.rebalance_days = 10
context.current_days_counted = 0
def close_orders(context, data):
open_orders = get_open_orders()
# open_orders is a dictionary keyed by sid, with values that are lists of orders.
if open_orders:
# iterate over the dictionary
for security, orders in open_orders.iteritems():
# iterate over the orders
for oo in orders:
cancel_order(oo)
log.info("Canceling order for %s" % security.symbol)

def day_counter(context, data):
"""
Increments our day counter at the end of day, every day
"""
context.current_days_counted += 1
def rebalance(context, data):
"""
The logic for rebalancing our algorithm
"""
if get_open_orders():
return
#: A quick check to see that we're only rebalancing every X days, defined by
#: context.rebalance_days
if context.current_days_counted % context.rebalance_days != 0:
return
#: Getting 200 days worth of historical data
#: If you wanted an intraday strategy based of minutely data you could change '1d' to '1m'
historical_data = history(200, '1d', 'price')

#: Getting the difference between the 50 day mean and the 200 day mean
past_50_day_mean = historical_data.tail(50).mean()
past_200_day_mean = historical_data.mean()
diff = past_50_day_mean / past_200_day_mean - 1

#: Cleaning up our diffs by removing any NaNs and sorting it in ascending order
diff = diff.dropna()
diff.sort()

#: Recording the stocks that we want to buy and the stocks that we want to sell
#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]

#: Create weights for our securities
short_length = min(context.stocks_to_short, len(sells))
short_weight = -1.0/short_length if short_length != 0 else 0

#: Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

#: Define a 2% stoploss for each security
stops =  historical_data.iloc[-1] * 0.02

#: Iterate through each security in data
for sym in data:

#: If the security exists in our sells.index then sell
if sells is not None and sym in sells.index:
log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
stop_price=data[sym].price - stops[sym])

log.info('LONG: %s'%sym.symbol)
stop_price=data[sym].price + stops[sym])

#: If the security is in neither list, exit any positions we might have in that security
else:
order_target(sym, 0)

def handle_data(context, data):
pass



Thanks Seong.

One rookie mistake I was making was running the algorithm in Daily mode. This prevented any orders from being filled.

I've pasted the above into an algorithm and ran it through the backtester to share with others.

Looked a little fishy, so I went back to the original algo: https://www.quantopian.com/posts/momentum-strategy-with-a-dynamic-universe

The original algo placed the order short order with "+ stops[sym]" and placed the long order with "- stops[sym]". I made this change along with changing the order type to limit orders:

        #: If the security exists in our sells.index then sell
if sells is not None and sym in sells.index:
log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
limit_price=data[sym].price + stops[sym])

log.info('LONG: %s'%sym.symbol)
limit_price=data[sym].price - stops[sym])


5
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
from datetime import datetime, timedelta
import numpy as np

def initialize(context):
#: Settinng our universe to be between the top 99.5% and the top 100% of stocks by Dollar Volume
set_universe(universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))
#: Setting the number of stocks that we want to long and the number of stocks that we want to short
context.stocks_to_long = 5
context.stocks_to_short = 5
schedule_function(func=rebalance,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_open())
schedule_function(func=day_counter,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_close())
schedule_function(func=close_orders,
date_rule=date_rules.every_day(),
time_rule=time_rules.market_close(minutes=15))
context.rebalance_days = 10
context.current_days_counted = 0

def close_orders(context, data):
open_orders = get_open_orders()
# open_orders is a dictionary keyed by sid, with values that are lists of orders.
if open_orders:
# iterate over the dictionary
for security, orders in open_orders.iteritems():
# iterate over the orders
for oo in orders:
cancel_order(oo)
log.info("Canceling order for %s" % security.symbol)

def day_counter(context, data):
"""
Increments our day counter at the end of day, every day
"""
context.current_days_counted += 1

def rebalance(context, data):
"""
The logic for rebalancing our algorithm
"""
if get_open_orders():
return
#: A quick check to see that we're only rebalancing every X days, defined by
#: context.rebalance_days
if context.current_days_counted % context.rebalance_days != 0:
return
#: Getting 200 days worth of historical data
#: If you wanted an intraday strategy based of minutely data you could change '1d' to '1m'
historical_data = history(200, '1d', 'price')

#: Getting the difference between the 50 day mean and the 200 day mean
past_50_day_mean = historical_data.tail(50).mean()
past_200_day_mean = historical_data.mean()
diff = past_50_day_mean / past_200_day_mean - 1

#: Cleaning up our diffs by removing any NaNs and sorting it in ascending order
diff = diff.dropna()
diff.sort()

#: Recording the stocks that we want to buy and the stocks that we want to sell
#: If the 50 mean is greater than the 200 day mean, add it to the buy
#: Vice versa for the shorts
sells = diff[diff < 0]

#: Create weights for our securities
short_length = min(context.stocks_to_short, len(sells))
short_weight = -1.0/short_length if short_length != 0 else 0

#: Select securities just above and below the slow moving average (the diff)
sells.sort(ascending=False)
sells = sells.iloc[:short_length] if short_weight != 0 else None

#: Define a 2% stoploss for each security
stops =  historical_data.iloc[-1] * 0.02

#: Iterate through each security in data
for sym in data:

#: If the security exists in our sells.index then sell
if sells is not None and sym in sells.index:
log.info('SHORT: %s'%sym.symbol)
order_target_percent(sym, short_weight,
stop_price=data[sym].price - stops[sym])

log.info('LONG: %s'%sym.symbol)
stop_price=data[sym].price + stops[sym])

#: If the security is in neither list, exit any positions we might have in that security
else:
order_target(sym, 0)

`