Skip to main content

Measuring success — the USDT objective

Specified; partly built (2026-09-05)

This page records the objective, accounting and benchmark suite the 2026-09-05 platform audit (§7) asks for before Nexus can claim it grows the portfolio. Where it stands the same evening: the Grafana "Nexus Treasury" dashboard and the nexus.financial alert rules are deployed (nexus-gitops 05c3ffd); the worker job that produces the NAV series, the collections, the read API and the benchmark engine are deployed since 2026-09-05 17:12 UTC (nexus-platform ac38aa1; UI /treasury in nexus-ui 13d3f2a); the series starts at the first snapshot (17:25 UTC, 91,138 USDT) — so the benchmark comparison has no history yet. Execution-cost capture on the signer side (fees, rent, realized quantities, costs_today) is live since 17:46 UTC (nexus-solana c483bbd / a74cc87, live-verified) — see Costs. There is still no forecast resolver (audit M3). P&L by source — the owner's "where are we winning money" ledger (JitoSOL, Kamino lending, swing round trips, market, costs, with an explicit unexplained residual) — is deployed (nexus-platform 3a91236 … 7d6ed59, worker rolled ≈ 20:3x UTC; first live rows at the 21:12 UTC snapshot); see P&L by source. The owner's independent re-verification (19:29–19:34 UTC) rates the whole of this as measurement implemented and live; validation immature — its qualifications are under Measurement qualifications. Section As built has the item-by-item status; the status register rows are §7 and O4.

Corrected 2026-09-06 — read this before any figure below

The first day of live P&L produced numbers that were wrong, not merely young, and they are fixed in nexus-platform 2553748 … 63a2de3 (deployed 2026-09-06, gitops 2c91751) with the exec side in nexus-solana 4804e26 (ea96739). What changed, and by how much:

DefectReadNowFix
A native-SOL sell's coin leg was booked as released rentcosts:tx +1,067.96, round trip +1,226.46costs:tx −0.0005, round trip +159.40exec 4804e26 stops emitting it; platform 2553748 refuses it three ways — Cost rules
A round trip was always derived (proceeds − cost − fees)sleeve realized all-time 1,628.48561.66the exec's own realized_pnl_usdc is taken verbatim when it read the chain — Profit source
1d / 7d / 30d windows resolved to from == to, 0 periods, delta 0"a flat 30 days"covered / truncated_to_series / series_starts_at — Window coverage72967bd
Lending interest sat beside balances it did not explain+2.96 interest against a +38.03 balance movebalance_delta = net_flows + interest + unattributed, contaminated periods named — Lending reconciliation72967bd / 4b5a181
An LP position's asset was the uppercased pool address with qty: 0exposure.SOL 18.71 %19.87 %one line per leg — LP as legs

The corrected figures after the 10:09:48Z snapshot (nav_1788689388951) and the whole of the remaining residual are under The 2026-09-06 correction. None of this changes NAV: the exposure and cap inputs were understated and the attribution was wrong, the portfolio value was not.

What the desk reports today, and what it is not​

The swing sleeve reported +402.30 USDC realized over 3 closed round trips at the audit snapshot (2026-09-05 ≈ 14:30 UTC). That number is:

  • a ledger figure — computed from the stored requested order amounts, not from realized token deltas and fees on-chain;
  • USDC-denominated, with no USDC/USDT conversion;
  • realized only — it excludes unrealized losses on held inventory (which the "never sell below basis" doctrine can accumulate), earning positions, capital flows and CEX holdings;
  • computed on a ledger that, at the snapshot, disagreed with the wallet (0.0521 cbBTC in the ledger vs ≈ 0.0000479 in EXEC, audit F5 — repaired 2026-09-05 16:20 UTC: the orphan sell was linked to its lot, the lot closed with 0.0001 cbBTC dust, and GET /v1/sleeve/reconcile now reports all_explained: true, unresolved: false; inventory is empty and committed_usdc = 10,537, the open buy escrow only).

It is therefore not a total-portfolio USDT return, and three profitable closes are not evidence of an edge. Treat it as an operational counter until the accounting below exists.

Objective (proposed by the audit; owner inputs still needed)​

Increase total portfolio NAV measured in USDT, net of execution, financing, infrastructure and AI costs, subject to explicit BTC/SOL exposure, drawdown, liquidity, custody and concentration limits; evaluate excess return versus frozen passive benchmarks.

The owner has to specify: investment horizon, maximum acceptable drawdown, minimum / maximum BTC and SOL exposure, stablecoin policy and minimum liquid reserve. Nothing here invents those numbers; the 20 % swing cap is a sleeve budget, not a portfolio loss limit.

USDT is the reporting unit; USDC is the implementation asset. Report in USDT while settling in USDC by applying real USDC/USDT and asset conversion rates — not by relabelling $ or assuming every stablecoin is worth 1. cbBTC is wrapped BTC exposure, not native BTC custody: track BTC market exposure separately from wrapper, custodian, liquidity and chain risk.

For every valuation time t:

NAV_USDT(t) = Σ (position quantity × executable or policy-approved USDT mark)
− liabilities − accrued costs

Include, once each: wallet assets (EXEC and VAULT), lending-receipt redemption value, JitoSOL value, open-order escrow, LP position value plus collectable fees, CEX assets, any borrow liabilities. Record price timestamp, source and a liquidity haircut per line. An unavailable balance is unknown, never zero.

Label capital in four buckets rather than "earning / sleeping": available, reserved for execution (order escrow — it earns nothing and may never fill), yield-bearing (lending, staking, LP), deployed risk.

Returns and attribution​

  • Period profit = ending NAV − starting NAV − net external inflows.
  • Time-weighted return (TWR) separates strategy performance from deposits and withdrawals; money-weighted return (MWR) is the owner's realized experience. Publish both, with realized and unrealized PnL together. As built the TWR is flow-timed since nexus-platform 8e1e0cb (in ee0cd80, deployed 2026-09-05, live-verified 23:4xZ) and every period carries a method label — TWR method labels.
  • Attribution, reported separately: trading decisions · passive BTC/SOL price movement · staking and lending yield · stablecoin basis · execution costs (fees, slippage, rent) · AI and infrastructure costs. The per-source ledger that implements this (deployed 2026-09-05, first live rows 21:12 UTC) is P&L by source; the coarse attribution buckets were a residual-based split until then — see Measurement qualifications.

Benchmark suite (five frozen counterfactuals)​

Run on the same cash flows, timestamps, fees and asset availability as the live book; parameters frozen in advance:

  1. Stablecoin cash with explicitly stated yield and risk assumptions.
  2. Fixed BTC/SOL buy-and-hold at an owner-chosen allocation.
  3. Fixed-interval BTC/SOL DCA.
  4. The passive configured HARVEST ladder, with realistic recall and slippage assumptions.
  5. The same deterministic system without AI timing decisions, to isolate the AI's contribution.

As built on 2026-09-05, benchmarks 1–4 are deployed (see below); benchmark 5 — the no-AI counterfactual — is not.

Track against each: net excess return, maximum drawdown, exposure-adjusted return, downside / tail loss, turnover, implementation shortfall, time underwater, idle and reserved capital, and compute cost per accepted decision. Evaluate decisions as of their original timestamps — no future information for entries, labels or benchmark parameters. Store rejected and no-trade decisions to measure selection effects and missed opportunities. Use walk-forward periods over several regimes and report uncertainty; promote strategy changes on preregistered evidence thresholds, not on explanations after profitable outcomes.

Forecasts: define a resolvable event and deadline, freeze the forecast, persist the subsequent observations, and score Brier / calibration against simple baselines. A model-generated confidence or a dream narrative is not a calibrated probability. (No resolver or scorer exists today; the live prompts' "Brier-scored" wording is unbacked — audit M3.)

Policy tensions the accounting must expose​

Current doctrineConsequence for USDT growthImprovement
Never sell below basisCan accumulate underwater inventory and hide losses from closed-trade statisticsSeparate strategic holdings from tactical risk; explicit drawdown and thesis-invalidation exits
Nothing idleEncourages protocol exposure even when marginal yield is smallTreat liquidity / optionality as a deliberate allocation with a risk budget
Order escrow is "earning"Reserved capital may generate no yield and may never fillLabel available, reserved, yield-bearing and deployed risk separately
Bottom forecast anchors deploymentStrong dependence on a specific price pathTest no-dip, shallow-dip and prolonged-downtrend alternatives
20 % swing capNot a total portfolio loss limitBound total correlated exposure and stress loss
Highest eligible APY rotationYield can be outweighed by costs, withdrawal friction and protocol riskRequire net benefit over the expected holding period plus risk constraints
Analyst agreementMultiple models may share evidence and anchoringMeasure marginal contribution, disagreement quality and forecast calibration
LP APR attractiveFees alone omit inventory drift and impermanent loss versus holdEvaluate total USDT return and excess return against holding the same assets

Scenario tests​

ScenarioWhat the system must demonstrate
BTC/SOL rise without reaching rungsQuantified opportunity cost; governed fallback; no silent strategy drift
Rapid crash through several rungsFIFO execution, available reserves, bounded slippage, no duplicates
Long bear market after swing buysHonest unrealized losses, exposure / drawdown control, no misleading win-rate claim
USDC or USDT moves off pegCorrect numeraire conversion, explicit stablecoin limits and recovery policy
cbBTC diverges from BTCWrapper / liquidity risk separated from BTC index exposure
Lending withdrawal unavailableLiquidity haircut and fallback; capital not counted as immediately spendable
JitoSOL exit spread widensSOL exposure remains correctly valued and liquidity-constrained
RPC confirms slowly or the process diesDurable signature recovery; no automatic duplicate send
LP exits during volatilityNet fees, inventory and slippage accounted against the hold benchmark
AI emits stale / malformed / adversarial outputNo financial authority without deterministic validation

As built (2026-09-05)​

Status words follow the register vocabulary: specified → in progress → built → deployed → live-verified. Everything below was worked on 2026-09-05 after the owner made §7 the priority (16:25 UTC).

Deployed — nexus-gitops 05c3ffd (2026-09-05 ≈ 16:27 UTC)​

ItemStatusWhat it is
Grafana dashboard Nexus Treasury (uid nexus-treasury, folder Nexus, 30 panels)deployedSections: NAV vs benchmarks, excess return, NAV components, exposure, attribution, flows & costs, marks, execution safety (signer / sleeve / ladder / outbox). Shipped as a sidecar ConfigMap next to Nexus Platform (files/dashboards/nexus-treasury.json).
PrometheusRule group nexus.financial (15 rules in nexus-platform-alerts)deployedNexusNavStale (snapshot > 3 h or absent), NexusNavIncomplete (unknown components for 2 h), NexusNavDrawdown 10 % warn / NexusNavDrawdownCritical 20 %, NexusNavDailyLoss 5 %, NexusStablecoinDepeg (|mark − 1| > 1 %), NexusMarkStale 30 min, NexusSignerUnreachable, NexusSignerPaused, NexusSignerAuthDenied, NexusSignerPolicyDenial (critical), NexusDailyCapNearlySpent 80 %, NexusSleeveUnresolved 30 min, NexusOutboxStuck 30 min (critical), NexusLadderRungFiringStuck 30 min. Thresholds are chart values monitoring.financial.{drawdownWarnFrac,drawdownCritFrac,dailyLossWarnFrac} (0.10 / 0.20 / 0.05).

Both consume the metric contract below. Until the worker image that exports it is deployed, every NAV panel is empty and NexusNavStale fires — that is the expected state, not a fault. The execution-safety rules that read the signer's existing status (nexus_exec_*, nexus_sleeve_*, nexus_outbox_*, nexus_ladder_*) also wait on the same exporter.

Deployed 2026-09-05 17:12 UTC — nexus-platform ac38aa1 (first snapshot 17:25 UTC: 91,138 USDT, 0 unknown components)​

Two things went wrong in the first hour and are recorded here because they shape what the series shows:

  • The first treasury-nav run was lost to a rollout race. The seeded job was fetched by the outgoing worker pod under the shared Apalis consumer name and sat in-flight; orphan reclaim never fires while the new pod's heartbeat keeps that name alive. It was re-enqueued by hand at 17:25 UTC (that is the first snapshot). Fixed by strategy: Recreate for the worker (nexus-gitops 441291a) and a per-pod consumer name nexus-jobs-<POD_NAME> (nexus-platform 9063b8d, gitops 078055c); runbook under Operations → Apalis queue.
  • The worker was unscrapeable 17:12–17:4x UTC. nexus_nav_component_usdt carried a label named component, which the nexus-observability registry already adds globally, so Prometheus rejected every worker scrape (label name "component" is not unique) — every NAV panel stayed empty and NexusNavStale kept firing although snapshots existed. The label is nav_component since nexus-platform 513a173 / d128e17 and dashboard gitops a5912c7; the rule is under Operations → Metric labels.
ItemStatusDetail
Worker job treasury_nav (hourly, NAV_INTERVAL_SECS)deployed ac38aa1Values every component, publishes a snapshot to accum_nav_snapshots: per-component valuation with known / unknown / stale; USDT numeraire via Coinbase USDT-USD / USDC-USD spot; TWR as a chain-linked index; high-water mark and drawdown; MWR / XIRR over 30 d and since inception; exposure SOL / BTC / STABLE; attribution market / yield / trading / costs.
accum_external_flowsdeployed ac38aa1Operator-recorded deposits and withdrawals (see below). Auto-detection from chain history is phase 2 — not implemented.
accum_benchmarks + accum_benchmark_configdeployed ac38aa1Frozen at inception: cash at an assumed APR; hold_btc_sol 50/50 at inception marks; dca_btc_sol weekly over 180 d; ladder_passive = the live rungs frozen at inception, firing at trigger with 0.3 % slippage. Every benchmark receives the same external flows as the live book.
accum_costsdeployed ac38aa1; signer-side capture live since 17:46 UTC (nexus-solana a74cc87)Transaction / priority / venue fees read from the signer's rows, plus an operator-set monthly infra + AI figure accrued pro-rata. What each row means and where it comes from: Costs. Ingestion is incomplete (route-only sends, infra / AI figure) — see Measurement qualifications.
Core read APIdeployed ac38aa1GET /v1/desk/nav, GET /v1/desk/nav/history?from&to&step, GET /v1/desk/nav/flows; POST /v1/desk/nav/flows is the one mutation and goes through the normal Core auth middleware.
Metric exporter (worker :9100)deployed ac38aa1The contract below, verbatim.

Inclusion map — each economic asset exactly once:

ComponentCounted as
EXEC walletSOL, USDC, USDT, JitoSOL, cbBTC
VAULT walletsame assets
Lending supplied, per protocolredemption value, optional liquidity haircut
Sleeve open-order escrowbuy escrow USDC + sell escrow coin; the ledger inventory sitting in that escrow is not added again
LP positionsboth legs + uncollected fees
CEX balances, per exchangeas reported; unavailable ⇒ unknown, never zero

NAV is published from the known components with unknown_components listed; NexusNavIncomplete alerts when that list stays non-empty.

Metric contract (what the dashboard and rules read):

MetricLabelsMeaning
nexus_nav_usdt—Total NAV in USDT (known components)
nexus_nav_usd—Same, in USD
nexus_nav_component_usdtnav_component, assetPer-line valuation (component is reserved by the registry)
nexus_nav_unknown_components—Count of components valued unknown
nexus_nav_snapshot_age_seconds—Age of the latest snapshot
nexus_nav_hwm_usdt—High-water mark
nexus_nav_drawdown_frac—Peak-to-now drawdown, net of flows
nexus_nav_return_fracwindow = 1d / 7d / 30d / inceptionTime-weighted return. Periods valued before ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ) treat flows as period-end; from that revision each period is flow-timed and labelled in meta.twr.method (TWR method labels, qualifications)
nexus_nav_mwr_fracwindow = 30d / inceptionMoney-weighted return (XIRR)
nexus_nav_exposure_fracasset = SOL / BTC / STABLEExposure share
nexus_nav_exposure_usdtassetExposure in USDT
nexus_nav_attribution_usdtbucket = market / yield / trading / costs, windowAttribution
nexus_nav_costs_accrued_usd—Costs accrued to date
nexus_nav_external_flow_usdtdirectionRecorded external flows
nexus_benchmark_nav_usdtbenchmarkBenchmark NAV on the same flows
nexus_benchmark_excess_return_fracbenchmark, windowLive minus benchmark
nexus_mark_usdassetMark used
nexus_mark_age_secondsassetAge of that mark
nexus_exec_up—Signer reachable
nexus_exec_armed—Signer armed
nexus_exec_paused—Runtime pause active
nexus_exec_auth_decisionsdecisionCaller-auth decisions
nexus_exec_policy_denialscodePolicy-gate denials
nexus_exec_policy_spent_today_usdc—Daily cap spent
nexus_exec_policy_max_daily_usdc—Daily cap
nexus_sleeve_unresolved—Ledger ≠ wallet flag
nexus_sleeve_committed_usdc—Sleeve committed capital
nexus_sleeve_realized_usdc—Sleeve realized PnL
nexus_sleeve_open_orderssideOpen sleeve orders
nexus_outbox_rowsstateLadder outbox rows
nexus_ladder_rungsstateLadder rungs

