Repository navigation
Releases: CryptoGnome/dlmmbot
Releases · CryptoGnome/dlmmbot
Release list
v0.26.2 — re-entry sizing goes under measurement
- Telemetry only — nothing about how the bot trades changes. Every same-token re-entry (2nd/3rd within 24h) now logs a
reentry_ladder_deferreddecision recording the 0.75×-ladder size actually used next to what full size would have been. - Why: on the live book, 2nd entries are the best cohort — 59 closes, 66% win rate, +0.79 SOL, against 51% / +0.72 across 117 first entries — so the ladder shrinks exactly the strongest trades. The 3rd-entry cutoff itself measures correct (3rd entries: 40% win rate, net negative) and is untouched.
- Rows are retention-exempt, so the experiment accumulates instead of being pruned. A few weeks of live data decides whether the shrink should start at the 3rd entry instead of the 2nd — same instrumented-off process that promoted the give-back stop.
- No new config keys, no operator action needed.
v0.26.1 — the daily-loss circuit breaker can now be switched off
circuit_daily_loss_pct = 0now disables the daily-loss circuit breaker (new entries no longer pause on a 24h realized-loss threshold). Previously 0 meant a zero threshold — any loss paused entries forever.- Shipped default is unchanged (
3). Disabling is a per-operator choice indata/config.toml, hot-reloaded, no restart needed. - The cluster brake (lossy hard-exit bursts) and HALT are unaffected.
v0.26.0 — give-back stop goes live
- New exit rule, on by default (meme sleeves only): the give-back stop. Once a position has been up past min(0.02 SOL, 5% of entry) counting banked fees, it closes the moment PnL falls back to 75% of that peak — leave before the round-trip completes. The token is benched 15 min like an escape; exits show as "Give-back stop" in the dashboard feed. Knobs:
give_back_enabled,give_back_keep_frac(hot-reloaded). Promoted after a month of watch-only telemetry: replay +2.657 SOL over 120 closes, and the 20 live triggers since 08-22 net +0.857 SOL — including both BANDOS stops of 08-28. stop_loss_count_claimed_feesdecided: stays off. Its pre-registered live-telemetry week found zero wick-rescues; the stops it would have deferred kept falling (median trough ~-70%). Verdict recorded in STRATEGY.md.- Live closes now log a same-instant Jupiter quote next to the exit swap (
exitSwapin the close event), so swap cost is measurable separately from market drift. Observation only — never delays or blocks a close. - New
npm run sim:skips: backfills the price path after each scanner rejection so gates can be judged on what they actually blocked; backfilled rows are exempt from DB pruning. - No operator action needed: missing config keys back-fill from the template on deploy.
v0.25.0 — the bot now refuses pools it cannot build a proper range on
Nothing about how the bot trades changes today. Both changes are verified no-ops on the current book.
- New entry gate. Some pools use very fine price steps, so a 40%-deep range would need 256 or 512 bins when the bot can only place 138. It used to quietly enter with a third of the range it meant to — that is how positions ended up 11–15% deep against a floor of 40, with nothing in the logs saying so. Now it skips the pool, logged as
range_too_shallow. Checked against history: 0 of 142 meme positions would have been blocked. - Majors untouched. The fine-step pools are ANSEM / PUMP / MET / ORE. They trade those legitimately and plan their ranges through a separate path, so the sleeve is unaffected — a gate applied there would have blocked 29 of 34 of its positions.
- No new setting to manage. The gate reads
min_down_pctandmax_position_accounts, so it always matches the depth floor you configured. stop_loss_sustain_polls_in_rangeadded and proven to do nothing. The stop had no wick tolerance while in range. Tested 1→4 over 138 replayed positions: no effect at any value, because every stop on the book fires below range, where the existing 4-poll wait already applies. Kept as instrumentation; default1is exactly today's behaviour.
Caveat: the measurement behind the gate is driven by the majors cohort, not the meme sleeve. It is a guard against silently entering a partial range, not a proven profit improvement.
v0.24.0 — the escape hatch's width bug is now measured in production, and the build pill tells the truth
Nothing about how the bot trades changes in this release. The escape hatch still fires on exactly the rule it always has.
- The escape hatch has a real width bug, and it is now being measured. It arms on a fraction of range depth, so the trigger price moves with how wide the range is — −26.4% on a 40%-deep range, −19.3% on a 30%-deep one, −8.8% on a shallow one. The fix (absolute drawdown from entry) is built but switched off: replayed against the book it changes 14 of the 96 positions the current rule reproduces faithfully — four carried by a single row, and ten are real escapes where no recorded data can say what holding would have done. The bot now logs an
escape_absolute_deferreddecision whenever the two rules disagree, so a week of live data decides instead of a backtest. - New config keys, all inert by default — back-filled into your existing
data/config.tomlautomatically, no action needed:escape_hatch_absolute = false,escape_hatch_drawdown_pct = 26.4,escape_hatch_recovery_drawdown_pct = 12.0. - The dashboard build pill and Changes tab were showing the wrong release. The deploy watcher fetched branch refs but never tags, so
git describestayed frozen at clone time — staging reportedv0.22.0whilemainwas two releases ahead. Fixed. Action: existing checkouts need onegit fetch --tagsto catch up; the watcher keeps them current after that. npm run simcan overlayescape_hatch_absoluteto replay either form of the hatch.- New:
RANGE-WIDTH-DECISION.md— a pre-registered rule for testing a narrower range, written before the data exists. The widely-shared −75% argument does not survive this book: 0 of 176 closed positions ever traded that deep, and 12 of the 13 that passed −50% had already been closed by P0/P1.
v0.23.2 — no transaction depends on the RPC websocket any more
- Finishes what v0.23.1 started.
sendAndConfirmTransactionconfirms through anonSignaturewebsocket subscription, so it was only as available as the ws endpoint. With Helius answering that endpoint429once a second, every confirm was guaranteed to fail while the transactions landed perfectly over HTTP — that is how a rent reclaim reportedhas expired: block height exceededfor a tx that had already succeeded. send()now broadcasts withsendTransactionand resolves every outcome throughsignatureFate(), which pollsgetSignatureStatusesand distinguishesexpiredfromunknownviaisBlockhashValid. That distinction is what the no-double-execute rule depends on, and a bare confirm cannot make it.- Behaviour preserved, checked against the web3.js source:
sendTransactionalways overwritesrecentBlockhash/lastValidBlockHeight, signs, and forces a new blockhash when the derived signature repeats — so retries still produce a different signature andtx.recentBlockhashis exactly what was signed.skipPreflight: falseis kept, so program failures still abort rather than retry. Nothing is resent before the previous attempt's fate is known. - Verified on live money before release: a real rent reclaim confirmed on the server (
+0.00207 SOL) with zerotx attempt … failedlines since deploy. The identical operation two hours earlier loggedhas expired: block height exceededand had to be recovered. ws error: 429may still appear in stderr; nothing on the critical path reads that transport any more.
v0.23.1 — swap confirms no longer depend on the RPC websocket
- The 429 storm was the cause, not background noise. web3.js implements
confirmTransactionwith anonSignaturewebsocket subscription, so a confirm is only as available as the RPC's ws endpoint. Helius has been answering ours with429once a second for hours, making every swap confirm guaranteed to time out at 30s while the transactions landed fine over HTTP. That is where pos#104'snot confirmed in 30.00 secondscame from — v0.23.0 made the fallout recoverable, this removes the cause. swapToSol/swapFromSolnow confirm by pollinggetSignatureStatuseson the same HTTP endpoint everything else already uses. Both still attach the signature on timeout, so v0.23.0's recovery keeps working if a confirm fails for another reason.- Effect: closes stop taking an extra 30 seconds per swap and stop emitting spurious "swap failed" errors while the ws is refused.
- The executor's own
send()is unchanged — it already resolves timed-out attempts by polling viasignatureFate(). Converting it away from the initial ws stall is the next step. - The ws 429 itself remains a Helius quota matter; the bot simply no longer depends on that transport for swaps.
v0.23.0 — exit swaps that timed out no longer book as total losses
- A close whose Jupiter swap timed out was booking the whole token side as a loss. On a below-range exit the swap is the exit value — the remove-liquidity tx returns little more than rent. When the confirm timed out, the slippage ladder correctly detected the swap had landed, logged
landed after all (wallet now 0)— and then returnednull, the caller's "no swap happened" value, so the signature never reached the close's wealth delta. - What it cost: Railway pos#104 PUMP booked −0.719 SOL / −88.8% on a position that came back roughly flat (0.070 SOL from the remove + 0.695 SOL from a swap 44 seconds later, against a 0.750 deposit). The phantom loss tripped the daily circuit breaker one second later and the bot stopped entering.
- Fix:
swapToSolattaches the signature to the confirm error instead of losing it with the throw, and the ladder hands it back whenever the wallet proves the tier landed — between tiers and after the last tier, which had no re-read and lost the same money on the final rung. It never invents one: no signature recovered still returnsnull, and now says so in the log. - New:
npm run repair-close -- <id…> [--apply](dry-run by default) for rows the bug already wrote. Reads the chain, not the ledger: a transaction counts only if the wallet sold that position's token for SOL near the close and the close did not already book it. Creditsrecovered_sol, leavingclose_return_solas the honest record of what the close could measure. Records a per-signature receipt, so re-running is a no-op. - New:
REPAIR_CLOSESenv var — Railway has no shell; set it to a comma-separated list of ids and redeploy to repair before the bot opens the DB. Safe to leave set, and it never blocks startup. - Seven rows were affected (Railway pos#104/#122/#123/#124, server pos#186/#187/#188), all inside a window of RPC 429s that made confirms expire. Nothing would have restated them —
stranded_soland the residual sweep only recover tokens left in the wallet, and these were sold. Server rows already repaired: +0.0539 SOL restored, all three flipping from negative to positive.
v0.22.0 — Helius spend cut ~6x: marks read positions by key, polls every 20s
- RPC credits per open position: 2.59M/month → 0.39M. Marking was essentially the whole Helius bill. Each mark ran a
getProgramAccounts— 10 credits on Helius, vs 1 for everything else we call — asking the program for every position the wallet holds in the pool, then threw away all but the accounts already recorded inposition_accounts. Marks now read those accounts by key, and take the active bin from thelbPairthatrefetchStates()just refreshed instead of payinggetActiveBin()for a value already in hand. Measured on mainnet against SDK 1.9.14: 15 credits → 3 per mark. manage.poll_sdefault 15 → 20, taking the remainder from 0.52M to 0.39M per position/month. A three-to-four position book was exhausting the 10M/month Developer plan; it now runs at roughly a fifth of it.- Action required to get the poll change: existing installs read their own
data/config.toml, not the repo template. Setpoll_s = 20in dashboard Settings (hot-reloaded, no restart) on each bot. The mark change needs nothing — it ships with the deploy. - Poll-counted windows are a third longer now, deliberately:
stop_loss_sustain_polls60s → 80s, memerotation_polls45s → 60s, majorsrotation_polls30min → 40min,follow.cold_polls_end45s → 60s. The counts were left unrescaled on purpose — changing them would change how many independent samples back each decision, which is what a "sustain" knob is for. Watch the P1 stop confirming 20s later.interval_sstays an exact multiple (60 = 3 × 20), so scans still snap to a tick. - Safety moved the right way.
trackedButMissingOnChainexisted because a lagging node answersgetProgramAccountswith[]and no error, which becamevalueSol 0→pool_dead→ a fabricated-open_cost_solon a position still sitting on chain. A keyed read cannot fail that way silently, and a miss on any tracked account now fails the mark — where the old all-or-nothing guard would still have marked a two-account position at half value if one account went blind. - Verified, not just reasoned: a live mainnet position returns identical
positionData(amounts,feeX/feeY, bin bounds, version) through the new path, and a bit-identical active-bin price. Typecheck clean, 418 tests green.
v0.21.0 — Parallel position marking + a working RPC fallback
- Manage ticks are no longer serial. Every open position used to be marked one at a time, so a tick cost the sum of their RPC latencies (4–5 round trips each, including a
getProgramAccounts). Marks now run concurrently across pools — never twice within the same pool, so tranche pairs and re-entries are unaffected. Same API calls, same rate-limit budget; they just stop queueing behind each other. - New knob:
manage.mark_concurrency(default4), hot-reloaded like the rest of[manage]. Set it to1to restore the old strictly-serial behaviour if your RPC provider starts throwing 429s. Optional — an install whoseconfig.tomlpredates the key keeps working. RPC_URL_FALLBACKfinally fails over. It has been offered in the dashboard as "used if the primary RPC fails", but nothing read it: every call site built its own connection on the primary, so an outage took the bot down with a backup node sitting idle. Failover is now per-request with a 20s per-attempt timeout, and a recovered primary is used again immediately.- Errors keep their cause chain, so a boot-reconcile
fetch failednow names the endpoint that failed instead of dead-ending.
Watch the first few ticks after deploy: if 429 backoff ladders get worse in the logs, drop mark_concurrency to 2.