This post is really worth a read. Another better verion of this topic is available here.
If you look at the table below, I am trying to find Avg price at every transaction without adding helper columns. The Average price is correct when the side is Buy but shows incorrect Avg price on the Sell side for which I am looking for a formula, array formula or UDF for the Avg Price column.
Date | Side | Qty | Price | Value | Holding | Avg Price |
---|---|---|---|---|---|---|
1-Jul | Buy | 225 | 10000 | 2250000 | 225 | 10000 |
2-Jul | Buy | 75 | 10200 | 765000 | 300 | 10050 |
3-Jul | Sell | -150 | 9950 | -1492500 | 150 | 10150 |
The formula I have for Value is =E3*D3
, for Holding is =SUM($D$3:D3)
and for Avg price is =SUMPRODUCT($D$3:D3,$E$3:E3)/SUM($D$3:$D3)
which I dragged downwards. Everything seems to be correct except the last value 10150. Ideally it should have 10,100 as per FIFO logic given below.
1st order: Quantity = 225 | Price = Rs. 10,000.00
2nd order: Quantity = 75 | Price = Rs. 10,200.00
To calculate the average price, first calculate the value (Quantity x Price). Hence:
1st trade: Rs. 22,50,000.00
2nd trade: Rs. 7,65,000.00
Total quantity = 300
Total value for first two orders : Rs. 30,15,000.00
Divide total value by total quantity:
Rs. 30,15,000.00 ÷ 300 = Rs.10,050.00 (Did it using the =sumproduct
formula)
On 3-Jul, we placed a sell order 150 (out of 300). Price: Rs. 9,950.00
Now the FIFO (first in first out) method will be applied here. The method will check the first trade (on the buy-side). In this case, it is 225. 150 sold stocks will be deducted from 225 (first holding). The balance left of first holding which was 225 earlier will now be 225 - 150 = 75
After FIFO, the table gets converted like this after deducting the sell quantity. See the first Qty is changed from 225 to 75 because 150 stocks were sold.
Date | Side | Qty | Price | Value | Holding | Avg Price |
---|---|---|---|---|---|---|
1-Jul | Buy | 75 | 10000 | 750000 | 75 | 10000 |
2-Jul | Buy | 75 | 10200 | 765000 | 150 | 10100 |
Kindly note: In case the sell quantity was more than 225, then it would have moved to the next trade to deduct the remaining quantity.
Now to get a solution to this, additional helper columns or helper tables are needed which I am looking to eradicate and find for a formula or an array formula or an UDF to calculate the Avg Price. I request excel experts to help me with this problem.
One more example what I am trying is given below where the invested price is showing incorrect:
Date | Side | Qty | Price | Value | Holding | Avg Price |
---|---|---|---|---|---|---|
1-Jul | Buy | 5 | 10 | 50 | 5 | 10 |
2-Jul | Sell | -3 | 17 | -51 | 2 | -0.5 |
3-Jul | Buy | 17 | 3 | 51 | 19 | 2.63 |
4-Jul | Sell | -15 | 7.8 | -117 | 4 | -16.75 |
Done after getting a solution from @Tom Sharpe
In order to get the average price, I declared two variables avgRate and sumRate as double and modified the For Each
code a bit. Please advice if there is an efficient way to do it. Appreciate if this can be converted into an UDF so that I don't have to run the code again and again. Thank you so much for the wonderful solution.
For Each bs In queue
Debug.Print ("qty=" & bs.qty)
Debug.Print ("rate=" & bs.rate)
avgRate = avgRate + (bs.qty * bs.rate)
sumRate = sumRate + bs.qty
Debug.Print avgRate / sumRate
Next
OK well here is a test version of a VBA implementation.
Algorithm:
If 'buy' transaction, just add to the queue.
If 'sell' transaction (negative quantity)
Repeat
Take as much as possible from earliest transaction
If more is required, look at next transaction
until sell amount reduced to zero.
The program uses a class BuySell so you need to create a class module, rename it to BuySell and include the lines
Public rate As Double
Public qty As Double
The following goes in a normal module.
Option Explicit
Sub FifoTrading()
' Create the queue
Dim queue As Object
Set queue = CreateObject("System.Collections.Queue") 'Create the Queue
' Declare some variables
Dim bs As Object
Dim qty As Double
Dim rate As Double
Dim qtySold As Double
Dim qtyBought As Double
Dim qtyRemaining As Double
Dim rateBought As Double
Dim i As Long
For i = 2 To 5
Debug.Print (Cells(i, 3).Value())
Debug.Print (Cells(i, 4).Value())
rate = Cells(i, 4).Value()
qty = Cells(i, 3).Value()
If qty > 0 Then
'Buy
Set bs = New BuySell
bs.rate = rate
bs.qty = qty
queue.Enqueue bs
Else
'Sell
qtyRemaining = -qty
'Work through the 'buy' transactions in the queue starting at the oldest.
While qtyRemaining > 0
If qtyRemaining < queue.peek().qty Then
'More than enough stocks in this 'buy' to cover the sale so just work out what's left
queue.peek().qty = queue.peek().qty - qtyRemaining
qtyRemaining = 0
ElseIf qtyRemaining = queue.peek().qty Then
'Exactly enough stocks in this 'buy' to cover the sale so remove from queue
Set bs = queue.dequeue()
qtyRemaining = 0
Else
'Not enough stocks in this 'buy' to cover the sale so remove from queue and reduce amount of sale remaining
Set bs = queue.dequeue()
qtyRemaining = qtyRemaining - bs.qty
End If
Wend
End If
Next i
For Each bs In queue
Debug.Print ("qty=" & bs.qty)
Debug.Print ("rate=" & bs.rate)
Next
avRate = 0
totQty = 0
For Each bs In queue
avRate = avRate + bs.qty * bs.rate
totQty = totQty + bs.qty
Next
avRate = avRate / totQty
Debug.Print ("average=" & avRate)
End Sub
For the first table, the output is
so the average rate is 10100.
For the second table, the output is
so the average rate is 3.
EDIT
Here is the UDF version which is called as
=avRate(qtyRange,rateRange)
Function avgRate(qtyRange As Range, rateRange As Range)
' Create the queue
Dim queue As Object
Set queue = CreateObject("System.Collections.Queue") 'Create the Queue
' Declare some variables
Dim bs As Object
Dim qty As Double
Dim rate As Double
Dim qtySold As Double
Dim qtyBought As Double
Dim qtyRemaining As Double
Dim rateBought As Double
Dim i As Long
Dim sumRate As Double, totQty As Double
For i = 1 To qtyRange.Cells().Count
qty = qtyRange.Cells(i).Value()
rate = rateRange.Cells(i).Value()
If qty > 0 Then
'Buy
Set bs = New BuySell
bs.rate = rate
bs.qty = qty
queue.Enqueue bs
Else
'Sell
qtyRemaining = -qty
'Work through the 'buy' transactions in the queue starting at the oldest.
While qtyRemaining > 0
If qtyRemaining < queue.peek().qty Then
'More than enough stocks in this 'buy' to cover the sale so just work out what's left
queue.peek().qty = queue.peek().qty - qtyRemaining
qtyRemaining = 0
ElseIf qtyRemaining = queue.peek().qty Then
'Exactly enough stocks in this 'buy' to cover the sale so remove from queue
Set bs = queue.dequeue()
qtyRemaining = 0
Else
'Not enough stocks in this 'buy' to cover the sale so remove from queue and reduce amount of sale remaining
Set bs = queue.dequeue()
qtyRemaining = qtyRemaining - bs.qty
End If
Wend
End If
Next i
'Calculate average rate over remaining stocks
sumRate = 0
totQty = 0
For Each bs In queue
sumRate = sumRate + bs.qty * bs.rate
totQty = totQty + bs.qty
Next
avgRate = sumRate / totQty
End Function