CLMM
Manage concentrated liquidity and inventory risk in a market with liquidity-dependent price impact.
What is a concentrated liquidity market maker?
A concentrated liquidity market maker (CLMM) is an automated market maker that lets liquidity providers (LPs) choose the price range in which their capital supplies liquidity. In a traditional constant-product AMM, such as Uniswap v2, the reserves follow and liquidity spans the full positive price range.
A CLMM divides the price range into discrete boundaries called ticks. You choose a lower and an upper tick, and your position supplies liquidity across the intervals between them. As traders buy and sell against the pool, its price moves through those intervals.
While the pool price is inside your range, your position earns a share of trading fees proportional to its contribution to the liquidity used by the trade. Outside the range, it becomes inactive and stops earning fees until the price returns or you reposition your liquidity.
When liquidity is active
The price moves. Your position stays put.
- Lower bound
- 99.50
- Pool price
- 100.00
- Upper bound
- 100.50
Inside the range, trades that use your liquidity can earn you fees. Outside it, the position is inactive until price returns or you rebalance.
Illustrative price path · not an evaluation result
The challenge
Design a dynamic liquidity provision strategy that captures trading fees while managing adverse selection, inventory exposure and gas costs.
- Narrow ranges concentrate your capital and can earn a larger fee share while active, but price movements can take them out of range sooner.
- Wide ranges spread your capital over more intervals and can stay active through larger price movements, with a smaller fee share at a given tick.
- Rebalancing moves your range to a new position, but costs gas after the first deployment. The gas charge does not depend on the width you choose.
Adverse selection occurs when informed traders trade against a pool price that lags the external market price. Your strategy must balance fee income against the resulting changes in inventory value and the cost of repositioning. The leaderboard rewards inventory-adjusted performance, as described in Scoring.
A strategy that rebalances
Follow the starter: ±50 ticks, every 100 steps.
- Lower bound
- 99.50
- Pool price
- 100.00
- Upper bound
- 100.50
The agent recenters its range on the pool tick every 100 steps, even if it is still in range. Between decisions, the boundaries stay fixed. Initial deployment is free; each rebalance costs 2 token1.
Illustrative price path · not an evaluation result
Narrow versus wide ranges
One price path. Two ways to position the same capital.
- Lower bound
- 99.90
- Pool price
- 100.00
- Upper bound
- 100.10
- Lower bound
- 99.50
- Pool price
- 100.00
- Upper bound
- 100.50
A narrow position concentrates capital over fewer ticks, but goes out of range sooner. A wider position covers more price movement with less concentration. Both ranges stay fixed here to isolate the effect of width; neither guarantees higher returns.
Illustrative price path · not an evaluation result
Challenge parameters
| Starting capital | $1,000 |
| Numeraire | token1 |
| Risky asset | token0 |
| Price process | Geometric Brownian motion · drift 0 · σ = 0.03 · S₀ = 100 |
| Order flow | Liquidity-kernel arrivals · baseline 300 · arbitrage coefficient 4,000 |
| Fee tier | 0.30% |
| Horizon | 1.0 time unit · 1,000 steps · 100 paths per seed |
| Position range | ±50 ticks · spacing 1.0001 |
| Rebalancing cost | 2 token1 per rebalance; initial deployment is free |
| Engine | CLMM v1 · liquidity-depth price impact |
| Language | Python 3.11+ |
| Code size limit | 64 KiB |
The simulation
Each evaluation seed runs 100 market paths, each with 1,000 steps over a horizon of one time unit, so . Your agent makes a decision at every step. The current challenge uses fixed, public market parameters; random seeds determine the realized prices and order arrivals.
External price process
The external market price is the reference value of token0 in token1. It follows a geometric Brownian motion model:
Here is a standard Brownian motion. The simulator uses an Euler update with independent standard-normal shocks, initial price , drift and volatility . These parameters remain fixed during evaluation.
Pool price and ticks
The pool price is distinct from the external price. It moves on a discrete grid with tick prices
The pool tracks 7,000 ticks. Your actions select lower and upper offsets within ±50 ticks of the current pool tick. Buying token0 pushes the pool price up; selling token0 pushes it down. Differences between and create opportunities for informed traders and adverse selection for LPs.
Order flow
Liquidity takers generate buy and sell orders. Their arrival intensities combine a baseline of noise trading, a directional liquidity term and an arbitrage response to the gap between external and pool prices. The implemented intensities are
The floor is , the baseline is , the liquidity coefficient is , and the arbitrage coefficient is . An underpriced pool attracts extra buys; an overpriced pool attracts extra sells. The opposite side retains its baseline flow.
The directional kernels weight liquidity in the next 10 executable intervals with weights , where is the interval distance, and normalize by . Since in this challenge, depth affects price impact but does not directly increase arrival intensity.
At most one order per side arrives in a step, with probability
Trade size and price impact
Each arriving trade has a fixed gross value of 250 token1. For sells, this value is converted into token0 units using the external price. The pool fee is 0.30%.
Price impact depends on the trade's net input and the average directional one-tick capacity across the next 10 executable intervals. More nearby liquidity absorbs a trade with less price movement. The ratio of net input to this depth determines the expected tick move; stochastic rounding converts it to a whole number of ticks, limited by the grid boundary. Depth has a numerical floor of .
Fees are allocated across the executable intervals used by the trade. A trade can earn fees for active LPs even when stochastic rounding produces no tick movement.
Gas costs
Initial deployment is free. Each subsequent rebalance costs 2 token1, independently of the chosen range. Holding an existing position incurs no rebalance gas charge. Gas is constant in this challenge, and the additional rebalance swap fee is zero.
Background liquidity
Other liquidity providers are represented by a background of 100,000 liquidity units per tick interval. Your position adds liquidity within its chosen range, influencing both your share of fees and the depth available to traders. Total liquidity is tracked interval by interval; the full liquidity array is private to the simulator.
Rules
Submit one Python file defining an Agent. The live leaderboard ranks each participant's best verified submission on at least three fixed private seeds (300 market paths).
After submissions close and live evaluations finish, each participant's best live submission is selected automatically. Final rankings use at least ten separate unseen seeds (1,000 market paths). A selected strategy that fails or is not reproducible is not ranked; no alternate submission is tried. Ties use earlier submission time, then submission ID. Live results remain provisional until final results are published together.
Use NumPy and the Python standard library permitted by the hosted sandbox. No training libraries or external data files are provided. Your agent receives current observations and returns an action on every simulation step.
Hosted runs execute in the platform sandbox. Official evaluation uses organizer-controlled seeds, separate from public practice seeds 42, 314, and 2718. Every seed runs twice with fresh agents to check identical actions and outcomes; only one pass contributes to the score. Invalid actions, exceptions, and timeouts do not receive a score.
Randomized policies are allowed when reproducible. Python's global random generator and NumPy's global generator start at public policy seed 0 before the source is executed. Initialize independent generators explicitly, for example with np.random.default_rng(config.policy_seed). The policy seed is unrelated to private market seeds. Avoid entropy, wall-clock time, and unseeded generators when choosing actions.
Hosted feedback includes aggregate scores and safe error categories. Agent prints and exception details remain private to organizers; use the local evaluator to debug your strategy.
Check the challenge overview for the current submission status and schedule.
Scoring
| Metric | Weight |
|---|---|
| Inventory-adjusted reward | 100% |
The objective is to grow portfolio value while controlling token0 inventory exposure. At each step, the reward is
where is portfolio value in token1 at the external market price, and is the token0 inventory in the LP position after the step.
Sum these rewards over 1,000 steps, then average over all paths and evaluation seeds. Higher is better. There is no terminal inventory penalty.
The inventory-adjusted score is distinct from raw PnL (final wealth minus initial wealth). Fees and gas already enter portfolio value. Raw PnL is reported separately as a percentage of initial capital.
Strategy interface
Define __init__(self, config) and get_action(self, state). Configuration contains public scalar parameters, including inventory_phi = 0.4, inventory_exponent = 2, and policy_seed = 0; it does not expose the simulation or private market seed.
Return a finite NumPy array with shape (num_trajectories, 3): lower tick offset, upper tick offset, and hold flag. Offsets are relative to the current pool tick, rounded and clipped to [−50, 49] and [−49, 50]. The engine corrects invalid ordering. A positive flag holds; a nonpositive flag deploys or rebalances.
Observation arrays have one entry per simulated path:
| Field | Meaning |
|---|---|
sqrt_price | Square root of the pool price; square it to obtain the price |
current_tick | Current absolute pool tick |
midprice | External reference price of token0 in token1 |
time | Elapsed simulation time |
lp_tick_lower, lp_tick_upper | Your position's absolute tick boundaries |
lp_ever_deployed | Whether you have deployed liquidity |
gas_cost | Rebalancing gas cost |
portfolio_value | Portfolio wealth valued at the external price |
active_liquidity | Total liquidity in the current pool interval |
lp_token0_amount | Token0 inventory in your LP position |
lp_alpha | Token0 value fraction of your LP position |
The starter deploys a ±50-tick position at step 0, then rebalances every 100 steps and holds between rebalances. You can change its width, timing and decision logic. Holding cash without deploying is also allowed. Full liquidity and fee arrays are private.