# Chapter 2: The ledger: transactions to holdings

*The Analyst's Handbook, Sifter's textbook for student investors · By Matt Lang and Bree Lang · Foundations · https://sifter.finance/textbook/ch02-ledger*

Buys, sells, dividends, gifts, and honest mistakes: how a pile of transactions becomes your holdings table.

Where this is on Sifter: Portfolio · Manage / Holdings

Imagine that you open your fund's Holdings page and ask yourself where all of those numbers actually live. It would be reasonable to guess that the table is stored somewhere, with each position's shares and average cost in a tidy database cell, but that guess would be wrong. The Holdings table is not stored anywhere, and neither is the AUM band, the performance chart or any attribution table. All of them are *replays*, rebuilt on demand from one object that underlies everything else in this book, the **transaction ledger**, which is the raw record of what was bought, sold and received. The relationship is the same as the one between a chess position and the list of moves, because the position never needs to be saved anywhere when it can always be reconstructed by playing the moves back.

The ledger has a second property that matters just as much, which is that it is **append-only**. Nothing in the ledger is ever edited and nothing is ever deleted. When a mistake is made (and mistakes will be made), the fix is to append an offsetting **VOID** entry that cancels the bad row and then write the proper row after it. Fund accounting worked this way long before computers came along, because an audit trail that can be quietly rewritten does not prove much of anything to an auditor. There is also a payoff hidden in the rule. Because every entry survives, the book can answer not only the question "what do we own" but the question "what did we believe we owned on any date in history", and that second question is the one the as-of date picker on the Holdings lens answers. This means that when you pick a past date, the software replays the ledger and simply stops early. The figure below shows the page on the sample book, so that you know what we are about to take apart. To take it apart by hand we need a ledger small enough to replay on paper, so under the figure is the entire history of a tiny fund we will call the **Maple Fund**, seven rows in all, which will be our practice book for the rest of the unit.

![The Holdings page on the sample book, cropped to the as-of control at the top, the AUM band with total AUM and cash, and the holdings table with ticker, weight, shares, average cost, cost, price, value, day and one-year change, P&L and contribution columns.](https://sifter.finance/textbook/figures/ch02-holdings.png)

*The Holdings page, on the sample book. The as-of control, the AUM band, and the table (the heatmap between the band and the table on the live page is cropped out here, since this chapter is about the table). Nothing in this picture is stored anywhere. Every number in it is replayed from the ledger this chapter is about, and by the end of the chapter you will be able to rebuild the Shares, Avg Cost and Cost columns by hand.*

**Maple Fund: the raw ledger**

| Date | Type | Symbol | Qty | Price | Note |
| --- | --- | --- | --- | --- | --- |
| 2025-11-03 | buy | MPL | 20 | $100.00 |  |
| 2025-11-17 | buy | MPL | 5 | $120.00 |  |
| 2025-12-01 | buy | MPL | 100 | $1.20 | fat-finger entry (meant 1 share at $120) |
| 2025-12-01 | void | MPL | -100 | $1.20 | offsetting VOID of the bad row |
| 2025-12-10 | sell | MPL | 5 | $130.00 |  |
| 2025-12-19 | reinvestment | MPL | 0.5 | $116.00 | dividend reinvested |
| 2025-12-22 | transfer_in | BRC | 10 | $80.00 | donated; price = market value on donation date |

Take a minute and read through the seven rows before moving on, because one of them is a deliberate mistake and another is its correction. When the ledger is replayed, the fat-finger buy and its VOID cancel each other out, and the replay skips both rows.

**From ledger to holding**

```
net shares = bought − sold        avg cost = total cost / shares bought
```

"Bought" includes dividend reinvestments (new shares, new cost). Selling reduces net shares but does NOT change average cost: you sold some shares at some price, but what you paid for the ones you bought is history and stays fixed. Voided rows are excluded from the replay entirely.

### Worked example: Worked example: replaying MPL

Let's walk the ledger from top to bottom and keep three running tallies as we go (shares bought, total cost and shares sold).

1. **Two real buys** 20 shares at $100.00 = $2,000, then 5 shares at $120.00 = $600. Bought: 25. Total cost: $2,600. *Average cost so far = $2,600 / 25 = $104.00.*
2. **The mistaken trade and its VOID** The 100-share at $1.20 entry was a typo. It was not edited or deleted; an offsetting VOID row was appended. Replay skips both. *Tallies unchanged. The mistake stays visible in the record forever, so the audit trail survives.*
3. **The sell** Sold 5 at $130.00. Sold tally: 5. Bought and total cost: untouched. *Net shares = 25 − 5 = 20. Average cost still $104.00: realizing a gain does not rewrite what you paid.*
4. **The dividend reinvestment** A dividend bought 0.5 more shares at $116.00 = $58. Reinvestments count as bought shares with cost. *Bought: 25.5. Total cost: $2,658. Net shares = 20.5. Average cost = $2,658 / 25.5 = $104.24.*

### Practice

Confirm the final MPL average cost from the worked example, in dollars (2 decimals).

**Solution.** $2,658 / 25.5 = $104.24. Take a moment to think about why the average moved at all. The reinvestment bought its shares at $116, and because $116 is above the old average of $104.00, folding those new shares in pulled the average cost up a bit.

### Check yourself

A teammate entered yesterday's trade with the wrong quantity. The correct fix is:

- Edit the row to the right quantity. The ledger is append-only, which means that rows are never edited after they are written. Editing the row would silently rewrite history, and every as-of replay of a past date would break along with it.
- Delete the row and re-enter it. Deleting the row runs into the same problem, because the record of the mistake disappears and the audit trail no longer tells the truth about what happened. Deletion is simply not in the ledger's vocabulary.
- Append a VOID for the bad row, then append the correct one. **(correct)** Right. The mistake, its cancellation and the correction all stay visible in the record, and the replay skips the voided pair, which means that auditors (and any as-of reconstruction) still get to see everything that happened.

Let's go back to the sell for a minute, because there is a question hiding in step three that we walked right past. We sold 5 shares of MPL at $130.00, and the tallies told us that the average cost stayed at $104.00. But which 5 shares did we sell? The fund had bought 20 shares at $100.00 and 5 more at $120.00, and the ledger does not say which of them went out the door. Average cost sidesteps the question entirely, because every share in the position is treated as costing the average, which means that the 5 shares we sold cost $104.00 apiece and the sale realized a gain of 5 × ($130.00 − $104.00) = $130.

Your brokerage would answer the question differently. Unless you tell it otherwise, a U.S. broker assumes that the first shares bought are the first shares sold, a convention called **first in, first out (FIFO)**. Under FIFO the 5 shares we sold came out of the 20 bought at $100.00, and the very same sale realized 5 × ($130.00 − $100.00) = $150. Take a moment to think about what just happened. The trade did not change, the $650 of cash that came in did not change, and the 20 shares still in the book did not change. The only thing that changed was a bookkeeping convention, and the realized gain moved by $20.

The $20 did not disappear, because it moved into the shares we still hold. FIFO sold the cheap shares, which means that the shares left behind are the more expensive ones (15 at $100.00 and 5 at $120.00, for a basis of $2,100), while average cost leaves 20 shares at $104.00, for a basis of $2,080. Under both conventions the basis of the shares sold plus the basis of the shares kept adds up to the $2,600 we actually paid, and if we mark the 20 remaining shares at $130.00, the total gain (realized plus unrealized) is $650 either way. In other words, the choice of convention moves dollars between the realized column and the unrealized column and nothing else. It cannot touch the value of the book, and it cannot touch performance, because the time-weighted return we will build in Chapter 3 is computed from values and never uses a cost basis at all. (The tax code cares a great deal about which convention a taxable investor uses, which is the reason your brokerage bothers to track lots in the first place.)

**MPL after the sell: one trade, two conventions**

|  | Average cost | FIFO |
| --- | --- | --- |
| Basis of the 5 shares sold | $520.00 | $500.00 |
| Realized gain | $130.00 | $150.00 |
| Basis of the 20 shares kept | $2,080.00 | $2,100.00 |
| Unrealized gain at $130.00 | $520.00 | $500.00 |
| Total gain | $650.00 | $650.00 |

Read down each column and then across. The $20 that FIFO adds to the realized row it takes back from the unrealized row, and the two totals agree because both columns are describing the same $2,600 of purchases and the same $130.00 price.

### Practice

Under FIFO, what gain did the MPL sell of 5 shares at $130.00 realize, in dollars?

**Solution.** 5 × ($130.00 − $100.00) = $150.00. Compare that to the $130.00 under average cost. The extra $20 is 5 shares times the $4.00 difference between the $100.00 first lot and the $104.00 average, and it comes straight out of the unrealized gain on the shares that remain.

Which convention does Sifter use? The worked example's replay, the one that kept three running tallies and landed on an average cost, is the one Sifter runs, and its average cost is the number the Holdings table reports for any position you built by recording trades. The FIFO gain you just computed is one click deeper. However, if your fund's history was imported from a brokerage statement, the cost figure on Holdings is whatever the broker carried on the day of the import, and at most U.S. brokers that figure is already FIFO. This means that two funds holding the same stock can show different cost bases on the same page for no reason other than how their histories got into the system, and it is healthy to know which one you are looking at before you compare a Sifter number to a statement. Fortunately you do not have to guess. Click any row on Holdings and the position drawer has a **Lots** card, which lists every open lot in the order it was bought, the FIFO gain on every sale, and the FIFO basis next to the ledger's average cost basis, so that both stories about the same shares sit side by side. The figure below shows the card for one position on the sample book.

![The Lots card from the position drawer on the sample book: the FIFO basis and the ledger average cost basis side by side and different, a note saying Holdings shows the ledger average cost, a table of three open lots with the first lot shown as partly sold, and a Realized FIFO line with the one sale, its proceeds, the basis relieved and the gain.](https://sifter.finance/textbook/figures/ch02-lots.png)

*The Lots card, on the sample book. This position was bought in three lots and part of it was sold between the second and third buys, so the two bases at the top disagree, and the note under them says which one the Holdings table is showing. FIFO relieved the sale from the first lot, which is why that lot appears as a fraction of its original size, and the Realized line shows the FIFO gain on the sale. The difference between the two bases is the same difference we saw in the Maple table above, at a larger scale.*

> **Where you will see this** Open Portfolio › Holdings, click a position that has had at least one partial sale, and find the Lots card in the drawer. The FIFO basis and the ledger average cost are at the top, and if the two disagree, the one-line note under them tells you which figure the Holdings table is showing.

One transaction type needs special care in a student fund, because it involves a gift. A **transfer_in** is a donated security, and the BRC row in the ledger above shows ten shares arriving with a price of $80.00. Some of you may assume that the figure is what the donor originally paid for the shares, but the fund records the **market value on the donation date**, the moment the shares became the fund's responsibility. Whatever gains or losses the donor accumulated before that date belong on the donor's tax return rather than on your track record. (File this away for Chapter 3 as well. A transfer_in is an external flow, and TWR will neutralize it on the day it arrives.)

### Practice

An alum donates 10 shares they bought years ago at $50.00 each. On the donation date the stock trades at $80.00. What cost basis per share does the fund record for performance, in dollars?

**Solution.** $80.00, the market value on the day the fund received the shares. If the fund booked the donor's $50 instead, it would be awarding itself a +60% "gain" that it never earned, because the fund's performance clock does not start until the asset enters the book.

One row in the Maple ledger, the reinvestment, is a bit different from the rest, because it is not about deciding to buy or sell a stock at all but about being *paid* for owning one. A **dividend** is income, and a **reinvestment** is that income spent immediately on more shares. Notice that total return already captures both of them, since the cash lands in the book (or the new shares do) and the performance replay counts them like any other change in value. Having said that, a fund often wants to look at its income on its own, and that is the job of the **Income tab** under Analyze, which pulls these same ledger rows back out as a stream instead of folding them into a single return number.

The tab also asks a forward-looking question that the ledger alone cannot answer. At today's share count, and at each holding's current dividend rate, how much income should the next year bring? Divide that projection by the value of the book and you have the portfolio's **yield**. Divide it instead by what you *paid* (the average cost from the worked example) and you have **yield on cost**, which explains how a position bought years ago can out-yield its own current price. There is one subtlety here that the tab handles for you. A forward yield should annualize a holding's *regular* dividend rather than blindly summing the last twelve months, because a single special distribution would otherwise masquerade as a permanent raise.

> **Where you will see this** Open Portfolio › Analyze › Dividends and take a look around. Forward income and portfolio yield are built from your current holdings, which means that they appear even before a single dividend has been recorded. Yield on cost measures that income against the average cost you computed in this chapter, and once the ledger holds dividend rows, realized income fills itself in by year and by holding.

All of this replaying has a destination, and it is the page your fund will stare at more than any other, the **Holdings** page, the one in the figure at the top of this chapter. Along the top runs the **AUM band**, which shows the fund's total value (broken out by sleeve if you use them). Below it is the table itself, with one row per position, and the satisfying thing about this table is that every column is now something we can derive rather than something we have to take on faith. Shares and Avg Cost come straight from the worked example's replay, Cost is simply the two multiplied together, the current price arrives from the market, and value is shares times price. That leaves **weight**, which is the position's value divided by the value of the whole book, and it needs more of your attention than any other column, because a pick helps or hurts the fund only in proportion to the weight it was given. A brilliant pick at a 1% weight will barely move the book no matter how right you were (although it will still make a nice story at your next meeting).

The table has three smaller features. The **as-of date picker**, the small control above the band in that figure, is the ledger replay made visible, and choosing any past date rebuilds the table as it stood that evening. Index positions such as SPY or VOO are treated as **passive**, with a one-click toggle that sets them aside, so the table always shows which part of the book is making active bets and which part is simply being the market (a distinction Chapter 8 will lean on hard). Lastly, clicking any row opens the position drawer you met in the Lots section, with that position's full story.

### Practice

MPL finished the worked example at 20.5 net shares. If the price is $120.00 and the whole book is worth $12,300, what is MPL's weight, as a percentage?

**Solution.** 20.5 × $120 = $2,460, and $2,460 / $12,300 = 20.0%. Take a moment to think about what that percentage means. One position is carrying a fifth of the entire book, and whether that reflects conviction or carelessness is a question for your investment committee, but at least you can now walk into that conversation holding the number.

> **Try it on Sifter: Verify a holdings row** Open your own Holdings page and pick one row. Verify on a calculator that value = shares × price, and then check that dividing that value by the AUM band's total gives you the weight column. From there, set the as-of date to the start of the semester and watch the whole book time-travel.

### Lab: CSV lab: replay one position yourself

1. Go to Portfolio › Manage › Activity and use Export CSV to download the transaction ledger. (On the sample book this file is empty, because the sample is served as positions rather than as a ledger; work the lab on the bundled Maple ledger below until you have imported your own.)
2. Filter the rows down to one ticker you hold, and exclude the voided rows (along with anything a VOID offsets).
3. Tally the shares bought (buys plus reinvestments), the total cost and the shares sold, then compute net shares and average cost.
4. Compare against that ticker's row on the Holdings lens: your net shares should match its Shares and your average cost should match its Avg Cost, exactly, since it is the same ledger and the same replay. (On a book imported from a broker, Avg Cost may be the broker's FIFO figure instead, as the Lots section explained.)
5. Bonus: on Holdings, set the as-of date to last month and watch the row change. You just replayed a shorter ledger.
