Кривая баланса на бэктестеДинамика капитала на отложенных рыночных данных (период: 2025-03-01 — 2025-06-01), с учётом комиссий и проскальзываний. Итоговое изменение баланса: +144.23%
Кривая баланса на бэктесте
Динамика капитала на отложенных рыночных данных (период: 2025-03-01 — 2025-06-01), с учётом комиссий и проскальзываний.
Итоговое изменение баланса: +144.23%

Введение

Приветствую вас, уважаемые читатели!
Цель данной статьи — предоставить вам полное техническое руководство по созданию торгового агента (проект), обученного с помощью обучения с подкреплением (Reinforcement Learning), на основе архитектуры Dueling Double Deep Q-Network (D3QN) с использованием Prioritized Experience Replay (PER).


Агент разработан для торговли на фьючерсном рынке Binance Futures. Он принимает решения на основе минутных рыночных данных, включая: open, high, low, close, volume, volume_weighted_average, num_trades.

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

Задачи проекта

Построить интеллектуальную систему, способную:

  • Самостоятельно принимать прибыльные торговые решения в условиях высокой волатильности;

  • Эффективно выявлять скрытые закономерности из исторических данных;

  • Демонстрировать высокую обобщающую способность на ранее не встречавшихся данных.

Также в задачи проекта входит создание:

  • Гибкой архитектуры, пригодной для развития в сторону более продвинутых подходов (Dreamer, Soft Actor-Critic и др.);

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

Что вы найдёте в статье

Я подготовил исчерпывающее описание всей системы. Все части проекта воспроизводимы. Код, данные и конфигурации доступны в открытом доступе, все ссылки представлены в конце данного раздела и в финальной части статьи.

Раздел

Содержание

Данные

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

Среда

Механика торговли, детальный расчёт прибыли и убытков (PnL), описание ограничений и особенностей обработки действий агента.

Агент

Архитектура D3QN, буфер Prioritized Experience Replay (PER) и стратегия ε-жадности, позволяющая агенту исследовать рынок и принимать оптимальные решения.

Обучение

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

Тестирование

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

Бэктест

Реалистичная симуляция торговли: комиссии, проскальзывания, риск-менеджмент, интеллектуальный выбор действий и независимая оценка стратегии.

Архитектура проекта

Модульная структура, взаимосвязи компонентов и принципы их взаимодействия.

Выводы

Обзор ограничений текущей системы, перспективы дальнейшего развития и план по переходу к архитектурам следующего поколения (от DQN до Model-Based RL, SAC и др.).

Об авторе

Меня зовут Юрий. Я «Senior Quantitative Researcher» с более чем десятилетним опытом в области машинного обучения и обучения с подкреплением (RL). Основная специализация — проектирование интерпретируемых, масштабируемых и воспроизводимых систем, применяемых на реальных финансовых рынках.

Мотивация

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

  • на реальных рыночных данных;

  • с полным контролем качества и рисков;

  • с высокой воспроизводимостью.

Данный проект создан как реальная исследовательская лаборатория, способная:

  • генерировать и проверять гипотезы;

  • выявлять слабые места в RL для трейдинга;

  • сравнивать SOTA-агентов на реальных данных с честным baseline.

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

Визуализация и контроль

Для глубокого понимания и эффективного мониторинга работы агента в проект встроены модули визуализации. Вы сможете отслеживать:

  • Динамику кривой награды и функции потерь, отражающие прогресс обучения агента;

  • Изменения показателя Win Rate, демонстрирующие долю прибыльных сделок;

  • Распределение PnL по тестовым сессиям, отражающее общую картину эффективности;

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

Исследовательский подход

Чтобы адекватно оценить производительность RL агента, я внедрил независимую baseline-модель — CNN классификатор, обученный в рамках классического обучения с учителем.

Данная модель использует те же тестовые данные, одинаковые метрики (mean PnL, win rate) и оформлена в отдельный модуль со своей конфигурацией.
Baseline-модель даёт нам отправную точку для адекватной оценки RL-агента.

