preview
Нейросети в трейдинге: Диффузионная генерация торговых планов (DPCC)

Нейросети в трейдинге: Диффузионная генерация торговых планов (DPCC)

MetaTrader 5 — Торговые системы |
61 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Введение

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

В предыдущих статьях мы учитывали распределение возможных результатов при обучении торговой политики. Спектральная оценка усиливала внимание к неблагоприятным исходам. Для выбора действия нужна и проверка конкретных ограничений счёта. План с привлекательной оценкой риска может потребовать слишком много маржи или превысить допустимую просадку.

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

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

Идею такого планирования предложили Ralf Römer, Alexander von Rohr и Angela P. Schoellig в работе Diffusion Predictive Control with Constraints. Метод DPCC объединяет диффузионную генерацию траекторий с проверкой динамики и ограничений. Он может учитывать ограничения, которых не было в демонстрациях. Перенос этого свойства на финансовый рынок требует отдельной проверки.

Диффузионная модель формирует траекторию из шума за несколько шагов. После каждого шага DPCC корректирует предложение по известной модели системы и заданным ограничениям. Следующее уточнение начинается уже с исправленной траектории. Так ограничения участвуют в самой генерации.

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

В первой статье разберём устройство DPCC и начнём его торговую адаптацию со сценарного расчёта. Два OpenCL-кернела дадут историю счёта и диагностику нарушений для заданного плана. Диффузионный генератор и механизм коррекции подключим позже.


Алгоритм DPCC

Исходная область DPCC — робототехника. Авторы обучают политику по демонстрациям и используют её для управления со скользящим горизонтом. Авторы проверили метод в симуляторе манипулятора. Финансовая постановка в этой статье представляет нашу адаптацию подхода.

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

Динамическая система и план действий

Обозначим состояние системы через st, а действие — через at. Номинальная модель f задаёт расчётный переход. Возмущение wt описывает отклонение реального перехода от модели:

s_(t + 1) = f(s_t, a_t) + w_t, ‖w_t‖_2 ≤ δ.

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

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

Прогнозная траектория объединяет будущие состояния и действия. Примем горизонт H, содержащий H переходов. Ему соответствуют H действий и H + 1 состояний:

τ = (s_0, a_0, s_1, a_1, …, s_(H − 1), a_(H − 1), s_H).

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

Для каждого момента задаются допустимые множества состояний 𝒮t + h и действий 𝒜t + h. В робототехнике такие множества ограничивают рабочую область, скорость и управляющее воздействие. В DPCC ограничения задаются при использовании обученной модели. Они могут отличаться от условий сбора демонстраций.

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

Диффузионная генерация траекторий

Диффузионное планирование использует распределение целых траекторий. Этот подход предложен в работе Diffuser и положен в основу DPCC. Генератор обучается по демонстрациям успешного выполнения задачи. Они содержат состояния, действия и цель. По этим примерам модель осваивает разные способы её достижения.

Представим траекторию единым массивом состояний и действий на всём горизонте. При обучении к исходному примеру τ0 добавляется шум. Его уровень определяется номером диффузионного шага j:

τ^j = √(ᾱ_j) τ^0 + √(1 − ᾱ_j) ε, ε ∼ 𝒩(0, I).

Здесь αj = 1 − βj. Величина ᾱj равна произведению коэффициентов αi от первого шага до j. Параметры βj задают расписание зашумления. Сеть предсказывает добавленный шум по искажённому примеру и номеру шага. Это классическая схема DDPM.

Индекс j относится к генерации, а не к будущему торговому бару. На одном диффузионном шаге уточняется вся траектория. Горизонт H определяет её длину во времени. Число обратных шагов J задаёт количество последовательных уточнений. Увеличение одного параметра не означает увеличения другого.

Генерация начинается со случайного массива τJ ∼ 𝒩(0, I). Обратный процесс проходит от J к нулю. На очередном шаге модель вычисляет среднее значение следующего предложения. К нему добавляется случайная составляющая:

τ̂^(j − 1) = μ_θ(τ^j, j, c) + σ_j ε_j, ε_j ∼ 𝒩(0, I).

Величина μθ зависит от параметров сети, текущего массива и контекста c. Коэффициент σj определяет масштаб шума на обратном шаге. В параметризации DDPM среднее вычисляется через предсказание шума εθ.

В исходном DPCC контекст содержит текущее состояние и цель. Он участвует в каждом обратном шаге сети.

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

Проекция с учётом динамики

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

Связь с динамикой определяет допустимые поправки. Нельзя произвольно сдвинуть только одно будущее состояние и оставить прежние действия. После такого изменения переход может перестать соответствовать модели. Проекция согласует траекторию целиком. Уже допустимое предложение не требует поправки.

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

В итоговом алгоритме DPCC сеть сначала вычисляет среднее значение следующего предложения. Затем к нему добавляется шум и применяется проекция. Этот порядок записан в формуле 16 авторской работы. Добавление шума после проекции могло бы снова вывести траекторию за допустимые границы.

Исправленная траектория поступает на следующий шаг генерации. Сеть продолжает уточнение с учётом внесённой поправки. В DPCC эта связь сохраняется на всех обратных шагах. Однократное исправление в конце генерации не даёт такого взаимодействия.

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

Запас на ошибку модели

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

Рассмотрим верхнюю границу состояния b и максимальную ошибку δ. Номинальное следующее состояние ограничим величиной b − δ. Тогда положительное отклонение в пределах δ ещё укладывается в исходный предел b. В многомерном случае вокруг расчётной точки должна помещаться вся область возможных отклонений.

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

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

Выбор траектории и обновление плана

DPCC-T выбирает допустимую траекторию с наименьшим отклонением от перекрывающегося участка предыдущего плана. При сравнении учитывается уже выполненное действие. Это помогает не менять начатый маршрут без причины. Близость к прежнему плану не отменяет проверки текущих ограничений.

DPCC-C выбирает траекторию с наименьшей суммой затрат на проекции. Это сумма квадратов расстояний до исправленных траекторий за все обратные шаги. Малая последняя поправка ещё не означает малого общего вмешательства.

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

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

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

Генератор будет предлагать последовательности действий. Состояния счёта получим их исполнением в финансовой модели. Один план пройдёт через весь набор ценовых сценариев. У него появятся разные истории средств и маржи. Такое разделение отличается от совместной генерации состояний и действий в исходном DPCC.

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

Проверка покажет нарушения ограничений и даст денежную оценку плана. Высокая оценка не отменяет превышения лимита. Спектральную оценку будем применять только к кандидатам, прошедшим сценарный фильтр. Это критерий нашей адаптации, отличный от DPCC-T и DPCC-C.

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

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



Практическая реализация для MetaTrader 5

Сценарный расчёт для MetaTrader 5 выполним в OpenCL. Управление буферами и запуск кернелов организуем средствами MQL5. На вход поступит готовый план. На выходе получим историю счёта и денежную оценку по каждому ценовому сценарию.

Подготовлены два OpenCL-кернела. DPCCRollout проводит счёт через весь горизонт планирования и сохраняет результаты отдельных сценариев. DPCCRolloutReduce объединяет проверки по сценариям. Его выход содержит признак сценарной допустимости каждого плана и общий системный статус расчёта.

Исходные данные и результаты

Обозначим через K число ценовых сценариев, через H — горизонт планирования. Буквой S обозначим вместимость текущего пакета планов. В DPCCRollout ей соответствует slot_capacity, а в DPCCRolloutReduce — slots. Для одного пакета эти значения должны совпадать. При обычной оценке S = M, где M — число исходных кандидатов. При проверке пробных поправок вместимость пакета может превышать M. Какие строки реально используются, показывает маска slot_active.

