DEV Community

Why I Added a Slippage Circuit-Breaker to My TWAP Execution Engine

If you've ever sent a large order to an exchange, you already know the problem: doing it all at once moves the price against you before you finish. The standard fix is a TWAP (Time-Weighted Average Price) engine - split the order into slices, space them out, let the market absorb each piece. I built one for my own trading setup, and along the way ran into a failure mode that most simple TWAP implementations don't handle: what happens when the market moves while you're slicing?

The Problem with Naive TWAP

A basic TWAP loop looks like this:

for i in range(slices):
    place_order(symbol, side, slice_amount, price)
    sleep(interval)

This works fine in calm markets. But if volatility spikes mid-execution, you keep dutifully sending slices into a market that's no longer the one you planned for. Your "safe" execution strategy quietly turns into buying (or selling) into a move against you - five separate times, unattended.

What I Added

Two things that I think should be standard in any execution engine, but often aren't in the small open-source ones I found:

  • Slippage-abort. The engine records the price at the start of the TWAP run. If the current price deviates beyond a configurable max_slippage_pct before all slices are sent, it halts the remaining slices and reports why - instead of silently continuing.

  • A pluggable risk-check interface. Instead of hardcoding what counts as "too risky," the engine accepts any object implementing a simple should_abort(current_data) -> bool method. Want to abort based on RSI? Order book imbalance? Your own proprietary signal? Plug it in. The engine doesn't need to know or care what your risk logic is - it just asks the question before each slice.

class RiskCalculator:
    def should_abort(self, current_data: dict) -> bool:
        raise NotImplementedError
        # your own logic, engine doesn't care what's inside

class MyRSIGuard(RiskCalculator):
    def should_abort(self, current_data):
        return current_data["rsi"] > 80
  • Minimum-notional guard. A smaller but annoying issue: if your slice size ends up below the exchange's minimum order value, you get repeated rejected orders instead of one clear message up front. The engine checks this before starting, not after failing five times.

What This Is Not

To be direct about it: this is an execution tool, not a trading strategy. It doesn't predict price, doesn't guarantee profit, and doesn't manage your overall portfolio risk. It answers one narrow question well - "how do I get this order filled without hurting myself in the process" - and stays out of everything else.

Where to Get It

The base engine (slicing + queue + reconciliation) is open source:

  • GitHub: [link buraya gelecek]
  • PyPI: pip install [paket-adı] [link buraya gelecek]

The extended version (slippage-abort + pluggable risk interface + documented examples) is available here:

  • Gumroad: [link buraya gelecek]

Exchange-agnostic via ccxt - works with any of the ~100 exchanges it supports, not just Binance.

Feedback, issues, and PRs welcome. If you're running your own bot and have hit the same "one slice too many" problem, I'd like to hear how you solved it.

Comments

No comments yet. Start the discussion.