DEV Community

Даниил Шпытко
Даниил Шпытко

Posted on

What Adding Bybit Taught Me About Multi-Exchange Trading Bots

The first MEXC-only version of my trading bot worked. That was exactly why adding a second exchange was dangerous.

When software supports only one exchange, its behavior can look generic even when exchange-specific assumptions are scattered everywhere. A field called status, a quantity returned by an order endpoint, or a successful HTTP response can quietly become part of the business logic. Nothing exposes those assumptions until another exchange behaves differently.

I recently added Bybit to NOVA, the Spot trading automation system I have been building around ten isolated trading profiles and a Telegram Mini App. Bybit is now deployed for nine of those profiles. CFX/USDT remains MEXC-only because that market is not supported on Bybit.

The visible result is a simple exchange selector. The work behind it was mostly about making sure that selector could not lie.

An accepted order is not an executed trade

The first boundary I had to reinforce was the difference between order acknowledgement and execution.

A successful response from a create-order endpoint tells me that the exchange accepted the request. It does not necessarily tell me that the purchase is complete or that the final executed quantity is already known.

That distinction matters because NOVA's normal cycle is sequential:

  1. Submit a Spot purchase.
  2. Confirm what actually executed.
  3. Calculate the quantity available for sale.
  4. Place a limit order at the user's target price.
  5. Track that order until the cycle reaches a terminal state.

If step two is skipped, the next order is built from an assumption rather than exchange state.

The Bybit adapter therefore treats the initial response as an acknowledgement. The lifecycle continues only after a private event or a bounded REST reconciliation produces a normalized, confirmed order state. The trading core does not need to understand Bybit's raw response fields, but it does need a trustworthy answer to a business question: how much was actually filled?

The requested quantity may not be the sellable quantity

This was the most practical difference.

Suppose the purchase request represents one unit of an asset. If the exchange charges the fee in the purchased asset, the balance that becomes available can be slightly below one unit. Trying to place the exit order for the original requested quantity can then fail because that full quantity is no longer available.

The safe path is:

confirmed fill
    -> executed base quantity
    -> subtract fee charged in the base asset
    -> round down using the market quantity step
    -> validate the minimum order requirements
    -> submit the exit order
Enter fullscreen mode Exit fullscreen mode

Rounding down is intentional. Rounding to the nearest step can create a quantity that is larger than the available balance.

When the execution data is incomplete, NOVA stops the transition instead of guessing. A visible failure is easier to recover from than an exit order created from invented numbers.

Market rules belong to the selected exchange

Symbol names are the obvious difference between exchange APIs. Precision rules are the more dangerous one.

Each selected market can define its own:

  • price tick size;
  • quantity step;
  • minimum quantity;
  • minimum order value;
  • supported order behavior;
  • fee representation.

Those values cannot be copied from the MEXC implementation or stored as convenient global defaults. The adapter must load metadata for the actual market on the actual exchange. If valid metadata is unavailable, order creation fails closed.

This also changed how I think about "multi-exchange support." It is not a boolean feature flag. Support is a capability matrix: an exchange may be integrated while a particular market or operation is still unavailable. That is why nine profiles can use Bybit while CFX/USDT remains tied to MEXC.

Changing exchanges is a lifecycle transition, not a dropdown change

The user-facing selector created another problem: what happens if a profile already has an unfinished operation on the old exchange?

Changing a saved value immediately would split ownership of the trading lifecycle. The application could start reading the new exchange while an order on the previous one was still open.

I chose a stricter rule:

def can_change_exchange(profile_state) -> bool:
    return not profile_state.has_unfinished_operation
Enter fullscreen mode Exit fullscreen mode

The real check contains more detail, but the invariant is simple. A profile cannot switch exchanges while its current operation still needs to be tracked or reconciled.

The old exchange remains responsible for the lifecycle it started. Only after that lifecycle reaches a safe terminal state can the profile be reconfigured.

This is one of those cases where a small interface control represents a large state transition. Treating it as ordinary settings persistence would have made the UI convenient and the recovery behavior unreliable.

Saving credentials is not permission to start trading

I also separated exchange connection from strategy activation.

When a user adds Bybit credentials, NOVA validates the account type, Spot trading access, and balance availability. Withdrawal permission is not required. After validation, the profile remains stopped.

Starting the trading cycle is a separate explicit action.

That distinction prevents a settings form from creating a financial side effect. It also makes recovery and support easier: "the exchange is connected" and "the strategy is running" are two different states, and the interface should show them separately.

What stayed neutral

Adding Bybit did not require a second trading core. The core still works with normalized concepts:

  • market identity;
  • order side and type;
  • confirmed execution state;
  • executed base and quote quantities;
  • average execution price;
  • lifecycle ownership;
  • capital limits;
  • recovery transitions.

The adapters own signing, endpoints, raw statuses, streams, market metadata, fee details, and exchange-specific error classification.

That boundary is not perfectly static. A second implementation always reveals places where an interface was shaped around the first one. But those changes should make the contract more semantic, not leak a new set of raw Bybit fields into the core.

I made the same choice for conditional exits. NOVA currently uses a regular limit order above the average entry price as its profit exit and does not automatically close a Spot position at a loss. I did not add a universal stop-loss type merely to make two adapters look symmetrical. If a future strategy genuinely requires conditional orders, they will enter through explicit capability checks because trigger semantics and Spot support differ between exchanges.

Tests are evidence, but not the final evidence

Before deployment, I ran 39 network-isolated scenarios covering the common Bybit lifecycle, fee-aware quantity calculation, market rules, events, recovery, and DCA-related paths. I also checked the public metadata for the supported markets.

All of those checks passed. They prove that the implementation satisfies the contracts I wrote. They do not prove that every real account state and exchange response has already been observed.

At the time of this release, the first complete user-driven Bybit cycle had not yet finished in live operation. I think that distinction is worth stating. "Deployed," "covered by tests," and "repeatedly confirmed with live executions" describe different levels of evidence.

The next practical milestone is not another adapter. It is completing full user cycles on Bybit and observing recovery behavior under real conditions.

The interface is the next boundary

NOVA is currently managed through Telegram bots and a Mini App. That was a useful way to build the first working control plane, but ten profiles and two exchanges are beginning to outgrow a messenger-first interface.

My longer-term direction is to make the website the primary management surface: connect exchanges, configure profiles, inspect open operations, view history, and control execution from a regular browser. Telegram will remain useful for notifications and quick actions rather than carrying the entire product interface.

The backend lesson will stay the same: a clean selector is only honest when lifecycle ownership, capabilities, and recovery rules are explicit underneath it.

If you have built a second exchange adapter, what exposed the biggest hidden assumption in your first implementation: order states, fees, precision rules, streaming, or recovery?

Top comments (0)