vault_report.sections

Documentation for eth_defi.vault_report.sections Python module.

Vault rankings and tables for the monthly vault report.

Each report section is a ranked subset of the vault metrics loaded by eth_defi.vault_report.data.fetch_vault_report_data(). The post structure is described in eth_defi/vault_report/README-blog-post-outline.md.

Vaults are classified into groups; each group except Other is ranked in its own table:

  • Lending: strategy tagged as lending, or a known lending protocol, see LENDING_PROTOCOL_SLUGS

  • Real-world assets (RWA): strategy tagged as investing in, lending against or financing real-world assets, see RWA_STRATEGY_TAGS

  • Perpetual futures DEX: Hyperliquid, GRVT, Lighter and other native trading vaults

  • AMM pools: GMX GM and GLV pools and Curve-based YieldBasis pools

  • Tokenised funds: vaults flagged tokenised_fund, e.g. money market funds

  • Other: everything else, e.g. yield aggregators and trading vaults. It has no section of its own; its vaults appear in the new vaults, per-chain, yield and risk and return sections

Tables rank by the annualised one-month return, like the website’s vault pages, net of fees when fee data is known and gross otherwise, shown without a net or gross marker. Charts use the steadier annualised three-month return, CHART_RETURN.

How the universes are narrowed, see eth_defi.vault_report.report.generate_monthly_vault_report():

  1. filter_eligible_vaults() drops blacklisted vaults, vaults without TVL or a one-month return, and stale vaults. The result feeds the report statistics and the inflows and outflows.

  2. select_comparable_vaults() drops vaults whose protocol is not identified, because their returns are often artefacts. Every table and chart that compares returns starts from here.

  3. The investability check (eth_defi.vault_report.vault_checks) removes vaults that are not investable in practice.

  4. exclude_amm_pools() leaves AMM pools only in their own section.

  5. The select_* and calculate_* functions apply per-section TVL, activity, risk and outlier thresholds from ReportCriteria.

TVL history charts, calculate_protocol_tvl_history() and friends, only drop blacklisted vaults, select_tvl_history_vaults(), so their totals match the website’s historical TVL charts.

Units: returns, volatilities and drawdowns are fractions (0.05 = 5%), as in the top vaults export; TVL is in USD; timestamps are naive UTC.

Module Attributes

UNKNOWN_PROTOCOL_SLUG

Protocol slug for vaults whose protocol we could not identify

BLACKLISTED_RISK

Technical risk label for vaults excluded from all listings

MAX_DISPLAYED_SHARPE

Sharpe ratios above this are displayed as >100.

CAPPED_ANNUALISED_RETURN

The top vaults export caps annualised returns at 10,000%, as the fraction 100.0 (max_cagr in eth_defi.research.vault_metrics period metrics).

CAPPED_RETURN_LABEL

Label of a capped annualised return, like on the website

SPARKLINE_URL

Public vault sparkline images, rendered by eth_defi.research.sparkline_export PNG rather than SVG, because email clients such as Gmail strip SVG images from newsletters

LENDING_STRATEGY_TAGS

Strategy tags that make a vault a lending vault

RWA_STRATEGY_TAGS

Strategy tags that make a vault a real-world asset (RWA) vault, see eth_defi.vault.strategy_tag.StrategyTag.

LENDING_PROTOCOL_SLUGS

Protocols whose vaults are lending vaults even without a strategy tag.

AMM_POOL_FEATURE

Vault feature of AMM liquidity-provider shares, set by the scanner for GMX GM and GLV pools and Curve-based YieldBasis LTs, see eth_defi.erc_4626.core.ERC4626Feature.amm_pool_like

TOKENISED_FUND_FLAG

Vault flag for tokenised funds, such as money market and treasury funds

UNIDENTIFIED_PROTOCOL_SLUGS

Protocol slugs that mean the vault's protocol has not been identified yet: generic ERC-4626 and ERC-7540 vaults and placeholders.

UNIDENTIFIED_PROTOCOL_NAMES

Protocol names, lower case, that mean the same.

OTHER_PROTOCOL

Label of the single pile of vaults whose protocol is not identified.

LENDING

Vault group labels, stored in the group column by eth_defi.vault_report.data.prepare_vault_metrics(), see classify_vault().

VAULT_TABLE_COLUMNS

Default columns for vault tables, same as in the previous blog posts.

CHAIN_TABLE_COLUMNS

Columns for the per-chain table, chain first

NUMERIC_COLUMNS

Right-aligned numeric columns

NOWRAP_COLUMNS

Columns whose values must not wrap onto two lines, e.g.

CHART_RETURN

Column with the return used by all charts: the annualised three-month return, net of fees when known.

LEGACY_VAULT_URLS

Legacy vault page URLs and their current form.

Functions

calculate_average_yields(yield_vaults, ...)

Calculate the TVL-weighted average three-month yield per group.

calculate_chain_tvl_changes(eligible_df, ...)

Find the blockchains whose vault TVL changed the most over the last month, in dollars.

calculate_chain_tvl_history(tvl_history, ...)

Sum vault TVL history per blockchain.

calculate_chain_yields(yield_vaults, criteria)

Average yield of the largest blockchains by TVL.

calculate_fund_nav_history(tvl_history, funds_df)

