One number, derived from one series
yield30dComp365 is the trailing 30-day share-price return, compounded and
annualized on a 365-day basis. The indexer takes the share class’s NAV-per-share 30 days ago
(Pstart) and now (Pend) and computes:
Key facts
-
Price source: NAV per share (
navPoolPerShare) reported on-chain by the pool’sShareClassManager— not a market price. Yield reflects NAV appreciation only. - It is backward-looking, purely price-derived, and recomputed on every daily snapshot and every on-chain price update.
-
Units: stored as Ray (27-decimal fixed point). Divide by 1e25 to get percent — the
SDK does this for you (e.g.
5.5= 5.5%). -
Alternatives: the same snapshot carries 23 yield fields — simple
total returns and annualized (360/365-basis) variants over 1/7/15/30/90/180-day windows, plus TTM, YTD and
since-inception. Centrifuge’s own invest app lets each pool choose which one to display as
“APY”, or pin a hardcoded target via
apyPercentage. -
Null until mature: a share class must have a price at least 30 days old before
yield30dComp365is non-null. Young pools show nothing — which is why the “fixed” APY mode exists.
Where the numbers come from
The indexer never computes yield from loan-level interest. Everything derives from one series: the share class token price over time.
ShareClassManager emits UpdateShareClass /
UpdatePricePoolPerShare with NAV per share (18-decimal, pool currency).
tokenPrice; writes a TokenSnapshot on
every price update and every 24h UTC period roll.
tokenSnapshots GraphQL query → SDK
PoolShareYieldsReport → our app reads
yield30dComp365.
The exact yield30dComp365 calculation
From compoundAnnualizedRayFromPrices() in
src/helpers/tokenYields.ts, with spec
{ periodDays: 30, compounded: true, dayCount: 365 }.
- d is the floor of UTC days between window start and snapshot time. Since the window start is defined as exactly 30 days before the snapshot, d is always 30 — the exponent is always 365/30 ≈ 12.167.
- Returns null if either price is missing or non-positive — i.e. no price history reaches back 30 days.
-
Result is clamped to Postgres
numeric(78); overflow becomes null rather than a wrong number.
NAV per share moved from 1.0000 to 1.0040 over the last 30 days (+0.40%):
| Field | Formula | Result |
|---|---|---|
yield30d |
0.0040 / 1.0000 | 0.400% |
yield30d360 |
0.0040 × 360/30 | 4.800% |
yield30d365 |
0.0040 × 365/30 | 4.867% |
yield30dComp360 |
1.0040360/30 − 1 | 4.907% |
yield30dComp365 |
1.0040365/30 − 1 | 4.977% |
Compounding makes the annualized figure slightly higher than the simple version (interest-on-interest), and the gap widens as the 30-day return grows. The compounded 365 variant is the most “APY-like” of the five — it matches the standard APY definition of a compounded annual rate.
Every yield field the indexer offers
The columns are generated from a config (src/config/tokenYield.ts): six rolling
windows × three flavors, two compounded 30-day variants, and three fixed anchors — 23 fields per
snapshot, all Ray-scaled.
| Window | Simple total return | Annualized · 360-basis | Annualized · 365-basis |
|---|---|---|---|
| 1 day | yield1d |
yield1d360 |
yield1d365 |
| 7 days | yield7d |
yield7d360 |
yield7d365 |
| 15 days | yield15d |
yield15d360 |
yield15d365 |
| 30 days | yield30d |
yield30d360 |
yield30d365 |
| 90 days | yield90d |
yield90d360 |
yield90d365 |
| 180 days | yield180d |
yield180d360 |
yield180d365 |
| 30 days · compounded | — | yield30dComp360 |
yield30dComp365 |
| Family | Formula | Notes |
|---|---|---|
SimpleyieldNd
|
(Pend − Pstart) / Pstart | Raw total return over the window. Not annualized. |
Simple annualizedyieldNd360 / 365
|
(Pend − Pstart) / Pstart × basis/N | Linear extrapolation — an APR, no compounding. 360-basis mirrors money-market convention; 365 mirrors ACT/365. |
Compound annualizedyield30dComp360 / 365
|
(Pend / Pstart)basis/30 − 1 | Geometric extrapolation — a true APY. Only offered for the 30-day window. |
Fixed anchorsyieldTtm · yieldYtd · yieldSinceInception
|
(Pend − Pstart) / Pstart | Simple total return from: 365 days ago · Jan 1 UTC · first positive price. None are annualized (YTD in March ≠ an annual rate). |
What Centrifuge lets a pool display as “APY”
Centrifuge’s SDK defines which of these a pool may display as its “APY”
(ApyMode in sdk/src/types/poolMetadata.ts). This is the
full menu Centrifuge itself supports, and therefore the natural option space for our app too.
| Mode | Backed by | Character |
|---|---|---|
7d365 · 7d360 |
yield7d365/360 |
Responsive, noisy — one odd NAV mark swings it hard. |
30d365 · 30d360 |
yield30d365/360 |
The common default; legacy modes (target, 7day, 30day,
90day, automatic) all fall back to 30d365.
|
30dComp365 · 30dComp360 |
yield30dComp365/360 |
What we use. Same window as 30d but compounded — a true APY, reads slightly higher. |
90d365/360 · 180d365/360 |
yield90d… / yield180d… |
Smoother, slower to reflect rate changes. |
ttm · ytd · sinceInception |
yieldTtm / yieldYtd / yieldSinceInception |
Total-return figures, not annual rates — display them as “return”, not “APY”. |
fixed |
apyPercentage in pool metadata |
A hardcoded, manager-declared number. Used for young pools with no 30-day history. Not measured. |
none |
— | Hide APY entirely. |
What to keep in mind before putting a number on the page
NAV-based, not market-based
The yield tracks the manager-reported NAV per share (accrued interest and revaluations net of fees baked into price). Secondary-market price or redemption friction is invisible to it. Any yield paid out as distributions rather than accrued into NAV would not appear.
Start-price lookup is “at or before”
If snapshots are sparse, Pstart can actually be older than 30 days while the exponent stays 365/30 — the figure can slightly overstate annualized yield across gaps in price history.
Stale NAV freezes the yield
If the manager stops posting NAV updates, daily snapshots keep re-using the last price; the 30-day window slowly flattens to zero rather than showing “unknown”.
Nulls are expected, not errors
Before a window has history (e.g. yield180d… for a three-month-old share
class), the field is null. Handle it in the UI.
360 vs 365 basis is purely conventional
360-basis reads ~1.4% lower relatively (e.g. 4.907% vs 4.977% in the worked example). Pick one and stay consistent across the product.
The V2 indexer, for comparison
yield7DaysAnnualized,
yield30DaysAnnualized and yield90DaysAnnualized, plus
MTD/QTD/YTD/since-inception on tranche snapshots. Its annualized figures were simple (linear)
annualization with a millisecond-precision day count — there was no compounded variant at all.
yield30dComp365 is new to the V3 (api-v3) indexer.