📝

Draft article

This draft is visible to admins and superusers only. Sign in with an authorized account.

← Retour aux articles
July 28, 2026
5 min de lecture

Active Uniswap v3 LPing as Market Making: Range Selection, Rebalancing, and Delta Hedging

Active Uniswap v3 LPing as Market Making: Range Selection, Rebalancing, and Delta Hedging
#uniswap
#defi
#market making
#delta hedging
#lvr
#jit liquidity
#quant
#ethereum

A passive Uniswap v3 position is a set of resting limit orders you cannot cancel. An active one — chosen range, re-centered on a schedule, hedged on a perp — is a market-making book, and it should be run like one. That reframing changes every decision. A market maker sizes quotes from a volatility forecast, not a gut feel. A market maker treats every requote as a cost, not a reflex. A market maker knows its inventory delta at all times and decides deliberately whether to carry it or hedge it. And a market maker knows that some of its counterparties are faster and better-informed, and prices that in.

The concentrated-liquidity article built the primitives: ticks, liquidity LL, the token-amount formulas, feeGrowthInside, and the fact that an LP position is a short-volatility payoff whose running cost is loss-versus-rebalancing (LVR). This piece is the operator's manual on top of that foundation. We size the range from a variance forecast, frame the rebalance decision as an explicit expected-fee-gain versus realized-cost trade, derive the position delta straight from the value function and neutralize it with perpetual futures, look at just-in-time (JIT) liquidity as the extreme case of active LPing, and close with the ways an honest on-chain backtest of all this can lie to you. Throughout, the mental model is Avellaneda–Stoikov with two brutal modifications: your quotes are on a public ledger before they fill, and every requote costs gas.

Range width from a volatility forecast

The single most consequential parameter of an active LP is the range width, and it is a pure volatility bet. Too wide and you dilute your liquidity across ticks that rarely trade, earning fees at the anemic rate of a v2 pool. Too narrow and you concentrate depth beautifully — right up until price walks out of your range, you go 100% into the losing asset, and you earn nothing until you rebalance (crystallizing the divergence loss on the way). The optimal width is the one that keeps price inside the range often enough that the concentrated fee rate more than pays for the LVR that concentration amplifies. That is a statement about the distribution of future returns, so you size it from a forecast of that distribution.

Model log-price over a holding horizon TT as approximately ln(PT/P0)N(0,σ2T)\ln(P_T/P_0) \sim \mathcal{N}(0, \sigma^2 T), with σ\sigma the per-period volatility. A symmetric kk-sigma range in log-price is

ln ⁣pbP0=kσT,ln ⁣paP0=kσT,\ln\!\frac{p_b}{P_0} = k\,\sigma\sqrt{T}, \qquad \ln\!\frac{p_a}{P_0} = -\,k\,\sigma\sqrt{T},

so pb=P0ekσTp_b = P_0\,e^{k\sigma\sqrt{T}} and pa=P0ekσTp_a = P_0\,e^{-k\sigma\sqrt{T}}. Choosing kk is choosing the probability of staying in range: k=1k=1 keeps price inside for roughly 68% of horizons, k=2k=2 for ~95%. Wider kk trades fee rate for time-in-range, and the sweet spot depends on the fee tier and the vol regime — which is exactly the quantity a market maker forecasts rather than guesses.

The translation to on-chain parameters is one line, because ticks are log-price. Since p(i)=1.0001ip(i) = 1.0001^i, the tick index is i=lnP/ln1.0001i = \ln P / \ln 1.0001, and ln1.0001104\ln 1.0001 \approx 10^{-4}, so the half-width in ticks is approximately kσT×104k\sigma\sqrt{T}\times 10^4. Snap to the pool's tick spacing and you have your bounds:

import numpy as np

TICK = np.log(1.0001)   # ≈ 1.0000e-4, one tick in log-price

