Jump to content

Recommended Posts

Posted

Аукционная теория в коде: торговый бот на Volume Profile, POC и walk-forward отборе монет


Алготрейдинг постепенно вырастает из эпохи «пересечений скользящих средних». Классические индикаторы вроде RSI, MACD и EMA — это производные от цены, и они почти всегда запаздывают. Эти сигналы видят все: их обрабатывают маркет-мейкеры, HFT-фонды и десятки тысяч розничных ботов. Преимущество, которое размазано на всех, перестаёт быть преимуществом.

Гораздо интереснее выглядят системы, работающие с самой структурой рынка: объёмом, ликвидностью, зонами принятия и отвержения цены. Именно к этому классу относится аукционная теория — взгляд на рынок как на непрерывный аукцион, где цена всё время ищет справедливый уровень.

В этой статье разберём полноценного торгового бота, построенного вокруг этой идеи. Система строит объёмный профиль (Volume Profile), находит POC в консолидациях, ждёт подтверждающего импульса, торгует ретесты уровней, а перед этим ещё и сама раз в сутки прогоняет walk-forward отбор монет — торгует только то, что было прибыльным на недавней истории.

Отдельно подчеркну то, ради чего половина этой архитектуры вообще существует: бэктест здесь устроен так, чтобы ему можно было верить. Тем же кодом, что считает историю, работает и live. Заглядывание в будущее исключено по построению, а не «вроде бы учтено».

Весь код выложен на GitHub — как обычно, крайне советую читать статью параллельно с исходниками, здесь я разбираю в основном логику и нюансы, а не каждую строчку.


Почему аукционная теория, а не «линии на графике»

Рынок — это аукцион. В каждый момент покупатели и продавцы торгуются за цену, и объём показывает, где именно они согласились на сделку. Там, где объём концентрируется, находится зона принятой стоимости. Там, где объёма почти нет, — зона, которую рынок отверг и быстро проскочил.

Центральная величина здесь — POC (Point of Control), ценовой уровень с максимальным наторгованным объёмом. Это самая «справедливая» цена внутри рассматриваемого участка, к которой рынок тянется снова и снова.

Логика движения в аукционной теории простая:

  • цена накапливается в зоне (консолидация вокруг POC);
  • затем происходит выход и импульс — рынок ищет ликвидность выше или ниже;
  • после чего часто возвращается к своему POC, чтобы перепроверить его, — и вот здесь появляется точка входа.

Ключевое отличие от рисования уровней вручную в том, что POC — это не субъективная линия, а статистика реального объёма. Мы не гадаем, «где сильный уровень», мы его вычисляем.


Общая архитектура системы

Логически конвейер представляет собой каскад фильтров: сырые свечи на входе, торговый сигнал на выходе. Каждая ступень отсекает лишнее, и до генерации сигнала доживают только по-настоящему качественные структуры.

ВСЕ СВЕЧИ СИМВОЛА
      ↓
Понятная консолидация      (узкий диапазон + концентрированный POC)
      ↓
Валидация импульсом        (выход из диапазона на >= высоту диапазона)
      ↓
Ретест уровня + реакция    (возврат к POC и подтверждение)
      ↓
Фильтр качества (RR)       (цель — ближайший сильный непротестированный POC)
      ↓
СИГНАЛ (long / short)

Код разбит на независимые модули — каждый отвечает за свой слой:

Файл Назначение
vprofile.py Объёмный профиль: POC, Value Area (VAL/VAH)
strategy.py Каузальный движок стратегии (общий для бэктеста и live)
data_feed.py Свечи Binance / BingX, вселенная символов, кэш CSV
bingx_client.py Подпись запросов BingX, баланс, плечо, маркет-ордера с TP/SL
backtest.py Симуляция, портфель, метрики, отчёты (CSV/JSON/график)
selector.py Ежедневный отбор: бэктест топ-100 CMC → прибыльные монеты
main.py Live: сканирование альтов, исполнение, риск-контроль
smoke_test.py Офлайн-проверка всего конвейера на синтетике

Такое разделение — не эстетика ради эстетики. Оно позволяет прогонять одну и ту же стратегию в трёх режимах (smoke-тест, бэктест, live), не дублируя логику. А значит, то, что вы протестировали, и то, что торгует бот, — это буквально один и тот же код.


Ядро: объёмный профиль

Всё начинается с vprofile.py. Здесь объём каждой свечи распределяется по ценовым бинам пропорционально тому, какую часть бина перекрывает диапазон свечи [low, high].

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

Реализовано векторно, без циклов по свечам:

left  = np.maximum(edges[None, :-1], lo_n)
right = np.minimum(edges[None, 1:],  hi_n)
w = np.clip(right - left, 0.0, None) / (hi_n - lo_n)
hist += (w * volume[norm][:, None]).sum(axis=0)

Дальше всё просто. POC — это бин с максимальным объёмом:

p = int(hist.argmax())
poc = 0.5 * (edges[p] + edges[p + 1])

А Value Area (по умолчанию 70% объёма) набирается расширением от POC в обе стороны, каждый раз добавляя более «тяжёлый» соседний бин, пока не наберётся нужная доля. Границы этой области — VAL и VAH.

Отдельно храним долю POC в общем объёме — она пригодится, чтобы отличать «понятный» профиль от размазанного.


Понятная консолидация

Не всякий POC достоин внимания. Нам нужна не просто область с объёмом, а чёткое накопление — то, что в аукционной теории и является базой для будущего движения.

Движок сканирует историю скользящими окнами трёх длин — 24, 48 и 96 баров (на часовом таймфрейме это примерно 1, 2 и 4 дня). Окно проходит отбор только если выполняются все условия сразу:

  • диапазон узкий — высота окна <= max_range_atr · ATR и <= max_range_pct от цены (по умолчанию 6·ATR и 10%);
  • внутри нет тренда|close_end − close_start| / range <= max_net_ratio (0.35), иначе это уже движение, а не накопление;
  • POC концентрированный — объём POC-бина как минимум в min_concentration раз больше, чем при равномерном распределении;
  • POC не у края — он должен быть в середине диапазона, а не прижат к границе окна.

Метрика концентрации здесь элегантная. Если бы объём был размазан равномерно, доля каждого бина была бы 1/bins, и произведение poc_share · bins равнялось бы единице. Поэтому:

conc = poc\_share \times bins

Значение conc = 2.5 буквально означает «в POC-бине в 2.5 раза больше объёма, чем было бы при равномерном распределении». Просто, наглядно и не зависит от числа бинов.

conc = prof.poc_share * bins
if conc < cfg.min_concentration:
    continue
pos = (prof.poc - win_lo) / rng
if pos < cfg.poc_edge_margin or pos > 1 - cfg.poc_edge_margin:
    continue

