Разработка платформы бэктестинга торговых стратегий

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка платформы бэктестинга торговых стратегий
Сложная
от 1 недели до 3 месяцев
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1062
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка платформы бэктестинга торговых стратегий

Платформа бэктестинга — это инфраструктура для проверки торговых стратегий на исторических данных. Хорошая платформа не просто воспроизводит стратегию на прошлых данных, а делает это максимально реалистично: с учётом комиссий, проскальзывания, ликвидности, и защитой от data leakage.

Архитектура платформы

Data Layer — хранилище исторических данных: OHLCV свечи, tick data, order book snapshots. ClickHouse или Arctic (Python) для эффективного доступа.

Backtest Engine — ядро симуляции. Event-driven или vectorized архитектура.

Strategy Runtime — среда исполнения пользовательских стратегий с изоляцией.

Results & Reporting — расчёт метрик, визуализация P&L, сравнение стратегий.

Optimization Engine — перебор параметров (grid search, genetic algorithm, Bayesian).

Event-driven vs Vectorized

Vectorized — вся логика применяется к pandas DataFrame сразу. Быстро (NumPy операции), но сложно моделировать реальное поведение ордеров:

# Vectorized подход
import pandas as pd
import numpy as np

def backtest_ma_crossover(df: pd.DataFrame, fast: int, slow: int) -> pd.Series:
    fast_ma = df['close'].rolling(fast).mean()
    slow_ma = df['close'].rolling(slow).mean()

    # Сигналы
    signal = np.where(fast_ma > slow_ma, 1, -1)
    signal = pd.Series(signal, index=df.index)

    # Returns
    returns = df['close'].pct_change()
    strategy_returns = signal.shift(1) * returns  # shift(1) = нет look-ahead

    return strategy_returns.cumsum()

Event-driven — более реалистичная симуляция. Каждое рыночное событие (свеча, тик) обрабатывается последовательно. Можно моделировать partial fills, order slippage, margin calls:

class EventDrivenBacktester:
    def run(self, strategy: Strategy, data_feed: DataFeed) -> BacktestResult:
        portfolio = Portfolio(initial_cash=100_000)
        broker = SimulatedBroker(portfolio, slippage=0.001, commission=0.0005)

        for event in data_feed:
            if isinstance(event, MarketEvent):
                strategy.on_market_data(event)

            elif isinstance(event, SignalEvent):
                order = strategy.generate_order(event)
                broker.submit_order(order)

            elif isinstance(event, FillEvent):
                portfolio.update(event)
                strategy.on_fill(event)

        return BacktestResult(portfolio.equity_curve, portfolio.trades)

Симуляция исполнения ордеров

Реалистичная симуляция — ключевое отличие хорошего бэктестера от плохого:

class SimulatedBroker:
    def __init__(self, slippage_pct: float = 0.001, commission_pct: float = 0.0005):
        self.slippage = slippage_pct
        self.commission = commission_pct
        self.pending_orders: list[Order] = []

    def simulate_fill(self, order: Order, bar: OHLCV) -> FillEvent:
        if order.type == "MARKET":
            # Market order исполняется по следующему open + slippage
            fill_price = bar.open * (1 + self.slippage if order.side == "BUY" else 1 - self.slippage)

        elif order.type == "LIMIT":
            # Limit order исполняется если цена достигла уровня
            if order.side == "BUY" and bar.low <= order.price:
                fill_price = min(order.price, bar.open)  # консервативный fill
            elif order.side == "SELL" and bar.high >= order.price:
                fill_price = max(order.price, bar.open)
            else:
                return None  # не исполнен

        commission = fill_price * order.quantity * self.commission
        return FillEvent(
            order_id=order.id,
            fill_price=fill_price,
            quantity=order.quantity,
            commission=commission,
            timestamp=bar.timestamp,
        )

Метрики бэктеста

