Skip to content

fix/clamp the LP band to the venue's granularity cap and keep one-sided ranges on their side - #251

Open
lgrawet wants to merge 5 commits into
hummingbot:mainfrom
lgrawet:fix/lp-range-granularity-clamp
Open

lgrawet wants to merge 5 commits into
hummingbot:mainfrom
lgrawet:fix/lp-range-granularity-clamp

Conversation

@lgrawet

@lgrawet lgrawet commented Sep 22, 2026 •

Copy link
Copy Markdown

Summary

Three defects in the Solana LP band construction, all in solana_dex_lp_expert:

  • The width cap was a follow-up check, not a ceiling. The playbook derived a
    width from volatility and then tried to shrink it to fit the venue's bin/tick
    limit — a step an agent could skip, and did: an Orca open at 20.8% on
    tick_spacing=8 is 237 spacings → SIMULATION_FAILED, no funds moved, slot
    left empty.
  • A flat percentage floor is impossible on fine-grained pools. A Meteora
    bin_step=10 pool caps the half-width at 3.40%, below any sane
    volatility-derived "4% floor", so a percent-first clamp is guaranteed to
    over-reach.
  • The side guardrail contradicted the one-sided ranges the same skill defines.
    It required lower < P < upper unconditionally, while BUY (side=1)
    deliberately places both bounds below P and SELL (side=2) both above.

Changes

  • lp_range_config skill: W_max tables for Meteora (68 bins, one under the 69
    cap) and Orca/Raydium (119 spacings, one under the 120 cap), derived from
    bin_step/tick_spacing; one-line off-table formula; the clamp expressed as
    ln(upper/lower) ≤ ln((1+W_max)/(1−W_max)) and required to preserve the
    band's side.
  • lp_slot_operator loop: the same table-driven clamp; range bounds split into
    RANGE (asymmetric by base_pct, venue-flipping) and one-sided (ε-offset,
    venue-independent, lower=upper·(1−W) / upper=lower·(1+W)); the RANGE
    formulas explicitly restricted to 0<base_pct<100.
  • AGENT.md: side-aware guardrail; width described as W = min(vol, W_max).

Verification

  • Every table entry recomputed from its formula — all 19 values match.
  • Each rounded Orca/Raydium entry re-counted against the cap: max 119.012
    spacings, under 120, so the four-significant-figure rounding cannot push a
    W_max band over.
  • pytest tests/test_lp_skill_loop_agreement.py tests/test_lp_correctness.py
    → 57 passed.

Supersedes

#252, folded in here: both PRs rewrote the same clamp block, and their
one-sided formulas had already drifted apart.

@greptile-apps

greptile-apps Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no blocking issues remain.

Summary

The PR derives range limits from each pool's bin_step or tick_spacing and keeps the agent, loop, and skill aligned.

  • LP bands now sit below, above, or around the price based on their side.
  • Each pool's bin or tick size sets the maximum LP band width.

Reviews (4) · Last reviewed commit: "Scope the LP band's RANGE formulas and g..." · Reviewed by Greptile

@lgrawet

lgrawet commented Sep 22, 2026

Copy link
Copy Markdown
Author

Note for whoever merges: this PR and #252 are two halves of the same fix.
This one makes the width clamp side-preserving, but the GUARDRAIL line just
above still reads "always verify lower < current_price < upper" — it still
demands a bracketing range. #252 is what turns that line into the per-side
check. Landing either alone leaves strategy.md self-contradictory.
Intended order: #251 first, then rebase #252 onto it — they touch the same
clamp paragraph.

@lgrawet lgrawet changed the title Bug/Derive the LP band's width cap from the pool's bin/tick granularity, not a flat percentage fix/Derive the LP band's width cap from the pool's bin/tick granularity, not a flat percentage Oct 1, 2026
…not a flat percentage