Costs — what accum_costs contains​

The costs:* P&L sources and nexus_nav_costs_accrued_usd are built from accum_costs. The rule (nexus-platform 8e1e0cb, in ee0cd80, deployed 2026-09-05 ≈ 23:4xZ; published verbatim as /v1/desk/nav.costs.rule): on-chain fees and rent are booked by signature for attribution only (costs:tx, costs:venue) — the wallets already paid them, so NAV never subtracts them again; costs_accrued_usd deducts off-portfolio costs only (infra, ai_tokens). Before that revision the same fields were parsed from sleeve rows only and route-only sends were never persisted.

Durable, keyed by signature. A row's _id is <kind>:<signature>:<field> and the insert is insert-once, so the same send reported by up to four feeds — a sleeve row (/v1/sleeve.recent_trades[]), an outbox fill (/v1/ladder.outbox.fired[]), the answer of the route the worker called (costs on /v1/lending/*, /v1/stake/*, /v1/swap/execute, /v1/squads/fund-exec; persisted at the 11 record_route_costs call sites in the worker's mob_jobs.rs since ee0cd80) and the day's aggregate (/v1/status.costs_today) — lands on the same rows. A fill's fee legs stay inside the sleeve round trip (sleeve trade fee note) and are excluded from costs:tx. costs_today carries no signatures, so it cannot be booked per send: it is persisted on the snapshot (meta.costs.exec_today) and reconciled against the signatures booked that day — unbooked_sends_today = sends the exec made that have no durable row (sends made outside the worker, or before a call site persisted its answer); the exec's costs_today.entries[] with signatures is a recorded follow-up. /v1/desk/nav.costs exposes {accrued_off_portfolio_usd, on_chain_usd, by_kind_usd, exec_today, rule}.

The signer reports every confirmed send in one vocabulary (nexus-solana apps/nexus-solana-exec/src/costs.rs, c483bbd, live since 2026-09-05 17:46 UTC with a74cc87):

Signer fieldNAV cost kindMeaning
fee_lamportstx_feebase signature fee(s) the owner paid (signatures × 5000)
priority_fee_lamportspriority_feethe compute-budget priority portion
tx_fee_lamports— (not booked; = the sum of the two above, meta.fee when the owner paid)the two fields above partition the transaction fee, so the parser sums both and never counts the total as well
rent_lamportsrentnet rent locked (+) / released (−) across the tx boundary (a temporary wSOL account cancels out). Option<i64> since nexus-solana 4804e26 (2026-09-06): null when the figure could not be defended, with rent_unattributed_lamports + rent_note beside it and rent_usd absent with it. Booked only when the pair is not native-SOL — Cost rules
rent_unattributed_lamports, rent_note— (never booked)the rejected figure and why, when a coin leg leaked into the rent delta or the number exceeded SLEEVE_RENT_SANITY_LAMPORTS (0.1 SOL)
sol_usd_mark, tx_fee_usd, rent_usd—the lamport figures at the oracle's SOL mark; absent when the mark is unknown, never 0
received_qty, spent_qty—realized quantities in UI units (base units in received_base / spent_base)
venue_fee_base, fee_usdswap_feethe venue fee only — e.g. the Jupiter Trigger output fee visible in a sleeve fill (expected output − received), in USD at the coin mark. fee_usd is never the transaction fee in USD.

Sources the NAV job reads, each once:

  • /v1/sleeve.recent_trades[] — filled sleeve rows (written at fill time; derived for older rows). This is what the parser in ac38aa1 consumes (fee_lamports → tx_fee, priority_fee_lamports → priority_fee, fee_usd → swap_fee).
  • /v1/ladder.outbox.fired[] and each outbox row's fill.costs — from the outbox phase-2 tx-meta verification. Empty in prod while EXEC_OUTBOX=0 (rungs fired on the legacy path carry no cost fields).
  • /v1/status.costs_today — {since, sends, tx_fee_sol, base_fee_sol, priority_fee_sol, rent_sol, fee_usd, unpriced_sends, sources}, since 00:00 UTC, keyed by signature over the in-memory ledger merged with the day's persisted rows (accum_outbox fills, accum_trades fills); route-only sends (lending, Squads, stake, LP) are in memory since boot on the exec's side. Since ee0cd80 the worker persists those route answers itself (above) and reconciles this aggregate as unbooked_sends_today; it is still not a NAV deduction (on-chain, already out of the wallets).
  • The operator-set monthly infra + AI figure (NAV_MONTHLY_FIXED_COST_USD, kind infra), accrued pro-rata — the only cost NAV deducts.

Rows from sends before the a74cc87 rollout (17:46 UTC) have no fee fields; until a costed send lands, the costs bucket holds the infra / AI accrual only (the re-verification at 19:12 UTC read 0 accrued costs); the dashboard's "flows & costs" panels read whatever accum_costs holds.

Explicitly not built​

  • Benchmark 5, the same system without AI timing decisions — not implemented; nothing isolates the AI's contribution yet.
  • External-flow auto-detection from chain history — phase 2; flows are operator-recorded.
  • Historical reconstruction — the NAV series starts the day the job first runs. There is no back-filled history, so benchmark comparisons and drawdown figures are meaningful only after weeks to months of data.
  • Forecast resolver / Brier scoring (audit M3) — unchanged, not started.
  • Scenario tests in the table above — none automated.
  • Cumulative lending interest "since deposit" — the exec exposes supplied_ui / apy only, so lending yield is derived per period (Δ balance − own flows) and cannot be restated from a deposit's cToken position; open item for nexus-solana (P&L by source).
  • ladder_realized:<asset> — the source exists in the vocabulary but is never emitted: there is no sell path for fired rungs.
  • costs:slippage and an unattributed_pre_ledger bucket (with a ledger_starts_at marker) — specified 2026-09-06, no code: verified absent from nexus-platform at 63a2de3. Until they exist, the execution cost of an internal conversion and every period before the flow ledger began land in unexplained — the residual.

How the operator records an external flow​

