Hi community,
I'm a relatively inexperienced user. I'm working on a long/short strategy that trades ~100 names per day. How can I conveniently assess the profit/loss per particular ticker name after running my backtest?
Thanks,
Hans
Hi community,
I'm a relatively inexperienced user. I'm working on a long/short strategy that trades ~100 names per day. How can I conveniently assess the profit/loss per particular ticker name after running my backtest?
Thanks,
Hans
Adapted from some code that I use and untested in this form shown here.
Try scheduling pnl_calc and enable the log line as a start.
def initialize(context):
c = context
c.cb = {} # last known cost_basis for pnl calc
c.pnl = {} # last known pnl
c.pnl_limit = 0 # Skip logging all except those with abs(pnl) beyond this. 0 to deactivate.
def pnl_calc(context, data): # rough last known pnl for test purposes
c = context
pos = c.portfolio.positions
for s in c.output_from_pipeline.index: # all stocks
pnl = 0
if s in pos and pos[s].amount: # if currently held, update, store values
pnl = pos[s].amount * (data.current(s, 'price') - pos[s].cost_basis)
c.cb[s] = pos[s].cost_basis # last known cost basis
elif s in c.cb:
pnl = pos[s].amount * (data.current(s, 'price') - c.cb[s])
elif s in c.pnl: # last resort
pnl = c.pnl[s] # using previously stored pnl, might have just sold.
if pnl:
c.pnl[s] = pnl # store last known pnl
#log.info('{} {} pnl_calc'.format(s.symbol.rjust(6), ('%.1f' % pnl).rjust(5)))
return pnl
With your 100 stocks, that will overwhelm the logging window with the log line but give you the general idea and store the values.
You could try something along this line more often or based on a condition for specific stocks.
def pnl_calc_stock(c, data, s): # rough last known pnl for test purposes
pos = c.portfolio.positions
pnl = 0
if s in pos:
pnl = pos[s].amount * (data.current(s, 'price') - pos[s].cost_basis)
c.cb[s] = pos[s].cost_basis
elif s in c.cb:
if s in pos and pos[s].amount:
pnl = pos[s].amount * (data.current(s, 'price') - c.cb[s])
elif s in c.pnl:
pnl = c.pnl[s] # Using previously stored pnl, might have just sold.
if pnl:
c.pnl[s] = pnl
#log.info('{} {} pnl_calc'.format(s.symbol.rjust(6), ('%.1f' % pnl).rjust(5)))
return pnl
def <in some other function>:
...
for s in <some list of stocks>:
pnl = pnl_calc_stock(c, data, s)
if c.pnl_limit:
if abs(pnl) > c.pnl_limit: # only show highs and lows
log.info('{}{}'.format('+' if pnl >= 0 else '-', '%.1f' % abs(pnl)))
Would be better for limit to be a percentage of max pnl.
On further review, since the whole pnl thing is tricky due to partial fills as both amount and cost_basis change, and depends on how the algorithm is built for most efficient use, there's no easy answer but hopefully that'll help save some time in figuring a best route. Could schedule pnl_calc() 1 minute before (catching those that will be sold) and after trade() (catching new positions) but again, what about the partial fills. Ideally one day we'll have a context variable with metrics on every stock ever traded in the backtest that Quantopian might want to create, with the all-time pnl for each one, however I think that may not be a straightforward or easy problem to solve from their side either.