research.wrangle_vault_prices
Documentation for eth_defi.research.wrangle_vault_prices Python module.
Clean vault price data.
Denormalise data to a single DataFrame
Remove abnormalities in the price data
Reduce data by removing hourly changes that are below our epsilon threshold
Generate returns data
The input is the raw scanner parquet conforming to
RawVaultPriceRow.
The output is a cleaned DataFrame conforming to
CleanedVaultPriceRow, consumed by
calculate_lifetime_metrics().
Module Attributes
NAV at or below this value counts as a complete Hypercore wipe-out. |
|
Maximum absolute mismatch when recognising a delayed PnL/NAV API update. |
|
Maximum relative mismatch when recognising a delayed PnL/NAV API update. |
|
PnL-first, NAV-confirming API lag needs a baseline and two later checkpoints. |
|
New capital must reach this NAV before a recapitalised vault is tracked again. |
|
For manual debugging, we process these vaults first |
|
Vault state and pass-through columns added by the historical scanner. |
Functions
|
Add denormalised data to the prices DataFrame. |
Build an approximate Hypercore economic-performance index from PnL and NAV. |
|
|
Ensure all vaults have unique human-readable name. |
|
Calculate returns for each vault. |
|
Check that we have metadata for all vaults in the prices DataFrame. |
|
TVL-based threshold filtering of returns. |
|
Clean returns data by removing rows with NaN or infinite values. |
|
Derive unified |
Start a recapitalised Hypercore vault at its new meaningful capital base. |
|
|
Ensure vault state columns are present in the DataFrame. |
|
Dedpulicate data rows with epsilon. |
|
Reduce vaults to stablecoin vaults only. |
|
Fix out rows with share price that is too high. |
|
Forward fill missing vault prices up to max_gap_hours. |
A command line script entry point to take raw scanned vault price data and clean it up to a format that can be analysed. |
|
|
Build a dictionary of vaults by their chain-address id. |
|
Preprocess vault data for further analysis. |
|
Remove initial inactive period from each vault's price history. |
|
Rebuild and atomically replace cleaned histories for selected vaults. |
|
Set up the order of vaults for processing. |
Classes
Schema for a single row in the cleaned vault price DataFrame. |
- HYPERCORE_ZERO_NAV_EPSILON = 1e-06
NAV at or below this value counts as a complete Hypercore wipe-out.
- HYPERCORE_ECONOMIC_CHECKPOINT_INTERVAL = Timedelta('0 days 04:00:00')
Fixed UTC interval used to select publishable Hypercore economic checkpoints.
- MAX_HYPERCORE_PNL_NAV_CONFIRMATION_DELAY = Timedelta('1 days 02:00:00')
Maximum elapsed time accepted around a delayed Hypercore NAV confirmation.
- HYPERCORE_PNL_NAV_LAG_ABSOLUTE_TOLERANCE = 1.0
Maximum absolute mismatch when recognising a delayed PnL/NAV API update.
- HYPERCORE_PNL_NAV_LAG_RELATIVE_TOLERANCE = 0.001
Maximum relative mismatch when recognising a delayed PnL/NAV API update.
- MIN_HYPERCORE_PNL_NAV_LAG_CHECKPOINTS = 3
PnL-first, NAV-confirming API lag needs a baseline and two later checkpoints.
- MIN_HYPERCORE_RECAPITALISATION_ASSETS = 1000.0
New capital must reach this NAV before a recapitalised vault is tracked again.
- MIN_HYPERCORE_RECAPITALISATION_RECOVERY_DELAY = Timedelta('7 days 00:00:00')
Ignore isolated zero-NAV observations that recover before this delay.
- class CleanedVaultPriceRow
Bases:
TypedDictSchema for a single row in the cleaned vault price DataFrame.
This is the enriched format produced by the cleaning pipeline in this module and consumed by
calculate_lifetime_metrics().It extends
RawVaultPriceRowwith denormalised metadata columns (id,name,event_count,protocol) and computed columns (returns_1h). The DataFrame uses aDatetimeIndexbuilt from thetimestampcolumn.Columns are grouped by availability:
General columns are present for all vault protocols.
ERC-4626 only columns come from on-chain ERC-4626 calls and are NaN / empty for native protocols.
Lending only columns are populated for lending protocol vaults (IPOR, Euler, Morpho, Gearbox, etc.) and NaN for others.
Hypercore only columns come from the Hyperliquid native vault API and are NaN for all other protocols.
Native protocol flow columns are populated for native protocols that provide daily deposit/withdrawal data (Hypercore, GRVT, Lighter, Hibachi) and NaN for ERC-4626 vaults.
- chain: int
EVM chain id (e.g.
1for Ethereum,8453for Base).Native (non-EVM) protocols use synthetic in-house chain ids:
9999— Hypercore (native Hyperliquid vaults), seeHYPERCORE_CHAIN_ID9998— Lighter DEX pools, seeLIGHTER_CHAIN_ID9997— Hibachi native vaults, seeHIBACHI_CHAIN_ID325— GRVT (Gravity Markets), seeGRVT_CHAIN_ID
The full mapping lives in
CHAIN_NAMES.General — present for all protocols.
- address: str
Vault contract address, lowercase.
Address formats vary by protocol:
EVM vaults:
0x-prefixed hex (e.g."0xabcd...")Hypercore:
0x-prefixed hex (Hyperliquid vault addresses)GRVT: platform-specific id (e.g.
"vlt:xxx")Lighter: synthetic id (e.g.
"lighter-pool-281474976710654")Hibachi: synthetic id (e.g.
"hibachi-vault-2")
See
is_good_multichain_address()for the validation function that accepts all these formats.General — present for all protocols.
- block_number: int
Block number of the on-chain read. For native protocols without blocks this is a synthetic sequence number.
General — present for all protocols.
- timestamp: pandas.Timestamp
Naive UTC timestamp (also used as the DatetimeIndex).
General — present for all protocols.
Share price in denomination token units.
For ERC-4626 vaults this is read directly from the contract (
convertToAssets(1e decimals)). GRVT, Lighter, and Hibachi provide native share prices from their respective APIs. Hypercore (native Hyperliquid vaults) does not expose a historical share price or supply. Its cleaned value is a PnL/NAV economic-performance index starting at1.0for each retained capital epoch. The scanner’s synthetic input remains available inraw_share_price.General — present for all protocols.
- total_assets: float
Total assets under management (TVL) in denomination token units.
General — present for all protocols.
- total_supply: float
Total supply of vault share tokens. Hypercore has no exposed historical token supply; its value is synthetic index units calculated as
total_assets / share_priceand must not be interpreted as an on-chain share count.General — present for all protocols.
- performance_fee: float
Performance fee at time of read (e.g. 0.20 = 20%). NaN if unknown.
General — present for all protocols.
- management_fee: float
Management fee at time of read (e.g. 0.02 = 2%). NaN if unknown.
General — present for all protocols.
- errors: str
Comma-separated RPC error messages, or empty string if no errors.
Example values:
"total_supply call failed","total_assets zero: 0","total_supply call missing". Always empty for native protocols.General — present for all protocols (always empty for native protocols).
- vault_poll_frequency: str
Dynamic poll frequency used when taking this sample. Empty string if not set.
Example values:
"1h","4h","24h". The scanner adjusts frequency based on vault TVL and activity; low-TVL vaults may be polled less frequently.General — present for all protocols (may be empty for native protocols).
- name: str
Human-readable vault name (unique within the dataset).
General — present for all protocols.
- event_count: int
Total deposit + redeem events observed for this vault.
Zero if the protocol does not support on-chain deposit/redeem event tracking (e.g. native vaults like GRVT, Lighter, Hibachi).
General — present for all protocols.
- protocol: str
Protocol name (e.g.
"Morpho","Yearn","Hyperliquid").General — present for all protocols.
- returns_1h: float
Hourly return as
pct_change()ofshare_pricewithin each vault group. Despite the name, for native protocols (Hypercore, GRVT, Lighter) the interval may be daily or irregular — the column name is kept for backward compatibility.General — present for all protocols.
- max_deposit: float
Maximum deposit amount allowed (ERC-4626
maxDeposit). NaN if unknown.ERC-4626 only — NaN for native protocols.
- max_redeem: float
Maximum redeem amount allowed (ERC-4626
maxRedeem). NaN if unknown.ERC-4626 only — NaN for native protocols.
- deposits_open: str
Whether deposits were open:
"true","false", or"".ERC-4626 only — empty for native protocols.
- redemption_open: str
Whether redemptions were open:
"true","false", or"".ERC-4626 only — empty for native protocols.
- trading: str
Whether the vault was actively trading:
"true","false", or"". Currently only supported for D2 Finance vaults.Protocol-specific — empty for most protocols.
- available_liquidity: float
Available liquidity for immediate withdrawal in denomination token units. NaN if not applicable.
Lending only — IPOR, Euler, Morpho, Gearbox, etc. NaN for other protocols.
- utilisation: float
Utilisation ratio (0.0–1.0) for lending vaults. NaN if not applicable.
Warning
This metric measures capital deployment efficiency (how much of the vault’s AUM is lent out), not redeemable liquidity. For single-market vaults (Euler EVK, Gearbox, Silo) high utilisation does mean low available liquidity. For multi-market aggregators (Morpho, Euler Earn, IPOR) a vault can show 95% utilisation yet have substantial instantly redeemable liquidity in low-utilisation underlying markets. See
README-vault-redeemable.mdandREADME-utilisation.mdineth_defi.erc_4626.vault_protocolfor details.Lending only — IPOR, Euler, Morpho, Gearbox, etc. NaN for other protocols.
- deposit_closed_reason: str
Unified reason why deposits are closed (e.g.
"Vault deposits disabled"). Empty string if deposits are open. Derived fromdeposits_openfor ERC-4626 vaults or set directly by native protocol exporters.General — present for all protocols (empty when deposits are open).
- written_at: pandas.Timestamp
When this price row was actually written/fetched (naive UTC). NaT for old data.
General — present for all protocols.
- vault_settlement_at: pandas.Timestamp
Latest asynchronous vault settlement timestamp in the interval ending at this price row.
General — populated by merging
vault-settlements.duckdbafter cleaning.NaTmeans no known settlement occurred since the previous cleaned price row.
- leader_fraction: float
Fraction of vault assets controlled by the leader (0.0–1.0).
Hypercore only — NaN for all other protocols.
- leader_commission: float
Commission rate charged by the vault leader (0.0–1.0).
Hypercore only — NaN for all other protocols.
- follower_count: float
Number of followers in the vault.
Hypercore only — NaN for all other protocols.
- account_pnl: float
Cumulative PnL of the vault leader account in USD.
Hypercore only — NaN for all other protocols.
- cumulative_volume: float
Cumulative trading volume of the vault in USD.
Hypercore only — NaN for all other protocols.
- hypercore_source: str
Hypercore scanner source:
"daily"or"hf".Used during wrangling to reconcile overlapping synthetic share prices. Hypercore only — NaN for all other protocols.
- epoch_reset: bool
The row starts a new performance epoch after a complete wipe-out.
Hypercore only — false for ordinary observations.
- hypercore_repair_status: str
Provenance of the cleaned Hypercore PnL/NAV approximation.
approximated_pnl_navmarks a four-hour economic checkpoint,approximated_pnl_nav_clippeda positive checkpoint capped at 100%,approximated_pnl_nav_lag_repaireda gain whose NAV confirmation arrived within the bounded confirmation window,approximated_pnl_nav_wipe_outa terminal NAV-corroborated loss, andapproximated_pnl_nav_carrieda row that adds no performance, either because it is not the selected checkpoint for its four-hour UTC bucket, because its PnL awaits NAV confirmation, or because the epoch is already at zero after a terminal loss. A later NAV confirmation can revise the recent provisional PnL-only checkpoint and subsequently compounded prices.deferred_pnl_navmeans inputs were missing anddeferred_pnl_nav_outliermeans an uncorroborated negative PnL step was not allowed to zero a funded vault. Hypercore only — empty for ordinary observations and other protocols.
- daily_deposit_count: float
Number of deposit events in the latest day.
Native protocol flow — Hypercore, GRVT, Lighter, Hibachi. NaN for ERC-4626 vaults.
- daily_withdrawal_count: float
Number of withdrawal events in the latest day.
Native protocol flow — Hypercore, GRVT, Lighter, Hibachi. NaN for ERC-4626 vaults.
- daily_deposit_usd: float
Total USD deposited in the latest day.
Native protocol flow — Hypercore, GRVT, Lighter, Hibachi. NaN for ERC-4626 vaults.
- daily_withdrawal_usd: float
Total USD withdrawn in the latest day.
Native protocol flow — Hypercore, GRVT, Lighter, Hibachi. NaN for ERC-4626 vaults.
- __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.
- PRIORITY_SORT_IDS = ['8453-0x0d877dc7c8fa3ad980dfdb18b48ec9f8768359c4']
For manual debugging, we process these vaults first
- get_vaults_by_id(rows)
Build a dictionary of vaults by their chain-address id.
- Parameters
rows (dict[eth_defi.vault.base.VaultSpec, eth_defi.vault.vaultdb.VaultRow]) – Metadata rows from vault database
- Returns
Dictionary of vaults by their chain-address id
- Return type
- VAULT_STATE_COLUMNS = {'account_pnl': nan, 'available_liquidity': nan, 'cumulative_volume': nan, 'daily_deposit_count': nan, 'daily_deposit_usd': nan, 'daily_withdrawal_count': nan, 'daily_withdrawal_usd': nan, 'deposit_closed_reason': '', 'deposits_open': '', 'epoch_reset': False, 'follower_count': nan, 'hypercore_repair_status': '', 'leader_commission': nan, 'leader_fraction': nan, 'max_deposit': nan, 'max_redeem': nan, 'redemption_open': '', 'trading': '', 'utilisation': nan, 'vault_settlement_at': NaT, 'written_at': NaT}
Vault state and pass-through columns added by the historical scanner. Ensure these are always present in cleaned data, even when processing old scan data that lacks them. See
CleanedVaultPriceRowfor column semantics.
- ensure_vault_state_columns(prices_df)
Ensure vault state columns are present in the DataFrame.
Adds missing columns with default values for backward compatibility with raw scan data generated before these fields were added.
- Parameters
prices_df (pandas.DataFrame) –
- Return type
- derive_deposit_closed_reason(prices_df)
Derive unified
deposit_closed_reasonfrom protocol-specific columns.For Hyperliquid vaults,
deposit_closed_reasonis already set bybuild_raw_prices_dataframe()with specific reason strings.For ERC-4626 vaults, the
deposits_openstring column (“true”/”false”/””) is converted to a generic reason.- Parameters
prices_df (pandas.DataFrame) – DataFrame with
deposit_closed_reasonanddeposits_opencolumns.- Returns
DataFrame with
deposit_closed_reasonfilled in for both vault types.- Return type
- assign_unique_names(rows, prices_df, logger=<built-in function print>, duplicate_nav_threshold=1000)
Ensure all vaults have unique human-readable name.
Rerwrite metadata rows
Find duplicate vault names
Add a running counter to the name to make it unique
- Parameters
rows (dict[eth_defi.vault.base.VaultSpec, eth_defi.vault.vaultdb.VaultRow]) –
prices_df (pandas.DataFrame) –
- Return type
- add_denormalised_vault_data(rows, prices_df, logger=<built-in function print>)
Add denormalised data to the prices DataFrame.
Take data from vault database and duplicate it across every row
Add protocol name and event count columns
- Parameters
rows (dict[eth_typing.evm.HexAddress, eth_defi.vault.vaultdb.VaultRow]) –
prices_df (pandas.DataFrame) –
- Return type
- filter_vaults_by_stablecoin(rows, prices_df, logger=<built-in function print>)
Reduce vaults to stablecoin vaults only.
In this notebooks, we focus on stablecoin yield
Do not consider WETH, other native token vaults, as their returns calculation would need to match the appreciation of underlying assets
[is_stablecoin_like](https://web3-ethereum-defi.readthedocs.io/api/core/_autosummary/eth_defi.token.is_stablecoin_like.html?highlight=is_stablecoin_like#eth_defi.token.is_stablecoin_like) supports GHO, crvUSD and other DeFi/algorithmic stablecoins
Note that this picks up very few EUR and other fiat-nominated vaults
- Parameters
rows (dict[eth_typing.evm.HexAddress, eth_defi.vault.vaultdb.VaultRow]) –
prices_df (pandas.DataFrame) –
- Return type
- calculate_vault_returns(prices_df, logger=<built-in function print>)
Calculate returns for each vault.
Filter out reads without a share price and add the compatibility
returns_1hcolumn. Consecutive zero prices after a complete loss carry a zero return: the loss was already recorded by the first transition to zero, and leaving later0 / 0changes as NaN makes the terminal curve unnecessarily difficult for downstream consumers to use.Example of input data:
chain address block_number timestamp share_price ... errors id name event_count protocol 207 42161 0x487cdc7d21ac8765eff6c0e681aea36ae1594471 13294721 2022-05-30 19:59:22 1.0 ... 42161-0x487cdc7d21ac8765eff6c0e681aea36ae1594471 LDAI 17 <unknown ERC-4626>
- Parameters
prices_df (pandas.DataFrame) – Price rows containing string
idand numericshare_pricecolumns, ordered by vault and timestamp.logger (Callable[[str], None]) – Notebook, console, or structured-log adapter accepting one message.
- Returns
Price rows with
returns_1hcalculated between consecutive rows.- Return type
- clean_returns(rows, prices_df, logger=<built-in function print>, outlier_threshold=0.5, display=<function <lambda>>, returns_col='returns_1h')
Clean returns data by removing rows with NaN or infinite values.
- In returns data we have outliers that are likely not real returns, or one-time events that cannot repeat.
Floating point errors: [Share price may jumps wildly when a vault TVL is near zero](https://x.com/0xSEM/status/1914748782102630455)
Bugs: Vault share price method to estimate returns does not work for a particular airdrop
Airdrops: Vault gets an irregular rewards that will not repeat, and thus are not good to estimate the future performance
We clean returns by doing an assumptions - Daily returns higher than static outlier - Daily TVL max does not make sense - Daily TVL min does not make sense - Daily TVL % below lifetime average TVL
- Parameters
rows (dict[eth_typing.evm.HexAddress, eth_defi.vault.vaultdb.VaultRow]) –
prices_df (pandas.DataFrame) –
display (Callable) –
- Return type
- clean_by_tvl(rows, prices_df, logger=<built-in function print>, tvl_threshold_min=1000.0, tvl_threshold_max=99000000000, tvl_threshold_min_dynamic=0.02, returns_col='returns_1h')
TVL-based threshold filtering of returns.
Clean returns from TVL-manipulation outliers
See https://x.com/moo9000/status/1914746350216077544 for manipulation example
Hypercore keeps its PnL/NAV price-derived return because rewriting the return alone would make profit disagree with the cleaned share price. Its low-TVL observations still receive
tvl_filtering_mask=Trueso investment-suitability consumers can exclude them. Other protocols retain the existing zero-return behaviour.- Parameters
rows (dict[eth_typing.evm.HexAddress, eth_defi.vault.vaultdb.VaultRow]) – Vault metadata keyed by address. Retained for compatibility with the existing cleaner interface.
prices_df (pandas.DataFrame) – Timestamp-indexed price rows containing
id,chain,total_assets, and the selected return column.logger – Notebook, console, or structured-log adapter accepting one message.
tvl_threshold_min – Absolute minimum NAV in USD.
tvl_threshold_max – Absolute maximum NAV in USD.
tvl_threshold_min_dynamic – Minimum NAV as a fraction of the vault’s all-time average NAV.
returns_col – Name of the return column to clean.
- Returns
The input frame with TVL audit columns and protocol-appropriate return cleaning applied.
- Return type
- filter_unneeded_row(prices_df, logger=<built-in function print>, epsilon=0.0025)
Dedpulicate data rows with epsilon.
Reduce data size by elimating rows where the value changes is too little
Remove rows where the total asset/share price/total supply change has been too small
Note
This filter conly yields 2% savings in row count, so it turned out not to be worth of the problems.
- Parameters
prices_df (pandas.DataFrame) – Assume sorted by timestsamp
epsilon – Tolerance for floating point comparison
- Return type
- remove_inactive_lead_time(prices_df, logger=<built-in function print>)
Remove initial inactive period from each vault’s price history.
At the beginning of a vault’s lifecycle, total supply may remain constant while the vault is inactive (e.g., 1, 1000, etc.)
When the vault activates, the share price may jump, causing abnormal returns
This function removes the initial rows where total_supply hasn’t changed
Uses exact equality for comparison
Skips initial rows with zero or NaN total_supply to find first valid value
- Parameters
prices_df (pandas.DataFrame) – Price data with ‘id’ and ‘total_supply’ columns. Assumes data is sorted by timestamp within each vault.
- Returns
DataFrame with inactive lead time removed for each vault
- Return type
Build an approximate Hypercore economic-performance index from PnL and NAV.
Hyperliquid does not expose historical vault share supply or an investable unit price through its vaultDetails API. Its rolling NAV and PnL windows can refresh at different timestamps, so the scanner-derived ERC-4626-like supply can change units between daily and HF reads. The July 2026 production investigation found that this left raw multi-hundred-percent moves in cleaned data and that a partial daily/HF repair itself created Order Block Hunter’s
+275.4%clean return. Ledger flows prove NAV accounting for some intervals, but their intra-period ordering is unavailable and therefore cannot recover exact time-weighted investor returns.The cleaned Hypercore price is consequently a conservative performance index. One freshest usable checkpoint is selected per fixed four-hour UTC bucket while retaining its original API timestamp. Missing buckets are not manufactured, and older history remains daily or weekly where Hyperliquid has already downsampled it. Between checkpoints, cumulative account-PnL change is divided by the larger of opening NAV, closing NAV, and one dollar; the resulting returns are compounded from
1.0within each recapitalisation epoch. This denominator prevents unknown capital-flow timing and small NAV from manufacturing performance. Positive returns are capped atmax_positive_returnper selected checkpoint. A return at or below-100%is accepted only when NAV is zero and does not recover in the same epoch; otherwise the price is carried because applying one questionable negative PnL baseline would permanently zero the index. Non-checkpoint rows are also carried, avoiding duplicate daily/HF returns without interpolating future information backwards.A July 2026 follow-up found a narrower timestamp problem in Fish Market. Cumulative PnL increased by
$2,169.43on 17 March while NAV remained unchanged; the same$2,169.43appeared in NAV on 18 March with no additional PnL or capital flow. Treating the first half of this staggered API update as a complete checkpoint produced a clipped+100%return. When a checkpoint within 26 hours exhibits this exact PnL-then-NAV pattern, within a small numerical tolerance, this function carries the premature checkpoint and applies the return at the confirming checkpoint using the larger opening/confirmed NAV. Flat intermediate checkpoints may occur; any other economic change disqualifies the repair. This gives approximately+54.5%for Fish Market. The bounded, positive-only and value-matching conditions avoid suppressing large but reconciled gains in Magixbox, IKAGI and Satori Quantum HF Vault. Because confirmation arrives later, the most recent provisional checkpoint and prices compounded after it may be revised on the next cleaning run.Input uses a timestamp index and requires
id(string),chain(integer),share_price(float),total_assets(float), and cumulative PnL as either exportedaccount_pnl(float) or scanner-shapedcumulative_pnl(float). Optionalwritten_attimestamps select the freshest scanner batch,hypercore_sourcestrings break otherwise equal ties in favour of HF, and booleanepoch_resetvalues separate performance epochs. The output preserves scanner values inraw_share_price, writes audit values tohypercore_repair_status, and recalculates Hypercoretotal_supplyas synthetic index units so thattotal_assets == share_price * total_supplyremains true. Hypercoretotal_supplyis not an actual token supply.- Parameters
prices_df (pandas.DataFrame) – Timestamp-indexed cleaned-price input containing the columns described above. Rows must already be ordered by vault and timestamp.
logger (Callable[[str], None]) – Notebook, console, or structured-log adapter accepting one message.
max_positive_return (float) – Maximum approximate return applied at one four-hour checkpoint. The default is
1.0(100%).
- Returns
A copy with every Hypercore row expressed on its PnL/NAV performance index; all non-Hypercore rows are unchanged.
- Return type
- discard_hypercore_pre_recapitalisation_history(prices_df, logger=<built-in function print>, min_recapitalisation_assets=1000.0, min_recovery_delay=Timedelta('7 days 00:00:00'))
Start a recapitalised Hypercore vault at its new meaningful capital base.
A complete wipe-out followed by new deposits cannot be represented by one continuous share-price series. The old investors have a -100% return, while the new investors must not inherit the destroyed share supply. When a vault has meaningful NAV, reaches zero, and does not regain any positive NAV until after
min_recovery_delay, discard its earlier observations from the cleaned output. The raw parquet remains unchanged.Recovery duration and the new tracking threshold are intentionally separate. The delay is measured to the first value above
HYPERCORE_ZERO_NAV_EPSILON, even when that value is belowmin_recapitalisation_assets. This prevents a sequence such as$2,000 -> $0 -> $900 next day -> $1,000 after seven daysfrom erasing valid history merely because the recovery crossed the display threshold later. Once a durable recovery is established, the first retained observation must have at leastmin_recapitalisation_assetsin NAV and is markedepoch_reset.Raw scanner
epoch_resetvalues are cleared before applying this rule. They mark arbitrary resets of the reconstructed synthetic supply, including funded vaults, and are not evidence of an economic wipe-out. Only the duration/NAV-qualified marker produced here may split the cleaned performance index.The July 2026 production snapshot contained four qualifying episodes across 569 Hypercore vaults. HODL My Perps, HLP Liquidator, Rehobot LR, and Sifu all still qualify when measuring the delay to the first positive NAV, removing 369 rows from cleaned output. The stricter definition was chosen because the same snapshot contained hundreds of transient zero observations which must not reset lifetime performance.
- Parameters
prices_df (pandas.DataFrame) – Vault price data indexed by timestamp, with
id,chain, andtotal_assetscolumns. It must be sorted by vault and timestamp.logger – Notebook or console logging function.
min_recapitalisation_assets (float) – Minimum NAV in USD needed before tracking the new investment epoch.
min_recovery_delay (pandas.Timedelta) – Minimum elapsed time between zero NAV and the first later positive NAV.
- Returns
Price data without the superseded pre-recapitalisation epochs.
- Return type
Fix out rows with share price that is too high.
Sometimes share price jump to an outlier value and back
This caused abnormal returns in returns calculations, messing all volatility numbers, sharpe, charts, etc.
The root cause is bad oracles, fat fingers, MEV trades, etc.
The lookback window is time-based (hours), not row-based, so it works correctly for vaults with non-hourly polling intervals
See
check-share-pricescript for inspecting individual prices
Case Fluegel DAO:
timestamp
chain
address
block_number
share_price
total_assets
total_supply
2024-07-16 15:02:57
8453
0x277a3c57f3236a7d458576074d7c3d7046eb26c
17176415
1.60
373,740.21
232,929.92
2024-07-16 16:02:57
8453
0x277a3c57f3236a7d458576074d7c3d7046eb26c
17178215
1.63
379,832.59
232,929.92
2024-07-16 17:02:57
8453
0x277a3c57f3236a7d458576074d7c3d7046eb26c
17180015
0.33
75,744.97
232,929.92
2024-07-16 18:02:57
8453
0x277a3c57f3236a7d458576074d7c3d7046eb26c
17181815
1.64
382,282.78
232,929.92
Case Untangle Finance:
Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-12 23:14:19 (3206): fixing: 1.038721 -> 1.038721, prev: 1.038827, next: 0.444865 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 00:14:13 (3207): fixing: 1.038931 -> 1.038931, prev: 1.038801, next: 0.444865 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 01:14:09 (3208): fixing: 1.038931 -> 1.038931, prev: 1.038801, next: 0.444865 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 02:14:03 (3209): fixing: 1.038931 -> 1.038931, prev: 1.038801, next: 0.444865 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 03:14:01 (3210): fixing: 1.038931 -> 1.038931, prev: 1.038801, next: 0.444865 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 04:13:56 (3211): fixing: 1.038931 -> 1.038931, prev: 1.038801, next: 0.444865 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 05:13:53 (3212): fixing: 1.038931 -> 1.038931, prev: 1.038801, next: 0.468629 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 06:13:51 (3213): fixing: 1.039134 -> 1.039134, prev: 1.038439, next: 0.468629 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 07:13:45 (3214): fixing: 1.039134 -> 1.039134, prev: 1.038439, next: 0.468629 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 08:13:40 (3215): fixing: 1.039134 -> 1.039134, prev: 1.038439, next: 0.468629 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 09:13:37 (3216): fixing: 1.039134 -> 1.039134, prev: 1.038439, next: 0.482511 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 10:13:27 (3217): fixing: 1.039134 -> 1.039134, prev: 1.038439, next: 0.482511 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-13 11:13:21 (3218): fixing: 1.039134 -> 1.039134, prev: 1.038439, next: 0.482511 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 00:11:51 (3230): fixing: 0.444865 -> 1.0405275, prev: 1.038721, next: 1.042334 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 01:11:42 (3231): fixing: 0.444865 -> 1.0406325, prev: 1.038931, next: 1.042334 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 02:11:33 (3232): fixing: 0.444865 -> 1.0407335, prev: 1.038931, next: 1.042536 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 03:11:36 (3233): fixing: 0.444865 -> 1.0407335, prev: 1.038931, next: 1.042536 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 04:11:26 (3234): fixing: 0.444865 -> 1.0407335, prev: 1.038931, next: 1.042536 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 05:11:17 (3235): fixing: 0.444865 -> 1.0407335, prev: 1.038931, next: 1.042536 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 06:11:10 (3236): fixing: 0.468629 -> 1.0407335, prev: 1.038931, next: 1.042536 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 07:11:01 (3237): fixing: 0.468629 -> 1.040835, prev: 1.039134, next: 1.042536 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 08:10:52 (3238): fixing: 0.468629 -> 1.0406445, prev: 1.039134, next: 1.042155 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 11:10:26 (3239): fixing: 0.468629 -> 1.0406445, prev: 1.039134, next: 1.042155 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 12:10:18 (3240): fixing: 0.482511 -> 1.0406445, prev: 1.039134, next: 1.042155 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 13:10:09 (3241): fixing: 0.482511 -> 1.0406445, prev: 1.039134, next: 1.042155 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-14 14:10:01 (3242): fixing: 0.482511 -> 1.0406445, prev: 1.039134, next: 1.042155 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 04:08:39 (3254): fixing: 1.042334 -> 1.042334, prev: 0.444865, next: 1.04251 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 05:08:33 (3255): fixing: 1.042334 -> 1.042334, prev: 0.444865, next: 1.04251 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 06:08:29 (3256): fixing: 1.042536 -> 1.042536, prev: 0.444865, next: 1.04251 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 07:08:24 (3257): fixing: 1.042536 -> 1.042536, prev: 0.444865, next: 1.04251 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 08:08:20 (3258): fixing: 1.042536 -> 1.042536, prev: 0.444865, next: 1.04251 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 09:08:12 (3259): fixing: 1.042536 -> 1.042536, prev: 0.444865, next: 1.04251 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 10:08:07 (3260): fixing: 1.042536 -> 1.042536, prev: 0.468629, next: 1.042519 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 11:08:03 (3261): fixing: 1.042536 -> 1.042536, prev: 0.468629, next: 1.042519 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 12:07:55 (3262): fixing: 1.042155 -> 1.042155, prev: 0.468629, next: 1.042519 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 13:07:51 (3263): fixing: 1.042155 -> 1.042155, prev: 0.468629, next: 1.042519 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 14:07:49 (3264): fixing: 1.042155 -> 1.042155, prev: 0.482511, next: 1.042519 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 15:07:42 (3265): fixing: 1.042155 -> 1.042155, prev: 0.482511, next: 1.042519 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-15 16:07:36 (3266): fixing: 1.042155 -> 1.042155, prev: 0.482511, next: 1.042354 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-23 02:04:05 (3433): fixing: 1.036482 -> 1.036482, prev: 1.126302, next: 0.487429 Abnormal share price detected for 42161-0x4a3f7dd63077cde8d7eff3c958eb69a3dd7d31a9 at index 2025-10-24 06:01:21 (3457): fixing: 0.487429 -> 1.041995, prev: 1.036482, next: 1.047508
- Parameters
prices_df (pandas.DataFrame) –
- Return type
- sort_and_index_vault_prices(prices_df, priority_ids)
Set up the order of vaults for processing.
If we do debugging we want vaults we debug go first, as the pipeline takes several minutes to run
- Parameters
prices_df (pandas.DataFrame) –
- process_raw_vault_scan_data(rows, prices_df, logger=<built-in function print>, display=<function <lambda>>, diagnose_vault_id=None)
Preprocess vault data for further analysis.
Assign unique names to vaults
Add denormalised vault data to prices DataFrame
Filter out non-stablecoin vaults
Calculate returns, rolling metrics
- Parameters
rows (Union[dict[eth_defi.vault.base.VaultSpec, eth_defi.vault.vaultdb.VaultRow], eth_defi.vault.vaultdb.VaultDatabase]) – Metadata rows from vault database
logger – Notebook / console printer function
display (Callable) – Display Pandas DataFrame function
prices_df (pandas.DataFrame) –
- Return type
- check_missing_metadata(rows, price_ids, prices_df, logger=<built-in function print>)
Check that we have metadata for all vaults in the prices DataFrame.
Vault id is in format:
56-0x10c90bfcfb3d2a7ae814da1548ae3a7fc31c35a0If there are vaults with price data but no metadata, they are logged at error level and their IDs returned so the caller can drop them.
- Parameters
rows (dict) – Metadata rows from vault database
prices_df (pandas.DataFrame) – The full prices DataFrame, used to extract context for missing vaults.
price_ids (pandas.Series) –
- Returns
Set of vault IDs that are missing from the metadata. These should be dropped from the price data before further processing.
- Return type
- generate_cleaned_vault_datasets(vault_db_path=PosixPath('/home/runner/.tradingstrategy/vaults/vault-metadata-db.pickle'), price_df_path=PosixPath('/home/runner/.tradingstrategy/vaults/vault-prices-1h.parquet'), cleaned_price_df_path=PosixPath('/home/runner/.tradingstrategy/vaults/cleaned-vault-prices-1h.parquet'), settlement_db_path=None, logger=<built-in function print>, display=<function display>, diagnose_vault_id=None)
A command line script entry point to take raw scanned vault price data and clean it up to a format that can be analysed.
Reads
vault-prices-1h.parquetand generatescleaned-vault-prices-1h.parquetCalculate returns and various performance metrics to be included with prices data
Clean returns from abnormalities
Stamp the cleaned Parquet with the current Docker
metadata.versionprovenance, matching vault scanner JSON exports
Note
Drops non-stablecoin vaults. The cleaning is currently applicable for stable vaults only.
- Parameters
settlement_db_path (Optional[pathlib.Path]) –
- replace_cleaned_vault_histories(vault_ids, *, vault_db_path=PosixPath('/home/runner/.tradingstrategy/vaults/vault-metadata-db.pickle'), raw_price_df_path=PosixPath('/home/runner/.tradingstrategy/vaults/vault-prices-1h.parquet'), cleaned_price_df_path=PosixPath('/home/runner/.tradingstrategy/vaults/cleaned-vault-prices-1h.parquet'), settlement_db_path=None, logger=<built-in function print>)
Rebuild and atomically replace cleaned histories for selected vaults.
The normal cleaner is deliberately whole-dataset: it reads every raw row, applies its transformations, and emits a new Parquet file. A historical repair only changes a small number of vaults, however, and each cleaner transformation is independent between vault ids. Recompute the complete raw history for the selected ids, then stream-copy all other cleaned row groups into a replacement Parquet. This avoids expensive pandas cleaning for unrelated vaults while preserving their existing cleaned rows and physical
id, timestamporder.The destination remains a single Parquet file, so its bytes must still be rewritten before the atomic replace. The function does not silently drop columns: a selected vault whose cleaned columns do not match the existing output raises an error before replacing the original file.
- Parameters
vault_ids (set[str]) – Canonical lower-case
chain_id-addressids to replace.vault_db_path (pathlib.Path) – Metadata database used for denormalisation and stablecoin filtering.
raw_price_df_path (pathlib.Path) – Raw scanner Parquet containing the replacement histories.
cleaned_price_df_path (pathlib.Path) – Existing cleaned Parquet to update atomically.
settlement_db_path (Optional[pathlib.Path]) – Optional settlement database applied to the selected cleaned rows.
- Returns
Number of cleaned rows written for the selected vaults.
- Return type
- forward_fill_vault(vault_df)
Forward fill missing vault prices up to max_gap_hours.
For displaying, calculating metrics, etc. we want continuous time series
Align random sample interval to 1h
- Parameters
vault_df (pandas.DataFrame) –
Price data for a single vault.
Assume 1h price data.
- Return type