Crypto arbitrage in Python

Crypto arbitrage exploits price gaps between venues, here is a cross-exchange basis strategy in Python, backtested on the Rust core.

What is crypto arbitrage?

Crypto arbitrage profits from the same asset trading at different prices in different places, across exchanges, between spot and perpetuals, or in the funding basis. The classic form is the cross-exchange basis: when one venue's price drifts from another's, trade the convergence.

Pure latency arbitrage is a hardware race retail cannot win, but the slower, structural basis between venues is tradeable and a useful thing to research. The key is that real costs, fees on both legs, slippage, transfer frictions, eat most of the headline gap.

The logic

We execute on dYdX and reference Binance's price via symbol_ref, computing the fractional basis between the two venues. Manifold-BT's cross-exchange mode lets one venue generate the signal while another is the execution target.

When dYdX trades more than 0.2% below Binance we go long dYdX (expecting convergence up); more than 0.2% above, short; otherwise flat. The threshold is what keeps the strategy from churning on noise inside the cost band.

crypto arbitrage in Python

Here is the full strategy in the Manifold-BT expression DSL. It imports manifoldbt, builds the signal, configures realistic execution, and runs the backtest against Rust.

crypto_arbitrage.py
import manifoldbt as mbt
from manifoldbt.indicators import close
from manifoldbt.expr import symbol_ref
from manifoldbt.helpers import time_range, Slippage, Interval

# Execution on dYdX; reference price from Binance, trade the cross-venue basis
bn = symbol_ref("binance:BTC-USDT:perp", "close")
basis = (close - bn) / bn       # dYdX vs Binance, as a fraction

strategy = (
    mbt.Strategy.create("xventure_basis")
    .signal("bn", bn)           # cross-venue signal must be named
    .signal("basis", basis)
    .size(mbt.when(basis < -0.002, 1.0, mbt.when(basis > 0.002, -1.0, 0.0)))
)

start, end = time_range("2024-01-01", "2026-01-01")
config = mbt.BacktestConfig(
    universe={
        "dydx":    ["BTC-USD:perp"],    # execution (fills here)
        "binance": ["BTC-USDT:perp"],   # reference (via symbol_ref)
    },
    time_range_start=start,
    time_range_end=end,
    bar_interval=Interval.hours(1),
    initial_capital=10_000,
    fees=mbt.FeeConfig.binance_perps(),
    slippage=Slippage.fixed_bps(1),
    warmup_bars=1,
)
store = mbt.ingest(provider="dydx", symbol="BTC-USD", symbol_id=1,
                   interval="1h", start="2024-01-01T00:00:00Z",
                   end="2026-01-01T00:00:00Z")
mbt.ingest(provider="binance", symbol="BTCUSDT", symbol_id=2,
           interval="1h", start="2024-01-01T00:00:00Z",
           end="2026-01-01T00:00:00Z", store=store)
result = mbt.run(strategy, config, store)
print(result.summary())

Backtest it with Manifold-BT

Cross-exchange backtesting is a Pro feature: signals come from one venue, fills from another. The one-basis-point slippage and perp fees are deliberately modest, real arbitrage net of all frictions is thin, which the backtest will show.

The headline return here is small by design, that is the honest picture of structural arbitrage. The value is in studying how often the basis is exploitable and how costs erode it, not in chasing a fantasy edge.

Backtest configuration
Universedydx (exec) + binance (signal)
Bar interval1h
FeesFeeConfig.binance_perps()
Slippagefixed 1 bps
Warmup1 bar
Sample tearsheet (illustrative, not a forecast)
Total return+8.6%
Sharpe1.71
Max drawdown-3.2%
Win rate64%
Trades742

Keep reading

Run your first backtest

Install Manifold-BT and reproduce the backtest above in seconds. The Rust core runs years of bars sub-second so you can sweep parameters instead of waiting.

$pip install manifoldbt