Прошедшее окно живёт как кандидат и обновляется, пока условие выполняется. Как только цена уходит из диапазона — кандидат «замораживается» и переходит на следующую стадию.


Валидация импульсом

И вот здесь принципиальный момент, который отличает эту систему от наивного «нашли зону — торгуем зону».

Сама по себе консолидация — это ещё не сигнал. Мало ли где рынок потоптался. Нам нужно доказательство, что этот уровень действительно значим для крупного капитала.

Таким доказательством выступает импульс. После выхода цены из диапазона у системы есть impulse_horizon баров (12), чтобы увидеть, как экстремум уходит от границы диапазона на величину не меньше высоты самого диапазона:

extreme\_move \geq impulse\_mult \times range

Только если это произошло — POC превращается в валидный уровень с зафиксированным направлением импульса. Если рынок вяло выполз за границу и завис — уровень не рождается, кандидат просто отбрасывается.

thr = cfg.impulse_mult * cand.rng
up = w.hi_ext >= thr
dn = w.lo_ext >= thr
if up:
    self._create_level(cand, +1, i)

Фактически мы отбираем только те зоны, из которых рынок ушёл резко и осознанно. Слабые, «ренджевые» POC отсеиваются на этой ступени.


Ретест и реакция: движок входа

Теперь у нас есть валидный уровень, и мы ждём его перепроверки. Вся логика реализована как небольшой конечный автомат:

Кандидат → Импульс → Уровень → Ретест → Реакция → Сигнал

Когда цена возвращается в зону уровня (± touch_tolerance_atr · ATR), запускается тест. Направление сделки определяется стороной подхода — тем, откуда пришла цена по последним закрытиям: подошли сверху → ищем лонг, подошли снизу → шорт.

Дальше уровень должен себя проявить. Возможны три исхода:

  • реакция подтверждена — в течение reaction_window баров закрытие уходит от уровня в сторону сделки на >= reaction_buffer_atr · ATR. Это наш вход;
  • тест провален — закрытие уходит против на >= invalidation_atr · ATR (0.9·ATR). Уровень пробит, ликвидность забрана, сигнал неактуален;
  • истёк срок — реакции так и не случилось.
failed  = (lvl.price - c) * d >= cfg.invalidation_atr * atr
success = (c - lvl.price) * d >= cfg.reaction_buffer_atr * atr
expired = (i - p.start) >= cfg.reaction_window

Уровень не бессмертен. Он отрабатывает максимум max_touches тестов (2), живёт ограниченное число баров (level_ttl), а если цену вокруг него «пилит» — при max_crosses пересечениях уровень объявляется мёртвым. Логика та же, что и у трейдера за терминалом: уровень, который перестал держать, торговать бессмысленно.

Отдельно система различает два типа сетапа. Если ретест случился в первые local_retest_window баров после импульса (выход из накопления → сразу ретест POC) — это kind = local, самый «свежий» и, как правило, самый качественный вход. Поздние возвраты помечаются kind = retest. Какие из них торговать — задаётся в конфиге, можно оставить только локальные.


Стоп, цель и фильтр качества

Здесь система ведёт себя как дисциплинированный трейдер, а не как генератор случайных входов.

Стоп ставится за экстремум ретеста с запасом stop_pad_atr · ATR — то есть за ту точку, где рынок в последний раз отказался идти дальше.

Цель выбирается не «на глаз» и не фиксированным процентом. Система ищет ближайший по направлению сделки сильный (концентрация >= strong_concentration) и ещё не протестированный POC — то есть следующую значимую зону ликвидности, к которой рынок логично потянется.

cands = [L2 for L2 in self.levels
         if L2 is not lvl and L2.alive and not L2.tested
         and L2.conc >= cfg.strong_concentration
         and (L2.price - entry) * d > 0.1 * atr]

И только теперь — фильтр качества сделки. Считаем соотношение риск/прибыль:

RR = \frac{(target - entry)\cdot dir}{(entry - stop)\cdot dir}

Если ближайшая сильная цель даёт RR < min_rr (1.5) — сделка пропускается, даже при идеальном во всём остальном сетапе. Если подходящего непротестированного POC по направлению нет — цель выставляется по fallback_rr (2.5). Слишком «узкие» сделки, где риск меньше min_risk_atr · ATR, тоже отсекаются.

Это критично. Огромная часть сигналов у новичков убыточна не потому, что вход плохой, а потому, что соотношение риск/прибыль изначально отрицательное. Здесь система физически не даст себе войти в плохую по RR сделку.


Почему этому бэктесту можно верить

А теперь — главное. То, из-за чего большинство «прибыльных» стратегий из интернета умирают в реальной торговле.

Самая страшная ошибка алготрейдинга — заглядывание в будущее (looking forward). Алгоритм принимает решение, используя данные, которых в реальном времени у него быть не могло. На истории такая система выглядит как грааль. В реальном рынке — сливает депозит.

Обычно эта ошибка прячется в мелочах: человек считает сигнал по незакрытой свече, или индикатор подсматривает значение на баре вперёд. Достаточно одного такого места, чтобы весь бэктест стал фальшивкой.

В этой архитектуре проблема решена радикально — на уровне дизайна. Движок каузальный: метод on_bar() получает бары строго по одному и ничего не знает о будущем.

def on_bar(self, o, h, l, c, v, ts) -> List[Signal]:
    """Обработать ЗАКРЫТЫЙ бар. Возвращает список сигналов (обычно 0..1)."""
    self.i += 1
    ...

Обратите внимание на комментарий: движок работает только с закрытыми барами. Никакой текущей, ещё формирующейся свечи. Именно тут новички чаще всего и ломают достоверность, торгуя -1 вместо -2.

Но самое важное даже не это. Самое важное — что бэктест и live используют один и тот же движок:

# backtest.py
signals = eng.on_bar(o[i], h[i], l[i], c[i], v[i], int(ts[i]))

# main.py
sigs = eng.on_bar(b["open"], b["high"], b["low"], b["close"], b["volume"], b["ts"])

Это значит, что расхождение между «бэктест показал» и «бот наторговал» сведено к минимуму по построению. Нельзя случайно протестировать одну логику, а торговать другую — логика физически одна.


Данные: объёмы с Binance, исполнение на BingX

Небольшая, но важная деталь. Объёмный профиль хорош ровно настолько, насколько честны объёмы, которые в него кладут.

Поэтому система берёт свечи и объёмы с Binance USDT-M futures — самого ликвидного рынка с наиболее «правильной» картиной объёма. А исполняет сделки на BingX perpetual. Если же монеты на Binance нет, свечи берутся с BingX, и такой символ помечается source='bingx'.

source = "binance" if b_sym in b_syms else "bingx"

И вот тонкость риск-менеджмента: для BingX-данных риск на сделку снижается (0.5% вместо 1%). Логика простая — картина объёма там менее надёжна, значит, и ставку на такой сигнал делаем меньше.