На каждом шаге действие содержит шесть компонентов: BuyVolume, BuyTP, BuySL, SellVolume, SellTP и SellSL. Разность объёмов покупки и продажи задаёт целевую позицию. Нулевое значение означает её закрытие.

Целевой объём задаёт итоговую позицию после действия. При донаборе открывается только разница между целевым и текущим объёмом. Разворот сначала закрывает прежнюю позицию, затем открывает противоположную. Совпадение целевой и текущей позиций не требует открытия или закрытия объёма. Защитные уровни при этом могут измениться.

Компоненты BuyTP и BuySL относятся к длинной позиции. Для короткой позиции используются SellTP и SellSL. Они задают расстояния до защитных уровней через масштабы инструмента. В расчёте также учитываются сторона котировки, спред и направление позиции.

В таблице указаны размеры буферов для одного пакета. Смещение plan_offset задаёт начало пакета в общем буфере планов. Оно измеряется в элементах типа float. Начало нужного участка маски активности задаёт slot_active_offset. Смещения позволяют работать с частью уже подготовленных данных.

БуферЭлементы пакетаНазначение
plansS × H × 6Последовательности торговых действий.
slot_activeSМаска используемых слотов.
returnsS × KДисконтированная денежная оценка G каждой пары план–сценарий.
statesS × K × (H + 1) × 24Исходное состояние и состояния после каждого перехода.
violationsS × K × 8Сводка нарушений и локальный статус пары.
system_statusS × KОшибки контекста и численного выполнения пары.
branch_diagnosticsS × K × H × 4Диагностика выбора внутрибарной ветви.

У буфера plans нет отдельной оси сценариев. Одна последовательность действий проверяется на всех K вариантах движения цены. Общий набор сценариев обеспечивает одинаковые внешние условия для сравнения планов и пробных поправок.

Буфер context состоит из восьми последовательных секций. В них находятся OHLC-сценарии, исходный счёт, свойства инструмента, ограничения, условия исполнения, число календарных событий, сами события и маска исходных кандидатов. В кернел передаются смещения секций и общая длина буфера. Запись счёта занимает 24 значения. Она содержит баланс, средства, позицию, защитные уровни и маржу. Маска slot_active относится к текущему пакетному вызову и не заменяет исходную маску кандидатов внутри context.

Кернел DPCCRollout

Распределим работу по двум измерениям: ценовым сценариям и слотам планов. Один рабочий элемент OpenCL обрабатывает пару план–сценарий со своим состоянием счёта и участком выходных буферов.

   const uint k = (uint)get_global_id(0);
   const uint slot = (uint)get_global_id(1);
   if(k >= K || slot >= slot_capacity)
      return;
   const size_t pair = (size_t)slot * K + k;
   const size_t state_offset = pair * ((size_t)H + 1) * 24;
   const size_t violation_offset = pair * 8;
   const size_t diagnostic_offset = pair * H * 4;

Индекс pair объединяет номера слота и сценария. По нему вычисляются смещения истории счёта, нарушений и диагностики. Разные пары независимы. Внутри пары переходы последовательны: каждый использует средства, позицию и маржу после предыдущего действия.

Сначала кернел обнуляет выходы своей пары. Индексы отсутствующего нарушения и невыбранной внутрибарной ветви получают −1. Это происходит до проверок входа. Даже раннее завершение не оставит результаты предыдущего запуска.

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

   const int checked = DPCCValidateRolloutContext(context, k, K, H, E, context_M,
                                                  off_account, off_instrument,
                                                  off_limits, off_execution,
                                                  off_calendar_count, off_calendar,
                                                  off_R_active, R_total, expected_price_basis,
                                                  expected_quote_adapter, gamma, volume_scale,
                                                  price_scale, max_quote_age_seconds,
                                                  max_last_age_seconds, money_scale);
   if(checked != 0)
     {
      system_status[pair] = (float)checked;
      violations[violation_offset + 5] = (float)checked;
      return;
     }
   const float active = slot_active[(size_t)slot_active_offset + slot];
   if(!isfinite(active) || (active != 0.0f && active != 1.0f))
     {
      system_status[pair] = (float)DPCC_STATUS_INVALID;
      violations[violation_offset + 5] = (float)DPCC_STATUS_INVALID;
      return;
     }
   if(active == 0.0f)
      return;

