Нажмите ESC, чтобы закрыть

Арбитраж DEX и CEX: Мануал по ручной отработке спредов

Анатомия спреда: почему DEX и CEX не могут жить в полной синхронизации

Если вы думаете, что арбитраж между DEX и CEX - это кнопка «бабло», где достаточно найти разницу в 2% и перегнать монеты туда-сюда, вас ждет жесткое приземление. Межбиржевой спред - это не бесплатные деньги, это премия за риск, неликвидность и задержки в инфраструктуре.

На CEX (Binance, Bybit, EXMON) цена формируется в ордербуке (Limit Order Book). Ликвидность там динамическая: маркетмейкеры двигают сетку ордеров за микросекунды, алгоритмы постоянно пересчитывают справедливую стоимость на основе фьючерсных индексов.

На DEX (Uniswap, Raydium, PancakeSwap) цена зависит от математической кривой пула ликвидности. В классическом AMM v2 (x · y = k) цена меняется только тогда, когда в пул физически прилетает транзакция. Если на Binance цена монеты X упала на 5% за секунду из-за крупного продавца, пул на Uniswap будет стоять на месте со старой высокой ценой до тех пор, пока:

  • В сеть не задопится транзакция арбитражного бота.
  • Транзакция не пройдет фазу валидации в блоке.

Вот в этом временном зазоре (от пары секунд в быстрых сетях до нескольких блоков в Ethereum) и рождается спред.

Анатомический срез: откуда берется ручной спред

Руками невозможно соперничать с HFT-ботами и MEV-поисковиками, которые сидят на частных RPC-нодах и платят чаевые валидаторам через Flashbots, чтобы вклиниться в тот же блок (backrunning). Мелкие спреды в 0.2%–0.5% на ETH или BTC забирают алгоритмы на микросекундах.

Но ручной трейдер охотится за совсем другим типом расхождений:

  • Импульсный дамп/памп среднекаповых альткоинов (Mid-caps). В пул на DEX прилетает «кит» и продает токен на $50,000. В не самом глубоком пуле цена мгновенно проваливается на 4–7%. Боты на CEX не могут мгновенно откупить этот пролив, потому что на самой CEX стакан плотный и цена там не менялась.
  • Сетевые заторы и скачки Gas Price. Во время жесткого шторма на рынке мемпул перегружен. MEV-боты временно отваливаются или требуют гигантских комиссий, из-за чего спреды на DEX могут висеть по 2–5 минут.
  • Несинхронный листинг и приостановка вывода (Deposit/Withdrawal Halt). Биржа закрывает ввод или вывод токена в определенной сети из-за обновления узла. Приток/отток ликвидности между DEX и CEX режется на корню. На CEX образуется «закрытый стакан», и цена там начинает жить своей жизнью, отклоняясь от DEX хоть на 15%.

Архитектура рабочего пространства и распределение капитала

Главная ошибка новичка - увидели спред на DEX, купили на CEX, нажали «Вывести на кошелек» и сидят ждут 15 минут, пока сеть подтвердит транзакцию. Пока монеты долетят до кошелька, спред не просто схлопнется, вы закрываетесь в гарантированный убыток.

Ручной спред-трейдинг требует дельта-нейтрального распределения или готового балансового расщепления.

Модель «Двойное плечо» (Split Balance)

Забудьте про переводы между площадками во время сделки. Переводы делаются до начала работы.

Ваш депозит делится на две равные части:

Total Deposit
 

При таком раскладе вы исполняете ордера одновременно:

  • Если токен на DEX стоит $1.10, а на CEX - $1.00: Вы моментально продаете ARB на DEX (забираете $1.10) и покупаете ARB на CEX (по $1.00).
  • Физического перевода монет нет. Вы забрали 10% дельты (минус комиссии) за 3 секунды.
  • Выравнивание (ребалансировка) депозита между кошельком и биржей происходит потом, когда рынок успокоится и комиссия сети упадет до минимума.

Математика сделки: расчет «чистого» спреда и точки безубытка

