Как стать автором
Поиск
Написать публикацию
Обновить

Как делать грамотный бэктест и анализ торговой стратегии: метрики, сигналы, сделки и выводы в алготрейдинге

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров190

📜 Введение

В мире алготрейдинга многие уверены: «если стратегия показывает хорошие сигналы — значит она прибыльна». Увы, это заблуждение. Чтобы стратегия действительно была рабочей, нужно грамотно провести бэктест — не просто «посчитать винрейт», а рассчитать ключевые метрики: Profit Factor, Sharpe Ratio, Max Drawdown, и лишь потом делать выводы.

В этой статье я покажу, как протестировать стратегию по реальным историческим данным, сохранить сигналы, симулировать сделки, рассчитать метрики — и понять, стоит ли стратегия того, чтобы торговать ей на бирже.

Все примеры — на Python. В предыдущей статье я показывал написание бота и бектест кода, который просто выдаёт сухие сделки и реализованную прибыль в %. Однако существует много разных параметров и переменных стратегии, без которых ее использование обычно убыточно.

📦 1. Получаем исторические данные и сигналы

Сначала загружаем данные и рассчитываем сигналы стратегии. Я не буду сейчас подробно описывать конкретную стратегию бота, функции и индикаторы. Остановимся на логике именно бектеста и анализе данных.

Начнём с импорта, инициализируем клиент биржи:

from binance.client import Client
import pandas as pd

client = Client()

Теперь нам нужно подгрузить исторические свечи, чтобы понять как стратегия отрабатывала на истории. В нашем бектесте будем анализировать 100000 5м свечей. Это около 350 дней, этого хватит для тестирования скальп стратегии.

Так как бинанс позволяет загружать до 1500 свечей за раз, то будем подгружать и добавлять новые свечи в массив постепенно.

def fetch_klines_paged(symbol='BTCUSDT', interval='5m', total_bars=100000, client=None):
    if client is None:
        client = Client()

    limit = 1000
    data = []
    end_time = None  # самый последний бар (новейшая точка)

    while len(data) < total_bars:
        bars_to_fetch = min(limit, total_bars - len(data))

        try:
            klines = client.futures_klines(
                symbol=symbol,
                interval=interval,
                limit=bars_to_fetch,
                endTime=end_time
            )
        except Exception as e:
            print("Ошибка Binance API:", e)
            break

        if not klines:
            break

        data = klines + data  # prepend! — старые свечи добавляем в начало
        end_time = klines[0][0] - 1  # сдвиг назад по времени
        time.sleep(0.2)

    df = pd.DataFrame(data, columns=[
        'timestamp', 'open', 'high', 'low', 'close', 'volume',
        'close_time', 'quote_asset_volume', 'number_of_trades',
        'taker_buy_base', 'taker_buy_quote', 'ignore'
    ])
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
    df[['open','high','low','close','volume']] = df[['open','high','low','close','volume']].astype(float)
    df = df.drop_duplicates('timestamp')
    df = df.sort_values('timestamp').reset_index(drop=True)
    return df

Функция вернет нам dataframe со всеми нужными свечами, с ним мы и будем работать.

2. Создаём условия для входа в позицию и начинаем бектест

Начнём с создания условий для входа:

def check_long_condition(row):
    return (
        row['CSI'] > 0 and
        row['CSI'] > row['CSI_prev'] and
        isinstance(row['cluster_id'], str) and row['cluster_id'].startswith('bull') and
        row['close'] > row['ema_fast'] > row['ema_slow']
        )
    
def check_short_condition(row):
    return (
        row['CSI'] < 0 and
        row['CSI'] < row['CSI_prev'] and
        isinstance(row['cluster_id'], str) and row['cluster_id'].startswith('bear') and
        row['close'] < row['ema_fast'] < row['ema_slow']
        )

Они могут быть любые, я для примера вставил часть условий для своего бота.

Перейдём к основной логике запуска. Для начала получим наш df (массив 100000 свечей) и добавим на него наши нужные индикаторы:

if __name__ == "__main__":
    client = Client()
    df = fetch_klines_paged('BTCUSDT', '5m', total_bars=100000, client=client)
    print(f"Получено свечей: {len(df)}")
    df = compute_indicators(df) #функция вида  df['RS'] = 1+rs ;  return df

3. Добавляем нашу логику к свечам

Теперь нужно рассмотреть все свечи и проверять на них наши сигналы. Разделим long и short сигналы в разные блоки, чтобы потом было удобнее анализировать статистику. Также сохраним все наши сделки в .csv таблицу. При необходимости можно будет сделать анализ конкретных сигналов.

    df['long_signal'] = df.apply(check_long_condition, axis=1)
    df['short_signal'] = df.apply(check_short_condition, axis=1)

    long_signals = df[df['long_signal']]
    short_signals = df[df['short_signal']]

    # 👇 Сохраняем сделки в CSV
    long_signals.to_csv('long_signals.csv', sep=';', index=False)
    short_signals.to_csv('short_signals.csv', sep=';', index=False)
    df.to_csv('all_data.csv', sep=';', index=False)

