Explanation of why results are so good

I am not sure how I have gotten a nearly 1000% yield in a year so I think my code is broken. Help please

16
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import datetime
import pytz
import pandas as pd

def initialize(context):

context.security = symbol('SPYG')

set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

def handle_data(context, data):

average_price = data[context.security].mavg(5)
current_price = data[context.security].price

cash = context.portfolio.cash

# Here is the meat of our algorithm.
# If the current price is 5% below the 5-day average price
# AND we have enough cash, then we will order.
# If the current price is below the average price,
# then we want to close our position to 0 shares.
if current_price > 0.95*average_price and cash > current_price:

# Need to calculate how many shares we can buy
number_of_shares = int(cash/current_price)

order(context.security, +number_of_shares)

elif current_price > average_price:

order(context.security, 0)
log.info("Selling %s" % (context.security.symbol))

# You can use the record() method to track any custom signal.
# The record graph tracks up to five different variables.
# Here we record the Apple stock price.
record(stock_price=data[context.security].price)
There was a runtime error.
5 responses

It looks like the algo keeps on buying shares even when you don't want it to. In this backtest I plotted the leverage used and it gets unrealistically high. You can prevent this using several different methods. I'll post a backtest that gives an example

3
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import datetime
import pytz
import pandas as pd

def initialize(context):

context.security = symbol('SPYG')

set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

def handle_data(context, data):

average_price = data[context.security].mavg(5)
current_price = data[context.security].price

cash = context.portfolio.cash

# Here is the meat of our algorithm.
# If the current price is 5% below the 5-day average price
# AND we have enough cash, then we will order.
# If the current price is below the average price,
# then we want to close our position to 0 shares.
if current_price > 0.95*average_price and cash > current_price:

# Need to calculate how many shares we can buy
number_of_shares = int(cash/current_price)

order(context.security, +number_of_shares)

elif current_price > average_price:

order(context.security, 0)
log.info("Selling %s" % (context.security.symbol))

# You can use the record() method to track any custom signal.
# The record graph tracks up to five different variables.
# Here we record the Apple stock price.
record(leverage=context.account.leverage)
There was a runtime error.

In this version, your trading logic is only executed once a day. This keeps the leverage at 1.

1
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import datetime
import pytz
import pandas as pd

def initialize(context):

context.security = symbol('SPYG')

set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

def handle_data(context, data):
exchange_time = get_datetime().astimezone(pytz.timezone('US/Eastern'))
average_price = data[context.security].mavg(5)
current_price = data[context.security].price

cash = context.portfolio.cash

# Here is the meat of our algorithm.
# If the current price is 5% below the 5-day average price
# AND we have enough cash, then we will order.
# If the current price is below the average price,
# then we want to close our position to 0 shares.
if exchange_time.hour == 15 and exchange_time.minute == 55:
if current_price > 0.95*average_price and cash > current_price:

# Need to calculate how many shares we can buy
number_of_shares = int(cash/current_price)

order(context.security, +number_of_shares)

elif current_price > average_price:

order(context.security, 0)
log.info("Selling %s" % (context.security.symbol))

# You can use the record() method to track any custom signal.
# The record graph tracks up to five different variables.
# Here we record the Apple stock price.
record(leverage=context.account.leverage)
There was a runtime error.

It's my first time using Quantopian, so I'm not really sure about this, but it looks like your cash is negative. Maybe, transactions takes some minutes to be done, so you keep buying shares, and when all the orders get executed your cash becomes negative. And because the performance is based on your initial capital, you get such a good performance.
When running this daily, your cash get updated before you can place a new order, and you get more reasonable results.

3
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import datetime
import pytz
import pandas as pd

def initialize(context):

context.security = symbol('SPYG')

set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

def handle_data(context, data):

average_price = data[context.security].mavg(1)
current_price = data[context.security].price

cash = context.portfolio.cash

# Here is the meat of our algorithm.
# If the current price is 5% below the 5-day average price
# AND we have enough cash, then we will order.
# If the current price is below the average price,
# then we want to close our position to 0 shares.
if current_price > 0.95*average_price and cash > current_price:

# Need to calculate how many shares we can buy
number_of_shares = int(cash/current_price)

order(context.security, +number_of_shares)

elif current_price > average_price:

order(context.security, 0)
log.info("Selling %s" % (context.security.symbol))

