Разработка индикатора ATR
Разработка индикатора ATR: Пограничный случай между простым и сложным
В этой статье я хочу рассказать о разработке индикатора Average True Range (ATR) — индикатора, который заставил меня пересмотреть границы между "простыми" и "сложными" индикаторами в моей библиотеке Taljure.
Почему ATR — особенный индикатор?
ATR измеряет волатильность рынка, но его реализация содержит несколько интересных особенностей:
- Требует структурных данных (бары с high, low, close)
- Имеет рекуррентную формулу как EMA
- Использует вспомогательную функцию (True Range)
- Имеет специальный случай для period=1
Архитектурные решения
1. Вспомогательная функция calculate-tr
Первое важное решение — вынесение расчета True Range в отдельную функцию:
(defn- calculate-tr "Вычисляет истинный диапазон (True Range) для одного бара" [prev-close bar] (let [h (:high bar) l (:low bar) c (:close bar) prev-close (or prev-close c) ; Умное использование or для первого бара tr1 (- h l) tr2 (Math/abs (- h prev-close)) tr3 (Math/abs (- l prev-close))] (max tr1 tr2 tr3)))
- Изоляция сложности — основная функция
atr
остается чистой - Тестируемость —
calculate-tr
можно тестировать отдельно - Повторное использование — True Range может пригодиться elsewhere
2. Обработка специального случая period=1
Для period=1 ATR превращается в простой диапазон:
(= 1 period) (->> bars (map #(- (:high %) (:low %))) (map #(Math/abs (double %))) last)
Инсайт: Специальные случаи часто упрощают вычисления и улучшают производительность.
3. Рекуррентная формула ATR
Основная формула использует рекуррентное вычисление как EMA:
(reduce (fn [prev tr] (double (/ (+ (* prev (dec period)) tr) period))) initial-atr remaining-trs)
Красота: Та же элегантность, что и в EMA, но с другой математикой.
Почему я поместил ATR в "simple"?
Это был сознательный и неочевидный выбор. Вот мои доводы:
Аргументы ЗА размещение в simple:
- Математическая простота — в основе лежит макс(3 значения)
- Отсутствие сложных зависимостей — не зависит от других индикаторов
- Широкая распространенность — базовый индикатор волатильности
- Прозрачность алгоритма — логику легко понять и проверить
Аргументы ПРОТИВ:
- Требует структурных данных — в отличие от простых числовых последовательностей
- Имеет рекуррентную природу — как "advanced" индикаторы
- Нужна вспомогательная функция — усложняет архитектуру
Компромиссное решение:
Подводные камни реализации
1. Проблема: Обработка первого бара
Как рассчитать TR для первого бара, если нет предыдущего close?
Решение: Умное использование or
:
prev-close (or prev-close c) ; Используем текущий close если предыдущего нет
2. Проблема: Производительность на больших данных
Рекуррентный reduce
может быть не оптимальным для очень больших временных рядов.
Решение: Я оставил читаемость, но отметил возможность оптимизации:
;; Для оптимизации можно использовать transient и persistent! ;; но оставил reduce для читаемости
3. Проблема: Валидация структуры баров
Что если бар не содержит нужных ключей?
Решение: Добавил проверки в тестах, но в продакшене нужно больше валидации:
;; В идеале добавить: (when (or (not (every? #(contains? % :high) bars)) (not (every? #(contains? % :low) bars)) (not (every? #(contains? % :close) bars))) (throw (Exception. "Некорректная структура баров")))
Тестирование
Тесты для ATR особенно важны из-за сложной логики:
Тест граничных случаев
(testing "Период 1 возвращает абсолютную разницу между максимумом и минимумом" (let [bars [{:high 100.0 :low 90.0 :close 95.0}]] (is (= 10.0 (atr bars 1)))))
Тест недостаточных данных
(testing "Недостаточное количество баров вызывает исключение" (let [bars [{:high 100.0 :low 90.0 :close 95.0}]] (is (thrown? IllegalArgumentException (atr bars 2)))))
Тест корректности расчета
(testing "Правильный расчет ATR" (let [bars [{:high 50.0 :low 40.0 :close 45.0} {:high 55.0 :low 45.0 :close 50.0}]] (is (number? (atr bars 2))) (is (pos? (atr bars 2)))))
Ключевые особенности
- Границы между "simple" и "advanced" размыты — классификация субъективна
- Вспомогательные функции — мощный инструмент для управления сложностью
- Специальные случаи часто упрощают реализацию
- Структурные данные требуют особого подхода к валидации
- Рекуррентные формулы универсальны для многих индикаторов
Почему ATR все же отправился в simple
В итоге я решил, что ATR уйдет в simple потому что:
- Концептуальная простота — измерение диапазона цен
- Отсутствие композиции — не строится на других индикаторах
- Прозрачность алгоритма — можно понять без глубокой математики
Заключение: Гибкая классификация вместо жестких правил
Разработка ATR научила меня, что классификация индикаторов — это не жесткая система, а гибкий инструмент.
- ✅ Концептуальная понятность
- ✅ Отсутствие сложных зависимостей
- ✅ Простота реализации
- ✅ Широта применения
ATR проходит по всем этим критериям, несмотря на некоторые технические сложности.
Финал: ATR остался в simple
, но с пометкой о его "продвинутых" особенностях в документации.
Исходный код доступен на GitFlic.