Skip to content

Adjusted SOPR (asopr) reports ~2.2 instead of ~1: per-block under-1h transfer volume subtracted from cumulative value created #42

Description

@johnnystaygood

Summary

Since v0.11.0 the adjusted SOPR series (sth_asopr_24h, sth_asopr_1w, and the all-cohort asopr) report ~2.0-2.3 across the entire history. Adjusted SOPR should hover around 1, like raw SOPR does. The cause is a one-line vec-selection mismatch: the under-1h per-block transfer-volume vec is subtracted from the cohort's cumulative value-created vec, so the "adjusted" numerator is effectively unadjusted.

Reproduced on a local v0.11.0 instance and live on bitview.space (2026-08-11); the relevant files are unchanged through v0.11.2 and main (26f07f0).

Observed

GET /api/series/sth_sopr_24h/day1?start=-5   -> 0.9999, 1.0029, 0.9999, 0.9982, 0.9972   (fine)
GET /api/series/sth_asopr_24h/day1?start=-5  -> 2.1691, 1.9377, 2.1882, 2.0091, 2.2855   (should be ~1)

Component evidence

Pulling the component series for the last days (values in cents):

date sth_value_destroyed_sum_24h sth_adj_value_created_sum_24h sth_adj_value_destroyed_sum_24h
2026-08-09 3,218,907,835,807 3,227,511,565,312 1,474,972,327,074
2026-08-10 4,712,233,636,460 4,668,370,715,877 2,323,586,346,378
2026-08-11 4,603,299,156,779 4,590,457,923,918 2,008,515,682,879

Two things follow:

  1. The destroyed side is adjusted correctly. sth_value_destroyed - under_1h_value_destroyed reproduces sth_adj_value_destroyed to the cent.
  2. The created side is not adjusted at all. sth_adj_value_created / sth_value_destroyed equals raw sth_sopr to 5+ decimals (e.g. 4,590,457,923,918 / 4,603,299,156,779 = 0.99722 = raw SOPR on 2026-08-11), i.e. adj_value_created carries the unadjusted value created.

So the served ratio is raw_created / adj_destroyed ~= 1 / adjusted_share ~= 2.2.

Recomputing adjusted SOPR from raw series via the per-cohort identity created = destroyed * sopr:

(sth_destroyed * sth_sopr - u1h_destroyed * u1h_sopr) / (sth_destroyed - u1h_destroyed)

gives 0.9998 / 0.9967 / 0.9940 for the same days - the expected ~1 band.

Root cause

crates/brk_computer/src/distribution/cohorts/utxo/groups.rs (~line 596): the two under-1h sources for the adjusted computation are taken from different vec shapes:

let under_1h_value_created = self
    .age_range
    .under_1h
    .metrics
    .activity
    .transfer_volume
    .block          // <-- per-block vec
    .cents
    .clone();
let under_1h_value_destroyed = self
    .age_range
    .under_1h
    .metrics
    .realized
    .sopr
    .value_destroyed
    .cumulative     // <-- cumulative vec
    .height
    .read_only_clone();

AdjustedSopr::compute_rest_part2 (crates/brk_computer/src/distribution/metrics/realized/adjusted.rs) then subtracts both from the cohort's cumulative base vecs (transfer_volume.inner.cumulative.cents.height and sopr.value_destroyed.cumulative.height, passed in metrics/cohort/extended_adjusted.rs). Subtracting a per-block value from a cumulative series is a near-no-op, which matches the evidence above exactly. Both call sites (sth and all in groups.rs) pass the same vec, so both cohorts' asopr are affected.

Suggested fix

Select the under-1h cumulative height transfer-volume vec, mirroring the value_destroyed selection, so both sides of the ratio subtract cumulative from cumulative.

Happy to test a fix against our recomputed reference series.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions