Part I asked whether an AMM surface formed. Part II ran pool state forward. Part III contrasted reserves with book depth. This piece runs the AMM defense layer: fixed fee vs state-responsive fee.
Part III ended on dynamic fees as evidence that the fixed-fee pool was under-defended-not as proof that hooks beat a book. Uniswap v4 dynamic fees make the defense layer callable: a hook can read pool state and set the fee per swap.
The design question is narrower than “can the hook observe more fields?” Under this constant-function market maker (CFMM) setup — specifically a constant-product market maker (CPMM) — does an extra field add information, or only rescale a response the invariant already implies?
I put Campbell et al. (2025) and Baggiani et al. (2025) into the same Rust lab. Campbell et al. supply the fixed-fee LP objective and the routing mechanism: when CEX trading is costly, a stale AMM can still attract fundamental flow by charging a low enough fee. Baggiani et al. supply the policy question — whether observation-based fees can beat a static optimum. Four policies share one FeePolicy interface and the same Monte Carlo paths: fixed 6 bps, fixed 10 bps, oracle-gap, and inventory-gap.
The bridge is market-making control. Campbell et al. define what the pool is trying to protect: fee revenue net of stale-price loss. Baggiani et al. change the fee from a scalar parameter into a policy over market state. A CLOB maker does this through spreads, size, cancels, and exposure limits when flow turns toxic. A programmable AMM cannot reproduce the whole book, but fee is its smallest spread-like defense.
The Campbell baseline
This is a baseline check, not a fee-design breakthrough. A CEX or oracle reference moves before the pool price does. Arbitrage closes the gap; the LP absorbs LVR. Campbell et al. formalize that setting with arbitrageurs, fundamental traders, and a passive LP. Hedged PnL is fee revenue minus tracking error.
In the lab, oracle_gap_bps is pool price vs CEX price in bps-the exposure window before arb trades. arb_delta closes the gap each step; hedged_pnl records what the LP kept net of LVR.
With a 10 bps CEX fee, the optimum AMM fee lands near 6 bps. Higher fees extract more per arb trade but widen the no-trade band and lose fundamental flow. I reproduced the sweep in Rust-1 to 100 bps on one GBM path, then 500-path Monte Carlo.

Below 6 bps, arb keeps trading but the fee does not cover LVR. Above 6 bps, the pool keeps some arb revenue but loses fundamental flow to the CEX.
Fee as a policy
Dynamic fees are control laws over observations. Production hooks override the fee in beforeSwap or via updateDynamicLPFee. The simulation uses the same surface: observe state, choose fee, run arb and fundamental trades.
FeeObservation carries oracle gap (bps), inventory skew, and recent realized volatility. In this comparison, realized volatility is recorded but not used in the fee rule. Each step calls policy.fee(&obs) before trades execute.
FixedFeePolicy reproduces the Campbell baseline. In these runs, fee is stored as a fraction ( 1 bps = 0.0001, so base_fee = 0.0006). The two dynamic policies are:
- OracleGapFeePolicy: fee = base_fee + 0.04 × |oracle_gap_bps| / 10 000, clamped to [1, 20] bps
- InventoryGapFeePolicy: fee = base_fee + 0.01 × |inventory_skew|, clamped to [1, 20] bps
Oracle-gap charges more when the pool has drifted from the CEX. Inventory-gap charges more when reserve composition is imbalanced. Same interface; different stated signal.
Oracle-gap beats fixed 6 bps on every path
Across 500 paired Monte Carlo paths, oracle-gap beat fixed 6 bps on every path: mean Δ hedged PnL +15.2, minimum +8.

The comparison is paired by seed: fixed, oracle-gap, and inventory-gap run on the same GBM path and demand draws. The delta distribution is the useful statistic because it asks what changed when only the fee rule changed.

Inventory-gap also beat fixed 6 bps on every path, but mean gain was only +1.8 -roughly one-eighth of oracle-gap’s advantage.
Inventory skew collapses into oracle gap
Inventory skew is not a new signal here. In a CPMM, both fields are monotone functions of the same quantity-deviation between AMM and CEX price:
oracle_gap_bps = (P_amm − P_cex) / P_cex × 10 000
inventory_skew = (P_amm − P_cex) / (P_amm + P_cex)
When arb keeps the pool near the CEX price, P_amm ≈ P_cex and the skew denominator is approximately 2 × P_cex:
inventory_skew ≈ oracle_gap_bps / 20 000

InventoryGapFeePolicy applies the same signal at lower gain—the step-level audit in the repo shows why. The +1.8 mean gain is amplitude, not a different signal.
This is specific to constant-product pools where arb continuously aligns pool price with the external reference. In pegged-pool designs, reserve imbalance can move while marginal price stays near peg, so inventory can carry information not captured by current oracle gap. Curve’s offpeg_fee_multiplier is aimed at that regime, not at CPMM-style rescaling.
What a hook should test next
If the feature is reserve-derived in CPMM, assume redundancy until shown otherwise. Current oracle gap is the primitive; inventory skew, reserve ratio, and pool price are functions of it. Adding them does not expand the information set. It rescales the response.
The next candidate is not another reserve-derived field. It is path-derived or external:
- Realized volatility: not recoverable from instantaneous CPMM state
- Order-flow imbalance: directional pressure within a block, visible before the swap executes
- Oracle latency: time since last reliable external update
- Toxic-flow proxy: share of recent volume from arb vs fundamental demand
These break collinearity because the invariant does not encode them. The test: does the feature add information beyond current oracle gap under the same path and demand assumptions?
Closing
Part II ran two settings: a seeded path where fees beat LVR, and a deterministic shock where LP-vs-hold stayed negative. The policy layer matters here: oracle-gap dynamic fees beat fixed 6 bps on every sampled path, while inventory-gap adds only a small lift because the two signals are nearly collinear.
That does not prove dynamic fees rescue LP economics in production. The run uses illustrative parameters (σ = 4%, μ = 0, 100 Y fundamental demand per step, CEX fee = 10 bps). The gain still depends on multiplier, demand, and volatility regime, even if the signal relationship is structurally redundant under the CPMM invariant.
Production should be stricter than simulation. This reduced-form world has no gas auction, block latency, queue dynamics, or LP repositioning cost. A hook that freely explores fee settings with LP capital can become statistical gambling once those frictions enter. The safer version starts from a champion rule, deviates only when edge clears cost and uncertainty, and can abstain.
Under CPMM, most fee innovation is control law, not information. The next test is strict: add a path-derived feature (realized volatility or order-flow proxy), check orthogonality to oracle gap on the same paths, then keep it only if paired PnL improves at matched risk.
Appendix: source
- Part I: The token appeared twice. The AMM market formed once.
- Part II: Before MEV, I Built a Rust AMM Lab to Measure Pool State.
- Part III: Hyperliquid Shows What a Market Looks Like Without an AMM Pool.
- Github Repo: amm-lab, Campbell simulation and dynamic-fee policy runs
- Campbell: Campbell, S., Bergault, P., Milionis, J., and Nutz, M. (2025). arXiv:2508.08152.
- Baggiani: Baggiani, L., Herdegen, M., and Sánchez-Betancourt, L. (2025). arXiv:2506.02869.
This post was originally published on my personal blog: https://egpivo.github.io/2026/07/21/dynamic-fees-amm-signal-matters.html
Dynamic Fees in AMMs: When the Signal Matters was originally published in The Capital on Medium, where people are continuing the conversation by highlighting and responding to this story.

