Видеть на экране красивую разницу в 3% и пересчитывать её в гарантированную прибыль — первая причина слива депозита у начинающих арбитражников. «Грязный» спред (Gross Spread) ничего не значит. Нас интересует только чистый спред (Net Spread) после уплаты всех точечных поборов каждой инфраструктуры.

Формула расчета чистой доходности

Net_Profit = (Psell · V) - (Pbuy · V) - FeeCEX - FeeDEX - Gas - Impact

Где:

  • Psell и Pbuy - реальные цены исполнения ордеров (не Mid-Price из шапки сайта, а средневзвешенные цены с учетом глубины стакана).
  • V - объем сделки в базах или стейблах.
  • FeeCEX - комиссия биржи за тейкер-ордер (обычно 0.05%–0.1%).
  • FeeDEX - комиссия пула (0.01%, 0.05%, 0.3% или 1% в зависимости от тира пула).
  • Gas - стоимость включения транзакции в блок, пересчитанная в USD.
  • Impact - влияние собственного объема на цену внутри пула AMM.

Главный враг - Price Impact на DEX

Если на CEX вы видите в стакане 10,000 монет по цене $1.00, вы заберете их ровно по $1.00 (плюс комиссия). На DEX со стандартной механикой Constant Product (x · y = k) каждая покупаемая монета стоит дороже предыдущей.

Формула цены продажи токена x за токен y в пуле x · y = k:

yout = (yreserve · xin · (1 - fee)) / (xreserve + xin · (1 - fee))

Если вы кидаете в пул с ликвидностью $100,000 ордер на $5,000, вы смещаете цену пула на ~5%. Ваш чистый спред в 3% мгновенно превращается в 2% убытка еще до того, как транзакция попала в блок.

Таблица реального спреда при разном объеме сделки (Пул $50k, грязный спред 2.5%)

Объем сделки ($)Price Impact (%)Комиссии (Gas + Fees)Чистый спред (%)Результат сделки
$100~0.2%~$1.50 (1.5%)+0.8%Мелкая прибыль
$500~1.0%~$1.50 (0.3%)+1.2%Оптимальный профит
$2,000~3.8%~$1.50 (0.075%)-1.375%Убыток из-за Impact

Вывод: для каждого размера пула существует свой строго математический потолок объема сделки. Вливать слишком много - ломать себе математику.

Автоматизация поиска: пишем мониторинг на Python

Заходить в браузер и обновлять вкладочки DexScreener и стакан биржи руками - это архаизм. Ручной трейдер использует софт не для автоматического исполнения сделок (там соревноваться с MEV бесполезно), а для мгновенного звукового или текстового алерта в Telegram/консоль, когда открывается аномальное окно.

Ниже приведен полностью рабочий, завершенный скрипт на Python 3.10+. Скрипт в реальном времени мониторит прямые резервы пула PancakeSwap v2 (BNB Chain) через RPC и сравнивает их с ордербуком CEX (в данном примере используется REST API Binance, легко адаптируемый под EXMON). Конечно, скрипт простой и до совершенства его еще следует доработать, но он вполне рабочий.

Код сканера спреда с учетом резервов и комиссий

import sys
import time
import requests
from web3 import Web3
# ============================================================
# CONFIG
# ============================================================
BSC_RPC = "https://bsc-dataseed1.binance.org"
POOL_ADDRESS = Web3.to_checksum_address(
   "0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE"
)
BINANCE_DEPTH_URL = (
   "https://api.binance.com/api/v3/depth"
   "?symbol=BNBUSDT&limit=20"
)
TRADE_VOLUME_USD = 300.0
CEX_TAKER_FEE = 0.001
DEX_FEE = 0.0025
MIN_NET_SPREAD_PCT = 0.8
# приблизительная оценка газа за swap
ESTIMATED_GAS_COST_USD = 0.15
REQUEST_TIMEOUT = 3
# ============================================================
# WEB3
# ============================================================
w3 = Web3(Web3.HTTPProvider(BSC_RPC))
if not w3.is_connected():
   print("Ошибка подключения к BSC RPC")
   sys.exit(1)