Every deposit into or withdrawal from the book (EXEC, VAULT, a CEX account) must be recorded, otherwise it shows up as return. Once the API is deployed:

POST /v1/desk/nav/flows # through the normal Core auth middleware
{ "direction": "in" | "out", # "deposit" / "withdrawal" also accepted
"asset": "USDC", "qty": 1000,
"at": "<RFC 3339 — when the money moved>",
"tx": "<signature, optional>", "note": "…" }

The row lands in accum_external_flows with source: manual, recorded_at and the caller's identity; value_usdt is computed at the marks of at. The NAV job applies each flow exactly once — in the first snapshot whose period contains recorded_at, so a flow recorded late is still counted (in the next period) — excludes it from TWR, includes it in MWR, and replays it into every benchmark. Field names are those of the local ExternalFlow record (nexus-platform 6dbd6ce); confirm against the deployed route before scripting. Until the route is live there is nothing to call; do not edit accum_external_flows by hand.

Honesty notes​

  • A NAV series that starts today has no history; benchmark comparisons are meaningful only after weeks or months.
  • Three profitable sleeve closes are not alpha.
  • The sleeve's realized USDC ledger profit is not the USDT NAV return; the two will diverge as soon as unrealized moves, yield, costs and the USDC/USDT rate enter.
  • NexusNavStale fired until the first snapshot was scraped (17:4x UTC, after the component label fix) — not until the job ran (17:25 UTC).
  • Execution costs enter NAV only from sends the exec image with a74cc87 confirms; earlier trades are un-costed in accum_costs.
  • The first P&L period (17:25 → 18:25 UTC) contains the 17:37 UTC Kamino rotation, made before internal-flow recording existed; its Kamino row is not interest and the period stays a gap in unexplained — see P&L by source → dry run.
  • Kamino realised APR vs quoted is now observable (lending:kamino rows, nexus_lending_apr_realized{venue="kamino"}): the first live hour (2026-09-05 21:12 UTC) showed +0.24 USDT ≈ 3.7 % annualised against 11.0 % quoted on the JLP reserve. Over 12 clean hours on 2026-09-06 the measurement was +2.96 USDC ≈ 3.8 % annualised, and at the 10:09:48Z snapshot the reconciled row read +3.04 since inception, realised APR 3.63 %, status contaminated. That is not a verdict on JLP: the reported supplied_ui carries ±$2 of noise on a $56k balance and fell in 2 of 11 hours, and the venue rotation of 2026-09-05 17:37 UTC contaminates the series (lending reconciliation). Judge the real rate from interest_clean_usdt / apr_realized_clean over a day with no rotation in it.
  • The first day's P&L was wrong, not merely young. A cost sign, a rent attribution and a derived profit produced a +1,226.46 round trip and a +1,068 cost row that never happened. It is repaired in the stored series (Cost rules), and it is the reason this page now states a rule for every figure it prints.

Measurement qualifications (owner re-verification, 2026-09-05 19:29–19:34 UTC)​

The owner re-verified the series independently against the 19:12:02 UTC snapshot (worker 7e1adcb, before the per-source rows existed) and rated it "measurement implemented and live; validation immature". Observed then: in-scope NAV 91,106.75 USDT (inception 91,138.32 at 17:25 UTC), inception TWR −0.03464 %, attribution total −31.57 USDT, marked SOL exposure 20.75 %, BTC 0.004 % (dust), stables 79.25 %, budget SOL after escrow + ladder reservations 69.37 % vs the 60 % cap, 1d/7d/30d windows null, accrued costs 0, no unknown components within the configured scope. The negative two-hour return is an observation, not a strategy verdict; the cash / hold / DCA / passive-ladder series exist but cannot establish an edge on this history; the frozen comparator assumptions (hold 50/50, cash 4 % APR, 0.3 % modeled slippage) are configuration, not verified yields or costs. The qualifications, each of which this page now carries:

QualificationWhat it means for the numbersStatus
Scope — in-scope NAVTwelve non-core CEX asset entries (outside NAV_CEX_ASSETS) are listed as out of scope and unvalued. Call the figure the in-scope strategy NAV until every material holding is valued or explicitly excluded from the mandate. Labelled since 0a58575 / ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ): /v1/desk/nav returns scope: "strategy", in_scope_nav_usdt (= nav_usdt, kept for compatibility) and out_of_scope{value_usdt_estimate, unpriced_entries, entries[{exchange, asset, qty, usdt_estimate | null, priced, pricing}]} — cex-manager's usdValue is an estimate, never counted; when built all 12 entries were unpriced (cex-manager reports 0 for them).labelled — owner scope decision still open
External flowsOnly operator-recorded flows existed at 19:12 UTC. Residual detection since 8e1e0cb / ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ): every snapshot compares the on-chain pool (wallet_exec, wallet_vault, sleeve_escrow, lending, lp) with the previous pool repriced at current marks, nets the sleeve fills' price edge, and treats a residual beyond NAV_FLOW_DETECT_MIN_USDT (50) + NAV_FLOW_DETECT_TOL_FRAC (0.01) × Σ|internal flows + fills| as a flow the ledger does not know: written to accum_external_flows with source: detected_unconfirmed (dated at the snapshot, wallet / asset by the largest move in that direction; skipped when a pool component is unknown), applied to the period like a manual flow, listed under /v1/desk/nav.flows.detected_unconfirmed for the operator to confirm or offset via POST /v1/desk/nav/flows. Unconfirmed because the exec exposes no wallet-history route (GET /v1/chain/transfers is the recorded follow-up; with it source: detected with signature and exact time).addressed (unconfirmed detection) — signature-confirmed capture open
TWR approximationAt 19:12 UTC the period return treated net flows as arriving at period end. Flow-timed since 8e1e0cb / ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ): the period is split at each flow and the portfolio is valued at the flow's time on marks interpolated between the two snapshots, with a constant residual rate solved so the chain ends at the observed NAV; each period carries meta.twr.method and keeps period_return_end_of_period as the comparison figure — TWR method labels. Periods before the roll keep their end-of-period figure.addressed — labelled per period
AttributionAt 19:12 UTC the coarse attribution.yield bucket was defined as the residual after market, realized trading and costs — the ≈ −28.20 USDT "yield" seen then was an unexplained residual, not negative protocol yield. Superseded since nexus-platform 7d6ed59 (worker rolled ≈ 20:3x UTC, first rows 21:12 UTC): yield is accrued per source (jito_staking, lending:<venue>, lp_fees:<pool>) and the residual is its own row, unexplained — which at 21:12 UTC was −95.61 USDT, the pre-recording 17:37 UTC rotation gap (P&L by source).superseded by 7d6ed59
CostsLive NAV reported zero accrued costs at 19:12 UTC: route-only sends (lending, Squads, stake, LP) were kept in memory since boot and the worker's fee_costs read sleeve rows only. Since 8e1e0cb / ee0cd80 (deployed 2026-09-05, live-verified 23:4xZ): rows are keyed <kind>:<signature>:<field> and insert-once across the four feeds, the 11 route call sites persist their answers' costs, costs_today is reconciled as unbooked_sends_today, and the double-subtraction rule is code and API text — on-chain fees / rent are attribution only (costs:tx / costs:venue), costs_accrued_usd = infra / AI only (Costs). The infra / AI figure is still whatever NAV_MONTHLY_FIXED_COST_USD says (0 unless set).addressed — durable, deduplicated; infra figure operator-set
LearningBrier resolver / calibration, the no-AI counterfactual (benchmark 5) and out-of-sample evidence remain missing.open

Owner priority 5 (register Next): validate the NAV and cost / flow series, label scope and attribution honestly, then evaluate benchmarks over meaningful history. The batch above rolled and was live-verified 2026-09-05 23:4xZ (ledger seeded at version 2, verdict available, in-scope NAV 90,981). It is the measurement that is validated, not the result: the very next day the P&L correctness batch found the first day's figures were wrong (the correction), and a day of history validates nothing.

