Skip to content
Back to skills

2523 Lp Rebalancer Guide 63b97f02

ASecurity

A concentrated liquidity (CLMM) position manager that automatically rebalances positions based on price movement and configurable price limits.

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 11, 2026
documentationgogitdocumentation

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2523-lp_rebalancer_guide_63b97f02 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2523 Lp Rebalancer Guide 63b97f02?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2523 Lp Rebalancer Guide 63b97f02
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2523-lp-rebalancer-guide-63b97f02/badge)](https://www.skillsdirectory.com/skills/tools-only-2523-lp-rebalancer-guide-63b97f02)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# LP Rebalancer Controller Guide

A concentrated liquidity (CLMM) position manager that automatically rebalances positions based on price movement and configurable price limits.

## Overview

LP Rebalancer maintains a single LP position and automatically rebalances it when price moves out of range. It uses a "grid-like" approach with separate BUY and SELL zones, anchoring positions at price limits to maximize fee collection.

### Key Features

- **Automatic rebalancing** when price exits position range
- **Configurable BUY and SELL price zones** (can overlap)
- **"KEEP" logic** to avoid unnecessary rebalancing when already at optimal position
- **Supports initial BOTH, BUY, or SELL sided positions**
- **Retry logic** for transaction failures due to chain congestion

### Use Cases

- **Range-bound trading**: Collect fees while price oscillates within a range
- **Directional LP**: Position for expected price movements (BUY for dips, SELL for pumps)
- **Grid-like strategies**: Automatically reposition at price limits

## Architecture

### Controller-Executor Pattern

```
┌─────────────────────────────────────────────────────────────────┐
│                        Strategy Layer                            │
│  (v2_with_controllers.py - orchestrates multiple controllers)   │
└─────────────────────────────────────────────────────────────────┘
                                │
                    ┌───────────┴───────────┐
                    ▼                       ▼
        ┌─────────────────────┐  ┌─────────────────────┐
        │   LPRebalancer      │  │   Other Controller  │
        │   (Controller)      │  │                     │
        │                     │  │                     │
        │ - Decides WHEN to   │  │                     │
        │   create/stop       │  │                     │
        │   positions         │  │                     │
        │ - Calculates bounds │  │                     │
        │ - KEEP vs REBALANCE │  │                     │
        └─────────┬───────────┘  └─────────────────────┘
                  │
                  │ CreateExecutorAction / StopExecutorAction
                  ▼
        ┌─────────────────────┐
        │     LPExecutor      │
        │     (Executor)      │
        │                     │
        │ - Manages single    │
        │   position lifecycle│
        │ - Opens/closes via  │
        │   gateway           │
        │ - Tracks state      │
        └─────────┬───────────┘
                  │
                  ▼
        ┌─────────────────────┐
        │   Gateway (LP)      │
        │                     │
        │ - Connector to DEX  │
        │ - Meteora, Raydium  │
        │ - Solana chain ops  │
        └─────────────────────┘
```

### Key Components

| Component | Responsibility |
|-----------|---------------|
| **Controller** (`LPRebalancer`) | Strategy logic - when to create/stop positions, price bounds calculation, KEEP vs REBALANCE decisions |
| **Executor** (`LPExecutor`) | Position lifecycle - opens position, monitors state, closes position on stop |
| **Gateway** (`gateway_lp.py`) | DEX interaction - sends transactions, tracks confirmations |
| **Connector** (`meteora/clmm`) | Protocol-specific implementation |

## Configuration

### Full Configuration Reference

```yaml
# Identity
id: lp_rebalancer_1                    # Unique identifier
controller_name: lp_rebalancer         # Must match controller class
controller_type: generic               # Controller category

# Position sizing
total_amount_quote: '50'               # Total value in quote currency
side: 0                                # Initial side: 0=BOTH, 1=BUY, 2=SELL
position_width_pct: '0.5'              # Position width as percentage (0.5 = 0.5%)
position_offset_pct: '0.1'             # Offset to ensure single-sided positions start out-of-range

# Connection
connector_name: meteora/clmm           # LP connector
network: solana-mainnet-beta           # Network
trading_pair: SOL-USDC                 # Trading pair
pool_address: 'HTvjz...'               # Pool address on DEX

# Price limits (like overlapping grids)
sell_price_max: 88                     # Ceiling - don't sell above
sell_price_min: 86                     # Floor - anchor SELL positions here
buy_price_max: 87                      # Ceiling - anchor BUY positions here
buy_price_min: 85                      # Floor - don't buy below

# Timing
rebalance_seconds: 60                  # Seconds out-of-range before rebalancing
rebalance_threshold_pct: '0.1'         # Price must be this % beyond bounds before timer starts

# Optional
strategy_type: 0                       # Connector-specific (Meteora strategy type)
```

### Configuration Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `id` | string | auto | Unique controller identifier |
| `total_amount_quote` | decimal | 50 | Total position value in quote currency |
| `side` | int | 1 | Initial side: 0=BOTH, 1=BUY, 2=SELL |
| `position_width_pct` | decimal | 0.5 | Position width as percentage |
| `position_offset_pct` | decimal | 0.01 | Offset from current price to ensure single-sided positions start out-of-range |
| `sell_price_max` | decimal | null | Upper limit for SELL zone |
| `sell_price_min` | decimal | null | Lower limit for SELL zone (anchor point) |
| `buy_price_max` | decimal | null | Upper limit for BUY zone (anchor point) |
| `buy_price_min` | decimal | null | Lower limit for BUY zone |
| `rebalance_seconds` | int | 60 | Seconds out-of-range before rebalancing |
| `rebalance_threshold_pct` | decimal | 0.1 | Price must be this % beyond position bounds before rebalance timer starts |

### Price Limits Visualization

```
Price:    84        85        86        87        88        89
          |---------|---------|---------|---------|---------|
                    ^         ^         ^         ^
               buy_min   sell_min   buy_max   sell_max
                    |         |         |         |
                    +---------+---------+         |
                       BUY ZONE [85-87]           |
                              +---------+---------+
                                SELL ZONE [86-88]
                              +---------+
                              OVERLAP [86-87]
```

## How It Works

### Side and Amount Calculation

Based on `side` and `total_amount_quote`:

| Side | Name | base_amount | quote_amount | Description |
|------|------|-------------|--------------|-------------|
| 0 | BOTH | `(total/2) / price` | `total/2` | Double-sided, 50/50 split |
| 1 | BUY | `0` | `total` | Quote-only, positioned below price |
| 2 | SELL | `total / price` | `0` | Base-only, positioned above price |

### Bounds Calculation

**Side=0 (BOTH)** - Initial only, centered on current price:
```
half_width = position_width_pct / 2
lower = current_price * (1 - half_width)
upper = current_price * (1 + half_width)
```

**Side=1 (BUY)** - Anchored at buy_price_max:
```
upper = min(current_price, buy_price_max)
lower = upper * (1 - position_width_pct)
```

**Side=2 (SELL)** - Anchored at sell_price_min:
```
lower = max(current_price, sell_price_min)
upper = lower * (1 + position_width_pct)
```

### Rebalancing Decision Flow

```
                    +---------------------------------------+
                    |    INITIAL (total_amount_quote=50)    |
                    |    side from config: 0, 1, or 2       |
                    +-------------------+-------------------+
                                        |
                                        v
              +-----------------------------------------------------+
              |                    ACTIVE POSITION                   |
              |    Stores [lower_price, upper_price] in custom_info  |
              +-------------------------+---------------------------+
                                        |
                        +---------------+---------------+
                        |                               |
         current < lower_price              current > upper_price
              (side=2 SELL)                     (side=1 BUY)
                        |                               |
                        v                               v
         +-------------------------+     +-------------------------+
         | lower == sell_price_min?|     | upper == buy_price_max? |
         +------+----------+-------+     +------+----------+-------+
                |          |                    |          |
           YES  |          | NO            YES  |          | NO
                v          v                    v          v
         +----------+ +------------+    +----------+ +------------+
         |   KEEP   | | REBALANCE  |    |   KEEP   | | REBALANCE  |
         | POSITION | | SELL at    |    | POSITION | | BUY at     |
         |          | | sell_min   |    |          | | buy_max    |
         +----------+ +------------+    +----------+ +------------+
```

### Rebalance vs Keep Summary

| Price Exit | At Limit? | Action |
|------------|-----------|--------|
| Above (BUY) | upper < buy_price_max | REBALANCE to buy_max |
| Above (BUY) | upper == buy_price_max | **KEEP** |
| Below (SELL) | lower > sell_price_min | REBALANCE to sell_min |
| Below (SELL) | lower == sell_price_min | **KEEP** |

## Scenarios

### Initial Positions

#### side=0 (BOTH) at price=86.5

```
Amounts: base=0.289 SOL, quote=25 USDC
Bounds:  lower=86.28, upper=86.72

Position:                     [===*===]
                            86.28    86.72
                                  ^
                              price=86.5 (IN_RANGE, centered)
```

#### side=1 (BUY) at price=86.5

```
Amounts: base=0, quote=50 USDC
Bounds:  upper=86.5, lower=86.07

Position:                 [========*]
                        86.07    86.50
                                  ^
                              price=86.5 (IN_RANGE at upper edge)
```

#### side=2 (SELL) at price=86.5

```
Amounts: base=0.578 SOL, quote=0 USDC
Bounds:  lower=86.5, upper=86.93

Position:                 [*========]
                        86.50    86.93
                          ^
                      price=86.5 (IN_RANGE at lower edge)
```

### Scenario A: Price Moves UP (starting from BUY)

**A1: Price 86.5 -> 87.5 (OUT_OF_RANGE above)**

```
Position:                 [========]            *
                        86.07    86.50      price=87.5
```

Decision: upper (86.50) < buy_price_max (87) -> **REBALANCE** to BUY anchored at buy_price_max

**A2: Price 87.5 -> 88.5 (still OUT_OF_RANGE above)**

```
Position:                     [========]                *
                            86.57    87.00          price=88.5
```

Decision: upper (87.00) == buy_price_max (87) -> **KEEP POSITION** - already anchored optimally

**A3: Price 88.5 -> 86.8 (back IN_RANGE)**

```
Position:                     [===*====]
                            86.57    87.00
                                ^
                            price=86.8 (IN_RANGE)
```

Price dropped back into range. Buying base. **This is why we KEEP** - positioned to catch the dip.

### Scenario B: Price Moves DOWN

**B1: Price 86.5 -> 85.5 (OUT_OF_RANGE below)**

```
Position:        *        [========]
             price=85.5 86.07    86.50
```

Decision: lower (86.07) > sell_price_min (86) -> **REBALANCE** to SELL anchored at sell_price_min

**B2: Price 85.5 -> 84.0 (still OUT_OF_RANGE below)**

```
Position:         *                   [========]
              price=84.0            86.00    86.43
```

Decision: lower (86.00) == sell_price_min (86) -> **KEEP POSITION** - already anchored optimally

**B3: Price 84.0 -> 86.2 (back IN_RANGE)**

```
Position:                 [*=======]
                        86.00    86.43
                          ^
                      price=86.2 (IN_RANGE)
```

Price rose back into range. Selling base. **This is why we KEEP** - positioned to catch the pump.

### All Scenarios Summary

| Starting Position | Price Movement | Result |
|-------------------|----------------|--------|
| BUY at current | up, not at limit | REBALANCE to buy_max |
| BUY at buy_max | up | KEEP |
| BUY at current | down | REBALANCE to SELL at sell_min |
| SELL at current | down, not at limit | REBALANCE to sell_min |
| SELL at sell_min | down | KEEP |
| SELL at current | up | REBALANCE to BUY at buy_max |
| BOTH at current | up | REBALANCE to BUY at buy_max |
| BOTH at current | down | REBALANCE to SELL at sell_min |
| Any | oscillate in range | No action, accumulate fees |

## Edge Cases

### Optional Price Limits (None)

If limits are not set, behavior changes:

| Limit | If None | Effect |
|-------|---------|--------|
| buy_price_max | No ceiling | BUY always uses current_price as upper |
| buy_price_min | No floor | Lower bound not clamped |
| sell_price_min | No floor | SELL always uses current_price as lower |
| sell_price_max | No ceiling | Upper bound not clamped |

**No limits = always follow price** (no anchoring, always rebalance).

### Gap Zone (sell_price_min > buy_price_max)

If there's no overlap between zones (e.g., buy_max=86, sell_min=88), positions in the gap [86, 88] work correctly:

- **BUY at price=87**: position [85.57, 86.00] below price, waiting for dips
- **SELL at price=87**: position [88.00, 88.44] above price, waiting for pumps

This is valid - positions don't need to contain current price.

## Retry Behavior and Error Handling

When a transaction fails (e.g., due to chain congestion), the executor automatically retries up to 10 times.

**When max_retries is reached:**
1. Executor stays in current state (`OPENING` or `CLOSING`) - does NOT shut down
2. Sets `max_retries_reached = True` in custom_info
3. Sends notification to user via hummingbot app
4. Stops retrying until user intervenes

**User intervention required:**
- For `OPENING` failures: Position was not created - user can stop the controller
- For `CLOSING` failures: Position exists on-chain - user may need to close manually via DEX UI

## Troubleshooting

### Common Issues

| Issue | Cause | Solution |
|-------|-------|----------|
| "Invalid bounds" | Calculated lower >= upper | Check price limits configuration |
| Position not created | Price outside valid range for side | Adjust price limits or wait for price |
| Repeated rebalancing | Price oscillating at limit | Increase `rebalance_seconds` |
| Transaction timeout | Chain congestion | Retry logic handles this automatically |
| "LP OPEN/CLOSE FAILED" notification | Max retries reached | Manual intervention required |

### Max Retries Reached - Intervention Required

**For OPENING failures:**
1. Position was NOT created on-chain
2. Stop the controller via hummingbot
3. Check chain status / RPC health
4. Restart when ready

**For CLOSING failures:**
1. Position EXISTS on-chain but couldn't be closed
2. Check the position address on Solscan/Explorer
3. Close manually via DEX UI (e.g., Meteora app)
4. Stop the controller after manual close

## Source

Full documentation: https://github.com/hummingbot/hummingbot/blob/development/controllers/generic/lp_rebalancer/README.md

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.