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.
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.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.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
returnsand native-basisreturns_nativegenuinely 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_strategyincludes aDirect Holdingsentry flagged_is_unattributed. - Suppressed risk metrics.
sharpe,sortinoandbetacome backnull, 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
returnsandreturns_native. - Risk ratios:
sharpeandsortinogo negative on a losing period.vol,max_dd, anddownsideare reported as positive decimals. - Holdings: a net-negative position (borrowing exceeding collateral) is kept deliberately, so
value_usd,value_reporting, and bothunrealized_pnlfields 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[].pctcan 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_pctorperf_pctproduces negative fee amounts throughout, and manual adjustments can push totals negative.
Null means “no data”, and sometimes the key is missing entirely
- A
nullreturn or risk value means the window is not covered by available history. A strategy whose data starts in August 2025 reportsnullfor"1y"and"3y". Render a dash, never a zero. risk.sharpeandrisk.sortinoarenullbelow 30 observations or under 1% annualized volatility. A stablecoin yield product otherwise produces mathematically correct but meaningless double-digit ratios.risk.betais alwaysnulluntil a benchmark feed is wired.allocation_pctisnullwhen fund AUM is zero;unrealized_pnl_pctisnullwhen cost basis is zero;holdings[].strategyisnullfor a plain spot position (render it as “Spot”).- When there is no usable timeseries at all,
returnsandriskcome back as empty objects{}, not as objects full of nulls. Guard key access rather than assuming the keys exist. recapisnullwhen there is no usable period anchor.
Returns are flow-neutral and time-weighted
Deposits and withdrawals are not performance. Every figure inreturns, 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.
Units and currency
- Every monetary figure comes in two units.
*_usdis the raw ledger value,*_reportingis that value in the report’s reporting currency, and*_nativeis 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_endfor the headline and for every fee conversion. The one exception isfees_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
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.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[] 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
hwmis true, thePnLterm 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_notesare 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.allocation_by_strategy[].pctis 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 aDirect Holdingsslice flagged_is_unattributed, and only when the gap exceeds 0.5% of AUM. Style that slice neutrally; it is not a real strategy.pctisnullwhen fund AUM is zero.allocation_by_asset[].pctis 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.strategyis 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, andnullfor plain spot positions.is_syntheticandvault_protocolappear on rows derived from a DeFi position rather than a wallet balance.- PnL on this table is lifetime, not period-bounded.
nav_series[]
Fund-level daily NAV, trimmed to the requested window.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
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, ornull 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.
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.
