Learn / track portfolio pnl
Track profit, not deposits
A sample ledger holds one equity position. Change the closing mark or add a deposit: new cash changes account value, but it is not investment profit. USD throughout.
Try it
Sample dataA sample ledger holds one equity position. Change the closing mark or add a deposit: new cash changes account value, but it is not investment profit. USD throughout.
Account value (includes deposits)Net asset value · USD
Exact chart data
| Series | Days since February 1, 2026 | Net asset value · USD |
|---|---|---|
| Account value (includes deposits) | 0 | 100000 |
| Account value (includes deposits) | 7 | 100375 |
| Account value (includes deposits) | 14 | 100750 |
| Account value (includes deposits) | 21 | 106125 |
| Account value (includes deposits) | 28 | 106500 |
Assumptions & conventions
{
"pnl": {
"conventionsVersion": "0.0.1",
"lotRelief": "fifo",
"identity": "ending NAV − beginning NAV − external flows = realized P&L + unrealized P&L + income − transaction costs − financing + foreign-exchange P&L + residual. Beginning and ending NAV are portfolioSnapshot at the two marks (each mark folds every event strictly before 00:00 UTC of its date); external flows are cash.deposit minus cash.withdrawal over the window; realized, income, costs, and financing are the differences of the fold accumulators; unrealized P&L is the difference of open-lot mark-to-market; the residual is what the components leave unexplained and is float rounding by construction.",
"conversionConvention": "Every local-currency component over the window converts to base at the CLOSING mark’s quote. Foreign-exchange P&L = Σ over non-base currencies of opening local value × (closing quote − opening quote) [translation] + Σ over cash.conversion events of (amount received × closing quote of the received currency − amount given × closing quote of the given currency). Note that portfolioPerformanceInputs converts external flows at the FLOW-DATE mark for FC4; this report converts them at the closing mark so the identity holds exactly.",
"residualTolerance": 0.0001065
},
"timeline": {
"conventionsVersion": "0.0.1",
"lotRelief": "fifo",
"markInstant": "A mark dated D values the fold of every event with effectiveTimestampMs strictly before 00:00 UTC of D — the mark lands BEFORE any external flow dated D, exactly FC4’s flow convention; external flows since the prior mark convert at THIS mark’s quotes (the P&L identity convention), while the FC4 series converts each flow at its own flow-date mark.",
"indexConvention": "indexValue is @insiderfinance/totalfinance/performance portfolioReturnIndex over the FC4 series (base value 1, geometric linking, flows at-flow-timestamp); drawdown is underwater() over that index — a flow-adjusted decline, so a withdrawal is never mistaken for a loss. One missing mark withholds the whole index with a reason rather than restarting it.",
"exposureConvention": "Exposure is position NOTIONAL value in base currency at the mark (quantity × mark × multiplier — a variation-margin position’s full notional, not its unsettled P&L): long = Σ positive values, short = Σ |negative values|, gross = long + short, net = long − short; cash is not exposure. exposureByGroup partitions the same positions by the caller’s classification (tags overlap and do not partition)."
},
"sample": "Linear hypothetical price path. No trading, fees, dividends, or FX during the measurement window."
}Diagnostics & warnings
{
"pnl": {
"warnings": [],
"eventCount": 1,
"residualWithinTolerance": true
},
"timeline": {
"warnings": [
"portfolioPerformanceInputs: 1 external flow(s) fall outside the [2026-02-01, 2026-03-01] mark window and are excluded from the series — widen the marks to include them."
],
"markCount": 5,
"gaps": []
}
}Full result · JSON
{
"pnl": {
"baseCurrency": "USD",
"from": {
"valuationDate": "2026-02-01",
"asOf": 1769904000000,
"netAssetValue": 100000
},
"to": {
"valuationDate": "2026-03-01",
"asOf": 1772323200000,
"netAssetValue": 106500
},
"netAssetValueChange": 6500,
"externalFlows": 5000,
"investmentReturn": 1500,
"components": {
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500
},
"residual": 0,
"byCurrency": [
{
"currency": "USD",
"quoteAtStart": 1,
"quoteAtEnd": 1,
"openingLocalValue": 100000,
"closingLocalValue": 106500,
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"externalFlows": 5000,
"conversionNet": 0,
"translationEffectBaseCurrency": 0
}
],
"groupings": [
{
"dimension": "position",
"rows": [
{
"label": "main/SAMPLE",
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500
}
],
"unattributed": {
"realizedPnl": 0,
"unrealizedPnl": 0,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 0
},
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "instrument",
"rows": [
{
"label": "SAMPLE",
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500
}
],
"unattributed": {
"realizedPnl": 0,
"unrealizedPnl": 0,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 0
},
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "account",
"rows": [
{
"label": "main",
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500
}
],
"unattributed": {
"realizedPnl": 0,
"unrealizedPnl": 0,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 0
},
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "currency",
"rows": [
{
"label": "USD",
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500
}
],
"unattributed": {
"realizedPnl": 0,
"unrealizedPnl": 0,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 0
},
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "underlying",
"rows": [
{
"label": "unclassified",
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500
}
],
"unattributed": {
"realizedPnl": 0,
"unrealizedPnl": 0,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 0
},
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "assetClass",
"rows": [
{
"label": "unclassified",
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500
}
],
"unattributed": {
"realizedPnl": 0,
"unrealizedPnl": 0,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 0
},
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "strategy",
"rows": [
{
"label": "unclassified",
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500
}
],
"unattributed": {
"realizedPnl": 0,
"unrealizedPnl": 0,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 0
},
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "tag",
"rows": [
{
"label": "unclassified",
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500
}
],
"unattributed": {
"realizedPnl": 0,
"unrealizedPnl": 0,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 0
},
"reconciliationResidual": 0,
"reconciles": false,
"reason": "tags overlap — an instrument may carry several, so tag rows double-count by design and do not partition the total"
}
],
"assumptions": {
"conventionsVersion": "0.0.1",
"lotRelief": "fifo",
"identity": "ending NAV − beginning NAV − external flows = realized P&L + unrealized P&L + income − transaction costs − financing + foreign-exchange P&L + residual. Beginning and ending NAV are portfolioSnapshot at the two marks (each mark folds every event strictly before 00:00 UTC of its date); external flows are cash.deposit minus cash.withdrawal over the window; realized, income, costs, and financing are the differences of the fold accumulators; unrealized P&L is the difference of open-lot mark-to-market; the residual is what the components leave unexplained and is float rounding by construction.",
"conversionConvention": "Every local-currency component over the window converts to base at the CLOSING mark’s quote. Foreign-exchange P&L = Σ over non-base currencies of opening local value × (closing quote − opening quote) [translation] + Σ over cash.conversion events of (amount received × closing quote of the received currency − amount given × closing quote of the given currency). Note that portfolioPerformanceInputs converts external flows at the FLOW-DATE mark for FC4; this report converts them at the closing mark so the identity holds exactly.",
"residualTolerance": 0.0001065
},
"diagnostics": {
"warnings": [],
"eventCount": 1,
"residualWithinTolerance": true
}
},
"timeline": {
"baseCurrency": "USD",
"rows": [
{
"valuationDate": "2026-02-01",
"asOf": 1769904000000,
"netAssetValue": 100000,
"cashBaseCurrencyValue": 85000,
"positionsBaseCurrencyValue": 15000,
"exposure": {
"long": 15000,
"short": 0,
"gross": 15000,
"net": 15000,
"grossLeverage": 0.15
},
"exposureByGroup": [
{
"dimension": "position",
"rows": [
{
"label": "main/SAMPLE",
"baseCurrencyNotionalValue": 15000
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "instrument",
"rows": [
{
"label": "SAMPLE",
"baseCurrencyNotionalValue": 15000
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "account",
"rows": [
{
"label": "main",
"baseCurrencyNotionalValue": 15000
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "currency",
"rows": [
{
"label": "USD",
"baseCurrencyNotionalValue": 15000
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "underlying",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15000
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "assetClass",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15000
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "strategy",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15000
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "tag",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15000
}
],
"reconciliationResidual": 0,
"reconciles": false,
"reason": "tags overlap — an instrument may carry several, so tag rows double-count by design and do not partition the total"
}
],
"externalFlowsSincePrior": 0,
"pnlSincePrior": null,
"indexValue": 1,
"drawdown": 0
},
{
"valuationDate": "2026-02-08",
"asOf": 1770508800000,
"netAssetValue": 100375,
"cashBaseCurrencyValue": 85000,
"positionsBaseCurrencyValue": 15375,
"exposure": {
"long": 15375,
"short": 0,
"gross": 15375,
"net": 15375,
"grossLeverage": 0.15317559153175592
},
"exposureByGroup": [
{
"dimension": "position",
"rows": [
{
"label": "main/SAMPLE",
"baseCurrencyNotionalValue": 15375
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "instrument",
"rows": [
{
"label": "SAMPLE",
"baseCurrencyNotionalValue": 15375
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "account",
"rows": [
{
"label": "main",
"baseCurrencyNotionalValue": 15375
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "currency",
"rows": [
{
"label": "USD",
"baseCurrencyNotionalValue": 15375
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "underlying",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15375
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "assetClass",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15375
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "strategy",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15375
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "tag",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15375
}
],
"reconciliationResidual": 0,
"reconciles": false,
"reason": "tags overlap — an instrument may carry several, so tag rows double-count by design and do not partition the total"
}
],
"externalFlowsSincePrior": 0,
"pnlSincePrior": {
"realizedPnl": 0,
"unrealizedPnl": 375,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 375,
"residual": 0
},
"indexValue": 1.00375,
"drawdown": 0
},
{
"valuationDate": "2026-02-15",
"asOf": 1771113600000,
"netAssetValue": 100750,
"cashBaseCurrencyValue": 85000,
"positionsBaseCurrencyValue": 15750,
"exposure": {
"long": 15750,
"short": 0,
"gross": 15750,
"net": 15750,
"grossLeverage": 0.15632754342431762
},
"exposureByGroup": [
{
"dimension": "position",
"rows": [
{
"label": "main/SAMPLE",
"baseCurrencyNotionalValue": 15750
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "instrument",
"rows": [
{
"label": "SAMPLE",
"baseCurrencyNotionalValue": 15750
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "account",
"rows": [
{
"label": "main",
"baseCurrencyNotionalValue": 15750
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "currency",
"rows": [
{
"label": "USD",
"baseCurrencyNotionalValue": 15750
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "underlying",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15750
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "assetClass",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15750
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "strategy",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15750
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "tag",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 15750
}
],
"reconciliationResidual": 0,
"reconciles": false,
"reason": "tags overlap — an instrument may carry several, so tag rows double-count by design and do not partition the total"
}
],
"externalFlowsSincePrior": 0,
"pnlSincePrior": {
"realizedPnl": 0,
"unrealizedPnl": 375,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 375,
"residual": 0
},
"indexValue": 1.0074999999999998,
"drawdown": 0
},
{
"valuationDate": "2026-02-22",
"asOf": 1771718400000,
"netAssetValue": 106125,
"cashBaseCurrencyValue": 90000,
"positionsBaseCurrencyValue": 16125,
"exposure": {
"long": 16125,
"short": 0,
"gross": 16125,
"net": 16125,
"grossLeverage": 0.1519434628975265
},
"exposureByGroup": [
{
"dimension": "position",
"rows": [
{
"label": "main/SAMPLE",
"baseCurrencyNotionalValue": 16125
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "instrument",
"rows": [
{
"label": "SAMPLE",
"baseCurrencyNotionalValue": 16125
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "account",
"rows": [
{
"label": "main",
"baseCurrencyNotionalValue": 16125
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "currency",
"rows": [
{
"label": "USD",
"baseCurrencyNotionalValue": 16125
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "underlying",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 16125
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "assetClass",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 16125
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "strategy",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 16125
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "tag",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 16125
}
],
"reconciliationResidual": 0,
"reconciles": false,
"reason": "tags overlap — an instrument may carry several, so tag rows double-count by design and do not partition the total"
}
],
"externalFlowsSincePrior": 5000,
"pnlSincePrior": {
"realizedPnl": 0,
"unrealizedPnl": 375,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 375,
"residual": 0
},
"indexValue": 1.0110726950354607,
"drawdown": 0
},
{
"valuationDate": "2026-03-01",
"asOf": 1772323200000,
"netAssetValue": 106500,
"cashBaseCurrencyValue": 90000,
"positionsBaseCurrencyValue": 16500,
"exposure": {
"long": 16500,
"short": 0,
"gross": 16500,
"net": 16500,
"grossLeverage": 0.15492957746478872
},
"exposureByGroup": [
{
"dimension": "position",
"rows": [
{
"label": "main/SAMPLE",
"baseCurrencyNotionalValue": 16500
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "instrument",
"rows": [
{
"label": "SAMPLE",
"baseCurrencyNotionalValue": 16500
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "account",
"rows": [
{
"label": "main",
"baseCurrencyNotionalValue": 16500
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "currency",
"rows": [
{
"label": "USD",
"baseCurrencyNotionalValue": 16500
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "underlying",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 16500
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "assetClass",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 16500
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "strategy",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 16500
}
],
"reconciliationResidual": 0,
"reconciles": true
},
{
"dimension": "tag",
"rows": [
{
"label": "unclassified",
"baseCurrencyNotionalValue": 16500
}
],
"reconciliationResidual": 0,
"reconciles": false,
"reason": "tags overlap — an instrument may carry several, so tag rows double-count by design and do not partition the total"
}
],
"externalFlowsSincePrior": 0,
"pnlSincePrior": {
"realizedPnl": 0,
"unrealizedPnl": 375,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 375,
"residual": 0
},
"indexValue": 1.0146453900709218,
"drawdown": 0
}
],
"valuations": [
{
"valuationDate": "2026-02-01",
"netAssetValue": 100000
},
{
"valuationDate": "2026-02-08",
"netAssetValue": 100375
},
{
"valuationDate": "2026-02-15",
"netAssetValue": 100750
},
{
"valuationDate": "2026-02-22",
"netAssetValue": 106125
},
{
"valuationDate": "2026-03-01",
"netAssetValue": 106500
}
],
"externalCashFlows": [
{
"cashFlowDate": "2026-02-15",
"amount": 5000,
"label": "additional-deposit",
"accountId": "main"
}
],
"cumulativePnl": {
"realizedPnl": 0,
"unrealizedPnl": 1500,
"income": 0,
"transactionCosts": 0,
"financing": 0,
"foreignExchangePnl": 0,
"totalPnl": 1500,
"residual": 0
},
"maxDrawdown": 0,
"assumptions": {
"conventionsVersion": "0.0.1",
"lotRelief": "fifo",
"markInstant": "A mark dated D values the fold of every event with effectiveTimestampMs strictly before 00:00 UTC of D — the mark lands BEFORE any external flow dated D, exactly FC4’s flow convention; external flows since the prior mark convert at THIS mark’s quotes (the P&L identity convention), while the FC4 series converts each flow at its own flow-date mark.",
"indexConvention": "indexValue is @insiderfinance/totalfinance/performance portfolioReturnIndex over the FC4 series (base value 1, geometric linking, flows at-flow-timestamp); drawdown is underwater() over that index — a flow-adjusted decline, so a withdrawal is never mistaken for a loss. One missing mark withholds the whole index with a reason rather than restarting it.",
"exposureConvention": "Exposure is position NOTIONAL value in base currency at the mark (quantity × mark × multiplier — a variation-margin position’s full notional, not its unsettled P&L): long = Σ positive values, short = Σ |negative values|, gross = long + short, net = long − short; cash is not exposure. exposureByGroup partitions the same positions by the caller’s classification (tags overlap and do not partition)."
},
"diagnostics": {
"warnings": [
"portfolioPerformanceInputs: 1 external flow(s) fall outside the [2026-02-01, 2026-03-01] mark window and are excluded from the series — widen the marks to include them."
],
"markCount": 5,
"gaps": []
}
}
}Use it in your project
portfolioPnl returns a report separating investment return from external cash flows. This example prints selected fields in USD. A timeline is optional: only the opening and closing marks are needed for this report.
Sample inputs
Sample inputs: a USD ledger with an opening deposit, a share purchase and an optional later deposit. The two market snapshots below are hypothetical marks, not a data connection. Copy setup + call includes the complete setup.
{
"quantity": 100,
"openingPrice": 150,
"closingPrice": 165,
"deposit": 5000
}Complete sample setup · included when copying
import { createPortfolioLedger, portfolioPnl, type PortfolioEventEnvelope } from '@insiderfinance/totalfinance/portfolio';
import { createMarketSnapshot } from '@insiderfinance/totalfinance/core/artifacts';
const events: PortfolioEventEnvelope[] = [
{
"schemaVersion": 1,
"sourceId": "sample",
"accountId": "main",
"provenance": {},
"eventId": "opening-deposit",
"eventType": "cash.deposit",
"effectiveTimestampMs": 1767225600000,
"recordedTimestampMs": 1767225600000,
"event": {
"eventType": "cash.deposit",
"amount": 100000,
"currency": "USD"
}
},
{
"schemaVersion": 1,
"sourceId": "sample",
"accountId": "main",
"provenance": {},
"eventId": "opening-fill",
"eventType": "trade.fill",
"effectiveTimestampMs": 1767312000000,
"recordedTimestampMs": 1767312000000,
"event": {
"eventType": "trade.fill",
"instrumentId": "SAMPLE",
"side": "buy",
"quantity": 100,
"pricePerUnit": 150,
"currency": "USD"
}
},
{
"schemaVersion": 1,
"sourceId": "sample",
"accountId": "main",
"provenance": {},
"eventId": "additional-deposit",
"eventType": "cash.deposit",
"effectiveTimestampMs": 1771113600000,
"recordedTimestampMs": 1771113600000,
"event": {
"eventType": "cash.deposit",
"amount": 5000,
"currency": "USD"
}
}
];
const ledger = createPortfolioLedger({ baseCurrency: 'USD', events });
const from = {
valuationDate: '2026-02-01',
market: createMarketSnapshot({
asOf: '2026-02-01',
observations: { spots: { SAMPLE: { price: 150, currency: 'USD' } } },
}),
};
const to = {
valuationDate: '2026-03-01',
market: createMarketSnapshot({
asOf: '2026-03-01',
observations: { spots: { SAMPLE: { price: 165, currency: 'USD' } } },
}),
};const pnl = portfolioPnl({ ledger, from, to });
const result = {
endingAccountValue: pnl.to.netAssetValue,
investmentReturn: pnl.investmentReturn,
externalFlows: pnl.externalFlows,
residual: pnl.residual,
};
console.log(result);
Output of this example
{
"endingAccountValue": 106500,
"investmentReturn": 1500,
"externalFlows": 5000,
"residual": 0
}
Reproduce this entire playground
This complete example reproduces every chart point and the full playground result. Sample generation and chart mapping are example setup, not required conversions of library outputs.
import { createPortfolioLedger, portfolioPnl, portfolioTimeline, type PortfolioEventEnvelope } from '@insiderfinance/totalfinance/portfolio';
import { createMarketSnapshot } from '@insiderfinance/totalfinance/core/artifacts';
const events: PortfolioEventEnvelope[] = [
{
"schemaVersion": 1,
"sourceId": "sample",
"accountId": "main",
"provenance": {},
"eventId": "opening-deposit",
"eventType": "cash.deposit",
"effectiveTimestampMs": 1767225600000,
"recordedTimestampMs": 1767225600000,
"event": {
"eventType": "cash.deposit",
"amount": 100000,
"currency": "USD"
}
},
{
"schemaVersion": 1,
"sourceId": "sample",
"accountId": "main",
"provenance": {},
"eventId": "opening-fill",
"eventType": "trade.fill",
"effectiveTimestampMs": 1767312000000,
"recordedTimestampMs": 1767312000000,
"event": {
"eventType": "trade.fill",
"instrumentId": "SAMPLE",
"side": "buy",
"quantity": 100,
"pricePerUnit": 150,
"currency": "USD"
}
},
{
"schemaVersion": 1,
"sourceId": "sample",
"accountId": "main",
"provenance": {},
"eventId": "additional-deposit",
"eventType": "cash.deposit",
"effectiveTimestampMs": 1771113600000,
"recordedTimestampMs": 1771113600000,
"event": {
"eventType": "cash.deposit",
"amount": 5000,
"currency": "USD"
}
}
];
const ledger = createPortfolioLedger({ baseCurrency: 'USD', events });
const marks = [
{
"valuationDate": "2026-02-01",
"price": 150
},
{
"valuationDate": "2026-02-08",
"price": 153.75
},
{
"valuationDate": "2026-02-15",
"price": 157.5
},
{
"valuationDate": "2026-02-22",
"price": 161.25
},
{
"valuationDate": "2026-03-01",
"price": 165
}
];
const valuationMarks = marks.map(mark => ({
valuationDate: mark.valuationDate,
market: createMarketSnapshot({ asOf: mark.valuationDate, observations: { spots: { SAMPLE: { price: mark.price, currency: 'USD' } } } }),
}));
const result = {
pnl: portfolioPnl({ ledger, from: valuationMarks[0]!, to: valuationMarks.at(-1)! }),
timeline: portfolioTimeline({ ledger, valuationMarks }),
};
console.log(result);Both examples use the last successfully calculated inputs. The first call may show fewer scenarios or selected report fields, as labeled above. Displayed metrics are rounded; copied calculations retain exact values.
Continue with the full API reference or the API levels guide.