research.vault_metrics

Documentation for eth_defi.research.vault_metrics Python module.

Vault metrics calculations.

Module Attributes

MAX_VALID_NAV

Vaults with TVL above this are considered broken smart contracts

MAX_VALID_SHARE_PRICE

Vaults with share price above this are considered broken smart contracts

MAX_VALID_VOLATILITY

Vaults with annualised volatility above this are considered broken.

VAULT_SCAN_CYCLE_NOTES

Human-readable note suffixes for vault scan cycles slower than active hourly scans.

LOOKBACK_AND_TOLERANCES

Period -> Perioud duration, max sparse sample mismatch

SPECIAL_VAULT_PROTOCOL_SLUGS

Protocol slugs for vaults where we do not necessarily have on-chain deposit/redeem event data.

FFN_GROUP_HEADINGS

Group headings for FFN performance stats, matching the None separators in PerformanceStats._stats().

Functions

analyse_vault(vault_db, prices_df, spec[, ...])

Create charts and tables to analyse a vault performance.

apply_abnormal_value_checks(risk, notes, flags)

Check for broken vaults by detecting abnormal metric values.

apply_bad_flag_check(risk, notes, flags)

Blacklist vaults that carry scanned bad flags.

apply_morpho_not_in_api_check(risk, notes, flags)

Blacklist Morpho vaults missing from Morpho API.

calculate_cumulative_returns(cleaned_returns)

Takes a returns series and calculates cumulative returns.

calculate_daily_returns_for_all_vaults(df_work)

Calculate daily returns for each vault in isolation

calculate_hourly_returns_for_all_vaults(df_work)

Calculate hourly returns for each vault in isolation

calculate_lifetime_metrics(df, vault_db[, ...])

Calculate lifetime metrics for each vault in the provided DataFrame.

calculate_net_profit(start, end, ...[, ...])

Calculate profit after external fees have been reduced from the share price change.

calculate_net_returns_from_gross(name, ...)

Convert a cumulative gross return series to a cumulative net return series after fees.

calculate_net_returns_from_price(name, ...)

Convert a share price series to net return series after fees.

calculate_performance_metrics_for_all_vaults(...)

Calculate performance metrics for each vault.

calculate_period_metrics(period, ...[, ...])

Calculate metrics for one period.

calculate_returns(share_price[, freq])

Calculate returns from resampled share price series.

calculate_sharpe_ratio_from_returns(...[, ...])

Calculate annualized Sharpe ratio from hourly returns.

calculate_vault_rankings(results_df[, ...])

Calculate rankings for all periods inside PeriodMetrics objects.

calculate_vault_record(prices_df, ...[, ...])

Process a single vault metadata + prices to calculate its full data.

clean_lifetime_metrics(lifetime_data_df[, ...])

Clean lifetime data so we have only valid vaults.

combine_return_columns(gross, net[, ...])

Create combined net / (gross) returns column for display.

create_fee_label(fee_data)

Create 2% / 20% style labels to display variosu kinds of vault fees.

cross_check_data(vault_db, prices_df[, printer])

Check that VaultDatabase has metadata for all price_df vaults and vice versa.

display_lifetime_table(df)

Render a formatted lifetime table as compact HTML in a Jupyter notebook.

display_vault_chart_and_tearsheet(...[, render])

Render a chart and tearsheet for a single vault.

export_lifetime_row(row)

Export lifetime metrics row to a fully JSON-serialisable dict.

extend_notes_with_vault_scan_cycle(notes, ...)

Append lower scan-cycle context to a vault note.

fmt_one_decimal_or_int(x)

Display fees to .1 accuracy if there are .1 fractions, otherwise as int.

format_bad_flag_note(bad_flags)

Create a generic note for vaults that carry bad scan flags.

format_ffn_performance_stats(report[, ...])

Format FFN report for human readable output.

format_ffn_performance_stats_grouped(report)

Format FFN report as logically grouped sections.

format_lifetime_table(df[, add_index, ...])

Format table for human readable output.

format_vault_database(vault_db[, index])

Format vault database for human readable output.

format_vault_header(vault_row)

Format vault header for human readable output.

get_latest_vault_poll_frequency(prices_df)

Get the latest known vault scan cycle from price rows.

get_period_metrics(period_results, period)

Get PeriodMetrics for a specific period from the results list.

is_special_vault(protocol_slug, vault_address)

Check if a vault is a special vault that may not have deposit/redeem event data.

make_vault_display_flags(red_flags, ...)

Build generic vault warning display entries for JSON export.

normalise_vault_poll_frequency(value)

Normalise a raw vault scan cycle value.

resample_returns(returns_1h[, freq])

Calculate returns from resampled returns series.

slugify_protocol(protocol)

