Comments 32
Было интересно почитать. Вопрос с чем связано такой маленький бэктест в 3 месяца, а не 1 год например? Ведь чем больше временной интервал, тем точнее полученный результат
Спасибо за отличный вопрос.
В алгоритмической торговле действительно существует два конкурирующих подхода к выбору длины бэктест-периода. Приведу несколько аргументов в пользу ограниченного окна в 3 месяца.
Во-первых, крипторынок — крайне динамичная и нестационарная среда, особенно на внутридневном уровне. Его статистические свойства (среднее, дисперсия, волатильность) быстро меняются. Стратегии, основанные на паттернах, актуальных год назад, легко теряют свою эффективность сегодня. Поэтому чрезмерное удлинение периода часто снижает обобщающую способность модели к текущим рыночным условиям.
Во-вторых, есть объективные ограничения по данным. Например, на дату 2025-08-05 на Binance Futures активно торгуются 505 тикеров. Если же взять потенциальный годовой бэктест от последней даты сбора (2025-06-01), то на 2024-06-01 в торговле было лишь 247 тикеров. Это означает, что при расширении окна мы теряем до 51% информации — не в объёме котировок, а в рыночном разнообразии: многие современные паттерны просто отсутствуют, особенно для новых листингов.
Для сравнения, на классических рынках вроде NASDAQ, где торгуются ~3500 тикеров и существуют минутные данные как минимум с 2008 года, годовой бэктест более уместен. Но даже в этом случае всё необходимо проверять эмпирически. На практике (в том числе по моему опыту) перенос подходов, работающих на фондовых рынках, в крипту часто приводит к провалу — условия слишком разные.
Наконец, если обратиться к академическому и профессиональному сообществу, строгого правила по длине бэктеста нет. Всё чаще на первый план выходит не временной интервал, а достаточное количество сделок, охватывающих разные рыночные фазы, включая периоды drawdown. Такой подход даёт более честную оценку стратегии, чем расширение по времени.
Я правильно понял, что трейдинг ведётся по одной переменной? То есть берутся в расчёт данные по одному инструменту? Не пытались улучшить результат за счёт анализа взаимозависимых активов? (ну, типа, когда ставка по доллару растёт, доллар растёт, а золото падает, а когда медь растёт, то акции растут и пр.)
Спасибо, это действительно хороший вопрос.
Да, в текущей реализации агент оперирует минутными данными одного инструмента за сессию. Это сознательное ограничение — основной фокус был на построении устойчивой архитектуры, способной обучаться и действовать в условиях частичной наблюдаемости и высокой волатильности без внешних признаков. Такой подход упрощает трассировку причинно-следственных связей и позволяет глубже интерпретировать поведение агента.
Тем не менее, идея анализа кросс-активных зависимостей (intermarket signals) интересна и перспективна. Архитектура проекта модульная, и расширение входного пространства дополнительными каналами — вопрос конфигурации. Можно, например, включать признаки от коррелированных тикеров, индексов, ставок и других рыночных индикаторов. Но важно учитывать: каждый новый источник — это рост размерности и потенциального шума, что требует строгого контроля качества и борьбы с утечками.
Кроме того, в перспективе возможно добавление текстовых данных — новостей, твитов, постов, комментариев с форумов. Но здесь критически важно находить именно первоисточник информации с точной временной меткой. Только так можно привязать событие к сигналу и адекватно оценить его предсказательную силу. Без этого — высокая вероятность ретроспективного искажения.
Для начала, как прикладную рекомендацию, можно попробовать включить минутные котировки Binance SPOT. Некоторые эксперименты показывают высокую краткосрочную корреляцию между фьючерсами и спотом, как по направлению движения, так и по динамике ликвидности. Это может дать агенту полезный контекст без значительного усложнения архитектуры.
Ваще отлично! не часто так все подробно и с исходниками!
OHLCV -- конечно нет! это все МЛщики мучают.. просто в топку, это нормально не работает.
Советую замутить хотя бы анализ по пробоям/отбоем уровней или какие-нить еще схожие именно "визуал"-данные.
Благодарю за отзыв и за совет.
Что касается OHLCV — согласен, сами по себе эти данные являются только низкоуровневым представлением рыночной динамики. В отрыве от контекста они действительно слабо информативны.
Однако в данной системе они проходят через мощную цепочку преобразований: логарифмирование, нормализация, агрегация контекста, архитектурная фильтрация через CNN + dueling Q-head. То есть модель не опирается напрямую на "сырые свечки", а учится выделять стабильные поведенческие паттерны.
Тем не менее, направление, которое вы упомянули — визуальные или структурные сигналы, — очень перспективно. Их вполне можно внедрить как отдельный канал признаков.
Проходил уже все это, в конечном варианте было 3 сетки, dreamer фантазировал на 200 тиков вперёд, A2C ценность сделки и обоснование и сетка которая наблюдала за двумя этими сетками, и на основании их действий обучалась и подсказывала. В итоге лучшее решение что приняла модель - не торговать вообще.
Благодарю за честный комментарий — опыт, которым вы делитесь, очень показателен.
Такая ситуация часто возникает, когда модель не получает чётких поведенческих ориентиров и работает в среде с низкой сигнальной насыщенностью. В результате — оптимальная стратегия сводится к полной пассивности.
В моём случае ключевой акцент был не на усложнении архитектуры, а на жёсткой формализации среды и структуры вознаграждения. Агент обучается только на сессиях с выраженной волатильностью, действие HOLD без позиции — штрафуется. Это формирует стратегию, в которой агент учится именно действовать, а не избегать риска.
Модели вроде Dreamer перспективны, но, по моему опыту, без чёткой поведенческой логики склонны к неопределённости или пассивности. Поэтому усложнение архитектуры имеет смысл лишь после стабилизации базовой стратегии.
Шикарно! Как раз в процессе написания подобного бота с применением RL, но планирую прикрутить также анализ новостей. Как считаете, достаточно будетанализировать тональность или прям текст векторизовать и подавать ему в качестве эмбеддингов?
Спасибо, рад, что статья оказалась в тему.
Если речь о включении новостей как дополнительного канала признаков, то многое зависит от архитектуры. В целом, тональность — это агрессивная компрессия, которая может работать в простых конфигурациях, но теряет нюансы. Если есть возможность, то лучше использовать эмбеддинги из трансформеров, синхронизированные с рыночными окнами.
Для этого подойдут модели семейства 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. Он изначально спроектирован для совместной обработки разнородных входов (временные ряды, тексты, изображения) и позволяет гибко совмещать ценовые данные с текстовыми эмбеддингами в рамках единой архитектуры.
Прикольно, то есть ставим на комп и зарабатываем или как?
Нет, ставишь на комп и допиливаешь в направлении постепенного перехода от улучшенного DQN к policy-based или модель-ориентированным методам, сопровождаемый:
-расширением данных и признаков,
-учётом неопределённости и риска в награде,
-реальным stream + execution контуром.
Таким образом надо:
1. Переписать replay-буфер: PER + β schedule; добавить n-step sampling.
2. Заменить ε-жадность на NoisyLinear; переобучить базовую сеть.
3. Докрутить Optuna для n-step, γ, β, noisy_σ; 200 trials достаточно.
4. Интегрировать risk-penalty в reward и пересчитать метрики.
5. Ветка A (механика): Transformer-encoder вместо CNN, затем C51.
6. Ветка B (action space): прототип SAC (continuous size), backtest 2025-03—06.
7. Развёртывание dry-run на Binance Testnet с real-time stream.
8. Собрать baseline Dreamer на тех же данных; сравнить sample-efficiency.
9. Выбрать победителя → live ключи под строгими лимитами.
Хороший вопрос.
Этот проект не относится к категории «установил и начал зарабатывать». Он изначально задумывался как исследовательская платформа, которая имитирует реальную торговлю с учётом всех ключевых факторов: комиссий, проскальзываний, ограниченной наблюдаемости и меняющихся рыночных условий.
Его цель — показать, как можно построить архитектуру, способную учиться принимать решения в среде, где ошибки стоят дорого. Здесь можно экспериментировать с агентами, менять модели, тестировать гипотезы, добавлять новые типы данных — но всё это уже на базе готового, воспроизводимого фреймворка с прозрачными метриками и бэктестом.
Если задача — просто «запустить и заработать», то рынок не прощает такой простоты. Но если цель — понять механику и выстроить систему, которая выдерживает реальную рыночную динамику, то это именно тот фундамент, с которого стоит начинать.
зарегался чтоб плюсануть, оч круто !
.
Прошу пояснить вот этот пример:

если правильно понял, то в момент времени 10.0 принимается решение об открытии лонга . Это решение принято потому, что цена изменилась от 0.5 до 0.65. Но , невозможно открыть позицию в момент времени 10.0, так как это цена уже совершенной сделки и по ней принято решение покупать. Очевидно, что покупка реально будет совершена не в 10.0, а в следующий момент. При этом цена покупки будет не 0.65, а 0.78 .
Т е в реальной торговле в этой позиции будет не прибыль, а убыток.
Именно об этом я писал ранее.
Если не согласны, то объясните каким образом совершится сделка по цене, которая уже в прошлом.
Спасибо за вопрос — разберу механику на примере этого графика.
Вертикальная пунктирная линия — это момент фиксации высоковолатильного импульса по процедуре, описанной в разделе «Данные» статьи. До этого момента агент не торгует.
В проекте используются минутные котировки OHLCV. Полная свеча за минуту загружается сразу после её закрытия.
Дальнейший алгоритм:
Минута
закрылась → в систему поступает её OHLCV.
Формируется входное состояние из последних N минут истории (в зависимости от конфигурации).
Агент принимает решение.
При открытии позиции исполнение моделируется по цене закрытия этой же минуты
с учётом параметров среды: slippage ±0.05% и taker-fee 0.04%.
В данном примере сигнал был зафиксирован 2025-01-10 08:56 UTC (индекс 10 на оси времени). Вход в позицию происходит по цене закрытия минуты возникновения сигнала ≈ 0.65, скорректированной на проскальзывание и комиссию. Дальнейшее движение до ~0.78 относится уже к последующим минутам удержания позиции.
Таким образом, агент не «покупает по цене из прошлого» и не ждёт ещё одну минуту: решение и исполнение синхронизированы с моментом появления полной минутной котировки. Именно эти цены учитываются в расчёте PnL и визуализациях действий агента.
Из графика не видно, сколько свечей от времени 10 когда цена=0.65 до значения цены 0.78. Если 1 свеча, то будет явно убыток.
Попробую пояснить, почему невозможно совершить сделку в момент сигнала т е по закрытию свечи. В реальных торгах сделки совершаются не по цене последней сделки, а по ценам наилучшего спроса или предложения, т е не по ценам сделок, а по ценам заявок в стакане.
Цена совершенной сделки - это всегда цена в прошлом.
Кроме того, сам алгоритм формирования цены закрытия свечи предполагает, что сделка будет обозначена как закрытие лишь в случае что истекло время свечи.
Т е от момента сделки до момента, когда 2 минутная свеча закроется может пройти в худшем случае все эти 2 минуты.
Поэтому, если сигнал создается по цене совершенной сделки, то заявка выставленная по этой же цене может никогда не исполнится.
Задание slippage ±0.05% и taker-fee 0.04%. никак не связано ни со спредом в стакане заявок, ни с движением цены на следующей свече. Т е это параметры с "потолка".
В реальности не существует заявок, в которых можно указать диапазон цены для сделки. Либо заявка рыночная, либо лимитная.
Если Вы применяете этого агента в реальном времени, то попробуйте совершить сделки по ценам заявок и сделок и сравните результат.
Заявки в стакане могут очень сильно и часто меняться и при этом сделки будут совершаться редко. Так работают HFT роботы, а их на биржах много.
-------------------
Кроме того, если рынок двигается сильно,как в вашем случае, то лимитная заявка по цене предыдущей сделки часто не будет совершаться очень долго, так как цена в заявках быстро изменится. Что тогда будет делать Ваш агент?
В таких случаях надо либо торговать по рынку, либо быстро передвигать заявку. Как у Вас решается эта проблема?
В проекте, описанном выше, используется модель исполнения, приближённая к рыночным ордерам: вход осуществляется по цене закрытия минуты, на которой зафиксирован сигнал, с учётом заданного проскальзывания (±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 обеспечивает динамическое размещение и отзыв лимитных ордеров в реальном времени, либо немедленное исполнение по рынку.
Проблема «зависших» ордеров или исполнения по цене, которая уже ушла, в системе не существует.
Это полноценный инженерный контур исполнения, а не теоретическая модель.
Вопрос “что будет делать агент” в подобных случаях имеет конкретный ответ — он либо обновляет лимитный ордер по лучшей цене, либо отказывается от сделки, либо входит по рынку в зависимости от заложенной политики риска.
Тоже зарегался чтобы плюсануть, спасибо.
Очень круто!
Можете пояснить,
для тестирования Вы тоже выделяете участки с импульсами или используете все данные с биржи?
Забавная работа, тоже сейчас занимаюсь трейд системой (только на технический идентификаторах), прибыльность на репрезентативных выборках 5-11% за квартал, только торги идут на спокойном рынке (волатильность ~0.002). Не знаю на счёт стержня системы, но как вариант для максимизации прибыли выглядит очень даже хорошо, спасибо за статью
Похоже Вы считаете среднее и отклонение по всему эпизоду (150), а потом с их помощью нормализуете каждое наблюдение (90 или 30). То есть происходит утечка из будущего.
И к тому же при реальной торговле таких данных естественно не получить.
П.С. Я не увидел в коде где при вычислении статс от последовательности отрезаются первые 90. Если я ошибаюсь, укажите, пожалуйста, это место в коде
Коротко: «утечки из будущего» -> нет.
Я не делаю сессионную нормализацию по окну 150 минут. Per-канальные mean/std считаются один раз только по train-сету, фиксируются и применяются к любому наблюдению на val / test / backtest / онлайне. Каждое состояние в момент времени строится каузально из последних
минут до
; будущие данные в расчёт не попадают.
Разберем по пунктам:
Никакой “150-минутной” z-нормализации. Статистики нормализации не считаются «по текущей сессии» и не знают о её пост-сигнальной части. Это базовый продакшн-паттерн: единое масштабирование, замороженное на train.
Кауза соблюдена. Сначала формирую историческое окно до
, затем apply_normalization(...) применяет train-статистики, и только после этого добавляются экстра данные (позиция, unrealized PnL, elapsed/remaining time, one-hot история действий).
“В реальной торговле таких данных не получить”. Это неверная предпосылка, как раз наоборот: в онлайне используются предварительно посчитанные train-статистики, а не «динамические» stats по текущему окну. Это исключает дрейф масштабирования и исключает leakage.
Про “отрезание первых 90 минут”. Резать ни в коем случае ничего не надо: stats не считаются по индивидуальному 150-минутному эпизоду в принципе. Даже если искусственно пересчитать mean/std только по первым 90 минутам каждой сессии и перегнать оценку, разница в метриках окажется в пределах статистического шума; выигрыша от такой «локальной» нормализации нет, а стабильность хуже.
RL ≠ SL. Здесь нет таргетов как в обучение с учителем (возможно именно этот момент вашего опыта в SL пробудил в вас сомнения), которые можно «подсмотреть». Агент видит только историю до
, получает reward каузально по шагам, а нормализация выполняет сугубо инженерную функцию — стабилизацию входов под фиксированные train-масштабы.
Дополнительно: для онлайна именно такая схема (фиксированные train‑stats) и применяется в индустрии: она устойчива и корректно переносится в продакшн‑поток без «онлайн‑оценок будущего».
Спасибо за подробный и развернутый ответ!!!
Я уже пересчитал со статс по 90 и, действительно, результат практически не отличается.
Возник еще один вопрос. В трейне по одной и той же торговой паре конечно же много разных эпизодов и по каждому считается статс. Какой из них (или среднее) потом будет использоваться в реале для этой торговой пары?
Спасибо
Для удобства добавил в репозиторий утилиту для просмотра 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
.
Интересная работа, но возникает вопрос: откуда берутся все ваши пороговые значения/выборы констант. Как по мне это совершенно не очевидный выбор, что с выбором порога высокой волатильности, что выбор сессионного окна, что даже выбор пороговых значений сигналов. Была ли какая-то оптимизация/оценки сверху данных параметров или это не более чем "прикинул на глаз"?
Спасибо за вопрос. Выбранные параметры в проекте — это не жёсткие константы, а демонстрационный пресет. Проект изначально сделан так, чтобы вы могли строить свой data layer, подставлять любые значения и тестировать разные комбинации.
Направления для экспериментов:
Порог волатильности: ниже — больше сигналов и выше чувствительность, но больше шума; выше — сигналы реже, но чище.
Окно волатильности: короче — реакция быстрее, но больше ложных входов; длиннее — меньше шум, но медленнее реакция.
Длина сессии: короче — чаще сброс контекста; длиннее — больше истории, полезно для трендов.
Мой выбор — лишь пример для демонстрации работы пайплайна. Оптимальные значения зависят от ваших целей. Генерируйте гипотезы, проверяйте их и делитесь результатами — проект создан именно для этого.
славно пролив отработал, прям как человек )))
крутая все ж штуковина отработать такой сквиз это прям оч хорошо.
надеюсь там реальная торговля с депом 10$к
/ слежу за ботом в телеге ) /
Вот у Вас в задачах проекта значится:
"Эффективно выявлять скрытые закономерности из исторических данных;"
Очень интересно.
И что-то далее про это ни слова...
RL-агент для алгоритмической торговли на Binance Futures: архитектура, бэктест, результаты