def range_from_vol(price, sigma_per_day, horizon_days, k=1.0, spacing=10):
    """Symmetric k-sigma range -> tick bounds for a v3 position.
    `price` is the RAW pool price (token1/token0, base units)."""
    sig_h = sigma_per_day * np.sqrt(horizon_days)     # vol over the horizon
    i0 = round(np.log(price) / TICK)                  # current tick
    half = round(k * sig_h / TICK)                    # half-width in ticks
    lower =  ((i0 - half) // spacing) * spacing        # floor to spacing
    upper = -(-(i0 + half) // spacing) * spacing       # ceil  to spacing
    return lower, upper, np.exp(lower*TICK), np.exp(upper*TICK)

A worked number fixes intuition. Take ETH with a forecast daily vol of 4%, a one-week re-centering horizon (T=7T = 7 days), and k=1k = 1. Then σT=0.047=0.1058\sigma\sqrt{T} = 0.04\sqrt{7} = 0.1058, so the range spans e±0.1058e^{\pm 0.1058}, i.e. roughly [0.90,1.11]×P0[0.90,\,1.11]\times P_0 — about a ±10.6%\pm 10.6\% band, or ±1058\pm 1058 ticks snapped to spacing. The width is linear in the vol forecast (to first order in log-space), which is the whole point: your liquidity concentration should breathe with the market's, not sit at a static "±10% because that felt right last month." Vary the two knobs — the regime and the confidence kk — and the band moves the way a market maker's quoted spread moves:

Forecast daily σ\sigma Regime k=1k=1 half-width k=2k=2 half-width Approx. ticks (k=1k=1)
2% calm ±5.3% ±10.6% ±529
4% normal ±10.6% ±21.2% ±1058
6% elevated ±15.9% ±31.8% ±1587
8% stressed ±21.2% ±42.3% ±2116

Read the table as a quoting policy, not a lookup. A tighter band earns a higher fee rate per dollar while in range (more depth per tick) but exits range sooner and re-centers more often; a wider band is the opposite. The concentrated-liquidity multiplier is the lever — a ±5%\pm 5\% band quotes roughly an order of magnitude more depth per tick than a full-range v2 position, and earns fees at that multiple while in range — but it is also that same multiple more exposed to LVR. Range width is where you choose your point on the fee-rate-versus-time-in-range frontier, and the vol forecast is what tells you where that frontier sits today.

One constraint bounds the tight end: tick spacing. Your bounds must snap to the fee tier's spacing, so the minimum range you can express is set by the pool you chose — roughly ±10 bp on the 0.05% tier (spacing 10), ±60 bp on the 0.30% tier (spacing 60), ±2% on the 1% tier (spacing 200). That couples two decisions that look independent: fee tier selection is range-resolution selection. A stable pair whose vol forecast wants a ±0.3% band can only realize it on the 0.05% tier; try it on the 1% tier and the tick grid forces you at least eight times too wide, diluting the very concentration you were paying the higher fee to get. Match the tier's granularity to the width your forecast asks for, not the other way round.

Mapping from a GARCH volatility forecast to Uniswap v3 tick bounds, showing the range widening in high-vol regimes and tightening in calm ones

Where does σ\sigma come from? Realized volatility over a trailing window is the crude answer and it is badly backward-looking — it tells you the range you should have set last week. The right tool is a conditional-variance model that reacts to the current regime: a GARCH(1,1) forecast aggregated to the horizon, as developed in GARCH volatility forecasting for crypto. GARCH gives you σ^t+12\hat\sigma^2_{t+1} and, by iterating the recursion, an hh-step-ahead variance whose square root is the σT\sigma\sqrt{T} you want. Crypto's leverage effect — vol jumps more on down moves — argues for an asymmetric variant, and the same vol-targeting machinery that resizes a directional position by 1/σ^1/\hat\sigma resizes an LP range by σ^\hat\sigma: opposite direction, identical forecast. Feed that series into range_from_vol on each re-center and the position's concentration tracks the forecast automatically. Which raises the question this article turns on next: how often should you re-center?

Rebalancing: the fee-gain versus realized-cost trade

Over-rebalancing is the number-one destroyer of retail LP returns, and it is a subtle killer because each individual re-center feels responsible. Price drifted toward your range edge, so you recentered to "stay active." But a v3 re-center is not free requoting on a CEX. It is three costs stacked:

  1. Gas — burn the old position, mint the new one, and (usually) a swap to rebalance inventory. On mainnet that is a non-trivial fixed cost per cycle, brutal for small positions.
  2. Swap cost — to re-center you must trade your lopsided inventory back toward the new range's ratio, paying the swap fee and price impact on that trade.
  3. Crystallized LVR — this is the invisible one. While price wandered, the position accumulated divergence loss on paper. Holding through a round-trip in price would recover much of it (you keep the fees, give back the inventory swing). Re-centering realizes that loss: you sell the asset you're now heavy in, at the depressed price, and reset. Every recenter converts paper LVR into booked LVR.

So the rebalance decision is not "am I near the edge?" but a market maker's requote calculus: does the expected future fee uplift from a better-centered position exceed the realized cost of getting there? This is structurally the transaction-cost control problem — the optimal policy is a no-rebalance band around center, not a trip-wire. Inside the band you let inventory drift and keep collecting fees; you only act when the expected benefit clears the cost.

(rfeecenteredrfeenow)VE[τ]expected fee uplift over next holding period τ    >    ggas+cswap+ΔLVRrealizedcost of re-centering now\underbrace{\big(r_{\text{fee}}^{\text{centered}} - r_{\text{fee}}^{\text{now}}\big)\cdot V \cdot \mathbb{E}[\tau]}_{\text{expected fee uplift over next holding period } \tau} \;\;>\;\; \underbrace{g_{\text{gas}} + c_{\text{swap}} + \Delta\text{LVR}_{\text{realized}}}_{\text{cost of re-centering now}}
def should_rebalance(pos, price, fee_apr, sigma, gas_usd, swap_bps):
    """Rebalance only if expected fee uplift beats the realized recenter cost."""
    V = pos.value(price)
    recenter_cost = (gas_usd
                     + swap_bps * 1e-4 * pos.inventory_to_swap(price)  # fee+impact
                     + pos.crystallized_lvr(price))                    # the hidden one
    tau = pos.expected_time_in_range(price, sigma)   # horizon until next recenter
    fee_uplift = (pos.fee_rate_centered(price) - pos.fee_rate_now(price)) \
                 * fee_apr * V * tau
    return fee_uplift > recenter_cost

A rebalance decision with numbers

Make it concrete. Say you hold a $50,000 position in an ETH/USDC 0.05% pool, price has drifted to the edge of your range, and re-centering would lift your effective fee capture from an anemic rnow=8%r^{\text{now}} = 8\% APR (you are barely in range) back to rcentered=30%r^{\text{centered}} = 30\% APR. Your GARCH forecast says a freshly-centered range of this width holds for about E[τ]5\mathbb{E}[\tau] \approx 5 days =0.0137= 0.0137 years before you would recenter again. The gross uplift is

(0.300.08)×$50,000×0.0137    $150.(0.30 - 0.08)\times \$50{,}000 \times 0.0137 \;\approx\; \$150.

Against that, the cost of recentering: $25 of gas (mint + burn + swap), a swap of the lopsided inventory — say $20,000 crosses at 5 bp of fee-plus-impact for $10 — and the crystallized LVR from selling into the move, another $60. Total realized cost \approx \95.Since. Since $150 > $95, you recenter — but note how close it is, and how a \5 gas spike or a shorter expected hold flips the sign. Now shrink the position to $5,000 with the same percentages: the uplift falls to $15 while gas stays $25, and the trade is never worth doing. The fee-uplift term scales with notional; the gas term does not. That single asymmetry is why small mainnet LPs should either run one wide, rarely-touched range or stay out.

Threshold versus periodic

Two regimes fall out of this inequality, and they are the honest answer to "threshold-based vs periodic." Threshold (band) rebalancing — act when price exits a tolerance band — is the theoretically correct shape, because it conditions on the state that actually matters. Periodic rebalancing — recenter every NN blocks or hours regardless — is a crude approximation that is defensible only because it is simpler to run and easier to reason about; its pathology is recentering during chop that would have mean-reverted for free, paying gas and crystallizing LVR for a round-trip you would have won by doing nothing. Empirically, the wider your fee tier and the higher the gas, the wider your band should be and the rarer your rebalances — a 1% pool re-centered daily is almost always lighting money on fire. The uncomfortable corollary: for many small positions on mainnet, the fee-uplift term never clears the gas term, and the correct number of rebalances is zero — set a wide range once and leave it, or don't LP at all.

This is also why the venue matters as much as the strategy. The whole inequality is dominated by the gas term on Ethereum mainnet, where a rebalance cycle costs real money; on an L2 (Arbitrum, Base, Optimism) the same cycle costs cents, so ggasg_{\text{gas}} collapses, the band that clears it narrows, and tight-range active LPing with frequent re-centering becomes viable for exactly the small positions that could never afford it on L1. That is not a footnote — it is why serious active LP strategies have largely migrated to L2s, and why "how often to rebalance" has a different answer on every chain. The LVR and swap-cost terms do not shrink with gas, though, so cheap gas lowers the barrier to over-rebalancing as much as to sensible rebalancing; the discipline of the inequality matters more where gas stops enforcing it for you.

There is a third option that avoids re-centering entirely: the one-sided range. Instead of a symmetric band that you slide to follow price, place an asymmetric range that expresses a view — liquidity only below spot if you are happy to accumulate the asset on the way down, only above if you want to distribute it on the way up. This is the on-chain limit-order use case, and it turns a "rebalance" into a "refill": when a one-sided range fills completely (price crosses it), you have executed your intended buy or sell at a fee-enhanced average price, and the decision is whether to re-arm, not whether to chase. For accumulation and distribution mandates it dominates symmetric re-centering, because the inventory swing you would fight with a hedge is the position you actually wanted.

A no-rebalance band around the position center, contrasting threshold-based recentering with periodic recentering and showing gas plus crystallized LVR as the cost that must be cleared

The connection to LVR is worth stating sharply, because it links this whole series: the sum of your realized re-center losses over the life of a position, benchmarked correctly, converges to the LVR the IL/LVR analysis predicts from the price path's realized variance. Rebalancing does not avoid LVR; it chooses when to pay it and in what currency. Rebalance too often and you also pay gas and swap fees on top of the LVR you were going to pay anyway. That is why over-rebalancing dominates the failure modes: it is LVR plus a self-inflicted tax.

Delta-hedging the LP with perpetual futures

If the range width is your vol bet and the rebalance band is your inventory policy, the delta hedge is how you separate the two so you are running a market-making book (short vol, collect the spread) and not a directional book (long or short ETH by accident). To hedge you need the position's delta, and the beauty of v3 is that it drops straight out of the value function with no numerics.

Value of an in-range position, in units of token1 (the numeraire, e.g. USDC), is the expression derived in the concentrated-liquidity piece:

V(P)=L(2PpaPpb).V(P) = L\left(2\sqrt{P} - \sqrt{p_a} - \frac{P}{\sqrt{p_b}}\right).

Differentiate with respect to the price PP of the risky asset (token0, e.g. ETH):

Δ  =  VP  =  L(1P1pb)  =  x(P).\Delta \;=\; \frac{\partial V}{\partial P} \;=\; L\left(\frac{1}{\sqrt{P}} - \frac{1}{\sqrt{p_b}}\right) \;=\; x(P).

The position delta equals the amount of token0 the position currently holds. This is not a coincidence and it is worth pausing on: V=xP+yV = x\cdot P + y, so VP=x+PxP+yP\tfrac{\partial V}{\partial P} = x + P\tfrac{\partial x}{\partial P} + \tfrac{\partial y}{\partial P}, and the AMM invariant makes the marginal on-curve trade value-neutral at the current price, i.e. Px+y=0P\,\partial x + \partial y = 0. The two trailing terms cancel and the delta is just the ETH inventory. An LP is long the risky asset by exactly the amount of it currently in the position — a number that shrinks as price rises (the position sells ETH into strength) and grows as price falls (it buys the dip). To neutralize, hold a short perp of size x(P)x(P) in ETH terms.

def lp_delta(L, price, pb):
    """dV/dP for an in-range v3 position == token0 (ETH) currently held."""
    sp, spb = price**0.5, pb**0.5
    return L * (1.0/sp - 1.0/spb)      # short this many ETH on a perp to neutralize

def lp_gamma(L, price):
    return -L / (2.0 * price**1.5)     # < 0: short gamma -> hedge must be re-shorted

The delta across the range, in numbers

Reuse the exact position from the concentrated-liquidity worked example: L=738.37L = 738.37 on the range [2500,3500][2500, 3500], minted at P=3000P = 3000. The delta x(P)=L(1/P1/pb)x(P) = L(1/\sqrt{P} - 1/\sqrt{p_b}) is the short you must carry, and it walks monotonically from a full ETH bag at the bottom to flat at the top:

PP (USDC/ETH) Position ETH =Δ= \Delta Perp hedge
2500 (lower bound) 2.287 short 2.287 ETH
2750 1.599 short 1.599 ETH
3000 (minted here) 1.000 short 1.000 ETH
3250 0.471 short 0.471 ETH
3500 (upper bound) 0.000 no hedge

The hedge is not "short 1 ETH and forget it." As price rallies from 3000 to 3250 the position sells half its ETH into strength and your required short drops from 1.0 to 0.47 — so you must buy back 0.53 ETH of hedge, at the higher price. On a sell-off to 2750 the position buys ETH and your short must grow to 1.60, sold at the lower price. Buy-high, sell-low on the hedge, every time: that is negative gamma made cash-flow-real, and its running cost is the LVR line in the ledger below.

The gamma is negative — 2V/P2=L/(2P3/2)<0\partial^2 V/\partial P^2 = -L/(2P^{3/2}) < 0 — which is the short-straddle payoff seen from the risk side. Negative gamma means the hedge is never static: as price rises the position's delta falls (it holds less ETH), so a short you sized at yesterday's price is now too large and you must buy some back; as price falls, delta rises and you must short more. You are always trading in the losing direction to stay neutral, and the cumulative cost of that continuous re-hedging is precisely LVR. That cost has a closed form for a v3 position, which is worth deriving because it turns the hedge into a number you can forecast. The IL/LVR analysis gives the instantaneous LVR rate under a GBM price of volatility σ\sigma as =12σ2P2x(P)\ell = \tfrac{1}{2}\sigma^2 P^2\,|x'(P)|, and we just computed x(P)=L/(2P3/2)|x'(P)| = L/(2P^{3/2}) — the magnitude of the position's gamma. Substituting:

(P)  =  σ2P22L2P3/2  =  σ2LP4.\ell(P) \;=\; \frac{\sigma^2 P^2}{2}\cdot\frac{L}{2P^{3/2}} \;=\; \frac{\sigma^2 L \sqrt{P}}{4}.

That is the dollar-per-unit-time bleed the delta hedge locks in — proportional to LL (concentration is a multiplier on the bleed as much as on the fees) and to the square of the vol forecast that sized your range. It also hands you the break-even test directly: the position is +EV before funding and gas only if the fee income rate clears it,

ϕνin-rangefee rate  >  σ2LP4,\underbrace{\phi \cdot \nu_{\text{in-range}}}_{\text{fee rate}} \;>\; \frac{\sigma^2 L \sqrt{P}}{4},

where ϕ\phi is the fee tier and νin-range\nu_{\text{in-range}} is the swap volume flowing through your range per unit time. Both sides scale with LL, so concentration cancels in the ratio — which is the deep reason range width alone never makes a losing pool profitable. What clears LVR is fee-generating volume relative to volatility, i.e. a pool where traders churn faster than arbitrageurs pick you off. A perfectly delta-hedged LP is not risk-free — it has swapped price risk for the certainty of paying \ell — and its P&L reduces to a clean ledger:

P&Lhedged  =  feespremium    LVRre-hedge cost    fundinghedge carry    gasoperations.\text{P\&L}_{\text{hedged}} \;=\; \underbrace{\text{fees}}_{\text{premium}} \;-\; \underbrace{\text{LVR}}_{\text{re-hedge cost}} \;-\; \underbrace{\text{funding}}_{\text{hedge carry}} \;-\; \underbrace{\text{gas}}_{\text{operations}}.

That funding term is the rent you pay to hold the hedge, and it deserves respect. A perpetual future's price is tethered to spot by periodic funding payments; a short position pays funding when the rate is negative and receives it when positive. As covered in how funding rates kill your leverage, crypto perp funding is positive on average — longs pay shorts in the typical contango regime — which means the delta-hedging short LP is often paid to carry the hedge, a genuine tailwind that can flip the whole trade positive. But it is regime-dependent and mean-reverting; in a sustained sell-off funding goes negative and your hedge starts costing you on top of everything else. Size the hedge to your realized delta, mark the funding as a real line item, and never assume the historical positive-funding tailwind is a constant.

The ledger, in numbers

Put the whole book on one page for the 6,523.69positionfromtheworkedexample(6,523.69 position from the worked example (L = 738.37,range, range [2500,3500],, P=3000),helddeltahedgedforoneweekata4), held delta-hedged for one week at a 4%-daily vol forecast. Using \ell = \sigma^2 L\sqrt{P}/4withthedailywith the daily\sigma$, the LVR bleed is

day=0.042×738.37×30004$16.18,week$113.\ell_{\text{day}} = \frac{0.04^2 \times 738.37 \times \sqrt{3000}}{4} \approx \$16.18, \qquad \ell_{\text{week}} \approx \$113.

That is 1.74% of the position in a single week — and against a full-range v2 position the same capital would bleed only σ2/80.14%\sigma^2/8 \approx 0.14\%, a 12.4×12.4\times ratio that is exactly the concentration multiplier of this range. Concentration is a lever on both blades: 12.4× the fee rate while in range, 12.4× the LVR. To clear $113/week the position needs a fee yield on the order of 90% APR — sobering, and the honest reason tight ranges in high vol so often lose. Funding, meanwhile, is a rounding error here: hedging roughly 1 ETH of notional at a typical +0.01%/8h rate earns about $6 over the week (the short collects in positive-funding regimes), a mild tailwind that neither makes nor breaks the trade. And each rebalance would add ~$25 of gas on top. The lesson the ledger teaches at a glance: fees are the only line item that can save you, LVR is the one that will sink you, and funding and gas are the details — size the range so the first beats the second, or don't put the trade on.

In-range v3 position delta as a function of price, equal to the token0 inventory, with a short perpetual futures hedge neutralizing it and funding shown as the carrying cost

Two practical notes. First, hedge granularity is its own cost trade-off: re-hedging every tick minimizes tracking error but maximizes perp trading fees and funding churn, so most desks re-hedge on a delta band, the same no-trade-region logic as the LP rebalance. Second, the hedge only neutralizes delta while in range; once price exits, the position becomes 100% one asset with a flat or fully-linear delta, and a hedge sized for the in-range regime is now wrong — another reason the range width and the rebalance policy cannot be designed independently of the hedge.

Where to put the hedge

The hedge instrument is a live decision, not a detail. A centralized-exchange perp (Binance, Bybit) gives the deepest liquidity and tightest spreads but reintroduces custodial and bridging risk — your LP is on-chain, your hedge is not, and the two legs can desync in a fast market or a withdrawal freeze. An on-chain perp (Hyperliquid, GMX, dYdX) keeps everything in one settlement domain and is composable with the LP position, at the cost of thinner books and its own oracle and funding quirks. Either way you carry basis risk: the perp tracks an index that is not identical to your pool's price, so a perfectly delta-neutral book still has a small residual that widens exactly when you can least afford it. And short gamma is a liquidation hazard — a hedge that must grow on the way down is the same direction as a margin call, so size the perp's collateral for the stressed-vol range from the first section, not the calm one, or a gap move liquidates the hedge and leaves the LP naked at the worst moment.

JIT liquidity: the one-block market maker

Push active LPing to its logical extreme — rebalance every block, hold the position for the duration of a single swap — and you get just-in-time liquidity, the sharpest strategy in the LP design space. A JIT provider watches the mempool for a large incoming swap, and in the same block: mints an extremely tight, extremely deep liquidity position bracketing the current price immediately before the victim swap, lets the swap execute against that wall of liquidity (capturing the lion's share of its fee, since the JIT LL dwarfs the passive LL already in the tick), then burns the position immediately after — all within one block.

The economics are striking. Because mint and burn straddle a single swap with essentially no price evolution between them, the position earns the full fee on a huge notional while carrying almost zero impermanent loss — there was no time for price to diverge. Concentrated liquidity's fee-rate multiplier, taken to the limit: provide, say, thousands of times the passive depth for the exact instant it earns, and vanish before any adverse selection can bite. It is the closest thing on-chain to risk-free market making, and it is why JIT exists.

The fee split is just liquidity share. A $10M swap through a 0.05% pool pays $5,000 in fees, divided pro-rata by in-range LL. If passive LPs hold LpassiveL_{\text{passive}} in the crossed ticks and the JIT bot mints Ljit=100LpassiveL_{\text{jit}} = 100\,L_{\text{passive}} for the block, the JIT position captures

$5,000×LjitLjit+Lpassive=$5,000×100101$4,950,\$5{,}000 \times \frac{L_{\text{jit}}}{L_{\text{jit}} + L_{\text{passive}}} = \$5{,}000 \times \frac{100}{101} \approx \$4{,}950,

leaving the passive LPs who were there all week with $50. The bot did nothing for 99.99% of the block and took 99% of the fee on the one swap that mattered — which is exactly why passive fee income, measured honestly, falls short of the pool's headline revenue.

And like every legible on-chain edge, JIT has been competed down. Once several bots watch the same large pending swaps and simulate the same fee capture, they bid one another up in the builder auction for the right to sit in front of the flow, and the tip paid to the builder converges toward the fee captured — the same enclosure that happened to on-chain liquidations and atomic arb. What is left is the residual: swaps other bots missed, pools with immature builder markets, and the operational quality to hold large idle inventory and never miss a burn. It is a genuine strategy and a real business; it is not a retail one.

The catch, and the reason JIT is a specialist's game rather than free money, is threefold. It needs real inventory. Unlike the atomic arbitrage with flash loans that funds itself and repays within one transaction, a JIT mint-swap-burn spans three separate transactions in the block (the mint, the victim's swap, the burn), so a flash loan — which must be repaid inside a single transaction — cannot fund the LP leg. The JIT bot must hold the underlying capital, often millions in both tokens, idle and ready. It is a bundle in the MEV supply chain. Winning the ordering — mint right before the swap, burn right after — requires submitting a bundle to a block builder through exactly the proposer-builder-separation plumbing dissected in the MEV and mempool article; JIT competes for block space and pays the builder like any other searcher, and the profit after that bid is thin. It is adverse selection for passive LPs. JIT bots skim the largest, most benign flow — the fat uninformed swaps where fees are juicy and toxicity is low — and leave passive positions holding the smaller trades and the toxic, informed flow that generates LVR. A passive LP's realized fee income is lower than the pool's headline fee revenue precisely because JIT captured the best slices. That last point is not just an economic footnote; it is a landmine in the next section.

JIT liquidity within a single block: mint deep liquidity before a large incoming swap, capture the fee, and burn in the same block, shown as a bundle in the proposer-builder-separation supply chain

Backtesting LP from on-chain data, honestly

Every claim above — vol-sized ranges beat static ones, band rebalancing beats periodic, hedged LPing is +EV in regime X — is a hypothesis you must test against on-chain history before committing capital. The data is right there: swap events and liquidity events in the pool contract's logs, or pre-aggregated in the Uniswap subgraph. The trap is that a naive LP backtest is systematically optimistic, and the biases all push the same direction. Three you must handle or your Sharpe is fiction.

Fee attribution. The seductive shortcut is to estimate fee income as (pool volume) × (fee tier) × (your share of pool TVL). This is wrong on two counts. Fees accrue only to liquidity that is in range at the moment of each swap, so the correct denominator is your share of in-range LL, not your share of TVL — and those differ whenever price sits away from your center. And fee growth is path-dependent: it depends on the exact sequence of swaps and where each one left the price relative to your bounds. The only honest method is to replay swaps and difference the feeGrowthInside accumulators across your range boundaries, exactly as the contract does — the O(1) bookkeeping from the concentrated-liquidity piece. Prorating volume by TVL can be off by a large multiple in either direction, and it is off most right when price is wandering near your edges, which is the regime you most need to model correctly.

Price-versus-pool-tick alignment. Two prices exist at every instant and they are not equal: the pool's own tick (where the AMM's marginal price sits after the last swap) and the external/CEX mid (where the asset actually trades). Mark your position at the pool tick and you build the arbitrageur's profit into your own returns, because the pool tick is stale between arbitrage trades; mark at the CEX mid and you must align timestamps across venues with different clocks and block times. Snapshot cadence compounds this: hourly subgraph snapshots silently miss intra-hour range crossings — a position that exited and re-entered your range within the hour looks like it sat in range the whole time, overstating fees and understating LVR. Anything you claim about time-in-range or LVR must come from swap-level, block-timestamped replay, not hourly marks.

In-pool MEV and JIT contaminating the fee estimate. This is the subtlest and the most dangerous, and it is why the JIT section sits right before this one. If you estimate a passive strategy's fee income from the pool's historical realized fee revenue, you are crediting your passive position with fees that JIT bots actually captured — income a real passive LP would never have seen, because the JIT liquidity was minted ahead of it for those exact swaps. Your backtest inherits phantom fees. The same swaps carry the other half of the distortion: a large fraction of pool volume is arbitrage — the toxic, informed flow that is your LVR — so counting all of it as fee-generating "good" volume while ignoring the adverse selection it imposes double-flatters the result. An honest LP backtest must (a) reconstruct in-range liquidity including transient JIT positions to attribute fees correctly, (b) separately account LVR from the realized price path rather than assuming fees are free money, and (c) treat arbitrage and JIT-skimmed swaps as what they are — the cost side of the ledger, not the revenue side. Skip this and your beautiful vol-sized, band-rebalanced, delta-hedged strategy will look profitable on paper and bleed in production, for reasons the paper never showed you.

Every one of these biases pushes the estimated return up, which is the worst possible failure mode — a backtest that flatters:

Bias source Naive shortcut Honest method Effect on estimated P&L
Fee attribution volume × TVL share difference feeGrowthInside on swap replay over- or understates, worst near edges
Price mark pool tick CEX mid, block-timestamp aligned overstates (bakes in arb profit)
Snapshot cadence hourly snapshots swap-level, block-timestamped overstates time-in-range
JIT skim credits phantom fees reconstruct transient JIT LL overstates fee income
Arbitrage flow counts as "good" volume account separately as LVR overstates (ignores adverse selection)

The discipline is the same one that separates a real trading-strategy backtest from a fantasy: model the costs with the same rigor as the revenue, and be most suspicious of the number when it looks best.

What to take away

Run a v3 position the way a desk runs a book, and the five decisions line up:

  1. Width is a vol bet. Size the range as a kk-sigma band from a forward variance forecast — GARCH, not trailing realized — so concentration breathes with the regime instead of sitting at a static default. The map to ticks is one line, because ticks are log-price.
  2. Rebalancing is a requote cost, not a reflex. Act only when expected fee uplift clears gas + swap + crystallized LVR; the correct shape is a no-rebalance band, and the correct frequency is far lower than instinct suggests. Over-rebalancing is LVR plus a self-inflicted tax, and for many small positions the optimal rebalance count is zero.
  3. The delta is the inventory. For an in-range position V/P=x(P)\partial V/\partial P = x(P), the ETH you currently hold; short that on a perp to run a market-making book instead of an accidental directional one. Negative gamma means continuous re-hedging, whose cost is LVR, and funding is the regime-dependent rent — often a tailwind, never a guarantee.
  4. JIT is the one-block limit of active LPing: deep liquidity around a single swap, near-zero IL, real capital required (no flash loan), and a bundle in the MEV supply chain that skims the best flow away from passive LPs.
  5. The backtest lies optimistically unless you difference feeGrowthInside for attribution, use swap-level block-timestamped marks, and treat JIT-skimmed and arbitrage flow as the cost side. Phantom fees plus ignored LVR is how a losing strategy passes review.

The through-line of this series is that an LP position has an exact analytic structure — value, delta, gamma, LVR — and the moment you write those down, LPing stops being "deposit and pray" and becomes a hedgeable, sizeable, testable trade. The math was in concentrated liquidity; the profitability calculus is in impermanent loss and LVR; this piece is how you actually operate the position between those two. None of it removes the risk. It just tells you, to the basis point, what risk you are being paid to hold.

References

  • Adams, H., Zinsmeister, N., Salem, M., Keefer, R., Robinson, D. (2021). Uniswap v3 Core (whitepaper).
  • Milionis, J., Moallemi, C., Roughgarden, T., Zhang, A. L. (2022). Automated Market Making and Loss-Versus-Rebalancing. arXiv:2208.06046.
  • Milionis, J., Moallemi, C., Roughgarden, T. (2023). Complexity–Approximation Trade-offs in Exchange Mechanisms: AMMs vs. LOBs, and related LVR-with-fees work. arXiv:2302.11737.
  • Cartea, Á., Drissi, F., Monga, M. (2023). Predictable Losses of Liquidity Provision in Constant Function Markets and Concentrated Liquidity Markets. arXiv:2309.08431.
  • Fritsch, R., et al. (2023). Analyzing and Benchmarking Just-in-Time (JIT) Liquidity on Uniswap v3.
  • Uniswap subgraph and core libraries: Position.sol, Tick.sol, SqrtPriceMath.sol (fee-growth and amount math for honest replay).
  • Bollerslev, T. (1986). Generalized Autoregressive Conditional Heteroskedasticity — the GARCH(1,1) forecast behind range sizing.
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Gardez une longueur d'avance sur le marché

Abonnez-vous à notre newsletter pour des insights exclusifs sur le trading IA, des analyses de marché et des mises à jour de la plateforme.

Nous respectons votre vie privée. Désabonnement possible à tout moment.