research.vault_metrics

Documentation for eth_defi.research.vault_metrics Python module.

Vault metrics calculations.

Module Attributes

LOOKBACK_AND_TOLERANCES

Period -> Perioud duration, max sparse sample mismatch

Functions

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

Create charts and tables to analyse a vault performance.

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_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-serializable dict.

fmt_one_decimal_or_int(x)

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

format_ffn_performance_stats(report[, ...])

Format FFN report for human readable output.

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_period_metrics(period_results, period)

Get PeriodMetrics for a specific period from the results list.

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

PeriodMetrics

Tearsheet metrics for one period.

VaultReport

One vault data analysed

Percent

Percent as the floating point.

0.01 = 1%

class PeriodMetrics

Bases: object

Tearsheet metrics for one period.

error_reason: str | None

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

period_start_at: pandas._libs.tslibs.timestamps.Timestamp | None

When was start share price sampled

period_end_at: pandas._libs.tslibs.timestamps.Timestamp | None

When was end share price sampled

share_price_start: float | None

Share price at beginning

share_price_end: float | None

Share price at end

raw_samples: int

Number of raw datapoints used

daily_samples: int

Number of daily datapoitns used

returns_gross: float | None

How much absolute returns we had

cagr_gross: float | None

Compounding annual returns

volatility: float | None

Annualised volatility, calculated based on daily returns

sharpe: float | None

Sharpe ratio

max_drawdown: float | None

Period maximum drawdown

tvl_start: float | None

TVL at the start of the period

tvl_end: float | None

TVL at the end of the period

tvl_low: float | None

Minimum TVL in the period

tvl_high: float | None

Maximum TVL in the period

ranking_overall: int | None

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

ranking_chain: int | None

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

ranking_protocol: int | None

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

__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)
Parameters
  • period (Literal['1W', '1M', '3M', '6M', '1Y', 'lifetime']) –

  • error_reason (str | None) –

  • period_start_at (pandas._libs.tslibs.timestamps.Timestamp | None) –

  • period_end_at (pandas._libs.tslibs.timestamps.Timestamp | None) –

  • share_price_start (float | None) –

  • share_price_end (float | None) –

  • raw_samples (int) –

  • samples_start_at (pandas._libs.tslibs.timestamps.Timestamp | None) –

  • samples_end_at (pandas._libs.tslibs.timestamps.Timestamp | None) –

  • daily_samples (int) –

  • returns_gross (float | None) –

  • returns_net (float | None) –

  • cagr_gross (float | None) –

  • cagr_net (float | None) –

  • volatility (float | None) –

  • sharpe (float | None) –

  • max_drawdown (float | None) –

  • tvl_start (float | None) –

  • tvl_end (float | None) –

  • tvl_low (float | None) –

  • tvl_high (float | None) –

  • ranking_overall (int | None) –

  • ranking_chain (int | None) –

  • ranking_protocol (int | None) –

Return type

None

