Upcomingtoken0 / token1 · fee 0.30%

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 xy=kx y = k 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.

Fixed position · ±50 ticksIn range
Fixed position · ±50 ticks: pool price and liquidity rangeAn illustrative pool-price path over 1,000 steps. The shaded band is the liquidity position. Its boundaries stay fixed as price moves inside and outside the range. Current values and status are shown below and above the chart.Pool price · token199.0199.50100.00100.50101.000250500750999step →
Lower bound
99.50
Pool price
100.00
Upper bound
100.50
Step 000 / 999

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.

Periodic position · ±50 ticksIn range
Periodic position · ±50 ticks: pool price and liquidity rangeAn illustrative pool-price path over 1,000 steps. The shaded band is the liquidity position. Its boundaries change every 100 steps; dots mark rebalances. Current values and status are shown below and above the chart.Pool price · token199.0199.50100.00100.50101.000250500750999step →
Lower bound
99.50
Pool price
100.00
Upper bound
100.50
Initial deployment · freeNext: step 100
Step 000 / 999

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.

Narrow · ±10 ticksIn range
Narrow · ±10 ticks: pool price and liquidity rangeAn illustrative pool-price path over 1,000 steps. The shaded band is the liquidity position. Its boundaries stay fixed as price moves inside and outside the range. Current values and status are shown below and above the chart.Pool price · token199.0199.50100.00100.50101.000250500750999step →
Lower bound
99.90
Pool price
100.00
Upper bound
100.10
Wide · ±50 ticksIn range
Wide · ±50 ticks: pool price and liquidity rangeAn illustrative pool-price path over 1,000 steps. The shaded band is the liquidity position. Its boundaries stay fixed as price moves inside and outside the range. Current values and status are shown below and above the chart.Pool price · token199.0199.50100.00100.50101.000250500750999step →
Lower bound
99.50
Pool price
100.00
Upper bound
100.50
Step 000 / 999

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
Numerairetoken1
Risky assettoken0
Price processGeometric Brownian motion · drift 0 · σ = 0.03 · S₀ = 100
Order flowLiquidity-kernel arrivals · baseline 300 · arbitrage coefficient 4,000
Fee tier0.30%
Horizon1.0 time unit · 1,000 steps · 100 paths per seed
Position range±50 ticks · spacing 1.0001
Rebalancing cost2 token1 per rebalance; initial deployment is free
EngineCLMM v1 · liquidity-depth price impact
LanguagePython 3.11+
Code size limit64 KiB

The simulation

Each evaluation seed runs 100 market paths, each with 1,000 steps over a horizon of one time unit, so Δt=0.001\Delta t = 0.001. 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 StS_t is the reference value of token0 in token1. It follows a geometric Brownian motion model:

dSt=μSt dt+σSt dWt.dS_t = \mu S_t\,dt + \sigma S_t\,dW_t.

Here WtW_t is a standard Brownian motion. The simulator uses an Euler update with independent standard-normal shocks, initial price S0=100S_0 = 100, drift μ=0\mu = 0 and volatility σ=0.03\sigma = 0.03. These parameters remain fixed during evaluation.

Pool price and ticks

The pool price PtP_t is distinct from the external price. It moves on a discrete grid with tick prices

P(i)=1.0001i.P^{(i)} = 1.0001^i.

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 StS_t and PtP_t 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

λbuy=max⁡ ⁣(α0,  α1+α2Kbuy+α3max⁡(St−Pt,0)),\lambda_{\mathrm{buy}} = \max\!\left(\alpha_0,\;\alpha_1 + \alpha_2 K_{\mathrm{buy}} + \alpha_3\max(S_t-P_t,0)\right), λsell=max⁡ ⁣(α0,  α1+α2Ksell+α3max⁡(Pt−St,0)).\lambda_{\mathrm{sell}} = \max\!\left(\alpha_0,\;\alpha_1 + \alpha_2 K_{\mathrm{sell}} + \alpha_3\max(P_t-S_t,0)\right).

The floor is α0=10\alpha_0 = 10, the baseline is α1=300\alpha_1 = 300, the liquidity coefficient is α2=0\alpha_2 = 0, and the arbitrage coefficient is α3=4,000\alpha_3 = 4{,}000. An underpriced pool attracts extra buys; an overpriced pool attracts extra sells. The opposite side retains its baseline flow.

The directional kernels KK weight liquidity in the next 10 executable intervals with weights e−0.5de^{-0.5d}, where dd is the interval distance, and normalize by 10610^6. Since α2=0\alpha_2 = 0 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

Pr⁡(arrival)=1−e−λΔt.\Pr(\text{arrival}) = 1 - e^{-\lambda\Delta t}.

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 10−1210^{-12}.

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

MetricWeight
Inventory-adjusted reward100%

The objective is to grow portfolio value while controlling token0 inventory exposure. At each step, the reward is

rt=Vt+1−Vt−0.4 Δt (qt+1(0))2,r_t = V_{t+1} - V_t - 0.4\,\Delta t\,(q_{t+1}^{(0)})^2,

where VtV_t is portfolio value in token1 at the external market price, and qt+1(0)q_{t+1}^{(0)} 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:

FieldMeaning
sqrt_priceSquare root of the pool price; square it to obtain the price
current_tickCurrent absolute pool tick
midpriceExternal reference price of token0 in token1
timeElapsed simulation time
lp_tick_lower, lp_tick_upperYour position's absolute tick boundaries
lp_ever_deployedWhether you have deployed liquidity
gas_costRebalancing gas cost
portfolio_valuePortfolio wealth valued at the external price
active_liquidityTotal liquidity in the current pool interval
lp_token0_amountToken0 inventory in your LP position
lp_alphaToken0 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.