# ============================================================
# ABI
# ============================================================
PAIR_ABI = [
   {
       "constant": True,
       "inputs": [],
       "name": "getReserves",
       "outputs": [
           {"name": "_reserve0", "type": "uint112"},
           {"name": "_reserve1", "type": "uint112"},
           {"name": "_blockTimestampLast", "type": "uint32"},
       ],
       "stateMutability": "view",
       "type": "function",
   },
   {
       "constant": True,
       "inputs": [],
       "name": "token0",
       "outputs": [{"name": "", "type": "address"}],
       "stateMutability": "view",
       "type": "function",
   },
   {
       "constant": True,
       "inputs": [],
       "name": "token1",
       "outputs": [{"name": "", "type": "address"}],
       "stateMutability": "view",
       "type": "function",
   },
]
pair = w3.eth.contract(
   address=POOL_ADDRESS,
   abi=PAIR_ABI
)
# ============================================================
# KNOWN TOKENS
# ============================================================
WBNB = Web3.to_checksum_address(
   "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
)
USDT = Web3.to_checksum_address(
   "0x55d398326f99059fF775485246999027B3197955"
)
# ============================================================
# VERIFY PAIR
# ============================================================
try:
   token0 = Web3.to_checksum_address(
       pair.functions.token0().call()
   )
   token1 = Web3.to_checksum_address(
       pair.functions.token1().call()
   )
except Exception as e:
   print("Ошибка чтения token0/token1")
   print(e)
   sys.exit(1)
PAIR_LAYOUT = None
if token0 == WBNB and token1 == USDT:
   PAIR_LAYOUT = "WBNB_USDT"
elif token0 == USDT and token1 == WBNB:
   PAIR_LAYOUT = "USDT_WBNB"
else:
   print("Пул не является парой WBNB/USDT")
   print("token0 =", token0)
   print("token1 =", token1)
   sys.exit(1)
print("Pair verified:")
print("token0 =", token0)
print("token1 =", token1)
# ============================================================
# BINANCE
# ============================================================
def get_orderbook():
   try:
       response = requests.get(
           BINANCE_DEPTH_URL,
           timeout=REQUEST_TIMEOUT
       )
       response.raise_for_status()
       data = response.json()
       if "bids" not in data:
           return None
       if "asks" not in data:
           return None
       return data
   except Exception:
       return None
def calculate_execution_buy_price(
   asks,
   target_usd
):
   """
   Покупка BNB за USDT.
   Возвращает:
       avg_price,
       received_bnb
   """
   spent_usd = 0.0
   received_bnb = 0.0
   for price_str, qty_str in asks:
       price = float(price_str)
       qty = float(qty_str)
       level_usd = price * qty
       if spent_usd + level_usd >= target_usd:
           remaining_usd = target_usd - spent_usd
           partial_qty = remaining_usd / price
           received_bnb += partial_qty
           spent_usd += remaining_usd
           break
       received_bnb += qty
       spent_usd += level_usd
   if spent_usd < target_usd:
       return None, None
   avg_price = spent_usd / received_bnb
   return avg_price, received_bnb
def calculate_execution_sell_price(
   bids,
   sell_bnb
):
   """
   Продажа BNB в USDT.
   Возвращает:
       avg_price,
       received_usdt
   """
   remaining_bnb = sell_bnb
   received_usdt = 0.0
   sold_bnb = 0.0
   for price_str, qty_str in bids:
       price = float(price_str)
       qty = float(qty_str)
       take_qty = min(
           remaining_bnb,
           qty
       )
       received_usdt += take_qty * price
       sold_bnb += take_qty
       remaining_bnb -= take_qty
       if remaining_bnb <= 0:
           break
   if remaining_bnb > 0:
       return None, None
   avg_price = received_usdt / sold_bnb
   return avg_price, received_usdt
# ============================================================
# DEX
# ============================================================
def get_reserves():
   try:
       reserves = (
           pair.functions
           .getReserves()
           .call()
       )
       raw0 = reserves[0]
       raw1 = reserves[1]
       if PAIR_LAYOUT == "WBNB_USDT":
           reserve_bnb = raw0 / 1e18
           reserve_usdt = raw1 / 1e18
       else:
           reserve_usdt = raw0 / 1e18
           reserve_bnb = raw1 / 1e18
       return (
           reserve_bnb,
           reserve_usdt
       )
   except Exception:
       return None, None