Create a slug from protocol name for URLs.

slugify_vault(name, symbol, address, ...)

Create a slug from vault metadata for URLs.

slugify_vaults(vaults)

Create slugs for a set of vaults.

zero_out_near_zero_prices(s[, eps, ...])

Replace values with abs(x) < eps by 0.

Classes

ExportMetadata

Provenance metadata for the vault metrics JSON export.

ExportVersionMetadata

Git version stamp of the exporter Docker image.

NetflowMetrics

Deposit and withdrawal flow metrics for a time period.

PeriodMetrics

Tearsheet metrics for one period.

VaultMetricsExport

Top-level structure of the vault metrics JSON export.

VaultMetricsRecord

Per-vault record in the JSON export.

VaultReport

One vault data analysed

Percent

Percent as the floating point.

0.01 = 1%

FeeInput: TypeAlias = float | decimal.Decimal | typing.Literal['-'] | None

Fee values as received from current and legacy vault metadata.

The exported metadata normally uses floats, but cached database rows can contain decimal.Decimal values after JSON/YAML deserialisation.

MAX_VALID_NAV: float = 100000000000

Vaults with TVL above this are considered broken smart contracts

MAX_VALID_SHARE_PRICE: float = 1000000

Vaults with share price above this are considered broken smart contracts

MAX_VALID_VOLATILITY: float = 10000

Vaults with annualised volatility above this are considered broken. This catches low-TVL Hyperliquid vaults with one or few trades that produce extreme volatility numbers.

VAULT_SCAN_CYCLE_NOTES = {'early': 'The vault data might be updated infrequently because the vault has low TVL and is still in the initial sampling period.', 'faded': 'The vault data might be updated infrequently because the vault has not reached sustained TVL traction.', 'peaked': 'The vault data might be updated infrequently because the vault TVL has fallen substantially from its historical peak.', 'small_tvl': 'The vault data might be updated infrequently because the vault TVL is below the active-vault threshold.', 'tiny_tvl': 'The vault data might be updated infrequently because the vault current TVL is very small.'}

Human-readable note suffixes for vault scan cycles slower than active hourly scans.

class PeriodMetrics

Bases: object

Tearsheet metrics for one period.

error_reason: Optional[str]

Error reason if metrics could not be calculated, None if successful

period_start_at: Optional[pandas.Timestamp]

When was start share price sampled

period_end_at: Optional[pandas.Timestamp]

When was end share price sampled

share_price_start: Optional[float]

Share price at beginning

share_price_end: Optional[float]

Share price at end

raw_samples: int

Number of raw datapoints used

daily_samples: int

Number of daily datapoitns used

returns_gross: Optional[float]

How much absolute returns we had

cagr_gross: Optional[float]

Compounding annual returns

volatility: Optional[float]

Annualised volatility, calculated based on daily returns

sharpe: Optional[float]

Sharpe ratio

max_drawdown: Optional[float]

Period maximum drawdown

tvl_start: Optional[float]

TVL at the start of the period

tvl_end: Optional[float]

TVL at the end of the period

tvl_low: Optional[float]

Minimum TVL in the period

tvl_high: Optional[float]

Maximum TVL in the period

ranking_overall: Optional[int]

Rank among all vaults (1 = best), based on CAGR

ranking_chain: Optional[int]

Rank among vaults on the same chain (1 = best), based on CAGR

ranking_protocol: Optional[int]

Rank among vaults in the same protocol (1 = best), based on CAGR

avg_utilisation: Optional[float]

Average utilisation over the period (lending vaults only, 0.0–1.0)

__init__(period, error_reason=None, period_start_at=None, period_end_at=None, share_price_start=None, share_price_end=None, raw_samples=0, samples_start_at=None, samples_end_at=None, daily_samples=0, returns_gross=None, returns_net=None, cagr_gross=None, cagr_net=None, volatility=None, sharpe=None, max_drawdown=None, tvl_start=None, tvl_end=None, tvl_low=None, tvl_high=None, ranking_overall=None, ranking_chain=None, ranking_protocol=None, avg_utilisation=None)
Parameters
Return type

None

class NetflowMetrics

Bases: object

Deposit and withdrawal flow metrics for a time period.

Aggregates daily deposit/withdrawal event counts and USD values over a given period (e.g. "1d", "7d", "30d").

Only available for chains that support vault flow tracking (currently Hyperliquid). For other chains this will be None in the vault record.

period: str

Period label (e.g. "1d", "7d", "30d")

deposit_count: int

Number of deposit events in the period

withdrawal_count: int

Number of withdrawal events in the period

deposit_usd: float

Total USD deposited in the period

withdrawal_usd: float

Total USD withdrawn in the period (positive value)

