All streams
Search
Write a publication
Pull to refresh
9
0
Send message

Здравствуйте!
В конфиге комиссия задаётся в долях, а не в процентах. Значение 0.0004 = 0.04% = 4 bps, что соответствует стандартным комиссиям Binance. Дополнительное умножение на 100 не требуется. Когда вы подставляете 0.05, то это уже 5% комиссии за сделку, что не имеет никакого отношения к реальности и естественно полностью убивает торговлю.

Спасибо за внимание к статье и отличный вопрос!
Хочу вас отдельно похвалить — вы сами нашли ответ на ваш вопрос. Это действительно бесценный опыт, когда человек самостоятельно разбирается в технических деталях, понимание остаётся с ним навсегда и формирует настоящий исследовательский навык.

Вы абсолютно правы, CCXT в некоторых режимах агрегирует сделки, из-за чего поле num_trades может отличаться от нативных значений Binance. Поэтому для точных исследований я всегда рекомендую использовать оригинальные данные напрямую из Binance Futures API. Один из самых удобных способов — через библиотеку python-binance (https://github.com/sammchardy/python-binance, устанавливается через pip install python-binance). Важно учитывать, что библиотека неофициальная (Binance её напрямую не поддерживает), но это самый используемый и функциональный инструмент в сообществе.

Минимальный пример получения минутных свечей по фьючерсам:

import datetime as dt
from binance.client import Client, BaseClient
from binance.enums import HistoricalKlinesType

symbol = "*USDT"
start_dt = dt.datetime(..., tzinfo=dt.timezone.utc)
end_dt = dt.datetime(..., tzinfo=dt.timezone.utc)

klines_type = HistoricalKlinesType.FUTURES
interval = BaseClient.KLINE_INTERVAL_1MINUTE

client = Client()

data = client.futures_klines(
    klines_type=klines_type,
    symbol=symbol,
    interval=interval,
    startTime=int(start_dt.timestamp() * 1000),
    endTime=int(end_dt.timestamp() * 1000),
)

client.close_connection()

Чтобы окончательно снять сомнения, я ещё раз вручную проверил через официальный API Binance и сравнил с локальной базой проекта. Все данные полностью совпали. Для прозрачности привожу три последовательные котировки (14:45, 14:46, 14:47 UTC), включая ту, которая вызвала вопрос:

=== Raw quotes ===

[1746456240000, '0.2720500', '0.2728000', '0.2712000', '0.2726500', '110004', 1746456299999, '29917.2349400', 557, '51011', '13887.8376800', '0']
[1746456300000, '0.2724100', '0.2732700', '0.2718400', '0.2721300', '99912', 1746456359999, '27229.3868100', 464, '41200', '11236.2752000', '0']
[1746456360000, '0.2723500', '0.2726500', '0.2719900', '0.2724700', '49486', 1746456419999, '13476.6411000', 349, '24452', '6662.0162200', '0']

=== Human-readable ===

[Date: 2025-05-05  Time: 14:45:00] Open=0.272050, High=0.272800, Low=0.271200, Close=0.272650, Volume=110004.00, Num Trades=557
[Date: 2025-05-05  Time: 14:46:00] Open=0.272410, High=0.273270, Low=0.271840, Close=0.272130, Volume=99912.00, Num Trades=464
[Date: 2025-05-05  Time: 14:47:00] Open=0.272350, High=0.272650, Low=0.271990, Close=0.272470, Volume=49486.00, Num Trades=349

Таким образом, в бэктесте использовались именно «чистые» данные Binance Futures API, без сторонней агрегации.

Спасибо за интересный вопрос!

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

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

Спасибо за интерес к проекту!

Подготовка данных для обучения в данном случае не является сложной задачей — вся логика и структура уже подробно описаны в статье. Реализацию data layer я оставил на усмотрение исследователей, чтобы было пространство для творчества.
Вы можете написать свой data layer под разные площадки (Binance, Bybit, OKX и др.) и тем самым реально прокачать свои навыки.

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

Binance публикует офлайн-архивы сделок (trades, aggTrades) и снимков стакана (bookDepth) на data.binance.vision. Эти данные можно скачивать и использовать для исследований.

Что касается стакана.
Глубина заявок критична в HFT и субсекундных стратегиях, где важна микроструктура рынка. Для минутных моделей, как описано в статье, достаточно OHLCV и при желании тиковых данных, а расширенный стакан добавляет сложность и не всегда даёт прирост качества.

Благодарю за отзыв и отличный вопрос.

Что касается влияния дополнительных фич — на практике классические индикаторы теханализа (RSI, скользящие и т.п.), построенные на основе тех же ценовых рядов, редко дают прирост качества. Современные нейросетевые архитектуры уже умеют извлекать локальные и глобальные паттерны непосредственно из «сырых» OHLCV-данных. Тем не менее, я сторонник экспериментальной проверки: при грамотном комбинировании технических индикаторов и feature engineering вполне возможен прирост точности или устойчивости. Важно не ограничиваться минутными свечами, а рассматривать другие источники данных — например тиковые потоки.

Что касается volume_weighted_average — здесь речь идёт о VWAP (Volume Weighted Average Price). В идеальном случае это считается на тиковом уровне по формуле:

VWAP = \frac{\sum (price \times volume)}{\sum volume}

В нашем случае доступны только минутные OHLCV-свечи, поэтому применяется упрощённый вариант — усреднение по (Open, High, Low, Close). Это приближение, которое позволяет хотя бы частично сохранить информацию о «средней цене сделки» в рамках свечи. Если у вас есть доступ к тиковым данным, всегда стоит использовать точный расчёт.

По поводу мониторинга: агент онлайн отслеживает все активные фьючерсные контракты Binance Futures со статусом TRADING. На текущий момент (30.08.2025) это 521 тикер. Действительно, прибыльные возможности возникают преимущественно на волатильных рынках, но фильтрация делается уже на этапе обработки сигналов. Ошибочные входы случаются — их стоимость контролируется встроенным риск-менеджментом. Это обязательный элемент любой продакшен-системы: ошибки неизбежны, но важно, чтобы они стоили кратно меньше, чем приносят прибыльные сделки.

В предыдущем ответе в псевдокоде при расчёте волатильности была опечатка.
Правильный вариант:

avg_volatility = mean( |Close[i+10] - Close[i]| / Close[i] for i in [t-90 .. t-10] )

Это гарантирует, что волатильность рассчитывается только на 90-минутном предшествующем участке, без захвата импульсного окна.

Спасибо за вопрос!

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


Что касается отбора локальных окон — в проекте предусмотрена гибкая конфигурация: можно менять длину исторического контекста, длительность торговой сессии и набор используемых каналов данных.
Однако сам процесс первичного формирования датасетов (например, под другие биржи или иные условия) действительно остаётся на стороне исследователя: описанная в статье логика легко переносится и может быть реализована в отдельном data layer.

Теперь про 10-минутное окно. Оно не входит в предшествующее 90-минутное окно «тишины». Алгоритм работает так:

  1. Сначала скользящим окном ищется 10-минутный отрезок с изменением цены ≥ 5%.

  2. Если условие выполняется — мы берём 90 минут, предшествующих началу этого окна, и проверяем их на отсутствие сильных движений (контрастность).

  3. Только если обе проверки пройдены — событие фиксируется как сигнал, и формируется финальная 150-минутная сессия (90 мин до + 60 мин после).

Псевдокод:

for each index t in close_prices:
    # проверяем 10-минутное окно на сильный импульс
    change = (Close[t+10] - Close[t]) / Close[t]
    if |change| >= MinAbsChange:

        # считаем среднюю волатильность за 90 минут до начала этого окна
        avg_volatility = mean( |Close[i+10] - Close[i]| / Close[i] for i in [t-90 .. t] )

        if avg_volatility * ContrastCoeff < |change|:
            record signal at time (t+10)

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

Спасибо за вопрос. Выбранные параметры в проекте — это не жёсткие константы, а демонстрационный пресет. Проект изначально сделан так, чтобы вы могли строить свой data layer, подставлять любые значения и тестировать разные комбинации.

Направления для экспериментов:

  1. Порог волатильности: ниже — больше сигналов и выше чувствительность, но больше шума; выше — сигналы реже, но чище.

  2. Окно волатильности: короче — реакция быстрее, но больше ложных входов; длиннее — меньше шум, но медленнее реакция.

  3. Длина сессии: короче — чаще сброс контекста; длиннее — больше истории, полезно для трендов.

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

Для удобства добавил в репозиторий утилиту для просмотра top-N лучших триалов Optuna (не только одного победителя). Это помогает выбирать конфигурацию под свой риск-профиль (например, меньше сделок, но выше точность).

Команда:

python get_info_from_optuna.py configs/alpha.py --n-best-trials 10

Все результаты автоматически сохраняются в той же папке рядом с файлом исследования: output/cfg_name/optuna_cfg_optimization_results/ —> как best_<N>_trials_<metric>.txt и .csv.

Коротко: «утечки из будущего» -> нет.

Я не делаю сессионную нормализацию по окну 150 минут. Per-канальные mean/std считаются один раз только по train-сету, фиксируются и применяются к любому наблюдению на val / test / backtest / онлайне. Каждое состояние в момент времени t строится каузально из последних N минут до t; будущие данные в расчёт не попадают.

Разберем по пунктам:

  • Никакой “150-минутной” z-нормализации. Статистики нормализации не считаются «по текущей сессии» и не знают о её пост-сигнальной части. Это базовый продакшн-паттерн: единое масштабирование, замороженное на train.

  • Кауза соблюдена. Сначала формирую историческое окно до t, затем apply_normalization(...) применяет train-статистики, и только после этого добавляются экстра данные (позиция, unrealized PnL, elapsed/remaining time, one-hot история действий).

  • “В реальной торговле таких данных не получить”. Это неверная предпосылка, как раз наоборот: в онлайне используются предварительно посчитанные train-статистики, а не «динамические» stats по текущему окну. Это исключает дрейф масштабирования и исключает leakage.

  • Про “отрезание первых 90 минут”. Резать ни в коем случае ничего не надо: stats не считаются по индивидуальному 150-минутному эпизоду в принципе. Даже если искусственно пересчитать mean/std только по первым 90 минутам каждой сессии и перегнать оценку, разница в метриках окажется в пределах статистического шума; выигрыша от такой «локальной» нормализации нет, а стабильность хуже.

  • RL ≠ SL. Здесь нет таргетов как в обучение с учителем (возможно именно этот момент вашего опыта в SL пробудил в вас сомнения), которые можно «подсмотреть». Агент видит только историю до t, получает reward каузально по шагам, а нормализация выполняет сугубо инженерную функцию — стабилизацию входов под фиксированные train-масштабы.

Дополнительно: для онлайна именно такая схема (фиксированные train‑stats) и применяется в индустрии: она устойчива и корректно переносится в продакшн‑поток без «онлайн‑оценок будущего».

В проекте, описанном выше, используется модель исполнения, приближённая к рыночным ордерам: вход осуществляется по цене закрытия минуты, на которой зафиксирован сигнал, с учётом заданного проскальзывания (±0.05%) и комиссии (0.04%). Это стандартная практика в исследовательских backtest-платформах, где цель — проверка стратегии принятия решений на минутном таймфрейме, а не симуляция HFT с тиковой глубиной стакана.

Агент не “покупает по цене из прошлого” — момент принятия решения и входа синхронизированы. Проскальзывание моделирует разрыв между последней сделкой и лучшим bid/ask на момент исполнения, что даёт усреднённо реалистичный результат без необходимости хранения L2/L3 данных.

В реальном исполнении система использует отдельный Execution Layer, который работает по логике, исключающей описанные вами риски:

  • Подключение к Binance Futures WebSocket через ThreadedWebsocketManager для получения ценовых данных в режиме высокой частоты.

  • Фиксация решения агента о входе → поиск лучшей цены в стакане для лимитного ордера.

  • Если лимитный ордер не исполняется в заданный временной интервал — ордер отменяется (_clean_orders()), чтобы избежать входа по невыгодной цене.

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

Пример кода Execution Layer:

# Инициализация WebSocket
self._web_socket = ThreadedWebsocketManager(
  api_key=cfg.BINANCE_API, 
  api_secret=cfg.BINANCE_SECRET, 
  testnet=cfg.USE_TESTNET
)

# Подписка на поток котировок
self._web_socket.start_kline_futures_socket(
    callback=self._get_update_event_by_stream(ticker),
    symbol=ticker_name,
    interval=KLINE_INTERVAL
)

# Логика ордера
# ...
self._clean_orders()
position_values, prices = self._get_position_values_and_prices(req_pos)
# …выставление нового лимитного ордера по лучшей цене

Таким образом:

  • В backtest мы моделируем немедленное исполнение по рынку с параметрами, отражающими усреднённые издержки Binance Futures.

  • В боевой системе Execution Layer обеспечивает динамическое размещение и отзыв лимитных ордеров в реальном времени, либо немедленное исполнение по рынку.

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

Это полноценный инженерный контур исполнения, а не теоретическая модель.

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

Спасибо за вопрос — разберу механику на примере этого графика.

Вертикальная пунктирная линия — это момент фиксации высоковолатильного импульса по процедуре, описанной в разделе «Данные» статьи. До этого момента агент не торгует.

В проекте используются минутные котировки OHLCV. Полная свеча за минуту t загружается сразу после её закрытия.

Дальнейший алгоритм:

  1. Минута t закрылась → в систему поступает её OHLCV.

  2. Формируется входное состояние из последних N минут истории (в зависимости от конфигурации).

  3. Агент принимает решение.

  4. При открытии позиции исполнение моделируется по цене закрытия этой же минуты t с учётом параметров среды: slippage ±0.05% и taker-fee 0.04%.

В данном примере сигнал был зафиксирован 2025-01-10 08:56 UTC (индекс 10 на оси времени). Вход в позицию происходит по цене закрытия минуты возникновения сигнала ≈ 0.65, скорректированной на проскальзывание и комиссию. Дальнейшее движение до ~0.78 относится уже к последующим минутам удержания позиции.

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

Хороший вопрос.

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

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

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

Спасибо, ценю ваш отклик. Рад, что проект оказался вам интересен.

Спасибо, рад, что статья оказалась в тему.

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

Для этого подойдут модели семейства sentence-transformers или специализированные финверсии типа FinBERT. Ниже минимальный пример извлечения эмбеддингов с mean pooling:

from transformers import AutoTokenizer, AutoModel
import torch

# Загружаем компактную модель из семейства Sentence Transformers
model_name = "sentence-transformers/all-MiniLM-L6-v2"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)