# You can use the record() method to track any custom signal.
# The record graph tracks up to five different variables.
# Here we record the Apple stock price.
record(stock_price=data[context.security].price)
There was a runtime error.

Here are the results by using a auxiliary cash variable.
But the regular cash variable still have a weird step behaviour I don't understand

3
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import datetime
import pytz
import pandas as pd

def initialize(context):

context.security = symbol('SPYG')

set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

context.real_cash = context.portfolio.cash

def handle_data(context, data):

average_price = data[context.security].mavg(1)
current_price = data[context.security].price

cash = context.portfolio.cash
record('cash',cash)

# Here is the meat of our algorithm.
# If the current price is 5% below the 5-day average price
# AND we have enough cash, then we will order.
# If the current price is below the average price,
# then we want to close our position to 0 shares.real_cash
if current_price > 0.95*average_price and cash > current_price:

# Need to calculate how many shares we can buy
number_of_shares = int(context.real_cash/current_price)

context.real_cash -= number_of_shares * current_price
if number_of_shares > 0:
order(context.security, number_of_shares)

elif current_price > average_price:

order(context.security, 0)
log.info("Selling %s" % (context.security.symbol))


There was a runtime error.

One problem is on the level of a typo, order(context.security, 0) where you intended to sell, it would buy or sell 0, oops, use order_target(context.security, 0) or order_target_percent(context.security, 0).
And then it looks like SPYG is thinly traded, lots of partial fills (both buy and sell).

2
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import datetime
import pytz
import pandas as pd

def initialize(context):
context.security = symbol('SPYG')

set_slippage(slippage.VolumeShareSlippage(volume_limit=0.25, price_impact=0.1))

# This triggers the summary at the end even if no trade last bar (skip of handle_data)
schedule_function(func = summary, date_rule = date_rules.every_day())

def handle_data(context, data):
#if get_open_orders():
#    track_orders(context, data)
#    summary(context, data) # accounting and last bar summary
#    return

average_price = data[context.security].mavg(5)
current_price = data[context.security].price

cash = context.portfolio.cash

# Here is the meat of the algorithm.
# If the current price is 5% below the 5-day average price
#   AND enough cash, then buy.
# If the current price is below the average price,
#   then close position to 0 shares.
if current_price > 0.95 * average_price and cash > current_price:
# Need to calculate how many shares can buy
number_of_shares = int(cash / current_price)

order(context.security, +number_of_shares)

elif current_price > average_price * 1.02:
# Place sell order (positive means buy, negative means sell)
shares = context.portfolio.positions[context.security].amount
if shares > 0:
#log.info("Selling {} {}".format(shares, context.security.symbol))
order_target(context.security, 0)

# record() to track custom signals, up to five different variables.
# Here, the stock price and cash.
#record(stock_price=data[context.security].price)
# ** = kw or keyword args of dictionary, key: value, like 'SPYG price' = 85.36
record(**{context.security.symbol + ' price': current_price})
record(cash_over_100k = context.portfolio.cash / 100000) # bringing cash near price

track_orders(context, data)
summary(context, data) # accounting and last bar summary

def track_orders(context, data):  # Log orders created or filled.
try:
context.orders
except:
context.orders = {}