Маска принимает только ноль или единицу. Единица включает слот, ноль штатно отключает его. При корректном контексте отключение не меняет system_status. Любое другое значение маски — ошибка входа.

Исходное состояние счёта

Для расчёта создаём структуру DPCCAccountState и копируем в неё 24 значения исходного счёта. Одновременно сохраняем начальную запись в states. Следующие записи соответствуют состояниям после переходов. Поэтому горизонт из H шагов требует H + 1 записей.

Поле equity_anchor связывает модельную переоценку с исходными средствами счёта. Сначала рассчитываем текущий результат позиции и стоимость обеспечения для соответствующего режима. Затем находим постоянную поправку. Она хранится в DPCCAccountState за пределами публичной записи из 24 значений.

   const float initial_pnl = DPCCProfit(account[2], account[3], account[account[2] >= 0.0f ? 15 : 16],
                                        0.0f, instrument[23], instrument[12], instrument[13], instrument);
   const float initial_collateral = (int)instrument[11] == DPCC_CALC_COLLATERAL ?
                                                          fabs(account[2]) * instrument[2] * state.native_mark *
                                                          instrument[24] * instrument[12] :
                                                          0.0f;
   state.equity_anchor = account[1] - account[0] - initial_pnl + account[6] - initial_collateral;
   if(!isfinite(initial_pnl) || !isfinite(initial_collateral) || !isfinite(state.equity_anchor))
     {
      system_status[pair] = (float)DPCC_STATUS_NUMERIC;
      violations[violation_offset + 5] = (float)DPCC_STATUS_NUMERIC;
      return;
     }

Поле account[2] хранит знаковый объём позиции. Поэтому в режиме обеспечения для исходной стоимости используется его модуль. Обеспечение остаётся неотрицательным и для длинной, и для короткой позиции.

При переоценке функция DPCCMark добавляет equity_anchor к балансу и другим составляющим средств. Поправка остаётся постоянной на всём горизонте. Сами средства меняются под влиянием цены, действий и издержек.

Для штатно отключённого слота финансовый переход не запускается. После очистки returns и system_status остаются нулевыми, а violations не содержит нарушений. В states нет рассчитанной истории. Кернел DPCCRolloutReduce устанавливает validity = 0. Это служебное заполнение, а не финансовый результат плана.

Проверка плана и прохождение горизонта

Для активного слота создаём структуру DPCCRiskState. Перед моделированием проверяем весь план: конечность компонентов, разность объёмов и знаки используемых параметров. Проверка охватывает и действия на дальнем горизонте. Они должны быть корректны даже при досрочном переходе счёта в терминальное состояние.

Некорректные компоненты означают отсутствие сценарной допустимости плана. В показанной ветви функция DPCCRiskAdd сохраняет номер нарушения и его шаг. Поле numeric_status получает код DPCC_STATUS_INVALID:

      if(!valid_action)
        {
         DPCCRiskAdd(&risk, 15, 1.0f, 1.0f, h, 1, -1);
         state.numeric_status = DPCC_STATUS_INVALID;
        }

