Skip to content
All library documents

Incremental Average-Cost Position Accounting from Trade Fills

Article Quant Q&A · Author: Andy Flury

Summary

The document explains how a trading system can update position quantity, cost basis, average price, and realized and unrealized profit and loss incrementally as fills arrive. Under average-cost accounting, the update uses the prior position and cost, plus the latest signed trade quantity and execution price. A fill that adds to the position increases cost at its execution price; a fill that closes exposure removes cost at the prior average basis and realizes the difference from the execution price. Unrealized P&L is then derived from current market value and remaining cost.

The answers include implementations and worked sequences to illustrate how the accounting changes through position reductions, reversals, and closes. They also contrast average-cost accounting with FIFO and LIFO: those methods require retaining individual open lots to determine which basis is closed. A simpler cash-and-position ledger computes total P&L but does not split realized from unrealized P&L or provide average price. The examples show accounting conventions, not market performance; implementations should be checked for sign conventions, valuation choices, and zero-position edge cases.

Key ideas

  • Average-cost accounting can update position cost and realized P&L using the previous state and the latest fill.
  • Closing trades realize P&L against the existing average cost basis.
  • Unrealized P&L is derived from current market value and the remaining position cost.
  • FIFO and LIFO require tracking open lots to determine which cost basis is closed.
  • A cash-and-position ledger gives total P&L but does not itself separate realized and unrealized amounts.

Tags

Full text
# Calculate Average Price, Cost, (Un)Realized P&L of a position based on executed trades


# Calculate Average Price, Cost, (Un)Realized P&L of a position based on executed trades












We have built an algorithmic trading software and need to calculate the following parameters for each position in our portfolio.

- Average Price

- Cost

- Realized Profit & Loss

- Unrealized Profit & Loss

These values can be calculated by going through all prior fills for that security. But in a trading system that potentially has thousands of trades for a single security this might take too long, especially if this calculation has to be done every time one needs those values. So I assume that it is much quicker to calculate those values based on prior values and the last fill received for that security.

Any help is appreciated!

## Answer by Andy Flury (score 8)

https://quant.stackexchange.com/a/9037

We actually managed to come up with the answer to this question ourselves but wanted to share the answer since it might be relevant to others as well.

The calculation depends on what method is used to calculate the cost. There is the FIFO, LIFO and the average cost method, see: http://www.accounting-basics-for-students.com/fifo-method.html

If FIFO or LIFO are used, there is no other way than going through each fill every time.

However for the average cost method one only needs the prior values of the position (quantity, cost and realized P/L) and the latest fill (qty and price).

These are the calculations (in shortened Java code):

```
closingQty = sign(oldQty) != sign(fillQty) ? min(Math.abs(oldQty), abs(fillQty)) * sign(fillQty) : 0
openingQty = sign(oldQty) == sign(fillQty) ? fillQty : fillQty - closingQty

newQty = oldQty + fillQty
newCost = oldCost + openingQty * fillPrice + closingQty * oldCost / oldQty
newRealizedPL = oldRealizedPL + closingQty * (oldCost / oldQty - fillPrice)
```

The other values can now be derived:

```
averagePrice = cost / qty
marketValue = qty * currentPrice
unrealizedPL = cost - marketValue
```

Thanks to everyone. Any feedback?

## Answer by mde (score 6)

https://quant.stackexchange.com/a/32664

Using Andy Flury answer and bit polishing it gives following Python class for PnL calculator:

```
class PnLCalculator:
    def __init__(self):
        self.quantity = 0
        self.cost = 0.0
        self.market_value = 0.0
        self.r_pnl = 0.0
        self.average_price = 0.0

    def fill(self, n_pos, exec_price):
        pos_change = n_pos - self.quantity
        direction = np.sign(pos_change)
        prev_direction = np.sign(self.quantity)
        qty_closing = min(abs(self.quantity), abs(pos_change)) * direction if prev_direction != direction else 0
        qty_opening = pos_change if prev_direction == direction else pos_change - qty_closing

        new_cost = self.cost + qty_opening * exec_price
        if self.quantity != 0:
            new_cost += qty_closing * self.cost / self.quantity
            self.r_pnl += qty_closing * (self.cost / self.quantity - exec_price)

        self.quantity = n_pos
        self.cost = new_cost

    def update(self, price):
        if self.quantity != 0:
            self.average_price = self.cost / self.quantity
        else: 
            self.average_price = 0
        self.market_value = self.quantity * price
        return self.market_value - self.cost
```

and using it :

```
positions = np.array([200, 100, -100, 150, 50, 0])
exec_prices = np.array([50.0, 51.0, 49.0, 51.0, 53.0, 52.0])
pnls = []
print('Pos\t|\tR.P&L\t|\tU P&L\t|\tAvgPrc')
print('-' * 55)
pos = PnLCalculator()
pnls = []
for (p,e) in zip(positions, exec_prices):
    pos.fill(p, e)
    u_pnl = pos.update(e)
    print('%+d\t|\t%.1f\t|\t%.1f\t|\t[%.1f]' % (pos.quantity, pos.r_pnl, u_pnl, pos.average_price))
    pnls.append(u_pnl + pos.r_pnl)
print('-' * 55)
print(pnls)
```