LOOKBACK_AND_TOLERANCES: dict[Literal['1W', '1M', '3M', '6M', '1Y', 'lifetime'], tuple[pandas._libs.tslibs.offsets.DateOffset, pandas._libs.tslibs.timedeltas.Timedelta]] = {'1M': (<DateOffset: days=30>, Timedelta('60 days 00:00:00')), '1W': (<DateOffset: days=7>, Timedelta('12 days 00:00:00')), '1Y': (<DateOffset: days=360>, 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

fmt_one_decimal_or_int(x)

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

Parameters

x (float | None) –

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
  • name (str | None) –

  • symbol (str | None) –

  • address (str) –

  • existing_slugs (set[str]) –

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.core.series.Series) – The original returns series.

Return type

pandas.core.series.Series

calculate_returns(share_price, freq='D')

Calculate returns from resampled share price series.

Parameters

share_price (pandas.core.series.Series) –

Return type

pandas.core.series.Series

calculate_cumulative_returns(cleaned_returns, freq='D')

Takes a returns series and calculates cumulative returns.

Parameters

cleaned_returns (pandas.core.series.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.core.series.Series) –

  • eps (float) –

  • clip_negatives (bool) –

Return type

pandas.core.series.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
  • 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 (float) – Annual management fee as a percent (0.02 = 2% per year).

  • performance_fee (float) – Performance fee as a percent (0.20 = 20% of profits).

  • deposit_fee (float | None) – Deposit fee as a percent (0.01 = 1% fee), or None if no fee.

  • withdrawal_fee (float | None) – Withdrawal fee as a percent (0.01 = 1% fee), or None if no fee.

  • sample_count (int | None) – 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

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
  • name (str) – For debugging

  • share_price (pandas.core.series.Series) – Share price series with datetime index.

  • management_fee_annual (float | None) – Annual management fee as a percent (0.02 = 2% per year).

  • performance_fee (float | None) – Performance fee as a percent (0.20 = 20% of profits).

  • deposit_fee (float | None) – Deposit fee as a percent (0.01 = 1% fee), or None if no fee.

  • withdrawal_fee (float | None) – 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

pandas.core.series.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.core.series.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[float]) – Annual management fee as a decimal (e.g., 0.02 for 2%).

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

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

  • withdrawal_fee (Optional[float]) – 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.core.series.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.core.series.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

list[eth_defi.vault.vaultdb.VaultRow] | None

calculate_period_metrics(period, gross_fee_data, net_fee_data, share_price_hourly, share_price_daily, tvl, now_)

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.core.series.Series) – Hourly share price series with DatetimeIndex

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

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

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

  • now_ (pandas._libs.tslibs.timestamps.Timestamp) –

Returns

PeriodMetrics dataclass with calculated metrics

Return type

eth_defi.research.vault_metrics.PeriodMetrics

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

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

  • Exported to frontend, everything

Parameters
  • prices_df (pandas.core.frame.DataFrame) – Price DataFrame for a single vault

  • vault_metadata_rows (dict[eth_defi.vault.base.VaultSpec, eth_defi.vault.vaultdb.VaultRow]) – Dictionary of vault metadata keyed by VaultSpec

  • month_ago (pandas._libs.tslibs.timestamps.Timestamp) – Timestamp for 1-month lookback

  • three_months_ago (pandas._libs.tslibs.timestamps.Timestamp) – Timestamp for 3-month lookback

  • vault_id (str | None) – Vault ID string. If not provided, extracted from prices_df[“id”].

Returns

Series with calculated metrics

Return type

pandas.core.series.Series

calculate_lifetime_metrics(df, vault_db, returns_column='returns_1h')

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.

Parameters
Returns

DataFrame, one row per vault.

Return type

pandas.core.frame.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

eth_defi.research.vault_metrics.PeriodMetrics | None

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.core.frame.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.core.frame.DataFrame

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.core.frame.DataFrame) –

Return type

pandas.core.frame.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
  • gross (pandas.core.series.Series) – Gross returns series

  • net (pandas.core.series.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')

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.

  • df (pandas.core.frame.DataFrame) –

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

Returns

Human readable data frame

Return type

pandas.core.frame.DataFrame

class VaultReport

Bases: object

One vault data analysed

rolling_returns_chart: plotly.graph_objs._figure.Figure

Rolling returns chart

hourly_df: pandas.core.frame.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.core.series.Series) –

  • hourly_returns (pandas.core.series.Series) –

  • hourly_df (pandas.core.frame.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.core.frame.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

eth_defi.research.vault_metrics.VaultReport | None

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.core.frame.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.core.frame.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.core.series.Series) –

Return type

pandas.core.series.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 (pandas.core.series.Series | None) –

Returns

DataFrame with formatted performance metrics

Return type

pandas.core.series.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.core.frame.DataFrame) –

Return type

pandas.core.frame.DataFrame

calculate_hourly_returns_for_all_vaults(df_work)

Calculate hourly returns for each vault in isolation

Parameters

df_work (pandas.core.frame.DataFrame) –

Return type

pandas.core.frame.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-serializable dict.

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

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

  • Preserves legacy fee field names.

Parameters

row (pandas.core.series.Series) –

Return type

dict