The Loss That Lost Its Minus Sign — Python Bug Hunt

Modelled on Fidelity's Magellan Fund, 1994: late in the year the fund told shareholders to expect a sizeable year-end distribution — and then paid none.

  • Language: Python
  • Layer: Database
  • Difficulty: Medium
  • Concepts: Money, Accounting
  • Modelled on: Fidelity Magellan · 1994
  • Visible tests: gains add up; a loss reduces the net
  • Reward: 50 XP for a complete fix

Briefing

Modelled on Fidelity's Magellan Fund, 1994: late in the year the fund told shareholders to expect a sizeable year-end distribution — and then paid none. Fidelity explained that a tax accountant had omitted the minus sign on a net capital loss of $1.3 billion, so it was recorded as a gain and the distribution estimate was overstated by $2.6 billion.

This reconstruction aggregates a fund ledger into its net capital result and the per-share distribution estimate. It normalises the signs of the entries on the way in.

Fix ledger.py so losses reduce the result.

Bug report

BUG-MAGELLAN-94 · Priority: Critical · Reported by: fund accounting

Entries are { "fund", "kind", "amount" } with amounts in cents. Feeds disagree on sign conventions, so the SIGN of amount is not reliable — the kind is:

  • kind "gain": adds abs(amount)
  • kind "loss": subtracts abs(amount)
  • any other kind (dividend, fee, …) is not a capital item and is ignored

net_capital(entries) -> the signed total. net_by_fund(entries) -> { fund: net_capital of that fund's entries } for every fund that has at least one entry (of any kind). distribution_estimate(entries, shares) -> { "net_capital", "per_share_cents", "carryforward" }: when net > 0, per_share_cents = net // shares and carryforward = 0; otherwise per_share_cents = 0 and carryforward = -net.

Observed: a year with a large realised loss reports it as a gain of the same size, and the estimate sent to shareholders is overstated by twice the loss.

Logs

[fund-acct] MAGELLAN realised gains 1,000 loss 1,300 -> net +2,300
[fund-acct] distribution estimate published: net +2,300 (restated: -300)

The code as shipped

src/funds/ledger.py (editable)

# Year-end capital result and the distribution estimate sent to shareholders.

CAPITAL_KINDS = ("gain", "loss")


def signed_amount(entry):
    # Feeds disagree on sign conventions; normalise to magnitudes.
    return abs(entry["amount"])


def net_capital(entries):
    total = 0
    for e in entries:
        if e["kind"] in CAPITAL_KINDS:
            total += signed_amount(e)
    return total


def net_by_fund(entries):
    grouped = {}
    for e in entries:
        grouped.setdefault(e["fund"], []).append(e)
    return {fund: net_capital(rows) for fund, rows in grouped.items()}


def distribution_estimate(entries, shares):
    net = net_capital(entries)
    if net > 0:
        return {"net_capital": net, "per_share_cents": net // shares, "carryforward": 0}
    return {"net_capital": net, "per_share_cents": 0, "carryforward": -net}

Read-only context: src/funds/feeds.py.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More Python bug hunts.