to_delete = []
for id in context.orders:
o   = get_order(id)
sec = o.sid
sym = sec.symbol
if o.filled:        # Filled at least some
trade = 'Bot' if o.amount > 0 else 'Sold'
log.info('    {} {} {} at {}\n'.format(
to_delete.append(o.id)

for sec, oo_for_sid in get_open_orders().iteritems(): # Open orders
sym = sec.symbol
for o in oo_for_sid: # Orders per security
if o.id in to_delete:
continue
context.orders[o.id] = 1
if o.status == 2:   # Cancelled
log.info('    Cancelled {} {} order\n'.format(
else:               # New
if o.limit:        # Limit order
log.info('  {} {} {} now {} limit {}\n'.format(
elif o.stop:       # Stop order
log.info('  {} {} {} now {} stop {}\n'.format(
else:              # Market order
log.info('  {} {} {} at {}\n'.format(
for d in to_delete:
del context.orders[d]

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

https://www.quantopian.com/posts/run-summary
'''
# Need a couple of imports, you might need to comment these out if already imported.
#   That's pretty much the only change that might be necessary.
from pytz import timezone
import re

# Yes try/except is narly yet makes to work with set_universe etc.
# Is there a better way?  An -| if 'books' in context: |- didn't work.
try:
context['books']    # See if this key exists yet.
b = context.books   # For brevity.
except:
'''
Preparation. Initialize one time.
'''
cash = context.portfolio.starting_cash
context.books = {   # Starting cash value from GUI or live restart...
'cash'          : cash,
'init_cash'     : cash,
'cash_low'      : cash,
'shares'        : 0,
'shares_value'  : 0,
'count_sell'    : 0,       # Overall sell count.
'cnt_sel_evnts' : 0,
'summary_print' : 0,
'costs_total'   : 0,       # Commissions.
'sids_seen'     : [],      # For set_universe since dynamic.
'prep_prnt'     : '',
'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
b['prep_prnt'] = ' {}\n  {}  {} to {}  {}  {}\n'.format(
b['arena'],
b['mode'],
'   $' + "%.0f" % b['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. b['prep_prnt'] += (sec.symbol + ' ') log.info(b['prep_prnt']) ''' Prepare individual securities dictionaries with dynamic set_universe, fetcher, IPO's appearing etc. ''' 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_buy' : 0, # Individual buy number of shares. 'count_sell' : 0, 'cnt_buy_evnts' : 0, # Individual buy events count. 'cnt_sel_evnts' : 0, } ''' Accounting. Update the numbers, manage orders if any. ''' accounting = {} # Locally, any orders ready to be counted. # Read open orders for security, oo_for_sid in get_open_orders().iteritems(): sym = security.symbol 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 # Take a look at current orders for id in b['orders']: o = get_order(id) # Current order, might be updated. # If filled is not zero, account for it if o.filled != 0: accounting[id] = o # Set to account for filled. # 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. # ToDo: Not sure of official actual fill prices. if o.filled != o.amount: cancel_order(id) # You might want to change/remove this. # Do any accounting, into books{} for id in accounting: sec = accounting[id]['sid'] sym = sec.symbol commission = accounting[id]['commission'] filled = accounting[id]['filled'] # Number filled, sell neg. if sec in data and 'price' in data[sec]: # Update if available. b[sym]['price'] = data[sec].price lkp = b[sym]['price'] # Last known price. transaction = filled * lkp 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['costs_total'] += commission if filled > 0: # Buy b[sym]['cnt_buy_evnts'] += 1 b[sym]['count_buy'] += filled elif filled < 0: # Sell b[sym]['cnt_sel_evnts'] += 1 b[sym]['count_sell'] += abs(filled) # Remove from the list, accounting done del b['orders'][id] # Keep track of lowest cash per symbol if b[sym]['balance'] < b[sym]['cash_low']: b[sym]['cash_low'] = b[sym]['balance'] # And overall 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 b['cash_low'] < 0: # Lowest cash points reached ... log.info(str(sym).ljust(5) \ + ' order for ' + (('$' + "%.0f" % transaction) \
+ ',').ljust(8) + ' cash low: ' + str(int(b['cash_low']))
)
'''
Show summary if this is the last bar
'''
last_bar_now = 0

if not b['summary_print']:
if context.books['arena'] == 'live':
# When paper/live print summary every day end of 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':
last_bar_now = 1

if last_bar_now or b['summary_print']:
'''
Summary output to the logging window
'''
# 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, and its price
#   is no longer accessible. Bad. Need help from Q.
if sec in data and 'price' in data[sec]:
b[sec.symbol]['price'] = data[sec].price

sym    = sec.symbol
shares = b[sym]['shares']
b['count_sell']    += b[sym]['count_sell']
b['cnt_sel_evnts'] += b[sym]['cnt_sel_evnts']
b['shares']        += shares
b['shares_value']  += (shares * b[sym]['price'])
done[sec] = 1

q__portfolio  = str(int(context.portfolio.portfolio_value))
cash_end      = context.portfolio.cash
init_cash     = b['init_cash']
avg_init_cash = init_cash / len(b['sids_seen'])
cash_low      = b['cash_low']
my_portfolio  = cash_end + b['shares_value']
cash_profit   = cash_end - b['init_cash']
xval          = 'x0'
max_spent     = init_cash - cash_low
drawdown      = max(init_cash, init_cash - cash_low)
cnt_s_evts    = ('  (' + str(b['cnt_sel_evnts']) + ' trades)').rjust(17)
untouchd      = '' if int(cash_low) <= 0 else \
'  (' + str(int(cash_low)) + ' unused)'
neg_cash      = '' if int(cash_low) >= 0 else '                       ' \
+ "%.0f" % cash_low + ' max negative cash'
if drawdown  != 0:               # Pure profit over input used.
xval      = 'x'  + "%.3f" % ((my_portfolio - init_cash) / drawdown)

w1 = 16; w2 = 8  # Widths of columns
outs = [
'  QPortfolio: '.rjust(w1)+('$'+str(q__portfolio)) .rjust(w2), ' Buy Count: '.rjust(w1)+str(b['count_buy']) .rjust(w2)+cnt_b_evts, ' Sell Count: '.rjust(w1)+str(b['count_sell']) .rjust(w2)+cnt_s_evts, ' Shares Now: '.rjust(w1) + str(b['shares']) .rjust(w2), 'Shares Value: '.rjust(w1) + str(int(b['shares_value'])).rjust(w2), ' Cash Now: '.rjust(w1) + str(int(cash_end)) .rjust(w2), ' Cash Profit: '.rjust(w1) + str(int(cash_profit)) .rjust(w2), ' Commissions: '.rjust(w1) + str(int(b['costs_total'])) .rjust(w2), ' Max Spent: '.rjust(w1) + str(int(max_spent)) .rjust(w2)+neg_cash, 'Initial Cash: '.rjust(w1) + str(int(init_cash)) .rjust(w2)+untouchd, ' Portfolio: '.rjust(w1)+('$'+str(int(my_portfolio))) .rjust(w2),
]
out  = '_\r\n'
for o in outs:
out += (o + '\r\n')
out += '        Return:  ' + xval + '   Profit/Drawdown\r\n'

# -------------------------------
# Individual securities detail
# -------------------------------
out_content_collections = []
count_sids  = len(b['sids_seen'])
sec_word    = ' security' if count_sids == 1 else ' securities'
out_content = '_      ' + "%.0f" % int(b['init_cash'] / count_sids) \
+ ' average initial cash, ' + str(count_sids) + sec_word + '\r\n'
lines_out   = 11    # Log in clumps to stay under logging limits.
count_lines = 0
col_widths  = {1: 8, 2: 7, 3: 7, 4: 12, 5: 8, 6: 8, 7: 9, 8: 9, 9: 8, 10: 9}
]
'Symbol','Ratio','Hold','Count','Evnts','Strt|Now','Spent','Now','Now', 'Value'
]

cc = 1  # Column count
out_content += h.center(col_widths[cc])
cc += 1
out_content += '~\r\n' # Tilde at the end of line 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.

count_lines += 1
cc = 1
out_content += h.center(col_widths[cc])
cc += 1
out_content += '~\r\n'
count_lines += 1

for sym in sorted(s.symbol for s in b['sids_seen']):
balance      = b[sym]['balance']
init_price   = b[sym]['init_price']
shares       = b[sym]['shares']
shares_value = shares * b[sym]['price']
xval         = 'x0'
max_spent    = abs(b[sym]['cash_low'])
drawdown     = min( avg_init_cash, abs(b[sym]['cash_low']) )
if drawdown != 0:
portf = balance + shares_value
xval  = 'x' + "%.1f" % ((portf - drawdown) / drawdown)
if xval == 'x-0.0' or xval == 'x0.0':  # Mainly clearing -0.0
xval = 'x0'    # -0.0 would have been something like -0.02
if init_price:
buy_hold = "%.1f" % ((b[sym]['price'] - init_price) / init_price)
content = [
sym,
xval,
+ str(b[sym]['count_sell']),
+ str(b[sym]['cnt_sel_evnts']),
"%.0f" % init_price + '|' + "%.0f" % b[sym]['price'],
"%.0f" % max_spent,
"%.0f" % balance,
shares,
int(shares_value)
]
cc = 1
for c in content:
out_content += str(c).center(col_widths[cc])
cc += 1
out_content += '~\r\n'
count_lines += 1

# Decide when to tuck a group away for later and
#    start a new group, 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.info(out)        # The top, general overall output first.

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

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