Why Bybit-specific matters for backtesting fidelity
If you have ever shipped a strategy from a multi-exchange backtester to a real account, you already know the moment we are about to describe. The simulator told you the strategy was profitable. The first live week made fifteen percent less. You walked through the trades and discovered that the simulator filled at the mid, ignored fees during a maker rebate window, and treated a partially-filled limit order as an instant fill at the requested price. The strategy was not broken. The fidelity was.
dgbit’s design center is the opposite of multi-exchange. The README is explicit: the framework targets Bybit, and the data fetcher, position model, and built-in strategies are written against the Bybit API specifically. This post is the argument for that choice, in three parts: what fidelity actually means, what the abstraction tax looks like in practice, and what a Bybit-specific backtester gets right that a generalized one cannot.
What “fidelity” actually means
Backtesting fidelity is the gap between simulator behavior and live behavior. It has three sources:
- Data fidelity — whether the OHLCV (or higher-resolution tick) data the simulator uses is the same data the exchange would have emitted to a live consumer at the same wall-clock moment.
- Execution fidelity — whether the simulator’s assumed fill price, fill quantity, and order acceptance rule match what the matching engine would have produced.
- Cost fidelity — whether the fee, slippage, and funding cost the simulator deducts match what would have actually hit your account.
Most strategies that look great in simulation and disappoint live are losing fidelity in at least two of these. And the worst part is that the loss is silent. A multi-exchange backtester that treats every exchange as a uniform abstract market will return optimistic numbers without warning you that the assumptions are loose. You only find out by funding the bot and watching the P&L diverge.
The abstraction tax
A multi-exchange framework has to find the lowest common denominator across every exchange it supports. That means:
- Order types collapse. Bybit offers post-only, reduce-only, and conditional orders; the generic abstraction may give you “limit” and “market” and call it a day. Strategies that depend on a specific order type quietly silently degrade.
- Fee schedules become averages. Real Bybit fees depend on your VIP tier, whether you are maker or taker, and the specific symbol. A generic engine usually models a flat fee that is close to nobody’s actual fee.
- Kline boundaries drift. Bybit publishes klines on its own clock. A generic engine that re-buckets ticks into bars often introduces a one- or two-second offset that quietly changes when signals fire.
- Funding events are abstracted away. Funding rate payments on perpetuals happen on Bybit’s schedule. A generic engine often ignores them entirely or applies a daily approximation.
None of these are bugs in the multi-exchange framework. They are the price of generality. If you want to support twenty exchanges, you cannot afford to encode every quirk of each one.
dgbit pays the opposite price. By committing to Bybit, the data fetcher can return klines on the same boundary the exchange itself uses (the README’s example pulls interval="15" directly), the fee model in BacktestConfig defaults to a Bybit-realistic transaction_fee=0.001, and the strategy interface does not need to abstract away order types that only Bybit exposes.
What Bybit-specific gets right
Look at the README’s first-backtest snippet:
fetcher = BybitDataFetcher()
data = fetcher.get_kline_data("BTCUSDT", interval="15", limit=1000)
config = BacktestConfig(initial_capital=10000.0, transaction_fee=0.001)
backtester = Backtester(config=config)
backtester.strategy = WaveletReversalStrategy(min_signal_threshold=0.75)
result = backtester.run(data)
Three things are happening here that a generic framework would have made harder:
- The fetcher is the exchange. There is no adapter layer between
BybitDataFetcherand the Bybit REST API. The kline data returned is in the same shape the exchange’s own consumers see. There is no normalization step that could silently round timestamps or repackage decimals. - The fee is one number, by design. Bybit’s fee schedule is more complicated than
0.001, but for a first-cut backtest you want one knob you can tune. The simplicity here is honest: the README does not pretend the simulator is matching every fee-tier nuance, so you know you must tunetransaction_feeto match your actual VIP tier before trusting a number. - The strategy interface is the live interface.
WaveletReversalStrategyplugs into theBacktesterthrough the sameBaseStrategycontract the live execution engine uses. When you decide to take the strategy live, you do not rewrite the signal generation; you change the data source.
That last point is the one that quietly compounds. In a generic framework, the backtest typically runs against a “Strategy” class that produces “Signals” against “Bars”, and the live engine consumes a different “OrderRouter” interface. Bridging the two is a project. In dgbit, the same strategy module is consumed by both sides — the README’s custom-strategy example registers a class with strategy_registry, and that one registration makes the strategy visible to backtest and live execution alike.
Where dgbit’s simulator is honest about limits
We want to be precise about what dgbit’s backtester is and is not. The README describes it as an “in-memory simulation with detailed metrics and interactive Plotly reports.” That phrase says two important things:
- In-memory. The simulator runs against the OHLCV array you pass it; it is not a tick-replay engine wired to a recorded order book. Latency assumptions, queue-position effects, and adversarial fills against your limit orders are not modeled.
- Detailed metrics. The result object exposes
total_return,win_rate, andmax_drawdown— the three numbers a sensible go/no-go decision actually needs. No vanity metrics, no Sharpe ratio annualized over twelve hours of data.
If you need order-book replay fidelity, you will need to extend the data fetcher beyond the kline API. The framework does not pretend to ship that out of the box, and we appreciate that it does not pretend. A simulator that promises more than it can deliver is worse than one that is up-front about its scope.
What this means for your strategy
The practical advice is short:
- Trust the bar boundaries. Because the fetcher pulls Bybit klines directly, signal evaluation in the backtest fires at the same wall-clock moment it would fire live. You do not need to add a “one-bar lag” cushion to defend against simulator drift.
- Set the fee to your actual tier. Find your real maker/taker fee in your Bybit account settings and use that for
transaction_fee. The default is reasonable; your actual number is better. - Treat the backtest as a filter, not a forecast. A strategy that fails an honest in-memory backtest is almost certainly going to fail live. A strategy that passes is a candidate for paper trading on Bybit testnet (
BYBIT_TESTNET=true), which is the next gate.
The exchange-specific bet is what makes the first two of those practical. The framework’s restraint about what the simulator models is what makes the third one safe.
Closing
There is a real cost to being Bybit-only: you cannot deploy the same dgbit instance against Binance or Kraken without writing your own data fetcher and execution layer. That is a deliberate trade. The team’s view, reflected throughout the README, is that an honest single-exchange framework is more useful than a vague multi-exchange one. Every strategy you ship has to clear a real exchange’s actual rules anyway. We would rather encode those rules precisely than abstract them away.