How to backtest a DeFi lending strategy without look-ahead
How do you backtest a lending or looping strategy without applying today's parameters to past data? Rebuild every parameter the strategy depends on from the protocol's own events, dated by the block that executed each change. One collateral on one Aave market shows why the usual sources fall short:
- The forum dates a change weeks early. WBTC's liquidation threshold on Aave V3 Optimism rose 34 days after the proposal was posted.
- A proposal can state the wrong starting value. It said the liquidation penalty was 10%. The chain had shown 9.4% for three weeks.
- A daily snapshot misses states that last hours. For 73 minutes on 30 October 2026, WBTC could be supplied but counted for nothing as collateral.
- The rate model changes too. For 64 hours, the steep part of the USDC rate curve was cut from 10% to 6%.
As of 5 November 2026. Every onchain figure below links to the transaction that set it.
Why today's parameters mislead
A lending market runs on rules that governance keeps changing: loan-to-value (LTV, the most you can borrow against a collateral), liquidation threshold (the point at which a position can be liquidated), liquidation penalty, caps, the interest rate model, and whether a reserve is frozen (no new supply or borrowing). On Aave, a position can be liquidated once its health factor falls below 1:
Health factor = (collateral value × weighted average liquidation threshold) / borrowed value
Run a 2023 position through a later threshold and the backtest liquidates different positions at different times. That is look-ahead bias: the test uses information that did not exist on the day of the trade. It matters to a desk testing a loop and to a curator testing how a vault's markets would have held up. Macroeconomic data handles the same problem with vintages. The St. Louis Fed's ALFRED lets you "retrieve each economic data release (vintage) that was available on a specific date in history." Protocol parameters need the same treatment, and the chain already stores them.
A dated example: WBTC on Aave V3 Optimism
This is every change to WBTC's LTV, liquidation threshold and liquidation penalty on Aave V3 Optimism, plus its freeze flag, read from the protocol's configuration contract:
| Effective (UTC) | LTV | Liq. threshold | Liq. penalty | Change |
|---|---|---|---|---|
| 11 Mar 2022 | 70% | 75% | 10% | Listing |
| 1 Mar 2023 | 70% | 75% | 9.4% | Penalty cut |
| 25 Apr 2023 | 73% | 78% | 8.5% | AIP-206 |
| 5 Sep 2023 | 73% | 78% | 7.5% | Penalty cut |
| 29 Oct 2026, 23:03 | 0% | 78% | 7.5% | Frozen |
| 30 Oct 2026, 12:44 | 0% | 78% | 7.5% | Unfrozen, LTV still 0 |
| 30 Oct 2026, 13:57 | 73% | 78% | 7.5% | LTV restored |
The history shows four things.
1. Date each change by the block that executed it
Chaos Labs, then one of Aave's risk managers, proposed raising WBTC's threshold from 75% to 78%. The change took five weeks to reach the chain:
- 22 March 2023: proposal posted on the forum.
- 31 March: offchain Snapshot vote closed.
- 22 April: AIP-206 executed on Ethereum, after an onchain vote from 18 to 21 April.
- 25 April, 17:35 UTC: live on Optimism, through the bridge executor that carries Ethereum decisions to the L2.
Keyed to the forum post, a backtest applies the change 34 days early. Keyed to the Snapshot result, 25 days early. Keyed to the Ethereum execution, 3 days early.
A hypothetical position shows what is at stake. Take $100,000 of WBTC against $70,000 of stablecoin debt, the most a 70% LTV allowed, and ignore accrued interest. After an 8% fall in WBTC the health factor is 0.99 under a 75% threshold, open to liquidation, and 1.03 under 78%. And a strategy that borrows the most it can takes a bigger loan under the new 73% LTV, so the two backtests don't even hold the same position.
2. Read values from the contract, not from the proposal
The 22 March proposal gives WBTC's current liquidation penalty as 10% and proposes 8.5%. On chain it had been 9.4% since 1 March 2023, when an earlier Gauntlet proposal took effect. The cut that executed in April was 0.9 points, not the 1.5 the proposal describes. Use the forum for the reason behind a change and the contract for its value.
3. Capture states that last hours
On 29 October 2026, after the Binance hot wallet exploit, the multisigs Aave keeps for emergencies froze 175 reserves on Aave V3 across 16 networks and 117 on Aave V4. On Optimism, WBTC was frozen at 23:03 UTC and unfrozen at 12:44 UTC the next day. Its LTV went to 0 with the freeze but stayed at 0 until 13:57, because in current Aave V3 code unfreezing a reserve does not restore its LTV: the emergency admin restores it in separate calls, outside e-mode and inside each e-mode (Aave's higher-LTV mode for correlated assets) where the asset is collateral. So for 73 minutes WBTC could be supplied again but counted for nothing as collateral. A history built only from freeze events misses that window.
A daily snapshot misses all of this, and the rules changed too: according to the post from LlamaRisk, another Aave risk manager, users could repay and withdraw and liquidations kept running, but no one could add collateral. Between 23:03 and 12:44 UTC, repaying debt was the one way to raise a health factor.
4. Treat the rate model as a parameter
At 02:01 UTC on 30 October, about three hours after WBTC was frozen, the USDC rate curve on Aave V3 Optimism was changed: Slope2, the steep part above 90% utilisation, went from 10% to 6%. LlamaRisk had recommended the cut because Optimism USDC was at 94.9% utilisation and, with new supply blocked, a higher rate mostly added to borrowers' debt. It went back to 10% on 1 November at 18:02 UTC, 64 hours later. A loop that borrowed USDC in those days paid a rate no daily snapshot or proposal would show.
Vault strategies have the same problem with fees. On Morpho, a V1 vault's fee change takes effect at once. On V2 it waits for a timelock the curator sets per function, which can be zero. Read each vault's fee and timelock history rather than assume a delay.
The method, step by step
- List every parameter the strategy touches. For an Aave loop: LTV, liquidation threshold, penalty and liquidation protocol fee for each collateral; reserve factor; caps, debt ceilings and isolation mode; the rate model of each borrowed asset; e-mode settings; frozen and paused flags; the close factor rules; and the oracle each reserve reads, including any price caps on it. For a vault: fees, timelocks, allocation caps and the markets it can use.
- Source each one from events. On Aave V3 most changes come from the configuration contract:
CollateralConfigurationChanged,ReserveFrozen,ReservePaused,AssetLtvzeroInEModeChangedand the other e-mode events,ReserveInterestRateDataChanged, and cap changes. Oracle source changes come from the oracle contract (AssetSourceUpdated). Store block, timestamp, transaction and value for each. - Index by block number. For each simulated action, take the latest value set at or before that block. This is an as-of join, the same one used for vintage data.
- Date by execution. Keep the forum post, vote and proposal as metadata that explains a change. On L2 deployments, the L1 execution is not the effective date either.
- Use prices as the protocol saw them. Liquidations trigger on the oracle price read at that block, not on an exchange close. Take the oracle value from the same block as the parameters.
- Reconcile against state. Spot check the rebuilt history with direct contract reads at chosen
blocks (on Aave,
getConfigurationagainst an archive node). If the two disagree, you are missing an event type.
Limits
- A clean history does not make a clean backtest. Point-in-time inputs remove look-ahead from the data. They do not stop overfitting from trying many variants of a strategy on the same history, a problem set out by Bailey, Borwein, López de Prado and Zhu (2017).
- Replaying a position under different rules assumes everyone else acts as they did. Liquidators, arbitrageurs and other borrowers might not.
- Event meanings change with upgrades. Before Aave 3.1 a freeze did not touch LTV; in current code an unfreeze does not restore it. Read each event in the context of the contract version that emitted it.
- A backtest is not a forecast. It shows how a strategy fared under the rules that applied. It says nothing certain about the next change.
Doing this across a book
One asset on one market already needed several event types, an L1-to-L2 execution trail and a check on the contract version. A real book spans many reserves, vaults and chains. Datablocks indexes every EVM chain from genesis and updates every block, so a backtest can run on what was known then, failures included. If you are building one, book a call below and tell us which protocols and strategies you need covered.