net_flow_usd: float

Net flow (deposit_usd - withdrawal_usd)

__init__(period, deposit_count=0, withdrawal_count=0, deposit_usd=0.0, withdrawal_usd=0.0, net_flow_usd=0.0)
Parameters
  • period (str) –

  • deposit_count (int) –

  • withdrawal_count (int) –

  • deposit_usd (float) –

  • withdrawal_usd (float) –

  • net_flow_usd (float) –

Return type

None

LOOKBACK_AND_TOLERANCES: dict[Literal['1W', '1M', '3M', '6M', '1Y', 'lifetime'], tuple[pandas.DateOffset, pandas.Timedelta]] = {'1M': (<DateOffset: days=30>, Timedelta('60 days 00:00:00')), '1W': (<DateOffset: days=7>, Timedelta('12 days 00:00:00')), '1Y': (<DateOffset: days=365>, Timedelta('410 days 00:00:00')), '3M': (<DateOffset: days=90>, Timedelta('135 days 00:00:00')), '6M': (<DateOffset: days=180>, Timedelta('225 days 00:00:00')), 'lifetime': (<DateOffset: years=100>, Timedelta('36500 days 00:00:00'))}

Period -> Perioud duration, max sparse sample mismatch

class VaultMetricsRecord

Bases: TypedDict

Per-vault record in the JSON export.

Uses total=False so that only the most critical fields are typed explicitly — remaining fields are still present but not enforced by the type checker. This is an incremental approach; more fields can be promoted to required as the schema stabilises.

name: str

Vault display name

chain_id: int

Chain identifier (integer chain ID)

chain: str

Chain display name, e.g. "Ethereum"

deployment: Optional[str]

Native protocol deployment slug, or None for ordinary EVM vaults.

For now this is populated for Lighter so consumers can distinguish the original ethereum deployment from Lighter on robinhood while retaining Lighter’s synthetic vault-dataset chain ID.

deployment_chain_id: Optional[int]

Real EVM chain ID associated with a native protocol deployment.

For now this is populated for Lighter on Ethereum (1) and Lighter on Robinhood Chain (4663). Both deployments keep chain_id=9998 because they share the native Lighter dataset namespace; their synthetic address prefixes and these deployment fields distinguish individual vaults.

protocol: str

Protocol display name, e.g. "Morpho"

protocol_slug: str

Protocol slug, e.g. "morpho"

curator_slug: Optional[str]

Curator slug, e.g. "gauntlet". None when no curator is known.

curator_name: Optional[str]

Curator display name, e.g. "Gauntlet".

protocol_curator: Optional[bool]

Whether the curator is the vault protocol itself.

address: str

Vault contract address (checksummed hex)

vault_slug: str

Unique vault slug for URLs

cagr: Optional[float]

Compound Annual Growth Rate (lifetime)

lifetime_return: Optional[float]

Lifetime total return

three_months_cagr: Optional[float]

3-month CAGR

one_month_cagr: Optional[float]

1-month CAGR

volatility: Optional[float]

Annualised volatility

sharpe: Optional[float]

Sharpe ratio

risk: Optional[str]

Technical risk classification

vault_poll_frequency: Optional[str]

Latest adaptive vault scan cycle, e.g. "large_tvl" or "peaked".

current_nav: Optional[float]

Current net asset value in USD

peak_nav: Optional[float]

Peak net asset value in USD

management_fee: Optional[float]

Management fee percentage

performance_fee: Optional[float]

Performance fee percentage

denomination_decimals: Optional[int]

Denomination (reserve) token ERC-20 decimals, e.g. 6 for USDC. Consumers use this to convert human amounts to raw uint256; a missing value must NOT be defaulted to 18 (see denomination_token_address).

deposit_manager: Optional[dict]

Static support for this library’s deposit/redemption manager, or None.

This does not mean the vault is currently open or that an account has permission, funds, acceptable slippage, spare cap, or liquidity.

share_token_decimals: Optional[int]

Share token ERC-20 decimals (the vault’s own ERC-4626 token).

other_data: dict

Protocol-specific extension data (Morpho flags, etc.)

core3: Optional[eth_defi.core3.vault_protocol.Core3VaultSection]

Compact Core3 risk summary for the vault’s protocol, or None when no Core3 data is available. See Core3VaultSection.

denomination_token_rate: eth_defi.feed.stablecoin_rate.DenominationTokenRate

Stablecoin rate data for the vault denomination token.

period_results: list[dict]

Per-period tearsheet results

__init__(*args, **kwargs)
__new__(**kwargs)
clear()

Remove all items from the dict.

copy()

Return a shallow copy of the dict.

fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items()

Return a set-like object providing a view on the dict’s items.

keys()