Слой данных умеет всё, что нужно долгоживущей системе: кэширование свечей в CSV (повторные запуски докачивают только хвост), троттлинг запросов, ретраи с бэк-оффом на ошибках 429/5xx. Это не мелочь — без этого любой сканер сотен монет быстро упрётся в rate limits биржи.


Честный бэктест: комиссии, проскальзывание, пессимизм

Вторая причина, по которой красивые бэктесты не переживают реальность, — комиссии и проскальзывание. Многие искренне считают, что 0.05% — это ноль. На низких таймфреймах именно эти «сотые процента» и съедают всю прибыль, ведь платишь ты и на входе, и на выходе, да ещё с плечом.

В симуляции всё это учтено. Комиссия и проскальзывание закладываются в R каждой сделки:

exit_px = px_raw * (1 - d * slip)
r_gross = (exit_px - pos["entry"]) * d / risk_ps
fee_r   = fee * (pos["entry"] + exit_px) / risk_ps
r_net   = r_gross - fee_r

Более того, бэктест пессимистичен: если на одном баре задеты и стоп, и тейк, засчитывается стоп. Мы не знаем, что внутри бара сработало раньше, поэтому считаем в худшую для себя сторону — это честнее, чем раздувать статистику оптимистичным допущением.

Дальше сделки всех символов сводятся в портфель: сортируются по времени входа, риск на сделку берётся в процентах от текущего эквити, действует лимит одновременных позиций (max_concurrent). На выходе — полный набор метрик и готовый отчёт:

=== Портфель ==================================================
Сделок: 148   Winrate: 41.9%   PF: 1.63   avg R: 0.34   sum R: 50.9
  local    n=92    WR=45.7% avgR=0.48
  retest   n=56    WR=35.7% avgR=0.11
Эквити: 10,000 -> 14,720 USDT (+47.2%)
Max drawdown: 18.3%   Пропущено из-за лимита позиций: 12

📌 Сюда — скриншот реальной equity curve из вашего прогона (bt_results/<дата>/equity.png) и таблица по символам. Живые цифры вашей системы будут убедительнее любого примера.

Честно оговорюсь, что бэктест не моделирует: funding-платежи, реальную глубину стакана и частичные исполнения. На низколиквидных альтах под это стоит закладывать запас. Идеальных симуляций не бывает — важно понимать их границы.


Walk-forward отбор: боремся с маленькой выборкой

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

Эту проблему система решает через ежедневный walk-forward отбор монет.

Раз в сутки, в 12:00 UTC, бот сам берёт топ-100 монет по капитализации (CoinMarketCap или CoinGecko), прогоняет по каждой бэктест за последние lookback_days (120 дней) — и торгует в realtime только те монеты, что оказались прибыльными по чётким критериям:

  • сделок >= min_trades (3);
  • суммарный R > min_sum_r (0);
  • profit factor >= min_profit_factor (1.0).
def select_profitable(per_symbol, scfg):
    rows = []
    for sym, d in per_symbol.items():
        m = d["metrics"]
        if m["n"] < scfg.min_trades:            continue
        if m["sum_r"] <= scfg.min_sum_r:        continue
        if m["profit_factor"] < scfg.min_profit_factor: continue
        rows.append(...)
    rows.sort(key=lambda r: -r["sum_r"])
    return rows[:scfg.max_selected]

Лучшие max_selected (15) монет попадают в selection.json, и до следующего отбора входы разрешены только по ним. Стейблкоины и «обёртки» (WBTC, stETH и т.п.) из рейтинга выкидываются автоматически.

Это принципиально иной подход, чем «подобрал параметры один раз и молюсь». Система постоянно переспрашивает рынок: что работает прямо сейчас? — и адаптирует список инструментов под текущий режим, а не под давно ушедший исторический участок. А чтобы не упереться в лимиты API, отбор идёт с паузами между запросами и кэшированием — первый прогон занимает несколько минут, последующие докачивают только сутки истории.


Live: риск-контроль и отказоустойчивость

В боевом режиме на первый план выходит уже не поиск сигнала, а выживание системы и депозита.

Вход исполняется маркет-ордером в hedge-режиме, причём стоп и тейк прикрепляются прямо к ордеру (STOP_MARKET / TAKE_PROFIT_MARKET по mark price). Даже если бот упадёт или потеряет связь — позицию закроет сама биржа. Это важнейшая страховка.

Поверх этого — несколько уровней защиты риска:

  • дневной kill-switch: при просадке -max_daily_loss (−5%) за день новые входы останавливаются до следующих суток UTC;
  • cooldown на символ после закрытия позиции — не входим в ту же монету сразу;
  • изолированная маржа и плечо из конфига, контроль свободной маржи перед входом;
  • лимит одновременных позиций и минимальный размер сделки.
if start > 0 and eq <= start * (1 - self.cfg.risk.max_daily_loss):
    log.error("KILL-SWITCH: эквити %.2f <= %.2f ...")
    self.state["halted"] = True

