Дисклеймер: Информация в этой статье предоставлена исключительно в образовательных и ознакомительных целях и не является финансовым, инвестиционным или торговым советом. Торговля криптовалютами сопряжена с высоким риском убытков.
Архитектура ultra-low-latency системы исполнения мультилег-арбитража: от приёма рыночных данных до отправки ордеров за 2-6 мс.
Представьте дирижёра, который управляет оркестром из пяти бирж одновременно. Каждый инструмент играет свою партию, и между первой нотой на скрипке и последним аккордом на контрабасе не должно пройти больше нескольких миллисекунд. Одна фальшивая нота — и арбитражная возможность превращается в убыток: заполненный лег на одной бирже и убежавшая цена на другой.
Это шестая часть серии «Сложные цепочки арбитража между фьючерсами и спотом», и она самая практическая. Мы спустимся на уровень байтов, кэш-линий и атомарных операций. Каждый раздел — рабочий Rust-код, который можно адаптировать для своей системы.
Исследования показывают: арбитражные спреды в крипте живут 200-800 мс. При общем времени исполнения ниже 50 мс hit rate достигает 82%, а выше 150 мс — падает до 31%. Каждая микросекунда на счету.
Оптимизация latency: от ядра ОС до парсинга
io_uring и AF_XDP: обход сетевого стека
io_uring предоставляет асинхронный I/O через shared-memory кольца между user-space и ядром — после инициализации операции не требуют syscall:
use io_uring::IoUring;
structUringReader {
ring: IoUring,
buffers: Vec<Vec<u8>>, // Пре-аллоцированные буферы: один на биржу
}
implUringReader {
fnnew(num_exchanges: usize) -> std::io::Result<Self> {
Ok(Self {
ring: IoUring::new(256)?,
buffers: (0..num_exchanges).map(|_| vec![0u8; 65536]).collect(),
})
}
/// Один syscall — чтение со всех бирж разомfnsubmit_batch(&mutself) -> std::io::Result<usize> {
// Формируем batch read-операций в submission queue// Каждый CQE возвращает exchange_idx через user_dataself.ring.submit()
}
}
AF_XDP — eBPF-программа перенаправляет пакеты прямо в user-space через UMEM-буферы (Single Producer / Single Consumer кольца), давая latency близкую к DPDK без эксклюзивного владения NIC.
Рекомендация для крипто-арбитража: AF_XDP или io_uring. DPDK избыточен — биржи общаются через WebSocket/HTTPS, а не raw-протоколы.
simd-json и zero-copy десериализация
Большинство бирж отдают JSON. simd-json использует SIMD-инструкции процессора для параллельного разбора, давая 2-4x ускорение:
#![feature(portable_simd)]use std::simd::{f64x4, SimdFloat, SimdPartialOrd};
/// Поиск арбитража: 4 пары bid/ask за одну SIMD-инструкциюfnfind_arbitrage(bids: f64x4, asks: f64x4) ->u64 {
letspreads = bids - asks;
letthreshold = f64x4::splat(0.001); // 0.1% минимум
spreads.simd_gt(threshold).to_bitmask()
}
Вертикальные SIMD-операции в 2.7x быстрее горизонтальных — структурируйте данные под поэлементную обработку. На stable Rust используйте крейт packed_simd2.
SIMD обрабатывает 4-8 ценовых пар одной инструкцией, давая пропорциональное ускорение при сканировании арбитражных возможностей.
Lock-free стаканы: без мьютексов
crossbeam-skiplist и ArcSwap
При миллионах обновлений в секунду мьютекс на стакане — главный bottleneck. Concurrent skip list даёт O(log n) поиск без блокировок:
Конвейер через барьеры: стакан обновляется -> стратегия + риск (параллельно) -> логирование. Все потребители видят одно событие без копирования. BusySpin wait strategy для минимальной latency.
structAmihudEstimator {
window_days: usize,
daily_returns: VecDeque<f64>,
daily_volumes: VecDeque<f64>,
}
implAmihudEstimator {
fnilliq(&self) ->f64 {
letn = self.daily_returns.len() asf64;
if n == 0.0 { returnf64::MAX; }
self.daily_returns.iter().zip(self.daily_volumes.iter())
.map(|(r, v)| if *v == 0.0 { 0.0 } else { r.abs() / v })
.sum::<f64>() / n
}
}
Мы используем все три модели совместно: анализ глубины стакана для моментальной оценки (микросекунды), Kyle's lambda на тиковых данных для динамической коррекции (миллисекунды), Amihud — для долгосрочного мониторинга режимов ликвидности.
Три уровня моделирования: мгновенный анализ стакана, Kyle's Lambda на тиковых данных, Amihud ILLIQ на дневных.
Атомарное исполнение мультилег: type-state паттерн
Главный вызов: сделки на разных биржах не могут быть атомарными. Leg risk — заполнился один лег, а другой нет. Система типов Rust позволяет сделать невозможные переходы ошибками компиляции.
Fill-or-Kill (FOK) ордера исключают частичное заполнение, но снижают fill rate. Мы используем FOK для самого рискованного лега, а для остальных — лимитные ордера с допуском.
Ключевой инсайт: для крипто-арбитража сетевой RTT к бирже (2-4 мс) доминирует в латентном бюджете. Оптимизация внутренней обработки ниже ~10 мкс даёт убывающую отдачу. Три главных направления оптимизации:
Co-location — размещение серверов в том же AWS-регионе, что и биржа (ap-northeast-1 для Binance, us-east-1 для Coinbase)
Параллельная отправка всех легов — сокращает общее время исполнения мультилег-арбитража
Устранение джиттера — ноль GC, ноль аллокаций на горячем пути, привязка к ядрам, huge pages для снижения TLB misses
В следующей части серии мы погрузимся в мониторинг и операционное управление продакшен-системой: распределённые трейсы, метрики, алерты и post-mortem анализ исполнений.
@software{soloviov2026complexarbitrageexecutionrust,
author = {Soloviov, Eugen},
title = {Исполнение сложного арбитража на Rust: от наносекунд до атомарных мультилег},
year = {2026},
url = {https://marketmaker.cc/ru/blog/post/complex-arbitrage-execution-rust},
version = {0.1.0},
description = {Как выжать максимум производительности из Rust для исполнения мультилег-арбитража: io_uring, lock-free стаканы, LMAX Disruptor, SIMD, type-state машины и arena-аллокаторы.}
}