Return a set-like object providing a view on the dict’s keys.

pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from mapping/iterable E and F.

If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values()

Return an object providing a view on the dict’s values.

class ExportVersionMetadata

Bases: TypedDict

Git version stamp of the exporter Docker image.

Produced by eth_defi.version_info.VersionInfo.as_dict(). All fields are None when the exporter runs outside a stamped Docker image, e.g. from a source checkout. Individual fields can also be None inside a stamped image when the corresponding build ARG was not passed.

tag: Optional[str]

Git tag at image build time, e.g. v0.30.

Often None: only set when the image was built from a tagged commit with the GIT_VERSION_TAG build ARG. Consumers should rely on commit_hash as the primary build identifier.

commit_message: Optional[str]

The latest git commit message at image build time.

commit_hash: Optional[str]

Git commit SHA hash at image build time.

__init__(*args, **kwargs)
__new__(**kwargs)
clear()

Remove all items from the dict.

copy()

Return a shallow copy of the dict.

fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items()

Return a set-like object providing a view on the dict’s items.

keys()

Return a set-like object providing a view on the dict’s keys.

pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from mapping/iterable E and F.

If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values()

Return an object providing a view on the dict’s values.

class ExportMetadata

Bases: TypedDict

Provenance metadata for the vault metrics JSON export.

Identifies which exporter build produced the file so stale deployments are diagnosable from the JSON alone.

version: eth_defi.research.vault_metrics.ExportVersionMetadata

Git version stamp of the exporter Docker image.

__init__(*args, **kwargs)
__new__(**kwargs)
clear()

Remove all items from the dict.

copy()

Return a shallow copy of the dict.

fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items()

Return a set-like object providing a view on the dict’s items.

keys()

Return a set-like object providing a view on the dict’s keys.

pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from mapping/iterable E and F.

If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values()

Return an object providing a view on the dict’s values.

class VaultMetricsExport

Bases: TypedDict

Top-level structure of the vault metrics JSON export.

Describes the shape of top_vaults_by_chain.json uploaded to R2.

generated_at: str

ISO 8601 UTC timestamp when the export was generated

metadata: eth_defi.research.vault_metrics.ExportMetadata

Export provenance metadata, see ExportMetadata.

core3_protocols: dict[str, eth_defi.core3.vault_protocol.Core3ExportRecord]

Core3 risk intelligence keyed by protocol slug. Only protocols present in the exported vaults are included.

curators: dict[str, 'CuratorExportRecord']

Curator metadata and recent feed entries keyed by curator slug. Only curators present in the exported vaults are included.

vaults: list[eth_defi.research.vault_metrics.VaultMetricsRecord]

List of per-vault metric records

__init__(*args, **kwargs)
__new__(**kwargs)
clear()

Remove all items from the dict.

copy()

Return a shallow copy of the dict.

fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items()

Return a set-like object providing a view on the dict’s items.

keys()

Return a set-like object providing a view on the dict’s keys.

pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from mapping/iterable E and F.

If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values()

Return an object providing a view on the dict’s values.

make_vault_display_flags(red_flags, yellow_flags, source)

Build generic vault warning display entries for JSON export.

Parameters
  • red_flags (list[str]) – Warning type strings with red severity.

  • yellow_flags (list[str]) – Warning type strings with yellow severity.

  • source (str) – Protocol or data-source slug that produced the warnings.

Returns

JSON-safe warning entries suitable for user interfaces.

Return type

list[dict[str, str]]

fmt_one_decimal_or_int(x)

Display fees to .1 accuracy if there are .1 fractions, otherwise as int.

Parameters

x (Optional[float]) –

Return type

str

slugify_protocol(protocol)

Create a slug from protocol name for URLs.

Parameters

protocol (str) – The protocol name.

Return type

str

slugify_vault(name, symbol, address, existing_slugs)

Create a slug from vault metadata for URLs.

Parameters
Return type

str

create_fee_label(fee_data)

Create 2% / 20% style labels to display variosu kinds of vault fees.

Order is: management / performance / deposit / withdrawal fees.

Parameters

fee_data (eth_defi.vault.fee.FeeData) –

resample_returns(returns_1h, freq='D')

Calculate returns from resampled returns series.

Parameters

returns_1h (pandas.Series) – The original returns series.

Return type

pandas.Series

calculate_returns(share_price, freq='D')

Calculate returns from resampled share price series.

Parameters

share_price (pandas.Series) –

Return type

pandas.Series

calculate_cumulative_returns(cleaned_returns, freq='D')

Takes a returns series and calculates cumulative returns.

Parameters

cleaned_returns (pandas.Series) –

zero_out_near_zero_prices(s, eps=1e-09, clip_negatives=True)

