Skip to main content

Computed Metrics

/computed returns the fees, NAV, returns, risk, holdings, and allocation figures that get baked into the rendered statement, as a JSON document instead of a paginated PDF. The statement and this endpoint share one implementation, so a dashboard built on /computed and a rendered PDF for the same period will agree number for number.
Read access is sufficient for both. POST exists only so you can overlay unsaved configuration before computing; it writes nothing.

Query parameters

All optional. Anything omitted falls back to the period saved on the report’s configuration.
string
YYYY-MM-DD. Start of the reporting window.
string
YYYY-MM-DD. End of the window. Also the as-of date for NAV, FX, and risk metrics.
string
Free-form display label, for example Q1 2026. Cosmetic; it does not affect any number.
Supply both bounds. With no period at all, the monthly fee formula falls back to a flat AUM x rate / 100 / 12 per month instead of the day-prorated calculation, so the numbers will not match a statement rendered with a period.

POST body

object
A patch in the same shape PUT /config accepts, applied in memory before computing. Use it to answer “what would the numbers look like at 2/20 instead of 2/15” without saving. Nothing is persisted.
A truthy non-object config_overrides returns 422. Falsy values (null, "", 0) are treated as “no overrides”. The values inside the object are not validated: a fee sent as a string returns 500, not 422.

Sample payload

The demo endpoint is rolling out. If it answers 404 on your environment, it has not reached it yet.
Returns a complete, realistic response for a synthetic fund. Use it to build and test your dashboard before your own portfolio has finished reconciling, which on a first wallet import can take anywhere from a few minutes to half an hour.
It accepts the same optional period_start, period_end and period parameters, needs no report, reads no portfolio, and carries "demo": true at the top level so a response can never be mistaken for live data. The sample runs the same computation the live endpoint runs, so its shape is guaranteed to match. It is also deliberately awkward, because the cases that break dashboards are the ones a tidy sample leaves out:
  • A losing month. January is down, so you will see negative PnL and a drawdown rather than an all-green fixture.
  • Two denominations. One USD pocket and one BTC pocket, where the BTC pocket’s USD-basis returns and native-basis returns_native genuinely diverge (about 11% against 2% inception-to-date). If your UI shows one number for both, this is where it shows.
  • An unattributed slice. Part of the fund sits outside the configured strategies, so allocation_by_strategy includes a Direct Holdings entry flagged _is_unattributed.
  • Suppressed risk metrics. sharpe, sortino and beta come back null, which is the normal case for a short track record, not an error.

Conventions

Read these before wiring anything up. Every one of them has burned someone.

Percentages: whole numbers going in, decimals coming out

The dividing line is configured rate versus computed result. Getting it backwards is a 100x error in either direction.

Negative values are normal, not an error state

Your dashboard must render these as negatives rather than clamping, hiding, or treating them as bad data:
  • PnL, everywhere: fund.pnl.*, strategies[].period_pnl.usd / reporting / pct, monthly[].pnl_native / pnl_usd.
  • All returns: every key in returns and returns_native.
  • Risk ratios: sharpe and sortino go negative on a losing period. vol, max_dd, and downside are reported as positive decimals.
  • Holdings: a net-negative position (borrowing exceeding collateral) is kept deliberately, so value_usd, value_reporting, and both unrealized_pnl fields can all be negative. Dropping them would break reconciliation against NAV.
  • Allocations: because a negative holding can sit in the denominator set, allocation_by_asset[].pct can be negative and the remaining slices can sum above 100%. Handle this before feeding a donut chart.
  • Fees: monthly performance fees are floored at zero against their base, but a negative configured mgmt_pct or perf_pct produces negative fee amounts throughout, and manual adjustments can push totals negative.

Null means “no data”, and sometimes the key is missing entirely

  • A null return or risk value means the window is not covered by available history. A strategy whose data starts in August 2025 reports null for "1y" and "3y". Render a dash, never a zero.
  • risk.sharpe and risk.sortino are null below 30 observations or under 1% annualized volatility. A stablecoin yield product otherwise produces mathematically correct but meaningless double-digit ratios.
  • risk.beta is always null until a benchmark feed is wired.
  • allocation_pct is null when fund AUM is zero; unrealized_pnl_pct is null when cost basis is zero; holdings[].strategy is null for a plain spot position (render it as “Spot”).
  • When there is no usable timeseries at all, returns and risk come back as empty objects {}, not as objects full of nulls. Guard key access rather than assuming the keys exist.
  • recap is null when there is no usable period anchor.

