Разработка системы бэктестинга с учетом ликвидности

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка системы бэктестинга с учетом ликвидности
Сложный
~3-5 дней
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1286
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    902
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1122
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    859

Разработка системы бэктестинга с учётом ликвидности

Моделирование ликвидности — наиболее сложный аспект реалистичного бэктестинга. Стратегия работает на малом капитале, но при масштабировании ликвидность рынка становится ограничивающим фактором. Крупный ордер не может быть исполнен по одной цене — он "ест" стакан, получая средневзвешенную цену (VWAP) хуже рыночной.

Проблема liquidity scaling

Типичная ошибка: бэктест показывает отличные результаты, стратегия масштабируется с $10K до $1M — и перестаёт работать. Причина: на $10K сделка составляет 0.01% дневного объёма, на $1M — 1%, и это уже заметно влияет на цену.

Правило thumb: ордер размером > 0.5–1% от дневного объёма начинает заметно двигать рынок.

Order Book Simulation

Для точного моделирования нужны order book snapshots. Реконструируем, сколько объёма доступно на каждом уровне:

import numpy as np
from dataclasses import dataclass

@dataclass
class OrderBookLevel:
    price: float
    quantity: float

@dataclass
class SimulatedOrderBook:
    symbol: str
    timestamp: int
    bids: list[OrderBookLevel]  # отсортированы по убыванию цены
    asks: list[OrderBookLevel]  # отсортированы по возрастанию цены

    def simulate_market_buy(self, quantity: float) -> tuple[float, float]:
        """Симулируем исполнение market buy order. Возвращает (avg_price, filled_qty)"""
        remaining = quantity
        total_cost = 0.0
        filled = 0.0

        for level in self.asks:
            if remaining <= 0:
                break

            fill_qty = min(remaining, level.quantity)
            total_cost += fill_qty * level.price
            filled += fill_qty
            remaining -= fill_qty

        if filled == 0:
            return 0.0, 0.0

        return total_cost / filled, filled

    def simulate_market_sell(self, quantity: float) -> tuple[float, float]:
        """Симулируем исполнение market sell order"""
        remaining = quantity
        total_proceeds = 0.0
        filled = 0.0

        for level in self.bids:
            if remaining <= 0:
                break

            fill_qty = min(remaining, level.quantity)
            total_proceeds += fill_qty * level.price
            filled += fill_qty
            remaining -= fill_qty

        if filled == 0:
            return 0.0, 0.0

        return total_proceeds / filled, filled

VWAP модель market impact

Если полных order book данных нет, используем аппроксимацию через объём свечи:

class VWAPLiquidityModel:
    """
    Модель market impact основанная на Almgren-Chriss framework.
    Предполагает линейный market impact пропорциональный participation rate.
    """

    def __init__(
        self,
        participation_rate: float = 0.05,  # % от дневного объёма на исполнение
        market_impact_coefficient: float = 0.1,  # коэффициент impact
    ):
        self.participation_rate = participation_rate
        self.impact_coeff = market_impact_coefficient

    def estimate_execution_price(
        self,
        side: str,
        order_size_usd: float,
        candle_volume_usd: float,
        candle_close: float,
        daily_volume_usd: float,
    ) -> dict:
        """
        Оцениваем цену исполнения с учётом market impact.
        Возвращает словарь с price, partial_fill, execution_time_candles
        """
        # Можем исполнить только participation_rate от объёма свечи
        max_fillable_usd = candle_volume_usd * self.participation_rate

        if order_size_usd > max_fillable_usd:
            # Придётся растягивать исполнение на несколько свечей
            candles_needed = int(np.ceil(order_size_usd / max_fillable_usd))
            effective_order = max_fillable_usd  # исполняем частями
            partial_fill = True
        else:
            candles_needed = 1
            effective_order = order_size_usd
            partial_fill = False

        # Market impact (линейная модель)
        order_fraction = effective_order / daily_volume_usd
        impact_pct = self.impact_coeff * np.sqrt(order_fraction)

        if side == 'BUY':
            execution_price = candle_close * (1 + impact_pct)
        else:
            execution_price = candle_close * (1 - impact_pct)

        return {
            'execution_price': execution_price,
            'filled_usd': effective_order,
            'partial_fill': partial_fill,
            'candles_to_complete': candles_needed,
            'slippage_pct': impact_pct * 100,
        }

Almgren-Chriss Framework

Более сложная, но и более точная модель для оценки торговых издержек крупных позиций:

class AlmgrenChrissModel:
    """
    Almgren-Chriss optimal execution model.
    Оптимальное расписание исполнения, минимизирующее
    impact cost + timing risk.
    """
    def __init__(
        self,
        daily_volume: float,
        price_volatility: float,  # дневная волатильность
        bid_ask_spread: float,
        market_depth: float,  # $/% temporary impact
        permanent_impact: float,  # постоянный impact на цену
    ):
        self.V = daily_volume
        self.sigma = price_volatility
        self.epsilon = bid_ask_spread / 2
        self.eta = market_depth
        self.gamma = permanent_impact

    def optimal_schedule(
        self,
        total_size: float,
        time_horizon: int,  # в торговых периодах
        risk_aversion: float = 1e-6,
    ) -> list[float]:
        """Возвращает оптимальные размеры частичных ордеров"""
        T = time_horizon
        N = T  # периодов = количеству частичных ордеров

        kappa_sq = (risk_aversion * self.sigma**2) / (self.eta / self.V)
        kappa = np.sqrt(max(kappa_sq, 0))

        # Оптимальная траектория
        schedule = []
        for j in range(N):
            t = j / N
            # Almgren-Chriss решение
            x_j = total_size * np.sinh(kappa * (1 - t)) / np.sinh(kappa)
            if j > 0:
                trade_j = schedule[-1] - x_j if j > 0 else total_size - x_j
                schedule.append(trade_j)

        return schedule

Масштабируемость стратегии

Важная аналитика для принятия решений об инвестировании:

def analyze_capacity(
    strategy_backtest: BacktestResult,
    volume_data: pd.DataFrame,
    participation_rate: float = 0.05,
) -> pd.DataFrame:
    """Анализируем как меняются результаты стратегии при разных размерах капитала"""
    capital_levels = [10_000, 50_000, 100_000, 500_000, 1_000_000, 5_000_000]
    results = []

    for capital in capital_levels:
        # Масштабируем все ордера пропорционально капиталу
        scale_factor = capital / strategy_backtest.initial_capital

        adjusted_returns = []
        for trade in strategy_backtest.trades:
            order_size = trade['size_usd'] * scale_factor
            avg_daily_vol = volume_data.loc[trade['date'], 'volume_usd']

            # Рассчитываем дополнительный slippage от масштаба
            participation = order_size / avg_daily_vol
            extra_slippage = 0.1 * np.sqrt(participation)  # square-root impact model

            adjusted_pnl = trade['pnl'] * scale_factor - order_size * extra_slippage
            adjusted_returns.append(adjusted_pnl / capital)

        adjusted_sharpe = np.mean(adjusted_returns) / np.std(adjusted_returns) * np.sqrt(252)
        results.append({
            'capital': capital,
            'sharpe': adjusted_sharpe,
            'annual_return_pct': np.mean(adjusted_returns) * 252 * 100,
        })

    return pd.DataFrame(results)

Результат такого анализа — кривая "капитал vs доходность". Стратегия сохраняет эффективность до определённого порога, после которого market impact нивелирует преимущество. Этот порог — максимальная ёмкость стратегии (strategy capacity).