Replace values with abs(x) < eps by 0. Optionally clip negatives to 0.

Keeps NaN as-is, turns +/- inf into NaN.

Parameters
Return type

pandas.Series

calculate_net_profit(start, end, share_price_start, share_price_end, management_fee_annual, performance_fee, deposit_fee, withdrawal_fee, seconds_in_year=31557600.0, sample_count=None)

Calculate profit after external fees have been reduced from the share price change.

Parameters
Returns

Net profit as a floating point (0.10 = 10% profit).

Return type

float

calculate_net_returns_from_price(name, share_price, management_fee_annual, performance_fee, deposit_fee, withdrawal_fee, seconds_in_year=31557600.0, zero_epsilon=0.001, freq='h')

Convert a share price series to net return series after fees.

Parameters
Returns

Cumulative net profit as a floating point (0.10 = 10% profit).

Return type

pandas.Series

calculate_net_returns_from_gross(name, cumulative_returns, management_fee_annual, performance_fee, deposit_fee, withdrawal_fee, seconds_in_year=31557600.0)

Convert a cumulative gross return series to a cumulative net return series after fees.

This function correctly models a High-Water Mark (HWM) for performance fees, which requires an iterative calculation (a loop). This loop operates on Numpy arrays for maximum speed.

  • Management fees are accrued based on the time delta of each period.

  • Performance fees are charged only on profits above the highest net value.

  • Deposit fees are applied once at the start (t=0).

  • Withdrawal fees are applied once at the end (t=T).