Output:

```
Pos     |   R.P&L   |   U P&L   |   AvgPrc
-------------------------------------------------------
+200    |   0.0     |   0.0     |   [50.0]
+100    |   100.0   |   100.0   |   [50.0]
-100    |   0.0     |   0.0     |   [49.0]
+150    |   -200.0  |   0.0     |   [51.0]
+50     |   0.0     |   100.0   |   [51.0]
+0      |   50.0    |   0.0     |   [0.0]
-------------------------------------------------------
[0.0, 200.0, 0.0, -200.0, 100.0, 50.0]
```

## Answer by Sebapi (score 3)

https://quant.stackexchange.com/a/54558

Using @mde answer's for the average price method and developing it for Fifo method:

```
# avg price based PnLCalculator
class PnLCalculator:
    def __init__(self):
        self.quantity = 0
        self.cost = 0.0
        self.market_value = 0.0
        self.r_pnl = 0.0
        self.average_price = 0.0

    def fill(self, pos_change, exec_price):
        n_pos = pos_change + self.quantity
        direction = np.sign(pos_change)
        prev_direction = np.sign(self.quantity)
        qty_closing = min(abs(self.quantity), abs(pos_change)) * direction if prev_direction != direction else 0
        qty_opening = pos_change if prev_direction == direction else pos_change - qty_closing
        new_cost = self.cost + qty_opening * exec_price
        if self.quantity != 0:
            new_cost += qty_closing * self.cost / self.quantity
            self.r_pnl += qty_closing * (self.cost / self.quantity - exec_price)
        self.quantity = n_pos
        self.cost = new_cost

    def update(self, price):
        if self.quantity != 0:
            self.average_price = self.cost / self.quantity
        else: 
            self.average_price = 0
        self.market_value = self.quantity * price
        return self.market_value - self.cost
```

Here is a similar class for Fifo and LIFO pnl:

```
# Fifo or Lifo PnL
class FIFOPnlCalculator:
    def __init__(self, isFifo=True):
        self.open_trades = []
        self.closed_trades = []
        self.fifoIndex = -1 if isFifo else 0
        self.r_pnl = 0
        self.quantity = 0
        self.average_price = 0

    def pnl(self,d):
        return (d['close_price']-d['open_price'])*d['pos']

    def fill(self, pos_change, exec_price):
        # new trade
        if len(self.open_trades)==0:
            self.open_trades = [{'pos':pos_change, 'price':exec_price}]
            return
        last_open = self.open_trades[self.fifoIndex]
        # new trade increases position
        if last_open['pos']*pos_change>0:
            self.open_trades += [{'pos':pos_change, 'price':exec_price}]
            return
        # new trade smaller or equal than last open trade
        if abs(last_open['pos'])>=abs(pos_change):
            d = {'pos':-pos_change, 'open_price':last_open['price'], 'close_price':exec_price}
            self.closed_trades += [d]
            self.r_pnl += self.pnl(d)
            last_open['pos'] += pos_change
            if last_open['pos']==0:
                self.open_trades.pop(self.fifoIndex)
            return
        # new trade greater exhausts last open trade
        d = {'pos':-pos_change, 'open_price':last_open['price'], 'close_price':exec_price}
        self.closed_trades += [d]
        self.r_pnl += self.pnl(d)
        pos_change += last_open['pos']
        self.open_trades.pop(self.fifoIndex)
        self.fill(pos_change, exec_price)

    def update(self, price):
        u_pnl = 0
        self.quantity = 0
        self.average_price = 0
        for r in self.open_trades:
            u_pnl += r['pos']*(price-r['price'])
            self.quantity += r['pos']
            self.average_price += r['pos']*r['price']
        if self.quantity!=0:
            self.average_price /= self.quantity
        return u_pnl
```

Here is the code to test for the results for average price:

```
quantities = np.array([200, 100, -100, 100, 100, -400])
exec_prices = np.array([50.0, 51.0, 49.0, 51.0, 53.0, 52.0])
pnls = []
print('Pos\t|\tR.P&L\t|\tU P&L\t|\tAvgPrc')
print('-' * 55)
pos = PnLCalculator()
pnls = []
for (p,e) in zip(quantities, exec_prices):
    pos.fill(p, e)
    u_pnl = pos.update(e)
    print('%+d\t|\t%.1f\t|\t%.1f\t|\t[%.1f]' % (pos.quantity, pos.r_pnl, u_pnl, pos.average_price))
    pnls.append(u_pnl + pos.r_pnl)
print('-' * 55)
print(pnls)
```

and for Fifo:

```
quantities = np.array([200, 100, -100, 100, 100, -400])
exec_prices = np.array([50.0, 51.0, 49.0, 51.0, 53.0, 52.0])
pnls = []
print('Pos\t|\tR.P&L\t|\tU P&L\t|\tAvgPrc')
print('-' * 55)
pos = FIFOPnlCalculator(False)
pnls = []
for (p,e) in zip(quantities, exec_prices):
    pos.fill(p, e)
    u_pnl = pos.update(e)
    print('%+d\t|\t%.1f\t|\t%.1f\t|\t[%.1f]' % (pos.quantity, pos.r_pnl, u_pnl, pos.average_price))
    pnls.append(u_pnl + pos.r_pnl)
print('-' * 55)
print(pnls)
```

As expected, both code output the same pnl, but the breakdown between realized and unrealized pnl is different.

## Answer by Serg (score 1)

https://quant.stackexchange.com/a/9027

This is a partial answer. It shows how to simply calculate Total P&L, which is sum of Realized and UnRealized. From my experience, I didn't really need to split it. Also, It doesn't calculate Average Price, but you can add this functionality if needed.

So, here is the simplest implementation (Java)

```
public class PNL {
    int position = 0;
    double money = 0.0;

    public void on_execution(double price, int quantity) {
        position += quantity;
        money -= quantity * price;
    }

    public double get_pnl(double current_price){
        double pnl = money + position * current_price;
        return pnl;
    }
}
```

Usage:

- Whenever execution occurs, call `on_execution()`, where `price` is execution price, and `quantity` is execution size. Note, that `quantity` is signed, i.e. it must be negative in case of SELL.

- Whenever you need the total P&L, call the `get_pnl()` with current price. The price is up to you to decide. It may be last price, midprice, position liquidation price, etc.

Explanation: The idea here is very intuitive: you change money into position and vice versa. You P&L is sum of money and the value of position. Signed `quantity` makes it consistent for any direction of trade.

## Answer by Taylor (score 0)

https://quant.stackexchange.com/a/40450

an implementation in c++ with slightly different signatures:

```
#ifndef PNL_CALCULATOR_H
#define PNL_CALCULATOR_H

class pnl_calc{

public:

    /**
     * @brief Default Ctor. Sets everything to 0.0
     */
    pnl_calc();

    /**
     * @brief Call this method every time there is a fill.
     * @param fill_qty signed (negative for sold) amount of shares in the most recent transaction
     * @param price the price (taken or received) for the transaction 
     */
    void on_fill(const int& fill_qty, const double& price);

    /**
     * @brief Call this method every time there is a market price movement.
     * @param price the most up-to-date price of an instrument.
     */
    void on_price(const double& price);

    /**
     * @brief get the realized pnl
     * @return realized pnl
     */
    const double& get_rpnl() const;

    /**
     * @brief get the average price (never negative because cost and qty are always the same sign)
     * @return the average price
     */
    const double& get_avg_price() const;    

    /**
     * @brief get the current quantity of shares owned (or sold if negative)
     * @return the number of shares as an integer
     */
    const int& get_qty() const;

private:
    int m_qty; // this is signed 
    double m_cost; // total dollar amount invested (negative for short)
    double m_mkt_val; // total dollar amount currently worth
    double m_rlzd_pnl; // realized profit and loss
    double m_avg_price; // cost / qty
    int sgn(const double& val); 
};

#endif // PNL_CALCULATOR_H
```

and

```
#include "pnl_calculator.h"

#include <algorithm> // min

pnl_calc::pnl_calc() : m_qty(0), m_cost(0.0), m_mkt_val(0.0), m_rlzd_pnl(0.0), m_avg_price(0.0)
{    
}

void pnl_calc::on_fill(const int& fill_qty, const double& price)
{
    int direction = sgn(fill_qty);
    int prev_direction = sgn(m_qty); 

    int qty_opening, qty_closing;
    if(prev_direction == direction){ 
        qty_closing = 0;
        qty_opening = fill_qty; 
    }else{  
        qty_closing = std::min(std::abs(m_qty), std::abs(fill_qty)) * direction;  // first case is reversal, second is a partial closeout
        qty_opening = fill_qty - qty_closing;
    }

    double new_cost = m_cost + qty_opening*price;
    if(m_qty != 0){
        new_cost += qty_closing*m_cost/m_qty;
        m_rlzd_pnl += qty_closing*(m_cost/m_qty - price);
    }

    m_qty += fill_qty;
    m_cost = new_cost;    

    if(m_qty != 0){
        m_avg_price = m_cost / m_qty;
    }else{
        m_avg_price = 0.0;
    }

}

void pnl_calc::on_price(const double& price)
{
    m_mkt_val = m_qty * price;
}

int pnl_calc::sgn(const double& val) 
{
    return (0.0 < val) - (val < 0.0);
}

const double& pnl_calc::get_rpnl() const
{
    return m_rlzd_pnl;
}

const double& pnl_calc::get_avg_price() const
{
    return m_avg_price;
}

const int& pnl_calc::get_qty() const
{
    return m_qty;
}
```

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.