Алгоритмическая торговля на максимумах: Что делать, когда сопротивления просто нет?

Разбираем реальную проблему торгового робота и даем рабочее решение на Clojure

Привет, коллега. Ты когда-нибудь сталкивался с ситуацией, когда твой торговый алгоритм, идельно работающий в середине графика, вдруг глупеет на самых свежих данных? Цена растет, обновляет максимумы, а твой бот беспомощно моргает светодиодиками — потому что не видит ни одного уровня сопротивления впереди. Сегодня мы разберем эту проблему и реализуем изящное решение.

Представь классическую стратегию торговли по уровням. Берём график, находим ключевые максимумы и минимумы, строим горизонтальные линии. Всё просто: покупаем у поддержки, продаём у сопротивления, стоп за ближайшим экстремумом. Стратегия работает десятилетиями.

Но что происходит, когда цена выходит в зону, где этих самых уровней… просто нет?

Реальная проблема: рынок без потолка

Возьмём конкретный пример — фьючерс, который начал торговаться несколько месяцев назад. Вот его полная история:

История фьючерса на таймфрейме 1Д.

Видишь? Цена дважды тестировала дно, потом начала рост с коррекциями — классическая картина. Но сейчас она находится выше всей своей истории. Выше предыдущих максимумов!

Где мы сейчас? На зоне вопроса — то уже конец роста или середина? А может быть все только начинается и мы в начале пути?

Классическая проблема: Слепая зона алгоритма

Представь: твой алгоритм определяет уровни поддержки и сопротивления через кластеризацию исторических экстремумов. Все работает отлично, пока цена движется внутри проторгованного диапазона. Но что происходит, когда фьючерс обновляет абсолютный максимум за несколько лет?

Правильно. Алгоритм честно отрабатывает свою логику:

;; Ищем сопротивление выше текущей цены
(->> resistance-levels
     (filter #(> (:level %) current-price))
     (sort-by :level <)
     first) 
;; => nil

Вот он, корень проблемы. Сопротивления нет. nil. И вся тщательно выстроенная стратегия летит к чертям. Бот не может выставить тейк-профит, не видит целей для пробоя — он слеп.

Философия решения: Если истории нет, её нужно спрогнозировать

Когда у нас нет исторических данных, мы переходим от анализа к прогнозированию. Нам нужен инструмент, который даст обоснованную гипотезу о том, где цена может встретить сопротивление.

Мы не будем использовать ATR (который слишком статичен) или случайные числа. Вместо этого возьмем проверенный временем инструмент — уровни расширения Фибоначчи.

Почему именно Фибо? Потому что это не просто абстрактные линии, а математическая проекция текущего импульса. Мы берем последнее значимое движение (от поддержки до текущей цены) и проецируем его в будущее.

Реализация: Clojure-модуль для виртуальных уровней

Создадим модуль app.trader.fibonacci:

(ns app.trader.fibonacci)

(def fib-levels {:level-0.618 0.618
                 :level-1.000 1.000
                 :level-1.618 1.618
                 :level-2.618 2.618})

(defn calculate-fibonacci-extensions [low-price high-price]
  "Рассчитываем уровни Фибоначчи для расширения движения"
  (let [distance (- high-price low-price)]
    (->> fib-levels
         (map (fn [[level-key multiplier]]
                [level-key (+ high-price (* distance multiplier))]))
         (into {}))))

(defn create-virtual-resistance [current-support-level current-price]
  "Создаем виртуальный уровень сопротивления на основе Фибоначчи"
  (when current-support-level
    (let [support-price (:level current-support-level)
          fib-levels (calculate-fibonacci-extensions support-price current-price)]
      {:level (get fib-levels :level-1.618)  ;; Уровень 161.8% - самый важный
       :type :fibonacci-virtual
       :source-price support-price
       :fib-levels fib-levels})))

Всего 20 строк кода — а сколько пользы!

Интеграция в существующую систему

Теперь модифицируем наш основной levels-calculator:

(ns app.trader.levels-calculator
  (:require [app.trader.fibonacci :as fib]))

(defn calculate [cluster-data current-price config]
  (let [;; ... старая логика поиска уровней ...
        historical-resistance (->> resistance-levels ... first)
        
        ;; НОВАЯ ЛОГИКА: проверяем, нужно ли создать виртуальный уровень
        virtual-resistance (when (or (nil? historical-resistance) 
                                     (< (:strength historical-resistance) 0.5))
                             (fib/create-virtual-resistance nearest-support 
                                                          current-price))]

    {:support nearest-support
     :resistance (or virtual-resistance historical-resistance)  ;; Виртуальный уровень имеет приоритет!
     :resistance-type (if virtual-resistance :virtual :historical)
     :all-support support-levels
     :all-resistance resistance-levels}))

Ключевой момент — оператор or, который гарантирует, что у нас всегда будет валидный уровень сопротивления.

Результат в бою

Посмотрим на реальные логи работы системы:

INFO: Current price: 44.14
INFO: Historical resistance: nil
INFO: Creating virtual Fibonacci resistance
INFO: Virtual resistance: 46.50228

{
  :support {:level 42.68, ...},
  :resistance {
    :level 46.50228,           ;; ВИРТУАЛЬНЫЙ УРОВЕНЬ!
    :type :fibonacci-virtual,
    :source-price 42.68
  },
  :resistance-type :virtual
}

Алгоритм больше не слеп! Даже на исторических максимумах у него есть ориентир — уровень 46.50, рассчитанный как 161.8% расширение Фибоначчи от последней значимой поддержки.

Полное техническое задание на алгоритм

🎯 Цель: Создать устойчивого бота, способного определять флэт и торговать на пробой с фильтрацией по объему.


✅ ЭТАП 1: Умные уровни с отслеживанием состояния

1.1. Базовый расчет Фибо

  • Функция calculate-fibonacci-extensions

1.2. Интеграция в калькулятор

  • Проверка на (nil? или weak?) исторического сопротивления
  • Создание виртуального уровня с меткой :type :fibonacci-virtual

1.3. ✅ ДОБАВЛЯЕМ НОВЫЙ ПУНКТ: Механизм отслеживания состояния

  • Создать Level State Manager – отдельный компонент, который:

  • Хранит все активные уровни (и исторические, и виртуальные) с их статусом (:hypothesis, :active,:confirmed,:broken`).

  • На каждом новом баре (или тике) проверяет, достигла ли цена уровня.

  • Обновляет статус на основе реакции цены (объем, паттерны).

  • Модифицировать levels-calculator: чтобы он не просто возвращал уровень, а регистрировал новый виртуальный уровень в Level State Manager.

1.4. Тестирование сценариев (РАСШИРЯЕМ)

  • Тест 1: Историческое сопротивление -> используется без изменения статуса.
  • Тест 2: Виртуальный уровень создан -> проверяется его регистрация в менеджере состояний.
  • Тест 3: Цена достигает виртуального уровня -> статус меняется с :hypothesis на :confirmed или :broken.

Выход с этапа: Алгоритм всегда возвращает валидный уровень сопротивления.


✅ ЭТАП 2: Логика анализа рынка и генерации сигналов

Цель: Научить бота определять момент для входа.

  • 2.1. Функция detect-flat?

  • Рассчитывает ширину канала между ближайшими поддержкой и сопротивлением.

  • Возвращает true, если ширина меньше заданного % от цены (например, < 2%).

  • 2.2. Функция detect-squeeze? (Определение сжатия)

  • Анализирует, сужается ли диапазон флэта с течением времени (понижающиеся максимумы и повышающиеся минимумы внутри канала).

  • 2.3. Функция check-breakout-volume (Фильтр объема)

  • На входе: current-price, key-level (уровень пробоя), 1m-volume-data.

  • Сравнивает объем текущей/прошлой свечи со средним объемом за последние N периодов.

  • Возвращает true, если объем аномально высокий (например, > 150% от среднего).

Выход с этапа: Алгоритм может отличить флэт от тренда и оценить силу пробоя.


✅ ЭТАП 3: Механика исполнения заявок («Стратегия пробоя»)

Цель: Реализовать корректную логику выставления и управления ордерами.

  • 3.1. Реализовать логику выставления отложенных ордеров

  • При обнаружении флэта автоматически выставляются:

  • Buy Stop на resistance-level + 0.1%

  • Sell Stop на support-level - 0.1%

  • 3.2. Реализовать отмену противостоящего ордера

  • Как только один стоп-ордер активируется (например, Buy Stop), второй ордер (Sell Stop) должен быть немедленно отменен.

  • 3.3. Интегрировать фильтр объема

  • ВАЖНО: Активация стоп-ордера (пробой уровня) должна быть проверена по объему.

  • Если check-breakout-volume возвращает false (пробой на слабом объеме), позиция не открывается (или сразу закрывается по малому убытку).

Выход с этапа: Бот может автоматически “дежурить” на границах флэта и входить в пробой с фильтрацией ложных срабатываний.


✅ ЭТАП 4: Интеграция и тестирование

  • 4.1. Соединить все модули в единый рабочий поток

  • 4.2. Написать интеграционные тесты на исторических данных

  • Сценарий “Успешный пробой флэта на высоком объеме”.

  • Сценарий “Ложный пробой (отскок от уровня)”.

  • 4.3. Запуск на демо-счете

  • Тестирование в реальных условиях без риска.

  • Сбор статистики и тонкая настройка параметров.


🎉 ЭТАП 5: Победа

  • Открыть шомпанское и праздновать!

Что дальше? От гипотезы к подтверждению

Важно понимать: виртуальный уровень — это гипотеза, а не истина в последней инстанции. Наша следующая задача — научить бота проверять эти гипотезы.

В следующей статье мы реализуем:

  • Механизм отслеживания состояния уровней (гипотеза/подтвержден/пробит)
  • Фильтрацию ложных пробоев через анализ объема
  • Стратегию торговли на пробой с использованием отложенных стоп-ордеров

Выводы

Проблема отсутствия сопротивления на исторических максимумах — не приговор для алгоритмической торговли. С помощью уровней Фибоначчи мы можем:

  • Избежать простоев бота в самых прибыльных движениях
  • Получить обоснованные цели для тейк-профитов
  • Сохранить единую логику работы алгоритма в любых условиях

Код готов к использованию и легко адаптируется под любую торговую платформу. Главное — понять принцип: когда история молчит, мы используем математическую проекцию текущей рыночной динамики.

А ты сталкивался с подобными проблемами в своих торговых роботах? Делись опытом в комментариях!

👁‍🗨 0