Lesson 39 · Risk-Graph Math · Deeper Track

How concentrated is a vault?

HHI, the 1/HHI readout, and the accounting that's most of the code. ~12 min.

Builds on: L13 · L38 · L27 Anchor: MetaMorpho vault allocations New: HHI = Σ shares² New: effective strategies = 1/HHI

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:

VaultHHIReading
one strategy at 100%1.0² = 1.0maximum concentration
four equal strategies (25% each)4 × 0.25² = 0.25well spread
n equal strategies1/nthe diversified floor
90% + ten × 1%0.81 + 0.001 ≈ 0.8111 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:

BucketConditionTreatment
pricedallocated_usd > 0enters the HHI
missingno allocated_usd property at all (un-sizable: mellow / erc4626 / event-sourced)excluded from HHI, but counted as StrategiesMissingUSD — a sparsity flag
exitedhas 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)

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

Grounded in: pkg/risk/vault_concentration.go (computeHHI = Σ shares² via floats.KeyedSum, computeVaultConcentration priced/missing/exited buckets + nil-on-no-priced em-dash, EffectiveStrategies = 1/HHI, pricedTotalUSD order-independent denominator, vaultTopStrategiesCap = 20 with uncapped scalar mirrors vault_allocation_hhi/vault_strategy_count/vault_total_allocated_usd, adapter-exclusion Morpho dedup, focus-token-independent edge-anchored 2h compute). Verify against source — the code is the truth.