Локальный DPCC_STATUS_INVALID относится к кандидату, а не ко всему запуску. После этой ветви нет выхода, и общий цикл продолжается. Однако DPCCRolloutReduce установит validity кандидата в ноль. Его денежная оценка исключается из выбора. Это не системная ошибка в system_status.

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

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

   DPCCObserve(&state, &risk, limits, volume_scale, 0, 0);
   float result = 0.0f;
   float discount = 1.0f;
   for(uint h = 0; h < H; h++)
     {
      const float equity_before = state.account[1];
      __global const float *action = plans + (size_t)plan_offset + ((size_t)slot * H + h) * 6;
      __global const float *ohlc = context + ((size_t)k * H + h) * 4;
      __global const float *execution = context + off_execution + ((size_t)k * H + h) * 24;
      __global const float *calendar = context + off_calendar + ((size_t)k * H + h) * E * 6;
      float branch[4];
      DPCCRiskState step_risk;
      DPCCRiskInit(&step_risk);
      DPCCAccountStep(&state, &step_risk, action, ohlc, instrument, execution, limits, calendar,
                      (uint)context[off_calendar_count + h], volume_scale, price_scale, h, H, branch);
      DPCCRiskMerge(&risk, &step_risk);
      result += discount * (state.account[1] - equity_before);
      discount *= gamma;
      for(int i = 0; i < 24; i++)
         states[state_offset + ((size_t)h + 1) * 24 + i] = state.account[i];
      for(int i = 0; i < 4; i++)
         branch_diagnostics[diagnostic_offset + (size_t)h * 4 + i] = branch[i];
      if(state.numeric_status == DPCC_STATUS_NUMERIC || !isfinite(result) || !isfinite(discount))
        {
         system_status[pair] = (float)DPCC_STATUS_NUMERIC;
         state.numeric_status = DPCC_STATUS_NUMERIC;
         result = 0.0f;
         break;
        }
     }
   returns[pair] = result;

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

Внутри бара рассматриваются два пути: Open–High–Low–Close и Open–Low–High–Close. Нарушения фиксируются в обеих ветвях, но историю счёта продолжает только одно выбранное состояние. При выборе учитываются защитный выход, средства в конце бара и показатели нарушений. При сравнении выходов по Stop Loss и Take Profit выбирается ветвь со Stop Loss.

Обе ветви начинаются из состояния после текущего действия. На следующий бар переносится только выбранное состояние. Нарушения обеих ветвей сохраняются, но альтернативное продолжение не рассчитывается. Поэтому проверка не охватывает полное дерево сочетаний внутрибарных путей и не доказывает безопасность всех продолжений. Функция DPCCAccountStep вызывается из DPCCRollout без отдельного запуска через OpenCL.

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

Регистрация нарушения не исправляет план: DPCCRollout только читает plans. Например, нехватка маржи для донабора попадёт в диагностику, а расчёт последствий может продолжиться. Шаг, фаза и категория укажут место проблемы. Подбор меньшего объёма или другого действия остаётся задачей последующих этапов.

Переменная equity_before хранит средства до перехода. Их изменение после вызова даёт результат шага с учётом переоценки позиции и расходов. Из этих приращений складывается оценка плана:

r_h = E_h^after − E_h^before
G = Σ_(h = 0)^(H − 1) γ^h r_h

Буква G обозначает накопленный дисконтированный результат пары план–сценарий. Переменная discount начинается с единицы и после каждого шага умножается на gamma. При коэффициенте ниже единицы поздние приращения средств получают меньший вес.

Возьмём изменения средств −20 и +50 денежных единиц. При gamma = 0,99 получим −20 + 0,99 × 50 = 29,5. При gamma = 1 результат составит 30. Буфер returns хранит эту дисконтированную денежную оценку, а не процентную доходность. При gamma < 1 она зависит от времени прибыли и убытка. Поэтому её нельзя считать фактическим итоговым PnL или простым изменением средств.

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

Цикл прерывается при численной ошибке внутри перехода или неконечном значении результата либо коэффициента дисконтирования. Кернел устанавливает системный код ошибки и записывает ноль в буфер результата. Этот ноль не используется для сравнения планов.

Сохранение нарушений

В структуре DPCCRiskState предусмотрено 16 позиций для учёта нарушений. Они охватывают финансовые ограничения и техническую корректность действий. В каждой позиции сохраняется максимальная нормированная величина превышения. После прохождения горизонта вычисляем два сводных показателя:

   float maximum = 0.0f;
   float sum = 0.0f;
   for(int i = 0; i < DPCC_RISK_COUNT; i++)
     {
      maximum = fmax(maximum, risk.maximum[i]);
      sum += risk.maximum[i];
     }