The band width was chosen from OHLCV volatility and clamped to a flat [4%, 20%],
with the venue's bin/tick ceiling applied afterwards as a prose step — "shrink W
until it fits" — that a tick can skip, and did. Two Orca opens at the identical
20.8% width landed 118 spacings on a `tick_spacing=16` pool (opened fine) and 237
on a `tick_spacing=8` pool (SIMULATION_FAILED, no funds moved, slot left empty).
On Meteora the floor is the trap: a `bin_step=10` pool caps the half-width at
3.40%, below the 4% floor, so a percent-first clamp is guaranteed to over-reach
the hard 69-bin limit and fail the open.

`W_max` is now derived from the pool's own `bin_step` / `tick_spacing` and applied
as the ceiling — `W = min(clamp(ATR×2, 4%, 20%), W_max)` — so the granularity cap
binds instead of trailing. The fit table (68 bins / 120 spacings) ships in the
`lp_range_config` skill, with the one-line solve for off-table pools, so no log
arithmetic is left to the caller under a tick budget. A "4% floor" is explicitly
void when `W_max < 4%`: a fine-grained pool cannot hold that band, and widening
past `W_max` is what fails the open.

Stated in the skill, in the `lp_slot_operator` strategy that copies it, and in
AGENT.md so the root summary no longer reads percent-first.
…et the clamp keep a one-sided band on its side of P

The table was computed AT 120 spacings, so rounding alone pushed 6 of its 8 entries over the cap (tick_spacing=8 at 4.80% is 120.098 spacings) — the very over-cap open this change exists to prevent. Values are now computed at 119 spacings, one unit under the cap like Meteora's 68-of-69, and the headroom is stated so a reader cannot round the last decimal back onto the cap; the off-table formula takes 119 too.

The clamp step also instructed the caller to keep P bracketed, which contradicts the one-sided BUY/SELL placement the table above defines (both bounds below P, or both above). It now preserves the band's side instead of forcing P inside it, so a valid one-sided range is neither widened across P nor skipped.

Adjacent boundary fixed while there: the 4%-floor note claimed Orca tick_spacing <= 8 falls under 4%, but W_max at 8 is 4.756% — the boundary is <= 4 (2.379%).
The table was recomputed one spacing under the cap (119, not 120), so the
"round the last decimal up and the clamp lands back on the cap" warning no
longer holds — the printed values already carry a spacing of headroom, and
5.943/9.491 are themselves rounded up from the 119-spacing figure. State
what the numbers are (ceilings at 119, rounded to four significant figures)
instead of a rounding rule that the table contradicts.
@lgrawet
lgrawet force-pushed the fix/lp-range-granularity-clamp branch from accc986 to e87a032 Compare November 2, 2026 21:58
The HARD GUARDRAIL in AGENT.md and the GUARDRAIL in lp_slot_operator told
the loop to verify `lower < current_price < upper` on every open. That
contradicts the one-sided ranges defined a few lines above in the same
files: BUY (`base_pct=0`) places both bounds below `P` by design and SELL
places both above, so an operator obeying the guardrail would either
rewrite a valid one-sided slot into a two-sided range or skip the slot.

Both now state the rule the venue orientation actually needs: the band
sits on the side `side` mandates — `side=1` entirely below (`upper < P`),
`side=2` entirely above (`lower > P`), `side=3` bracketing
(`lower < P < upper`). A bound pair on the wrong side is still treated as
the inverted venue formula it is (Meteora `P` applied to an Orca/Raydium
pool, or the reverse) and still fails the open's simulation.
… own bounds

The width clamp still told the operator to keep P bracketed and the inline
RANGE formulas collapse onto P at base_pct 0 or 100, so a BUY or SELL range
still had to be rewritten by hand — the conflict this PR set out to remove.
The asymmetric formulas now apply to side=3 only, side=1 and side=2 get an
explicit venue-independent construction wholly below / above P with an
epsilon offset, and the clamp shrinks the width while preserving the side
rather than always bracketing P.
@lgrawet lgrawet changed the title fix/Derive the LP band's width cap from the pool's bin/tick granularity, not a flat percentage fix/clamp the LP band to the venue's granularity cap and keep one-sided ranges on their side Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant