You are on page 1of 9

Advanced FIFO Tax Calculator Exercise

Background Part I

Basic Transaction and TotalTax data structures


In this exercise, we will initially model buy/sell transactions as events with a globally unique, fully orderable timestamp
( txn_time ) as well as other attributes. For the purpose of this exercise, the other attributes of a transaction are

irrelevant - the only thing that is important is the txn_time of the transaction:

Transaction
txn_time: DATE
other_attributes: ...security, quantity, buy/sell, etc...

We will also initally model the total tax owed as a state record with a globally unique, fully orderable tax_time

attribute as well as a total_amount_owed . For a given TotalTax record, total_amount_owed represents the total tax

owed as of the tax_time :

TotalTax

tax_time: DATE
total_amount_owed: DECIMAL

Basic fifo_calculator function


This exercise assumes we already have an implementation of a fifo tax calculator that takes an ordered oldest-to-newest
list of Transaction and calculates the total_amount_owed :

fifo_calculator(transactions: List<Transaction>) => total_amount_owed: DECIMAL

Basic total_tax_calculator function


Imagine we have a table/CRUD-style repository of Transaction records:
txn_time other_attributes

1 …

2 …

3 …

4 …

5 …

We can define a total_tax_calculator function which calculates the TotalTax record for a given tax_time by

looking up transactions from the Transaction repository:

total_tax_calculator(Date:tax_time) => TotalTax:total_tax

The simplest and least efficient implementation of this is to always calculate the total_tax from scratch using
fifo_calculator :