Каждый эксперимент — это изолированная единица, определяемая конфигурацией:

  • Все гиперпараметры, пути и настройки определяются через configs/*.py;

  • При запуске автоматически создаётся директория по имени эксперимента;

  • Внутри — логи, модели и графики: всё хранится по папкам, с учётом продакшен-подхода, применяемого в исследованиях.

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

Бонус

В конце статьи вас ждёт дополнительный бонус — возможность наблюдать за работой обученного ИИ-агента на Binance Futures в реальном времени.

? Код, данные:

2. Описание и подготовка данных

Общая логика формирования обучающего датасета

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


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

2.1 Обоснование

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

  • сопровождаются быстрым движением цены более чем на 5% в течение короткого временного интервала;

  • возникают на фоне предшествующей рыночной инерции (стабильность до сигнала);

  • потенциально вызывают сильное продолжение импульса или резкий откат.

2.2 Логика извлечения торговых сессий

Каждая торговая сессия строится вокруг скользящего окна длиной 10 минут, где выполняется условие:

\text{Volatility} = \left| \frac{P_{\text{close}(t+10)} - P_{\text{close}(t)}}{P_{\text{close}(t)}} \right| \geq 0.05

Далее применяются фильтры:

  • Исключаются сигналы, окружённые шумом. Предшествующие 90 минут анализируются на отсутствие сильных движений:

\text{Filter} = \frac{1}{N} \sum_{i=1}^{N} \left| \frac{P_{i+10} - P_i}{P_i} \right| < \frac{\text{Volatility}}{\lambda}

Где \lambda — коэффициент контрастности (в данной реализации равен 5.0).

  • Если сигнал проходит фильтр, формируется обучающее окно:

Секция

Длина

Назначение

Пре-сигнал

90 мин

Входные признаки для агента

Пост-сигнал

60 мин

Торговая сессия

Общее окно

150 мин

Единица обучающей выборки

2.3 Структура датасета

Каждый элемент датасета представляет собой:

  • np.ndarray формы (150, 7) — 150 минут × 7 каналов;

  • Каналы: open, high, volume_weighted_average, low, close, volume, num_trades;

  • Уникальный ключ: (TICKER, datetime).

Название

Кол-во примеров

Период

Назначение

Train

24 104

[2020-01-14 — 2024-08-31)

Обучение

Validation

1377

[2024-09-01 — 2024-12-01)

Оценка модели

Test

3400

[2024-12-01 — 2025-03-01)

Финальный тест

Backtest

3186

[2025-03-01 — 2025-06-01)

Реалистичная эмуляция

2.4 Примеры визуализаций

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

  • Вертикальная линия на 90-й минуте указывает момент начала торговой сессии.

  • Визуализация строится по каналу close.

  • Заголовок графика содержит: Ticker Name, datetime (точное время сигнала в UTC)

Графики формируются для всех четырех подвыборок: Train, Validation, Test, Backtest.

Train Example
Train Example
Validation Example
Validation Example
Test Example
Test Example
Backtest Example
Backtest Example

2.5 Механика обработки и нормализации данных

Формирование корректного состояния на каждом шаге взаимодействия агента с рынком требует строгой обработки данных, включая:

  • фильтрацию и сортировку каналов;

  • преобразование в относительные значения;

  • нормализацию;

  • логарифмические преобразования;

  • защиту от не числовых и нестабильных значений.

Все ключевые функции реализованы в модуле utils.py и являются частью единого data pipeline:

Функция

Описание

load_npz_dataset(path: str)

Загружает датасет из .npz, возвращая список сессий и метаинформацию

select_and_arrange_channels(...)

Выбирает подмножество каналов, например ['close', 'volume']

calculate_normalization_stats(...)

Вычисляет среднее и стандартное отклонение по каналам

apply_normalization(...)

Формирует финальную версию состояния для обучения агента

Вывод

  • Агент обучается не на всём рынке, а на отобранных высоковолатильных сессиях;

  • Структура данных воспроизводима и управляется конфигурацией;

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

3. Постановка задачи

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


В рамках данного проекта задача формализуется как эпизодическая задача обучения с подкреплением (RL), где агент учится оптимальной политике управления позицией в течение фиксированного временного интервала после появления сигнала высокой волатильности.

3.1 Формализация: RL как торговый процесс

Каждый эпизод начинается в момент возникновения сигнала высокой волатильности (см. раздел 2). Агенту предоставляется:

  • 90 минут рыночной истории — в качестве входных признаков;

  • 60 шагов торговой сессии — окно принятия решений (по одной минуте на шаг).

На каждом шаге выполняется следующий цикл взаимодействия агента со средой:

  1. Агент получает текущее наблюдение s_t​;

  2. На основе состояния s_t​ прогнозирует оптимальное дискретное действие a_t \in \{0, 1, 2, 3\};

  3. Среда возвращает:

    • новое состояние s_{t+1},

    • скалярную награду r_t​,

    • флаг завершения эпизода;

  4. Переход (s_t, a_t, r_t, s_{t+1}) сохраняется в буфер опыта;

  5. При достаточном заполнении буфера агент обновляет параметры Q-функции.

Формально цикл описывается следующей схемой:

s_t \xrightarrow{a_t} (r_t, \, s_{t+1})

3.2 Возможные действия агента

Код

Действие

Описание

0

Hold / Wait

Ничего не делать

1

Open Long

Открыть длинную позицию

2

Open Short

Открыть короткую позицию

3

Close Position

Закрыть позицию

Ограничения:

  • Нельзя открыть новую позицию, если активна текущая;

  • На последнем шаге сессии позиция закрывается принудительно.

Состояние: ( s_t \in \mathbb{R}^D )

Наблюдение включает:

  1. Нормализованные рыночные данные за последние N минут;

  2. Экстра-фичи: позиция, нереализованный PnL, прошедшее/оставшееся время;

  3. One-hot кодированная история последних действий.

# Пример формирования состояния в среде:
extras = [position, unrealized_pnl, time_elapsed, time_remaining]
state = np.concatenate([normalized.flatten(), extras, action_history_onehot])

Награда: ( r_t )

Формируется по формуле:

reward = (pnl_change / initial_balance) - inaction_penalty

Где:

  • pnl_change — реализованный доход при закрытии позиции;

  • inaction_penalty — штраф за бездействие.

Альтернативная математическая запись:

r_t = \frac{\Delta \text{PnL}}{\text{Initial Balance}} - \lambda \cdot \mathbb{I}_{\text{Hold, No Position}}

где ( \lambda = 0.001 ) — штраф за бездействие вне позиции.

3.3 Описание среды: step()

Функция step() в среде TradingEnvironment содержит всю необходимую логику, включая:

  • открытие / закрытие позиций,

  • расчёт прибыли,

  • комиссии,

  • проскальзывание.

3.4 Целевая функция: максимизация дисконтированной награды

Цель агента — стратегия:  \pi(a \mid s) максимизирующая ожидаемую кумулятивную награду:

\pi^* = \arg\max_\pi \mathbb{E} \left[ \sum_{t=0}^{T-1} \gamma^t \cdot r_t \right]

где:

  • ( T= 60 ) — длительность сессии;

  • ( \gamma = 0.99 ) — коэффициент дисконтирования.

3.5 Уравнение Беллмана в контексте трейдинга

Используется модифицированное уравнение Беллмана для Double DQN:

Q(s, a) \leftarrow r + \gamma \cdot Q_{\text{target}}(s', \arg\max_{a'} Q_{\text{policy}}(s', a'))

Реализация в agent.learn():

next_actions = self.policy_net(next_states).argmax(dim=1)
next_q_values = self.target_net(next_states).gather(1, next_actions.unsqueeze(1)).squeeze(1)
target_q_values = rewards + gamma * next_q_values * (1 - dones)
loss = F.smooth_l1_loss(current_q_values, target_q_values)

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

  • Агент открыл Long, рынок вырос → ( r > 0 ), Q увеличивается;

  • Агент выбрал Hold при явной возможности войти → ( r \approx 0 ), Q снижается;

  • Target-сеть стабилизирует обучение через задержку обновлений.

3.6 Псевдокод обучения агента

FOR each episode:
    s₀ ← env.reset()
    FOR t = 1 to T:
        aₜ ← ε-greedy(sₜ)
        sₜ₊₁, rₜ ← env.step(aₜ)
        buffer.add(sₜ, aₜ, rₜ, sₜ₊₁)
        IF len(buffer) > TRAIN_START:
            batch ← buffer.sample()
            loss ← update_Q(batch)
        sₜ ← sₜ₊₁

3.7 Ограничения среды

Элемент

Значение

Комиссия (Taker)

0.04% от объёма

Проскальзывание

±0.05%

Штраф за бездействие

−0.001 по умолчанию

Эти параметры приближают поведение среды к реальной торговле на Binance Futures.

Заключение

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


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

4. Архитектура проекта

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

4.1 Поток данных

  1. train.py:

    • загружает данные через utils.load_npz_dataset();

    • формирует среду: TradingEnvironment(data[i]);

    • инициирует агента D3QN и буфер PER.

  2. Внутри цикла:

    • агент получает state, выбирает action;

    • env.step(action)next_state, reward, done;

    • буфер сохраняет переход; вызывается agent.learn().

  3. model.py:

    • обрабатывает state через CNN;

    • вычисляется:

      Q(s, a) = V(s) + \left( A(s, a) - \frac{1}{|\mathcal{A}|} \sum_{a'} A(s, a') \right)

  4. replay_buffer.py:

    • обновляет приоритеты на основе TD-ошибок;

    • обеспечивает семплирование важных переходов через SumTree.

  5. Логгинг:

    • записываются reward, loss, ε, win_rate;

    • периодическая валидация - метрики сравниваются, сохраняется best.pth.

  6. Бэктест и оптимизация:

    • backtest_engine.py: запускает симуляцию торговли на новых данных;

    • optimize_cfg.py: автоматический поиск оптимальной стратегии через Optuna;

    • Бэктест интегрирован в пайплайн: использует тот же агент, конфигурации и предобработку;

    • Поддерживает параллельные сделки, логгинг, стратегии фильтрации действий, риск-менеджмент.

4.2 Структура проекта

? rl_trading_binance/
│
├── train.py                        # Обучение агента
├── test_agent.py                   # Тестирование агента
├── backtest_engine.py              # Симуляция торговли
├── optimize_cfg.py                 # Поиск оптимальной конфигурации
├── baseline_cnn_classifier.py      # Baseline: CNN классификатор
├── config.py                       # Базовая структура конфигов (pydantic)
├── configs/                        # Отдельные конфиги под эксперименты
│   ├── alpha.py
│   ├── alpha_baseline_cnn.py
│   └── ...
│
├── model.py                        # Dueling CNN + Q-value head
├── agent.py                        # D3QN Agent (PER + epsilon decay)
├── replay_buffer.py                # Prioritized Experience Replay (SumTree)
├── trading_environment.py          # RL-среда с расчётом PnL
├── utils.py                        # Логгинг, нормализация, визуализация, метрики
└── output/
    └── experiment_name/            # Результат 
        ├── logs/
        ├── plots/
        ├── saved_models/
        └── optuna_cfg_optimization_results/

4.3 Конфигурационная система (cfg)

Я внедрил промышленный подход к управлению параметрами: все модули используют централизованный cfg-объект, основанный на pydantic.BaseModel. Он разбит на логические блоки:

Конфиг-блок

Назначение

paths

Пути к данным, логам, моделям, артефактам

seq

Структура входных последовательностей

data

Каналы (OHLCV и доп.), нормализация

rl

Гиперпараметры RL обучения

model

Архитектура нейросети (CNN + Dueling)

trainlog

Параметры логирования и валидации

smart

Настройки стратегии выбора действия

...

Другие: market, device, eps, debug и т.д.

Каждый новый эксперимент описывается как отдельный configs/cfg_name.py, например:

# configs/alpha.py
from config import MasterConfig

cfg = MasterConfig()

ACTION_HISTORY_LEN = 3

cfg.model.cnn_maps = [32, 64, 128]
cfg.model.cnn_kernels = [7, 5, 3]
cfg.model.cnn_strides = [2, 1, 1]
cfg.model.dense_val = [128, 64]
cfg.model.dense_adv = [128, 64]
cfg.model.additional_feats = 4 + ACTION_HISTORY_LEN * 4
cfg.model.dropout_p = 0.1

cfg.trainlog.num_val_ep = 3500
cfg.trainlog.val_freq = 1000
cfg.trainlog.episodes = 55_000
cfg.trainlog.plot_top_n = 10

cfg.per.buffer_size = 230_000

cfg.rl.batch_size = 16
cfg.rl.learning_rate = 1e-4
cfg.rl.train_start = 10_000

cfg.seq.agent_history_len = 30
cfg.seq.agent_session_len = 10
cfg.seq.action_history_len = ACTION_HISTORY_LEN

cfg.backtest_mode = True
cfg.backtest.max_parallel_sessions = 2
cfg.backtest.position_fraction = 0.5
cfg.backtest.selection_strategy = "advantage_based_filter"

При запуске:

python train.py configs/alpha.py

вся структура логирования, моделей и графиков будет сохранена по пути:

output/alpha/
├── logs/
├── plots/
└── saved_models/

Это делает весь проект воспроизводимым, масштабируемым и идеально управляемым.

4.4 CNN baseline модуль

В проекте реализованн один baseline-модуль, который оформлен как независимый .py-скрипт. Он следуют тем же правилам запуска, что и основной агент:

Модуль

Описание

baseline_cnn_classifier.py

Классификация при помощи CNN сети с числом параметров, равным RL-сети

CNN baseline:

  • использует cfg из configs/*.py;

  • автоматически создаёт output/name_cfg/;

  • логирует все метрики (PnL, WinRate, ROC AUC);

  • обучается и валидируется отдельно (cnn_classifier);

  • использует идентичную нормализацию, как и RL-агент (calculate_normalization_stats, apply_normalization);

  • предсказывает классы направления (↑/↓), где таргет: 1, если end_price > start_price; иначе 0.

Это позволяет честно сравнивать baseline-модель и RL-агента по метрикам:

  • mean PnL — суммарный средний доход на сессию;

  • win rate — доля прибыльных сделок.

Вывод

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

5. RL-среда (TradingEnvironment)

Центральный элемент проекта — это симулированная торговая среда, построенная на базе gym.Env (совместима с Gymnasium).
Она моделирует поведение крипторынка на основе исторических минутных котировок, управляет внутренним состоянием торговли, рассчитывает прибыль/убыток и формирует наблюдение для агента.

5.1 Ключевые элементы среды

Среда работает с одной торговой сессией продолжительностью N шагов. Каждый шаг моделирует одну минуту торговли.

Переменная

Описание

balance

Текущий баланс агента (в USD)

position

Текущая позиция: 1 — Long, -1 — Short, 0 — нет

entry_price

Цена входа в позицию

realized_pnl

Совокупный реализованный PnL за эпизод

step_idx

Номер текущего шага в торговой сессии

closed_trades

Количество завершённых сделок

profitable_trades

Количество сделок с положительным PnL

history_actions

Последние k действий (для one-hot-истории)

5.2 Пространства среды

Action Space (дискретный):

self.action_space = spaces.Discrete(4)  # A = {0: Hold, 1: Long, 2: Short, 3: Close}

Observation Space:

self.observation_space = Box(
    low=-inf,
    high=inf,
    shape=(obs_dim,),
    dtype=np.float32
)

5.3 Reward Function

Награда формируется из реализованного изменения PnL, нормализованного на начальный баланс, плюс штраф за бездействие вне позиции.

r_t = \frac{\Delta \text{PnL}_t}{\text{InitialBalance}} - \lambda \cdot 1_{\text{Idle}}

Где:

  • ( \Delta \text{PnL}_t ) — прирост реализованной прибыли за текущий шаг

  • ( \lambda ) — штраф за бездействие

  • ( 1_{\text{Idle}} = 1 ), если агент вне позиции и выбрал Hold

5.4 Торговая логика step(action)

def step(self, action: int) -> Tuple[np.ndarray, float, bool, bool, dict]:
    ...

Упрощенная логика действий:

if action == 1 and position == 0:
    entry_price = price * (1 + slippage)
    position = 1

elif action == 2 and position == 0:
    entry_price = price * (1 - slippage)
    position = -1

elif action == 3 and position != 0:
    if position == 1:
        pnl = (sell_price - entry_price) * qty
    else:
        pnl = (entry_price - buy_price) * qty
    realized_pnl += pnl - fee
    position = 0

Особенность: Action Masking на последнем шаге

В конце каждой торговой сессии, если позиция остаётся открытой, агенту принудительно навязывается единственное допустимое действие — CLOSE. Это реализовано через механизм action masking:

if self.step_id == max_steps - 1 and self.position is not None:
    forced_action = CLOSE

Чтобы минимизировать влияние принудительного закрытия, в вектор наблюдений были добавлены две временные характеристики: elapsed time и remaining time до окончания сессии. Эти признаки позволяют агенту учитывать временной контекст и самостоятельно принимать решение о закрытии позиции до наступления последнего шага.

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

5.5 Формирование состояния (_get_observation())

Каждое состояние — это конкатенация нормализованного окна цен и дополнительных признаков.

Компоненты:

window = self.current_seq[start:end]
normalized = apply_normalization(window, stats, ...)

Extras:

extras = np.array([
    float(self.position),
    unrealized_pnl,
    time_elapsed,
    time_remaining
])

Action History:

hist_onehot = np.zeros(ACTION_HISTORY_LEN * NUM_ACTIONS)
for i, a in enumerate(history_actions):
    if a is not None:
        hist_onehot[i * NUM_ACTIONS + a] = 1

Финальное наблюдение:

state = np.concatenate([
    normalized.flatten(),
    extras,
    hist_onehot
])

5.6 Псевдокод среды

initialize(balance=10_000, position=0, entry_price=0.0)

FOR each episode:
    load sequence from data
    FOR t in 0 to 59:
        state ← get_observation()
        action ← agent(state)

        IF action == Open AND no position:
            enter position
        IF action == Close AND have position:
            exit position
        update balance, position, realized_pnl

        reward ← calc_reward(pnl, penalty_if_no_actions)
        obs_next ← get_observation()
        done ← (t == N)
        return (obs_next, reward, done, info)

Вывод

Торговая среда эмулирует реалистичную рыночную механику с высоким уровнем детализации.

TradingEnvironment:

  • учитывает комиссии, проскальзывание, штрафы

  • предоставляет агенту частичные наблюдения с памятью действий

  • защищает от некорректных действий с помощью action masking

  • возвращает релевантные награды, способствующие обучению прибыльного поведения

6. Архитектура агента (D3QN + PER)

Алгоритм, лежащий в основе агента, - это Dueling Double Deep Q-Network с Prioritized Experience Replay (D3QN + PER). Выбор данной архитектуры обусловлен следующими требованиями:

  • стабильность обучения и высокая способность обобщения;

  • способность различать «важные» состояния от неинформативных;

  • снижение эффекта переоценки Q-функции (overestimation).

6.1 Почему Dueling DQN особенно эффективен в трейдинге?

Классический DQN напрямую оценивает значение каждого действия в состоянии Q(s, a), не разделяя вклад самого состояния и специфики действия. Это делает модель чувствительной к рыночному шуму и усложняет обучение в ситуациях, где различия между действиями несущественны.

В Dueling DQN эта проблема решается через декомпозицию функции ценности на две составляющие:

  • V(s) - скалярная оценка полезности состояния вне зависимости от действия;

  • A(s, a) - преимущество действия a относительно среднего уровня действий в этом состоянии.

Формула:

Q(s,a) = V(s) + \left(A(s,a) - \frac{1}{|A|} \sum_{a'} A(s,a')\right)

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

Пример:

Рассмотрим два сценария:

  1. Пустое состояние ближе к концу сессии: цены стабильны, объёмы снижаются, сигналов нет.

    • V(s) будет низким: рыночная ситуация не представляет интереса.

    • A(s, a) ≈ 0 для всех a: ни одно из действий не даёт ощутимого преимущества.

  2. Сильный импульс на рынке: высокая волатильность и явная направленность движения.

    • V(s) будет высоким: ситуация потенциально прибыльная.

    • A(s, Long) >> A(s, Short): преимущество действия Long ярко выражено.

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

Double DQN

Решает проблему переоценки Q-значений путём разнесения действий:

  • одна сеть выбирает действие (policy);

  • вторая оценивает результат (target).

Формула:

Q_{\text{target}} = r + \gamma \cdot Q_{\theta^-}(s', \arg\max_{a'} Q_{\theta}(s', a'))

Применяется в методе learn().

Prioritized Experience Replay (PER)

Обеспечивает фокусировку обучения на значимых переходах. Вероятность выборки пропорциональна TD-ошибке:

p_i \propto (|\delta_i| + \varepsilon)^\alpha

Где ( \delta_i ) — TD-ошибка, ( \varepsilon ) — сглаживающий параметр.

6.2 Класс агента D3QN_PER_Agent

Описан в agent.py. Реализует:

  • инициализацию policy и target сетей;

  • ( \varepsilon )-жадную стратегию;

  • буфер PER;

  • логику обучения.

Кэш предсказаний для бэктеста

Чтобы ускорить массовую симуляцию и повторное использование модели, агент поддерживает дисковое кэширование Q-значений:

  • Каждое состояние ассоциируется с ключом (тикер, время), по которому сохраняются Q-оценки;

  • При следующем обращении к тому же состоянию, модель не вызывается — результат берётся из qval_cache.pkl;

  • Это снижает время бэктеста в десятки раз и делает возможным реальный перебор сотен конфигураций в Optuna;

  • Кэш автоматически сохраняется и загружается при запуске агента.

# Пример использования кэша:
action = agent.select_action(
    state, training=False, use_cache=True, cache_key=("BTCUSDT", datetime.utcnow())
)

6.3 Псевдокод D3QN + PER

FOR each episode:
    s₀ ← env.reset()
    FOR t = 1 to T:
        aₜ ← ε-greedy(sₜ)
        sₜ₊₁, rₜ ← env.step(aₜ)
        buffer.add(sₜ, aₜ, rₜ, sₜ₊₁)

        IF len(buffer) ≥ TRAIN_START:
            B ← buffer.sample(BATCH_SIZE)
            Compute target_q via Double DQN
            Compute loss = SmoothL1(Q - target)
            Backprop with PER weights
            Clip gradients
            Update policy_net
            Every N steps: sync target_net

Вывод

Архитектура D3QN + PER объединяет лучшие практики обучения с подкреплением:

  • Dueling — разделение оценки состояния и действий;

  • Double — снижение переоценки Q-значений;

  • PER — фокусировка на значимых обучающих переходах;

  • Clip Gradients, Target Sync и Epsilon Decay — стабильность и устойчивость обучения.

Результат — надёжная и масштабируемая реализация D3QN, подходящая для сложных рыночных условий.

7. Нейронная сеть (Dueling CNN-Net)

Для аппроксимации Q-функции используется гибридная архитектура, включающая:

  1. Сверточный блок (CNN) — извлекает краткосрочные рыночные паттерны из временных рядов;

  2. Dueling head — разделённые потоки Value и Advantage, объединяемые в итоговые Q-значения.

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

7.1 Почему CNN?

На первом этапе я использовал сверточную нейросеть (CNN) как стартовую архитектуру по следующим причинам:

  • CNN хорошо подходит для обработки локальных паттернов в временных рядах;

  • она проста в реализации, быстро обучается и легко масштабируется;

  • структура CNN хорошо отражает идею движущегося окна, что интуитивно соответствует анализу рыночных данных.

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

  • iTransformer (Inverted Transformers) — специализируется на time-series;

  • Perceiver IO — эффективно обрабатывает данные переменной длины;

  • Temporal Fusion Transformer — SOTA в мультивариативных временных рядах.

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

7.2 Основные компоненты

Feature Extractor (CNN):

for i in range(len(cnn_maps)):
    Conv2D(in_channels → out_channels, kernel=(k, 1), stride=(s, 1))
    ReLU

Concatenation:

combined = torch.cat([cnn_flat, extras], dim=1)

Value Head:

value = self.value_stream(combined)  # shape: (batch, 1)

Advantage Head:

advantage = self.advantage_stream(combined)  # shape: (batch, num_actions)

Q-финализация:

q_value = value + (advantage - advantage.mean(dim=1, keepdim=True))

7.3 Потенциал для расширения

Возможные направления для дальнейшего развития:

  • Замена CNN на более выразительные архитектуры (см. раздел 7.1): Perceiver IO, TFTransformer, iTransformer;

  • Увеличение receptive field через dilated convolutions — для захвата более длительных рыночных зависимостей.

Вывод

Несмотря на простоту, классическая CNN демонстрирует высокую эффективность при работе с рыночными временными рядами.

CNN:

  • обучается быстро;

  • обладает компактной реализацией (подходит для embedded / edge-сценариев);

  • легко интерпретируема и отлаживаема;

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

Такой баланс делает её оптимальной отправной точкой для RL-проектов, ориентированных на реальные рынки и продакшн-развёртывание.

8. Буфер PER (Prioritized Experience Replay)

Буфер воспроизведения опыта с приоритетами (PER) — это ключевая техника ускорения обучения в DQN-семействе. Вместо равновероятной выборки опытов, как в классическом replay buffer, здесь применяется динамическая приоритезация на основе ошибки обучения (TD-ошибки).

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

8.1 Идея Prioritized Experience Replay

Prioritized Experience Replay (Schaul et al., 2015) предлагает выбирать те переходы, на которых агент ошибается сильнее. Каждому элементу i сопоставляется приоритет pᵢ, определяющий вероятность выборки:

P(i) = \frac{pᵢ^\alpha}{\sumₖ pₖ^\alpha}

где:

  • ( pᵢ = |δᵢ| + ε ) — TD-ошибка с защитой от нуля;

  • ( \alpha \in [0, 1] ) — степень приоритезации (( \alpha = 0 \rightarrow ) равномерная);

  • ( ε ) — положительная константа, исключающая нулевые приоритеты.

8.2 Реализация через SumTree

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

Свойства SumTree:

  • Корень:

     \text{tree}[0] = \sum_i p_i

  • Листья: приоритеты отдельных элементов

Пример:

       [30]
      /    \
    [12]   [18]
   /  \    /  \
 [5] [7] [10] [8]

8.3 Псевдокод буфера

class PrioritizedReplayBuffer:
    def add(exp, td_error):
        p = (abs(td_error) + eps) ** alpha
        sum_tree.add(p)
        data[ptr] = exp
        ptr = (ptr + 1) % capacity

    def sample(batch_size):
        segment = sum_tree.total / batch_size
        batch = []
        for i in range(batch_size):
            z = uniform(i * segment, (i+1) * segment)
            idx, p = sum_tree.find(z)
            prob = p / sum_tree.total
            w = (1 / (N * prob)) ** beta
            batch.append((data[idx], w, idx))
        return batch

    def update_priorities(indices, td_errors):
        for i, delta in zip(indices, td_errors):
            p = (abs(delta) + eps) ** alpha
            sum_tree.set(i, p)

8.4 Связь с агентом

Буфер интегрирован в agent.learn() следующим образом:

batch = buffer.sample(batch_size)
...
td_errors = abs(target_q - current_q)
buffer.update_priorities(indices, td_errors)

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

  • Агент фокусирует внимание на самых проблемных примерах;

  • Но сохраняет контроль над смещением (через importance sampling weights).

Вывод

PER — ключевой компонент в условиях высокой нестабильности, где:

  • важные события редки;

  • сигналы асимметричны;

  • цена ошибки велика.

SumTree и TD-ошибка позволяют систематически и с приоритетом обучаться на значимых ситуациях, избегая потери времени на бессмысленные шаги.

9. Обучение

Процесс обучения реализован в модуле train.py и включает:

  • загрузку и предобработку данных;

  • инициализацию среды, агента и буфера;

  • запуск цикла обучения с валидацией;

  • логирование и сохранение лучших весов;

  • генерацию графиков и отчётных артефактов.

9.1 Цикл обучения

Центральный цикл реализует классическую схему обучения агента с использованием буфера воспроизведения и механизма ε-жадности:

FOR episode IN range(1, EPISODES + 1):
    obs ← env.reset()
    total_reward ← 0
    losses ← []

    WHILE not done:
        action ← agent.select_action(obs)
        next_obs, reward, done, _, info ← env.step(action)

        agent.store_experience(obs, action, reward, next_obs, done)

        IF buffer.ready():
            loss ← agent.learn()
            IF loss:
                losses.append(loss)

        agent.increment_step()
        obs ← next_obs
        total_reward += reward

    логгирование: reward, avg_loss, epsilon, win_rate

    IF валидация включена AND episode % VAL_FREQ == 0:
        metrics ← evaluate(agent, val_env)
        IF улучшение:
            save best.pth

В процессе обучения используется механизм delayed reward, отражающий отложенное влияние решения на итоговую прибыль.

9.2 Логирование и контроль

В проект встроено детальное логирование всех ключевых величин:

  • используется logging (stdout + файл) и tqdm для визуального контроля;

  • создаются отдельные директории логов и графиков для каждого эксперимента: output/logs/, output/plots/;

  • сохраняются: значения на каждом шаге + сглаженное среднее (moving average).

9.3 Метрики обучения

  • Reward R_{episode} = \sum_{t=1}^{T} r_t

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

  • Loss L = \mathbb{E}_{(s,a,r,s')} \left[ (Q(s,a) - y)^2 \right]
    Ошибка TD-обновления. Служит индикатором стабильности обучения. Рост может сигнализировать о переобучении или расхождении Q-функции.

  • Win Rate \text{WinRate} = \frac{\text{Положительных сессий}}{\text{Общее число сессий}}

    Процент торговых сессий, завершившихся положительным итоговым PnL.

Проект автоматически сохраняет визуализации всех ключевых метрик:

График

Назначение

training_rewards.png

Награда по эпизодам

training_losses.png

Ошибка TD обучения

training_win_rate.png

Процент успешных сессий

epsilon_decay.png

Динамика ε-жадности

Все графики сохраняются в папке output/plots/.

Пример: training_rewards.png

График отражает прогресс агента в процессе обучения на протяжении 55_000 эпизодов.
График отражает прогресс агента в процессе обучения на протяжении 55_000 эпизодов.

Анализ обучающей динамики:

  • Начало обучения: средние награды находятся около нуля. Это нормально — агент только начинает осваивать среду и действует почти случайно.

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

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

Итог:

  • Агент демонстрирует стабильное обучение, однако тренд указывает на то, что потенциал далеко не исчерпан.

После завершения обучения сохраняется:

  • лучшая модель best.pth (по метрике на валидации);

  • финальная модель, которая фиксирует веса в конце обучения final.pth.

Вывод

Обучающий pipeline выстроен по всем стандартам индустриального RL:

  • регулярная валидация и логирование;

  • контроль динамики метрик;

  • автоматическое сохранение моделей;

  • полная воспроизводимость через конфигурационный запуск.

10. Тестирование и визуализация

Оценка качества обученного агента проводится в модуле test_agent.py на отложенных, ранее не встречавшихся данных (test_data.npz).

Цель — проверить способность модели обобщать стратегию на новых рыночных сценариях без утечек данных из train/val.

10.1 Структура скрипта test_agent.py

Последовательность действий:

  1. Загрузка тестового датасета.

  2. Подготовка входных данных.

  3. Инициализация среды с тестовыми сессиями.

  4. Загрузка лучшей обученной модели.

  5. Прогон заданного числа тестовых эпизодов.

  6. Расчёт метрик, отрисовка результатов, сохранение артефактов.

10.2 Примеры сессий

Скрипт отрисовывает лучшие и худшие торговые сессии по заданной метрике (pnl или win_rate).
Отрисовка выполняется через plot_sessions().

На каждом графике отображаются:

  • кривая цен закрытия;

  • вертикальная линия начала сессии;

  • действия агента в виде цветных маркеров: ◦ серый — Hold ◦ зелёный — Long ◦ синий — Short ◦ красный — Close

  • подпись: тикер, время, метрика (PnL или win rate)

Пример: profitable_session_1

Profitable Session 1
Profitable Session 1
  • Тикер: HIGHUSDT

  • Агент открыл LONG практически в момент локального минимума и зафиксировал прибыль на резком отскоке вверх.

  • Результат: +1805.02 USDT

Пример: profitable_session_2

Profitable Session 2
Profitable Session 2
  • Тикер: COOKIEUSDT

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

  • Результат: +2562.19 USDT

Пример: unprofitable_session_1

Unprofitable Session 1
Unprofitable Session 1
  • Тикер: BRETTUSDT

  • Агент открыл LONG сразу после просадки цены, далее на первых минутах после открытия позиции Тикер показывал ожидаемое поведение, но затем последовал резкий спад.

  • Результат: −577.59 USDT

  • Вывод: стратегия удержания агента не учла нарастающий нисходящий импульс. Но в реальной торговле подобные ситуации перекрываются риск-менеджментом (Stop Loss).

Пример: unprofitable_session_2

  • Тикер: ARCUSDT

  • Агент стартует с SHORT в момент, когда цена достигает нисходящего пика сигнала, но рынок быстро отскакивает и агент понимая это закрывает позицию. Далее агент вероятно попытался компенсировать убыток, открыв LONG на откате, но движение вверх оказалось слабым, и позиция была закрыта без существенного восстановления.

  • Результат: −161.46 USDT

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

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

10.3 Метрики качества

Во время теста вычисляются агрегированные метрики:

Метрика

Описание

Test_mean_reward

Средняя награда за эпизод

Test_mean_pnl

Средний реализованный PnL за эпизод

Test_win_rate

Средняя доля прибыльных сделок

Test_all_pnls

Массив PnL по всем сессиям (для гистограммы)

Финальные результаты RL-агента на тестовых данных:

  • ▸ Средняя награда (mean_reward): 0.00285

  • ▸ Средний PnL за сессию (mean_pnl): +28.47 USDT

  • ▸ Доля прибыльных сессий (win_rate): 55.67%

> Важно подчеркнуть:

  • Эти результаты были получены при условиях, далёких от максимума возможностей архитектуры.

  • В конфигурации эксперимента намеренно использовались укороченные сессии (10 минут торговли вместо 60) и ограниченный исторический контекст (30 минут вместо 90).

  • Цель — обеспечить визуальную интерпретируемость и возможность воспроизведения на обычной CPU-машине.

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

Это указывает на то, что даже в "демо-режиме" RL-агент уже способен обучиться распознаванию рыночных паттернов, адекватной стратегии входа/выхода и управлению позицией.

Форма распределения наград и стабильность поведения на ранее не встречавшихся данных (test_data.npz) подтверждают обобщающую способность стратегии. На практике это означает, что агент не переобучается под конкретные сценарии, а усваивает общие принципы, применимые к широкому спектру рыночных условий.

Для оценки состоятельности RL-агента в проект был внедрён честный baseline — сверточный классификатор (CNN), обученный в парадигме supervised learning.

Результаты baseline-модели:

  • ▸ Средний PnL: –27.95 USDT

  • ▸ Win Rate: 47.85%

CNN-модель, несмотря сопоставимое число параметров (~256k), не смогла обучиться прибыльной стратегии, она уступает RL-агенту по всем метрикам, что указывает на слабую способность различать прибыльные и убыточные сессии.

Вероятная причина: supervised-подход обучается на статической разметке, не видя последствий своих решений. В то время как агент в рамках RL оптимизирует стратегию с учётом delayed reward, комиссий, риска и временного контекста. Это фундаментальное преимущество подхода обучения с подкреплением в торговле.

10.4 Распределения

Распределение PnL:
Показывает, как часто агент зарабатывает или теряет в рамках одной сессии.

Идеально:

  • распределение асимметрично сдвинуто вправо. Это означает, что агент ограничивает убытки и старается максимизировать прибыль.

Распределение Win Rate:
Показывает долю прибыльных сделок на одну сессию.
Чем ближе к 1.0, тем стабильнее стратегия.

10.5 Визуализация

Все графики сохраняются автоматически:

  • test_pnl_distribution.png

  • test_win_rate_distribution.png

  • profitable_session_*.png

  • unprofitable_session_*.png

Путь: output/plots/

Вывод

Скрипт test_agent.py позволяет:

  • объективно оценить обобщающую способность агента;

  • визуально проверить решения агента на новых данных;

  • получить графическую и статистическую обратную связь о качестве стратегии.

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

11. Бэктест: реалистичная оценка торгового агента

Оценка стратегии в условиях, приближенных к реальной торговле, требует тщательно спроектированного бэктеста.

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


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

11.1 Особенности реализации

Бэктест реализован как полноценный симулятор торговли, с учётом всех ключевых аспектов реального рынка:

  • Реалистичность исполнения:

    • Учитываются комиссии (0.04%) и проскальзывания (±0.05%);

    • Поддерживается риск-менеджмент: стоп-лосс, тейк-профит, трейлинг-стоп.

  • Логгинг сделок:

    • Все действия агента, включая направление, объём, результат, изменение цены и влияние на баланс, записываются в лог backtest_session.log с привязкой к времени.

11.2 Интеллектуальные стратегии выбора действий

Агент может использовать одну из двух стратегий принятия решения:

  • Advantage-based filter, действие принимается только если его advantage превосходит заданный порог уверенности:
    Adv(a)=Q(a)−Q(Hold)>Threshold

  • Ensemble Q-Filter (MC Dropout), выполняется несколько стохастических проходов по сети:

    • вычисляются средние Q-значения и дисперсия (неуверенность);

    • действие принимается, только если одновременно выполнены условия по уверенности и допустимому уровню неопределённости (σ): Adv(a)>θa​,Uncertainty(a)<σmax​, такой подход имитирует логику "soft ensemble" и позволяет гибко управлять допустимым уровнем риска.

11.3 Оптимизация через Optuna

Для нахождения оптимальных гиперпараметров стратегии реализован отдельный модуль optimize_cfg.py, построенный на базе Optuna:

  • Параметры поиска включают:

    • пороги уверенности (long_thr, short_thr, close_thr);

    • включение/отключение риск-менеджмента;

    • значения stop_loss, take_profit, trailing_stop;

    • ограничение на дисперсию в ensemble_q_filter.

  • Бэктест вызывается внутри каждой trial-сессии (run_backtest()), используя кэшированные предсказания;

  • Каждая конфигурация сохраняется, логи ведутся отдельно по триалам;

  • Лучшее решение сохраняется как best_backtest_cfg.json, а графики (optuna_history.png, pareto.png) визуализируют динамику поиска.

11.4 Метрики бэктеста

Система метрик реализована в MetricsCollector и предоставляет полный срез поведения агента. Все метрики автоматически логируются и могут использоваться в качестве целевых для Optuna.

Метрика

Описание

final_balance_change

Финальное изменение капитала в %

total_trades

Общее число совершённых сделок

profit_days

Доля торговых дней с положительным результатом

accuracy

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

sharpe, sortino

Классические risk-adjusted показатели

max_drawdown

Максимальная просадка по балансу

avg_trade_amount

Средний размер сделки

total_commission

Общие потери на комиссиях

correct_avg_change

Среднее изменение цены при верных сделках

incorrect_avg_change

Среднее изменение цены при ошибках

Финальные метрики симуляции на отложенном датасете (backtest_data.npz):

▸ Финальная доходность портфеля:     +144.23%
▸ Sharpe коэффициент:                1.85
▸ Sortino коэффициент:               2.05
▸ Accuracy сигналов:                 69.6%
▸ Максимальная просадка:           	 –22.49%
▸ Кол-во торговых дней:              56
▸ Прибыльных дней:                   44 (78.57%)
▸ Общее число сделок:              	 112
▸ Средняя сделка:                 	 11,324.29 USDT
▸ Сделок в день:                     ~2.00
▸ Комиссионные издержки:           	 –9.68%

Помимо аггрегированных метрик, сохраняется кривая баланса backtest_balance_curve.png (данный график уже был продемонстрирован в начале статьи) и полный лог сделок.

Backtest Balance Curve
Backtest Balance Curve

Агент демонстрирует сбалансированный профиль риск-доходности:

  • достигнута итоговая доходность +144.23%, что соответствует среднему дневному приросту капитала на уровне +1.61%.

  • Значения коэффициентов Sharpe (1.85) и Sortino (2.05) указывают на то, что стратегия обеспечивает положительное соотношение доходности к риску, без чрезмерной зависимости от редких экстремальных результатов.

Примечательно также поведение на уровне микростатистики:

▸ Доля верных long-сделок:    69.9%  (из 93 позиций)
▸ Доля верных short-сделок:   68.4%  (из 19 позиций)
▸ Среднее изменение цены при успешных трейдах:   +4.38%
▸ Средний убыток при ошибке:  –3.67%


Результаты были получены с теми же ограничениями, что и на тренировке: модель с 256k параметров, сессии по 10 минут, контекст — 30 минут. Это всего лишь малая часть от потенциала, заложенного в архитектуру проекта.

Демонстрационный режим: скрытый потенциал, ограничения модели и данных.

Чтобы обеспечить высокую воспроизводимость эксперимента на любой машине (включая ноутбук без GPU), а также упростить визуальный анализ поведения агента, в проекте был активирован облегчённый режим:

▸ Размер модели: ~256,000 параметров
▸ История: 30 минут (вместо 90)
▸ Сессия: 10 минут (вместо 60)

Такая конфигурация была выбрана осознанно, чтобы:

  • ускорить цикл обучения и тестирования;

  • дать пользователю возможность запустить весь пайплайн даже на CPU;

  • показать поведение агента на коротких трейдах — с полной визуализацией всех шагов.

Реалистичная длина сессии в продакшене предполагает:

  • Контекст: 90 минут

  • Длительность: 60 минут

  • Модель > 1 млн параметров

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

  • Увеличить глубину модели

  • Расширить временной горизонт

  • Применить более мощные архитектуры: iTransformer, Perceiver IO

11.5 Режим использования

Бэктест и оптимизация запускаются командами:

# Запуск бэктеста для накопления кэша
python backtest_engine.py configs/alpha.py

# Оптимизация стратегии
python optimize_cfg.py configs/alpha.py --trials 100 --jobs 1

После завершения — можно загрузить best_backtest_cfg.json и использовать найденную стратегию в реальной торговле.

11.6 Пример логов бэктеста

Логирование торговли и метрик реализовано в духе классических торговых платформ. Ниже фрагменты реального лога:

Начало сессии и действия агента:

[Starting backtest...]:
[INFO] : Got 1 signals @ Date: 2025-03-04 Time: 05:19 For Tickers -> GPSUSDT
[INFO] : (SHORT) SELL 27445.27879275 GPSUSDT for 0.17786 at 2025-03-04 05:20
[INFO] : (CLOSE) BUY TP 27434.30068123 GPSUSDT for 0.15744 at 2025-03-04 05:21 PnL = +556.42
...
[INFO] : Got 1 signals @ Date: 2025-04-06 Time: 21:43 For Tickers -> AUCTIONUSDT
[INFO] : (LONG) BUY 653.41295438 AUCTIONUSDT for 13.64841 at 2025-04-06 21:43
[INFO] : (CLOSE) SELL TP 653.15158920 AUCTIONUSDT for 13.97151 at 2025-04-06 21:46 PnL = +203.81

Сводка всех сделок:

[Trades Summary]:
...
[INFO] : 2025-03-27 17:31 LONG  MUBARAKUSDT    5903:   +601.23 (+10.18%  |  +5.09%) PRICE CHANGE: +10.27%
[INFO] : 2025-04-14 15:26 LONG  OMUSDT        11094:   +893.56 ( +8.05%  |  +4.03%) PRICE CHANGE: +8.14%
[INFO] : 2025-04-29 06:14 LONG  INITUSDT      12111:   -236.20 ( -1.95%  |  -0.98%) PRICE CHANGE: -1.87%
[INFO] : 2025-05-21 02:31 LONG  SXTUSDT       13663:   +611.52 ( +4.48%  |  +2.24%) PRICE CHANGE: +4.56%
...

Финальные метрики:

[Final Metrics]:
[INFO] :        total_commission = -9.68%
[INFO] :          avg_commission = -9.13
[INFO] :                max_loss = -3474.09
[INFO] :              max_profit = 5119.57
[INFO] :        total_trade_days = 56
[INFO] :             profit_days = 44 (78.57%)
[INFO] :    final_balance_change = 144.23%
[INFO] :          exp_day_change = 1.61%
[INFO] :            max_drawdown = -22.49%
[INFO] :                  sharpe = 1.85
[INFO] :                 sortino = 2.05
[INFO] :           trades_sharpe = 0.18
[INFO] :          trades_sortino = 0.19
[INFO] :                accuracy = 69.6%
[INFO] :            total_trades = 112
[INFO] :             total_longs = 93
[INFO] :            total_shorts = 19
[INFO] :           longs_correct = 65 (69.9%)
[INFO] :          shorts_correct = 13 (68.4%)
[INFO] :      correct_avg_change = 4.38%
[INFO] :    incorrect_avg_change = -3.67%
[INFO] :        avg_trade_amount = 11324.29
[INFO] :          trades_per_day = 2.00

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

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

12. Выводы

12.1 Итоговый результат

В рамках данного проекта я спроектировал, реализовал и обучил торгового агента на базе обучения с подкреплением (Reinforcement Learning) с использованием Dueling Double DQN и Prioritized Experience Replay для краткосрочной торговли на Binance Futures.


Агент получает нормализованные окна минутных рыночных данных и принимает одно из четырёх действий: HOLD, LONG, SHORT, CLOSE.

Система продемонстрировала:

  • устойчивое обучение на тысячах рыночных сессий;

  • положительный средний PnL на тестовых данных;

  • интерпретируемое поведение с возможностью визуализации;

  • масштабируемость, конфигурационную гибкость и модульность.

12.2 Поведенческие шаблоны агента

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

  • Идентификация импульсов - например, вход в SHORT на экстремумах с последующим выходом при признаках отката;

  • Избежание сделок на флэтовых участках - сниженная активность при малой волатильности;

  • Дисциплина во времени - грамотное завершение позиций ближе к окончанию сессии, до принудительного закрытия;

12.3 Обоснование эффективности

Для объективной оценки агент был сопоставлен с CNN baseline-моделью:

  • Сверточный классификатор (CNN) - архитектура сопоставимая по мощности с агентной моделью.

Результат: агент превзошёл baseline-модель по метрикам mean PnL и Win Rate, что подтверждает состоятельность его стратегии в условиях частично наблюдаемой среды и задержанного вознаграждения.

12.5 Заключение

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

Данная реализация является инфраструктурным фундаментом для дальнейшего исследовательского и инженерного прогресса:

  • Переход от value-based к policy-based методам: Actor-Critic, A3C, PPO, SAC;

  • Модельно-ориентированное обучение: внедрение Dreamer и MuZero с внутренним прогнозированием среды;

  • Расширение пространства действий: от дискретных к непрерывному управлению объёмом и риском (DDPG, TD3);

  • Интеграция ансамблей агентов и адаптивных reward shaping функций;

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

Бонус: Наблюдайте за агентом в реальном времени

Чтобы продемонстрировать работоспособность данной системы на практике, запущен онлайн AI-агент с более продвинутой архитектурой, который в реальном времени анализирует рынок Binance Futures и публикует свои действия в Telegram.

Что делает агент:

  • Мониторит рынок по всем тикерам, каждую минуту.

  • Фиксирует сигналы: публикует тикер, направление сделки и уровень уверенности.

  • Входит и выходит из позиции при оптимальном соотношении риск/награда.

  • Публикует результат сделки: прибыль или убыток.

Live Signal + Prediction                 |                Verification Example
Live Signal + Prediction | Verification Example

Вы можете:

  • Наблюдать за поведением агента в реальных рыночных условиях.

  • Анализировать его логику для совершенствования навыков торговли.

  • Использовать его аналитику для принятия решений.

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

Проект является исследовательским и предоставляется бесплатно исключительно в образовательных целях.

Telegram-канал агента


Что дальше: путь к полной автономии

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

Но для перехода от исследовательского проекта к полноценной автономной торговой системе потребуется ещё два важных компонента:

1. Поток рыночных данных (Stream Layer)

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

Что для этого нужно:

  • Подключение к Binance API (python-binance, ccxt):

    • Загрузка исторических данных (kline 1m);

    • Подписка на WebSocket для live-обновлений.

  • Локальное хранилище:

    • PostgreSQL + TimescaleDB - отличное решение для временных рядов;

    • Запись данных по всем тикерам.

  • Автоматизация:

    • Используйте Airflow для оркестрации задач;

    • Контролируйте полноту, корректность и актуальность данных.

Минимальный стек:

  • python-binance - данные

  • PostgreSQL + TimescaleDB - база

  • Airflow - расписание

2. Исполнение торговых решений (Execution Layer)

Решения агента необходимо конвертировать в прибыльные сделки на реальной бирже.

Что для этого нужно:

  • Интерпретация действий:

    • LONG / SHORT → MARKET / LIMIT - ордер;

    • CLOSE → отмена / закрытие позиции.

  • Связь с Binance (через REST API):

    • Получение баланса, открытие и контроль ордеров;

    • Проверка исполнения и расчёт PnL.

  • Безопасность и контроль:

    • Запуск через Binance Testnet;

    • Поддержка dry-run (логика без реальных сделок);

    • Система алертов (Telegram / email).

Минимальный стек:

  • python-binance - исполнение

  • Telegram Bot API - нотификации

Практический вывод

У вас уже есть мозг системы - стратегически мыслящий RL-агент.
Вам необходимо добавить:

  1. Поток данных → агент начнёт видеть рынок в реальном времени;

  2. Модуль исполнения → агент сможет действовать и зарабатывать.

Реализация этих модулей это отличная возможность усовершенствовать свои навыки и глубже понять:

  • работу с API бирж;

  • потоковую обработку данных;

  • надежную автоматизацию и fault-tolerant дизайн.

Начните с малого: напишите стример данных в базу и модуль dry-run исполнения.
Вы удивитесь, как быстро из прототипа вырастает полноценная торговая система.
Если вы дошли до этого раздела, то я хочу вас поздравить, вы уже на другом уровне.


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


Исходный код, датасеты и работа агента в режиме реального времени:

Комментарии (14)


  1. Greem4ik
    05.08.2025 18:37

    Было интересно почитать. Вопрос с чем связано такой маленький бэктест в 3 месяца, а не 1 год например? Ведь чем больше временной интервал, тем точнее полученный результат


    1. quantAIengineer Автор
      05.08.2025 18:37

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

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

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

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

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

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


  1. Yozh-lyudoyed
    05.08.2025 18:37

    Я правильно понял, что трейдинг ведётся по одной переменной? То есть берутся в расчёт данные по одному инструменту? Не пытались улучшить результат за счёт анализа взаимозависимых активов? (ну, типа, когда ставка по доллару растёт, доллар растёт, а золото падает, а когда медь растёт, то акции растут и пр.)


    1. quantAIengineer Автор
      05.08.2025 18:37

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

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

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

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

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


  1. SatCat
    05.08.2025 18:37

    Ваще отлично! не часто так все подробно и с исходниками!

    OHLCV -- конечно нет! это все МЛщики мучают.. просто в топку, это нормально не работает.

    Советую замутить хотя бы анализ по пробоям/отбоем уровней или какие-нить еще схожие именно "визуал"-данные.


    1. quantAIengineer Автор
      05.08.2025 18:37

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

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

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

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


  1. myltik81
    05.08.2025 18:37

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


    1. quantAIengineer Автор
      05.08.2025 18:37

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

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

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

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


  1. ITS_HOT
    05.08.2025 18:37

    Шикарно! Как раз в процессе написания подобного бота с применением RL, но планирую прикрутить также анализ новостей. Как считаете, достаточно будетанализировать тональность или прям текст векторизовать и подавать ему в качестве эмбеддингов?


    1. quantAIengineer Автор
      05.08.2025 18:37

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

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

      Для этого подойдут модели семейства 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. Он изначально спроектирован для совместной обработки разнородных входов (временные ряды, тексты, изображения) и позволяет гибко совмещать ценовые данные с текстовыми эмбеддингами в рамках единой архитектуры.


  1. TryDotAtwo
    05.08.2025 18:37

    Прикольно, то есть ставим на комп и зарабатываем или как?


    1. Robomanus
      05.08.2025 18:37

      Нет, ставишь на комп и допиливаешь в направлении постепенного перехода от улучшенного 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 ключи под строгими лимитами.


  1. lexa_15
    05.08.2025 18:37

    зарегался чтоб плюсануть, оч круто !


    1. quantAIengineer Автор
      05.08.2025 18:37

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