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
----------  -------------------  ------  ---  -----  ---  -------------