🧪 4. Симулируем сделки и считаем прибыль

Теперь нужно понять: какую прибыль дали сигналы. Для простоты мы входим по цене close на сигнальной свече и выходим через 3 свечи (т.е. 15 минут спустя). Выход через 15минут был выведен на основе большого опыта работы через 5м сигналы путём смены интервала через циклы.

Для начала напишем функцию счёта профита:

def calculate_profits(df, signal_col='long_signal', direction='long', exit_after=3):
    trades = []
    for i in range(len(df) - exit_after):
        if df.iloc[i][signal_col]:
            entry_price = df.iloc[i]['close']
            exit_price = df.iloc[i + exit_after]['close']
            if direction == 'long':
                profit = exit_price - entry_price
                profit_pct = (exit_price / entry_price - 1) * 100
            else:
                profit = entry_price - exit_price
                profit_pct = (entry_price / exit_price - 1) * 100
            trades.append({
                'entry_time': df.iloc[i]['timestamp'],
                'exit_time': df.iloc[i + exit_after]['timestamp'],
                'entry_price': entry_price,
                'exit_price': exit_price,
                'profit': profit,
                'profit_%': profit_pct
                'close': entry_price  # ← добавляем сюда цену входа
            })
    return pd.DataFrame(trades)

И применим это к нашим сигналам в основном блоке:

long_trades = calculate_profits(df, signal_col='long_signal', direction='long', exit_after=3)
short_trades = calculate_profits(df, signal_col='short_signal', direction='short', exit_after=3)

long_trades.to_csv('long_trades.csv', sep=';', index=False)
short_trades.to_csv('short_trades.csv', sep=';', index=False)

📊 5. Анализ метрик стратегии

И вот теперь — самое важное: какие метрики мы можем рассчитать и что они значат?

✅ Win Rate

Процент прибыльных сделок: насколько стратегия вообще угадывает?

win_rate = len(df[df['profit'] > 0]) / len(df) * 100

✅ Profit Factor

Сумма всей прибыли / сумма всех убытков. Главное — выше 1.

profit_factor = df[df['profit'] > 0]['profit'].sum() / abs(df[df['profit'] < 0]['profit'].sum())

✅ Sharpe Ratio

Стабильность стратегии: насколько равномерно зарабатываем?

sharpe = df['profit'].mean() / df['profit'].std()

✅ Max Drawdown

Максимальное падение капитала — нужен для оценки риска.

equity = df['profit'].cumsum()
rolling_max = equity.cummax()
drawdown = equity - rolling_max
max_dd = drawdown.min()

Также добавим классическую статистику в виде дельты и профита:

    total_profit = trades_df['profit'].sum()
    avg_price = trades_df['close'].mean()
    delta = total_profit / avg_price if avg_price != 0 else float('nan')

✅ Всё вместе

Функция анализа:

def analyze_trades(trades_df):
    if trades_df.empty:
        print("Нет сделок для анализа.")
        return

    win_trades = trades_df[trades_df['profit'] > 0]
    loss_trades = trades_df[trades_df['profit'] <= 0]

    win_rate = len(win_trades) / len(trades_df) * 100
    profit_factor = win_trades['profit'].sum() / abs(loss_trades['profit'].sum()) if not loss_trades.empty else float('inf')

    # Sharpe Ratio (annualized)
    daily_returns = trades_df['profit']
    mean_return = daily_returns.mean()
    std_return = daily_returns.std()
    sharpe_daily = mean_return / std_return if std_return != 0 else 0
    sharpe_annual = sharpe_daily * np.sqrt(252)

    # Equity и просадка
    equity_curve = trades_df['profit'].cumsum()
    rolling_max = equity_curve.cummax()
    drawdown = equity_curve - rolling_max
    max_drawdown = drawdown.min()

    # Общий профит и дельта
    total_profit = trades_df['profit'].sum()
    avg_price = trades_df['close'].mean()
    delta = total_profit / avg_price if avg_price != 0 else float('nan')

    print("\n📊 Результаты анализа сделок:")
    print(f"Всего сделок: {len(trades_df)}")
    print(f"Win Rate: {win_rate:.2f}%")
    print(f"Profit Factor: {profit_factor:.2f}")
    print(f"Sharpe Ratio (annualized): {sharpe_annual:.2f}")
    print(f"Max Drawdown: {max_drawdown:.2f}")
    print(f"📈 Общий профит: {total_profit:.2f}")
    print(f"⚖️ Дельта (profit / avg BTC): {delta:.4f}")