def amm_out(
   amount_in,
   reserve_in,
   reserve_out
):
   if (
       amount_in <= 0
       or reserve_in <= 0
       or reserve_out <= 0
   ):
       return 0.0
   amount_in_fee = (
       amount_in
       * (1 - DEX_FEE)
   )
   numerator = (
       amount_in_fee
       * reserve_out
   )
   denominator = (
       reserve_in
       + amount_in_fee
   )
   return numerator / denominator
# ============================================================
# MONITOR
# ============================================================
def monitor():
   print()
   print("DEX/CEX Arbitrage Monitor Started")
   print("-" * 80)
   while True:
       book = get_orderbook()
       if book is None:
           time.sleep(1)
           continue
       reserve_bnb, reserve_usdt = (
           get_reserves()
       )
       if (
           reserve_bnb is None
           or reserve_usdt is None
       ):
           time.sleep(1)
           continue
       bids = book["bids"]
       asks = book["asks"]
       # ====================================================
       # SCENARIO A
       # BUY ON DEX
       # SELL ON CEX
       # ====================================================
       bnb_from_dex = amm_out(
           TRADE_VOLUME_USD,
           reserve_usdt,
           reserve_bnb
       )
       if bnb_from_dex <= 0:
           time.sleep(1)
           continue
       sell_price, usdt_received = (
           calculate_execution_sell_price(
               bids,
               bnb_from_dex
           )
       )
       if usdt_received is None:
           time.sleep(1)
           continue
       usdt_received *= (
           1 - CEX_TAKER_FEE
       )
       usdt_received -= (
           ESTIMATED_GAS_COST_USD
       )
       spread_a = (
           (usdt_received - TRADE_VOLUME_USD)
           / TRADE_VOLUME_USD
       ) * 100
       # ====================================================
       # SCENARIO B
       # BUY ON CEX
       # SELL ON DEX
       # ====================================================
       buy_price, bought_bnb = (
           calculate_execution_buy_price(
               asks,
               TRADE_VOLUME_USD
           )
       )
       if bought_bnb is None:
           time.sleep(1)
           continue
       bought_bnb *= (
           1 - CEX_TAKER_FEE
       )
       usdt_from_dex = amm_out(
           bought_bnb,
           reserve_bnb,
           reserve_usdt
       )
       usdt_from_dex -= (
           ESTIMATED_GAS_COST_USD
       )
       spread_b = (
           (usdt_from_dex - TRADE_VOLUME_USD)
           / TRADE_VOLUME_USD
       ) * 100
       dex_price = (
           reserve_usdt
           / reserve_bnb
       )
       sys.stdout.write(
           "\r"
           f"DEX={dex_price:.2f} | "
           f"A:{spread_a:+.2f}% | "
           f"B:{spread_b:+.2f}%"
       )
       sys.stdout.flush()
       if spread_a >= MIN_NET_SPREAD_PCT:
           print(
               "\n"
               f"[SIGNAL] "
               f"BUY DEX -> SELL CEX "
               f"({spread_a:.2f}%)"
           )
       elif spread_b >= MIN_NET_SPREAD_PCT:
           print(
               "\n"
               f"[SIGNAL] "
               f"BUY CEX -> SELL DEX "
               f"({spread_b:.2f}%)"
           )
       time.sleep(1.5)
# ============================================================
# ENTRY
# ============================================================
if __name__ == "__main__":
   try:
       monitor()
   except KeyboardInterrupt:
       print("\nStopped.")

Этот скрипт считает реальное проскальзывание, потому что вычисляет цену не по дефолтному курсу, а пропускает ваш объём в $300 через математическую модель пула.

Протокол ручного исполнения: как не стать жертвой сэндвич-ботов

Нажать две кнопки на разных сайтах кажется просто задачей. На практике 80% ручных трейдеров сливают профит в момент отправки транзакции в публичный мемпул.

