📜 Введение
В мире алготрейдинга многие уверены: «если стратегия показывает хорошие сигналы — значит она прибыльна». Увы, это заблуждение. Чтобы стратегия действительно была рабочей, нужно грамотно провести бэктест — не просто «посчитать винрейт», а рассчитать ключевые метрики: 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. Это результат продуманного алгоритма, который успешно балансирует между агрессивной доходностью и контролем риска.
Без аналитики полученной после бектеста статистика запуск ботов по трейдингу — чистое казино. Используйте стратегии с умом и всегда проводите тщательное тестирование.
ссылка на оригинал статьи https://habr.com/ru/articles/935196/
Добавить комментарий