Переменная maximum содержит наибольшее накопленное нарушение. В sum складываются максимумы по категориям. Повторная фиксация того же превышения сама по себе сумму не увеличивает.

Нарушения нормируются по заранее заданным масштабам объёма, цены и денег. В интерфейс входят, в частности, volume_scale, price_scale и money_scale. Эти масштабы должны быть одинаковыми для всех сравниваемых поправок. Значения maximum и sum зависят от них и не имеют общей денежной шкалы. Это показатели для сравнения поправок, а не сумма убытка или спектральная мера риска.

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

Индекс в записи violationsСодержимое
0Шаг самого раннего нарушения. Значение −1 означает отсутствие записи.
1Категория самого раннего нарушения по шагу и фазе.
2Максимальная нормированная величина нарушения.
3Сумма максимумов по категориям нарушений.
4Признак терминального состояния счёта.
5Локальный код состояния пары. Он может пометить некорректный кандидат без системной ошибки запуска.
6Фаза самого раннего нарушения внутри перехода.
7Индекс действия, связанного с нарушением. При отсутствии определённого действия сохраняется −1.

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

Кернел DPCCRolloutReduce

После DPCCRollout определим сценарную допустимость планов и общий системный статус. Кернел DPCCRolloutReduce запускается в одном измерении с числом рабочих элементов slots + 1. Параметр slots равен slot_capacity первого кернела. Дополнительный элемент собирает общий статус.

Каждый из первых рабочих элементов обслуживает один слот. Маска задаёт начальное значение valid: единица для активного слота, иначе ноль. Затем проверяются все K сценариев. Нарушение, ненулевой статус или неконечная сводная величина обнуляют сценарную допустимость. Достаточно одного проблемного сценария.

__kernel void DPCCRolloutReduce(__global const float *system_status,
                                __global const float *violations,
                                __global const float *slot_active,
                                __global float *validity,
                                __global float *aggregate_system,
                                uint K, uint slots, uint slot_active_offset)
  {
   const size_t slot = get_global_id(0);
   if(slot < slots)
     {
      float valid = slot_active[(size_t)slot_active_offset + slot] == 1.0f ? 1.0f : 0.0f;
      for(uint k = 0; k < K; k++)
        {
         const size_t pair = slot * K + k;
         if(system_status[pair] != 0.0f || violations[pair * 8] >= 0.0f ||
            violations[pair * 8 + 5] != 0.0f || !isfinite(violations[pair * 8 + 2]) ||
            !isfinite(violations[pair * 8 + 3]))
            valid = 0.0f;
        }
      validity[slot] = valid;
     }
   else
      if(slot == slots)
        {
         float status = 0.0f;
         for(size_t pair = 0; pair < (size_t)slots * K; pair++)
           {
            const float current = system_status[pair];
            if(!isfinite(current) || (current != 0.0f && current != 3.0f && current != 5.0f))
               status = (float)DPCC_STATUS_NUMERIC;
            else
               status = fmax(status, current);
           }
         aggregate_system[0] = status;
        }
  }

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

Буфер system_status хранит системный статус расчёта пары. Поле violations[5] дополнительно отмечает локальную невалидность кандидата. Для расчёта validity проверяются оба поля. В aggregate_system собираются только значения system_status. Локальный ненулевой код при нулевом системном исключает план, но не весь запуск.

Дополнительный рабочий элемент собирает system_status всех пар. При корректном контексте неактивные слоты оставляют его нулевым. Коды — малые целые значения, записанные в float без арифметических преобразований. Маска проверена на точные 0 и 1. Поэтому сравнение с константами не требует допуска на округление.

Код 5 означает численную ошибку и имеет приоритет над кодом 3 — ошибкой исходных данных в system_status. Неожиданное или неконечное значение также даёт код 5. Ноль в aggregate_system подтверждает отсутствие системных ошибок, но не сценарную допустимость планов.

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