Parameters
  • name (str) – Name for the returned pandas Series.

  • cumulative_returns (pandas.Series) – A pandas Series with a DatetimeIndex representing the cumulative gross return index (e.g., 1.0, 1.02, 1.05) OR cumulative gross profit (e.g., 0.0, 0.02, 0.05).

  • management_fee_annual (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Annual management fee as a decimal (e.g., 0.02 for 2%).

  • performance_fee (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Performance fee as a decimal (e.g., 0.20 for 20% of profits above the High-Water Mark).

  • deposit_fee (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Fee applied to the initial deposit as a decimal (e.g., 0.01 for 1%).

  • withdrawal_fee (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Fee applied to the final withdrawal as a decimal (e.g., 0.01 for 1%).

  • seconds_in_year – The number of seconds in a year for precise management fee accrual.

Returns

A pandas Series of the cumulative net profit (e.g., 0.10 for 10%).

Return type

pandas.Series

calculate_sharpe_ratio_from_returns(hourly_returns, risk_free_rate=0.0, year_multiplier=365)

Calculate annualized Sharpe ratio from hourly returns.

Parameters
  • hourly_returns (pandas.Series) – Pandas Series of hourly percentage returns.

  • risk_free_rate (float) – Annualized risk-free rate (default 2%).

  • year_multiplier (float) –

Returns

Sharpe ratio as a float.

Return type

float

slugify_vaults(vaults)

Create slugs for a set of vaults.

  • Always give the primary slug to the vault that was created first.

  • Mutates VaultRow data in-place

Parameters

vaults (dict[eth_defi.vault.base.VaultSpec, eth_defi.vault.vaultdb.VaultRow]) – The vault metadata entries.

Return type

Optional[list[eth_defi.vault.vaultdb.VaultRow]]

calculate_period_metrics(period, gross_fee_data, net_fee_data, share_price_hourly, share_price_daily, tvl, now_, utilisation=None)

Calculate metrics for one period.

Parameters
  • period (Literal['1W', '1M', '3M', '6M', '1Y', 'lifetime']) – Period identifier (1W, 1M, 3M, 6M, 1Y, lifetime)

  • gross_fee_data (eth_defi.vault.fee.FeeData) – Fee data before fee mode adjustments

  • net_fee_data (eth_defi.vault.fee.FeeData) – Fee data after fee mode adjustments (for net return calculations)

  • share_price_hourly (pandas.Series) – Hourly share price series with DatetimeIndex

  • share_price_daily (pandas.Series) – Daily share price series with DatetimeIndex

  • tvl (pandas.Series) – Total value locked series with DatetimeIndex

  • now – The reference timestamp (usually the last timestamp in the data)

  • utilisation (Optional[pandas.Series]) – Optional utilisation series (lending vaults only, values 0.0–1.0). When provided, avg_utilisation is computed for the period window.

  • now_ (pandas.Timestamp) –

Returns

PeriodMetrics dataclass with calculated metrics

Return type

eth_defi.research.vault_metrics.PeriodMetrics

apply_abnormal_value_checks(risk, notes, flags, current_nav=None, current_share_price=None, three_months_volatility=None)

Check for broken vaults by detecting abnormal metric values.

Automatically blacklists vaults with unrealistic TVL, share price, or volatility. These thresholds catch broken smart contracts and low-TVL vaults that produce meaningless metrics.

Called multiple times as more metrics become available (first with NAV/price, then again after 3M volatility is computed).

Parameters
Returns

Updated (risk, notes, flags) tuple.

Return type

tuple[eth_defi.vault.risk.VaultTechnicalRisk, str, set[eth_defi.vault.flag.VaultFlag]]

apply_morpho_not_in_api_check(risk, notes, flags)

Blacklist Morpho vaults missing from Morpho API.

Dynamic scan flags are not visible to get_vault_risk(), because that helper only reads the static manual flag table. This helper handles the Morpho-specific dynamic blacklist flag before metrics export.

Parameters
Returns

Updated risk, notes, and flags.

Return type

tuple[eth_defi.vault.risk.VaultTechnicalRisk | None, str | None, set[eth_defi.vault.flag.VaultFlag]]

apply_bad_flag_check(risk, notes, flags)

Blacklist vaults that carry scanned bad flags.

Scan-time flags may come from protocol adapters and are not visible to get_vault_risk(), which only checks the static manual flag table. Any flag listed in eth_defi.vault.flag.BAD_FLAGS means the vault must not be exported as a normal investable row.

Parameters
Returns

Updated risk, notes, and flags.

Return type

tuple[eth_defi.vault.risk.VaultTechnicalRisk | None, str | None, set[eth_defi.vault.flag.VaultFlag]]

format_bad_flag_note(bad_flags)

Create a generic note for vaults that carry bad scan flags.

This note is used before any protocol-specific metadata has been analysed. Later checks may replace it with a richer note if the generic bad flag and the protocol-specific warning describe the same underlying issue.

Parameters

bad_flags (set[eth_defi.vault.flag.VaultFlag]) – Bad vault flags to report.

Returns

Human-readable note.

Return type

str

normalise_vault_poll_frequency(value)

Normalise a raw vault scan cycle value.

The cleaned price parquet may carry missing values as None, NaN or pd.NA depending on how the column was materialised. This helper converts valid scan cycle values to stripped strings and hides missing values from JSON-facing metric records.

Parameters

value (object) – Raw vault_poll_frequency cell value from a price row.

Returns

Normalised scan cycle string, or None when missing.

Return type

Optional[str]

get_latest_vault_poll_frequency(prices_df)

Get the latest known vault scan cycle from price rows.

vault_poll_frequency is produced during the vault scan and persisted in cleaned price data. Some historical rows may be empty, so this helper walks the values backwards and returns the newest non-empty scan cycle.

Parameters

prices_df (pandas.DataFrame) – Price DataFrame for one vault, conforming to CleanedVaultPriceRow.

Returns

Latest scan cycle string, or None if the column is not available.

Return type

Optional[str]

extend_notes_with_vault_scan_cycle(notes, vault_poll_frequency)

Append lower scan-cycle context to a vault note.

Frontends use the note field as the short human explanation for unusual vault state. When a vault is intentionally scanned less often, this helper adds that explanation without replacing existing risk or manual notes.

Parameters
  • notes (Optional[str]) – Existing vault note, if any.

  • vault_poll_frequency (Optional[str]) – Latest adaptive scan cycle for the vault.

Returns

Existing note plus reduced scan-cycle context when applicable.

Return type

Optional[str]

calculate_vault_record(prices_df, vault_metadata_rows, month_ago, three_months_ago, vault_id=None, core3_protocols=None, stablecoin_rate_feeder=None)

Process a single vault metadata + prices to calculate its full data.

  • Exported to frontend, everything

Parameters
Returns

Series with calculated metrics

Return type

pandas.Series

calculate_lifetime_metrics(df, vault_db, returns_column='returns_1h', core3_protocols=None, stablecoin_rate_feeder=None)

Calculate lifetime metrics for each vault in the provided DataFrame.

  • All-time returns

  • 3M returns, latest

  • 1M returns, latest

  • Volatility (3M)

Lookback based on the last entry.

Each output row contains a denomination_token_rate value produced by eth_defi.feed.stablecoin_rate.StablecoinRateFeeder.get_denomination_token_rate_section(). The value is a eth_defi.feed.stablecoin_rate.DenominationTokenRate carrying both USD rate fields and, for non-USD stablecoins, native source currency rate fields.

Parameters
Returns

DataFrame, one row per vault.

Return type

pandas.DataFrame

get_period_metrics(period_results, period)

Get PeriodMetrics for a specific period from the results list.

Parameters
Returns

The matching PeriodMetrics or None if not found

Return type

Optional[eth_defi.research.vault_metrics.PeriodMetrics]

calculate_vault_rankings(results_df, min_tvl_chain_protocol=10000, min_tvl_overall=50000)

Calculate rankings for all periods inside PeriodMetrics objects.

Updates PeriodMetrics objects in-place within the period_results lists. Rankings are calculated for all 6 periods (1W, 1M, 3M, 6M, 1Y, lifetime).

Vaults are excluded from rankings if: - They have no CAGR data (zero or NaN) - They have an error_reason set - They are blacklisted (risk == VaultTechnicalRisk.blacklisted) - Their period TVL is below the threshold

Parameters
  • results_df (pandas.DataFrame) – DataFrame from calculate_lifetime_metrics()

  • min_tvl_chain_protocol (float) – Minimum TVL required for chain and protocol rankings (default: $10,000)

  • min_tvl_overall (float) – Minimum TVL required for overall rankings (default: $50,000)

Returns

DataFrame with rankings updated in PeriodMetrics objects

Return type

pandas.DataFrame

SPECIAL_VAULT_PROTOCOL_SLUGS = {'grvt', 'hibachi', 'hyperliquid', 'lighter'}

Protocol slugs for vaults where we do not necessarily have on-chain deposit/redeem event data.

These vaults get their data from off-chain APIs (e.g. GRVT, Hyperliquid, Lighter) or are hardcoded protocol entries that may lack standard ERC-4626 events.

is_special_vault(protocol_slug, vault_address)

Check if a vault is a special vault that may not have deposit/redeem event data.

  • GRVT and Hyperliquid vaults get data from off-chain APIs

  • Hardcoded protocol vaults may lack standard ERC-4626 deposit/redeem events

Parameters
  • protocol_slug (str) – Protocol slug (e.g. “grvt”, “hyperliquid”, “morpho”)

  • vault_address (str) – Vault contract address

Returns

True if this vault should bypass the minimum event count filter

Return type

bool

clean_lifetime_metrics(lifetime_data_df, broken_max_nav_value=99000000000, lifetime_min_nav_threshold=100.0, max_annualised_return=3.0, min_events=25, logger=<built-in function print>)

Clean lifetime data so we have only valid vaults.

Returns

Cleaned lifetime dataframe

Parameters

lifetime_data_df (pandas.DataFrame) –

Return type

pandas.DataFrame

combine_return_columns(gross, net, new_line=' ', mode='percent', profit_presentation='split')

Create combined net / (gross) returns column for display.

E.g. 8.3% (10.5%)

Parameters
Returns

Combined string series

format_lifetime_table(df, add_index=False, add_address=False, add_share_token=False, drop_blacklisted=True, profit_presentation='split', html_links=False)

Format table for human readable output.

See calculate_lifetime_metrics()

Parameters
  • add_index – Add 1, 2, 3… index column

  • add_address

    Add address as a separate column.

    For vault address list copy-pasted.

  • drop_blacklisted – Remove vaults we have manually flagged as troublesome.

  • html_links

    Wrap Name, Chain, and Protocol values in <a> tags linking to tradingstrategy.ai. Use display_lifetime_table() to render the result with compact styling in a Jupyter notebook.

    Example:

    from eth_defi.research.vault_metrics import (
        format_lifetime_table,
        display_lifetime_table,
    )
    
    formatted = format_lifetime_table(df, html_links=True)
    display_lifetime_table(formatted)
    

  • df (pandas.DataFrame) –

  • profit_presentation (Literal['split', 'net_only']) –

Returns

Human readable data frame

Return type

pandas.DataFrame

display_lifetime_table(df)

Render a formatted lifetime table as compact HTML in a Jupyter notebook.

Produces an HTML table with minimal cell padding and renders <a> links created by format_lifetime_table() with html_links=True.

Example:

formatted = format_lifetime_table(df, html_links=True)
display_lifetime_table(formatted)
Parameters

df (pandas.DataFrame) – DataFrame returned by format_lifetime_table().

class VaultReport

Bases: object

One vault data analysed

rolling_returns_chart: plotly.graph_objs._figure.Figure

Rolling returns chart

hourly_df: pandas.DataFrame

All hourly columns

__init__(vault_metadata, rolling_returns_chart, performance_stats, daily_returns, hourly_returns, hourly_df)
Parameters
  • vault_metadata (dict) –

  • rolling_returns_chart (plotly.graph_objs._figure.Figure) –

  • performance_stats (ffn.core.PerformanceStats) –

  • daily_returns (pandas.Series) –

  • hourly_returns (pandas.Series) –

  • hourly_df (pandas.DataFrame) –

Return type

None

analyse_vault(vault_db, prices_df, spec, returns_col='returns_1h', logger=<built-in function print>, chart_frequency='daily')

Create charts and tables to analyse a vault performance.

  • We plot our annualised 1 month rolling returns on the chart, to see how vaults move in the direction of the markets, or what kind of outliers there are

Parameters
  • vault_db (eth_defi.vault.vaultdb.VaultDatabase) – Database of all vault metadata

  • price_df

    Cleaned price and returns data for all vaults.

    Can be be in any time frame.

  • id – Vault chain + address to analyse, e.g. “1-0x1234567890abcdef1234567890abcdef12345678”

  • chart_frequency (Literal['hourly', 'daily']) –

    Do we plot based on daily or hourly datapoints.

    Hourly data has too many points, chocking Plotly.

  • prices_df (pandas.DataFrame) –

  • spec (eth_defi.vault.base.VaultSpec) –

  • returns_col (str) –

Returns

Analysis report to display.

None if the vault does not have price data.

Return type

Optional[eth_defi.research.vault_metrics.VaultReport]

calculate_performance_metrics_for_all_vaults(vault_db, prices_df, logger=<built-in function print>, lifetime_min_nav_threshold=100.0, broken_max_nav_value=99000000000, cagr_too_high=10000, min_events=25)

Calculate performance metrics for each vault.

  • Only applicable to stablecoin vaults as cleaning units are in USD

  • Clean up idle vaults that have never seen enough events to be considered active

  • Calculate lifetime returns, CAGR, NAV, etc.

  • Filter out results with abnormal values

Returns

DataFrame with lifetime metrics for each vault, indexed by vault name.

Parameters
Return type

pandas.DataFrame

format_vault_database(vault_db, index=True)

Format vault database for human readable output.

Parameters

vault_db (eth_defi.vault.vaultdb.VaultDatabase) – Vault database to format

Returns

DataFrame with vault metadata, with human readable columns

Return type

pandas.DataFrame

format_vault_header(vault_row)

Format vault header for human readable output.

See format_vault_database()

Returns

DataFrame with formatted performance metrics

Parameters

vault_row (pandas.Series) –

Return type

pandas.Series

format_ffn_performance_stats(report, prefix_series=None)

Format FFN report for human readable output.

  • Return a Series with formatted performance metrics

  • Multiple series can be combined to a comparison table

Parameters
  • prefix_data – Extra header data to insert.

  • report (ffn.core.PerformanceStats) – FFN performance report to format

  • prefix_series (Optional[pandas.Series]) –

Returns

DataFrame with formatted performance metrics

Return type

pandas.Series

FFN_GROUP_HEADINGS = ['Overview', 'Returns', 'Period returns', 'Daily statistics', 'Monthly statistics', 'Yearly statistics', 'Drawdown statistics']

Group headings for FFN performance stats, matching the None separators in PerformanceStats._stats().

format_ffn_performance_stats_grouped(report, prefix_series=None)

Format FFN report as logically grouped sections.

Returns a list of (heading, series) tuples where each tuple represents a group of related metrics. The groups correspond to the None separators in FFN’s PerformanceStats._stats().

Parameters
  • report (ffn.core.PerformanceStats) – FFN performance report to format.

  • prefix_series (Optional[pandas.Series]) – Extra header data to prepend to the first group.

Returns

List of (heading, series) tuples for each logical group.

Return type

list[tuple[str, pandas.Series]]

cross_check_data(vault_db, prices_df, printer=<built-in function print>)

Check that VaultDatabase has metadata for all price_df vaults and vice versa.

Returns

Number of problem entries.

Should be zero.

Parameters
Return type

int

calculate_daily_returns_for_all_vaults(df_work)

Calculate daily returns for each vault in isolation

Parameters

df_work (pandas.DataFrame) –

Return type

pandas.DataFrame

calculate_hourly_returns_for_all_vaults(df_work)

Calculate hourly returns for each vault in isolation

Parameters

df_work (pandas.DataFrame) –

Return type

pandas.DataFrame

display_vault_chart_and_tearsheet(vault_spec, vault_db, prices_df, render=True)

Render a chart and tearsheet for a single vault.

  • Use in notebooks

:param render;

Disable rendering in tests

Parameters
export_lifetime_row(row)

Export lifetime metrics row to a fully JSON-serialisable dict.

  • Recursively handles nested dicts, lists, tuples, sets, and dataclasses.

  • Normalises pandas, numpy, datetime, and custom types.

  • Converts finite Decimal values to JSON number floats.

  • Preserves legacy fee field names.

The denomination_token_rate dataclass from eth_defi.feed.stablecoin_rate.DenominationTokenRate is converted to JSON fields here. This includes usd_rate/usd_rate_fetched_at for all stablecoins and native_rate/native_rate_fetched_at only when the native source currency is not USD.

Parameters

row (pandas.Series) –

Return type

dict