# Arbitrage bot development: what the build really involves

URL: https://fincodelabs.com/en/blog/arbitrage-bot-development · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Trading bots · Published: 2026-11-05 · Updated: 2026-11-05

Arbitrage bot development as a build: pre-funded balances, leg risk, partial fills, fee and transfer data, clock sync, rate limits, failed-leg reconciliation.

**Short answer:** **Arbitrage bot development** is mostly about what happens when two independent exchanges do not behave as one market. Because no atomic cross-venue execution exists, the build needs pre-funded balances on each venue, leg-risk handling, a fee and transfer model, clock sync, a rate-limit budget per venue and reconciliation after a failed leg, while the price comparison is the small part. An arbitrage bot can lose money, and this guide makes no claim about how often usable gaps exist.

Comparing two prices takes a few lines of code. Most of the work in an arbitrage build goes into what happens when two exchanges, each with its own clock, limits and failure modes, disagree with your records in the middle of a trade.

This guide is for leads scoping such a build. The concept, types and cost stack are in [crypto arbitrage bot](https://fincodelabs.com/en/blog/crypto-arbitrage-bot). It gives no opportunity figures, recommends no venue or rule, and excludes flash-loan, MEV and sandwich variants, which raise different risks, including harm to other users. API details come from official Binance, Bybit and Kraken documentation read on 4 November 2026, listed alphabetically and with no recommendation. Disclosure: Fincode sells this kind of work, so this guide is not neutral on that point. Fincode builds from rules the customer wrote and does not supply strategies.

## What does arbitrage bot development involve?

Arbitrage bot development involves building an execution system across independent venues, in which the price comparison is one module among several.

**Modules of an arbitrage build**

| Module | Job | What goes wrong without it |
| --- | --- | --- |
| Venue connectors | Normalize symbols, filters, errors and order states | A rejection is read as a fill, or the reverse |
| Edge calculator | Apply the customer's threshold, net of current fees, at the intended size | Trades that only looked positive before costs |
| Inventory manager | Track balances and limits per venue | A venue runs out of one asset mid-trade |
| Leg coordinator | Send both legs and track each to a final state | Unmatched exposure goes unnoticed |
| Rate-limit budget | Allocate requests per venue | Requests are refused when a cancel is most needed |
| Reconciler | Compare local records with each venue | The bot trades on a position it does not have |

[CCXT](https://fincodelabs.com/en/blog/ccxt-python-trading-bot) shortens the first module, but it unifies method names, not the differences covered below, and each venue's book must be synchronized as described in [maintaining a local order book](https://fincodelabs.com/en/blog/websocket-order-book).

## Pre-funded balances and no atomic execution

Both legs must trade from balances already on each venue, and nothing makes the two orders succeed or fail together.

Transfers wait for network confirmations, so they cannot be part of a live trade. That leaves capital split across venues and exposed to each platform, with rebalancing as a separately controlled step: the bot's keys carry read and trade permission only.

Each venue also decides alone. Bybit's order documentation says an acknowledgement only means the request was accepted and that the result arrives asynchronously. Binance says that after an HTTP 5XX response the execution status is unknown and the order may have succeeded. Immediate-or-cancel and fill-or-kill conditions, documented by all three venues, limit what one order leaves behind, but they do not tie one venue's order to another's. The result is leg risk: one side fills, the other does not, and you hold a position you did not intend.

The specification therefore needs a written leg policy: how long to wait, whether to retry or close, what price is acceptable for closing and when to stop trading. Those are the customer's decisions; the developer makes them executable and testable.

## The fee and transfer model

The edge calculation needs current fee and transfer data per venue, read from the API rather than hard-coded.

**Where the data comes from (official documentation, read 4 November 2026)**

| Data | Binance | Bybit | Kraken |
| --- | --- | --- | --- |
| Your trading fee | `GET /api/v3/account/commission` | `GET /v5/account/fee-rate` | `POST /0/private/TradeVolume` with `pair` |
| Deposit and withdrawal status | `depositEnable`, `withdrawEnable` per network in `GET /sapi/v1/capital/config/getall` | `chainDeposit`, `chainWithdraw` in `GET /v5/asset/coin/query-info` | `status` in the public `GET /0/public/Assets` |
| Withdrawal fee | `withdrawFee`, same endpoint | `withdrawFee`, `withdrawPercentageFee`, same endpoint | `WithdrawInfo`: deprecated, needs the Withdraw permission |

A fee model must never be the reason a trading key gains withdrawal rights: Kraken's `WithdrawInfo` requires the Withdraw permission, so read such values outside the trading process. A suspended network is a stop condition, because inventory cannot be moved, and what pauses is set by a rule the customer has written. Model a transfer as both a cost and a delay.

## Latency budget and clock sync

Set a maximum age for every quote and keep the host clock synchronized, because each venue judges request times by its own rule.

A latency budget adds up the time from the market data event to the last order reaching a venue. The legs run in parallel, so the slower leg sets the window.

> **Hypothetical budget.** Quote age limit 40 ms, decision 5 ms, order path 30 ms to venue A and 120 ms to venue B. The exposure window is 40 + 5 + 120 = 165 ms, not 195 ms. Measured numbers replace these, per venue and per time of day.

**Request timing rules, read 4 November 2026**

| Venue | Rule | Note |
| --- | --- | --- |
| Binance | Rejects a timestamp more than 1 second ahead of server time or older than `recvWindow` | Default 5,000 ms, maximum 60,000 ms |
| Bybit | `server_time - recv_window <= timestamp < server_time + 1000` | Default 5,000 ms; the guide asks for NTP-synchronized time |
| Kraken | The nonce must always increase per API key | Repeated invalid nonces can bring temporary bans; Add Order also takes a `deadline` 2 to 60 seconds ahead |

Do not widen these windows to hide drift. Alert on clock offset against each venue's server time, and log your own receive time next to venue timestamps.

## A rate-limit budget across venues

Give each venue its own budget and reserve part of it for cancels and reconciliation, because limits count different things on each venue.

**What each venue counts (official documentation and Binance exchangeInfo, read 4 November 2026)**

| Venue | Counted by | Documented limit | Over the limit |
| --- | --- | --- | --- |
| Binance | IP for request weight, account for unfilled orders | 6,000 weight a minute; 100 unfilled orders per 10 seconds; 200,000 a day | HTTP 429, then an automated IP ban (HTTP 418) from 2 minutes to 3 days |
| Bybit | IP, plus a rolling per-second limit per UID by endpoint | 600 requests per 5 seconds per IP; order create per UID 10/s linear, 20/s spot (UTA 2.0 Pro table) | HTTP 403 and a wait of at least 10 minutes at IP level; `retCode` 10006 at API level |
| Kraken | A counter for each API key | Maximum 15, 20 or 20 with decay of 0.33, 0.5 or 1 per second (Starter, Intermediate, Pro); order calls use a different limiter | `EAPI:Rate limit exceeded` |

> **Hypothetical reconciliation on Kraken's Starter tier.** Polling has pushed the counter to 14 when a leg fails. Reconciling needs balances (1), open orders (1), an order query (1) and trade history (2), which is 5 points against a maximum of 15. The counter would be 4 points over, and at a decay of 0.33 per second that means about 12 seconds of waiting with a position open.

Stream market data instead of polling, and keep a reserve that normal operation cannot spend. Because Binance counts weight per IP, other processes on the same address draw on the same budget.

## Crypto arbitrage bot in Python: a failed-leg sketch

The code path that matters most runs after a leg fails, so write and test it first.

This sketch uses synthetic venues with scripted fill ratios and no network calls. Prices and fees are hypothetical and say nothing about how often such a gap exists. It closes any unmatched quantity on the venue that filled, then compares total inventory with the starting figure and halts on any difference. Venue `b` holds 100 in quote because buying back costs more than the sale returned; the sketch does not check that a repair can be paid for.

```python
from decimal import Decimal as D

class Venue:
    """Synthetic venue: pre-funded balances, scripted IOC fill ratios."""
    def __init__(self, base, quote, fee, fills):
        self.base, self.quote, self.fee = D(base), D(quote), D(fee)
        self.fills = iter(fills)

    def ioc(self, side, qty, price):
        got = qty * D(next(self.fills, 0))
        cash = got * price
        if side == "BUY":
            self.base += got
            self.quote -= cash * (1 + self.fee)
        else:
            self.base -= got
            self.quote += cash * (1 - self.fee)
        return got

def run_pair(buy, sell, qty, ask, bid, slip=D("0.003")):
    """Send both legs, close any unmatched quantity, then reconcile."""
    start = buy.base + sell.base
    if buy.quote < qty * ask * (1 + buy.fee) or sell.base < qty:
        return "SKIP not enough inventory"
    bought = buy.ioc("BUY", qty, ask)      # concurrent in production
    sold = sell.ioc("SELL", qty, bid)
    gap = bought - sold                    # unmatched quantity
    if gap > 0:
        buy.ioc("SELL", gap, ask * (1 - slip))
    elif gap < 0:
        sell.ioc("BUY", -gap, bid * (1 + slip))
    drift = buy.base + sell.base - start   # must be zero after repair
    return f"{'HALT' if drift else 'OK'} gap={gap:.1f} drift={drift:.1f}"

cases = {"both legs fill": ([1], [1]),
         "sell leg 40%": ([1, 1], [D("0.4")]),
         "sell leg rejected": ([1, 1], [0]),
         "buy leg rejected": ([0], [1, 1]),
         "repair also fails": ([1, 0], [0])}
for name, (fills_a, fills_b) in cases.items():
    a = Venue(0, 5000, "0.001", fills_a)
    b = Venue(10, 100, "0.001", fills_b)
    before = a.quote + b.quote
    status = run_pair(a, b, D(10), D("100.00"), D("100.30"))
    print(f"{name:18} {status:24} cash {a.quote + b.quote - before:+9.2f}")
```

Output:

```
both legs fill     OK gap=0.0 drift=0.0     cash     +1.00
sell leg 40%       OK gap=6.0 drift=0.0     cash     -2.60
sell leg rejected  OK gap=10.0 drift=0.0    cash     -5.00
buy leg rejected   OK gap=-10.0 drift=0.0   cash     -5.02
repair also fails  HALT gap=10.0 drift=10.0 cash  -1001.00
```

Read the cash column as the shape of the problem, not a forecast: here one rejected leg costs about five times what one filled pair returns, and the last case leaves an open position for a person to resolve. A real connector sends the legs concurrently and sets a client order ID (`newClientOrderId` on Binance, `orderLinkId` on Bybit, `cl_ord_id` on Kraken), so that a timed-out order can be looked up before any resend.

## Reconciliation and testing before real money

Reconcile after every failed leg and on a schedule, and use test environments to prove fault handling, not opportunity.

Reconciliation compares open orders, fills and balances with each venue and stops on any unexplained difference. Some venues add a safety net: Kraken's `CancelAllOrdersAfter` cancels all orders when a timer expires, and its documentation suggests calling it every 15 to 30 seconds with a 60-second timeout. Such a timer cancels orders; it does not close a position.

Test environments cannot tell you whether a gap is real. Binance states that its Spot Testnet's prices and order books are independent from the live exchange, that Demo Mode's are similar to it, and that realistic data is not real data. A check of public endpoints on 4 November 2026 found testnet BTCUSDT quotes close to live ones with different quantities, so a close price on a given day proves nothing about live fills. Test behavior instead: inject rejected legs, partial fills, timeouts and dropped connections, and replay recorded books.

A monitoring-only system built first shows whether your rules would ever trigger after costs, as in the [multi-exchange arbitrage monitoring](https://fincodelabs.com/en/projects/multi-exchange-arbitrage-monitoring) scenario. Limits and the kill switch are covered in [trading bot risk management](https://fincodelabs.com/en/blog/trading-bot-risk-management).

## Crypto arbitrage software: buy, build or hire?

Buy only if a product's model fits your rules and you accept who holds the keys, and build or hire once you can write down the rules and the leg policy.

- **Specify:** venues, instruments, leg policy, inventory limits per venue, the rebalancing rule, maximum quote age, budget per venue and halt conditions.
- **Accept against:** the failure cases above, never a return target.
- **Check:** each venue's API terms.

An arbitrage trading bot development company can be assessed with the checklist in [crypto bot development company](https://fincodelabs.com/en/blog/crypto-bot-development-company), and one seller's deliverables are on the [custom trading bot development](https://fincodelabs.com/en/services/algorithmic-trading) page. Treat any monthly return quoted for crypto arbitrage trading software as a reason to walk away.

Which venues you may use depends on your jurisdiction and each platform's terms. For readers in Turkey, Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized. Never trade against yourself to manufacture volume: wash trading is market abuse. Automated trading can lose money, and correct software does not change that.

## Frequently asked questions

### How is an arbitrage bot different from a normal trading bot?

It has to coordinate orders on independent venues that cannot be made atomic, so leg handling, per-venue inventory, rate limits and reconciliation dominate the work.

### What is leg risk?

The risk that one side of the trade fills and the other does not, leaving an unintended position that has to be closed at whatever price is then available.

### Can I build a crypto arbitrage bot in Python?

Yes. Python with a library such as CCXT suits many designs, within the latency your measurements allow. Failure handling is the harder part, whatever the language.

### Can an arbitrage bot lose money?

Yes. Fees, slippage, failed legs, outages and counterparty failure can each turn a visible gap into a loss, and backtests do not guarantee future results.

## Sources

- [Binance Spot API documentation on GitHub: REST API (limits, request security, HTTP 5XX, account commission)](https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md)
- [Binance Wallet API: Capital (coin and network configuration)](https://developers.binance.com/en/docs/catalog/core-trading-wallet/api/rest-api/capital)
- [Binance Spot API documentation: Demo Mode (testnet and demo mode compared)](https://github.com/binance/binance-spot-api-docs/blob/master/demo-mode/general-info.md)
- [Bybit V5 API: Place Order (asynchronous response, time in force, orderLinkId)](https://bybit-exchange.github.io/docs/v5/order/create-order)
- [Bybit V5 API: Rate Limit](https://bybit-exchange.github.io/docs/v5/rate-limit)
- [Bybit V5 API: Integration guidance (timestamps and recv_window)](https://bybit-exchange.github.io/docs/v5/guide)
- [Bybit V5 API: Get Coin Info (chain status, withdrawal fees)](https://bybit-exchange.github.io/docs/v5/asset/coin-info)
- [Bybit V5 API: Get Fee Rate](https://bybit-exchange.github.io/docs/v5/account/fee-rate)
- [Kraken Developers: Spot REST Rate Limits](https://docs.kraken.com/exchange/guides/rest/ratelimits)
- [Kraken Developers: Spot REST Authentication (nonce)](https://docs.kraken.com/exchange/guides/rest/authentication)
- [Kraken Developers: Add Order (time in force, deadline, cl_ord_id)](https://docs.kraken.com/api-reference/trading/add-order)
- [Kraken Developers: Cancel All Orders After X](https://docs.kraken.com/api-reference/trading/cancel-all-orders-after-x)
- [Kraken Developers: Get Withdrawal Information (permissions, deprecation)](https://docs.kraken.com/api-reference/funding/get-withdrawal-information)
- [Kraken Developers: Get Trade Volume (fees per pair)](https://docs.kraken.com/api-reference/account-data/get-trade-volume)
- [Capital Markets Law No. 6362, consolidated text, Article 99/A (Turkish)](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf)

> This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the [editorial policy](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/arbitrage-bot-development

Cite as: Fincode, "Arbitrage bot development: what the build really involves", updated November 5, 2026, https://fincodelabs.com/en/blog/arbitrage-bot-development