Ну и теперь вызовем нашу функцию:

print("LONG TRADES:")
analyze_trades(long_trades)

print("\nSHORT TRADES:")
analyze_trades(short_trades)

Всю логику можно оформить как модуль, а сами сигналы и сделки анализировать и визуализировать, например, в Jupyter, Excel или Python-дашборде.

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

📈 Анализ лонг-сделок:

📊 Результаты анализа сделок:
Всего сделок: 417
Win Rate: 76.98%
Profit Factor: 6.89
Sharpe Ratio (annualized): 3.13
Max Drawdown: -1313.60
📈 Общий профит: 98254.50
⚖️ Дельта (profit / avg BTC): 1.1034

📉 Анализ шорт-сделок:

📊 Результаты анализа сделок:
Всего сделок: 336
Win Rate: 75.00%
Profit Factor: 8.02
Sharpe Ratio (annualized): 3.09
Max Drawdown: -1442.50
📈 Общий профит: 86625.40
⚖️ Дельта (profit / avg BTC): 0.9710

Всего сделок: 753 (417 лонгов + 336 шортов)

Разбор показателей

1. Win Rate: качество сигналов

Win Rate около 75% — очень высокий показатель. Это обеспечивается частотой сделок и маленьким временем удержания позиции. Выход на одну позицию меньше, но и винрейт очень высок. Однако высокая точность входов здесь достигается благодаря тщательному фильтрованию сигналов и вероятно жёсткому контролю риска.

2. Profit Factor (PF): соотношение прибыли к убыткам

PF = 6.89 (лонг) и 8.02 (шорт) — очень высокие значения. В индустрии PF > 2 уже считается хорошим. Значение около 7–8 говорит о том, что суммарный профит почти в 7–8 раз превышает убытки.

Вывод: Выдерживается дисциплина по стоп-лоссам и грамотный выбор целей для тейк-профитов.

3. Sharpe Ratio: риск и доходность

Sharpe Ratio около 3— это очень хороший результат, обычно фондовые стратегии имеют значения 1–2, а 3–4 считаются очень хорошими. Высокий Sharpe говорит о низкой волатильности дохода и стабильности.

Что важно: Для криптовалютного рынка с высокой волатильностью это крайне позитивный сигнал, что стратегия адекватно управляет рисками.

4. Max Drawdown: максимальное проседание капитала

Дровдаун в районе 1300–1400 условных единиц. Это менее 2% чистого движения на фиксированную позицию, тоесть 20% на всю позицию с плечом. (Т.к. средняя цена биткойна около 80к). Это отличный показатель для этой тс.

Общий профит и дельта

  • Общий профит порядка 98 000 для лонга и 86 000 для шорта — отличные результаты.

  • Дельта (profit / avg BTC) около 1 для каждого направлению — говорит о том, что на каждую среднюю единицу базового актива стратегия приносит примерно такую же сумму прибыли, что свидетельствует о высокой доходности позиции. Но не надо забывать, что прибыль идёт на полную позицию. Поэтому вложив 100$ выход будет 1000$ (на байбит фикс. плечо 10х).

Комиссии

Часть важность коммисий и проскальзывания опускается при анализе статистики. Но в HTF и скальп стратегии без этого никуда. Покажу анализ нашей стратегии:

📌 Условия:

  • Входной капитал: $1,000

  • Плечо: 10x → значит, ты торгуешь как будто бы $10,000

  • Комиссия на сделку (открытие + закрытие): 0.1% = 0.001

  • Сделок всего: 753

🔢 Расчёт:

1. Объём одной сделки (с плечом):

1000*10 = 10000 USDT

2. Комиссия с одной сделки:

10,000×0.1% = 10$

3. Общая комиссия за 753 сделки:

10×753=7530 USDT

Так что видим что комиссионные сьели достаточно сильную часть прибыли. От этого не уйти, к сожалению, но всё же даже с учётом коммиссий видим хороший результат - 200% прибыли, т.е. 20000$-7530$=12470$ прибыли за год со вложенных всего 1000$ на позицию. (т.е. на счёте нужно иметь хотя бы чуть более 2к$, так как позиции редко, но бывает, что открываются подряд)

Заключение

Разобрали как рассчитать все необходимые показатели для понимания эффективность торговых алгоритмических систем.

Представленная торговая система демонстрирует впечатляющие показатели — высокие Win Rate, Profit Factor и Sharpe Ratio, а также управляемый Max Drawdown. Это результат продуманного алгоритма, который успешно балансирует между агрессивной доходностью и контролем риска.

Без аналитики полученной после бектеста статистика запуск ботов по трейдингу - чистое казино. Используйте стратегии с умом и всегда проводите тщательное тестирование.

Теги:
Хабы:
0
Комментарии1

Публикации

Ближайшие события