Skip to content

fix: Uncapped retry loop on keeper-cancelled orders drains wallet gas #752

@guibvieira

Description

@guibvieira

Problem

On Feb 18 2026, a single ONDO/USDC short position ($15.75) triggered 436 on-chain transactions in 17 hours, draining ~$50 in ETH gas fees. The bot kept retrying the same doomed close order every ~15 seconds with no backoff or retry limit.

What happened

  1. Ichimoku strategy fires senkou_a_4h_exit_short signal for ONDO
  2. Bot submits create_order(market buy, reduceOnly=True) → on-chain tx succeeds (gas consumed)
  3. GMX keeper attempts execution → contract rejects → emits OrderCancelled event
  4. Bot raises InvalidOrder, freqtrade logs "Unable to exit trade", trade stays open
  5. Next cycle (~15s later), same exit signal fires, bot retries the exact same order
  6. Repeat 436 times across 3 ETH top-ups until wallet is empty

The cancellations came in bursts, each draining a top-up within minutes:

Burst Duration On-chain txs ETH burned
1 3 min 11 ~0.001 ETH
2 35 min 107 ~0.010 ETH
3 2h 15min 319 ~0.010 ETH

Why we don't know the cancel reason

Every cancellation logs the same generic message: "Order ordercancelled". The actual GMX error (e.g. OrderNotFulfillableAtAcceptablePrice, MinPositionSize) is encoded in the reasonBytes field of the OrderCancelled event but is not being decoded in the exchange polling path. The decoder exists (decode_error_reason() in events.py) but isn't wired into the CCXT adapter's event processing.

What we'd like to see

  1. Circuit breaker: After N consecutive keeper cancellations for the same trade (e.g. 3-5), stop retrying and either skip the pair for a cooldown period or alert. The bot should never burn gas on the same failing order hundreds of times.

  2. Decode cancel reasons: Wire decode_error_reason() into the CCXT adapter's OrderCancelled event handling so we get actionable errors like OrderNotFulfillableAtAcceptablePrice(price=274100, acceptablePrice=273280) instead of the generic fallback.

  3. Per-failure response: Once we have the actual reason, the bot can respond appropriately — e.g. increase slippage for OrderNotFulfillableAtAcceptablePrice, skip pair for MinPositionSize, etc.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions