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

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

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

Введение

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

Продолжим адаптацию фреймворка Diffusion Predictive Control with Constraints (DPCC). В авторском методе диффузионная модель генерирует состояния и действия. После формирования очередного предложения и добавления шума применяется проекция по динамике и ограничениям. Исправленная траектория поступает на следующий обратный шаг. В нашей постановке генератор предлагает только действия, а состояния счёта вычисляются по ценовым сценариям. Сегодня подготовим расчётчик и проектор. Позднее их взаимодействие войдёт в сам процесс генерации.

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

Во второй статье перешли к поиску поправок. Семь новых OpenCL-кернелов сохраняют оригинал, формируют варианты действий, оценивают последствия и выбирают поправку по нарушениям. Мы предусмотрели повторную проверку и бюджет поиска. Теперь эти операции нужно связать с MQL5-классами.

Создадим два слоя: CNeuronScenarioRolloutOCL для расчёта и CNeuronPlanProjectionOCL для коррекции. Разберём данные, ключевые методы и взаимодействие объектов. Это подготовит их к использованию в торговой модели DPCC.


Сценарный расчёт торгового плана

Сценарный расчёт организует CNeuronScenarioRolloutOCL. Класс наследуется от CNeuronBaseOCL и использует его интерфейсы. Он выполняет финансовые переходы по установленным правилам.

Пусть M — число планов, K — число сценариев, H — горизонт. Действие содержит шесть компонентов: BuyVolume, BuyTP, BuySL, SellVolume, SellTP и SellSL.

Для инструмента предусмотрена одна нетто-позиция. Разность BuyVolume и SellVolume задаёт её целевой объём в лотах. Нулевая разность означает закрытие. Например, два объёма по 0,5 лота задают ту же нулевую цель, а не встречные позиции. Параметры TP/SL при нулевой цели не задают новые уровни. Конечность всех шести компонентов всё равно проверяется. При локальном приведении нулевая цель записывается шестью нулями.

Донабор или сокращение рассчитывается как разница между целевой и текущей позицией. При смене направления сначала закрывается прежняя позиция, затем открывается противоположная. Учитываются расходы обеих операций и разрешение на их исполнение. Для новой позиции используются TP/SL выбранной стороны.

Пакет занимает M×H×6 значений. Каждый план проверяется на всех K сценариях без изменения действий. Выбирать объём по заранее неизвестному развитию цены нельзя.

Расчётчик получает готовые сценарии латентного прогноза. Сам класс не декодирует признаки. Вместе с планами передаём финансовый контекст через дополнительный вход SecondInput. Структура SDPCCLayout задаёт смещения его восьми секций:

СекцияРазмерСодержание
InputsK×H×DСценарии рыночного движения
Account24Начальное состояние счёта и позиции
Instrument48Свойства торгового инструмента
Limits24Ограничения и параметры риска
ExecutionK×H×24Условия исполнения и расчётные издержки
CalendarCountHОбщее для сценариев число записей на шаге
CalendarK×H×E×6Календарь расчётных событий
SlotActiveMПризнаки активности планов

E задаёт вместимость календаря на шаге. В CalendarCount хранится целое число от 0 до E для каждого бара. Все сценарии читают одинаковое количество записей из собственных участков Calendar. Содержимое записей может различаться. Разное число событий по сценариям этот формат напрямую не задаёт.

BuildLayout рассчитывает смещения в элементах float и проверяет переполнения. Маски активности принимают только 0 или 1. Длины секций не заменяют покомпонентный контракт.

Основной выход имеет форму M×K и содержит денежные оценки G. Отдельно хранятся H+1 состояний счёта на пару план–сценарий, признаки допустимости и диагностика. Начальный снимок включён в историю. Здесь и далее допустимость относится только к переданным сценариям и принятой модели исполнения.

В начале Account расположены баланс, средства, подписанный объём, средняя цена позиции, SL и TP — поля 0–5. Поля 15 и 16 содержат Bid и Ask, 17 и 18 — флаги наличия SL и TP. Средства учитывают плавающий результат. Остальные поля нужны для маржи, издержек, контроля потерь и продолжения расчёта. Их порядок должен совпадать с финансовым переходом, а не только с длиной записи.

ReadDescriptor проверяет CLayerDescription, включая число выходов M×K. Затем Init вызывает базовый метод и выделяет буферы:

if(!CNeuronBaseOCL::Init(0, myIndex, open_cl, desc.count, desc.optimization, desc.batch))
   return(Fail(DPCC_OPENCL_ERROR, true));
m_myIndex = (int)myIndex; activation = None;
bTrain = false;
m_configured = false;
m_fixed = false;
ZeroMemory(m_config);
if(!EnsureBuffer(m_states, m_shape.M * m_shape.K * (m_shape.H + 1) * 24))
   return(Fail(m_status, true));
if(!EnsureBuffer(m_validity, m_shape.M))
   return(Fail(m_status, true));
if(!EnsureBuffer(m_violations, m_shape.M * m_shape.K * 8))
   return(Fail(m_status, true));
if(!EnsureBuffer(m_diagnostics, m_shape.M * m_shape.K * m_shape.H * m_shape.D))
   return(Fail(m_status, true));

В m_states хранится история счёта, в m_validity — признаки допустимости. На пару план–сценарий в m_violations приходится восемь полей, а в m_diagnostics — D поля на переход.

Метод EnsureBuffer сверяет размер с ёмкостью GPU-буфера. Подходящая память используется повторно, несовместимая пересоздаётся. Рабочие области готовятся до расчёта.

Configure задаёт финансовый профиль. Масштабы должны быть положительными и конечными, коэффициент γ — удовлетворять условию 0 < γ ≤ 1.

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

Общий валидатор OpenCL проверяет цены, инструмент, маски и лимиты. Активный предел объёма должен быть неотрицательным, нижняя граница TP/SL — не выше верхней. Значения NaN, бесконечности и противоречивые диапазоны вызывают отказ. Поля Instrument и Limits должны соответствовать одной версии финансового контракта.

Прямой проход выполняет feedForward с SecondInput. Без финансового контекста вызов отклоняется. До запуска проверяются формы, OpenCL-контекст и возможные пересечения буферов.

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

if(!RolloutPlans(source.getOutput(), 0, m_shape.M, context, (uint)m_layout.offsets[7], context,
                 m_return_scratch, m_state_scratch, m_violation_scratch, m_diagnostic_scratch))
   return(false);
if(!ReduceValidity(context, (uint)m_layout.offsets[7], m_shape.M, m_violation_scratch, m_validity_scratch))
   return(false);

RolloutPlans передаёт в DPCCRollout планы, контекст, выходные буферы и маску. В публичном проходе маска берётся из SlotActive. В EvaluatePlanBatch используется явно переданная slot_active длиной slot_capacity; маска исходных M планов не накладывается повторно на 9M предложений. Проверка общего контекста сохраняется.

OpenCL обрабатывает K сценариев и slots планов. Один рабочий элемент последовательно выполняет H переходов для своей пары. Разные пары независимы, но соседние бары используют общее состояние счёта. Параллельно рассчитываются планы и сценарии, а не переходы внутри одной истории.

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

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

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

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

Выбор неблагоприятного пути сам по себе не подтверждает учёт частичного исполнения, requote, stop-out и изменения маржинальных требований. Для них нужны отдельные правила и проверки модели.

После каждого перехода сохраняем состояние счёта. Денежную оценку строим по изменениям средств Equity. Для плана m и сценария k:

G(m, k) = Σh=0…H−1γh × [Equityh+1 − Equityh]

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

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

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

После обработки сценариев метод ReduceValidity запускает DPCCRolloutReduce. Здесь отделяем торговую недопустимость плана от системной ошибки расчёта.

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

Для объединения результатов кернел запускается по одному рабочему элементу на каждый слот и ещё одному для общего системного статуса:

const uint offset[] = {0};
const uint global[] = {slots + 1};
if(!OpenCL.Execute(kernel, 1, offset, global))
   return(Fail(DPCC_OPENCL_ERROR));
//--- Only the single GPU system verdict crosses the host boundary.
if(!m_aggregate.BufferRead())
   return(Fail(DPCC_OPENCL_ERROR));
const float status = m_aggregate[0];
if(status == 3)
   return(Fail(DPCC_INVALID_INPUT));
if(status == 5)
   return(Fail(DPCC_NUMERIC_ERROR));
if(status != 0)
   return(Fail(DPCC_OPENCL_ERROR));
return(true);

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

Возвращаемое ReduceValidity значение true подтверждает успешное выполнение вычислений. Оно не означает, что каждый план допустим. Для такого вывода нужно прочитать индивидуальные признаки в m_validity и учесть маску активности.

В m_aggregate код 0 означает отсутствие системной ошибки, 3 — некорректные исходные данные, 5 — численную ошибку. Коды 3 и 5 переводятся соответственно в DPCC_INVALID_INPUT и DPCC_NUMERIC_ERROR. Кернелы записывают малые целые коды в float без округления и усреднения, поэтому точное сравнение здесь корректно.

DPCC_OPENCL_ERROR в показанном методе используется при отказе OpenCL-вызова или неожиданном коде ответа. Он не всегда означает неисправность устройства. Нулевой денежный исход сам по себе ошибкой не является.

После успешной проверки слой публикует результаты. Вместо копирования больших массивов feedForward меняет местами указатели рабочих и публичных буферов:

   CBufferFloat *temporary = Output;
   Output = m_return_scratch;
   m_return_scratch = temporary;
   temporary = m_states;
   m_states = m_state_scratch;
   m_state_scratch = temporary;
   temporary = m_validity;
   m_validity = m_validity_scratch;
   m_validity_scratch = temporary;
   temporary = m_violations;
   m_violations = m_violation_scratch;
   m_violation_scratch = temporary;
   temporary = m_diagnostics;
   m_diagnostics = m_diagnostic_scratch;
   m_diagnostic_scratch = temporary;
   m_public_valid = true;
   m_phase = DPCC_LAYER_READY;
   return(true);

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

getOutput возвращает финансовые оценки, GetStates — историю счёта, GetValidity — допустимость. Нарушения доступны через GetViolations, внутрибарная диагностика — через GetDiagnostics.

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

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

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

Этот маршрут реализует EvaluatePlanBatch. Он принимает планы, маску активности, контекст и выходные буферы. Затем выполняет общий сценарный расчёт:

   if(!PrepareScratch(slot_capacity))
      return(false);
   m_fixed = true;
   if(!RolloutPlans(plans, plan_offset, slot_capacity, slot_active, 0, rollout_context,
                   returns_out, states_out, violations_out, m_batch_diagnostics))
      return(false);
   return(ReduceValidity(slot_active, 0, slot_capacity, violations_out, m_batch_validity));

plan_offset задаёт смещение в элементах float, slot_capacity — вместимость пакета, slot_active — его активные места. Для предложений вместимость равна 9M. Одинаковые размеры и маска используются при расчёте и сведении результатов. Все оценки текущей коррекции получают один неизменный финансовый контекст.

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


Коррекция с учётом ограничений

Вернёмся к плану с донабором позиции. Если перед донабором уменьшилась свободная маржа, прежний объём может нарушить лимит. Изменим одно из действий и повторим расчёт до конца горизонта. Так проверим последствия поправки.

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

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

Класс CNeuronPlanProjectionOCL наследуется от CNeuronBaseOCL. Вход и выход имеют одинаковую форму M×H×6. Исправление не меняет длину плана и не создаёт отдельный план для каждого сценария.

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

Сценарные последствия проверяет тот же CNeuronScenarioRolloutOCL. Связь устанавливается методом BindRollout:

bool CNeuronPlanProjectionOCL::BindRollout(CNeuronScenarioRolloutOCL *rollout)
  {
   m_public_valid = false;
   m_rollout = NULL;
   if(!m_configured || m_phase == DPCC_LAYER_FAILED || CheckPointer(rollout) == POINTER_INVALID ||
      rollout.Type() != defNeuronScenarioRolloutOCL ||
      !rollout.MatchesProjectionContract(m_shape, m_config, OpenCL) || !ValidateWorkspace())
      return(Fail(DPCC_CONTRACT_MISMATCH));
   m_rollout = rollout;
   m_phase = DPCC_LAYER_READY;
   m_status = DPCC_OK;
   return(true);
  }

MatchesProjectionContract проверяет совместимость форм, конфигурации и OpenCL-контекста; тип отдельно проверяется в BindRollout. Эти проверки не сравнивают рыночные снимки. Оригинал и поправки должны получать одинаковые OHLC, исходный счёт, лимиты, календарь и условия исполнения. Между оценками меняются планы и соответствующие маски, но не внешние условия.

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

Сначала создаём и настраиваем CNeuronScenarioRolloutOCL, затем — CNeuronPlanProjectionOCL. После успешного BindRollout можно запускать прямой проход. Несогласованные финансовые настройки привели бы к неверному сравнению планов.

Метод инициализации готовит формы данных и буферы. Configure задаёт лимиты поиска. После загрузки сохранённого объекта связь с расчётчиком устанавливается заново.

Для поиска нужны несколько копий плана. В составе m_work буфер DPCC_PW_ORIGINAL хранит оригинал, DPCC_PW_CURRENT — текущий вариант, а DPCC_PW_PROPOSAL — предложения. Отдельные области содержат финансовые состояния, нарушения, маски и диагностику.

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

Размеры рабочих областей рассчитывает WorkspaceSizes:

   const ulong mh6 = (ulong)m_shape.M * m_shape.H * 6;
   const ulong mk = (ulong)m_shape.M * m_shape.K;
   const ulong mp = (ulong)m_shape.M * 9;
   const ulong mpk = mp * m_shape.K;

Здесь mh6 равно M×H×6, mk — M×K, mp — 9M, а mpk — 9M×K. Девять мест на план отводятся под локальные предложения.

История для всех альтернатив занимает 9M×K×(H+1)×24 элементов. Метод EnsureWorkspace выделяет память заранее. ValidateWorkspace проверяет фактическую ёмкость и пересечения областей памяти.

Основной маршрут feedForward принимает планы и финансовый SecondInput. После проверки связи с расчётчиком начинается исходная оценка:

  m_fixed = true;
   if(!ValidatePlans(source.getOutput(), context))
      return(false);
   if(!m_rollout.EvaluatePlanBatch(m_work[DPCC_PW_CURRENT], 0, m_shape.M,
                                  m_work[DPCC_PW_BASE_ACTIVE], context,
                                  m_work[DPCC_PW_BASE_RETURN], m_work[DPCC_PW_BASE_STATE],
                                  m_work[DPCC_PW_BASE_VIOLATION]))
      return(Fail(m_rollout.LastStatus()));
   if(!Summarize(false, m_work[DPCC_PW_CURRENT], m_work[DPCC_PW_BASE_ACTIVE]) ||
      !BuildRepairIndexOrder(context, 1))
      return(false);

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

Затем EvaluatePlanBatch рассчитывает исходные финансовые траектории. Метод Summarize запускает DPCCProjectionSummary. В сводке появляются допустимость, максимум и сумма нарушений, расстояние до оригинала и первое проблемное событие.

Максимум показывает сильнейшее нормированное нарушение. Сумма объединяет максимумы категорий по всем сценариям без вероятностных весов и деления на K. Она зависит от состава набора. Сравнивать поправки нужно при неизменных сценариях и шкалах нормировки. Ни эти показатели, ни расстояние до оригинала не равны прибыли.

Первый вызов BuildRepairIndexOrder запускает стадию 1 кернела DPCCProjectionPrepare. Проверенный допустимый оригинал сразу сохраняется без изменений. Для плана с нарушениями локальное приведение начинается только при наличии ещё двух оценок: для его результата и заключительной проверки. Иначе возвращается неизменённый план со статусом 2.

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

