Блог им. RomellaAkumov

Полностью автоматизируем трейдинг по аукционной теории — от базы до python робота

Полностью автоматизируем трейдинг по аукционной теории — от базы до python робота.

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

Ключевым элементом такого подхода является Volume Profile, а именно Point of Control (POC) — уровень цены, на котором за выбранный период был проторгован максимальный объём. В терминах аукционной теории POC соответствует зоне максимального согласия участников рынка.

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

  • POC

  • Value Area High (VAH)

  • Value Area Low (VAL)

В качестве основы используется Python‑скрипт back.py, предназначенный для параметрического бэктеста стратегии.

Все скрипты из статьи я выложил на github для вашего удобства.

Архитектура backtest‑скрипта

Скрипт логически разделён на несколько уровней:

  1. Загрузка и подготовка рыночных данных (Binance Futures)

  2. Расчёт Volume Profile и POC

  3. Генерация торговых сигналов

  4. Рыночная структура (swing‑экстремумы)

  5. Управление риском (SL / TP)

  6. Симуляция сделок

  7. Анализ результатов

Такое разделение важно: в дальнейшем те же блоки будут переиспользованы в real‑time боте практически без изменений.

Расчёт Volume Profile и POC

Ключевая функция стратегии — calculate_volume_profile.

<code>def calculate_volume_profile(df, bins=100, buffer_ratio=0.05):
  price_min = df['low'].min()
  price_max = df['high'].max()
  
  
  buffer = (price_max - price_min) * buffer_ratio
  price_min -= buffer
  price_max += buffer
  
  
  price_bins = np.linspace(price_min, price_max, bins)
  bin_centers = (price_bins[:-1] + price_bins[1:]) / 2
  volume_profile = np.zeros(bins - 1)</code>

Что происходит:

  1. Берётся диапазон цен за весь доступный период

  2. Добавляется буфер, чтобы исключить краевые искажения

  3. Диапазон разбивается на фиксированное число ценовых бинов

Далее каждая свеча распределяет свой объём по пересекаемым ценовым уровням:

<code>for i in range(len(df)):
  low = df['low'].iloc[i]
  high = df['high'].iloc[i]
  vol = df['volume'].iloc[i]
  
  
  bin_indices = np.digitize([low, high], price_bins) - 1
  start_bin = max(0, min(bin_indices))
  end_bin = min(bins - 2, max(bin_indices))
  
  
  bin_vol = vol / (end_bin - start_bin + 1)
  for b in range(start_bin, end_bin + 1):
  volume_profile[b] += bin_vol</code>

Таким образом формируется реальный объёмный профиль

Расчёт POC и Value Area

<code>poc_index = np.argmax(volume_profile)
poc = bin_centers[poc_index]</code>

Value Area считается классическим способом — через накопление 68% объёма от POC наружу.

Генерация торговых сигналов

Логика входа реализована в generate_signals().

 

<code>if (prev_close <= va_low * (1 + threshold) and close > va_low):
  signals.iloc[i] = 'Buy'</code>

Интерпретация:

  • цена находилась в зоне дисбаланса ниже VAL

  • затем вернулась внутрь value area

  • рынок «принял» цену обратно

Short-сигнал зеркален относительно VAH.

Объёмный фильтр

<code>if vol < avg_vol_i * min_volume_ratio:
  continue</code>

Сигнал игнорируется, если возврат в value происходит без участия объёма.

Рыночная структура: swing-экстремумы

Для управления сделкой используется структура рынка.

Internal swings

<code>def detect_internal_swings(df):
  if low[i] < low[i-1] and low[i] < low[i+1]:
  swing_low[i] = True</code>

Используются для:

  • First Trouble Area — первая зона, в которой можно получить реакцию

  • ближних целей

External swings

<code>def detect_external_swings(df, win=20):
  if low[i] == low[L:R].min():
  swing_low[i] = True</code>

Используются как цели ликвидности и структурные стоп-лоссы

Stop Loss: приоритет структуры

<code>def calculate_sl(df, entry_idx, entry_price, direction):
  if direction == 'long':
    return last_swing_low</code>

Логика:

  1. SL ставится за ближайший структурный экстремум

  2. Если экстремума нет — fallback на ATR

Это принципиально отличает стратегию от индикаторных систем.

Take Profit: ATR, Liquidity, FTA

<code>if tp_mode == 'liquidity':
  tp = tp_liquidity(df, i, direction)</code>

TP может быть:

  • фиксированным по волатильности

  • по цели ликвидности

  • по первой проблемной зоне

Каждая сделка проходит фильтр, чтобы риск-ревард был более 1. В ином случае эта сделка просто не выгодна.

Симуляция сделок

<code>for j in range(i + 1, i + max_bars):
  if bar_low <= sl:
    exit_price = sl</code>

Модель исполнения:

  • проверка SL → TP

  • без подглядывания в будущее

  • одна позиция = одна сделка

Комиссии учитываются с двух сторон.

Анализ результатов

<code>winrate = len(wins) / trades_cnt * 100
profit_factor = wins.sum() / abs(losses.sum())</code>

Также рассчитываются:

  • Sharpe Ratio (annualized)

  • Max Drawdown

  • Net PnL

Метрики считаются по сделкам, а не по свечам.

Почему этот подход работает

  1. Используется реальное распределение объёма

  2. Входы строятся от логики аукциона, а не индикаторов

  3. Риск контролируется структурой рынка

  4. Бэктест максимально приближен к реальному исполнению

Запустив бектест, мы получим ряд данных. Для примера:

<code>Trades: 54, Net PnL (USD): 937.03, Winrate: 57.41%, MaxDD: -3.85
Result: Trades=54, Net PnL=937.03, Winrate=57.41%

