If you work with DeFi, you eventually run into a difficult question:
How do I know whether an AMM liquidity strategy actually works?
Providing liquidity to an AMM is very different from simply buying and holding an asset.
An LP position can earn trading fees, but it can also suffer from impermanent loss, price movements, rebalancing costs, and other forms of execution risk.
That makes historical testing particularly useful.
One interesting JavaScript/Node.js project in this area is amm-strategy-backtester, a package designed around the idea of testing AMM liquidity strategies programmatically.
Why AMM strategies need a different kind of backtest
A traditional trading strategy might be as simple as:
if price > moving average:
buy
if price < moving average:
sell
An AMM liquidity strategy has a different lifecycle:
Deposit liquidity
↓
Price changes
↓
Trades occur
↓
LP earns fees
↓
Token composition changes
↓
Strategy decides whether to rebalance
↓
Withdraw or continue providing liquidity
The important part is that an LP position is path dependent.
Two markets can finish at exactly the same final price but produce very different LP results because the price may have taken completely different paths to get there.
That is one reason AMM strategy backtesting is interesting.
What is amm-strategy-backtester?
The npm package is intended to provide a programmatic way to experiment with AMM liquidity strategies rather than manually calculating LP performance from historical data.
amm-strategy-backtester on npm
The broader concept is straightforward:
- Provide historical market data.
- Define an LP strategy.
- Simulate how the strategy behaves.
- Track the resulting portfolio.
- Compare the result with alternative strategies or benchmarks.
For developers, this is useful because the strategy can become code instead of a spreadsheet.
That makes it possible to experiment with different rules and repeat the same experiment consistently.
The simplest AMM strategy
Imagine starting with:
$10,000
You could create a baseline strategy:
Deposit liquidity
↓
Never rebalance
↓
Hold until the end of the test
This gives you a baseline against which more sophisticated strategies can be compared.
For example:
Strategy A: Buy and hold
Strategy B: Static LP
Strategy C: Periodic rebalancing
Strategy D: Volatility-based rebalancing
Strategy E: Dynamic liquidity ranges
The goal isn't necessarily to find the strategy with the highest backtest return.
The goal is to understand why each strategy performs differently.
Fees vs. impermanent loss
One of the most important concepts when evaluating an AMM LP strategy is the relationship between fees and impermanent loss.
Suppose an LP earns:
Trading fees: +$1,500
Impermanent loss: -$900
Other costs: -$200
--------------------------------
Net effect: +$400
Looking only at the fee number would give you the wrong impression.
A strategy that generates $1,500 in fees isn't necessarily better than a strategy generating $1,000.
The relevant question is:
What was the total portfolio outcome after accounting for everything?
This is where backtesting becomes valuable.
Why rebalancing matters
Consider a concentrated liquidity strategy.
You start with a price range:
Lower bound: $1,800
Upper bound: $2,200
If the market moves from:
$2,000 → $2,100 → $2,200 → $2,400
your position may eventually move out of the active range.
At that point, your strategy has a decision to make.
Should it:
1. Do nothing?
2. Withdraw liquidity?
3. Move the range?
4. Create a new position?
5. Wait for the market to return?
Each decision produces a different result.
A backtester lets you turn these decisions into explicit rules and evaluate them against historical conditions.
A strategy is just a set of rules
One of the things I like about approaching AMM strategies from a software perspective is that the strategy can be expressed as a deterministic function.
Conceptually:
function strategy(state) {
if (state.price > state.upperRange) {
return "REBALANCE";
}
if (state.price < state.lowerRange) {
return "REBALANCE";
}
return "HOLD";
}
You can then make the rules considerably more sophisticated.
For example:
function strategy(state) {
if (state.volatility > HIGH_VOLATILITY) {
return "WIDEN_RANGE";
}
if (state.price > state.upperRange) {
return "MOVE_RANGE_UP";
}
if (state.price < state.lowerRange) {
return "MOVE_RANGE_DOWN";
}
return "HOLD";
}
The backtesting engine becomes the environment in which those rules can be evaluated.
Don't optimize only for returns
A common mistake in backtesting is to search for the configuration with the highest return.
Imagine testing 100 strategies and discovering this:
Strategy #73
Return: +247%
That looks fantastic.
But then you discover:
Maximum drawdown: -82%
Number of rebalances: 1,842
Transaction costs: extremely high
The strategy might not be useful in practice.
For AMM strategies, I would look at several dimensions:
- Total return
- Net P&L
- Maximum drawdown
- Fees earned
- Impermanent loss
- Number of rebalances
- Time spent out of range
- Capital efficiency
- Gas/transaction costs
- Performance across different market regimes
The best strategy isn't necessarily the one with the largest number in the return column.
Test different market regimes
A strategy that works during a sideways market may behave very differently during a strong trend.
For example:
Market regime Strategy behavior
--------------------------------------
Sideways Potentially favorable
Slow uptrend Needs investigation
Strong uptrend Range may become inactive
Slow downtrend Needs investigation
Sharp crash Potentially significant loss
High volatility Frequent rebalancing
This is why a good AMM backtest shouldn't rely on a single historical period.
Test multiple periods.
Test multiple assets.
Test multiple volatility environments.
And, ideally, keep some data completely outside the optimization process for out-of-sample validation.
Backtesting isn't the same as predicting
This distinction is important.
A backtest answers:
"What would this set of rules have done under these historical assumptions?"
It does not answer:
"What will happen next?"
A strategy can produce an excellent historical equity curve and still fail in live trading.
Potential reasons include:
- Overfitting
- Look-ahead bias
- Poor historical data
- Unrealistic execution assumptions
- Slippage
- Gas costs
- Liquidity changes
- Timing differences
- Market regime changes
The more realistic the simulator, the more useful the result becomes.
There is active research specifically around backtesting concentrated-liquidity market makers on Uniswap V3, which highlights how specialized the problem is compared with ordinary trading backtests.
Why JavaScript is interesting for this
A lot of quantitative research is done in Python, but JavaScript/TypeScript has a useful place in DeFi development.
The ecosystem is already heavily connected to:
Node.js
TypeScript
Ethereum tooling
RPC providers
DEX APIs
Web3 libraries
That means you can potentially keep strategy research closer to the code used by your actual application or trading infrastructure.
For example:
Historical data
↓
Backtest engine
↓
Strategy
↓
Performance metrics
↓
Visualization
↓
Live strategy
The long-term goal shouldn't necessarily be:
"I built a profitable backtest."
It should be:
"I built a strategy whose assumptions I understand and can progressively validate against real execution."
What I would add to a serious AMM backtester
If you're building on top of an AMM backtesting project, there are several areas worth paying attention to.
1. Real swap-level data
Candle data can be convenient, but AMMs operate through swaps.
For higher-fidelity simulation, historical swap events and pool state can become extremely important.
2. Execution costs
A strategy that rebalances every few minutes may look great before costs.
After gas and execution costs, the result could be completely different.
3. Slippage
Large LP positions shouldn't necessarily assume perfect execution.
The simulator should model the relationship between trade size, pool liquidity, and execution price.
4. Concentrated liquidity
For Uniswap V3-style strategies, price ranges and ticks introduce another layer of complexity.
A realistic simulator needs to understand what happens when price moves through those ranges.
5. Out-of-sample testing
Don't optimize and evaluate on exactly the same data.
A better workflow is:
Historical data
↓
Training / optimization period
↓
Strategy selection
↓
Out-of-sample period
↓
Robustness analysis
6. Monte Carlo and stress testing
Historical data represents only one path.
Stress testing can help answer questions such as:
What if volatility doubles?
What if price crashes 40%?
What if fees fall?
What if rebalancing costs increase?
What if the market trends continuously?
These questions are often more useful than another decimal place of historical return.
A practical research workflow
If I were experimenting with amm-strategy-backtester, I'd structure the research process like this:
Step 1
Choose an AMM and trading pair
↓
Step 2
Collect historical data
↓
Step 3
Create a simple buy-and-hold benchmark
↓
Step 4
Create a static LP benchmark
↓
Step 5
Implement one active strategy
↓
Step 6
Run the same period across all strategies
↓
Step 7
Measure fees, P&L, IL and drawdown
↓
Step 8
Add realistic costs
↓
Step 9
Test different market regimes
↓
Step 10
Validate using out-of-sample data
This approach is much more informative than immediately trying to build a complicated "AI-powered" LP strategy.
Start simple.
Establish a baseline.
Then add complexity one variable at a time.
Final thoughts
AMM liquidity provision is an interesting software problem because it sits at the intersection of:
DeFi
+
Market microstructure
+
Quantitative research
+
Software engineering
A project such as amm-strategy-backtester is useful because it encourages developers to treat liquidity provision as something that can be defined, simulated, measured, and iterated on.
But the important lesson is that a backtest is only as good as its assumptions.
If your simulator ignores execution costs, liquidity changes, slippage, timing, or other important parts of the market, a beautiful equity curve may tell you very little.
The real objective should therefore be:
Build the most honest simulation you can, not the most profitable-looking one.
And once the simulation is trustworthy, you can start asking the much more interesting questions:
Which LP strategy is robust?
Which market conditions does it survive?
How much of the return comes from fees versus directional exposure?
And does the strategy still work when the assumptions become less favorable?
That's where AMM backtesting becomes more than a coding exercise — it becomes a serious quantitative research tool.
Top comments (0)