Returns are flow-neutral and time-weighted

Deposits and withdrawals are not performance. Every figure in returns, returns_native and period_pnl.pct is computed so that external flows move the capital base without ever registering as a return:
  • Share-token strategies measure price per share, which is flow-neutral by construction.
  • Portfolio-level strategies chain-link each step’s gross-NAV delta over the capital at work during that step. Profit earned after a deposit is credited against the larger base, not against the opening one.
The practical consequence: a fund that opens at 10,000, receives a 70,000 wire and ends at 81,000 having earned 500 reports roughly +0.6%, not +709%. If you are comparing against figures pulled before 12 August 2026, expect them to have moved for exactly this reason.

Units and currency

  • Every monetary figure comes in two units. *_usd is the raw ledger value, *_reporting is that value in the report’s reporting currency, and *_native is in the strategy’s own denomination (BTC for a BTC vault).
  • NAV is as of period_end, not live. Requesting a historical quarter returns the AUM that was on the books at that quarter’s close.
  • FX is resolved at period_end for the headline and for every fee conversion. The one exception is fees_summary.fund_nav_by_month_reporting, which converts at each month’s own close. Reconciling a monthly fee cell against the monthly NAV cell on a non-USD report will therefore show a small FX-date difference.

Response

Top-level returns and risk are the fund-level figures. Per-strategy figures live inside each entry of strategies[].

display_options

Formatting preferences set on the report. Mirror them so your dashboard renders numbers the way the statement does.
  • round_fiat (boolean): when true, headline fiat values (NAV, period PnL, total AUM, the fee grand total) are shown with no decimals.
  • crypto_decimals (integer): precision for crypto-native fee and AUM cells. Minimum 2, no maximum.

fund

inception_date is the configured date when set, otherwise the first date the ledger shows a non-zero NAV. fund.pnl is lifetime (realized plus unrealized), not period-bounded.

period

months is the list of month keys the fee tables and monthly buckets are keyed by. An invalid or reversed date pair yields an empty months and empties every month-keyed block downstream, without raising an error.

fees_defaults

Fund-level fee rates, before per-strategy overrides. Whole-number percentages.

strategies[]

One entry per reporting strategy.
strategies[] is capped at four entries. With five or more configured strategies, the three largest by NAV appear verbatim and the rest are folded into a synthetic Other Strategies (N) entry flagged _is_other_rollup, with denomination USD, zero fee rates, and fee_terms: "various". It carries NAV for the donut but has no cards, risk metrics, or fee blocks.A fund with no configured strategies gets a single synthesized entry covering the whole book, flagged _is_portfolio_fallback.Check both flags before treating an entry as a real strategy.
nav.native vs nav.reporting. For a non-fiat strategy, native is the real underlying quantity (a BTC count), not a USD equivalent. Display it with native_ccy. For fiat-denominated strategies the two are the same value in different currencies. returns vs returns_native. returns is denominated in the reporting currency, so it includes movement in the underlying asset’s price. returns_native is in the strategy’s own units, which for a BTC vault is the pure protocol yield with BTC price movement stripped out. An LP who funded in BTC wants returns_native; a fund-level roll-up wants returns. For fiat and stablecoin strategies the two are equivalent. fees.mgmt_source / perf_source report which rate won: strategy_override or portfolio_default. Use it to show an “inherited” badge.
monthly[].fx_to_reporting converts native units to the reporting currency, not USD to the reporting currency. For a BTC strategy it is the EUR-per-BTC price at that month’s close, a number in the tens of thousands. Multiplying aum_usd by it inflates the figure by around five orders of magnitude. You never need to: aum_reporting, mgmt_reporting, perf_reporting, and total_reporting are all published directly.
monthly[] fee formula, matching the statement exactly:
AUM is the month’s own closing NAV under current_eom, or the prior month’s close under prior_eom. Months with no data are omitted rather than filled with zeros. Two modifiers apply on top:
  • When hwm is true, the PnL term is replaced by the month’s gain above the prior cumulative peak, so recovery months bill no performance fee.
  • Amounts declared in the report’s recap.deposit_notes are netted out of that month’s PnL first, so performance fees are not charged on deposits.