# Текстовая новость (можно брать заголовок или краткое содержание)
text = "Bitcoin breaks $70K amid macroeconomic pressure."

# Токенизация с усечением и паддингом
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True)

with torch.no_grad():
    outputs = model(**inputs)
    attention_mask = inputs['attention_mask']
    last_hidden = outputs.last_hidden_state

    # Вычисление эмбеддинга через среднее по токенам с учётом attention mask
    embeddings = (last_hidden * attention_mask.unsqueeze(-1)).sum(1) / attention_mask.sum(1, keepdim=True)

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

Если планируете двигаться в сторону полноценной мультимодальной модели, рекомендую обратить внимание на Perceiver IO. Он изначально спроектирован для совместной обработки разнородных входов (временные ряды, тексты, изображения) и позволяет гибко совмещать ценовые данные с текстовыми эмбеддингами в рамках единой архитектуры.

Благодарю за честный комментарий — опыт, которым вы делитесь, очень показателен.

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

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

Модели вроде Dreamer перспективны, но, по моему опыту, без чёткой поведенческой логики склонны к неопределённости или пассивности. Поэтому усложнение архитектуры имеет смысл лишь после стабилизации базовой стратегии.

Благодарю за отзыв и за совет.

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

Однако в данной системе они проходят через мощную цепочку преобразований: логарифмирование, нормализация, агрегация контекста, архитектурная фильтрация через CNN + dueling Q-head. То есть модель не опирается напрямую на "сырые свечки", а учится выделять стабильные поведенческие паттерны.

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

