Coding
Quant Python

PnL From a Trade Blotter

Difficulty

Read the problem, hints and solution here. The editor needs a bigger screen: open this page on a laptop to write and run your code.

End of day, the desk hands you the blotter: every trade, in execution order. The risk system wants realized PnL per symbol under average-cost accounting, the convention where your open position carries one blended cost basis rather than per-lot tax-style matching.

Each trade is a dict {"symbol": str, "side": "B" or "S", "qty": int, "price": float}. Track, per symbol, a signed position and its average cost. Rules, precisely (process trades strictly in order; a buy has signed quantity +qty, a sell -qty):

  1. Opening or extending (position is flat, or the trade has the same sign as the position): no PnL is realized; the average cost updates by the weighted formula avg = (avg * abs(position) + price * abs(trade_qty)) / (abs(position) + abs(trade_qty)), then the position absorbs the signed quantity.
  2. Closing (trade sign opposite to the position): quantity closed = min(abs(trade_qty), abs(position)) realizes (price - avg) * closed, positive sign if closing a long, negative if closing a short (covering a short below your average cost is a profit). The average cost of the remaining position is unchanged.
  3. Flipping through flat: if the trade is larger than the position, the excess opens a position on the other side whose average cost is the trade price. (Selling short is allowed at any time: a sell from flat simply opens a negative position.)

Implement realized_pnl(trades) returning a sorted list of (symbol, pnl) tuples, one per symbol appearing in the blotter (include symbols whose realized PnL is zero), with each pnl rounded to 2 decimal places.

Pandas is preloaded and available if you want it; see the editorial for why a naive groupby cannot express this computation.

Examples

realized_pnl([{"symbol": "AAPL", "side": "B", "qty": 100, "price": 10.0}, {"symbol": "AAPL", "side": "B", "qty": 100, "price": 12.0}, {"symbol": "AAPL", "side": "S", "qty": 150, "price": 13.0}])
# [('AAPL', 300.0)]
# Average cost after both buys is 11.0; selling 150 realizes (13 - 11) * 150.

realized_pnl([{"symbol": "CL", "side": "S", "qty": 50, "price": 80.0}, {"symbol": "CL", "side": "B", "qty": 80, "price": 78.0}, {"symbol": "CL", "side": "S", "qty": 30, "price": 77.0}, {"symbol": "NG", "side": "B", "qty": 10, "price": 3.5}])
# [('CL', 70.0), ('NG', 0.0)]
# Covering 50 of the 80-lot buy realizes (80 - 78) * 50 = +100; the excess
# 30 flips CL long at 78.0, and selling it at 77.0 realizes -30.

Constraints

  • Up to \(10^5\) trades, up to a few hundred symbols; quantities are positive integers, prices positive floats.
  • Follow the prescribed average-cost formula literally so results are reproducible to the cent.
  • One ordered pass is O(n); anything that reorders trades within a symbol computes a different (wrong) PnL.
Rate this problem
Language: Pythonrealized_pnl
Sample tests

Run your code to check it against the sample tests. Results appear here.

Rate this problem
Next in Quant Dev 50Rolling-Window Signal