TWR method labels (meta.twr.method)​

Written by the worker on every snapshot since 8e1e0cb and returned as /v1/desk/nav.twr ({method, period_return, period_return_end_of_period, sub_periods[]}):

methodMeaning
no_flowsno external flow in the period: nav / nav_prev − 1, exact
flow_time_interpolated_marksthe period was split at each flow; the value at a flow's time is the previous snapshot's holdings repriced on marks interpolated linearly between the two snapshots (SOL-family and BTC units × mark, stables flat), times a constant residual rate solved so the chain ends at the observed NAV (yield, fills, costs and unexplained assumed to accrue uniformly)
flow_time_flatsame split, but a coin mark was missing at one end, so the value path is the constant residual rate alone
end_of_period_fallbackthe constant-rate solve did not converge (a withdrawal larger than the modelled value at its time): the end-of-period figure is used and labelled

period_return_end_of_period is the old approximation, kept on every period as the comparison figure (the benchmark simulators still apply their flows at the step).

P&L by source (accountability)​

Built 2026-09-05, first live rows 21:12 UTC

nexus-platform 3a91236 (records) → 2c79b0e (worker accrual + internal flows) → d76eb11 (Core routes) → 750c5ce (gauges) → 3646865 / 7d6ed59 (tests, dry run), pushed ≈ 20:1x UTC, worker rolled ≈ 20:3x UTC; UI section nexus-ui f8327f1; dashboard row nexus-gitops b5b6079. First live rows at the 21:12 UTC snapshot (/v1/desk/pnl?window=all; the identity holds). The figures in the dry-run section below come from the offline run over captured live data.

The owner's requirement, as stated: see the winning part for JitoSOL, the Kamino lending and other winnings, and the profit for the swing orders buy/sell — so we can see easily from where we are winning money. The attribution buckets above (market / yield / trading / costs) answer it only in aggregate; this ledger answers it per source, and it is reconciled to NAV every hour.

The identity​

For every snapshot period (previous snapshot → this one):

ΔNAV − external flows = Σ explained sources + unexplained

unexplained is a row like any other — written, exposed in the API and the dashboard, never folded into a neighbour. A period that does not reconcile shows a large residual, not a wrong yield figure. The worker job treasury_nav computes the decomposition after each snapshot (apps/nexus-worker/src/treasury/pnl.rs) and writes one row per source into accum_pnl_sources (_id = <snapshot_id>:<source>); realized and accrued events go to accum_pnl_events (_id = <source>:<ref>, so a fill is booked once however often the ledger is re-read). Windows (1d, 7d, 30d, inception) are sums of period rows; all adds the sleeve round trips that predate the NAV series.

Source vocabulary​

Fixed in crates/nexus-db/src/treasury.rs (pnl_source); any other string is rejected. Groups are what the dashboard and the coarse attribution buckets roll up to.

SourceKindGroupMeaning
jito_stakingaccrualyieldJitoSOL qty × Δ(pool rate) × SOL mark — the staking reward, separated from the SOL price move
lending:<venue>accrualyieldper protocol (kamino, marginfi): Δ supplied balance − net own supply / withdraw flows in the period
lp_fees:<pool>accrualyieldΔ uncollected LP fees (each snapshot at its own marks) + fees collected (lp_collect flows). Read the whole rule — this was implemented against a field the exec does not publish and reported 0 until 8123882: The LP fee defect
sleeve_realized:<pair>realizedtradinga filled swing sell: proceeds − lot cost − venue fee − tx fees
ladder_realized:<asset>realizedtradinga fired rung later sold — never emitted, no sell path exists
sleeve_unrealized:<pair>market_markmarketΔ of open sleeve inventory's (mark − vwap) × qty; nets to 0 over a round trip's life
market:SOLmarket_markmarketSOL-family units held at period start (incl. JitoSOL × rate, excl. sleeve inventory) × ΔSOL
market:BTCmarket_markmarketBTC-family units (excl. sleeve inventory) × ΔBTC
market:STABLEmarket_markmarketstable balances × Δ(USDC / USDT mark in USDT) — peg drift, so it does not land in unexplained
market:LPmarket_markmarketLP position legs drift, net of add / remove flows at the snapshot's marks. The uncollected-fee legs are excluded, so no dollar is both yield and drift
costs:txfeecostson-chain tx / priority / rent fees not already inside a sleeve fill. Since 2553748: never ≤ 0, never above PNL_COST_SANITY_USD, and rent_* is skipped whole on a native-SOL pair — Cost rules
costs:venuefeecostsswap / CEX fees not already inside a sleeve fill; same three rules
costs:infrafeecostsinfra + AI accrual (NAV_MONTHLY_FIXED_COST_USD)
unexplainedresidualunexplainedΔNAV − external flows − Σ of everything above

How each source is derived​