Для активных записей после приведения вновь вызываем EvaluatePlanBatch и Summarize:

   if(!m_rollout.EvaluatePlanBatch(m_work[DPCC_PW_CURRENT], 0, m_shape.M,
                                  m_work[DPCC_PW_BASE_ACTIVE], context,
                                  m_work[DPCC_PW_BASE_RETURN], m_work[DPCC_PW_BASE_STATE],
                                  m_work[DPCC_PW_BASE_VIOLATION]))
      return(Fail(m_rollout.LastStatus()));
   if(!Summarize(false, m_work[DPCC_PW_CURRENT], m_work[DPCC_PW_BASE_ACTIVE]) ||
      !BuildRepairIndexOrder(context, 2) || !ReadCommon(0, common))
      return(false);

На стадии 2 DPCCProjectionPrepare использует новую сводку. Прошедший проверку план направляется на стадию 4 — заключительную оценку. Туда же переходят планы без доступных индексов или с остатком бюджета только на завершение. Остальные получают стадию 3 — локальный поиск.

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

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

projection_lookback_steps ограничивает число проверяемых индексов, а не строгое расстояние в барах. Связанное действие может находиться дальше ближайших шагов. При нуле список пуст. Стадия 2 переводит такой план на заключительную проверку, не включая поиск. После успешной подготовки common равен 0. Нулевой предел волн не вызывает ошибку.

Порядок хранится в DPCC_PW_ORDER. У каждого плана свой курсор. Без улучшения он переходит к следующему шагу, а после принятой поправки порядок пересчитывается.

BuildLocalProposals запускает DPCCProjectionProposals. Кернел создаёт копии текущего плана и меняет в каждой только одно выбранное действие.

Предусмотрено девять типов поправок: уменьшение объёма до 75%, 50%, 25% или нуля; возврат к предыдущей цели; четыре граничных варианта TP/SL. Для первого шага предыдущая позиция берётся из исходного счёта. Остальная последовательность сохраняется.

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

Повторы и явно запрещённые направления исключаются. Маска DPCC_PW_PROPOSAL_ACTIVE отмечает активные предложения. Оставшийся бюджет ограничивает их количество.

Метод EvaluateProposals передаёт готовый пакет общему финансовому расчётчику:

bool CNeuronPlanProjectionOCL::EvaluateProposals(CBufferFloat *context)
  {
   if(!m_rollout.EvaluatePlanBatch(m_work[DPCC_PW_PROPOSAL], 0, m_shape.M * 9,
                                  m_work[DPCC_PW_PROPOSAL_ACTIVE], context,
                                  m_work[DPCC_PW_PROPOSAL_RETURN], m_work[DPCC_PW_PROPOSAL_STATE],
                                  m_work[DPCC_PW_PROPOSAL_VIOLATION]))
      return(Fail(m_rollout.LastStatus()));
   return(Summarize(true, m_work[DPCC_PW_PROPOSAL], m_work[DPCC_PW_PROPOSAL_ACTIVE]));
  }

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

Метод SelectImprovement запускает DPCCProjectionSelect. Для каждого исходного плана выбирается не более одного улучшения.

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

Например, максимум снизился с 0,40 до 0,38, а сумма выросла с 0,50 до 0,60. При допуске 0,005 поправка проходит сравнение по максимуму. В другом случае близость максимумов допускает небольшой рост первого показателя ради снижения суммы. Сам допуск не превращает нарушение лимита в допустимость.

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

d = Σh=0…H−1 Σa=0…5 ((A′h,a − A⁰h,a) / sa)²

Здесь A⁰ — оригинал, A′ — поправка, а s — положительные масштабы шести компонентов. Величина d безразмерна: это сумма квадратов нормированных отклонений, а не комиссия или денежная стоимость сделки.

projection_improvement_atol применяется к максимуму и сумме нарушений. Масштаб суммы зависит от числа сценариев, поэтому допуск фиксируется вместе с профилем. Для расстояния отдельно используется projection_distance_tie_atol.