Sum tokenised fund NAV history per fund.

calculate_high_yield_protocols(yield_vaults, ...)

Average yield of the highest-yielding vault protocols.

calculate_protocol_tvl_history(tvl_history, ...)

Sum vault TVL history per protocol.

calculate_protocol_yields(yield_vaults, criteria)

Average yield of the largest vault protocols by TVL.

calculate_tvl_changes(eligible_df, criteria)

Find the vaults with the largest TVL changes over the last month, in dollars.

canonical_vault_urls(text)

Rewrite legacy vault page URLs to the website's current /vaults/ paths.

classify_vault(vault)

Classify a vault into one of the report's vault groups.

exclude_amm_pools(vaults_df, criteria)

Leave AMM pools out of the rankings and charts outside their own section.

exclude_chart_risks(vaults_df, criteria)

Leave out vaults whose risk rating is too high to feature in a chart.

filter_eligible_vaults(vaults_df, ...)

Remove vaults that must not appear in any listing.

find_period(period_results, period)

Find one period's results in a vault's period_results.

format_return(net, gross)

Format an annualised return, preferring the net value.

format_sharpe(value)

Format a Sharpe ratio.

format_tvl(current)

Format the current TVL for a table cell.

format_vault_cells(row[, sparkline_ids])

Format one vault as HTML table cells.

group_min_tvl(group, criteria)

Minimum TVL of a vault group's table and chart: higher for AMM pools.

is_blacklisted(vaults_df)

Find blacklisted vaults.

is_identified_protocol(protocol, protocol_slug)

Check whether a vault's protocol has been identified.

rank_vaults(df[, column])

Sort vaults by a metric, best first.

render_section_table(section)

Render a report section as a HTML table for the blog.

select_average_yield_vaults(eligible_df, ...)

Select the vaults counted in the average yield charts.

select_chain_chart_vaults(eligible_df, criteria)

Best vaults on each chain for the per-chain chart.

select_comparable_vaults(eligible_df)

Select the vaults that may appear in performance comparisons.

select_group(eligible_df, criteria, group, *)

Select the best vaults of a vault group.

select_new_vaults(eligible_df, criteria)

Best stablecoin yield vaults launched recently.

select_risk_return_vaults(yield_universe, ...)

Vaults of the risk and return chart: the yield vaults and the perp DEX vaults.

select_tvl_history_vaults(vaults_df)

Select vaults whose TVL history is counted in the market TVL chart.

select_vaults_by_chain(eligible_df, criteria)

Best vaults on each chain, including perpetual DEX chains.

select_yield_vaults(eligible_df, criteria)

Stablecoin yield vaults: every group except perpetual futures DEX vaults.

web_link(text, url)

Escaped HTML link, or the escaped text alone when the URL is not a web link.

Classes

ReportCriteria

Selection thresholds for the report sections.

ReportSection

One ranked vault table in the report.

UNKNOWN_PROTOCOL_SLUG = 'protocol-not-yet-identified'

Protocol slug for vaults whose protocol we could not identify

BLACKLISTED_RISK = 'Blacklisted'

Technical risk label for vaults excluded from all listings

MAX_DISPLAYED_SHARPE = 100

Sharpe ratios above this are displayed as >100.

Lending vaults with near-zero volatility get Sharpe ratios in the millions, which carry no information beyond “very smooth”.

CAPPED_ANNUALISED_RETURN = 100.0

The top vaults export caps annualised returns at 10,000%, as the fraction 100.0 (max_cagr in eth_defi.research.vault_metrics period metrics).

Annualising a large one-month gain, common for perp DEX vaults and broken share tokens, overflows to absurd numbers, so the exporter clamps it. Values at the cap are displayed as CAPPED_RETURN_LABEL, and rank_vaults() breaks the resulting ties.

CAPPED_RETURN_LABEL = '>9,999%'

Label of a capped annualised return, like on the website

SPARKLINE_URL = 'https://vault-sparklines.tradingstrategy.ai/sparkline-table-90d-{vault_id}.png'

Public vault sparkline images, rendered by eth_defi.research.sparkline_export PNG rather than SVG, because email clients such as Gmail strip SVG images from newsletters

LENDING_STRATEGY_TAGS = frozenset({'lending', 'lending_looping', 'lending_optimisation'})

Strategy tags that make a vault a lending vault

RWA_STRATEGY_TAGS = frozenset({'rwa', 'rwa_credit', 'rwa_lending', 'rwa_royalties'})

Strategy tags that make a vault a real-world asset (RWA) vault, see eth_defi.vault.strategy_tag.StrategyTag.

RWA lending vaults are RWA vaults, not lending vaults: their risk is the real-world borrowers and collateral. Tokenised funds and perp DEX vaults financing real-world credit keep their own groups.

LENDING_PROTOCOL_SLUGS = frozenset({'3jane', '40acres', 'aave', 'arcadia-finance', 'curvance', 'dolomite', 'euler', 'fluid', 'frax', 'gearbox', 'llama-lend', 'morpho', 'sentiment', 'silo-finance', 'spark', 'term-finance'})

Protocols whose vaults are lending vaults even without a strategy tag.

Not every protocol adapter sets strategy tags, so the report also recognises the lending markets it knows, matched against the export’s protocol_slug. Tagging these adapters would make this list unnecessary. Untagged RWA vaults of these protocols land in lending until tagged with the categorise-vault-strategy skill.

