«Робот за 5 000 рублей со 300 % годовых» вызывает у меня улыбку: простого торгового робота можно написать за вечер. Я Андрей Соколов, десять лет пишу бэкенд на Go, и недавно сделал такого бота на скользящих средних. Торговый робот — программа, которая по заданным правилам сама выставляет заявки: когда войти, когда выйти, где стоп. Моего я прогнал по реальным часовым свечам BTC/USDT с комиссией 0,1 % и проскальзыванием 0,05 % за сторону. Получилось минус 3 % за двенадцать с половиной суток, а просто лежащий биткоин за то же время потерял 0,7 %.
Повод — скриншот в одном чате: робот за 5 000 рублей, кривая капитала уходит вверх, подпись про 300 % в год. Продавца называть не буду, такие скриншоты друг на друга похожи. Мне захотелось понять, что можно выжать из самой простой идеи, если считать честно. Считать, как выяснилось, неприятно. Данные у меня до полуночи 12 октября 2026 года, и это учебный эксперимент, а не торговая идея.
Как устроен простой торговый робот
Правила у моего бота на две строки. Считаем две скользящие средние по цене закрытия, быструю на 10 свечей и медленную на 30. Быстрая выше медленной — сидим в биткоине, быстрая под медленной — сидим в деньгах. Сигнал считается по закрытию свечи, а сделка идёт по открытию следующей: иначе бот «видит» цену, по которой в жизни не успел бы торговать. В первой версии я торговал по открытию той же свечи, и результат выглядел лучше (+0,5 % до комиссий вместо −0,9 %). Заглянуть в будущее на одну свечу — самый дешёвый способ получить красивую кривую. Прототипы я пишу на Python, на Go переписываю то, что стоит оставить.
import json
def load(name):
return json.load(open(f"data/{name}.json")) # [[ts, open, high, low, close, volume], ...]
def sma(xs, n):
out, s = [None] * len(xs), 0.0
for i, x in enumerate(xs):
s += x
if i >= n:
s -= xs[i - n]
if i >= n - 1:
out[i] = s / n
return out
def backtest(bars, fast, slow, lo, hi, fee=0.001, slip=0.0005):
"""Сигнал по закрытию свечи i, сделка по открытию i+1. Торгуем свечи [lo, hi)."""
close = [b[4] for b in bars]
f, s = sma(close, fast), sma(close, slow)
eq, entry, start, trades = 1.0, None, 1.0, []
for i in range(max(lo, slow - 1), hi - 1):
px = bars[i + 1][1]
if f[i] > s[i] and entry is None: # быстрая выше медленной: покупаем
entry, start = px * (1 + slip), eq
eq *= 1 - fee
elif f[i] <= s[i] and entry is not None: # иначе выходим в деньги
eq *= px * (1 - slip) / entry * (1 - fee)
trades.append(eq / start - 1)
entry = None
if entry is not None: # в конце закрываем по последней цене
eq *= bars[hi - 1][4] * (1 - slip) / entry * (1 - fee)
trades.append(eq / start - 1)
return eq - 1, tradesЭто весь «робот»: функция backtest. Комиссия 0,1 % за каждую сторону — привычный уровень обычного тарифа, у вас может быть другой. Проскальзывание 0,05 % — допущение, что заявка исполняется чуть хуже цены открытия. Круг, вход плюс выход, обходится в 0,3 %. В живой торговле добавятся API брокера, ошибки сети, частичные исполнения. Ничего этого в коде нет.
Что показал бэктест на 300 часовых свечах
Данные — 300 часовых свечей BTC/USDT с 13:00 по Москве 29 сентября до полуночи 12 октября 2026 года, двенадцать с половиной суток. Запуск и вывод:
bars = load("BTCUSDT_1h")
for fee, slip in [(0, 0), (0.001, 0), (0.001, 0.0005)]:
r, trades = backtest(bars, 10, 30, 0, len(bars), fee, slip)
print(f"комиссия {fee:.1%}, проскальзывание {slip:.2%}: {r:+.2%}, сделок {len(trades)}, в плюсе {sum(t > 0 for t in trades)}")комиссия 0.0%, проскальзывание 0.00%: -0.90%, сделок 7, в плюсе 3
комиссия 0.1%, проскальзывание 0.00%: -2.28%, сделок 7, в плюсе 2
комиссия 0.1%, проскальзывание 0.05%: -2.96%, сделок 7, в плюсе 2Комиссия и проскальзывание забирают 0,3 % за круг. Семь сделок — это 2,1 п. п., из −0,90 % получается −2,96 %. До издержек идея в плюс тоже не выходит. Из семи сделок в плюсе две. Первая — №5: вход 3 октября в 22:00, выход 5-го, +0,54 %. Вторая, +0,75 %, — №7: код закрыл её по последней цене, потому что данные кончились.
Нагляднее всего серия 1 октября: бот трижды зашёл в монету, в 10:00, 15:00 и 21:00 (на графике это сделки №2–№4), и трижды вышел в минус: −1,14 %, −0,14 % и −0,75 %. Скользящие средние зарабатывают на тренде и теряют на пиле. За эти две недели цена ходила в диапазоне от 80,4 до 87,3 тысячи, толком не выбрав направления, и пилы хватило на всё.

