import csv from collections import defaultdict, deque class FIFOQueue: def __init__(self): self.queue = deque() def add(self, amount, cost): self.queue.append((amount, cost)) def remove(self, amount): remaining = amount cost_basis = 0 while remaining > 0: if not self.queue: raise ValueError(f"Insufficient assets in queue to process sale of {amount}.") quantity, cost = self.queue[0] if quantity > remaining: cost_basis += remaining * cost self.queue[0] = (quantity - remaining, cost) remaining = 0 else: cost_basis += quantity * cost remaining -= quantity self.queue.popleft() return cost_basis def process_ledger(file_path): fifo_queues = {} # Separate FIFO queue per cryptocurrency trades_by_refid = defaultdict(list) report = [] with open(file_path, 'r') as file: reader = csv.DictReader(file) for row in reader: # Group trades by refid if row["type"] == "trade": trades_by_refid[row["refid"]].append(row) # Handle deposits elif row["type"] == "deposit": currency = row["asset"] fifo_queues.setdefault(currency, FIFOQueue()) amount = float(row["amount"]) price = 0 # Deposits typically have no associated cost basis fifo_queues[currency].add(amount, price) # Process grouped trades for refid, trades in trades_by_refid.items(): if len(trades) == 2: # Ensure we have two related rows (EUR + crypto) eur_trade = next((trade for trade in trades if trade["asset"] == "EUR"), None) crypto_trade = next((trade for trade in trades if trade["asset"] != "EUR"), None) if eur_trade and crypto_trade: crypto_asset = crypto_trade["asset"] eur_amount = float(eur_trade["amount"]) eur_fee = float(eur_trade["fee"]) crypto_amount = float(crypto_trade["amount"]) crypto_fee = float(crypto_trade["fee"]) fifo_queues.setdefault(crypto_asset, FIFOQueue()) if eur_amount < 0: # Purchase of cryptocurrency stake_amount = -eur_amount - eur_fee # Account for EUR fees crypto_amount -= crypto_fee # Adjust for crypto fees fifo_queues[crypto_asset].add(crypto_amount, stake_amount) elif eur_amount > 0: # Sale of cryptocurrency sale_proceeds = eur_amount - eur_fee # Account for EUR fees cost_basis = fifo_queues[crypto_asset].remove(crypto_amount) profit_or_loss = sale_proceeds - cost_basis report.append((eur_trade["time"], crypto_asset, profit_or_loss)) else: raise ValueError(f"Unexpected trade grouping for refid {refid}") else: raise ValueError(f"Unexpected number of trades for refid {refid}") return report # Usage ledger_path = "kraken_ledger.csv" # Replace with your file path profit_and_loss_report = process_ledger(ledger_path) for entry in profit_and_loss_report: print(entry)