def calculate_metrics(equity_curve: pd.Series, trades: list[Trade]) -> BacktestMetrics:
    returns = equity_curve.pct_change().dropna()
    annual_factor = 252  # торговых дней

    # Базовые метрики
    total_return = (equity_curve.iloc[-1] / equity_curve.iloc[0]) - 1
    annual_return = (1 + total_return) ** (annual_factor / len(returns)) - 1

    # Risk-adjusted
    sharpe = returns.mean() / returns.std() * np.sqrt(annual_factor) if returns.std() > 0 else 0
    sortino = returns.mean() / returns[returns < 0].std() * np.sqrt(annual_factor)

    # Drawdown
    rolling_max = equity_curve.cummax()
    drawdown = (equity_curve - rolling_max) / rolling_max
    max_drawdown = drawdown.min()
    calmar = annual_return / abs(max_drawdown) if max_drawdown != 0 else 0

    # Trade-level метрики
    winning_trades = [t for t in trades if t.pnl > 0]
    losing_trades = [t for t in trades if t.pnl < 0]
    win_rate = len(winning_trades) / len(trades) if trades else 0

    gross_profit = sum(t.pnl for t in winning_trades)
    gross_loss = abs(sum(t.pnl for t in losing_trades))
    profit_factor = gross_profit / gross_loss if gross_loss > 0 else float('inf')

    return BacktestMetrics(
        total_return=total_return,
        annual_return=annual_return,
        sharpe_ratio=sharpe,
        sortino_ratio=sortino,
        max_drawdown=max_drawdown,
        calmar_ratio=calmar,
        win_rate=win_rate,
        profit_factor=profit_factor,
        total_trades=len(trades),
        avg_trade_pnl=sum(t.pnl for t in trades) / len(trades) if trades else 0,
    )

Защита от data leakage

Самая распространённая ошибка в бэктестинге — data leakage: использование данных будущего при принятии решений в прошлом.

Look-ahead bias — стратегия использует данные за текущий период (например, close цену) для принятия решения, которое должно быть сделано до закрытия бара.

# Неправильно: использование close той же свечи
signal = df['close'].rolling(20).mean()  # signal[-1] включает текущий close
entry_price = df['close']  # вход по тому же close

# Правильно: вход на следующий bar
signal = df['close'].rolling(20).mean().shift(1)  # shift(1) = прошлый период
entry_price = df['open']  # вход по следующему open

Survivorship bias — если тестируете на активных в данный момент символах, исключая деликтованные, результаты завышены. Используйте исторические списки индексов.

Parameter optimization leakage — если параметры оптимизировались на всех данных, а тест проводится на тех же данных — это не тест, это подгонка. Всегда выделяйте out-of-sample период.

Walk-forward Validation

def walk_forward_backtest(
    strategy_class,
    data: pd.DataFrame,
    train_period: int,  # дней
    test_period: int,   # дней
    optimization_func,
) -> list[BacktestResult]:
    results = []
    start_idx = 0

    while start_idx + train_period + test_period <= len(data):
        train_data = data.iloc[start_idx:start_idx + train_period]
        test_data = data.iloc[start_idx + train_period:start_idx + train_period + test_period]

        # Оптимизируем параметры на train данных
        best_params = optimization_func(strategy_class, train_data)

        # Тестируем на out-of-sample
        strategy = strategy_class(**best_params)
        result = run_backtest(strategy, test_data)
        results.append(result)

        start_idx += test_period

    return results

Оптимизация параметров

Grid search, genetic algorithm, Bayesian optimization — разные подходы к нахождению оптимальных параметров. Ключевой принцип: оптимизация на train, оценка на test, никогда не наоборот.

Для платформы с пользовательскими стратегиями — очередь задач (Celery, RQ) для распределённого выполнения бэктестов на нескольких воркерах. Один сложный бэктест (1 год данных, тысячи комбинаций параметров) может занимать часы — асинхронное выполнение с уведомлением по завершению обязательно.