total_tax_calculator(Date:tax_time) => {

transactions = SELECT t.*


FROM Transactions AS t
WHERE t.txn_time <= :tax_time:
ORDER BY t.txn_time ASCENDING

total_amount_owed = fifo_calculator(transactions)

return TotalTax(tax_time, total_amount_owed)

Incremental fifo_calculator function


Most of us who have implemented a fifo tax calculator have realized that we need to maintain some in-memory state
between processing each transaction so that we can calculate the correct total tax amount owed. Those of us familiar
with financial accounting will recognize that this state has a generally accepted name: tax_lots . Those of us who are

not familiar with financial accounting should just accept that it is possible to refactor a fifo tax calculator
implementation to be incremental by serializing and deserializng its internal state:
incremental_fifo_calculator(
old_total_amount_owed: DECIMAL,
old_tax_lots: STATE,
new_transactions: List<Transaction>
) => (new_total_amount_owed: DECIMAL, new_tax_lots: STATE)

To understand the relationship between the regular fifo_calculator and the incremental_fifo_calculator ,

consider the following example:

// Calculate total amount owed for five transactions all at once


total_amount_owed = fifo_calculator([ Txn1, Txn2, Txn3, Txn4, Txn5 ])

// Calculate amount owed and tax_lots for first three transactions


total_amount_owed_123, tax_lots_123 = incremental_fifo_calculator(
old_total_amount_owed: 0,
old_tax_lots: null,
new_transactions: [ Txn1, Txn2, Txn3 ]
)

// Use previous calculation as starting point and add on remaining two transactions
total_amount_owed_12345, tax_lots_12345 = incremental_fifo_calculator(
old_total_amount_owed: total_amount_owed_123,
old_tax_lots: tax_lots_123,
new_transactions: [ Txn4, Txn5 ]
)

// Both approaches should produce the same final result


total_amount_owed == total_amount_owed_12345

Extended TotalTax data structure


Since it appears storing the tax_lots associated with a given total_amount_owed may come in handy to support

incremental total tax calculation, let’s extend the TotalTax data structure to include it:

TotalTax
tax_time: DATE
total_amount_owed: DECIMAL
tax_lots: STATE

Furthermore, let’s imagine we also have a table/CRUD_style repository of these extended TotalTax records:
tax_time total_amount_owed tax_lots

2 … …

5 … …

Exercise 1: Smarter total_tax_calculator function

In this exercise, we will psuedo-code a smarter version of total_tax_calculator that pulls as few Transaction

records as possible by first checking to see if there are any existing TotalTax records we can use as a starting point for

incremental_fifo_calculator

If our Transaction records repository contains:

txn_time other_attributes

1 …

2 …

3 …

4 …

5 …

And if our TotalTax records repository contains:

tax_time total_amount_owed tax_lots

2 … …

5 … …

Then our smarter total_tax_calculator implementation should behave in the following way
total_tax_calculator(1)
Should not use any existing TotalTax records
Should only pull the txn_time=1 Transaction record

total_tax_calculator(2)
Should use existing TotalTax record for tax_time=2
Should not pull any Transaction records

total_tax_calculator(3)
Should use existing TotalTax record for tax_time=2
Should only pull the txn_time=3 Transaction record

total_tax_calculator(4)

Should use existing TotalTax record for tax_time=2


Should only pull the txn_time=3 and txn_time=4 Transaction records

total_tax_calculator(5)
Should use existing TotalTax record for tax_time=5
Should not pull any Transaction records

Background Part II: Back to the Future

Temporal Transaction data structure


Up until this point, we have been modeling Transaction as simply having a txn_time and assuming that we never

rewrite history. Unfortunately in the real world we have to deal with being notified about transactions out-of-order. To
accomodate this fact, we will extend the Transaction object with a knowledge_time which represents the actual

wall-clock time when we were notified of the corresponding transaction:

Transaction
txn_time: DATE
knowledge_time: DATE
other_attributes: ...security, quantity, buy/sell, etc...

To understand the relationship between txn_time and knowledge_time , consider how the Transaction

repository evolves over the following sequence of events:

At t=10, we were notified that a transaction took place at txn_time=1:


txn_time knowledge_time other_attributes

1 10 …

At t=20, we were notified that a transaction took place at txn_time=2:

txn_time knowledge_time other_attributes

1 10 …

2 20 …

At t=30, we were notified that a transaction took place at txn_time=4:

txn_time knowledge_time other_attributes

1 10 …

2 20 …

4 30 …

At t=40, we were notified that a transaction took place at txn_time=5:

txn_time knowledge_time other_attributes

1 10 …

2 20 …

4 30 …

5 40 …

At t=50, we were notified of a backdated transaction that took place at txn_time=3:

txn_time knowledge_time other_attributes

1 10 …

2 20 …

3 50 …

4 30 …

5 40 …

Temporal TotalTax data structure


Since we’ve introduced knowledge_time to our Transaction data structure, it makes sense to also include it in our

TotalTax data structure. Specifically, the knowledge_time of a given TotalTax record implies that it was
calculated using only Transaction records which were created at or before that knowledge_time :

TotalTax
tax_time: DATE
knowledge_time: DATE
total_amount_owed: DECIMAL
tax_lots: STATE

Basic temporal total_tax_calculator function


Now that both Transaction and TotalTax are knowledge_time -aware, we need to update

total_tax_calculator to be knowledge_time -aware as well.

total_tax_calculator(
Date:tax_time,
Date:knowledge_time
) => TotalTax:total_tax

Once again, the simplest and least efficient implementation of this is to always calculate the total_tax from scratch
using fifo_calculator :

total_tax_calculator(
Date:tax_time,
Date:knowledge_time
) => {

transactions = SELECT t.*


FROM Transactions AS t
WHERE t.txn_time <= :tax_time:
AND t.knowledge_time <= :knowledge_time:
ORDER BY t.txn_time ASCENDING

total_amount_owed = fifo_calculator(transactions)

return TotalTax(tax_time, knowledge_time, total_amount_owed)

}
Exercise 2: Smarter temporal total_tax_calculator
function

In this exercise, we will psuedo-code a smarter version of total_tax_calculator that is knowledge_time -aware

while still pulling as few Transaction records as possible by using existing TotalTax records where possible.

If our temporal Transaction records repository contains:

txn_time knowledge_time other_attributes

1 10 …

2 20 …

3 50 …

4 30 …

5 40 …

And if our temporal TotalTax records repository contains:

tax_time knowledge_time total_amount_owed tax_lots

2 20 … …

4 30 … …

Then our temporal total_tax_calculator implementation should behave in the following way
total_tax_calculator(txn_time: 5, knowledge_time: 10)
Should not use any existing TotalTax records
Should only pull the txn_time=1 Transaction record

total_tax_calculator(txn_time: 5, knowledge_time: 20)


Should use existing TotalTax record for tax_time=2, knowledge_time=20
Should not pull any Transaction records

total_tax_calculator(txn_time: 5, knowledge_time: 30)


Should use existing TotalTax record for tax_time=4, knowledge_time=30
Should not pull any Transaction records

total_tax_calculator(txn_time: 5, knowledge_time: 40)


Should use existing TotalTax record for tax_time=4, knowledge_time=30
Should only pull the txn_time=5 Transaction record

total_tax_calculator(txn_time: 5, knowledge_time: 50)


Should use existing TotalTax record for tax_time=2, knowledge_time=20
Should only pull the txn_time=3, txn_time=4, and txn_time=5 Transaction records

You might also like