Спасибо, это действительно хороший вопрос.

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

Тем не менее, идея анализа кросс-активных зависимостей (intermarket signals) интересна и перспективна. Архитектура проекта модульная, и расширение входного пространства дополнительными каналами — вопрос конфигурации. Можно, например, включать признаки от коррелированных тикеров, индексов, ставок и других рыночных индикаторов. Но важно учитывать: каждый новый источник — это рост размерности и потенциального шума, что требует строгого контроля качества и борьбы с утечками.

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

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

Спасибо за отличный вопрос.

В алгоритмической торговле действительно существует два конкурирующих подхода к выбору длины бэктест-периода. Приведу несколько аргументов в пользу ограниченного окна в 3 месяца.

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

Во-вторых, есть объективные ограничения по данным. Например, на дату 2025-08-05 на Binance Futures активно торгуются 505 тикеров. Если же взять потенциальный годовой бэктест от последней даты сбора (2025-06-01), то на 2024-06-01 в торговле было лишь 247 тикеров. Это означает, что при расширении окна мы теряем до 51% информации — не в объёме котировок, а в рыночном разнообразии: многие современные паттерны просто отсутствуют, особенно для новых листингов.

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

Наконец, если обратиться к академическому и профессиональному сообществу, строгого правила по длине бэктеста нет. Всё чаще на первый план выходит не временной интервал, а достаточное количество сделок, охватывающих разные рыночные фазы, включая периоды drawdown. Такой подход даёт более честную оценку стратегии, чем расширение по времени.

Information

Rating
Does not participate
Registered
Activity

Specialization

ML Engineer
Lead
Machine learning
Deep Learning
Pytorch
Math modeling
Neural networks
Computer Science
NumPy