Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Per ticker PnL attribution

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

3 responses

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.

Thank you, for my purposes I actually don't need to worry about partial fills so far.