L13 named this metric — "HHI, and effective strategies = 1/HHI" — in one line. The formula really is one line.
But the file is 500 lines, and the gap between those numbers is the lesson: a market-concentration index is trivial to
write and surprisingly subtle to compute honestly over messy on-chain allocation data.
Your anchor: the curator lens
A MetaMorpho-style vault deploys its deposits across strategies — usually several lending markets — via
VAULT_ALLOCATION edges. The risk question (FORTA-2880, "the curator lens") is: is this vault diversified, or
is it secretly all-in on one market? A vault with 95% in a single risky market is one bad market away from a loss,
no matter how many tiny side-positions pad its strategy count. HHI is how you catch that.
1 · HHI — squares punish bigness
The Herfindahl-Hirschman Index is the standard market-concentration measure (antitrust regulators use the same one). Take
each strategy's share of the vault's total deployed USD, square it, and sum:
HHI = Σi ( allocatedi / total )² range (0, 1]
The squaring is the whole trick. A 50% position contributes 0.25; a 1% position contributes 0.0001. Big positions dominate
the sum, so HHI rises toward 1 as capital concentrates:
Vault
HHI
Reading
one strategy at 100%
1.0² = 1.0
maximum concentration
four equal strategies (25% each)
4 × 0.25² = 0.25
well spread
n equal strategies
1/n
the diversified floor
90% + ten × 1%
0.81 + 0.001 ≈ 0.81
11 strategies, but concentrated
2 · Effective strategies = 1/HHI — the human readout
An HHI of 0.81 is abstract. Its reciprocal isn't: EffectiveStrategies = 1/HHI is "the number of
equally-sized strategies that would produce this HHI."
The reciprocal turns an index back into a count
HHI 0.25 → 4 effective strategies (and indeed four equal strategies give 0.25). The 90%+ten×1% vault? HHI ≈ 0.81 →
≈ 1.2 effective strategies: it has eleven positions but behaves like one-and-a-bit bets. That single number —
"this vault is effectively 1.2 strategies" — is the headline a curator or a rule actually wants. The index measures
concentration; its reciprocal communicates it.
3 · What counts — the accounting that's most of the file
The math is one line; deciding which allocations enter it is the real work. Each strategy edge is sorted into one of
three buckets:
Bucket
Condition
Treatment
priced
allocated_usd > 0
enters the HHI
missing
no allocated_usd property at all (un-sizable: mellow / erc4626 / event-sourced)
excluded from HHI, but counted as StrategiesMissingUSD — a sparsity flag
exited
has the property but ≤ 0 (empty / closed market)
silently ignored — not a current strategy, not a gap signal
Em-dash, not a misleading zero
If a vault has no priced strategy, computeVaultConcentration returns nil and the caller skips
stamping entirely — so the admin panel shows an em-dash. Why not stamp HHI = 0? Because HHI = 0 reads as
infinitely diversified — the exact opposite of "we couldn't size this vault." A false "perfectly safe" is worse than a
visible "unknown." Same honest-absence discipline as L20's honest-zero and L32's skip-don't-lie.
4 · Two data-quality guards (the real-world grit)
Morpho double-write dedup. The Morpho allocator writes twoVAULT_ALLOCATION edges per allocation (a protocol quirk); the enumeration excludes target_type = 'adapter' so each allocation counts once and the shares sum to ≤ 1. Miss this and concentration silently halves.
Order-independent sums. Both the denominator (pricedTotalUSD) and the squared-share sum (computeHHI) go through floats.KeyedSum — sorted-key Neumaier compensation — because the strategy slice comes from Go map iteration and float addition is non-associative. A naive total += s.usd would ulp-drift the HHI denominator run-to-run and flip downstream threshold checks. The same float-determinism discipline you saw in L19 and L32, applied in two places here.
5 · Cap the detail, never the headline
The embedded top_strategies list is capped at 20 (JSON-bloat guard), but HHI, strategy_count,
and total_allocated_usd reflect all priced strategies. And those three are stamped as scalar mirrors
(vault_allocation_hhi, etc.) alongside the JSON blob, so the admin panel and rules can threshold on HHI without
parsing JSON. Cap the list you render; never cap the numbers you compute.
A contrast worth noting (vs L38)
Unlike node_risk_score (per-token partial, focus-token-scoped, L38), vault concentration is focus-token-independent
— a vault is concentrated or not regardless of who watches it. So it runs as one cheap edge-anchored read over
VAULT_ALLOCATION (by edge type, never a node scan), folded Go-side, no RPC, on a 2h sweep. Two risk fields, two
totally different compute shapes — dictated by whether the metric depends on a focus token.
Check yourself
1. HHI squares each strategy's share before summing. What does the squaring accomplish?
2. A vault has four equal strategies (25% each). What's its HHI?
3. A vault holds 90% in one market and ten 1% side-positions. EffectiveStrategies ≈ 1.2 despite 11 positions. What does that capture?
4. An allocation edge has no allocated_usd property at all (an un-sizable mellow/erc4626 leg). How is it handled?
5. A vault has zero priced strategies. Why return nil and stamp nothing instead of HHI = 0?
6. Why does the enumeration exclude target_type = 'adapter' edges?
7. Both the denominator and the squared-share sum route through floats.KeyedSum. Why?
8. top_strategies is capped at 20, but HHI / count / total are not. What's the principle?
↳ Ask your teacher
Try: "Show me the VAULT_ALLOCATION enumeration Cypher and the adapter exclusion." ·
"How does a rule threshold on vault_allocation_hhi in practice?" ·
"What's the sweep-memo gate that survives pod restarts?" ·
"Why is rwa_concentration intentionally absent (the FORTA-2862 note)?" ·
"How does EffectiveStrategies behave as a vault adds a tiny 11th position?"
What you can now do
Write HHI = Σ(share²) and explain why squaring makes it a concentration measure.
Interpret EffectiveStrategies = 1/HHI as "how many equal bets this vault behaves like."
Sort allocation edges into priced / missing / exited and justify each treatment.
Explain em-dash-not-zero, the Morpho adapter dedup, and the KeyedSum determinism (in both sum sites).
Explain cap-the-detail-not-the-headline and why this metric is focus-token-independent (vs L38).