Переобучение торгового робота: подбираем параметры, которые «сработали»
Числа 10 и 30 я взял с потолка. Что будет, если подобрать лучшие? Это и есть переобучение, и я прогнал его по-честному. Перебрал быструю среднюю от 2 до 30 свечей и медленную от быстрой плюс 3 до 120: 2 958 пар. Оставил те, где на обучающем куске есть хотя бы пять сделок. Обучающий кусок — первые 200 свечей, проверочный — последние 100. Лучшую пару выбираем по обучению и смотрим, что с ней на проверке.
def sweep(bars, cut, fee=0.001, slip=0.0005):
"""Перебираем пары на первых cut свечах, лучшую проверяем на остальных."""
rows = []
for fast in range(2, 31):
for slow in range(fast + 3, 121):
r, trades = backtest(bars, fast, slow, 0, cut, fee, slip)
if len(trades) >= 5:
rows.append((r, fast, slow))
rows.sort(reverse=True)
r, fast, slow = rows[0]
test = [backtest(bars, f, s, cut, len(bars), fee, slip)[0] for _, f, s in rows]
best_test = backtest(bars, fast, slow, cut, len(bars), fee, slip)[0]
return rows, fast, slow, r, best_test, testimport statistics
for name in ("BTCUSDT_1h", "BTCUSDT_4h"):
bars = load(name)
rows, fast, slow, r, best_test, test = sweep(bars, 200)
print(name, f"пар с >=5 сделок: {len(rows)}, лучшая {fast}/{slow}: {r:+.2%} на обучении, {best_test:+.2%} на проверке")
print(" в плюсе на обучении:", sum(x[0] > 0 for x in rows), "на проверке:", sum(t > 0 for t in test), "медиана проверки:", f"{statistics.median(test):+.2%}")BTCUSDT_1h пар с >=5 сделок: 408, лучшая 5/19: +1.33% на обучении, -0.40% на проверке
в плюсе на обучении: 2 на проверке: 273 медиана проверки: +0.35%
BTCUSDT_4h пар с >=5 сделок: 438, лучшая 3/6: +11.35% на обучении, -2.71% на проверке
в плюсе на обучении: 72 на проверке: 11 медиана проверки: -3.16%Лучшая пара на часовиках, 5/19, на обучении даёт +1,33 % за шесть сделок. Выглядит как находка. С учётом издержек в плюсе на обучении оказались только две пары из 408, и я бы выбрал именно её. На проверочных 100 свечах она показала −0,40 %. Медиана остальных вариантов на тех же свечах +0,35 %, в плюсе были 273 из 408, а выбранную пару обошли 328 пар из 408. Пара, выбранная по прошлому, проиграла среднему варианту.
Сто часов — это четыре дня, шум. Чтобы не делать выводов по четырём дням, взял те же 300 свечей, но четырёхчасовые: 50 дней, с 23 августа по 11 октября. Лучшая на обучении пара 3/6 заработала +11,35 % за 15 сделок, чуть больше, чем просто держать биткоин на тех же 200 свечах (+9,0 %). На проверке она дала −2,71 %. В плюсе на обучении были 72 пары из 438, на проверке — 11. Здесь выбранная пара вышла лучше большинства соседей (её обошли 170 из 438, медиана проверки −3,16 %), но всё равно в минусе.
| 1 час, 12 суток | 4 часа, 50 суток | |
|---|---|---|
| Пар с пятью и более сделками | 408 из 2 958 | 438 из 2 958 |
| Лучшая пара на первых 200 свечах | 5/19 | 3/6 |
| Её результат на обучении | +1,33 % | +11,35 % |
| Она же на последних 100 свечах | −0,40 % | −2,71 % |
| Пар, обошедших её на проверке | 328 из 408 | 170 из 438 |
| Пар в плюсе на обучении | 2 из 408 | 72 из 438 |
| Пар в плюсе на проверке | 273 из 408 | 11 из 438 |
| Просто держать на проверке | +0,50 % | −0,55 % |
Без комиссий та же пара 3/6 дала бы на проверке −0,05 %. Идея не работала и до комиссий, комиссии её только добили. Переобучение без терминов выглядит так: из почти четырёх сотен вариантов вы выбираете тот, что лучше всех подошёл под прошлое, и подошёл он под шум. Это тест, который проходит на моём ноутбуке и падает в CI: он подогнан под одну среду.
Робот за 5 000 рублей: вопросы, которые я задал бы продавцу
После этого эксперимента мне есть чем ответить на скриншот из чата. Вот вопросы к продавцу; без ответов на них я не стал бы смотреть даже на цену.
- Какой период и сколько сделок? Меньше нескольких сотен сделок — анекдот, статистики тут нет. У меня их семь.
- На каком куске подбирали параметры и на каком проверяли? Если показан один график на всей истории, это обучающий кусок.
- Какие комиссии и проскальзывание заложены? У меня 0,3 % за круг съели больше двух пунктов за двенадцать дней.
- Есть ли выписка по реальному счёту? Картинку можно нарисовать за вечер.
- Можно ли увидеть правила? Если нет, вы покупаете чёрный ящик, и сам продавец, возможно, не знает, что в нём.
Есть ещё простая экономика: продавец зарабатывает на продаже, и эта выручка не зависит от того, прибыльный робот или нет. Поэтому я смотрю на ответы на вопросы выше, а не на красоту кривой на лендинге.
Что в моём эксперименте упрощено
В нём нет фандинга и спреда, частичных исполнений, больших объёмов, при которых проскальзывание растёт, и проверки на разных режимах рынка: рост, падение, боковик. Главное — нет достаточной выборки. Для серьёзной проверки нужны годы данных и сотни сделок. Пока их нет, любой результат, мой в том числе, я воспринимаю как иллюстрацию механики, о рынке он ничего надёжного не говорит. Код после этого лежит у меня в папке с экспериментами. К реальному счёту он не подключён.






Комментарии
Чтобы участвовать в обсуждении, или .