risk. Step returns are log returns, annualized against the actual observation cadence rather than assuming daily. See the null semantics above for when values are suppressed. _source reports where the strategy’s numbers came from: vault_ts, holdings_value, ledger_fallback, portfolio_fallback, or empty. Diagnostic; useful when a strategy reads lower than expected.

allocation_by_strategy[] and allocation_by_asset[]

Chart-ready arrays for the two donuts.
The two arrays use different denominators and will not always agree.
  • allocation_by_strategy[].pct is measured against fund AUM, so the donut reconciles with the headline AUM figure. Entries with non-positive NAV are excluded. A strategy backed by a position group contributes the group’s NAV, summed from its legs. Any AUM not covered by a configured strategy is surfaced as a Direct Holdings slice flagged _is_unattributed, and only when the gap exceeds 0.5% of AUM. Style that slice neutrally; it is not a real strategy. pct is null when fund AUM is zero.
  • allocation_by_asset[].pct is measured against the sum of holdings values, and negative holdings are included in both numerator and denominator. A slice can be negative and the rest can sum above 100%.

holdings[]

Per-asset rollup, largest position first.
  • strategy is a label, not a reference: the configured strategy name when the holding is that strategy’s share token (matched by symbol, pinned contract, or share-token alias), the protocol tag for synthetic DeFi rows, and null for plain spot positions.
  • is_synthetic and vault_protocol appear on rows derived from a DeFi position rather than a wallet balance.
  • PnL on this table is lifetime, not period-bounded.
Fund-level daily NAV, trimmed to the requested window.
No anchor point from before the window is carried in. For the headline figure use fund.total_aum, which is looked up as of period_end separately.

earn_income

Cumulative earn and reward accruals.

returns and risk (fund level)

Same shape as their per-strategy counterparts, computed on the fund’s NAV series. When the portfolio has no timeseries at all, returns degrades to { "itd": <lifetime PnL over cost basis> } so a dashboard still has something to show.

fees_summary

total_native and by_month_native sum native units across strategies. When a fund runs strategies of different denominations (a BTC vault plus a USD vault), those totals add BTC to dollars and are not meaningful. Use the _reporting figures.
fund_nav_by_month_* comes straight from the reconciled portfolio ledger and is the verification column: it is the NAV source of truth, never recomputed by the report. It is also the only block converted at each month’s own FX close.

recap

The rolling recap block, or null when there is no usable period anchor. By default its columns are the months of the requested reporting period. A year-start or inception window applies only when the caller supplies no period, or when the report configures recap.window_basis or an explicit month selection. Auto column plans are capped at four columns; an explicit month picker allows up to thirteen, chunked into stacked tables of four. Each column carries per-strategy NAV and flow-adjusted PnL in native and reporting units. The statement consumes this block verbatim. Unless you are replicating the recap tables, you can ignore it.

adjustments

Echoes the report’s manual adjustment configuration, always present ({} when none). Adjustments are signed deltas in the reporting currency that a manager applies on top of computed figures.
Adjustments are applied to totals only. fund.total_aum, fund.pnl.total_*, per-strategy nav and period_total, and fees_summary.total_* already include them. The monthly rows (strategies[].monthly[], fees_summary.by_month_*) and fund.pnl.realized_* / unrealized_* are not adjusted. After an adjustment, monthly buckets no longer sum to the period totals and realized plus unrealized no longer equals total. Do not build a reconciliation check that assumes they do.
Percentage figures (returns, risk metrics) are deliberately left unadjusted: an adjustment nudges absolutes, it does not redefine the track record. When any adjustment is non-zero the block also carries an applied array of {scope, slot, amount, note} entries, ready to render as a disclosure card. Never apply the deltas yourself.

Errors

An invalid or reversed period_start / period_end pair does not error. period.months comes back empty, monthly[] and fees_summary.by_month_* are empty, recap is null, and headline NAV still resolves at period_end.