В методе SelectImprovement параметры передаются кернелу следующим образом:

   const uint values[] = {m_shape.M, m_shape.H, m_config.projection_max_evaluations,
                           m_config.projection_max_iterations, m_config.projection_lookback_steps};
   if(!BindBuffers(kernel, buffers) || !BindIntegers(kernel, 7, values) ||
      !OpenCL.SetArgument(kernel, 12, (float)m_config.projection_improvement_atol) ||
      !OpenCL.SetArgument(kernel, 13, (float)m_config.projection_distance_tie_atol))
      return(Fail(DPCC_OPENCL_ERROR));
   return(ExecuteRows(kernel, m_shape.M));

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

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

Вычислительные затраты зависят от числа сценариев и альтернатив. Для управления поиском предусмотрены два счётчика.

projection_max_iterations ограничивает число принятых поправок значениями от 1 до 16. projection_max_evaluations задаёт бюджет от 1 до 160 оценок на исходный план. Одна оценка — полный расчёт одного активного плана по всем K сценариям, а не отдельный запуск кернела.

Не каждый план проходит все стадии. При бюджете 1 проверяется оригинал. Допустимый возвращается со статусом 0, нарушающий ограничения — без изменений со статусом 2. Бюджета 2 для изменения также недостаточно.

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

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

Цикл поиска выполняется внутри feedForward:

   const ulong wave_bound = ((ulong)m_config.projection_max_iterations + 1) *
                              m_config.projection_lookback_steps;
   ulong wave = 0;
   while(common == 1 && wave < wave_bound)
     {
      if(!BuildLocalProposals(context) || !EvaluateProposals(context) || !SelectImprovement() ||
         !ReadCommon(0, common))
         return(false);
      wave++;
     }
   if(common == 1)
      return(Fail(DPCC_OPENCL_ERROR));
   return(VerifyProjection(context));

ReadCommon получает результат DPCCProjectionReduce в режиме 0. Единица означает наличие активного кандидата на стадии 3. Ноль означает отсутствие локального поиска, но не готовность выходов: могут оставаться планы на стадии 4. Они обрабатываются в VerifyProjection. Системные коды обрабатываются отдельно от этого признака.

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

Штатные условия остановки выводят кандидатов из поиска до срабатывания предохранителя. Сохранившаяся единица common означает нарушение ожидаемых переходов. Здесь DPCC_OPENCL_ERROR обозначает сбой протокола, а не доказанную ошибку устройства. Выдавать такой исход за штатное исчерпание поиска нельзя.

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

Для остальных VerifyProjection вызывает EvaluatePlanBatch, обновляет сводку и запускает DPCCProjectionPublish:

   if(!m_rollout.EvaluatePlanBatch(m_work[DPCC_PW_CURRENT], 0, m_shape.M,
                                  m_work[DPCC_PW_FINAL_ACTIVE], context,
                                  m_work[DPCC_PW_BASE_RETURN], m_work[DPCC_PW_BASE_STATE],
                                  m_work[DPCC_PW_BASE_VIOLATION]))
      return(Fail(m_rollout.LastStatus()));
   if(!Summarize(false, m_work[DPCC_PW_CURRENT], m_work[DPCC_PW_FINAL_ACTIVE]))
      return(false);
   const int kernel = def_k_DPCCProjectionPublish;
   CBufferFloat *buffers[] = {m_work[DPCC_PW_ORIGINAL], m_work[DPCC_PW_CURRENT],
                              m_work[DPCC_PW_BASE_SUMMARY], m_work[DPCC_PW_CONTROL],
                              m_work[DPCC_PW_RESULT], m_work[DPCC_PW_COST_TEMP],
                              m_work[DPCC_PW_STATUS_TEMP], m_work[DPCC_PW_DIAGNOSTICS_TEMP]};

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

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

Для получения результатов предусмотрены методы:

МетодВозвращаемые данные
getOutputИтоговые торговые планы размерности M×H×6
GetOriginalPlansНеизменённые исходные предложения
GetCorrectionCostСумма квадратов нормированных отклонений
GetCandidateStatusКоды кандидатов; читаются вместе с активностью и признаком проверки
GetDiagnosticsСведения о ходе поиска и оставшихся нарушениях
LastStatusОбщий статус последней операции проектора