Как только ваша транзакция покупки на DEX попадает в публичный мемпул Ethereum, BNB Chain или Arbitrum, её моментально считывают MEV-боты (Maximum Extractable Value). Если у вас стоит проскальзывание (Slippage Tolerance) больше 0.5%, бот исполняет сэндвич-атаку (Sandwich Attack):

  • Бот перед вашей транзакцией покупает токен, задирая цену вверх (Frontrun).
  • Ваша транзакция исполняется по худшей из возможных цен на грани вашего Slippage limit.
  • Бот сразу после вас продает токен с прибылью (Backrun).

В итоге вы получаете токен по сдвинутой цене, и весь спред уходит MEV-поисковику.

Защищенный контур исполнения сделки

Чтобы руками забирать спреды и не кормить ботов, исполнение настраивается по следующей схеме:

  • Приватные RPC-ноды (MEV-Blocker / Flashbots). Забудьте про дефолтные сетевые настройки в MetaMask. Все транзакции на DEX отправляются только через приватные реле, которые транслируют транзакцию напрямую валидатору блока, минуя публичный мемпул.
    Для Ethereum: https://rpc.mevblocker.io или https://rpc.flashbots.net
    Для BNB Chain / Polygon / Arbitrum: используйте службы с MEV-Protection от QuickNode или службы вроде BloXroute.
  • Жесткий Slippage Limit. Никаких дефолтных 1% или 2% в интерфейсе Uniswap/1inch. Максимально допустимый проскальзывание для арбитражной сделки — 0.1%–0.3%. Если цена ушла за мгновение до включения в блок, транзакция должна свалиться по ошибке (revert), а не исполниться с убытком.
  • Оптимизация параметров Gas Fee. Настройка комиссии в Web3 кошельке переводится в ручной режим (Advanced Gas Controls). Max Priority Fee (чаевые валидатору) выставляется чуть выше текущего медианного значения сети, чтобы выкупить место в начале следующего блока. На L2-сетях (Arbitrum, Base, OP) критично следить за L1 Data Fee - во время нагрузки на Ethereum отправка транзакций в L2 может стоить в 5 раз дороже обычного.

Продвинутая техника: Дельта-нейтральный арбитраж «DEX Spot vs CEX Futures»

Физически держать альткоин на обоих балансах - это риск понести убыток от падения курса самого актива (Market Price Risk), пока вы ждете появления спреда. Если монеты упадут на 15%, ваш зафиксированный спред в 2% не перекроет просадку депозита.

Продвинутая методология, применяемая в Академии EXMON - дельта-нейтральный спред-трейдинг через фьючерсы.

Механика работы через Short-хедж

Вместо покупки спотового актива на CEX, вы держите там позицию в фьючерсе с плечом 1x в шорт.

Delta Portfolio
 

Алгоритм отработки:

  • На DEX возник пролив цены: токен провалился до $0.95, тогда как на CEX фьючерс и спот стоят $1.00 (спред 5.2%).
  • Вы откупаете спот на DEX по $0.95.
  • Одновременно на CEX открываете Short по фьючерсу по цене $1.00.
  • Ваша дельта мгновенно обнулена: при любом движении рынка общий эквивалент депозита не меняется.
  • Когда цена на DEX выравнивается с CEX (например, обе площадки встали на уровень $0.98), вы закрываете оба:
    • Продаете спот на DEX по $0.98 (Профит: +$0.03 на монету).
    • Закрываете шорт на CEX по $0.98 (Профит: +$0.02 на монету).

Суммарный чистый профит: $0.05 с монеты (5% к задействованному объему) при полном отсутствии риска падения самого токена.

Дополнительный бонус: Если фьючерс на CEX торгуется с контанго (цена фьючерса выше спота), вы параллельно забираете положительный Funding Rate (ставку финансирования) каждый расчетный час.

Малоизвестные подводные камни и риски

Если стратегия выглядит безупречно, значит, вы не увидели скрытых уязвимостей. Ручной спред-трейдинг полон скрытых инженерных «мин».

1. Токены с встроенным налогом (Transfer Tax / Reflection Tokens)

Некоторые ERC-20 / BEP-20 токены зашивают в смарт-контракт комиссию за каждый перевод (например, 2%–5% на сжигание или маркетинговый кошелек).

