LP Position Rebalancing System Development
LP provider on Uniswap v3 with position in $1800-$2200 range for ETH/USDC pair. ETH goes to $2500. Position fully converts to USDC, stops earning fees. Without rebalancing it stays a dead weight — no fees accruing, IL locked. Manual monitoring and position recreation at the right moment is a task worth automating once you have more than one position.
When and How to Rebalance
Rebalancing Triggers
Three main approaches to determining rebalancing moment:
Price-based trigger. Rebalance when price reaches range boundary or enters zone N% from boundary. Simple, but only responds to price event. Works for most cases.
Time-based trigger. Rebalance every N hours regardless of price. Creates predictable gas expenses, but often does unnecessary work.
Fee-accumulation trigger. Rebalance when accumulated uncollected fees exceed gas cost of rebalancing multiplied by K (e.g., K=5). This is economically optimal approach but requires tracking tokensOwed from Uniswap v3 Position Manager.
In practice, use combination: price-based as main trigger, fee-accumulation as check before execution.
Strategies for Choosing New Range
After price exits range, need to choose new one. Options:
Fixed width centered on current price. Simplest: new range ±N% from current price. Width N depends on asset volatility: for ETH/USDC optimal is 10-20%, for stablecoin pairs — 0.1-0.5%.
Volatility-adjusted width. Range width = k * ATR(period), where ATR is Average True Range over period. High volatility — wider range (fewer rebalances, higher IL risk), low volatility — narrower range (more rebalances, higher fees). ATR calculated off-chain from historical prices.
Asymmetric range. In trending market, range shifts with trend: [currentPrice * 0.95, currentPrice * 1.15] in uptrend. Requires trend determination — e.g., via deviation from moving average.
Technical Rebalancing Challenges
Gas Optimization in Remove + Add Liquidity
One rebalancing operation in Uniswap v3 consists of:
-
collect()— collect accumulated fees -
decreaseLiquidity()— withdraw liquidity -
collect()again — get remaining tokens - Swap to restore needed ratio
-
mint()— create new position
Total 4-5 transactions if executed separately. Via multicall in NonfungiblePositionManager, multiple calls can be packed in one transaction. This reduces gas by ~40% and removes state change risk between steps.
Swap to restore ratio is separate problem. After withdrawing liquidity you might have 80% in token A and 20% in token B, but new range needs 50/50. Swap changes ratio but moves price in pool (slippage) and gives MEV bots sandwich opportunity.
Protection: minAmountOut in swap with acceptable slippage 0.3-0.5%. Or use flash loans for atomic rebalancing without intermediate swap.
Minimizing Impermanent Loss During Rebalancing
Each rebalancing locks in current IL. If price returns to initial level — without rebalancing IL disappears, with rebalancing it's locked. Too frequent rebalances in sideways market = accumulated IL without sufficient fee income.
Backtest strategy on historical data before deployment — mandatory. For ETH/USDC on Arbitrum, get data from Uniswap v3 subgraph for 6-12 months, simulate different rebalancing strategies, compare net APY after deducting gas and IL.
Keeper Infrastructure
Automatic triggers implemented via:
Chainlink Automation. Contract implements AutomationCompatibleInterface with checkUpkeep function (off-chain calculation, whether rebalancing needed) and performUpkeep (on-chain execution). Payment in LINK. Reliable, but has several-block delay.
Gelato Network. Similar, but with more flexible condition logic and payment in native token. Supports Web3 functions as triggers (reading from contract).
Custom keeper bot. Node.js/TypeScript service with cron or event-driven logic. Cheaper operationally, but requires own infrastructure and uptime monitoring.
For production — Chainlink Automation or Gelato for reliability. Custom bot as fallback and for additional logic (profitability assessment accounting for gas price).
Rebalancer Contract
interface IRebalancer {
function rebalance(
uint256 tokenId,
int24 newLowerTick,
int24 newUpperTick,
uint256 minAmount0,
uint256 minAmount1
) external returns (uint256 newTokenId);
function shouldRebalance(uint256 tokenId)
external view returns (bool, int24, int24);
}
shouldRebalance — view function for keeper checkUpkeep. Checks current price relative to position range and returns new ticks if rebalancing needed.
Access to rebalance function — only position owner or authorized keeper. No public position-changing functions.
Timeline Estimates
Basic rebalancing system for one position with Chainlink Automation — 5 working days. Includes: rebalancer contract, off-chain range selection strategy, keeper setup and mainnet fork tests.