В GetDiagnostics на кандидата отведено двенадцать полей. Поля 0–1 содержат число оценок и принятых поправок, 2–3 — максимум и сумму нарушений, 4–8 — шаг, фазу, сценарий, категорию и связанное действие. Поле 9 хранит номер последнего предложения, 10 — активность, 11 — признак выполненной проверки.

GetCandidateStatus не является самостоятельным разрешением на использование плана. Код 0 означает подтверждённый сценарный результат активного кандидата. Код 2 не подтверждает такого результата: у активного плана это отказ поиска или раннее завершение без изменений, у неактивного — служебное заполнение. После системной ошибки эти поля вообще нельзя трактовать как актуальный итог.

При корректном завершении feedForward возвращает true. Общий LastStatus равен DPCC_OK либо DPCC_SEARCH_EXHAUSTED. Второй код означает отсутствие допустимого результата хотя бы у одного активного кандидата; остальные могут пройти проверку. Полностью неактивный пакет тоже может дать DPCC_OK, но не содержит кандидата для выбора.

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

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

Расчётчик и проектор подключаются через интерфейсы прямого прохода и сериализации. Это не сквозной обучаемый граф: стандартный backward для них не поддерживается и возвращает DPCC_BACKWARD_UNSUPPORTED. Обучаемые компоненты готовятся отдельно; градиент через дискретный выбор поправки здесь не проводится.

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


Заключение

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

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

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


Ссылки


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

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

Таблица показывает сопровождающие исходники. Рассмотренные классы находятся в NeuroNet_AgentsRL.mqh, OpenCL-операции — в NeuroNet.cl. Остальные программы относятся к подготовке торговой модели.

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

Прикрепленные файлы |
MQL5.zip (7607.41 KB)
Трейдинг с экономическим календарём MQL5 (Часть 11): Модульная новостная панель на Canvas Трейдинг с экономическим календарём MQL5 (Часть 11): Модульная новостная панель на Canvas
Мы перестраиваем информационную панель Экономического календаря MQL5: вместо монолитной панели, основанной на объектах, она становится модульной системой на Canvas, разделённой на четыре файла. В обновлении добавлены две темы оформления — светлая и тёмная, сворачиваемые группы дней, макет с изменяемым размером и прокруткой с пиксельной точностью, маркеры пересмотренных значений, а также обратный отсчёт в реальном времени со всплывающими уведомлениями. Кэш событий-кандидатов и таймер с ускоренной веткой, который перерисовывает только изменившиеся ячейки, повышают отзывчивость и упрощают расширение кодовой базы.
Теория экстремальных значений в MQL5: Создание индикатора риска обвала с оценкой хвоста распределения — за рамками VaR по методу Монте-Карло Теория экстремальных значений в MQL5: Создание индикатора риска обвала с оценкой хвоста распределения — за рамками VaR по методу Монте-Карло
Стандартные инструменты оценки риска на MQL5 используют недавние исторические данные и не учитывают, насколько тяжелым может быть левый хвост распределения. Мы реализуем теорию экстремальных значений (EVT) в MetaTrader 5: подгонку обобщенного распределения Парето (GPD) методом превышений над порогом (POT) с помощью ALGLIB, индикатор, который в реальном времени выводит VaR и ES на основе EVT, а также параметр формы хвоста, и советник, рассчитывающий размер позиций по оценке хвоста распределения. Тестирование на исторических данных в контролируемых условиях демонстрирует снижение просадки при неизменных точках входа.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
За пределами максимальной просадки: Создание анализатора ДНК просадки на MQL5 За пределами максимальной просадки: Создание анализатора ДНК просадки на MQL5
Максимальная просадка — это одно число, за которым скрывается то, что действительно важно: как часто снижается кривая эквити, как долго она остается ниже предыдущего пика и как быстро восстанавливается. В этой статье мы создадим нативный инструмент на MQL5, который восстанавливает кривую просадки, разбивает ее на отдельные эпизоды (глубина, продолжительность, время восстановления), рассчитывает индекс Ulcer, индекс боли и фактор восстановления, а затем объединяет эти показатели в единую оценку устойчивости с практическими рекомендациями. Никаких внешних библиотек, никакого Python, никакого ИИ.