Если вы не проверили код контракта через эксплорер или сканер (TokenSniffer, GoPlus), вы отправите токен из пула, потеряете 5% на механике самого токена, еще 0.3% на комиссии AMM и закроете сделку в тяжелейший минус.

2. Сдвиг виртуальной ликвидности в Uniswap v3 (Concentrated Liquidity Trap)

В Uniswap v2 ликвидность размазана от нуля до бесконечности. В v3 ликвидность концентрируется в узких диапазонах (Ticks).

Если спред возник из-за того, что цена вылетела за пределы диапазона основного маркетмейкера, визуально разница цен может казаться огромной (до 10%–20%). Но объём ликвидности на текущем тике может составлять всего $15. Попытка совершить сделку приведет к мгновенному сдвигу цены на десятки процентов против вас.

3. Дисбаланс ликвидности и «Застрявший капитал» (Inventory Risk)

Отработав спред 5 раз подряд в одну сторону (например, Покупка на DEX -> Продажа на CEX), вы полностью опустошите запас USDT на CEX и зальете свой Web3-кошелек альткоинами.

Ребалансировка требует времени и затрат на комиссию. Если рынок пойдет в сильный тренд, вы окажетесь заперты в одной ноге позиции. Правило риска: никогда не допускайте перекоса баланса одной из площадок более чем 70/30.

Чек-лист трейдера перед нажатием кнопки

Разместите этот чек-лист перед рабочим монитором. Вход в сделку осуществляется только при совпадении всех 5 пунктов:

  • Защита от MEV включена: В кошельке выбран приватный RPC-узел (MEV-Blocker / Flashbots).
  • Глубина пула проверена: Объем сделки не превышает 1%–2% от общих резервов пула на DEX (Price Impact < 0.3%).
  • Чистый спред зафиксирован: Расхождение цен перекрывает совокупные комиссии CEX + DEX + Gas в 2.5 раза.
  • Смарт-контракт чист: Токен не имеет скрытых комиссий за перевод (Transfer Fee = 0%).
  • Капитал распределен: На CEX и DEX достаточно баланса в обеих валютах пары для мгновенного исполнения без ожидания переводов.

Спред-трейдинг между децентрализованными и централизованными площадками - это математическая дисциплина. Здесь выигрывает не тот, кто угадывает направление тренда, а тот, кто лучше считает комиссии, контролирует инфраструктуру и умеет ждать свою аномалию.


FAQ

Ручной арбитраж между DEX и CEX базируется на сборе временных раскорреляций цен между пулами ликвидности AMM и лимитными ордербуками централизованных бирж с использованием предварительно распределенного капитала. Трейдер не перегоняет токены между площадками в момент импульса, а одновременно исполняет противоположные ордера на имеющиеся балансы, забирая дельту, когда размер спреда перекрывает совокупность комиссий taker, сборов пула, стоимость сетевого газа и волатильность от Price Impact.

Защита от сэндвич-атак реализуется путем отправки транзакций в блокчейн через приватные RPC-узлы (Flashbots, MEV-Blocker), вычеркивающими ордер из публичного мемпула. Дополнительно в интерфейсе кошелька принудительно урезается показатель Slippage Tolerance до диапазонов 0.1%–0.3%, что вызывает гарантированный откат (revert) транзакции в случае попытки MEV-поисковика сдвинуть резервы пула в блоке перед вашей сделкой.

Чистая прибыль рассчитывается путем вычитания полной комиссии инфраструктуры из валового разрыва цен между площадками. В расчете учитываются комиссия за тейкер-ордер на CEX, комиссия пула DEX, стоимость включения транзакции в блок (Gas), пересчитанная в базовый актив, и математическое смещение цены внутри AMM-кривой (X x Y = k), вызванное объемом входящей сделки. Вход оправдан только тогда, когда грязный спред перекрывает эту сумму с учетом доступной глубины ликвидности.
Martyn Borkowski

I am a crypto trader specializing in digital assets and blockchain markets.

My focus is on identifying opportunities, managing risk, and optimizing strategies to achieve consistent growth in the fast-evolving world of cryptocurrency.

Verification & Professional Profiles: X Profile

...

Поделитесь своим мнением

Ваш e-mail не будет опубликован. Обязательные поля отмечены *