# Бот на пробое канала

Полный, работающий торговый бот к статье. Ничего скачивать и нигде
регистрироваться не нужно — данные лежат в папке `data`. Нужен только
Python 3.10 или новее, никаких библиотек.

```
python run_portfolio.py                    # корзина из 6 инструментов
python run_portfolio.py --leverage 3       # тот же бот, масштаб риска x3
python run_portfolio.py --split 2023-12-31 # выбор на ранних годах, проверка на поздних
python run_portfolio.py --costs 8          # а если издержки вдвое выше?
python run_backtest.py --instrument btc    # один инструмент, с журналом сделок
python run_backtest.py --worst             # 10 худших сделок
python tests/run_all.py                    # все проверки
```

## Правило

Цена вышла за максимум последних 10 баров, причём не на копейку, а с запасом
в половину среднего размаха (ATR) — покупаем на открытии следующего бара.
Стоп кладём за минимум бара, который дал сигнал. Тейк — вдвое дальше стопа.
Симметрично вниз для продажи. Одна позиция на инструмент. Бары четырёхчасовые.

## Результат

Шесть инструментов (NASDAQ, золото, серебро, газ, RTS, биткойн), равный риск
на ногу, издержки 4 б.п. на круг, 2019–2026.

| масштаб риска | доходность в год | волатильность | просадка | худший месяц | худший день |
|---|---|---|---|---|---|
| x1 | +5.8% | 4.2% | −4.0% | −2.2% | −1.6% |
| x2 | +11.7% | 8.4% | −7.9% | −4.3% | −3.3% |
| x3 | +17.8% | 12.5% | −11.7% | −6.4% | −4.9% |
| x4 | +24.0% | 16.7% | −15.5% | −8.6% | −6.6% |
| x5 | +30.2% | 20.9% | −19.1% | −10.7% | −8.2% |

Коэффициент Шарпа 1.37, месяцев в плюсе 71%, все восемь лет положительные.
2182 сделки, доля прибыльных 39.8%, профит-фактор 1.21. Самая глубокая
просадка длилась 72 дня — и это, а не проценты, обычно и решает, доживёт ли
человек до конца эксперимента.

Масштаб риска ничего не улучшает: доход и просадка растут ровно в одинаковое
число раз, Шарп остаётся 1.37 на всех строках таблицы. Это ручка «сколько
я готов терять», а не «сколько я хочу зарабатывать».

## Что проверено

**Устойчивость к издержкам.** 2 б.п. → Шарп 1.47; 4 → 1.37; 8 → 1.16;
12 → 0.95; 16 → 0.74. Деградация плавная, обрыва нет.

**Проверка на поздних годах.** Масштаб ног посчитан только по 2019–2023,
затем бот прогнан по 2024–2026: Шарп 1.39 на ранних, 1.36 на поздних.

**Тесты ловят подсаженные ошибки.** Мы намеренно ломали движок — исполняли
стоп по цене стопа при разрыве, обновляли индикаторы до принятия решения,
разрешали вход, когда цена уже ушла за стоп, — и каждый раз тесты падали.
Тест, который не падает на сломанном коде, ничего не доказывает.

Всего 21 проверка. Две из них устроены иначе остальных: они не проверяют
конкретный сценарий, а прогоняют бота по всей истории и требуют соблюдения
инварианта. `test_stop_can_never_be_profitable` — что ни один выход по стопу
не оказался прибыльным. `test_exit_price_is_never_better_than_the_market_gave` —
что движок ни разу не исполнил выход по цене лучше той, что дал рынок.
Такие тесты не знают, каким способом ошибка может появиться, и ловят любой,
включая те, которых мы ещё не придумали. Первый из них и нашёл в этом коде
единственную настоящую ошибку исполнения (см. правило 4 в `breakout/engine.py`).

## Чего здесь нет

**Секрета.** Пробой канала опубликован в 1983 году. Это образец устройства
бота, а не способ разбогатеть.

**Отправки заявок брокеру.** В бою между решением и позицией лежит сеть,
и ответ может не прийти. Это отдельная большая тема.

**Гарантии, что цифры выше повторятся.** Параметры (канал 10, запас 0.5 ATR,
тейк 2R) выбраны не из воздуха, но и не из книги: они найдены перебором на этой
же истории. Соседние наборы работают заметно хуже — например, канал 50 с тейком
1.5R даёт на ранних годах почти столько же, а на поздних разваливается. Это
значит, что часть результата выше — премия за знание ответа. Честное ожидание
от такой системы вслепую — Шарп ближе к 1.0, чем к 1.4, то есть при том же
масштабе риска x4 просадка была бы не 15%, а около 20–25%.

## Оговорки по данным

Данные приложены как есть, и в них есть три вещи, которые честнее назвать
вслух, чем спрятать.

**Склейка фьючерсных контрактов.** Ряды по газу, золоту, серебру и RTS —
непрерывные, склеенные из последовательных контрактов без поправки на разницу
цен между ними. В момент склейки в ряду возникает разрыв, которого на реальном
счёте не было. Хуже всего это в газе: 97 межбаровых скачков больше 3%, часть
из них приходится на конец месяца, то есть на переход контракта. Газ в корзине
и без того убыточен — но держать в голове, что часть его движений придумана
календарём, а не рынком, обязательно.

**Остановка торгов в RTS.** С 25 февраля по 24 марта 2022 года Московская
биржа не работала. В данных это выглядит как один разрыв в 24%. Он настоящий,
но пройти через него было нельзя ни с каким стопом.

**Разное число торговых дней.** Биткойн торгуется 365 дней в году, фьючерсы —
около 250. В `breakout/portfolio.py` это учтено: если пересчитывать дневную
волатильность в годовую по одной константе для всех, биткойн получит примерно
на 20% больше риска, чем остальные ноги, и «равный риск» перестанет быть
равным ровно в той ноге, которая заработала больше всех.

## Где здесь всё-таки подсматривается будущее

Одно место осталось, и его стоит назвать прямо: по умолчанию масштаб каждой
ноги считается по волатильности за всю историю, то есть с использованием
данных, которых в 2019 году не было. Это не ошибка кода, а свойство отчёта:
чтобы увидеть цену этого допущения, есть ключ `--split`, который считает
масштаб только по ранним годам. Разница получается небольшой (1.39 против
1.36), но проверять её нужно всегда, а не верить на слово.

## Файлы

| файл | что внутри |
|---|---|
| `breakout/core.py` | типы: свеча, сигнал, позиция, сделка |
| `breakout/indicators.py` | канал и ATR, посчитанные бар за баром |
| `breakout/strategy.py` | правило входа: канал, запас, стоп, тейк |
| `breakout/engine.py` | исполнение: разрывы, приоритет стопа, переоценка |
| `breakout/portfolio.py` | сборка корзины по равному риску |
| `breakout/metrics.py` | метрики: посделочные и календарные |
| `breakout/data.py` | загрузка и проверка котировок |
| `tests/` | проверки на заглядывание в будущее и на исполнение |
| `tools/make_data.py` | пересборка CSV из локальных выгрузок |