AMM_POOL_FEATURE = 'amm_pool_like'

Vault feature of AMM liquidity-provider shares, set by the scanner for GMX GM and GLV pools and Curve-based YieldBasis LTs, see eth_defi.erc_4626.core.ERC4626Feature.amm_pool_like

TOKENISED_FUND_FLAG = 'tokenised_fund'

Vault flag for tokenised funds, such as money market and treasury funds

UNIDENTIFIED_PROTOCOL_SLUGS = frozenset({'erc-4626', 'protocol-not-yet-identified', 'unknown', 'unknown-erc-7450'})

Protocol slugs that mean the vault’s protocol has not been identified yet: generic ERC-4626 and ERC-7540 vaults and placeholders. The same set as the website’s isUnknownVaultProtocol() in src/lib/top-vaults/helpers.ts.

UNIDENTIFIED_PROTOCOL_NAMES = frozenset({'', 'unknown', 'unknown vault protocol'})

Protocol names, lower case, that mean the same. Names starting with <, such as <unknown ERC-4626> placeholders, are also unidentified, see is_identified_protocol().

OTHER_PROTOCOL = 'Other'

Label of the single pile of vaults whose protocol is not identified. Charts also sum their small protocols into it, so unidentified vaults never show as a protocol of their own.

LENDING = 'lending'

Vault group labels, stored in the group column by eth_defi.vault_report.data.prepare_vault_metrics(), see classify_vault(). Each group except OTHER has its own best-performing section in eth_defi.vault_report.post.BEST_SECTIONS.

class ReportCriteria

Bases: object

Selection thresholds for the report sections.

One place for every editorial threshold, so a month’s post can be tuned with dataclasses.replace() without touching the selectors. The investability check reuses the same selectors with deeper top_n and chain_top_n values to collect replacement candidates, see eth_defi.vault_report.report.collect_top_lists().

The defaults reproduce the rules in README-blog-post-outline.md; the section notes in eth_defi.vault_report.report.make_criteria_notes() and the chart subtitles quote these values, so changing one changes the post text too.

Returns, volatilities and drawdowns are fractions (0.25 = 25%), TVL is in USD.

min_tvl: float

Minimum current TVL for the best-performing vault tables, the yield universe of the hero image and the risk and return chart.

Large vaults have no table of their own: this threshold and the TVL column cover them, see the outline’s change log.

min_events: int

Minimum lifetime deposit and redemption events (export event_count) for lending, RWA, other and new vaults.

Filters out inactive vaults, whose returns no depositors stand behind. Perp DEX vaults and tokenised funds have no comparable onchain ERC-4626 events and are exempt, see select_group().

top_n: int

Number of vaults in each best-performing table

max_data_age: datetime.timedelta

Exclude vaults whose latest metrics (export end_date) are older than this, relative to the report data date.

A vault the scanner stopped reading keeps its last, possibly flattering, returns in the export, which would otherwise keep ranking.

chain_min_tvl: float

Minimum TVL for the per-chain table and chart

chain_top_n: int

Vaults listed per chain

new_vault_min_tvl: float

Minimum TVL for the new vaults table, lower than min_tvl because a vault launched weeks ago has not had time to grow

include_amm_pools: bool

Include AMM pools in the rankings and charts outside their own section. AMM pools, such as GMX GM pools and YieldBasis, carry the market risk of the traded assets, so by default they are ranked only in the AMM pools section and counted only in the TVL summaries.

amm_min_tvl: float

Minimum TVL for the AMM pools table

new_vault_max_age: datetime.timedelta

Maximum age of a vault in the new vaults table

performance_chart_vaults: int

Number of vaults in each performance chart; more equity curves in one shared-axis chart become unreadable

crypto_benchmark_min_volatility: float

Vaults at or above this annualised three-month volatility are compared with BTC and ETH instead of the Treasury bill.

The report’s own extension of the website’s benchmark rules: a trading-like stablecoin vault is better judged against the crypto market it trades, see eth_defi.vault_report.benchmarks.select_benchmarks().

crypto_benchmark_max_drawdown: float

Vaults with a three-month drawdown, a negative fraction, at or below this are compared with BTC and ETH instead of the Treasury bill

chart_max_return: float

Leave vaults above this annualised three-month return, 400%, out of the hero image, where one outlier number would dwarf the others

chart_min_excluded_risk: int

Lowest technical risk level left out of the charts: the hero image, the performance charts and risk and return. Dangerous and above, see eth_defi.vault.risk.VaultTechnicalRisk. Charts get shared without the tables, so tables keep these vaults. Unrated vaults are shown.

hero_max_volatility: float

Leave vaults above this annualised three-month volatility out of the hero image

scatter_max_return: float

Clip the risk and return scatter’s y axis at most at this annualised return, 100%; vaults above are left out

yield_top_chains: int

Number of blockchains in the average yield chart, the largest by TVL

yield_top_protocols: int

Number of protocols in the average yield chart, the largest by TVL

yield_min_protocol_tvl: float

Minimum TVL of a protocol in the high TVL average yield chart

yield_high_yield_min_protocol_tvl: float

