Classical Arbitrage Explained: How Risk-Free Profit Works in Practice
Classical arbitrage is the limiting case of every relative-value trade: a position with zero net investment, no possibility of loss, and a positive expected payoff. In practice that ideal almost never exists — what desks actually run is near-arbitrage, where the residual risk is small, quantifiable, and priced. This piece treats classical arbitrage as an execution and inventory problem rather than a textbook curiosity: where the gross edge comes from, how much of it survives fees and latency, and how it decays as faster capital arrives.
A precise definition
Let V(t) be the value of a self-financing portfolio. A textbook arbitrage requires V(0) <= 0, V(T) >= 0 almost surely, and P(V(T) > 0) > 0. Real markets violate the "almost surely" clause: legs fill at different times, balances sit on insolvent venues, and convergence depends on settlement that can fail. So the working definition for a practitioner is:
net edge = gross price gap
- fees(both legs)
- expected slippage(your size)
- funding/borrow over holding period
- convergence/settlement risk premium
If that quantity isn't comfortably positive and you can execute both legs before the gap moves, you don't have an arbitrage — you have a directional bet with extra steps. Everything below is about estimating each term honestly.
The law of one price as a no-arbitrage condition
The law of one price is not a description of how markets behave; it is the boundary that arbitrageurs enforce. Two claims with identical state-contingent payoffs must have identical prices, otherwise the long-cheap/short-dear portfolio is a money pump. The width of the band around parity is set by the marginal arbitrageur's all-in cost. Tighter costs (co-location, fee rebates, pre-funded inventory) shrink the band; that is the entire competitive dynamic. When you see a persistent gap, the correct first hypothesis is not "free money" but "my cost estimate is wrong" — stale data, an un-modeled withdrawal fee, or a venue you can't actually settle on.
The main forms and where the edge actually lives
Spatial / cross-exchange
The same asset, two venues, two prices. The naive gap is askcheap vs biddear, but the binding constraint is inventory location. You cannot profit from BTC being 30 bps richer on Venue B if your BTC is on Venue A and an on-chain transfer takes 10–30 minutes with price risk the whole way. Serious operators pre-fund both sides and trade locally, treating the on-chain network as a slow rebalancing channel, not an execution path.
Cash-and-carry (spot vs. futures)
Buy spot, sell the future, capture the basis at convergence. This is the cleanest near-arbitrage in crypto because perpetual and dated futures persistently trade rich to spot in bull regimes. The edge is the annualized basis net of two legs of fees and the funding you pay or receive while carrying. See futures trading basics for term-structure mechanics, and crypto interest rate arbitrage for the rate-curve view of the same trade.
Conversion / put-call parity
C - P = S - K·e^{-rT} must hold for European options on a non-dividend asset. When it breaks, a conversion (long stock, short call, long put) or reversal locks the gap. The residual risks are early-exercise on American options, dividend uncertainty, and the borrow cost on the short leg — see options Greeks and short selling mechanics.
ETF / index arbitrage
ETF price vs. NAV of the basket; authorized participants create/redeem to close it. Retail can't be an AP, so the tradeable residual is tiny and dominated by creation-unit size and basket liquidity.
| Form | Gross edge source | Binding constraint | Residual risk |
|---|---|---|---|
| Cross-exchange | Venue price gap | Inventory location, latency | Legging, custody |
| Cash-and-carry | Basis / funding | Capital lockup to expiry | Liquidation, funding flip |
| Conversion | Parity violation | Borrow availability | Early exercise, dividends |
| ETF/NAV | Premium/discount | AP access, basket depth | Tracking, basket liquidity |
| Triangular | Cross-rate inconsistency | Three-leg latency | Legging |
Worked example: cross-exchange, with depth
The single biggest beginner error is quoting off top-of-book. The best ask is for a tiny size; filling real notional walks the book. Model the executable edge against actual depth, not the headline gap.
def executable_edge(asks_cheap, bids_dear, qty, fee=0.001, transfer_cost=0.0):
"""asks_cheap, bids_dear: lists of (price, size) from each book.
Returns net PnL for buying `qty` on the cheap venue and selling on the dear one,
walking both books and paying `fee` per leg."""
def fill(levels, qty):
cost, remaining = 0.0, qty
for price, size in levels:
take = min(remaining, size)
cost += take * price
remaining -= take
if remaining <= 0:
break
if remaining > 0:
return None # not enough depth
return cost
buy_cost = fill(asks_cheap, qty)
sell_proceeds = fill(bids_dear, qty)
if buy_cost is None or sell_proceeds is None:
return None
fees = fee * (buy_cost + sell_proceeds)
return sell_proceeds - buy_cost - fees - transfer_cost
asks_a = [(60000, 0.4), (60010, 0.8), (60025, 2.0)] # cheaper venue
bids_b = [(60300, 0.3), (60280, 0.6), (60250, 1.5)] # dearer venue
print(executable_edge(asks_a, bids_b, qty=1.0, fee=0.001))
Notice the top-of-book gap is 300 (0.5%), but after walking 1 BTC of depth and paying two taker fees, the realized edge is a fraction of that. This is the generic shape of cross-exchange arbitrage: the visible number is a marketing figure; the executable number is what you trade.
Pre-funding and the real cost: capital and counterparty
Pre-funding converts a slow on-chain race into two fast local trades, but it has a cost the textbook ignores: idle capital exposed to exchange solvency risk. Holding 10 BTC on Venue A and the USDT equivalent on Venue B means your effective return is realized spread / total capital deployed across all venues, and every dollar of that capital carries custody risk that the risk-free rate does not. The correct hurdle is not "gross edge > 0" but "annualized net edge > risk-free rate + a credit spread for the worst venue you hold balances on." Diversifying balances across venues is risk management, not diversification of returns.
Cash-and-carry: annualizing and netting funding
def carry_apr(spot, future, days, fee=0.001, funding_apr=0.0):
gross = future / spot - 1.0
net = gross - 2 * fee # fee on both legs at entry
apr = net * (365 / days)
return apr - funding_apr # subtract carrying funding if using a perp
# 60,000 spot, 61,800 90-day future, 0.1%/leg, no perp funding
print(f"{carry_apr(60_000, 61_800, 90):.2%} net APR")
A 3% gross basis over 90 days is ~12.2% annualized gross; after two legs of fees it's lower, and if you express the short via a perpetual you must net the funding you pay or receive each interval. Rank the result against what the same capital earns risk-free — if the spread isn't a few hundred bps above that, the lockup and liquidation risk aren't worth it.
Where the edge decays and who arbitrages it
Classical arbitrage edge is a function of cost asymmetry between you and the fastest competitor. As more capital with co-located infrastructure and fee-tier rebates enters, the no-arbitrage band tightens toward their marginal cost, and your slower capital is left with the trades they decline — typically smaller venues, thinner books, and worse counterparty risk. The honest characterization of the retail/semi-pro opportunity set:
- Liquid majors on tier-1 venues: band is microseconds wide; you will not win it.
- Cross-exchange on smaller venues: real gaps exist but are compensation for
custody and withdrawal risk you are implicitly underwriting.
- Cash-and-carry basis: the most durable retail-accessible edge, because it pays
you for capital lockup and liquidation-risk tolerance rather than pure speed.
This is why "the easy gaps are gone" is literally true: every closed gap raised the bar for the next competitor, and the survivors compete on infrastructure.
The frictions that turn arbitrage into a loss
- Legging risk. One leg fills, the other moves. This is the single most
common way a clean-looking arb becomes an unhedged position. Mitigate with near-simultaneous order submission, maker legs where tolerable, and an abort rule if the second leg can't fill within a price tolerance.
- Slippage and depth. Size to the thinner side of the book; see
transaction costs and slippage and strategy capacity & market impact.
- Settlement and transfer latency. On-chain confirmation and withdrawal
queues are execution-killing for anything but pre-funded inventory.
- Counterparty/custody. A venue halting withdrawals turns "risk-free" into a
total loss on the balance held there.
- Stale data. A juicy gap is usually a feed that hasn't updated; the real
prices are already in line. Timestamp and sanity-check every quote.
Before committing capital, validate the whole pipeline — including realistic fee and depth models — in a backtest, and study market microstructure to understand how the book behaves under your size. The transaction cost analysis framework is the right lens for measuring whether your live fills match your model.
Conclusion
Classical arbitrage is the cleanest expression of no-arbitrage pricing, but the tradeable residual is an infrastructure and inventory business, not a discovery business. The gross gap you see is gross of fees, slippage, funding, capital lockup, and counterparty risk — and in liquid markets the survivors have already competed the net edge down to their marginal cost. The durable opportunities for non-co-located capital are the ones that pay you for bearing carry, lockup, and counterparty risk explicitly: cash-and-carry basis and cross-venue trades on the exchanges faster players avoid. Price the hidden term first, and only then decide whether what's left is arbitrage or merely a bet.
For adjacent relative-value strategies, see triangular arbitrage, pairs trading, statistical arbitrage, and market making. To build the systems that execute them, start from building a trading bot and the quant research workflow.
