11
$\begingroup$

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!

$\endgroup$
4
  • $\begingroup$ Move closed trades into a different collection, simple as that.By the way, you should re-calculate an average price each time you get a fill, in that sense you can store and retrieve the avg price (or for that matter any other metric) at any time without additional computational overhead. (unless of course you query or utilize avg price much more seldom than you receive fills). $\endgroup$
    – Matt Wolf
    Sep 25, 2013 at 14:42
  • $\begingroup$ Thanks Matt. I believe I understand what you are saying. Store all those values with the Position and whenever a new Fill comes in calculate the new values based on the old values plus qty and price from the new Fill? You are right, this way one would not have to go through the entire collection each time. Do you have any idea where to find an example of those calculations, e.g. new unrealized P/L = f(old unrealized P/L, new Fill)? $\endgroup$
    – Andy Flury
    Sep 25, 2013 at 14:58
  • $\begingroup$ Sorry dont have, but its a trivial calculation, weigh the average price by the previous size underlying the old avg price and the fill size and fill price. $\endgroup$
    – Matt Wolf
    Sep 25, 2013 at 15:24
  • $\begingroup$ Thanks Matt. Let me try to come up with those formulas and maybe I can paste them here as an answer to my own question, if that makes sense $\endgroup$
    – Andy Flury
    Sep 26, 2013 at 8:04

5 Answers 5

8
$\begingroup$

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?

$\endgroup$
8
  • $\begingroup$ I thought your closingQty was assuming that the position was not being flipped but now that I've written lots of test cases, it handles all of them ! Thank-you ! $\endgroup$ Jan 19, 2014 at 16:25
  • 1
    $\begingroup$ C# implementation with test cases: here $\endgroup$ Jan 19, 2014 at 18:49
  • $\begingroup$ The calculation fails for the following test case, which the expected result is 54.0 for realized profit, and the calculated is 52.0: pos.addFill(1, 80.0); pos.addFill(-3, 102.0); pos.addFill(-2, 98.0); pos.addFill(3, 90.0); pos.addFill(-2, 100.0); $\endgroup$ Mar 28, 2014 at 17:06
  • $\begingroup$ This algorithm is pretty standard for calculation of UPL/RPL but it's order dependent. ie.( +100@10, - 50@15, +100@12 will produce different result from +100@10, +100@12, - 50@15 ). This could lead to values that are very difficult to explain $\endgroup$
    – Kozyarchuk
    Apr 15, 2015 at 22:21
  • $\begingroup$ Did you choose the average cost method because of its computational efficiency or another reason? My understanding is that FIFO is the most commonly technique used in finance. $\endgroup$
    – Morten
    Jul 6, 2015 at 15:33
5
$\begingroup$

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]
$\endgroup$
3
+50
$\begingroup$

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.

$\endgroup$
1
$\begingroup$

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.

$\endgroup$
1
  • 1
    $\begingroup$ This method only calculates TPL and does not provide Average Price, Cost, UPL/RPL $\endgroup$
    – Kozyarchuk
    Apr 15, 2015 at 21:04
0
$\begingroup$

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;
}
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.