Привет, Хабр!
Если вы увлечены трейдингом и хотите автоматизировать анализ рынка по концепции Smart Money Concept, то в этом вам может помочь собственноручно написанный индикатор. Написание индикаторов и систем по концепту smart mpney(ict) - часто довольно сложная история.
В этой статье я разберу, как создать такой индикатор в Pine Script версии 6 шаг за шагом. Мы пройдемся по коду, выделяя ключевые фрагменты с объяснениями, чтобы вы могли не только применить его на TradingView, но и понять логику, доработать или даже интегрировать в свои стратегии. Давайте нырнем в детали, начиная с основ.
Весь код есть на github.
Выглядеть наш индикатор минимально будет следующим образом:

Отрисовка уровней и свингов - его ключевая задача. А исходя из этого мы уже сможет работать с позициями.
Настройка индикатора и входные параметры
Первый шаг — объявление индикатора и его параметров. Это позволяет пользователям кастомизировать поведение без редактирования кода.
Вот фрагмент кода для инициализации:
//@version=6 indicator("Smart Money Structure Analyzer", overlay=true, max_labels_count=500) // ===== INPUTS ===== swingPeriod = input.int(3, "Swing Period", minval=3, maxval=21) showFractals = input.bool(false, "Show Fractals") showStructure = input.bool(false, "Show Structure Lines") showBOS = input.bool(true, "Show Break of Structure") showCHoCH = input.bool(true, "Show Change of Character") showMitigation = input.bool(false, "Show Mitigation Levels")
Здесь мы используем indicator с параметром overlay=true, чтобы индикатор накладывался на график цены, и max_labels_count=500 для ограничения меток, чтобы избежать перегрузки. Входные данные — это swingPeriod для длины окна свингов (от 3 до 21, чтобы захватывать разные таймфреймы), и булевы флаги для визуализации: от фракталов (свингов) до уровней смены характера.
По умолчанию BOS и CHoCH включены, так как они являются ключевыми факторами структуры, а остальные - опциональны, чтобы не перегружать чарт.
Выявление свингов: основа анализа
Свинги — это пики и впадины, вокруг которых строится структура. Мы реализуем их детекцию аккуратно, чтобы избежать ложных сигналов.
Рассмотрим функцию swingHigh():
swingHigh() => middle = math.floor(swingPeriod / 2) if bar_index < swingPeriod false else // Центральная свеча - наивысшая в окне isHighest = high[middle] == ta.highest(high, swingPeriod)[middle] // Проверяем lower highs слева leftCondition = true for i = 1 to middle leftCondition := leftCondition and high[middle] > high[middle - i] // Проверяем lower highs справа rightCondition = true for i = 1 to middle rightCondition := rightCondition and high[middle] > high[middle + i] isHighest and leftCondition and rightCondition
Эта функция проверяет, является ли центральная свеча в окне свингом: она должна иметь наивысший high, и соседние high слева/справа должны быть ниже (фланкирование). Мы используем ta.highest для сравнения, а циклы for обеспечивают строгую проверку. Если баров меньше периода, возвращаем false - это предотвращает ошибки на начале графика. Аналогично работает swingLow(), только для low.
Затем мы присваиваем currentSwingHigh = swingHigh() и currentSwingLow = swingLow() для использования на каждом баре.
Хранение свингов для исторического анализа
Чтобы анализировать структуру, нужно помнить прошлые свинги. Мы используем массивы для этого.
Фрагмент добавления свингов:
var float[] swingHighPrices = array.new_float() var int[] swingHighBars = array.new_int() var float[] swingLowPrices = array.new_float() var int[] swingLowBars = array.new_int() if currentSwingHigh middle = math.floor(swingPeriod / 2) swingPrice = high[middle] swingBar = bar_index - middle // Проверяем, чтобы это был действительно новый свинг (не рядом с предыд��щим) shouldAdd = true if array.size(swingHighBars) > 0 lastBar = array.get(swingHighBars, 0) if math.abs(swingBar - lastBar) < swingPeriod shouldAdd := false if shouldAdd array.unshift(swingHighPrices, swingPrice) array.unshift(swingHighBars, swingBar)
Мы создаем массивы для цен и баров свингов. При обнаружении свинга рассчитываем цену и индекс (с учетом смещения), но добавляем проверку shouldAdd: если свинг слишком близко к предыдущему, игнорируем — это фильтрует шум. array.unshift добавляет в начало, сохраняя порядок от новых к старым. Ограничиваем размер до 20 с array.pop, чтобы оптимизировать производительность. То же для low. Это умный способ хранить только релевантную историю.
Анализ структуры рынка
Теперь переходим к сути: определение тренда на основе свингов.
Функции для ключевых уровней:
getLastHigherLow() => if array.size(swingLowPrices) < 2 na else lastLow = array.get(swingLowPrices, 0) prevLow = array.get(swingLowPrices, 1) lastLow > prevLow ? lastLow : na // Аналогично для getLastLowerHigh(), getLastLowerLow(), getLastHigherHigh()
Эти функции сравнивают последние два свинга: если последний low выше предыдущего, это Higher Low (HL). Аналогично есть и higher high (hh), а также lower low и lower high для нисходящей структуры. Мы получаем lastHL, lastLH и т.д. Возвращаем na, если данных мало — это предотвращает ложные выводы.
Затем состояние структуры:
getStructureState() => hasEnoughData = array.size(swingHighPrices) >= 2 and array.size(swingLowPrices) >= 2 if not hasEnoughData "INSUFFICIENT_DATA" else hhExists = not na(lastHH) llExists = not na(lastLL) hlExists = not na(lastHL) lhExists = not na(lastLH) if hhExists and hlExists "UPTREND" else if llExists and lhExists "DOWNTREND" else if (hhExists and llExists) or (hlExists and lhExists) "CONSOLIDATION" else "UNDEFINED"
Здесь мы классифицируем: HH + HL = аптренд, LL + LH = даунтренд, смешанное = консолидация. Это основано на последовательных свингах, что отражает реальное поведение "умных денег".
Детекция BOS и CHoCH: сигналы сдвига
BOS — прорыв структуры, CHoCH — подтверждение смены.
Для BOS вниз:
bosDown() => if not na(lastHL) isBearish = close < open closeBelowHL = close < lastHL and low < lastHL isBearish and closeBelowHL else false
Прорыв, когда медвежья свеча закрывается ниже HL. Симметрично для bosUp() выше LH. Ключ: проверка на бычью/медвежью свечу дополняет фильтр.
Для CHoCH:
chochDown() => if hasBOSDown and currentStructure == "UPTREND" not na(lastLL) else false
После BOS в аптренде ждем LL для подтверждения даунтренда.
Уровни смены характера:
getMitigationLevel() => if hasBOSDown and not na(lastHL) lastHL * 1.002 else if hasBOSUp and not na(lastLH) lastLH * 0.998 else na
Небольшой отступ (0.2%) для потенциального возврата — нужно для грамотных входов.
Визуализация и алерты
Визуалы помогают быстро читать график.
Для свингов:
plotshape(swingHighToShow, "Swing High", shape.xcross, location.abovebar, color=color.new(color.red, 0), size=size.small, offset = -math.floor(swingPeriod / 2))
Крестики с offset для точного позиционирования.
Линии структуры:
if showStructure if not na(lastHL) if na(lastHLLine) lastHLLine := line.new(bar_index - 50, lastHL, bar_index, lastHL, color=color.new(color.blue, 70), width=1, style=line.style_dashed) else line.set_xy1(lastHLLine, bar_index - 50, lastHL) line.set_xy2(lastHLLine, bar_index, lastHL)
Динамические линии, обновляемые с line.set_xy — они тянутся в реальном времени.
Сигналы BOS/CHoCH — треугольники/круги, смена характера — линия с точками. Инфо-лейбл на последнем баре показывает статус с цветом по тренду.
Алерты:
if hasBOSDown alert("BOS DOWN detected at " + str.tostring(close), alert.freq_once_per_bar_close)
Уведомления для алерт-реакции.
Заключение: почему этот индикатор стоит попробовать
Этот индикатор превращает абстрактную SMC в рабочий инструмент. Сделали аналитику структуры и красивую отрисовку. Попробуйте на TradingView, поэкспериментируйте с периодами, и увидите, как он помогает ловить трендовые развороты. Безусловно, тут есть много возможностей для доработок.
Всегда рад услышать ваши мысли и предложения!
