Создаем алгоритм маркет-мейкинга для криптовалютных пар с использованием модели Авелланеда-Стоикова
Привет, друзья! Сегодня я расскажу о том, как создать алгоритм маркет-мейкинга для криптовалютных пар USD+/wETH и USD+/cbbtc. Мы будем использовать модель Авелланеда-Стоикова (A-S) и дополним её алгоритмом Reinforcement Learning (PPO) для динамической оптимизации спредов. Звучит сложно? Не волнуйтесь, я разобью всё на понятные шаги, чтобы даже начинающий разработчик смог разобраться.
Что такое маркет-мейкинг и зачем он нужен?
Маркет-мейкинг - это стратегия, при которой трейдер одновременно выставляет ордера на покупку и продажу актива, зарабатывая на спреде (разнице между ценами). В DeFi-пространстве маркет-мейкеры играют ключевую роль, обеспечивая ликвидность и уменьшая проскальзывание для других участников рынка.
Представьте, что вы - продавец на рынке, который всегда готов купить товар чуть дешевле рыночной цены и продать чуть дороже. Ваша прибыль - разница между ценами покупки и продажи. Но есть нюанс: если цена резко пойдет в одну сторону, вы можете накопить слишком много товара или, наоборот, остаться с пустыми руками.
Модель Авелланеда-Стоикова: математика на службе трейдинга
Модель A-S - это математический подход к определению оптимальных цен для маркет-мейкинга. Её главное преимущество в том, что она учитывает не только текущую рыночную цену, но и размер вашей позиции (инвентаря), волатильность рынка и риск-аппетит.
Основные формулы модели:
δ_a = S_t + (1/γ) * ln(1 + γ/k) + q_t * σ² * T
δ_b = S_t - (1/γ) * ln(1 + γ/k) - q_t * σ² * T
где:
δ_aиδ_b- цены продажи и покупкиS_t- текущая рыночная ценаγ- параметр риска (чем выше, тем шире спред)k- интенсивность ордеров на рынкеq_t- текущий инвентарьσ- волатильностьT- временной горизонт
Особенности onchain-торговли
Когда мы переносим алгоритм в блокчейн, появляются дополнительные сложности:
- Latency (задержка) - в блокчейне транзакции не мгновенны, и цена может измениться до исполнения ордера
- Gas costs - каждая транзакция требует оплаты комиссии сети
- Особенности AMM/PMM - механики пулов ликвидности отличаются от традиционных бирж
Давайте посмотрим, как учесть эти факторы в нашем алгоритме.
Шаг 1: Настраиваем окружение и собираем данные
Первым делом нам нужно настроить среду для получения рыночных данных. Для этого мы будем использовать Binance API для получения текущих цен и глубины книги ордеров.
std::tuple MarketMaker::get_binance_data(const std::string& pair) {
// В реальном коде здесь будет запрос к API Binance
// Возвращаем: mid_price, bid, ask, bid_volume, ask_volume
double mid_price = 2000.0;
double bid = mid_price - 1.0;
double ask = mid_price + 1.0;
double bid_volume = 10.0;
double ask_volume = 8.0;
return {mid_price, bid, ask, bid_volume, ask_volume};
}
Также нам понадобятся onchain-метрики, такие как стоимость газа и задержка сети:
std::pair MarketMaker::get_onchain_metrics() {
// В реальном коде здесь будет запрос к Ethereum ноде
// Возвращаем: gas_price (wei), latency (seconds)
return {50e9, 12.0};
}
Шаг 2: Реализуем базовую модель A-S
Теперь реализуем расчет спредов по модели A-S:
std::pair MarketMaker::calculate_spreads(double S_t, double sigma, double k, double q_t) {
// Формула Avellaneda-Stoikov
double spread_term = (1.0 / gamma_) * log(1.0 + gamma_ / k);
double inventory_term = q_t * sigma * sigma * T_;
double delta_a = S_t + spread_term + inventory_term; // Ask price
double delta_b = S_t - spread_term - inventory_term; // Bid price
return {delta_a, delta_b};
}
Обратите внимание на inventory_term. Если у нас положительный инвентарь (много актива), то цена продажи снижается, а цена покупки ещё сильнее снижается, чтобы стимулировать продажи и ограничить покупки. И наоборот для отрицательного инвентаря.
Шаг 3: Адаптируем модель для onchain-торговли
Теперь нужно учесть особенности блокчейна. Начнем с задержки (latency):
double MarketMaker::adjust_price_with_latency(double S_t, double sigma, double latency) {
// Моделируем случайное изменение цены из-за задержки
double latency_adjustment = utils::normal_dist(0.0, sigma * std::sqrt(latency));
return S_t + latency_adjustment;
}
Здесь мы используем модель случайного блуждания: чем выше волатильность и длиннее задержка, тем сильнее может измениться цена до исполнения ордера.
Теперь учтем стоимость газа:
double MarketMaker::calculate_gas_cost(double gas_price, double trade_size) {
const double GAS_LIMIT_PER_ORDER = 100000; // Примерное значение для ордера
return (gas_price * GAS_LIMIT_PER_ORDER * trade_size) / 1e18; // Конвертируем wei в ETH
}
И наконец, адаптируем спреды под особенности PMM-пулов:
std::pair MarketMaker::adjust_spreads_for_pmm(double S_t, double delta_a, double delta_b, double pool_depth) {
// Упрощенная модель PMM: корректируем спреды в зависимости от глубины пула
const double MIN_POOL_DEPTH = 10.0;
double depth_factor = std::max(pool_depth, MIN_POOL_DEPTH) / MIN_POOL_DEPTH;
// Уменьшаем спреды при большей глубине пула
double spread_reduction = 1.0 / std::sqrt(depth_factor);
double mid_price = (delta_a + delta_b) / 2;
double new_delta_a = mid_price + (delta_a - mid_price) * spread_reduction;
double new_delta_b = mid_price - (mid_price - delta_b) * spread_reduction;
return {new_delta_a, new_delta_b};
}
Шаг 4: Управление инвентарем
Для отслеживания и управления инвентарем создадим простой класс:
class InventoryManager {
public:
InventoryManager() : inventory_(0.0) {}
void update_inventory(double size, bool is_buy) {
inventory_ += is_buy ? size : -size;
}
double get_inventory() const {
return inventory_;
}
private:
double inventory_;
};
Шаг 5: Объединяем всё в один алгоритм
Теперь объединим все компоненты в единый алгоритм маркет-мейкинга:
void MarketMaker::step(double S_t, double sigma, double k, double latency, double gas_cost, double trade_size) {
// Получаем текущий инвентарь
double current_inventory = inventory_.get_inventory();
// Рассчитываем спреды на основе текущих рыночных условий и инвентаря
auto [delta_a, delta_b] = calculate_spreads(S_t, sigma, k, current_inventory);
auto [adjusted_delta_a, adjusted_delta_b] = adjust_spreads_for_onchain(S_t, delta_a, delta_b, latency, sigma, gas_cost, trade_size);
// Генерация независимой рыночной цены
double market_price = S_t + utils::normal_dist(0.0, sigma);
// Определяем, должны ли произойти сделки на основе рыночной цены и спредов
bool is_buy = (market_price = adjusted_delta_a);
// Выполняем сделки и обновляем инвентарь
if (is_buy) {
inventory_.update_inventory(trade_size, true);
std::cout reset();
// Выполнить действие и получить новое состояние, награду и флаг завершения
std::tuple, double, bool> step(const std::array& action);
private:
// Получить текущее состояние среды
std::vector get_state() const;
MarketMaker& mm_;
double current_inventory_;
double current_profit_;
int current_step_;
int max_steps_;
// Текущие параметры рынка
double mid_price_;
double sigma_;
double latency_;
double pool_depth_;
std::mt19937 rng_;
};
Состояние нашей среды - это вектор из текущей цены, инвентаря, волатильности, задержки сети и глубины пула. Действие - это вектор из спредов и объемов для покупки и продажи.
Теперь реализуем функцию награды:
double reward = profit_term - inventory_risk - gas_cost;
Где:
profit_term- прибыль от сделокinventory_risk- штраф за большой инвентарь (риск)gas_cost- затраты на газ
Наконец, обучим PPO-агента:
void PPOTrainer::train(int episodes) {
for (int ep = 0; ep states;
std::vector actions;
std::vector rewards;
while (true) {
// Получаем действие из политики
auto action_probs = policy_net_->forward(torch::tensor(state));
auto action = action_probs.multinomial(1);
// Выполняем шаг в среде
auto [next_state, reward, done] = env_.step(action);
// Сохраняем переход
states.push_back(torch::tensor(state));
actions.push_back(action);
rewards.push_back(reward);
if (done) break;
state = next_state;
}
// Обновляем политику PPO
update_policy(states, actions, rewards);
}
}
Шаг 7: Тестирование и визуализация
Для тестирования нашего алгоритма создадим простую симуляцию:
int main() {
// Используем T = 300 секунд, как указано в задаче
MarketMaker mm(0.1, 300.0);
// Симуляция исторических данных для волатильности
std::vector prices = {2000.0};
double S_t = 2000.0;
double trade_size = 1.0;
double initial_sigma = 0.05; // 5% волатильность
for (int i = 0; i < 300; ++i) {
std::cout << "Step " << i + 1 << ": ";
// Получение данных (заглушки)
auto [mid_price, bid_ask] = mm.get_binance_data("USD+/wETH");
auto [gas_cost, latency] = mm.get_onchain_metrics();
// Добавляем случайное движение цены для симуляции реального рынка
S_t = mid_price + utils::normal_dist(0.0, mid_price * 0.01);
// Вычисление волатильности
double sigma = mm.calculate_volatility(prices, 5);
if (sigma < 0.01) sigma = initial_sigma;
// Интенсивность ордеров (заглушка)
double k = 5.0;
mm.step(S_t, sigma, k, latency, gas_cost, trade_size);
// Обновление цены для следующего шага
S_t += utils::normal_dist(0.0, S_t * 0.02);
prices.push_back(S_t);
}
return 0;
}
Что дальше?
Наш алгоритм маркет-мейкинга готов, но есть множество направлений для улучшения:
- Подключение к реальным API: заменить заглушки на реальные запросы к Binance API и Ethereum ноде
- Улучшение модели волатильности: использовать GARCH или другие продвинутые модели
- Расширение PPO: добавить больше параметров в состояние и действие
- Оптимизация газа: стратегии для минимизации затрат на газ
- Мультиактивная стратегия: расширение на несколько пар одновременно
Заключение
Мы создали алгоритм маркет-мейкинга, который учитывает особенности onchain-торговли и использует как классическую модель A-S, так и современные методы RL. Этот подход позволяет адаптироваться к изменяющимся рыночным условиям и максимизировать прибыль при контролируемом риске.
Конечно, в реальной торговле есть множество дополнительных факторов, которые нужно учитывать, но наш алгоритм даёт хорошую основу для дальнейшего развития. Главное помните: в алгоритмической торговле важна не только математика, но и тщательное тестирование, мониторинг и постоянная оптимизация.
Надеюсь, эта статья помогла вам лучше понять принципы маркет-мейкинга и вдохновила на создание собственных алгоритмов. Удачи в торговле!
Цитирование
@software{soloviov2025marketmakingavellanedastoikov,
author = {Soloviov, Eugen},
title = {Создаем алгоритм маркет-мейкинга для криптовалютных пар с использованием модели Авелланеда-Стоикова},
year = {2025},
url = {https://marketmaker.cc/ru/blog/post/market-making-avellaneda-stoikov},
version = {0.1.0},
description = {Пошаговое руководство по созданию алгоритма маркет-мейкинга для криптовалютных пар USD+/wETH и USD+/cbbtc с использованием модели Авелланеда-Стоикова и PPO. Особенности onchain-торговли, управление инвентарём, RL-обучение.}
}
MarketMaker.cc Team
Количественные исследования и стратегии