All amounts are USDT. A quantity change is valued at the end snapshot's marks; a price change uses both ends.

  • JitoSOL (jito_staking): qty × (rate₁ − rate₀) × SOL₁. The SOL price move on the same coins is market:SOL (qty × rate₀ × (SOL₁ − SOL₀)), so the split is exact: q·(r₁p₁ − r₀p₀) = q·r₀·(p₁ − p₀) + q·(r₁ − r₀)·p₁. The pool rate steps at epoch boundaries (≈ 2–3 days), so hourly rows show zeros and then one step — that is the reward arriving, not a bug. nexus_staking_apr_realized annualises the rate change since inception.
  • Lending (lending:<venue>): Δ supplied balance − net internal supply / withdraw flows in the period, valued at the end mark. The exec's /v1/positions exposes supplied_ui and apy only — no cToken quantity or exchange rate — so this period method is the only derivation, and a cumulative "interest since deposit" cannot be restated from the position itself. The reported APY rides along in the row (apr_reported) next to the realised one (apr_realized, interest over the average balance, annualised). Open item for nexus-solana: expose cToken qty + exchange rate.
  • Swing round trips (sleeve_realized:<pair>): built from the complete sleeve ledger by linking each filled sell to its buy lot(s) (lots[], or parent) — entry and exit price, cost, proceeds, fees, profit, holding time. A sell that closed before the NAV inception (2026-09-05 17:25 UTC) is historical: true: it appears in the cumulative table and in window=all, never in a period row or in TWR. fees_unknown: true while neither row carries cost fields — true for every fill before the exec image with a74cc87; new fills carry fee_lamports / priority_fee_lamports / fee_usd (Costs).
  • Open swing inventory (sleeve_unrealized:<pair>): open lots (including the coins sitting in a resting sell's escrow) × (mark − vwap), booked as a period delta. Sleeve inventory is excluded from the market:* units, so a position's price move sits here while open and is reversed at the fill — over a round trip's life the source nets to 0 and the full profit lands in sleeve_realized, which is what the owner reads.
  • Market (market:SOL|BTC|STABLE|LP): units held at the period start × Δprice; stables × Δ(USDC/USDT) so peg drift is named.
  • Costs (costs:tx|venue|infra): the period's accum_costs rows, minus the fee legs already inside a sleeve fill (sleeve trade fee note), so nothing is counted twice.

Internal flows. Separating a deposit from interest needs the worker's own movements. Since 2c79b0e every gate call site records the confirmed send in accum_internal_flows (_id = <kind>:<ref>): lending_supply, lending_withdraw, stake, unstake, lp_add, lp_remove, lp_collect, swing_buy, swing_sell (hygiene rotate / repark, swing placement, stake, LP open / collect / close in mob_jobs.rs). The kind ladder_fire exists in the vocabulary but has no call site: rungs fire on the exec plane and ladder fills are read from /v1/ladder. Anything moved before the flow ledger existed lands in unexplained by construction, with the period's inputs in the row's detail so the operator can chase it — as does swap slippage on DCA / stake conversions, CEX balance changes and Kamino balance rounding.

Cost rules — what is not a cost (2026-09-06)​

nexus-platform 2553748 (apps/nexus-worker/src/treasury/costs.rs), deployed 2026-09-06. A cost is a positive, small number. Three rules keep a mis-parsed figure out of the ledger; each one alone would have caught the defect where the exec reported the sold SOL leaving the order escrow as rent (rent_lamports −10,005,519,280, −1,068 USD) and the P&L subtracted it, inflating a 159 USDC round trip to 1,226.

  1. Never negative. A cost field ≤ 0 (or non-finite) is ignored — never booked, never subtracted from a profit. This retires the old "a rent refund is a negative row" behaviour: a refund is a balance change NAV already sees, not a credit to attribute.
  2. Never implausible. A single on-chain row above PNL_COST_SANITY_USD (default 25) is not a fee. It is recorded as CostKind::Unattributed with the raw value in the note, is in no cost bucket, and is surfaced in /v1/desk/pnl.notes and costs.unattributed_usdt.
  3. Never the coin leg. For a native-SOL pair the rent_* fields are skipped outright: the SOL being bought or sold moves through the order escrow / wSOL account, so its lamports land in the exec's rent delta and are the trade, not a cost. native_sol_pair is true when either side of <A>/<B> is SOL; JITOSOL/USDC is not — JitoSOL is an ordinary SPL token with an ordinary ATA.

Every refusal increments nexus_pnl_cost_anomalies_total{field} (one call site passes a CostKind string rather than a field name, so read the label as "which figure was refused", not strictly as a field). The exec side of the same rule is TxCosts::attribute_rent — nexus-solana → Rent.

The repair pass. The rules stop a bad figure entering the ledger, but rows already in Mongo would keep the defect alive for ever: an accum_costs row is insert-once on a deterministic _id, so is an accum_pnl_events row, and an accum_pnl_sources row belongs to a period that will never be recomputed. So the hourly treasury_nav job runs a repair pass inline, before it reads the period's costs (there is no separate job to trigger). It is idempotent — a clean series makes it a no-op — looks back LOOKBACK_DAYS 30, and does three things in order:

  1. Purge bad cost rows — every on-chain row (one with a signature) in the lookback whose amount fails the rules is deleted and logged. Deleting is the right repair: the _id is deterministic, the feeds no longer book it, and a delete run twice is still a delete. Rows already recorded unattributed are kept — they are the audit trail and are in no bucket.
  2. Recompute the sleeve events from the exec's own recent_trades[] under the current rules, overwriting when the amount moved.
  3. Rewrite the affected period rows, moving that period's unexplained by the opposite amount so delta = explained + unexplained still holds. Since 63a2de3 a lending period is re-judged against PNL_LENDING_SANITY_MULT from the working the row already carries.

Nothing in the pass reads a price feed or the chain: the marks are the ones the original rows were written with, so a repair never re-marks history. What one run changed is reported on /v1/desk/pnl.costs.repair (costs_deleted, events_fixed, rows_fixed, notes, sanity_usd, rule).

Profit source — the exec's figure, or ours​

nexus-platform 2553748. A round trip's profit_usdc is the exec's own realized_pnl_usdc whenever the exec read the confirmed transaction — i.e. the fill row carries realized_pnl_usdc and proceeds_source: "tx_meta". Every round trip says which:

profit_sourceMeaning
exec_realizedthe exec's realized_pnl_usdc, taken verbatim from token deltas on the confirmed transaction (exposed on the platform side as exec_realized_usdc)
derivedthe platform computed proceeds − lot cost − known fees because the row carried no such figure — fees_unknown is only ever set on this path

sleeve.profit_source_counts ({exec_realized: n, derived: n}) says how much of the cumulative table is which. sleeve.exec_realized_total_usdc carries the exec's own all-time total beside the platform's.

Refused fee fields are listed per round trip in round_trips[].notes[], so a trip whose profit looks too clean can be traced to the figure that was thrown away.

Window coverage — a young series is not a flat one​

nexus-platform 72967bd. Before it, 1d / 7d / 30d on a series hours old resolved to from == to, 0 periods, delta 0 — which the UI drew as "the desk made nothing", not "the desk is four hours old".

resolve_window now clamps a requested window to the series' own start and says so. /v1/desk/pnl and /v1/desk/pnl/history carry a coverage object — requested_days, covered, truncated_to_series, series_starts_at, covered_days, and a note that is null when covered is true and a sentence otherwise. /v1/desk/pnl also lifts covered, truncated_to_series and series_starts_at to the top level; /v1/desk/pnl/history adds covered_from_ms.

/v1/desk/nav gains returns_coverage — one object per rolling window (1d, 7d, 30d) with covered, truncated_to_series, series_starts_at, covered_days. It is hand-built by the worker and, unlike the P&L coverage, carries no note and no requested_days. A truncated window returns the since-inception figure with covered: false, never null.

The UI's half of the contract is on nexus-ui: it opens on the largest covered window and renders an uncovered cell as n/c, never 0.

Lending reconciliation — interest that ties to its own balances​

nexus-platform 72967bd / 4b5a181 (crates/nexus-db/src/treasury.rs, lending_window), shared verbatim by the worker and the Core API so the two cannot disagree. The old block put balance_start, balance_end and interest_usdt side by side without stating how they related — and on 2026-09-06 they did not: the balances differed by +38.03 while the interest claimed 2.96.

Every lending[] row now carries the identity

balance_delta_usdt = net_flows_usdt + interest_usdt + unattributed_usdt

verbatim in an identity field, with unattributed_usdt computed as the remainder — so a row that does not tie says so in its own numbers rather than hiding the gap in interest.

Contamination. A period is contaminated when the venue label changed between its ends, when the period was already flagged over_sanity, or when |Δ balance| > PNL_LENDING_SANITY_MULT × |accrual implied by the venue's own reported APR| (default 10). Such a period is not interest — it is a reserve rotation, a migration, or a read of a different position. Contaminated periods are listed in contaminated_periods[] (at, snapshot_id, delta_qty, implied_qty, hours, reason) and excluded from the clean figures: interest_clean_usdt, clean_hours and apr_realized_clean sit beside the raw interest_usdt, apr_realized and the venue's apr_reported. negative_periods counts the periods whose balance fell.

The same rule runs per period in the worker (interest_qty, implied_qty, over_sanity, booked_qty — 0 when over) and is what the repair pass re-judges for stored rows.

Note: these contamination sentences appear on lending[].notes and on earning[].note, not in the top-level /v1/desk/pnl.notes.

LP as legs​

nexus-platform 72967bd (apps/nexus-worker/src/treasury/nav.rs). Until it, an LP position was one NAV component whose asset was the uppercased pool address and whose qty was 0, even though the position held 10 SOL — so exposure.SOL excluded the LP's coin leg, and with it the F6 sol_exposure cap input.

Now a position is decomposed into ordinary holdings:

  • one lp component per leg (SOL, USDC, …) with the real qty, valued qty × mark like any other holding, at venue <protocol>:<pool> — orca:<pool> when the exec reports orca; the protocol is read from the exec's own venue / protocol / dex field, not hard-coded;
  • uncollected fees on their own line at venue <protocol>:<pool>:fees — they are earnings not yet collected, not principal;
  • the note on each line names the position and its range (orca SOL/USDC position <id>; range 105.9600–112.0200; in_range=false).

Because the legs are ordinary components, each one lands in its exposure family by its own asset symbol — no special case in exposures(). A leg whose qty is 0 emits no line at all.

When a position cannot be decomposed (no readable <BASE>/<QUOTE> pair, or a missing mark) the exec's own value_usd + fees_usd is kept under the readable pair label and the note says the quantity is in no exposure family — the honest fallback, not a silent zero.

Measured effect at the 10:09:48Z snapshot: exposure.SOL 18.71 % → 19.87 %. This was an exposure and cap understatement, never a NAV understatement — the LP's value was already counted.

The LP fee defect — real fees reported as zero yield​

nexus-platform 8123882 / ebf42e6 / 2f9c0db, nexus-ui 2d91151 / 41dff12 / 0fc6392. Found 2026-09-06 by the owner, who could see the liquidity page reporting $1.00 of uncollected fees on the Orca SOL/USDC position while Treasury reported that same position earning nothing.

Both pages were reading the platform correctly. The platform was wrong, in two independent places.

1. lp_fees measured a field that does not exist. The decomposition read the uncollected balance from fees_usd / unclaimed_fees_usd / pending_fees_usd. The exec publishes fee_coin_ui and fee_usdc_ui, and none of those three. So fees_start_usd and fees_end_usd were 0.0 every period by construction, and lp_fees was structurally incapable of being anything but zero. Meanwhile the exec's value_usd — which market:LP differenced — includes the uncollected fees, so every dollar of fee income was silently booked as market drift. The money was never lost from NAV; it was filed under the wrong heading, which is exactly the question the owner was asking the page.

The fix measures the fee legs the NAV decomposition already emits (the <protocol>:<pool>:fees lines above), and takes them out of market:LP:

lp_fees:<pool> = Δ(uncollected fee legs, each snapshot at its own marks)
+ fees collected in the period
market:LP = Δ(position legs) − adds/removes at this snapshot's marks

The Δ is split further in the source detail, the way staking is split: q₁p₁ − q₀p₀ = (q₁−q₀)p₁ + q₀(p₁−p₀), giving fees_accrued_usdt (fees that actually arrived) beside fees_price_move_usdt (the price move on fees that had already arrived). A leg present at only one end is priced with the one unit price that exists, so the whole move lands in the accrual and nothing is invented.

A collect is not a fee loss. Collecting moves the same dollar from the fee legs to the wallet, so the pair (Δuncollected + collected) is the income. lp_collect previously recorded only the USDC half in value_usdt ("USDC side only; coin side per quote"), which would have read as a loss; both legs now go into legs.outputs.

2. The earning[] LP row keyed the source wrongly. lp_state keyed the pool as <POOL uppercased>@<venue> and the API built the source id as lp_fees:<key>, producing lp_fees:CZFQ…@orca while the row had been written as lp_fees:CZFQ…. Neither earned_*_usdt nor since ever matched, so the panel showed 0.0 and null regardless of what the decomposition found.

The pool id is now the exec's own string, never re-cased. Base58 is case-sensitive; uppercasing it makes lp_fees:<pool> name a pool no component belongs to. The UI matches pool ids case-insensitively as well, so a stale uppercased row still resolves.

The repair pass rewrites the history it wrote wrong. It replays the same pure period function over the stored snapshots' own components and marks, deletes the stale lp_fees:<UPPERCASE> rows, and moves each period's unexplained by the opposite amount so delta = explained + unexplained still holds. Idempotent, and it is what makes the inception figures below appear rather than only future hours.

A realized APR needs a day behind it. apr_realized_inception is null while age_days < 1, with the reason in note. The liquidity page applies the same 24-hour floor. Annualising four hours of fees turns $0.88 into a headline percentage the position has never earned.

Live-verified at snapshot nav_1788701605541 (2026-09-06T13:33:25.541Z): lp_fees:Czfq3xZZ… +1.3232 USDT (was 0.0000, and the id now carries the exec's own base58 case; +1.3233 with e6e1fd2, one lp[] row whose drift_usdt is the whole +10.7763 and nothing unattributed), market:LP +10.7763, groups.yield 5.1183 = Kamino 3.7950 + LP 1.3232 + Jito 0.0001, and the earning[] LP row earned_inception_usdt +1.3232 on principal 1,061.05 since 09:13:02.486Z with apr_realized_inception null at 0.18 days. The three-way identity is exact over 18 periods: 5.1183 + 159.4049 + 312.9606 − 0.4755 − 95.6093 − 6.3612 = 375.0378 = delta_usdt.

One position keeps one identity​

nexus-platform 8839c8d / e6e1fd2. The fee split above immediately surfaced a second defect it had not caused: lp[] returned two rows for the one Orca position — the real pool with drift_usdt +1061.05 and a phantom orca row with −1050.28, plus an empty lp_fees:orca source. They netted to the correct market:LP and the identity held, but neither figure was a drift. It was the position's whole value appearing under one identity and vanishing under the other, across the LP-as-legs boundary at ≈ 10:09Z. market:LP had been a single number, so the artifact was real but unseen; splitting it per pool is what made it visible.

The shape change is sharper than "the venue gained a pool id" — the whole component changed:

09:13Z CZFQ3XZZDMSDGDUYRNLTRHGC47CXCZTLG4CRRYFU44ZE@orca = 1056.3187
↑ the asset IS the pool address, upper-cased; the venue is bare

10:09Z SOL@orca:Czfq3xZZ… = 935.5123 SOL@orca:Czfq3xZZ…:fees = 0.2095
USDC@orca:Czfq3xZZ… = 131.2648 USDC@orca:Czfq3xZZ…:fees = 0.2550

Three rules now govern pool identity, all inside the one pure period function so the hourly run and the repair pass cannot disagree:

  1. A relabelling is not a close and an open. One key vanishes, another appears, same protocol, one-to-one, and no lp_add / lp_remove on that protocol in the period ⇒ the start values carry onto the new key, detail.renamed_from names the old one, and the note states both identities. A genuine close-and-open leaves flows behind, fails the guard, and stays two rows.
  2. The legacy shape names its own pool. A component whose venue carries no : and whose asset is a pool identifier resolves to that pool. Two guards keep it from firing loosely: the asset must be 32–44 ASCII alphanumeric characters (SOL, SOL/USDC, LP all fail), and it must fold-match a pool id the series really carries. The canonical spelling comes from the exec's own payload, never from the upper-cased legacy string. This is recovery of a known encoding, not inference — the old asset field literally holds the address.
  3. A bare protocol that names nothing stays unattributed. Its drift remains in market:LP and is reported as lp_unattributed_drift_usdt with lp_rule stating Σ lp[].drift_usdt + lp_unattributed_drift_usdt = market:LP, rather than being credited to the nearest pool.

market:LP never moves under any of the three — the defect was always a split, never a total. The repair pass rewrites keys and details only: the stored lp_fees:orca rows are all 0.0000, so deleting them moves explained by nothing and leaves unexplained untouched.

Treasury page review, 2026-09-07​

The operator asked for a correctness pass over the whole page. Five defects, all fixed the same day:

  • A "contaminated" lending row that could never clear. Two of forty hourly periods had failed the reconciliation — a +996 USDC step (an operator-direct supply on 09-06 that bypassed the worker, so no flow row) and a −127 step (a 640 USDC withdraw that delivered 769.02, the reserve's cToken rate; the flow row recorded the request). Correcting the two rows changed nothing, because the repair pass re-judged lending periods from their stored working and never re-read the flows. It now recomputes each period's net flows from accum_internal_flows as they stand, re-derives the interest, and re-judges under the sanity rule — so a flow row written or corrected after the fact reaches the verdict. The exec-side withdraw sizing is a separate defect on the follow-up list.
  • Fees collected at a close vanished from yield. A close or remove recorded only the principal leg; the fee legs the quote carries were dropped, so lp_fees:<pool> saw the uncollected legs fall to zero with nothing collected and booked a loss equal to everything the position had earned. The 09-07 07:10Z close had collected 0.0157 SOL + 1.67 USDC; the worker now records an lp_collect row beside the lp_remove, and the missing row was written by hand.
  • Two positions in one pool showed the last one's quantity. lp_state keyed by pool and the second position overwrote the first, so the earning row read 6.19 SOL of principal against a $3,101 value. Pool state now sums quantities and value across positions, is in range if any position is, and lists the ids (positions[], position_count).
  • The "material" badge on a $2.51 residual. The bar was 1 % of the window's Δ, which on a small window flagged dust. It is now the largest of 5 USDT, 5 % of |Δ| and 0.02 % of NAV; smaller residuals show a muted "small" badge — still shown, never hidden.
  • "Black on black" bars in Sources over time. Not the palette (both themes validate against their surfaces); the 1 px surface-coloured gap drawn around every stacked segment. With 44 periods and eight series most segments are under a pixel, and an outline in the surface colour on a sub-pixel fill is a black sliver on the dark theme. The gap is drawn only on segments at least 3 px tall; a thin segment keeps its colour and a hairline of height.
  • Components table: an LP line now names its position (position Ff4o45…), since two positions in one pool share a venue label.

What the desk publishes for a pool now — lp[] gains fees_uncollected_usdt (the balance still in the pool at window end), fees_collected_usdt (what left it), fees_accrued_usdt, and a split string naming the identity; drift_usdt is filled in rather than null. The liquidity page reads them, so its "Fees collected: not available yet — the desk P&L carries no LP rows" is gone, and the tile is now Fee income (desk) because the figure is income, not a cash sweep.

earning — what is making money right now​

nexus-platform 72967bd, served on GET /v1/desk/pnl as earning[]. There is no /v1/desk/earning route.

The owner's question is "how much yield did I win", and the window selector is the wrong axis for it: a position earns since it was opened, not since the chart's left edge. So this block is built over the whole series as well as the requested window, and every row says whether it is currently earning — an out-of-range Orca position accrues nothing however good its APY looks.

FieldMeaning
kindlending | staking | lp
venue, assetthe venue key and a readable asset / pair
principal_qty, principal_usdtwhat is deployed
rate_reportedthe venue's own advertised rate (null for staking)
earned_window_usdtthis window's P&L rows for that source
earned_inception_usdtthe same since the series began
apr_realized_inceptionannualised from what was actually earned. null under one day of age (age_days says how much), with the reason in note — annualising a few hours of fees quotes a rate the position has never earned
age_dayshow long the source has been in the ledger, the divisor the APR uses
statusearning | idle | out_of_range | contaminated
sincefirst period the source appears in
notesee below

Per-kind extras: a lending row adds earned_inception_clean_usdt and a reconciliation block (balance_delta_usdt, net_flows_usdt, unattributed_usdt); an LP row adds uncollected{coin_ui, usdc_ui, usd}, fees_uncollected_usdt, fees_collected_usdt, range and price. Status precedence for lending is idle (no principal) before contaminated before earning; for staking, a Jito pool rate that did not move is idle (the rate steps at epoch boundaries, ≈ 2 days — a flat hour is normal); for LP, out_of_range wins over everything.

note is not a string. The platform emits either null or a JSON array of sentences — the lending row passes lending[].notes straight through (possibly []), staking and LP emit a one-element array. The API doc contract is written as string | string[] | null so a future single-sentence note does not break a consumer, and the UI normalises string | string[] | object | null defensively. It crashed /treasury with e.note?.trim is not a function on 2026-09-06 before that normalisation existed (nexus-ui 068ef0f) — the same class of bug as the null reserves in /v1/reconcile.

The 2026-09-06 correction, and what is still unattributed​

Corrected figures after the 10:09:48Z snapshot (nav_1788689388951), platform 63a2de3 / exec ea96739:

  • NAV 91,611.15 USDT, TWR index 1.005188 (+0.52 % since inception), 0 unknown components.
  • ΔNAV since inception +472.83, decomposed: market:SOL +537.96, sleeve_realized:SOL/USDC +159.40 (was +1,226.46), sleeve_unrealized −134.90, unexplained −101.96, market:LP +16.98, market:STABLE −7.22, lending:kamino +3.04, costs:venue −0.47, costs:tx −0.0005 (was +1,067.96).
  • Sleeve realized all-time 561.66 (was 1,628.48).
  • earning: Kamino 57,433.72 principal / +3.04 / realised APR 3.63 % / contaminated; JitoSOL 17,165.63 / 0 / earning; LP orca 1,067.27 / 0 / earning.

The −101.96 residual, itemised (this is analysis of the live series, not a code claim):

PartAmountWhat it is
Pre-ledger periods−95.61the three NAV periods before the source ledger's first row (2026-09-05T20:12:14.720Z). No attribution exists for them at all; the Kamino main → JLP rotation of 17:37 UTC sits inside
Internal conversion slippage−6.23the execution cost of the JitoSOL → SOL unstake and the LP add — venue slippage on our own conversions, currently booked to no cost source
Rounding≈ −0.12

Neither remedy exists in code. Grepped at platform 63a2de3 (working tree clean, HEAD == origin/main): unattributed_pre_ledger, ledger_starts_at and a costs:slippage source have zero occurrences — the P&L source vocabulary is closed in crates/nexus-db/src/treasury.rs::pnl_source and slippage is explicitly documented as landing in unexplained. So, in register vocabulary:

  • specified — a separate unattributed_pre_ledger bucket with a ledger_starts_at marker, so "we have no attribution for this era" stops looking like "the desk lost money it cannot explain";
  • specified — a costs:slippage source for internal conversions, so a known execution cost stops arriving as a residual.

Until they exist, read unexplained on any window that reaches back before 2026-09-05 20:12 UTC as mostly the boundary of the ledger, and check series_starts_at before drawing a conclusion from it.

What the dry run showed​

Offline run of pnl_dry_run (nexus-platform 3646865) over the snapshots, sleeve, positions, LP and ladder payloads captured on 2026-09-05:

  • Swing round trips — the three closed trips reconcile exactly to the ledger's +402.30 USDC: SOL/USDC 67 @ 102.20 → 104.90 = +180.90 (19.2 h); CBBTC/USDC 0.052 @ 76,600 → 78,300 = +88.40 (42.1 h); SOL/USDC 35 @ 99.00 → 102.80 = +133.00 (43.9 h). All three are historical and fees_unknown.
  • Open position — the 10 SOL resting sell (vwap 90): unrealized ≈ +139 USDT at a 103.89 mark.
  • Since inception (2 periods) — JitoSOL +0.00016 USDT (the pool rate barely moved inside the window). Kamino shows "+36.34" in period 1 — that is not interest: it is the 17:37 UTC rotation kamino/main → kamino/jlp, executed before internal-flow recording existed, so the balance change has no matching flow (true interest at the reported 11.87 % APY ≈ 0.68 USDC for the hour). Period 1 therefore stays a gap in unexplained (−63.58). Period 2 reconciles to 1e-11.

Read the first period as the boundary of the ledger, not as a Kamino gain.

Where to read it​

  • API: GET /v1/desk/pnl?window=…, /pnl/history, /pnl/events, /pnl/flows — shapes on the API reference.
  • Dashboard: Nexus Treasury → row "Where the money comes from" (nexus-gitops b5b6079), and the gauges under Operations → Observability — nexus_pnl_source_usdt{source,window}, nexus_pnl_unexplained_usdt{window}, nexus_sleeve_round_trips_total, nexus_sleeve_realized_usdt_total, nexus_sleeve_unrealized_usdt, nexus_staking_apr_realized, nexus_lending_apr_realized{venue}, nexus_lending_interest_usdt{venue,window}, and since 2553748 nexus_pnl_cost_anomalies_total{field} (on-chain cost figures the sanity rules refused — a rising counter means the exec is reporting something the rules will not book).
  • Collections: accum_pnl_sources, accum_pnl_events, accum_internal_flows — HARVEST §11.