Отдельно продумана отказоустойчивость. Ошибка данных по одному символу не роняет весь цикл — она логируется, и цикл идёт дальше. Сбой ежедневного отбора не останавливает торговлю: используется предыдущий список. Состояние (открытые позиции, cooldown'ы, бумажный эквити) сохраняется в live_state.json, так что перезапуск не теряет контекст.

И, конечно, безопасная лестница внедрения — от бумаги к бою:

бэктест → demo (VST) → DRY-RUN на реальных данных → боевой счёт с минимальным риском

Боевой режим ещё и переспрашивает подтверждение вручную, чтобы случайно не запустить реальную торговлю.


Проверка конвейера без сети

Приятная инженерная деталь — smoke_test.py. Он собирает синтетический ряд с заранее заложенной структурой (консолидация → импульс → ретест POC → отработка) и прогоняет через него весь конвейер: движок, бэктест, портфель, фильтр отбора.

assert eng.stats.get("cand_created", 0) >= 1, "консолидация не найдена"
assert eng.stats.get("level_created", 0) >= 1, "уровень не валидирован"
assert len(sigs) >= 1, "нет ни одного сигнала ретеста"

Это позволяет за секунды убедиться, что после правок логика не сломалась, — вообще без обращения к биржам. Для торговой системы, которую вы будете дорабатывать, такой офлайн-тест экономит массу нервов.


Заключение

Мы собрали систему, которая работает не с производными от цены, а с самой структурой рынка. По шагам:

  • объёмный профиль и POC вместо субъективных линий на графике;
  • понятная консолидация с концентрированным объёмом как база сетапа;
  • валидация импульсом — торгуем только значимые уровни;
  • ретест + реакция как триггер входа, с чётким разделением local / retest;
  • цель по ближайшему сильному непротестированному POC и жёсткий фильтр по RR;
  • walk-forward отбор монет каждый день — адаптация под текущий режим рынка;
  • риск-контроль: kill-switch, cooldown, изолированная маржа, TP/SL на стороне биржи.

Но главная ценность этой архитектуры даже не в самой стратегии. Она в том, что вокруг стратегии выстроена честная инфраструктура проверки: каузальный движок, единый код для бэктеста и live, пессимистичная симуляция с комиссиями и проскальзыванием, отбор на скользящем окне. Именно этого не хватает большинству «граалей» из интернета — и именно поэтому они не переживают встречу с реальным рынком.

Красивый бэктест почти ничего не значит без адекватной проверки. Рынок очень быстро наказывает за самообман. Эта система хотя бы честна сама с собой — а это уже половина дела.

Спойлер: идеальных систем всё равно не существует. Но система, которая не врёт себе на истории, имеет куда больше шансов, чем та, что рисует красивую кривую заглядыванием в будущее.


Это программный инструмент и разбор архитектуры, а не инвестиционная рекомендация. Торговля с плечом может привести к потере всего депозита; результаты бэктеста не гарантируют будущую доходность. Все решения и риск-менеджмент остаются за вами. Перед боевым запуском обязательно сверьте эндпоинты BingX с актуальной документацией и прогоните всё на демо-контуре (VST). API-ключ создавайте без права вывода средств и с ограничением по IP.

Posted

Вообще, Volume profile далеко не свежий инструмент, который давно общеизвестен. Я его ещё лет 20 назад делал, но нифига не понял что из этого можно извлечь. 

А про RSI, MACD и EMA я вообще молчу, если кто-то их использует, то либо у него очень много денег, либо скоро они у него закончатся. Либо он волшебник, который может и на фазах луны заработать. 

Кстати, про свечки экскурс интересный, такое часто встречается, даже у меня разок было... 😄 

  • 3 weeks later...
Posted (edited)

 

Трендовая линия — самый субъективный инструмент технического анализа. Дайте одному графику десять трейдеров — получите десять разных линий. Кто-то ведёт по теням, кто-то по телам, кто-то «на глаз подгоняет» так, чтобы линия подтверждала уже открытую позицию. Это не анализ, это гадание с линейкой.

А раз линию рисует человек — её нельзя ни протестировать, ни автоматизировать. Каждый раз получается новая.

Интересная инженерная задача — формализовать «настоящую» трендовую линию в строгую геометрию: набор чётких математических условий, по которым машина строит линию объективно, одинаково и, главное, честно — не заглядывая в будущее. Никаких индикаторов, только цена, экстремумы и ATR.

В этой статье разберём детектор, который это делает. Он находит трендовые поддержки и сопротивления по фрактальным пивотам, отсеивает всё, что «уровнем» на самом деле не является, ловит пробой телом с фильтрами качества — и присылает сигнал в Telegram с нарисованным скрином: линия, якоря, свеча-пробой, уровни входа, стопа и тейка.

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

Весь код выложен на GitHub — советую читать статью параллельно с исходниками.

 


Что делает линию «настоящей»

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

Поэтому в детекторе к линии предъявляются жёсткие требования — уровня настоящей трендовой, а не случайной прямой:

  • минимум 3 пивота на линии (включая якоря) с допуском — линия должна реально проходить через несколько подтверждённых экстремумов;
  • минимум 3 раздельных касания, причём соседние касания сливаются в одно (об этом ниже);
  • уровень недавно уважаем ценой — последнее касание не старше N баров, а не «археология» месячной давности;
  • ни одна свеча не закрывалась за линией дальше маленького допуска с момента первого якоря — иначе линия уже пробита;
  • линия не параболическая — слишком крутые уровни (|угол| больше порога) не берём.

Из всех кандидатов выбирается лучший по приоритету: больше пивотов → больше касаний → длиннее → свежее.

score = (pivot_touches, touches, i2 - i1, i2)
if best is None or score > best_score:
    best, best_score = tl, score

Дальше — как каждое из этих условий превращается в код.


Фрактальные пивоты — и почему без заглядывания в будущее

Всё строится на фрактальных пивотах: локальных минимумах (для поддержек) и максимумах (для сопротивлений). Пивот подтверждён, только когда справа от него закрылось right баров:

for i in range(left, n - right):
    window = values[i - left : i + right + 1]
    if kind == "low":
        m = window.min()
        if values[i] == m and int(np.sum(window == m)) == 1:
            out.append(i)

Здесь принципиальный момент, о котором постоянно забывают новички. Пивот нельзя считать подтверждённым в реальном времени — в момент формирования свечи вы ещё не знаете, что она окажется локальным минимумом. Требование «справа закрылось right баров» гарантирует, что мы работаем только с уже подтверждёнными экстремумами. Никакого заглядывания в будущее.

Более того, вся детекция линий идёт по данным без последних свечей-подтверждений пробоя. Линия строится на df[:-nconf], а пробой проверяется на полном df. Отсюда важное следствие: любой найденный уровень гарантированно не был пробит закрытием ранее — иначе он бы не прошёл валидацию. Причинность соблюдена по построению, а не «вроде бы учтена».


Нормированный угол: масштаб-инвариантность

Вот здесь — самая элегантная деталь.

Нам нужно отличать плоский уровень от наклонного. Наивно — взять наклон линии в цене за бар. Но сырой наклон бессмысленен: 0.5 доллара за бар — это круто для монеты по $3 и незаметно для монеты по $60000. Классификация «плоский/наклонный» поедет от одного лишь масштаба цены.

Решение — нормировать наклон на волатильность через ATR:

angle = \arctan\!\left(\dfrac{\text{slope}}{ATR}\right)

где slope — наклон в цене за бар, а ATR — средний истинный диапазон. Смысл простой и красивый: 45° соответствует движению линии ровно на 1 ATR за бар. Такое определение угла не зависит ни от цены актива, ни от масштаба графика, ни от единиц измерения.

angle = math.degrees(math.atan(slope / atr_now))
if abs(angle) > max_angle_deg:
    continue                      # параболу не торгуем
is_flat = abs(angle) <= flat_angle_max_deg

Плоский уровень (по умолчанию |угол| < 7.5°) и наклонный обрабатываются по-разному: у плоского тейк и стоп фиксированы от ATR(1h), у наклонного — стоп за структурой, тейк на якоре линии. Но сама граница между ними теперь объективна и одинакова для любого инструмента.

Тот же нормированный порог задаёт и допустимое направление наклона: восходящая поддержка (или плоская) — да, падающая круче плоского — нет, потому что её логика пробоя уже другая.


Кластеризация касаний: одно касание вместо десяти

Три касания — это требование. Но что считать касанием?

Наивный подсчёт «сколько баров подошло к линии» ломается моментально: если цена десять баров подряд ползёт вдоль линии, это одно касание, а не десять. Иначе любое залипание у линии накрутит счётчик и пропустит мусорный уровень.

Поэтому касания кластеризуются: серия соседних баров у линии — одно касание, а кластеры ближе min_gap баров сливаются в один:

def _count_touch_clusters(mask: np.ndarray, min_gap: int = 1) -> int:
    idx = np.flatnonzero(mask)
    if idx.size == 0:
        return 0
    gaps = np.diff(idx)
    return 1 + int(np.sum(gaps > max(1, min_gap)))

Идея на пальцах: берём индексы всех баров, коснувшихся линии, смотрим разрывы между соседними. Каждый разрыв больше порога — это граница нового кластера

Просто, векторно и ровно то, что нужно: считаем раздельные подходы к уровню, а не отдельные бары.


Валидация линии: чистая геометрия, векторно

Теперь собираем всё вместе. Для каждой пары пивотов строим линию-кандидата и прогоняем через фильтры — весь горячий путь на numpy, без циклов по свечам.

idx = np.arange(i1, n)
line = p1 + slope * (idx - i1)

if kind == "support":
    if np.any(closes[i1:n] < line - violation_tol):
        continue                       # закрытие уже пробило линию — не уровень
    rel = lows[i1:n] - line            # <= tol -> касание/прокол тенью
    touch_mask = rel <= touch_tol
    dist_now = closes[-1] - line[-1]   # насколько цена сейчас выше линии

Логика по шагам:

  • валидностьnp.any(closes < line − tol) мгновенно отсекает линию, если хоть одно закрытие ушло за неё дальше допуска (проколы тенями при этом разрешены — это нормальные касания);
  • касания — маска low − line <= tol, дальше кластеризация из прошлого раздела;
  • свежесть — последнее касание не старше last_touch_max_age баров;
  • актуальность — линия дальше max_dist_atr · ATR от текущей цены отбрасывается;
  • пивоты на линии — считаем, сколько подтверждённых пивотов реально лежит на прямой (отклонение ≤ pivot_tol), и требуем минимум три:
in_span = piv_arr[(piv_arr >= i1)]
devs = np.abs(anchor_vals[in_span] - (p1 + slope * (in_span - i1)))
pivot_touches = int(np.sum(devs <= pivot_tol))
if pivot_touches < min_pivot_touches:
    continue

Именно проверка «через сколько пивотов проходит линия» и отделяет настоящую трендовую от прямой по двум случайным точкам. Это, по сути, сердце всей детекции.


Пробой = закреп телом, а не касание тенью

Линия найдена. Теперь пробой. И здесь снова классическая ошибка новичка: реагировать на прокол тенью. Цена ткнулась за линию фитилём, все закрылись обратно — а вход уже открыт по ложному сигналу.

Правильный пробой — закреп телом: закрытие свечи за линией, а не касание. Причём подтверждающих свечей может быть несколько (по умолчанию две): обе должны закрыться за линией, вход на второй, возврат второй свечи отменяет сетап.

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

  • тело ≥ 0.35 ATR — импульсный пробой, а не вялое сползание сквозь линию;
  • глубина закрытия ≤ 1.2 ATR за линией — не сигналим по уже улетевшей цене (заодно это держит стоп адекватным);
  • закрытие в крайних 40% диапазона свечи (шорт — у low, лонг — у high) — сильное закрытие без длинного хвоста возврата.
body = abs(closes[i] - opens[i])
if min_body_atr > 0 and body < min_body_atr * atr_now:
    return None                        # сползание, а не импульс

depth = abs(closes[i] - line.value_at(i))
if max_depth_atr > 0 and depth > max_depth_atr * atr_now:
    return None                        # не догоняем улетевшую цену

rng = highs[i] - lows[i]
if close_pos_max < 1.0 and rng > 0:
    pos = ((closes[i] - lows[i]) / rng if direction == "short"
           else (highs[i] - closes[i]) / rng)
    if pos > close_pos_max:            # закрытие слабое, с хвостом возврата
        return None

Совокупно эти фильтры существенно режут число сигналов, оставляя только структурно качественные пробои. Пробой поддержки — шорт, пробой сопротивления — лонг.


Стоп, тейк и риск-ревард

Уровни сигнала считаются не «на глаз». Для наклонного пробоя стоп ставится за структурой — за последним подтверждённым свингом младшего таймфрейма (swing high для шорта, swing low для лонга) с небольшим запасом:

for i in reversed(find_pivots(highs, pivot_left, pivot_right, "high")):
    if highs[i] > entry_ref:
        return float(highs[i] + buffer_abs)   # ближайший свинг выше входа

Тейк — на цене первого пивота-якоря, от которого началась линия: логичная цель, к которой рынок уже ходил.

И финальный фильтр — риск-ревард:

RR = {|TP - entry|}{|entry - SL|}

Если RR < 1 — сетап отбраковывается, сигнала нет. Если RR > 5 — стоп в сигнале отодвигается, пока RR не станет ровно 5 (структурный стоп при этом остаётся внутри нового). Так в сигнал не попадают ни сделки с отрицательным матожиданием, ни абсурдно широкие цели.

if min_rr > 0 and rr < min_rr:
    return None
if max_rr > 0 and rr > max_rr:
    new_risk = reward / max_rr
    sl = entry + new_risk if side == "short" else entry - new_risk
    rr = max_rr

Скрин сигнала: линия, которую видно

Текстовый сигнал по трендовой почти бесполезен — уровень надо видеть. Поэтому к каждому сигналу рисуется график в тёмном стиле TradingView: свечи рабочего ТФ, сама наклонная с якорями, свеча-закреп со стрелкой и пунктирные линии входа, стопа и тейка.

Рендерит matplotlib в файл (Agg, без дисплея), затем PNG уходит в Telegram через sendPhoto. Если matplotlib не установлен или рендер упал — сигнал уходит текстом, бот не останавливается.

ax.plot([x1, x2], [line.value_at(line.i1), line.value_at(i_last + 3)],
        color=LINE_C, linewidth=2.2,
        label=f"наклонная {line.angle_deg:+.1f}°, "
              f"пивотов {line.pivot_touches}, касаний {line.touches}")

Прямо в легенде — паспорт линии: нормированный угол, число пивотов и касаний. Видно не только «где», но и почему это уровень.

photo_2026-08-02_15-21-00.thumb.jpg.587d447ff8e1db0cb5288fba19e725ae.jpg


Архитектура сигнального бота

Вокруг детектора — минимальная, но живучая обвязка. Код разложен по ответственностям:

Модуль Ответственность
trendlines.py Детекция линий, пивотов, пробоев, структурных стопов, RR
chart.py Рендер скрина сигнала (matplotlib)
data_feed.py Публичные свечи BingX (без ключей API)
breakout_bot.py Цикл, отбор сигналов, отправка в Telegram, состояние
config.py Все параметры стратегии

Несколько инженерных решений, которые делают бота пригодным для «поставил и забыл»:

Только публичные данные. Свечи берутся с публичного эндпоинта BingX — ключи API не нужны вообще. Бот ничего не отправляет на биржу, он только читает рынок и присылает сигналы. Из данных сразу выбрасывается текущая (незакрытая) свеча — работаем строго по закрытым барам.

Никаких повторных сигналов. Отработанная линия помечается по её сигнатуре, и это состояние переживает рестарт (атомарная запись в JSON). После перезапуска бот не пришлёт сигнал по той же линии второй раз. Плюс кулдаун на актив после сигнала.

self.state.consumed_lines.add(line.signature())
self.state.cooldown_until_ms[symbol] = now_ms + cfg.SIGNAL_COOLDOWN_MIN * 60_000

Отказоустойчивость. Ошибка по одному активу логируется и уходит в Telegram, но не роняет цикл — остальные пары обрабатываются как ни в чём не бывало. Плюс защита от спама одинаковыми ошибками.

Интерактивность. Команда /menu в Telegram включает фильтр объёма и меняет его коэффициент кнопками — настройки тоже переживают рестарт. Условие фильтра: avg_vol(3 свечей) > avg_vol(40 свечей) × коэффициент.

Один цикл в логах выглядит так:

2026-08-02 10:30:12 | INFO | SOL-USDT: пробой! наклонный уровень (поддержка), угол +12.0 град., пивотов на линии 3, касаний 4 -> SHORT

Что в итоге

Мы формализовали самый субъективный инструмент трейдинга в строгую геометрию. Инженерно самое ценное здесь:

  • линия как набор жёстких условий — ≥3 пивота на прямой, ≥3 раздельных касания, недавняя уважаемость, отсутствие закрытий за линией;
  • фрактальные пивоты с подтверждением и построение линий без свечей-пробоев — причинность по построению, без заглядывания в будущее;
  • нормированный через ATR угол — классификация наклона, не зависящая от масштаба цены и графика;
  • кластеризация касаний — считаем подходы к уровню, а не отдельные бары;
  • пробой телом с фильтрами качества — импульс вместо сползания, без догоняющих входов и хвостов возврата;
  • структурный стоп + лимиты RR и рендер скрина — сигнал, который видно и который отбракован по матожиданию.

Но честно про главное. Формализация не равна прибыли. Строгая, красиво посчитанная линия — это всё ещё просто линия, и пробои трендовых регулярно оказываются ложными. Алгоритм не предсказывает будущее — он лишь убирает из процесса субъективность и ручной труд «пересмотреть сотню графиков глазами», рисуя уровни объективно и одинаково. Что делать с сигналом — решаете вы, глядя на скрин и контекст.

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

 

Edited by cryptomaniac_dt
Posted

@cryptomaniac_dt

Нарушение 3.2
У вас уже есть тема про это, пишите только в ней. Перемещено. Последний пост, созданный как отдельная тема, прицеплен к основной.
Если вы делаете новый пост, и вдруг форум требует придумать к нему заголовок— значит, вы нажали что-то не то и создаете новую тему.
Самое простое: внизу вашей темы окно для набива сообщения. Пишем там (ну или аккуратно копипастим—убивая форматирование), нажимаем Отправить — все ок

 

Posted

Зоны спроса и предложения как алгоритм: скоринг зон, мультитаймфрейм и вход по пинбару (Python, asyncio)

Уровень сложности: Средний · Время на прочтение: ~11 мин · Хабы: Python, Асинхронное программирование, Криптовалюты, Финансы в IT, Визуализация данных


Зоны спроса и предложения (supply/demand) — один из самых сильных концептов в трейдинге и один из самых субъективно рисуемых. Идея красивая: цена уходит из области, где крупный игрок набрал позицию, а потом возвращается «добрать» — и от этой области снова разворачивается. Проблема ровно та же, что и с трендовыми линиями: каждый рисует эти прямоугольники по-своему. Где начало зоны, где конец, какая «сильная», какая нет — сплошная вкусовщина.

Интересная инженерная задача — оцифровать это. Не «нарисовать box на глаз», а формализовать: что такое зона, из чего она рождается, как измерить её силу числом и как отсеять мусорные.

В этой статье разберём сканер, который делает именно это. Он проходит по всему рынку BingX, находит зоны спроса и предложения на старшем таймфрейме, оценивает каждую зону скорингом, ждёт возврата цены и подтверждающего пинбара на 5-минутках — и присылает сигнал в Telegram с нарисованным графиком: свечи, зоны, выделенная сигнальная область, уровни входа и стопа.

Упор в разборе — на технику: как превратить «зону на глаз» в набор условий и число, зачем разносить поиск зоны и вход по разным таймфреймам, как считать импульс через ATR и как асинхронно сканировать весь рынок каждые пять минут. Это сигнальный инструмент: ордера на бирже он не выставляет, только находит сетап и присылает уведомление.

Весь код выложен на GitHub — советую читать статью параллельно с исходниками.

image.thumb.png.43f2e5b709aecce9272d049ad42bff1b.png


Что такое зона — и почему её оцифровать сложно

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

То есть зона — это «база» импульса: то место, откуда он стартовал.

Но чтобы это работало в коде, на каждый расплывчатый термин нужно жёсткое определение. Из какой свечи строить зону? Какой у неё размер? Насколько сильным должен быть импульс, чтобы зона считалась настоящей? Дальше — как раз про это.


Зона рождается из разворотной свечи

Первый вопрос — где искать базу импульса. Ответ: в точке разворота. А разворот на графике выражается характерными свечами — пинбаром или поглощением (engulfing).

Пинбар («свеча-молот») — это длинный хвост в сторону, откуда цену вынесли, и маленькое тело. Но «длинный хвост» — снова вкусовщина, поэтому его нужно формализовать точными условиями:

def is_bullish_pinbar_df(df, i):
    ...
    body = abs(c - o)
    total_range = h - l
    if body > 0.3 * total_range:          # тело <= 30% диапазона свечи
        return False
    upper_wick = h - max(o, c)
    lower_wick = min(o, c) - l
    if c <= o:                            # для demand — свеча бычья
        return False
    if lower_wick >= body * 2 and upper_wick <= body * 0.5:
        return True                        # длинный нижний хвост, короткий верхний
    return False

Три чётких условия вместо «на глаз»: тело не больше 30% диапазона, нижний хвост минимум вдвое длиннее тела, верхний хвост не длиннее половины тела. Для медвежьего пинбара всё зеркально. Поглощение проверяется классически — тело текущей свечи перекрывает тело предыдущей противоположного цвета.

Именно эти свечи и становятся кандидатами на базу зоны: бычий пинбар или бычье поглощение → зона спроса, медвежьи → зона предложения.


Геометрия зоны

Свеча-кандидат найдена. Теперь из неё нужно построить сам прямоугольник. Зона берётся не по всей свече, а от экстремума до основания тела — то есть область хвоста, где и произошёл выкуп/слив:

if zone_type == 'demand':
    low = l                       # низ — минимум свечи (конец хвоста)
    high = min(o, c)              # верх — основание тела
    if (high - low) > max_size:   # ограничение размера
        high = low + max_size

Для зоны предложения зеркально: от основания тела (max(open, close)) до максимума свечи.

Важная деталь — ограничение размера зоны: high - low <= MAX_ZONE_ATR · ATR (по умолчанию 4 ATR). Слишком широкая зона бесполезна — вход в неё означал бы гигантский стоп, а «сработать» она может где угодно внутри. Ограничение через ATR делает максимальный размер зоны адаптивным к волатильности конкретного инструмента, а не фиксированным в долларах.


Скоринг: превращаем «сильную зону» в число

Вот главная техническая идея. Вместо интуитивного «эта зона выглядит мощно» каждой зоне присваивается числовой скор, и в работу идут только те, что набрали порог.

Скор складывается из трёх компонент:

score = \min(3,\ \text{impulse}) + 2\cdot\dfrac{body}{range} + 2\cdot[\,\text{swing}\,]

Разберём по частям.

Сила импульса. Смотрим вперёд на LOOKAHEAD (10) баров после зоны и меряем, как далеко ушла цена, нормируя на ATR:

if zone_type == "demand":
    future_high = df['high'].iloc[i+1:i+1+lookahead].max()
    move = future_high - zone['high']
else:
    future_low = df['low'].iloc[i+1:i+1+lookahead].min()
    move = zone['low'] - future_low

impulse_strength = move / atr_val
score += min(3, impulse_strength)
if impulse_strength < IMPULSE_MIN_ATR:     # слабый импульс — зона отбраковывается
    return 0

impulse = move / ATR — это и есть измерение силы движения в единицах волатильности. Если импульс слабее IMPULSE_MIN_ATR (2 ATR), зона отбрасывается сразу: не было настоящего дисбаланса — нет и зоны. Это ключевой фильтр.

Тело свечи-базы. Чем плотнее свеча (больше тело относительно диапазона), тем решительнее был разворот: score += 2 · body/range.

Swing-подтверждение. Если база зоны совпала с локальным экстремумом (swing low для спроса, swing high для предложения) — ещё +2.

Порог MIN_ZONE_SCORE = 4. Максимум ~7. Понижаете порог — больше зон и больше шума, повышаете — меньше зон, но качественнее. Один параметр рулит всей строгостью отбора.

И вот важный момент про причинность. Скор смотрит на бары после формирования зоны — значит, зону можно оценить только когда импульс уже случился. Поэтому детекция сознательно останавливается за LOOKAHEAD баров до текущего момента: мы доверяем зоне, только когда она уже доказала себя реальным движением, а затем ждём свежего возврата к ней. Это не заглядывание в будущее — это отложенная торговля проверенной зоны.


Жизненный цикл зоны

Найти зону мало — ею нужно управлять. В системе три механизма.

Инвалидация. Пробитая зона больше не работает. Проверяем, не зашла ли цена после формирования зоны за её границу — по закрытию или по тени (настраивается):

if zone['type'] == 'supply':
    break_level = slice_df['close'].max()   # или 'high' для проверки по тени
    return break_level > zone['high']

Пробитые зоны выкидываются из списка сразу.

Одноразовость. Как только зона дала сигнал, она помечается использованной и повторно не торгуется:

used_zones.setdefault(symbol, set()).add((zone['high'], zone['low']))

Логика та же, что у трейдера: один раз отработали уровень — второй раз он уже «поюзан» и менее надёжен.

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

Оставшиеся зоны сортируются по скору и свежести, и берётся топ:

supply_zones = sorted(supply_zones, key=lambda z: (z["score"], z["start_bar"]),
                      reverse=True)[:MAX_ZONES]

Мультитаймфрейм: зона на старшем ТФ, вход на 5m

А это — самое сильное архитектурное решение системы.

Зоны ищутся на старшем таймфрейме (ZONE_TF, в примере на скрине — 4h): там видна значимая структура, крупные области дисбаланса. Но входить по 4h-свече поздно и грубо — сигнал придёт, когда цена уже в глубине зоны, а стоп получится огромным.

Поэтому вход выносится на младший таймфрейм. Когда цена возвращается в старшую зону, система переключается на 5-минутки и ждёт там подтверждающий пинбар прямо внутри зоны:

def check_short_signal(symbol, df_5m, zones):
    last = df_5m.iloc[-2]                 # последняя закрытая 5m свеча
    price = last["close"]
    if not is_bearish_pinbar_df(df_5m, len(df_5m) - 2):
        return None                       # нет пинбара — нет входа
    for zone in zones:
        if price_in_zone(price, zone):    # цена внутри зоны (с допуском)
            return zone
    return None

Смысл связки: старший ТФ отвечает за «где» (значимая зона), младший — за «когда» (точный момент разворота). Макроконтекст плюс микроточность. Пинбар на 5m внутри 4h-зоны — это уже не грубое «цена коснулась box», а зафиксированный отказ рынка идти дальше именно там, где мы его ждали. Ложных входов такой двойной фильтр отсекает заметно больше, чем вход по одному таймфрейму.

Стоп ставится за экстремум сигнальной 5m-свечи (за её high для шорта, за low для лонга) — то есть за точку, где рынок в последний раз отказался идти дальше. Вход — по текущей цене возврата в зону.


Async-сканер всего рынка

Ловить такие сетапы надо по всему рынку и часто, иначе большинство возвратов в зону просто пропустишь. Поэтому сканер асинхронный.

Каждый цикл система берёт все тикеры BingX и запускает обработку параллельно, ограничивая одновременные задачи семафором:

async def main_loop():
    symbols = bx.get_all_tikers()
    semaphore = asyncio.Semaphore(MAX_CONCURRENT)   # не больше 10 разом
    while True:
        tasks = [process_symbol(symbol, semaphore) for symbol in symbols]
        await asyncio.gather(*tasks)
        await asyncio.sleep(SCAN_INTERVAL_SEC)       # пауза между полными сканами

Семафор здесь принципиален: сотни монет нельзя запрашивать одновременно — упрёшься в лимиты биржи и получишь шквал ошибок. Semaphore(10) держит ровно десять параллельных обработок, а gather дожидается всех. Ошибка по одной монете изолирована в try/except внутри process_symbol и не роняет весь скан — остальные пары обрабатываются как ни в чём не бывало.

На каждый символ — минимум запросов: одна пачка свечей старшего ТФ для зон и две последние 5m-свечи для проверки входа, и то лишь если рядом вообще есть актуальная зона.


Рендер сигнала

Сигнал по зоне без картинки почти бесполезен — зону надо видеть. Поэтому к каждому сигналу рисуется график: свечи старшего ТФ, все ближайшие зоны (предложение — красным, спрос — синим, полупрозрачной заливкой) и сигнальная зона жирной обводкой.

# сигнальную зону выделяем жирной рамкой
ax.add_patch(patches.Rectangle(
    (rel_start, signal_zone['low']),
    len(df) - rel_start, signal_zone['high'] - signal_zone['low'],
    facecolor='none', edgecolor=color, linewidth=3))

Свечи рисуются вручную прямоугольниками тела и линиями теней, готовый PNG стримится в память (io.BytesIO) и уходит в Telegram через sendPhoto — без сохранения на диск. В подписи — символ, тип сигнала, границы зоны, цена входа и стоп.

image.png.80d9f25848fae58e23d998f77c6ae530.png

Оговорюсь честно: сигнал даёт вход и стоп, но не тейк-профит — цель вы определяете сами по ближайшей противоположной зоне или структуре. И направление у сигнала есть, но пробой/возврат зоны всё равно может не отработать: инструмент показывает качественный сетап, а не гарантию.


Что в итоге

Мы оцифровали ещё один субъективный концепт трейдинга. Инженерно самое ценное здесь:

  • зона как формализованный объект — база импульса из пинбара/поглощения, с чёткой геометрией и адаптивным к ATR размером;
  • скоринг зоны числом — сила импульса в единицах ATR, плотность свечи, swing-подтверждение и один порог, рулящий строгостью;
  • отложенная торговля проверенной зоны — доверяем только зоне, уже доказавшей себя импульсом, без заглядывания в будущее;
  • мультитаймфрейм — зона на старшем ТФ («где»), вход по пинбару на 5m («когда»): макроконтекст плюс микроточность;
  • жизненный цикл зоны — инвалидация по пробою, одноразовость, только ближайшие релевантные зоны;
  • async-сканер всего рынка — семафор, gather, изоляция ошибок по символу, рендер сигнала прямо в память.

И про главное — честно. Оцифровка не равна прибыли. Красиво посчитанная зона со скором 6.5 — это всё ещё зона, и возвраты к ней регулярно пробивают её насквозь. Алгоритм не предсказывает рынок; он убирает субъективность и ручной труд «пересмотреть весь рынок глазами», рисуя и оценивая зоны одинаково и по правилам. Что делать с сигналом — решаете вы, глядя на скрин и контекст.

Спойлер: грааля в supply/demand-зонах нет — как и в любом другом одиночном инструменте. Но зона, найденная по чётким правилам, оценённая числом и подтверждённая разворотом на младшем ТФ, — это куда более честная отправная точка, чем прямоугольник, пририсованный на глаз к уже открытой позиции.


Это сигнальный инструмент и разбор архитектуры, а не инвестиционная рекомендация. Возврат к зоне — не гарантия разворота; торговля фьючерсами с плечом сопряжена с риском потери средств. Решения и риск-менеджмент остаются за вами. Токен Telegram-бота держите в секрете; публичные эндпоинты BingX со временем меняются — сверяйтесь с актуальной документацией.

Posted

@cryptomaniac_dt

Нарушение 3.2
У вас уже есть тема про это, пишите только в ней. Перемещено. Последний пост, созданный как отдельная тема, прицеплен к основной.
Если вы делаете новый пост, и вдруг форум требует придумать к нему заголовок— значит, вы нажали что-то не то и создаете новую тему.
Самое простое: внизу вашей темы окно для набива сообщения. Пишем там (ну или аккуратно копипастим—убивая форматирование), нажимаем Отправить — все ок

 

 

Не слышите, нихт?

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Similar Topics

    • Теория четырехлетних циклов биткоина: а вдруг это и правда миф

      Всего несколько лет назад халвинг биткоина воспринимался как катализатор бычьего тренда на рынке. Однако после халвинга 2024 года рынок цифровых активов не продемонстрировал ожидаемого восходящего ралли, что побудило ряд экспертов поставить под сомнение работоспособность популярной теории о четырехлетних циклах первой криптовалюты. У теории четырехлетних циклов (Bitcoin halving cycles) нет конкретного автора и она возникла не как научная гипотеза, а как эмпирическое наблюдение участников

      in Новости криптовалют

    • Налогообложение цифровых активов в России: теория и практика

      Минфин и ФНС России планируют внести поправки в Налоговый кодекс, обязать россиян отчитываться о владении криптоактивами, а также ужесточить налоговый контроль над доходами от криптовалютных операций. Криптовалюта создавалась для того, чтобы каждый мог свободно и, главное, анонимно, распоряжаться своими деньгами. Однако у государства иное мнение. Ему не нравится сама идея новых денег, поэтому оно будет пытаться их запрещать или контролировать, хотя бы в части обязательного информирования и

      in Статьи Bits.media

    • Прогнозируем криптовалютные циклы: волновая теория Эллиотта

      При вложениях в криптовалюту инвесторы хотят купить подешевле, а продать подороже. Один из способов сделать это — воспользоваться волновой теорией Эллиотта, которая дает ориентиры на начало и конец роста и коррекции. Волновая теория Эллиотта — это метод анализа и прогнозирования цен на финансовых рынках путем выявления различных циклов (волн). Ее основоположником стал американский финансист Ральф Нельсон Эллиотт (Ralph Nelson Elliott) в середине 1930-х годов.   Теория сводится к оп

      in Новости криптовалют

    • Современная монетарная теория (MMT) и Биткоин

      Мог ли Карл Маркс повлиять на современную денежную теорию (ММТ)? И при чем здесь Биткоин? Недавно в «Коммерсанте» вышла статья под интригующим заголовком – в духе патриарха российской журналистики Максима Соколова – «Молодой, но с бородой». Мне почему-то сразу вспомнился сам Максим Соколов; наверняка, многие помнят его едкие запоминающиеся статьи-фельетоны в раннем «Коммерсанте», и его фирменную подпись «Максим Ъ-Соколов». Сколько было шуток по поводу его окладистой бороды а-ля Карл Маркс

      in Экономика

    • Австрийская школа: как экономическая теория связана с криптовалютой

      Биткоин — это уникальный феномен, за которым стоит целая философия, идеи свободного рынка, анархии, либертарианства и даже отдельные тезисы целых экономических школ, в том числе Австрийской экономической школы (АЭШ). Первая криптовалюта появилась в 2009 году, и она фундаментально отличалась от всех прочих форм денежных расчетов. Главным образом сеть Биткоина выделялась тем, что она была децентрализована, а за выпуском монет не стояли какие-либо государства, центробанки и другие подобные стр

      in Новости криптовалют

×
×
  • Create New...