Last updated 2018-05-29 Faster when using 'symbols' for filtering
Updates
- Faster.
- Option to filter for a list of symbols.
- Handles Stop and Limit orders.
- Specify start and/or optional stop dates to hone in on a particular area while not overwhelming the logging window.
- Precedes each line with the minute of the day (more readable than the log line timestamp).
- Option to add order id's on each line.
- Option to add cash on each line.
About how to run this: Be sure its schedule_function is last, after any others.
def initialize(context, data):
[your things]
Drop all of this at the end of initialize() and run it. Then you can move track_orders() if you want to.
for i in range(1, 391):
schedule_function(track_orders, date_rules.every_day(), time_rules.market_open(minutes=i))
def track_orders(context, data):
''' Show orders when made and filled.
Info: https://www.quantopian.com/posts/track-orders
'''
c = context
try: c.trac
except:
c.t_opts = { # __________ O P T I O N S __________
'symbols' : [], # List of symbols to filter for, like ['TSLA', 'SPY']
'log_neg_cash': 1, # Show cash only when negative.
'log_cash' : 1, # Show cash values in logging window or not.
'log_ids' : 1, # Include order id's in logging window or not.
'log_unfilled': 1, # When orders are unfilled. (stop & limit excluded).
'log_cancels' : 0, # When orders are canceled.
} # Move these to initialize() for better efficiency.
c.t_dates = { # To not overwhelm the log window, start/stop dates can be entered.
'active': 0,
'start' : [], # Start dates, option like ['2007-05-07', '2010-04-26']
'stop' : [] # Stop dates, option like ['2008-02-13', '2010-11-15']
}
c.trac = {}
log.info('track_orders active. Headers ...')
log.info(' Shares Shares')
log.info('Min Action Order Sym Now at Price PnL Stop or Limit Cash Id')
from pytz import timezone as _tz # Python only does once, makes this portable.
# Move to top of algo for better efficiency.
# If 'start' or 'stop' lists have something in them, triggers ...
if c.t_dates['start'] or c.t_dates['stop']:
_date = str(get_datetime().date())
if _date in c.t_dates['start']: # See if there's a match to start
c.t_dates['active'] = 1
elif _date in c.t_dates['stop']: # ... or to stop
c.t_dates['active'] = 0
else: c.t_dates['active'] = 1 # Set to active b/c no conditions.
if c.t_dates['active'] == 0: return # Skip if not active.
def _minute(): # To preface each line with the minute of the day.
bar_dt = get_datetime().astimezone(_tz('US/Eastern'))
return (bar_dt.hour * 60) + bar_dt.minute - 570 # (-570 = 9:31a)
def _trac(to_log): # So all logging comes from the same line number,
log.info(' {} {}'.format(str(_minute()).rjust(3), to_log)) # for vertical alignment in the logging window.
for oid in c.trac.copy(): # Existing known orders
o = get_order(oid)
if c.t_opts['symbols'] and (o.sid.symbol not in c.t_opts['symbols']): continue
if o.dt == o.created: continue # No chance of fill yet.
cash = ''
prc = data.current(o.sid, 'price') if data.can_trade(o.sid) else c.portfolio.positions[o.sid].last_sale_price
if (c.t_opts['log_neg_cash'] and c.portfolio.cash < 0) or c.t_opts['log_cash']:
cash = str(int(c.portfolio.cash))
if o.status == 2: # Canceled
do = 'Buy' if o.amount > 0 else 'Sell' ; style = ''
if o.stop:
style = ' stop {}'.format(o.stop)
if o.limit: style = ' stop {} limit {}'.format(o.stop, o.limit)
elif o.limit: style = ' limit {}'.format(o.limit)
if c.t_opts['log_cancels']:
_trac(' Canceled {} {} {}{} at {} {} {}'.format(do, o.amount,
o.sid.symbol, style, prc, cash, o.id[-4:] if c.t_opts['log_ids'] else ''))
del c.trac[o.id]
elif o.filled: # Filled at least some.
filled = '{}'.format(o.amount)
filled_amt = 0
#if o.status == 1: # Nope, is either partial or complete
if o.filled == o.amount: # Complete
if 0 < c.trac[o.id] < o.amount:
filled = 'all {}/{}'.format(o.filled - c.trac[o.id], o.amount)
filled_amt = o.filled
else: # c.trac[o.id] value is previously filled total
filled_amt = o.filled - c.trac[o.id] # filled this time, can be 0
c.trac[o.id] = o.filled # save fill value for increments math
filled = '{}/{}'.format(filled_amt, o.amount)
if filled_amt:
now = ' ({})'.format(c.portfolio.positions[o.sid].amount) if c.portfolio.positions[o.sid].amount else ' _'
pnl = '' # for the trade only
amt = c.portfolio.positions[o.sid].amount ; style = ''
if (amt - o.filled) * o.filled < 0: # Profit-taking scenario including short-buyback
cb = c.portfolio.positions[o.sid].cost_basis
if cb:
pnl = -filled_amt * (prc - cb)
sign = '+' if pnl > 0 else '-'
pnl = ' ({}{})'.format(sign, '%.0f' % abs(pnl))
if o.stop:
style = ' stop {}'.format(o.stop)
if o.limit: style = ' stop () limit {}'.format(o.stop, o.limit)
elif o.limit: style = ' limit {}'.format(o.limit)
if o.filled == o.amount: del c.trac[o.id]
_trac(' {} {} {}{} at {}{}{}'.format(
'Bot' if o.amount > 0 else 'Sold', filled, o.sid.symbol, now,
'%.2f' % prc, pnl, style).ljust(52) + ' {} {}'.format(cash, o.id[-4:] if c.t_opts['log_ids'] else ''))
elif c.t_opts['log_unfilled'] and not (o.stop or o.limit):
_trac(' {} {}{} unfilled {}'.format(o.sid.symbol, o.amount,
' limit' if o.limit else '', o.id[-4:] if c.t_opts['log_ids'] else ''))
oo = get_open_orders().values()
if not oo: return # Handle new orders
cash = ''
if (c.t_opts['log_neg_cash'] and c.portfolio.cash < 0) or c.t_opts['log_cash']:
cash = str(int(c.portfolio.cash))
for oo_list in oo:
for o in oo_list:
if c.t_opts['symbols'] and (o.sid.symbol not in c.t_opts['symbols']): continue
if o.id in c.trac: continue # Only new orders beyond this point
prc = data.current(o.sid, 'price') if data.can_trade(o.sid) else c.portfolio.positions[o.sid].last_sale_price
c.trac[o.id] = 0 ; style = ''
now = ' ({})'.format(c.portfolio.positions[o.sid].amount) if c.portfolio.positions[o.sid].amount else ' _'
if o.stop:
style = ' stop {}'.format(o.stop)
if o.limit: style = ' stop {} limit {}'.format(o.stop, o.limit)
elif o.limit: style = ' limit {}'.format(o.limit)
_trac('{} {} {}{} at {}{}'.format('Buy' if o.amount > 0 else 'Sell',
o.amount, o.sid.symbol, now, '%.2f' % prc, style).ljust(52) + ' {} {}'.format(cash, o.id[-4:] if c.t_opts['log_ids'] else ''))