Класс CNeuronScenarioRolloutOCL связывает оба кернела с прямым проходом библиотеки нейронных сетей. После объединения статусов на CPU считывается один элемент. Результаты, состояния и нарушения остаются на GPU для следующих операций.



Заключение

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

Кернел DPCCRollout рассчитывает последствия плана, а DPCCRolloutReduce объединяет проверки по сценариям. Диагностика сохраняет шаг и фазу нарушения. Положительный результат не скрывает превышения лимита, а системная ошибка отделена от локальной невалидности кандидата.

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

Но это только начало пути. В следующей статье будет продолжена работа по адаптации DPCC к задачам финансовых рынков.


Ссылки

Файлы проекта

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

#ИмяТипОписание
1Trajectory.mqhОбщий модульПостроение моделей и вспомогательные функции обучения
2StudyForecast.mq5СоветникОбучение прогнозной модели
3Study.mq5СоветникОбучение базовой модели
4StudyOnline.mq5СоветникОнлайн-обучение модели
5Test.mq5СоветникТестирование модели на отложенном периоде
6NeuroNet.mqhБиблиотека классовБиблиотека классов нейронных сетей
7NeuroNet.clOpenCL-программаOpenCL-кернелы библиотеки нейронных сетей

Исходные файлы доступны в репозитории проекта.

Прикрепленные файлы |
MQL5.zip (7492.99 KB)
Нейронная сеть на практике: Функции активации Нейронная сеть на практике: Функции активации
Это, без сомнения, статья на тему, которую мне до сих пор больше всего понравилось писать. В ней я показал, что на самом деле нам не нужно ничего особенного, чтобы достичь определённой цели. Мы можем достичь этого разными способами, если у нас есть необходимые знания и готовность учиться и посвящать себя этому делу. Я от всей души благодарю всех, кто помог мне с выбором функций. Приведённый здесь материал носит исключительно учебный характер. Ни в коем случае не следует рассматривать это как нечто, предназначенное для чего-либо, кроме обучения и изучения представленных концепций.
От начального к среднему уровню: Подокна (IV) От начального к среднему уровню: Подокна (IV)
В этой статье мы увидим, что не всё так, как многим кажется поначалу. Одна из самых интересных особенностей программирования заключается в том, что мы можем гарантировать, что всё всегда будет именно так, как мы запланировали. Поэтому внимательно прочитайте эту статью, чтобы разобраться в некоторых из самых запутанных понятий, связанных с использованием подокон. Если вы поймёте то, что будет здесь объяснено, вы сможете понять многое из того, что мы будем делать дальше.
От базового к среднему уровню: Ресурсы От базового к среднему уровню: Ресурсы
В этой статье вы познакомитесь с концепцией, которая во многих случаях может оказаться чрезвычайно полезной и значительно упростить обмен вашими приложениями и проектами. Хотя эту концепцию непросто полностью объяснить в одной статье, того, что будет здесь изложено и объяснено, уже хватит, чтобы в будущем мы смогли сделать многое, в том числе и то, что иначе было бы невозможно. Именно поэтому эта статья ещё не была опубликована: чтобы у вас был вспомогательный материал и начальная база для изучения.
Стресс-тестирование последовательностей сделок методом Монте-Карло в MQL5 Стресс-тестирование последовательностей сделок методом Монте-Карло в MQL5
Бэктест показывает лишь одну траекторию из множества возможных исходов. Данный MQL5-скрипт выполняет 1000 бутстрап-передискретизаций Монте-Карло для ряда P&L по сделкам, строит на графике веерную диаграмму процентилей с помощью CCanvas и выводит вероятность разорения, Value at Risk (VaR) и наихудшую просадку на уровне 95-го процентиля. В результате получается практический взгляд на траекторный риск и подверженность просадкам, выходящий за рамки одной кривой эквити.