Minimum TVL of a protocol in the high yield average yield chart, which ranks protocols by yield.

Lower than yield_min_protocol_tvl so smaller, high-yield protocols can appear; the chart’s TVL column shows how much money earns the yield.

yield_min_vault_tvl: float

Minimum TVL per vault to be counted in the average yield charts.

Lower than the table minimum because the averages are TVL-weighted: small vaults barely move them but still show as dots.

yield_max_return: float

Exclude outlier vaults above this annualised three-month return, 400%, from the average yields

yield_max_volatility: float

Exclude vaults above this annualised three-month volatility from the average yields.

Keeps volatile crypto index funds and AMM liquidity positions out of the stablecoin yield figures.

yield_chart_max_return: float

Cap of the average yield charts’ x axis for the largest chains and protocols, 40%. The axis otherwise fits the data, see eth_defi.vault_report.charts.create_average_yield_figure(); vault dots beyond it are not drawn but still count in the averages. The high yield chart is capped at yield_max_return instead.

tvl_change_top_n: int

Number of largest inflows and of largest outflows in the TVL change chart

check_buffer_ratio: float

Investability check: extra candidates collected for each top list, as a share of the vaults it shows, see eth_defi.vault_report.vault_checks. Checking 50% deeper than shown lets excluded vaults be replaced without another agent round.

check_max_rounds: int

Investability check: rounds of refilling the top lists after exclusions. Each round runs the agent, minutes of LLM time, so the number is capped; unchecked in-scope vaults after the last round stop the report.

check_prescreen_min_tvl: float

Investability check: minimum TVL of an aggregate chart vault to prescreen

check_max_escalations: int

Investability check: maximum number of prescreened vaults sent to the agent

__init__(min_tvl=100000, min_events=10, top_n=20, max_data_age=datetime.timedelta(days=7), chain_min_tvl=100000, chain_top_n=3, new_vault_min_tvl=15000, include_amm_pools=False, amm_min_tvl=1000000, new_vault_max_age=datetime.timedelta(days=60), performance_chart_vaults=8, crypto_benchmark_min_volatility=0.25, crypto_benchmark_max_drawdown=- 0.1, chart_max_return=4.0, chart_min_excluded_risk=50, hero_max_volatility=0.5, scatter_max_return=1.0, yield_top_chains=10, yield_top_protocols=10, yield_min_protocol_tvl=1000000, yield_high_yield_min_protocol_tvl=150000, yield_min_vault_tvl=10000, yield_max_return=4.0, yield_max_volatility=0.5, yield_chart_max_return=0.4, tvl_change_top_n=10, check_buffer_ratio=0.5, check_max_rounds=3, check_prescreen_min_tvl=1000000, check_max_escalations=20)
Parameters
  • min_tvl (float) –

  • min_events (int) –

  • top_n (int) –

  • max_data_age (datetime.timedelta) –

  • chain_min_tvl (float) –

  • chain_top_n (int) –

  • new_vault_min_tvl (float) –

  • include_amm_pools (bool) –

  • amm_min_tvl (float) –

  • new_vault_max_age (datetime.timedelta) –

  • performance_chart_vaults (int) –

  • crypto_benchmark_min_volatility (float) –

  • crypto_benchmark_max_drawdown (float) –

  • chart_max_return (float) –

  • chart_min_excluded_risk (int) –

  • hero_max_volatility (float) –

  • scatter_max_return (float) –

  • yield_top_chains (int) –

  • yield_top_protocols (int) –

  • yield_min_protocol_tvl (float) –

  • yield_high_yield_min_protocol_tvl (float) –

  • yield_min_vault_tvl (float) –

  • yield_max_return (float) –

  • yield_max_volatility (float) –

  • yield_chart_max_return (float) –

  • tvl_change_top_n (int) –

  • check_buffer_ratio (float) –

  • check_max_rounds (int) –

  • check_prescreen_min_tvl (float) –

  • check_max_escalations (int) –

Return type

None

VAULT_TABLE_COLUMNS = ['Vault', '3M history', '1M ann.', '3M ann.', 'Lifetime ann.', '3M Sharpe', 'TVL', 'Age (y)', 'Token', 'Chain', 'Protocol']

Default columns for vault tables, same as in the previous blog posts. Cell values come from format_vault_cells(); there is deliberately no risk rating column, the rating only filters the charts.

CHAIN_TABLE_COLUMNS = ['Chain', 'Protocol', 'Vault', '1M ann.', '3M ann.', 'Lifetime ann.', 'TVL', 'Age (y)', 'Token']

Columns for the per-chain table, chain first

NUMERIC_COLUMNS = {'3M Sharpe', 'Age (y)'}

Right-aligned numeric columns

NOWRAP_COLUMNS = {'1M ann.', '3M ann.', 'Lifetime ann.', 'TVL'}

Columns whose values must not wrap onto two lines, e.g. >9,999% or $1.2M split at the comma or point

class ReportSection

Bases: object

One ranked vault table in the report.

