research.vault_metrics
Documentation for eth_defi.research.vault_metrics Python module.
Vault metrics calculations.
Calculate various performance reports and charts for vaults.
Module Attributes
Vaults with TVL above this are considered broken smart contracts |
|
Vaults with share price above this are considered broken smart contracts |
|
Vaults with annualised volatility above this are considered broken. |
|
Human-readable note suffixes for vault scan cycles slower than active hourly scans. |
|
Period -> Perioud duration, max sparse sample mismatch |
|
Protocol slugs for vaults where we do not necessarily have on-chain deposit/redeem event data. |
|
Group headings for FFN performance stats, matching the None separators in |
Functions
|
Create charts and tables to analyse a vault performance. |
|
Check for broken vaults by detecting abnormal metric values. |
|
Blacklist vaults that carry scanned bad flags. |
|
Blacklist Morpho vaults missing from Morpho API. |
|
Takes a returns series and calculates cumulative returns. |
Calculate daily returns for each vault in isolation |
|
Calculate hourly returns for each vault in isolation |
|
|
Calculate lifetime metrics for each vault in the provided DataFrame. |
|
Calculate profit after external fees have been reduced from the share price change. |
|
Convert a cumulative gross return series to a cumulative net return series after fees. |
|
Convert a share price series to net return series after fees. |
Calculate performance metrics for each vault. |
|
|
Calculate metrics for one period. |
|
Calculate returns from resampled share price series. |
|
Calculate annualized Sharpe ratio from hourly returns. |
|
Calculate rankings for all periods inside PeriodMetrics objects. |
|
Process a single vault metadata + prices to calculate its full data. |
|
Clean lifetime data so we have only valid vaults. |
|
Create combined net / (gross) returns column for display. |
|
Create 2% / 20% style labels to display variosu kinds of vault fees. |
|
Check that VaultDatabase has metadata for all price_df vaults and vice versa. |
Render a formatted lifetime table as compact HTML in a Jupyter notebook. |
|
|
Render a chart and tearsheet for a single vault. |
|
Export lifetime metrics row to a fully JSON-serialisable dict. |
|
Append lower scan-cycle context to a vault note. |
Display fees to .1 accuracy if there are .1 fractions, otherwise as int. |
|
|
Create a generic note for vaults that carry bad scan flags. |
|
Format FFN report for human readable output. |
Format FFN report as logically grouped sections. |
|
|
Format table for human readable output. |
|
Format vault database for human readable output. |
|
Format vault header for human readable output. |
|
Get the latest known vault scan cycle from price rows. |
|
Get PeriodMetrics for a specific period from the results list. |
|
Check if a vault is a special vault that may not have deposit/redeem event data. |
|
Build generic vault warning display entries for JSON export. |
Normalise a raw vault scan cycle value. |
|
|
Calculate returns from resampled returns series. |
|
Create a slug from protocol name for URLs. |
|
Create a slug from vault metadata for URLs. |
|
Create slugs for a set of vaults. |
|
Replace values with abs(x) < eps by 0. |
Classes
Provenance metadata for the vault metrics JSON export. |
|
Git version stamp of the exporter Docker image. |
|
Deposit and withdrawal flow metrics for a time period. |
|
Tearsheet metrics for one period. |
|
Top-level structure of the vault metrics JSON export. |
|
Per-vault record in the JSON export. |
|
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.Decimalvalues 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:
objectTearsheet metrics for one period.
- 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 at beginning
Share price at end
- 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
period (Literal['1W', '1M', '3M', '6M', '1Y', 'lifetime']) –
period_start_at (Optional[pandas.Timestamp]) –
period_end_at (Optional[pandas.Timestamp]) –
raw_samples (int) –
samples_start_at (Optional[pandas.Timestamp]) –
samples_end_at (Optional[pandas.Timestamp]) –
daily_samples (int) –
- Return type
None
- class NetflowMetrics
Bases:
objectDeposit 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
Nonein the vault record.
- 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:
TypedDictPer-vault record in the JSON export.
Uses
total=Falseso 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.- deployment: Optional[str]
Native protocol deployment slug, or
Nonefor ordinary EVM vaults.For now this is populated for Lighter so consumers can distinguish the original
ethereumdeployment from Lighter onrobinhoodwhile 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=9998because they share the native Lighter dataset namespace; their synthetic address prefixes and these deployment fields distinguish individual vaults.
- vault_poll_frequency: Optional[str]
Latest adaptive vault scan cycle, e.g.
"large_tvl"or"peaked".
Current net asset value in USD
Peak net asset value in USD
- denomination_decimals: Optional[int]
Denomination (reserve) token ERC-20 decimals, e.g.
6for USDC. Consumers use this to convert human amounts to raw uint256; a missing value must NOT be defaulted to 18 (seedenomination_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 ERC-20 decimals (the vault’s own ERC-4626 token).
- core3: Optional[eth_defi.core3.vault_protocol.Core3VaultSection]
Compact Core3 risk summary for the vault’s protocol, or
Nonewhen no Core3 data is available. SeeCore3VaultSection.
- denomination_token_rate: eth_defi.feed.stablecoin_rate.DenominationTokenRate
Stablecoin rate data for the vault denomination token.
- __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:
TypedDictGit version stamp of the exporter Docker image.
Produced by
eth_defi.version_info.VersionInfo.as_dict(). All fields areNonewhen the exporter runs outside a stamped Docker image, e.g. from a source checkout. Individual fields can also beNoneinside 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 theGIT_VERSION_TAGbuild ARG. Consumers should rely oncommit_hashas the primary build identifier.
- __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:
TypedDictProvenance 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:
TypedDictTop-level structure of the vault metrics JSON export.
Describes the shape of
top_vaults_by_chain.jsonuploaded to R2.- 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
- Returns
JSON-safe warning entries suitable for user interfaces.
- Return type
- fmt_one_decimal_or_int(x)
Display fees to .1 accuracy if there are .1 fractions, otherwise as int.
- slugify_protocol(protocol)
Create a slug from protocol name for URLs.
- slugify_vault(name, symbol, address, existing_slugs)
Create a slug from vault metadata for URLs.
- 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
- calculate_returns(share_price, freq='D')
Calculate returns from resampled share price series.
- Parameters
share_price (pandas.Series) –
- Return type
- calculate_cumulative_returns(cleaned_returns, freq='D')
Takes a returns series and calculates cumulative returns.
The cleaned returns series is created by
eth_defi.research.wrangle_vault_prices.
- 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
s (pandas.Series) –
eps (float) –
clip_negatives (bool) –
- Return type
- 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
start (datetime.datetime) – Start datetime of the investment period.
end (datetime.datetime) – End datetime of the investment period.
share_price_start (float) – Share price at the start of the investment period.
share_price_end (float) – Share price at the end of the investment period.
management_fee_annual (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Annual management fee as a percent (0.02 = 2% per year).
performance_fee (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Performance fee as a percent (0.20 = 20% of profits).
deposit_fee (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Deposit fee as a percent (0.01 = 1% fee), or None if no fee.
withdrawal_fee (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Withdrawal fee as a percent (0.01 = 1% fee), or None if no fee.
sample_count (Optional[int]) – If we have not enough returns data, do not try to calculate profit.
- Returns
Net profit as a floating point (0.10 = 10% profit).
- Return type
- 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
name (str) – For debugging
share_price (pandas.Series) – Share price series with datetime index.
management_fee_annual (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Annual management fee as a percent (0.02 = 2% per year).
performance_fee (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Performance fee as a percent (0.20 = 20% of profits).
deposit_fee (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Deposit fee as a percent (0.01 = 1% fee), or None if no fee.
withdrawal_fee (Optional[Union[float, decimal.Decimal, Literal['-']]]) – Withdrawal fee as a percent (0.01 = 1% fee), or None if no fee.
freq – The time series frequency (hourly, daily, etc) for management fee calculation.
- Returns
Cumulative net profit as a floating point (0.10 = 10% profit).
- Return type
- 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
- 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
- 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
- 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_utilisationis computed for the period window.now_ (pandas.Timestamp) –
- Returns
PeriodMetrics dataclass with calculated metrics
- Return type
- 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
risk (eth_defi.vault.risk.VaultTechnicalRisk) – Current risk classification.
notes (str) – Current notes string.
flags (set[eth_defi.vault.flag.VaultFlag]) – Current vault flags set.
current_nav (Optional[float]) – Current TVL in USD. Checked against
MAX_VALID_NAV.current_share_price (Optional[float]) – Current share price. Checked against
MAX_VALID_SHARE_PRICE.three_months_volatility (Optional[float]) – 3-month annualised volatility. Checked against
MAX_VALID_VOLATILITY. Catches low-TVL Hyperliquid vaults with one or few trades that produce extreme volatility numbers.
- 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
risk (Optional[eth_defi.vault.risk.VaultTechnicalRisk]) – Current risk classification.
notes (Optional[str]) – Current note, if any. Existing notes have priority.
flags (set[eth_defi.vault.flag.VaultFlag]) – Dynamic flags collected during vault scan.
- 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 ineth_defi.vault.flag.BAD_FLAGSmeans the vault must not be exported as a normal investable row.- Parameters
risk (Optional[eth_defi.vault.risk.VaultTechnicalRisk]) – Current risk classification.
notes (Optional[str]) – Current note, if any. Existing notes are preserved.
flags (set[eth_defi.vault.flag.VaultFlag]) – Scan-time and manual flags collected for the vault.
- 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
- normalise_vault_poll_frequency(value)
Normalise a raw vault scan cycle value.
The cleaned price parquet may carry missing values as
None,NaNorpd.NAdepending 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.
- get_latest_vault_poll_frequency(prices_df)
Get the latest known vault scan cycle from price rows.
vault_poll_frequencyis 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
Noneif the column is not available.- Return type
- 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.
- 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
prices_df (pandas.DataFrame) – Price DataFrame for a single vault, conforming to
CleanedVaultPriceRowvault_metadata_rows (dict[eth_defi.vault.base.VaultSpec, eth_defi.vault.vaultdb.VaultRow]) – Dictionary of vault metadata keyed by VaultSpec
month_ago (pandas.Timestamp) – Timestamp for 1-month lookback
three_months_ago (pandas.Timestamp) – Timestamp for 3-month lookback
vault_id (Optional[str]) – Vault ID string. If not provided, extracted from prices_df[“id”].
core3_protocols (Optional[dict[str, eth_defi.core3.vault_protocol.Core3ExportRecord]]) – Optional Core3 risk records keyed by our protocol slug, as returned by
eth_defi.core3.vault_protocol.build_core3_protocols_for_export(). When given, a compact per-vaultcore3summary (Core3VaultSection) is attached to the record for the vault’s protocol, orNoneif the protocol has no Core3 data.stablecoin_rate_feeder (Optional[eth_defi.feed.stablecoin_rate.StablecoinRateFeeder]) – Stablecoin rate/depeg lookup helper. If omitted, a default feeder using package stablecoin metadata is constructed for this vault. Batch callers should pass one shared
StablecoinRateFeederso the stablecoin YAML lookups are cached consistently across the batch.
- Returns
Series with calculated metrics
- Return type
- 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_ratevalue produced byeth_defi.feed.stablecoin_rate.StablecoinRateFeeder.get_denomination_token_rate_section(). The value is aeth_defi.feed.stablecoin_rate.DenominationTokenRatecarrying both USD rate fields and, for non-USD stablecoins, native source currency rate fields.- Parameters
df (pandas.DataFrame) – Cleaned price DataFrame conforming to
CleanedVaultPriceRow. Must have aDatetimeIndex.vault_db (Union[eth_defi.vault.vaultdb.VaultDatabase, dict[eth_defi.vault.base.VaultSpec, eth_defi.vault.vaultdb.VaultRow]]) – Pass all vaults or subset of vaults as VaultRows, or full VaultDatabase
core3_protocols (Optional[dict[str, eth_defi.core3.vault_protocol.Core3ExportRecord]]) – Optional Core3 risk records keyed by our protocol slug, as returned by
eth_defi.core3.vault_protocol.build_core3_protocols_for_export(). Threaded through tocalculate_vault_record()to attach a per-vaultcore3summary.Noneto skip Core3 enrichment.stablecoin_rate_feeder (Optional[eth_defi.feed.stablecoin_rate.StablecoinRateFeeder]) – Stablecoin rate/depeg lookup helper shared across all vault rows in this calculation. If omitted, one default feeder is constructed.
returns_column (str) –
- Returns
DataFrame, one row per vault.
- Return type
- get_period_metrics(period_results, period)
Get PeriodMetrics for a specific period from the results list.
- Parameters
period_results (list[eth_defi.research.vault_metrics.PeriodMetrics]) – List of PeriodMetrics objects from a vault record
period (Literal['1W', '1M', '3M', '6M', '1Y', 'lifetime']) – The period to find (e.g., “1W”, “1M”, “3M”, “6M”, “1Y”, “lifetime”)
- Returns
The matching PeriodMetrics or None if not found
- Return type
- 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
- 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
- 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.
Filter out vaults that have broken records or never saw daylight
- Returns
Cleaned lifetime dataframe
- Parameters
lifetime_data_df (pandas.DataFrame) –
- Return type
- 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
gross (pandas.Series) – Gross returns series
net (pandas.Series) – Net returns series
mode (Literal['percent', 'usd']) –
profit_presentation (Literal['split', 'net_only']) –
- 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. Usedisplay_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
- 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 byformat_lifetime_table()withhtml_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:
objectOne 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
- 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
vault_db (eth_defi.vault.vaultdb.VaultDatabase) –
prices_df (pandas.DataFrame) –
- Return type
- 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
- format_vault_header(vault_row)
Format vault header for human readable output.
- Returns
DataFrame with formatted performance metrics
- Parameters
vault_row (pandas.Series) –
- Return type
- 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
- 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 theNoneseparators in FFN’sPerformanceStats._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
- 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
vault_db (eth_defi.vault.vaultdb.VaultDatabase) –
prices_df (pandas.DataFrame) –
- Return type
- calculate_daily_returns_for_all_vaults(df_work)
Calculate daily returns for each vault in isolation
- Parameters
df_work (pandas.DataFrame) –
- Return type
- calculate_hourly_returns_for_all_vaults(df_work)
Calculate hourly returns for each vault in isolation
- Parameters
df_work (pandas.DataFrame) –
- Return type
- 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
vault_spec (eth_defi.vault.base.VaultSpec) –
vault_db (eth_defi.vault.vaultdb.VaultDatabase) –
prices_df (pandas.DataFrame) –
- 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
Decimalvalues to JSON number floats.Preserves legacy fee field names.
The
denomination_token_ratedataclass frometh_defi.feed.stablecoin_rate.DenominationTokenRateis converted to JSON fields here. This includesusd_rate/usd_rate_fetched_atfor all stablecoins andnative_rate/native_rate_fetched_atonly when the native source currency is not USD.- Parameters
row (pandas.Series) –
- Return type