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