Trades: 45, Net PnL (USD): 937.38, Winrate: 60.00%, MaxDD: -3.36
Result: Trades=45, Net PnL=937.38, Winrate=60.00%

Result: Trades=0, Net PnL=0.00, Winrate=0.00%</code>

 

Здесь мы можем увидеть, что есть действительно неплохая стратегия с винрейтом в 60%. Она дала нам 937$ прибыли при входе на 0.002 BTC в каждой сделке.

Параметры следующие:
{'bins': 100, 'threshold': 0.003, 'tp_mode': 'liquidity', 'atr_coeff': 2, 'min_tp_pct': 0.003, 'min_volume_ratio': 1.2, 'require_trend_confirmation': False}

В real-time боте будем использовать именно их.

Часть 2. Реализация real-time бота

В этой части не повторяется логика бэктеста и не объясняются основы стратегии.

Задача real-time реализации — одна:

корректно и без искажений перенести готовую стратегию в живой рынок.

Общая архитектура real-time бота

Ключевой принцип — жёсткое разделение ответственности:

Binance (данные) → Strategy Engine → BingX (исполнение)

  • Binance используется исключительно как источник свечей

  • BingX — только как торговая площадка, так как имеет меньшие комисии

Это позволяет избежать логических расхождений и упрощает отладку.

Execution layer: BingxClient

Для грамотной работы нам необходим bingX client — для удобства я написал SDK библиотеку со всеми функциями для этой биржи. Это позволить не переписывать сложные функции с подписями и запросами для каждой стратегии, а использовать один скрипт. Он вместе со стратегией хранится на github.

Инициализируем библиотеку:

<code>bingx_client = BingxClient(
api_key=API_KEY,
api_secret=API_SECRET,
symbol="BTCUSDT"
)</code>

Сделки будем открывать с помощью функции:

<code>def place_market_order(self, side: str, qty: float, symbol: str = None, stop: float = None, tp: float = None):</code>

Цикл получения данных

<code>df = fetch_klines_paged(
SYMBOL,
INTERVAL,
total_bars=2000,
client=binance_client
)</code>

Особенности:

  • подгружается достаточно длинная история для корректного Volume Profile

  • данные каждый цикл пересобираются заново

  • используются только закрытые свечи

Это дороже по API, но гарантирует идентичность логики с бэктестом.

Подготовка данных перед сигналом

<code>df = detect_internal_swings(df)
df = detect_external_swings(df)
df = calculate_atr(df)
df = generate_signals(df, params)</code>

Важно:

  • порядок вызовов полностью совпадает с бэктестом

  • никаких оптимизаций или «ускорений» не используется

Любое отклонение здесь приводит к несовпадению сигналов.

Работа строго по последней закрытой свече

if df['long_signal'].iloc[-2]:

Бот:

  • не реагирует на текущую формирующуюся свечу

  • не пересчитывает POC intra-bar

Это сознательный компромисс:

  • меньше сделок

  • но полное соответствие backtest → live

Формирование ордера

<code>def open_order_bingx(direction, qty, entry_idx, df):
  entry_price = float(df.iloc[entry_idx]['close'])
  sl = calculate_sl(...)
  tp = tp_liquidity(...) or tp_fta(...)</code>

Все параметры сделки:

  • entry

  • stop-loss

  • take-profit

рассчитываются до отправки ордера.

Биржа не принимает решений — она только исполняет.

Отправка ордера в отдельном потоке

<code>threading.Thread(
target=open_order_bingx,
args=(direction, QTY, entry_idx, df)
).start()</code>

Это решает проблему прерывания основного цикла, это не является чем-то основным, но всё же делает логику безопаснее.

Основной цикл бота

<code>while True:
  run_live_bot(params)
  time.sleep(60)</code>

Минимализм цикла — осознанный выбор:

  • нет хранения позиций

  • нет локального state

  • нет логики сопровождения

Позиция живёт на стороне биржи.

Ключевые проблемы real-time и как они решены

1. Drift между backtest и live

  • одинаковый код

  • одинаковый порядок расчётов

2. Рассинхронизация свечей

  • только закрытые бары

3. API latency

  • threading

4. Ошибка исполнения

  • торговая логика изолирована от AP

Вывод

Этот real-time бот — не отдельная система, а прямое продолжение бэктест-движка.

Если стратегия работает в истории, она будет вести себя так же и в реальном рынке — с учётом комиссии, проскальзывания и latency.

Именно это и является основной задачей real-time реализации.

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

327 | ★1
1 комментарий
Комментировать ИИ тексты и коды не буду — это бесполезно.

Многим «платформам» с ГитХаб скоро придет трындец. Он уже приходит. Проще с нуля через ИИ написать готовое с коннекторами, бэктестерами и блэк-джеками, чем изучать чужое поделие и изделие.

Читайте на SMART-LAB:
Фото
USD/CAD: инициатива постепенно переходит к медведям?
Канадский доллар отскочил от точки пересечения пробитого даунтренда (на графике обозначен цифрами 1 и 2) и уровня поддержки 1.3730. Быки удержали...
Фото
Как изменились средние доходности облигаций (по рейтингам) за неделю? Для ВДО - сильно
Средние доходности облигаций в зависимости от рейтинга (бледные столбцы — доходности без сглаживания). И как они изменились за неделю (для...
Короткие тезисы с онлай-звонка Моzgovik-клуба на этой неделе. 
В среду на этой неделе мы провели традиционный онлайн звонок, где постарались ответить на вопросы наших клиентов. Напомню, что участие в звонках и...

теги блога Roman crypto_maniac

....все тэги



UPDONW
Новый дизайн