FIFO inventory with ledger-cli
Ledger is a powerful command-line plain-text accounting system. It has extensive built-in support for tracking complex ‘commodities’, such as currencies and inventory items, but does not support FIFO valuation.
This is a small Python script that can take input from Ledger, break down the prices of commodities based on FIFO accounting, and validate that FIFO accounting has been adhered to:
#!/usr/bin/env python3
import csv
from decimal import Decimal
import sys
from tabulate import tabulate
reader = csv.reader(sys.stdin)
rows = list(reader)
rows.sort(key=lambda x: x[0]) # Sort by date
balances = []
results = []
for row in rows:
# Parse row
value = row[2].split()[0]
if value[0] not in '-0123456789':
# Has commodity symbol
commodity = value[0]
value = Decimal(value[1:])
else:
# Has commodity name
commodity = row[2].split()[1]
value = Decimal(value)
price = row[2][row[2].index('{')+1:row[2].index('}')]
# Process row
if value > 0:
stock_item = [row[0], commodity, price, value]
i = len(balances)
balances.append(stock_item)
else:
i, stock_item = next(((i, x) for i, x in enumerate(balances) if x[1] == commodity), None)
if not stock_item:
print('Got {} {{{}}} on {} but none available'.format(value, price, row[0]))
sys.exit(1)
if stock_item[2] != price:
print('Got {} {{{}}} on {} but expected {} (FIFO violation)'.format(value, price, row[0], balances[0][2]))
sys.exit(1)
if stock_item[3] + value < 0:
print('Got {} {{{}}} on {} but only {} available'.format(value, price, row[0], stock_item[3]))
sys.exit(1)
stock_item[3] += value
# Report transaction
for j, x in enumerate(balances):
result = ((row[0].replace('/', '-'), row[1]) if j == 0 else ('', '')) + ((value, commodity) if i == j else ('', '')) + (x[3], x[1], x[2])
results.append(result)
results.append(('', '', '', '', '', '', ''))
if stock_item[3] == 0:
del balances[i]
del results[-1]
print(tabulate(results, colalign=('left', 'left', 'decimal', 'left', 'decimal', 'left', 'left')))
Use it as follows:
ledger csv --csv-format '%(quoted(date)),%(quoted(payee)),%(quoted(display_amount))\n' AccountNameHere | python fifotool.py
Example output:
---------- ------------------- ------ --- ----- --- -------------
2019-07-01 Opening Balances 98.09 USD 98.09 USD $1.4238270936
2019-07-07 Income 98.09 USD $1.4238270936
18.97 USD 18.97 USD $1.4324417829
2019-07-08 Expenditure -9.40 USD 88.69 USD $1.4238270936
18.97 USD $1.4324417829
2019-07-21 Further expenditure -88.69 USD 0.00 USD $1.4238270936
18.97 USD $1.4324417829
2019-07-22 Next FIFO item -5.00 USD 13.97 USD $1.4238270936
2019-08-07 Further income 13.97 USD $1.4324417829
18.97 USD 18.97 USD $1.477728466
---------- ------------------- ------ --- ----- --- -------------