Built by eth_defi.vault_report.report.build_report_sections(), rendered to HTML by render_section_table() and written to the bundle’s tables/*.csv.

vaults_df: pandas.DataFrame

Vaults in display order, rows from eth_defi.vault_report.data.VaultReportData.vaults_df, already truncated to the table length

columns: list[str]

Table columns, see VAULT_TABLE_COLUMNS

numbered: bool

Show a 1, 2, 3… rank column

sparkline_ids: frozenset[str]

Vault ids with a published sparkline image, see eth_defi.vault_report.data.fetch_available_sparklines(). Filled in after the tables are selected, so only their vaults are probed; other vaults get an empty “3M history” cell instead of a broken image.

__init__(vaults_df, columns=<factory>, numbered=True, sparkline_ids=frozenset({}))
Parameters
Return type

None

filter_eligible_vaults(vaults_df, data_end_at, criteria)

Remove vaults that must not appear in any listing.

The first filter of every ranked section and of the inflows and outflows; the report statistics count its result as the vaults with up-to-date data. Drops:

Staleness is measured against the report data date, the newest end_date in the export, not the wall clock, so regenerating an old report from a cached export gives the same result.

Parameters
Returns

Filtered vault metrics, the same columns.

Return type

pandas.DataFrame

exclude_amm_pools(vaults_df, criteria)

Leave AMM pools out of the rankings and charts outside their own section.

AMM pool returns include the price moves of the pooled crypto assets, so ranking them next to stablecoin yield would mislead. The AMM pools section itself selects from the unfiltered universe, see select_group(). See ReportCriteria.include_amm_pools.

Parameters
Returns

Vaults without AMM pools, or all vaults when AMM pools are included.

Return type

pandas.DataFrame

exclude_chart_risks(vaults_df, criteria)

Leave out vaults whose risk rating is too high to feature in a chart.

Charts get shared on social media without the tables around them, so a Dangerous vault in a chart looks like a recommendation. Tables keep these vaults. The comparison is written as ~(risk >= threshold) rather than risk < threshold so unrated vaults, NaN risk_numeric, are kept.

Parameters
Returns

Vaults rated below the threshold, and unrated vaults.

Return type

pandas.DataFrame

select_comparable_vaults(eligible_df)

Select the vaults that may appear in performance comparisons.

Vaults without an identified protocol, the OTHER_PROTOCOL pile, are often broken or misread: generic ERC-4626 wrappers, lending pool receipts and tokens with unusual share accounting. They are left out of every table and chart that compares returns, and counted only in TVL summaries: the TVL by protocol chart, inflows and outflows and the report statistics.

Parameters

eligible_df (pandas.DataFrame) – Output of filter_eligible_vaults().

Returns

Eligible vaults with an identified protocol.

Return type

pandas.DataFrame

is_identified_protocol(protocol, protocol_slug)

Check whether a vault’s protocol has been identified.

Generic ERC-4626 and ERC-7540 vaults, unknown and placeholder protocols are not identified. The report puts them all in one OTHER_PROTOCOL pile until their protocols are mapped, following the website’s isUnknownVaultProtocol() (src/lib/top-vaults/helpers.ts in the frontend), so the report and the website agree on what counts as a protocol. Both the name and the slug are checked, because a placeholder can show up in either. Mapping a protocol in the vault metadata moves its vaults out of the pile with no report change.

Called by eth_defi.vault_report.data.prepare_vault_metrics() to fill the protocol_identified column.

Parameters
  • protocol (Optional[str]) – Protocol name, export protocol.

  • protocol_slug (Optional[str]) – Protocol slug, export protocol_slug.

Returns

True for a named, mapped protocol.

Return type

bool

classify_vault(vault)

Classify a vault into one of the report’s vault groups.

Every vault belongs to exactly one group, which decides its best-performing section, its table thresholds in select_group() and whether it counts as stablecoin yield. The rules are checked in priority order, the first match wins:

  1. AMM: the scanner’s amm_pool_like vault feature. The amm strategy tag is deliberately not used: vaults tagged amm, such as Gains Network gTrade and KiloEx, stay in their other groups.

  2. PERP_DEX: the perp_dex_trading_vault flag, precomputed as is_perp_dex.

  3. TOKENISED_FUND: the tokenised_fund flag, so funds financing real-world credit stay with the other funds.

  4. RWA: an RWA strategy tag, checked before lending so RWA lending vaults count as RWA, see RWA_STRATEGY_TAGS.

  5. LENDING: a lending strategy tag, or a known lending protocol for adapters that set no tags, see LENDING_PROTOCOL_SLUGS.

  6. OTHER: everything else.

Applied row by row with pandas.DataFrame.apply() in eth_defi.vault_report.data.prepare_vault_metrics(). List columns come from JSON and are NaN or None when missing, hence the isinstance checks.

Parameters

vault (pandas.Series) – Vault metrics row with features, is_perp_dex, flags, strategy_tags and protocol_slug. features, flags and strategy_tags are lists of enum values or missing.

Returns

AMM, PERP_DEX, TOKENISED_FUND, RWA, LENDING or OTHER.

Return type

str

CHART_RETURN = 'three_months_cagr_best'

Column with the return used by all charts: the annualised three-month return, net of fees when known. Tables rank by the one-month return like the website; charts use the steadier three-month return, because one month of an annualised return is dominated by a single good or bad week. Filled by eth_defi.vault_report.data.prepare_vault_metrics().

rank_vaults(df, column='one_month_cagr_best')

Sort vaults by a metric, best first.

The single ranking used by every table and chart selector, so a vault’s position is the same wherever it appears.

The export caps annualised returns at 10,000%, CAPPED_ANNUALISED_RETURN, so several perp DEX or newly launched vaults can share the same top value. Ties are broken with the absolute, non-annualised return of the same period (export three_months_returns or one_month_returns), which is not capped: three months for metrics whose name starts with three_months, which includes the three-month Sharpe ranking, one month otherwise. Vaults without the metric sort last instead of being dropped, so callers decide whether to filter them.

Parameters
  • df (pandas.DataFrame) – Vault metrics with column and its tie-breaker column.

  • column (str) – Ranking metric, by default the annualised one-month return.

Returns

Sorted vault metrics, the same rows.

Return type

pandas.DataFrame

group_min_tvl(group, criteria)

Minimum TVL of a vault group’s table and chart: higher for AMM pools.

AMM pools need ReportCriteria.amm_min_tvl instead of ReportCriteria.min_tvl: they are exempt from the deposit event threshold, and the higher TVL stands in for it. Also quoted in the performance chart subtitles.

Parameters
Returns

Minimum TVL in USD.

Return type

float

select_group(eligible_df, criteria, group, *, by='one_month_cagr_best')

Select the best vaults of a vault group.

The selector of every best-performing section in eth_defi.vault_report.post.BEST_SECTIONS: the table with by the section’s table metric, one-month return or three-month Sharpe, and the performance chart with by CHART_RETURN. The result is not truncated, so the investability check can take deeper candidate lists from the same ranking.

Applies the TVL threshold, ReportCriteria.amm_min_tvl for AMM pools, and the activity threshold for lending, RWA and other vaults. Perpetual futures DEX vaults and tokenised funds have no comparable onchain deposit events, and AMM pools have the higher TVL threshold instead. Vaults without the ranking metric are dropped, because a table or chart cannot place them.

Parameters
Returns

All matching vaults, best first, not truncated.

Return type

pandas.DataFrame

select_yield_vaults(eligible_df, criteria)

Stablecoin yield vaults: every group except perpetual futures DEX vaults.

Perp DEX vaults are trading strategies, not yield, so they are left out of the hero image, whose headline is the best stablecoin yield. Used for the hero image and as the base of select_risk_return_vaults(). Applies the table thresholds; tokenised funds are exempt from the deposit event threshold, like in select_group().

Parameters
Returns

Vaults above the TVL and activity thresholds, best first.

Return type

pandas.DataFrame

select_risk_return_vaults(yield_universe, ranked_df, criteria)

Vaults of the risk and return chart: the yield vaults and the perp DEX vaults.

Perp DEX vaults are selected like their tables, with the table TVL minimum and no deposit events, and need the three-month return the chart plots. The chart shows them as one Perpetual futures category, see eth_defi.vault_report.charts.create_risk_return_figure(), so readers can see how much more volatility trading vaults take for their return. The caller then drops Dangerous vaults and vaults whose share price did not move.

Parameters
Returns

Vaults, best first.

Return type

pandas.DataFrame

select_new_vaults(eligible_df, criteria)

Best stablecoin yield vaults launched recently.

The New vaults table, and the base of its performance chart. Vault age comes from the export years field, lifetime in years, so the maximum age ReportCriteria.new_vault_max_age is converted to years of 365 days. Perp DEX vaults are left out as trading strategies, and the lower ReportCriteria.new_vault_min_tvl lets young vaults in; the deposit event threshold still applies to every group here, so a new vault needs real depositors.

Parameters
Returns

Top ReportCriteria.top_n new vaults, best first by the one-month return.

Return type

pandas.DataFrame

select_vaults_by_chain(eligible_df, criteria)

Best vaults on each chain, including perpetual DEX chains.

The Vaults on each chain table. Every group is ranked together, with no deposit event threshold, so chains like Hyperliquid whose vaults are perp DEX vaults are represented too.

Parameters
Returns

Up to ReportCriteria.chain_top_n vaults per chain, sorted by chain name, then by one-month return.

Return type

pandas.DataFrame

select_chain_chart_vaults(eligible_df, criteria)

Best vaults on each chain for the per-chain chart.

Like select_vaults_by_chain(), but ranked by the charts’ annualised three-month return, CHART_RETURN, and leaving out vaults rated too risky for charts. The chart names the two best vaults on each chain and draws the rest as runners-up, see eth_defi.vault_report.charts.create_chain_best_figure().

Parameters
Returns

Up to ReportCriteria.chain_top_n vaults per chain, best first within each chain.

Return type

pandas.DataFrame

select_average_yield_vaults(eligible_df, criteria)

Select the vaults counted in the average yield charts.

The universe of the Yield by chain and protocol dot plots, and of the investability check’s prescreen of large chart vaults. The averages are meant to describe what stablecoin holders earn, so the filters remove what would skew a TVL-weighted mean:

There is no deposit event threshold here, unlike the tables; a small vault barely moves a TVL-weighted average. Vaults with a missing three-month return or volatility fail the comparisons and are dropped.

Parameters
Returns

Selected vaults, unsorted.

Return type

pandas.DataFrame

calculate_average_yields(yield_vaults, group_column)

Calculate the TVL-weighted average three-month yield per group.

Weighting by current TVL answers “what does a dollar in this chain or protocol earn”, which is what the post compares with the T-bill; an unweighted mean would let many small vaults outvote the few large ones that hold most of the money. Computed as sum(tvl * return) / sum(tvl) in one groupby, with the products precomputed in a weighted column.

Parameters
Returns

DataFrame indexed by group with columns tvl (USD), avg_return (TVL-weighted annualised three-month return, 0.05 = 5%) and vault_count.

Return type

pandas.DataFrame

calculate_chain_yields(yield_vaults, criteria)

Average yield of the largest blockchains by TVL.

Automates the “average vault yield per blockchain” figure, previously a screenshot of the chain overview. Unlike the protocol charts, unidentified protocols need no extra filter here: the input is already comparable vaults only.

Parameters
Returns

See calculate_average_yields(), the top ReportCriteria.yield_top_chains chains by TVL.

Return type

pandas.DataFrame

calculate_protocol_yields(yield_vaults, criteria)

Average yield of the largest vault protocols by TVL.

The Yield by protocol, high TVL dot plot. Placeholder protocols, such as generic ERC-4626 vaults of unidentified protocols, are left out, so the OTHER_PROTOCOL pile never shows up as a protocol. The TVL floor is applied before picking the largest, so a month with few large protocols shows fewer rows rather than small ones.

Parameters
Returns

See calculate_average_yields(), the top ReportCriteria.yield_top_protocols protocols by TVL with at least ReportCriteria.yield_min_protocol_tvl TVL.

Return type

pandas.DataFrame

calculate_high_yield_protocols(yield_vaults, criteria)

Average yield of the highest-yielding vault protocols.

The Yield by protocol, high yield dot plot. Complements calculate_protocol_yields(), which shows the largest protocols: this ranks all identified protocols with at least ReportCriteria.yield_high_yield_min_protocol_tvl TVL by their TVL-weighted average yield. Small protocols with a few vaults can top this chart; the TVL floor keeps single test vaults out, and the chart’s TVL column shows how much money earns the yield.

Parameters
Returns

See calculate_average_yields(), the top ReportCriteria.yield_top_protocols protocols by average yield.

Return type

pandas.DataFrame

LEGACY_VAULT_URLS = ((re.compile('https://tradingstrategy\\.ai/trading-view/[a-z0-9-]+/vaults/'), 'https://tradingstrategy.ai/vaults/'), (re.compile('https://tradingstrategy\\.ai/trading-view/(?!vaults\\b)([a-z0-9-]+)/vaults\\b'), 'https://tradingstrategy.ai/vaults/chains/\\1'), (re.compile('https://tradingstrategy\\.ai/trading-view/vaults\\b'), 'https://tradingstrategy.ai/vaults'))

Legacy vault page URLs and their current form. The website moved vault pages from /trading-view/ and redirects the old paths; exports and posts made before the move still carry them.

The post’s writing rules require the current paths, and rewriting saves readers a redirect per click. Applied in order: vault pages first, because the per-chain listing pattern also matches the start of a vault page path and would turn it into a /vaults/chains/... link.

canonical_vault_urls(text)

Rewrite legacy vault page URLs to the website’s current /vaults/ paths.

Used on the export’s trading_strategy_link values and on the HTML copied from the previous post, see LEGACY_VAULT_URLS.

Parameters

text (str) – A URL, or HTML with links.

Returns

The text with legacy vault URLs rewritten, following the website’s redirects.

Return type

str

find_period(period_results, period)

Find one period’s results in a vault’s period_results.

The top vaults export stores per-period metrics as a list of dicts, one per period, see eth_defi.research.vault_metrics.PeriodMetrics, rather than flat columns. The report reads the one-month TVL and the three-month maximum drawdown from here.

Parameters
  • period_results (Optional[list[dict]]) – period_results list of a top vaults JSON record, or None.

  • period (str) – Period name, e.g. 1M or 3M.

Returns

The period’s results, or an empty dict if missing.

Return type

dict

calculate_tvl_changes(eligible_df, criteria)

Find the vaults with the largest TVL changes over the last month, in dollars.

The Inflows and outflows by vault chart. Uses the one-month period’s tvl_start and tvl_end from the top vaults export period_results. A TVL change includes both deposits and redemptions and the vault’s own returns. Net flows would be cleaner, but the export’s estimated flow_value covers only about a fifth of the vaults, so dollar TVL changes are used for every vault alike.

Some funds are listed under several share token addresses with the same TVL; duplicates with the same name, chain and change are counted once. Separate share classes of one fund have different TVLs and each appear.

Increases and decreases are ranked separately, so a month with one huge outflow still shows the largest inflows. A single change far larger than the rest is drawn off scale by eth_defi.vault_report.charts.create_tvl_change_figure(), which labels the bars with name, falling back to address.

Parameters
Returns

The ReportCriteria.tvl_change_top_n largest increases and decreases, with tvl_start, tvl_end and tvl_change columns in USD, sorted from the largest increase to the largest decrease.

Return type

pandas.DataFrame

calculate_chain_tvl_changes(eligible_df, criteria)

Find the blockchains whose vault TVL changed the most over the last month, in dollars.

The Inflows and outflows by blockchain chart. Sums the one-month TVL changes of each chain’s vaults, after the same deduplication as calculate_tvl_changes(), so a chain’s bar is its net change: inflows and outflows of its vaults cancel out.

Parameters
Returns

The ReportCriteria.tvl_change_top_n largest net increases and decreases, indexed by chain name, with name, address, tvl_start, tvl_end and tvl_change columns in USD, sorted from the largest increase to the largest decrease.

Return type

pandas.DataFrame

format_return(net, gross)

Format an annualised return, preferring the net value.

The net return is shown when fee data exists and the gross return otherwise, without a net or gross marker, like the website. Values at the export’s 10,000% cap, CAPPED_ANNUALISED_RETURN, are shown as CAPPED_RETURN_LABEL, because the capped number itself is not the vault’s return.

Parameters
Returns

E.g. 12.3%, >9,999% for capped values, or ---.

Return type

str

format_sharpe(value)

Format a Sharpe ratio.

Near-zero volatility lending vaults can have Sharpe ratios in the millions, which are displayed as >100.

Parameters

value (Optional[float]) – Sharpe ratio.

Returns

Formatted value.

Return type

str

format_tvl(current)

Format the current TVL for a table cell.

Tables show the current TVL only, in dollars with k, M and B suffixes rather than full digits, see the writing rules in README-blog-post-outline.md. The bundle’s CSV files keep the exact values.

Parameters

current (Optional[float]) – Current TVL.

Returns

E.g. $275k, $1.2M or $2.1B.

Return type

str

Escaped HTML link, or the escaped text alone when the URL is not a web link.

Links come from exports and other data: accept only https:// links, never e.g. javascript: URLs.

Parameters
Returns

HTML.

Return type

str

format_vault_cells(row, sparkline_ids=frozenset({}))

Format one vault as HTML table cells.

Every value is HTML-escaped here, because names, symbols and links come from onchain data and the export, and the cells are pasted into the Ghost post as raw HTML. Vault names link to the vault page, chains and protocols to their website pages; unidentified protocols show as OTHER_PROTOCOL without a link.

Parameters
Returns

Column label -> escaped cell HTML, for all columns in VAULT_TABLE_COLUMNS.

Return type

dict[str, str]

render_section_table(section)

Render a report section as a HTML table for the blog.

The table markup matches the tables in the previous blog posts, so the blog styles them the same way. The blog frontend (BlogPostContent.svelte) wraps post tables in a horizontally scrollable container on narrow screens.

Parameters

section (eth_defi.vault_report.sections.ReportSection) – Report section.

Returns

HTML <table> string.

Return type

str

is_blacklisted(vaults_df)

Find blacklisted vaults.

A vault is blacklisted when the export rates it Blacklisted, which includes vaults with bad flags when the export was made (see eth_defi.research.vault_metrics.apply_bad_flag_check()), or when this repository’s eth_defi.vault.flag gives it a bad flag. The second rule applies blacklist entries added after the export, e.g. by the investability check, right away, without waiting for the next export cycle. Only flags in eth_defi.vault.flag.BAD_FLAGS count: review_needed vaults stay in the report.

Used by filter_eligible_vaults() and select_tvl_history_vaults(). The flag lookup is a Python call per vault, keyed by address and protocol.

Parameters

vaults_df (pandas.DataFrame) – Vault metrics with risk, address and protocol columns.

Returns

Boolean series, True for blacklisted vaults.

Return type

pandas.Series

select_tvl_history_vaults(vaults_df)

Select vaults whose TVL history is counted in the market TVL chart.

Excludes blacklisted vaults, see is_blacklisted(), like the website’s historical TVL charts, so the totals match the website. Unlike the rankings, stale vaults, unidentified protocols, AMM pools and vaults the investability check excluded are kept: the TVL charts report where money is, not where to invest, and a stale vault still has a history up to when it stopped.

Parameters

vaults_df (pandas.DataFrame) – All vault metrics, eth_defi.vault_report.data.VaultReportData.vaults_df.

Returns

Vaults to include.

Return type

pandas.DataFrame

calculate_protocol_tvl_history(tvl_history, vaults_df, top_n=7)

Sum vault TVL history per protocol.

The Stablecoin TVL by DeFi vault protocol chart. The caller passes DeFi vaults only; tokenised funds have their own NAV chart, because their share classes would dominate this one. Grouping by protocol_label sends unidentified protocols to the OTHER_PROTOCOL band, so they count in the total without showing as a protocol.

Parameters
Returns

DataFrame with one column per protocol, largest first, then Other.

Return type

pandas.DataFrame

calculate_chain_tvl_history(tvl_history, vaults_df, top_n=7)

Sum vault TVL history per blockchain.

The Stablecoin TVL by blockchain chart: the same DeFi vaults as calculate_protocol_tvl_history(), grouped by where they live.

Parameters
Returns

DataFrame with one column per blockchain, largest first, then Other if more blockchains remain.

Return type

pandas.DataFrame

calculate_fund_nav_history(tvl_history, funds_df, top_n=7)

Sum tokenised fund NAV history per fund.

The Stablecoin NAV by tokenised fund chart. A fund deployed on several chains under the same name is counted as one fund, because readers know the fund, not its deployments. A fund without a name falls back to its address.

Parameters
Returns

DataFrame with one column per fund, largest first, then Other if more funds remain.

Return type

pandas.DataFrame