The ledger
How Investi matches lots (FIFO, LIFO, HIFO), counts round trips, rounds realized P/L, treats fees, applies splits, handles shorts and legacy sales with unknown basis, and compares lot methods.
On this page
The ledger turns your fills into lot matches, round trips and realized P/L. It is deterministic: the same fills, splits and lot method always produce the same result to the cent. Your original executions are never edited; the ledger is recomputed from them every time.
Definitions#
These are the definitions the engine implements (packages/core/src/ledger.ts):
- Lot match: a quantity of an opening fill paired with a quantity of a closing fill.
- Round trip: one position in one account taken from flat to open and back to flat. The trip opens when the position moves away from zero and closes when it returns to zero.
- Realized P/L: per lot match, proceeds minus cost, each rounded to cents.
- Fees: buy fees are added to cost; sell fees reduce proceeds.
Matching is per account and instrument#
Fills are grouped by account and security before anything is matched. The same ticker held in two accounts never cross-matches: a sale in one account cannot close a lot opened in another.
Within a group, fills are processed in trade-time order. Fills with identical timestamps keep the order they arrived in, so a buy and a sell recorded on the same day in that order match as a buy then a sell.
Lot methods#
When a fill closes shares, the method decides which open lot it closes first:
| Method | Long positions | Short positions |
|---|---|---|
| FIFO | Oldest lot first | Oldest lot first |
| LIFO | Newest lot first | Newest lot first |
| HIFO | Highest cost per share first (price plus opening fee per share) | Lowest short price per share first |
FIFO is the default. Change it under Settings > Reporting lot method; see Settings. The setting changes only how lots are matched for reporting. Every surface (web, API, MCP, extension) switches at once because the method is read on the server.
Worked example#
Three buys, then one sale, no fees:
| Date | Fill |
|---|---|
| Jan 2 | Buy 10 at $100 |
| Jan 3 | Buy 10 at $120 |
| Jan 4 | Buy 10 at $90 |
| Jan 5 | Sell 10 at $110 |
The Jan 5 sale closes a different lot under each method:
| Method | Lot closed | Cost | Proceeds | Realized |
|---|---|---|---|---|
| FIFO | Jan 2, 10 at $100 | $1,000.00 | $1,100.00 | +$100.00 |
| LIFO | Jan 4, 10 at $90 | $900.00 | $1,100.00 | +$200.00 |
| HIFO | Jan 3, 10 at $120 | $1,200.00 | $1,100.00 | -$100.00 |
If you then sell the remaining 20 shares at $110, every method arrives at the same total for the full trip: $3,300.00 of proceeds against $3,100.00 of cost, or +$200.00. The method moves P/L between periods; it does not change what a closed trip earned.
Fees#
Fees are spread per share across the fill that paid them.
- Buy fees become part of the lot's cost. A lot remembers its opening fee per share, so a partial close carries only its share of the fee.
- Sell fees are subtracted from the proceeds of the shares they closed.
Example: buy 30 shares at $10.00 with a $1.00 fee, then sell 10 at $12.00 with a $1.00 fee.
- Cost of the 10 shares closed: 10 x $10.00 + 10 x ($1.00 / 30) = $100.333..., rounded to $100.33.
- Proceeds: 10 x $12.00 - $1.00 = $119.00.
- Realized: $119.00 - $100.33 = +$18.67.
The remaining 20 shares stay open with a cost of $200.67, including their two-thirds of the original fee.
Rounding#
For every lot match, cost and proceeds are each rounded to cents, half away from zero, and realized is the difference of the two rounded values. That mirrors how a broker statement settles each lot. Totals across matches are summed in integer cents, so they never drift. Quantities keep up to 6 decimals for fractional shares. See Canonical numbers.
Round trips#
A round trip starts when a position leaves zero and ends when it returns to zero, per account and security. Partial sells and add-on buys in between belong to the same round trip.
Each round trip records its direction, open and close dates, status (OPEN or CLOSED), the fills involved, the shares bought and sold, the largest position held, the share-weighted average entry and exit prices (excluding fees), realized P/L, fees, and holding days.
- Holding days are calendar days from the opening fill to the fill that returned the position to zero, rounded to whole days.
- Win rate is the share of closed round trips with realized P/L above zero.
- Average holding days is the mean over closed round trips.
Reversals#
A fill that crosses zero closes one round trip and opens the next. Example: long 10 at $100, then sell 15 at $110.
- The first 10 shares close the long: +$100.00. The long round trip is CLOSED.
- The remaining 5 shares were never bought in the record, so they are a legacy sale with unknown basis (see below): $550.00 of proceeds, excluded from realized P/L. Nothing is opened.
- A later buy of 5 at $100 opens a new long round trip.
Only a sale marked as a short (Sell Short or Sell to Open) opens a short.
Shorts#
A sell that opens a position (Sell Short or Sell to Open) creates a short lot. A buy that closes it (Buy to Cover or Buy to Close) is matched against it with the same lot method.
- Short proceeds are the short sale price times shares, minus the opening fee.
- Short cost is the cover price times shares, plus the closing fee.
A plain sell that exceeds the long shares Investi knows about never opens a short. It is a legacy sale; see the next section.
Legacy sales, basis unknown#
Your imported history starts on some date. Shares you bought before it and sold after it have no purchase in the record, so Investi cannot know their cost. A plain sell (not Sell Short) beyond the long shares on record is therefore a legacy sale, basis unknown:
- It adds nothing to realized P/L: not a gain at zero cost, and not a loss.
- It opens nothing. Earlier versions treated it as a short sale, which marked the shares against every later price and could swing the cumulative P/L by hundreds of thousands of dollars.
- Its proceeds are reported, net of fees, as legacy sales, basis unknown on Home, Analytics, Portfolio, History and in the MCP, with the symbols and dates.
- The cumulative P/L curve and the dollar-matched benchmarks leave those shares out, and the data checks list each one as a legacy sale.
Example: history starts on November 10, 2023 with the sale of 1,000 META at $327.37, bought before 2022. Later, buy 100 META at $600 and sell them at $650. Realized is +$5,000.00 from the round trip with a known basis; the $327,370.00 of 2023 proceeds is listed as a legacy sale and counted nowhere else.
The weekly review counts such sells in unknownBasisSells and totals their proceeds in unknownBasisProceeds.
Splits#
Splits are corporate actions applied to open lots in date order. A split of ratio r multiplies each open lot's shares by r and divides its price and fee per share by r, so total cost is unchanged. A split dated the same day as a fill is applied before that fill.
Example: buy 10 at $100, a 2-for-1 split, then sell 20 at $55. The lot becomes 20 shares at $50, and the sale realizes 20 x ($55 - $50) = +$100.00 with the round trip closed.
Split ratios come from your imports; see Import your history.
Unknown basis#
Shares transferred in from another broker usually arrive without a cost basis. Investi records them as a buy at price 0 marked unknown basis, rather than inventing a number.
- Lot matches against unknown-basis lots never count toward verified realized P/L.
- Their proceeds are reported separately as unknown-basis proceeds.
- The round trip is flagged as containing unknown basis, and the History page shows "Partial basis" in place of its realized P/L.
Example (FIFO): 10 shares transferred in with unknown basis, then buy 10 at $100, then sell 20 at $120. The first 10 sold match the transferred lot: $1,200.00 goes to unknown-basis proceeds. The next 10 match the $100 lot: verified realized is +$200.00.
Fills with zero or negative quantity are skipped with a warning.
Realized by method#
History shows realized P/L per calendar year (the sale's New York date) three ways, so you can match the figure on your 1099-B:
| Column | Method |
|---|---|
| FIFO | First in, first out lot matching. |
| Average cost | Every share of a symbol in one account at its running average cost; buy fees capitalized, sell fees netted. This is also what the weekly review uses. |
| Your setting | The lot method chosen in Settings, when it is LIFO or HIFO. |
| Schwab's method | The broker's own lot method when an import reports it. Schwab's transactions and positions exports do not, so the column says so; compare against your 1099-B or Schwab's Realized Gain/Loss page. |
Legacy sales are excluded from every column and their proceeds shown per year. The same table is in evaluate_strategy_health as realizedByMethod.
Where you see it#
- History lists round trips and fills with the lot method in use.
- Portfolio shows open lots and positions; see Portfolio.
- Research shows your trading history with the security inside the company report.
GET /api/v1/round-tripsreturns round trips with the method and the definition text. See the REST API.