Нейросети в трейдинге: Двухуровневая адаптация торговой политики (Окончание)
Введение
Рыночные режимы возвращаются, но редко повторяются в точности. Знакомый импульс возникает при другой волатильности, спреде или загрузке счёта. Поэтому торговой модели недостаточно распознать похожее движение. Ей полезно помнить, какая локальная поправка уже помогала в близких условиях. Эта задача была поставлена в первой статье.
При обычном обучении опыт каждого эпизода растворяется в общих весах сети. Модель получает способность к обобщению, но отдельные устойчивые направления коррекции могут потеряться. Было предложено сохранить такой опыт во внешней памяти на основе фреймворка D2Skill.
В авторском D2Skill память имеет два уровня. Task Skills дают общую опору на протяжении задачи. Step Skills помогают исправлять локальные ошибки. Для оценки навыков авторы сравнивают траектории одной и той же политики: базовые и с подключённой памятью. Разница результатов формирует ретроспективный сигнал Utility. Он участвует в поиске, обслуживании банка и оптимизации политики.
В торговой адаптации мы сохранили двойную гранулярность, парное сравнение и динамическое обслуживание памяти. Изменилась форма навыка. Вместо текстовой рекомендации используется коррекция скрытого представления Актёра. Её источником служит псевдоостаток после обратного прохода. Навык не выдаёт приказ купить или продать. Он лишь уточняет внутренний контекст, после чего обычная торговая политика формирует действие.
Банки навыков сохраняют локальные коррекции без частого переобучения всей модели. Базовая модель остаётся главным источником торгового решения. Если подходящего навыка нет, Актёр работает по исходной политике. Модель сохраняет уже найденные решения и может использовать повторяющийся рыночный опыт.
Адаптацию разделили на четыре статьи. В первой части создали объект CD2SkillItem и выбрали раздельное накопление направления и масштаба коррекции. Во второй — построили динамический объект CD2SkillBank. Он получил контекстный поиск, состояние Candidate, жизненный цикл, Influence и Utility.
В третьей статье два банка навыков вошли в объект верхнего уровня CD2Skill. Банк Task Skills работает с общим контекстом торговой задачи. Банк Step Skills получает локальное представление прогнозного сценария. Оба банка встроены только в архитектуру Актёра. Критик сохранил исходную структуру и оценивает уже сформированное действие.
Пришло время завершить адаптацию на уровне всей торговой системы. Отдельные классы уже готовы, но сами по себе они не дают обученную модель. Нам предстоит связать их с программами обучения, задать точки контроля и проверить работу памяти на новых данных.
Переход между стадиями нельзя определять только завершением очередного запуска. Каждый этап должен оставить проверяемый результат: устойчивый прогноз, исполнимую базовую политику, подтверждённые навыки и фактически обновлённую Utility.
Базовую архитектуру менять не будем. В основе остаётся ранее построенная адаптация ORION. Прогнозный модуль создаёт набор возможных сценариев рынка. Актёр связывает их с состоянием счёта и позиции. А D2Skill добавляет память для локальных коррекций. При отключённых банках работает та же политика, но без дополнительного опыта.
В этой статье мы соберём полный учебный контур. Для каждого этапа укажем изменяемые компоненты и точки контроля. Затем сравним базовую модель с вариантом, в котором работают оба уровня памяти.

Обучение моделей
Обучение проходит в четыре этапа. Сначала готовится прогнозная модель. Затем обучаются базовые Актёр и Критики. После этого формируются навыки. На последнем этапе калибруется финансовая полезность навыков.
В авторском D2Skill политика и банк навыков обучаются совместно. В нашей реализации операции разведены по времени. Причина связана с латентной формой навыка. Его ключ и коррекция имеют смысл только в устойчивой системе координат. Поэтому сначала мы фиксируем представление рынка и базовую политику. Парное сравнение базовой ветви и ветви с навыками сохраняется. Банк также продолжает учитывать накопленную полезность.
Подготовка прогнозной модели
В качестве Forecast используется сценарная модель из адаптации ORION. Она получает доступную рыночную историю и формирует несколько вариантов дальнейшего движения цены. Архитектура прогнозного блока и его обучение уже подробно рассмотрены в статье об ORION. Здесь повторять этот материал не будем.
Прогнозная модель обучается первой, поскольку ключи и коррекции банков привязаны к его латентному пространству. Если позднее переобучить Forecast, смысл этих координат изменится. Навык начнёт искать уже не то состояние, для которого создавался. После подготовки прогнозная модель фиксируется.
Подготовку выполняет советник StudyForecast.mq5. Пользователь задаёт начало и конец выборки, а также число эпох. В эксперименте использовались данные EURUSD H1. При наличии совместимого состояния программа продолжает обучение. Новые эпохи добавляются к уже завершённым.
После каждой полной эпохи сохраняются рабочий энкодер рыночных состояний, энкодер целевых значений и манифест, в котором записаны размерности, число эпох и сигнатуры файлов. Следующие программы проверяют этот контракт перед загрузкой модели.
Переходить дальше стоит после стабилизации ошибки обучения. Сценарии при этом должны сохранять различия. Следует также проверить число некорректных пакетов и восстановлений неактивных сценариев. При постоянном схлопывании кодбука один прогноз просто размножается под разными номерами.
После обучения Актёра и Критиков модель прогнозирования переводится в режим вывода. Её параметры больше не меняются. Один и тот же файл используется при подготовке базовой политики, формировании навыков, калибровке Utility и итоговом тестировании.
Обучение базовой торговой модели
Следующий этап формирует торговую основу. Актёр учится выбирать направление, объём и защитные уровни. Два критика связывают это действие с ожидаемым финансовым результатом. Банки навыков пока выключены.
Банки остаются выключенными, пока Актёр не сформирует самостоятельную политику. Иначе память начнёт накапливать поправки к случайным решениям. Базовая модель также служит контрольной точкой. Позже мы включим D2Skill и оценим его вклад без замены основных весов.
Офлайн-обучение выполняет советник Study.mq5 с пресетом 01Base.set. Стадия задана значением BASE. Режим банков также равен BASE. Исполнение переводится в D2_DISABLED. Параметр UtilityAware выключен. Объект CD2Skill уже находится в Актёре, но его память не участвует в прямом и обратном проходах.
Период обучения начинается 1 января 2024 года и завершается 1 января 2026 года. Рабочий таймфрейм — H1. Параметры RSI, CCI, ATR и MACD совпадают с конфигурацией прогнозной модели. Иначе Актёр получит сценарии, построенные на другом наборе рыночных признаков.
Представление навыка заранее установлено как Direction–Magnitude. Банки пока отключены, но значение входит в контракт сохраняемой модели. Параметры MinUtility и UtilityScale на этой стадии не влияют на действие. Они понадобятся после формирования памяти.
Параметры Stage, Mode и ExecutionMode решают разные задачи. Первый отмечает стадию эксперимента. Второй выбирает доступные банки. Третий задаёт их поведение. Поэтому одного значения BASE недостаточно. Для чистого обучения память должна быть явно отключена.
Программа загружает Forecast (в замороженном виде) и проверяет манифест. Затем создаются или восстанавливаются Актёр, Q1 и Q2. Совместимая контрольная точка продолжает обучение. Несовместимый комплект не пересоздаётся без явного подтверждения. Это защищает предыдущий результат от случайной перезаписи.
На каждом переходе Forecast строит сценарии только по доступной истории. Актёр получает их вместе с состоянием счёта. На выходе формируются шесть значений. Это объёмы покупки и продажи, а также уровни Take Profit и Stop Loss для обоих направлений.
Далее программа моделирует переход к следующему бару. Учитываются открытая позиция, изменение объёма, разворот, спред, плавающий результат и защитные уровни. Обучающий сигнал связан с последствием конкретного торгового действия, а не с простым направлением цены.
Оба критика получают одинаковый контекст и действие, но обучаются независимо. В офлайн-контуре целью служит рассчитанный результат текущего перехода. Рекурсивная TD-цель здесь не применяется.
Для расширения выборки программа добавляет учительское и случайное действие. Учительский пример строится по реализованному будущему. Он не попадает во вход Forecast и не используется во время реальной торговли. Это только дополнительная цель на истории. Положительный пример обучает Актёра. Оба критика получают и прибыльные, и убыточные варианты.
Случайное действие всегда должно быть исполнимым: объём ограничивается доступной маржой, а защитные уровни учитывают требования символа. Такие примеры не дают Критикам замкнуться около текущей политики. Положительная случайная альтернатива также может обновить Актёр.
В пресете задано один миллион переходов. История разбивается на эпизоды по 1008 баров. В начале эпизода очищается рекуррентное состояние и создаётся новое состояние виртуального счёта. Нижняя граница баланса равна 50.
Градиент от Критика передаётся в Актёр на каждой пятой итерации. Эту частоту задаёт UpdatePolicy. Учительские и положительные случайные примеры могут обновлять политику между такими шагами. Градиент применяется только к исполнимому действию. Иначе небольшое изменение выхода сети не гарантирует реальную торговую операцию.
Во время обучения контролируются ошибки Q1 и Q2, число учительских и случайных примеров, обновления политики и пропуски неисполнимых действий. Ошибки должны перейти в устойчивый диапазон. Доля пропусков не должна доминировать. Объёмы и защитные уровни должны оставаться конечными и соответствовать ограничениям символа.
Счётчики teacherQ и randomQ показывают дополнительное покрытие действий. Показатель teacherA должен расти при появлении полезных учительских примеров. Счётчик policy отражает обновления по градиенту Критика. Показатель skip показывает пропуски из-за неисполнимых команд. По этим счётчикам видно, получает ли Актёр исполнимые сигналы и хватает ли Критикам разнообразных примеров.
Программа также проверяет неизменность Forecast. При нарушении сигнатуры новая контрольная точка не сохраняется. Результат публикуется только после полного выполнения цикла. На диск записываются D2SkillActor.nnw, D2SkillQ1.nnw, D2SkillQ2.nnw и манифест D2SkillActorCritic.manifest.
Формального завершения миллиона переходов недостаточно. Перед следующим этапом нужно увидеть устойчивые оценки Критиков и регулярные исполнимые действия Актёра. Полезен и контрольный прогон с отключёнными банками. Он показывает, что базовая политика уже имеет осмысленное торговое поведение.
Формирование навыков
После базового обучения начинается непосредственная работа D2Skill. Теперь мы ищем состояния, в которых Актёр снова получает похожее направление коррекции. Цена и волатильность могут отличаться. Но внутренний контекст модели способен указывать на один и тот же локальный способ улучшить решение.
Формирование выполняется тем же советником Study.mq5. Для него загружается пресет 02Formation.set.
| Параметр | Значение | Назначение |
|---|---|---|
| InpD2SkillStage | D2Skill_D2_STAGE_FORMATION | Формирование банков |
| InpD2SkillMode | D2Skill_D2_MODE_FULL | Работа обоих уровней памяти |
| InpD2SkillExecutionMode | D2_COLLECT | Сбор навыков без применения |
| InpD2SkillUtilityAware | false | Поиск только по контексту и градиенту |
На этой стадии Utility ещё не должна менять структуру банка. Сначала система проверяет повторяемость контекста и псевдоостатка. Финансовую ценность навыков мы измерим позже.
Отключение UtilityAware разделяет повторяемость и полезность. Новый элемент ещё не имеет надёжной финансовой истории. Если ранняя оценка начнёт влиять на поиск, банк будет чаще обновлять случайно выбранные слоты. Сначала навык должен доказать устойчивость направления. Только затем он получает оценку результата.
Внутренние алгоритмы CD2SkillItem, CD2SkillBank и CD2Skill уже разобраны в первой, второй и третьей статьях. Там показаны накопление коррекции, поиск, Candidate, подтверждение и жизненный цикл. Здесь рассмотрим только новый код учебного контура.
Сначала программа переводит выбранный режим банков в два рабочих флага. Эту задачу выполняет метод D2SkillD2BankFlags.
void D2SkillD2BankFlags(bool &task_enabled, bool &step_enabled) { const bool execution_enabled = (D2SkillD2ExecutionMode != D2_DISABLED); task_enabled = (execution_enabled && (D2SkillD2Mode == D2Skill_D2_MODE_TASK || D2SkillD2Mode == D2Skill_D2_MODE_FULL)); step_enabled = (execution_enabled && (D2SkillD2Mode == D2Skill_D2_MODE_STEP || D2SkillD2Mode == D2Skill_D2_MODE_FULL)); }
При D2_DISABLED оба флага остаются выключенными. В пресете формирования выбран режим FULL. Поэтому доступны и Task Bank, и Step Bank.
Далее метод D2SkillEnableD2Banks находит объект CD2Skill внутри Актёра. Затем он включает банки и задаёт их общие параметры.
bool D2SkillEnableD2Banks(CNet &actor) { CNeuronBaseOCL *layer = actor.Layer(1); if(!layer || layer.Type() != defNeuronD2Skill) ReturnFalse; CD2Skill *skill = (CD2Skill*)layer; if(!skill || !skill.Ready()) ReturnFalse; bool task_enabled = false; bool step_enabled = false; D2SkillD2BankFlags(task_enabled, step_enabled); if(!skill.SetMode(D2SkillD2ExecutionMode) || !skill.SetOnlineDirectionUpdate(D2SkillD2OnlineDirectionUpdate) || !skill.Enable(task_enabled, step_enabled)) ReturnFalse; CD2SkillBank *task = skill.TaskBank(); CD2SkillBank *step = skill.StepBank(); if(!task || !step) ReturnFalse; if(!D2SkillConfigureBankRepresentation(task, step) || !task.SetThresholds(0.65f, 0.0f) || !step.SetThresholds(0.65f, 0.0f) || !task.SetLifecycle(3, 32, 256) || !step.SetLifecycle(3, 32, 256) || !task.SetMaxCorrection(10.0f) || !step.SetMaxCorrection(10.0f) || !task.SetAlpha(1.0f) || !step.SetAlpha(1.0f) || !task.SetUtilityPolicy(D2SkillD2UtilityAware, (float)MathMin(1.0, MathMax(-1.0, D2SkillD2MinUtility))) || !step.SetUtilityPolicy(D2SkillD2UtilityAware, (float)MathMin(1.0, MathMax(-1.0, D2SkillD2MinUtility)))) ReturnFalse; PrintFormat("D2Skill D2 stage=%d mode=%d execution=%d task=%s step=%s utility=%s scale=%.3f direction_ema=%s", D2SkillD2Stage, D2SkillD2Mode, D2SkillD2ExecutionMode, (task_enabled ? "on" : "off"), (step_enabled ? "on" : "off"), (D2SkillD2UtilityAware ? "aware" : "similarity"), D2SkillD2UtilityScale, (D2SkillD2OnlineDirectionUpdate ? "on" : "off")); return(true); }
Функция проверяет тип слоя и готовность объекта. Затем она применяет представление коррекции, порог сходства и правила жизненного цикла. Для подтверждения Candidate требуется три наблюдения. Новый элемент защищён 32 обновления. Порог неактивности равен 256. Максимальный масштаб коррекции ограничен значением 10.
Последняя строка выводит фактическую конфигурацию в журнал. Для стадии формирования должны быть включены оба банка. Режим исполнения должен быть D2_COLLECT, а поиск — similarity. Проверить эту строку лучше до запуска длительного обучения.
Одного включения памяти недостаточно. Обычные веса Актёра нужно зафиксировать. Иначе ключи и коррекции будут формироваться в постоянно меняющемся пространстве. Права записи задаёт метод D2SkillConfigureActorCriticUpdates.
bool D2SkillConfigureActorCriticUpdates(CNet &actor, CNet &q1, CNet &q2) { bool actor_weights = false; bool critic_weights = false; switch(D2SkillD2ExecutionMode) { case D2_DISABLED: actor_weights = true; critic_weights = true; break; case D2_COLLECT: critic_weights = true; break; case D2_EVALUATE: case D2_INFERENCE: break; case D2_ONLINE_CALIBRATION: critic_weights = D2SkillD2OnlineCriticUpdate; break; default: ReturnFalse; } if(!actor.SetWeightsUpdate(actor_weights) || !q1.SetWeightsUpdate(critic_weights) || !q2.SetWeightsUpdate(critic_weights) || !D2SkillMarket.SetWeightsUpdate(false) || !D2SkillMarket.TrainMode(false)) ReturnFalse; return(true); }
В режиме D2_COLLECT веса Актёра не изменяются. Оба критика продолжают обучение. Forecast остаётся замороженным при любом режиме обучения Актёра и Критиков.
Запрет записи весов не прерывает обратный проход. Градиент проходит через Актёр и достигает банков. В прямом проходе они только наблюдают свои скрытые состояния. Коррекция не добавляется к текущему действию. После обратного прохода банк обновляет существующий навык или продолжает формирование Candidate.
Так сохраняется причинный порядок. Сначала базовая политика принимает решение. Затем программа оценивает его последствия. Только после этого опыт попадает в память. Новый навык сможет повлиять лишь на будущие похожие состояния.
Учебный поток остаётся прежним. Банки получают сигналы от текущей политики, положительных учительских примеров и удачных случайных альтернатив. Поэтому одна итерация не равна одному обновлению навыка. Параметр UpdatePolicy регулирует только обратный проход от Критика через текущую политику.
Во время формирования ошибки Критиков должны оставаться устойчивыми. Торговое поведение при одинаковом входе должно совпадать с базовой политикой. Навыки ещё не участвуют в прямом проходе. Рост прибыли на этой стадии не является контрольной целью.
Статистику банков нужно смотреть отдельно. Task Bank работает с более общим состоянием счёта и задачи. Step Bank видит локальное состояние прогноза. Их число подтверждений может отличаться. Но оба банка должны выйти за пределы одних только Candidate.
Частые замены указывают на нестабильную память. Возможная причина — конфликтующие градиенты или слишком разнородные контексты. Если один слот получает почти все наблюдения, банк снова смешивает разные рыночные режимы в одной усреднённой коррекции.
После завершения Актёр сохраняется вместе с двумя банками. В контрольную точку входят ключи, коррекции, состояния Candidate и статистика подтверждённых навыков. Перед переходом к калибровке нужно проверить наличие подтверждений, число замен и долю no_skill. Пустой или постоянно перезаписываемый банк ещё не готов к оценке.
Калибровка Utility
Повторяемый навык не обязательно полезен. Рынок может несколько раз направить модель к одной и той же ошибке. Поэтому после формирования нужно сравнить торговый результат с памятью и без неё.
Similarity показывает близость контекста. Influence отражает участие выбранной коррекции в градиенте. Utility связывает это участие с результатом завершённой траектории. Алгоритм обновления банка уже рассмотрен во второй статье. Здесь нас интересует внешний контур парного сравнения.
Калибровку выполняет советник StudyOnline.mq5 с пресетом 03UtilityCalibration.set. Его можно запускать в тестере стратегий. Бары поступают последовательно, как при реальной работе.
| Параметр | Значение | Назначение |
|---|---|---|
| InpD2SkillStage | D2Skill_D2_STAGE_ONLINE | Последовательная калибровка |
| InpD2SkillMode | D2Skill_D2_MODE_FULL | Оба банка |
| InpD2SkillExecutionMode | D2_ONLINE_CALIBRATION | Накопление Influence и Utility |
| InpD2SkillUtilityAware | true | Учёт накопленной Utility |
| InpD2SkillMinUtility | -1.0 | Без жёсткого отсечения |
| InpD2SkillUtilityScale | 1.0 | Масштаб терминального сигнала |
| InpD2SkillOnlineDirectionUpdate | false | Направления навыков заморожены |
| InpD2SkillOnlineCriticUpdate | false | Критики заморожены |
| UpdatePolicy | 1 | Influence на каждом переходе |
| CheckpointTransitions | 256 | Период запроса сохранения |
| InpD2SkillPairHorizonTransitions | 0 | Только терминальная пара |
| MinBalance | 50.0 | Финансовый терминал |
Стандартный пресет не продолжает обычное обучение модели. Forecast, веса Актёра, направления навыков и оба критика зафиксированы. Меняется только Utility реально использованных элементов.
Параметр UtilityAware включает накопленную оценку в ранжирование. Параметр MinUtility равен −1. Он не запрещает отрицательные навыки. Их оценка снижает итоговый рейтинг, но решение всё ещё зависит от сходства контекста.
Один финансовый результат не показывает вклад навыка. Прибыль могла возникнуть из-за самого движения рынка. Поэтому программа ведёт две ветви одной политики. Базовая ветвь работает без памяти. Вторая ветвь использует оба банка. Обе ветви начинают с одинакового счёта и одного рекуррентного состояния.
Как и в авторском D2Skill, Utility определяется разницей между двумя режимами одной политики. В исходной работе этот сигнал также участвует в оптимизации политики. В нашей стадии калибровки веса уже зафиксированы. Поэтому терминальная разница изменяет только память.
Метод BuildOnlineBaselineAction строит базовое действие. Он временно отключает банки, выполняет прямой проход и сохраняет новое состояние базовой ветви. Затем восстанавливается ветвь с навыками.
bool BuildOnlineBaselineAction(void) { if(!OnlinePairActive) return(true); CNeuronBaseOCL *layer = Actor.Layer(1); CD2Skill *skill = (layer && layer.Type() == defNeuronD2Skill ? (CD2Skill*)layer : NULL); if(!skill) ReturnFalse; bool task_enabled = false; bool step_enabled = false; D2SkillD2BankFlags(task_enabled, step_enabled); if(!D2SkillRestorePairedActorState(BaselineActorForwardState, skill, false, false, "online_baseline_start") || !Actor.feedForward(GetPointer(BaselineVirtualAccount), 1, false, GetPointer(D2SkillMarket), -1) || !ReadAction(Actor, GetPointer(BaselineVirtualAction)) || !D2SkillValidateAction(GetPointer(BaselineVirtualAction)) || !D2SkillCaptureActorForwardState(BaselineActorForwardState) || !D2SkillRestorePairedActorState(ActorForwardState, skill, task_enabled, step_enabled, "online_baseline_to_skill")) ReturnFalse; return(true); }
После этого Актёр строит действие с навыками. Каждая ветвь ведёт собственный виртуальный счёт. У них отдельно меняются позиция, маржа, средства, просадка и издержки. Сравниваются полноценные последовательности решений, а не два выхода сети на одном баре.
Для распределения терминального результата нужно знать, какие навыки участвовали в решениях. Эту роль выполняет Influence. Он требует обратного прохода через Актёр. При этом записывать новые веса не нужно. Эти два разрешения разделены в методе D2SkillRequiresBankGradientBackward.
bool D2SkillRequiresBankGradientBackward(void) { if(D2SkillD2Mode == D2Skill_D2_MODE_BASE) return(false); return(D2SkillD2ExecutionMode == D2_COLLECT || D2SkillD2ExecutionMode == D2_EVALUATE || D2SkillD2ExecutionMode == D2_ONLINE_CALIBRATION); }
Для базовой ветви дополнительный маршрут не нужен. В режимах формирования, оценки и калибровки градиент должен дойти до банков даже при замороженном Актёре.
В методе TrainPreviousTransition это условие объединяется с обычным разрешением на обучение весов.
const bool bank_gradient_backward = D2SkillRequiresBankGradientBackward(); const bool actor_backward = (Actor.WeightsUpdateEnabled() || bank_gradient_backward); if(actor_backward && UpdatePolicy > 0 && Transition > 0 && Transition % (ulong)UpdatePolicy == 0 && !PolicyBackward(Actor, Q1, GetPointer(ScalarTarget), GetPointer(D2SkillMarket), -1)) ReturnFalse; if(!Q1.backProp(GetPointer(ScalarTarget), GetPointer(D2SkillMarket), -1) || !Q2.backProp(GetPointer(ScalarTarget), GetPointer(D2SkillMarket), -1)) ReturnFalse; PreviousMarginPenalty = 0; //--- Per-transition guard reads only the three small trainable weight buffers. if(!D2SkillVerifyFrozenWeightsExact()) ReturnFalse; if(UpdateTargets > 0 && Transition > 0 && Transition % (ulong)UpdateTargets == 0) if((Actor.WeightsUpdateEnabled() && !TargetActor.WeightsUpdate(GetPointer(Actor), Tau)) || (Q1.WeightsUpdateEnabled() && !TargetQ1.WeightsUpdate(GetPointer(Q1), Tau)) || (Q2.WeightsUpdateEnabled() && !TargetQ2.WeightsUpdate(GetPointer(Q2), Tau))) ReturnFalse; return(true);
При калибровке метод PolicyBackward не обновляет Актёр. Он только проводит градиент к выбранным навыкам. После каждого перехода программа проверяет точную неизменность замороженных весов. Параметр UpdatePolicy равен 1. Поэтому Influence учитывается на каждом шаге пары.
Локальный градиент строится по одноступенчатой TD-цели. Он нужен для оценки Influence. В Utility эта цель напрямую не записывается. Финансовый сигнал формируется только после завершения парных траекторий.
В пресете горизонт пары равен нулю. Это означает терминальное сравнение без искусственного обрезания по числу баров. Пара закрывается при достижении финансового терминала одной из ветвей. Закрытие отдельной сделки терминалом не считается.
Нулевой горизонт выбран осознанно, потому что навык может влиять на цепочку последующих решений: изменения объёма меняют баланс, маржу и допустимый риск. Короткий горизонт обрезал бы часть этого эффекта. Но терминальное обновление возникает редко. Поэтому его наличие проверяется отдельно.
После завершения пары вызывается метод FinishOnlinePair.
bool FinishOnlinePair(void) { if(!OnlinePairActive) return(true); double delta_j = 0.0; bool utility_mutated = false; const bool utility_operation_ok = (D2SkillValidatePairedEpisode(BaselineEpisodeOutcome, SkillEpisodeOutcome) && D2SkillComputePairedEpisodeDelta(BaselineEpisodeOutcome, SkillEpisodeOutcome, delta_j) && D2SkillUpdateD2UtilityDelta(Actor, delta_j, utility_mutated)); if(!ResetD2SkillEpisodeInfluence(Actor)) ReturnFalse; OnlinePairActive = false; OnlineVirtualTransitionPending = false; OnlineBaselineTerminal = false; OnlineSkillTerminal = false; if(!utility_operation_ok) ReturnFalse; PrintFormat("D2Skill online paired terminal JBase=%.8f JSkill=%.8f DeltaJ=%.8f " "utility_mutated=%s " "base_balance=%.2f skill_balance=%.2f base_drawdown=%.2f skill_drawdown=%.2f " "base_cost=%.2f skill_cost=%.2f base_risk=%.2f skill_risk=%.2f " "base_duration=%u skill_duration=%u transitions=%I64u", BaselineEpisodeOutcome.Outcome(), SkillEpisodeOutcome.Outcome(), delta_j, (utility_mutated ? "true" : "false"), BaselineEpisodeOutcome.Balance(), SkillEpisodeOutcome.Balance(), BaselineEpisodeOutcome.Drawdown(), SkillEpisodeOutcome.Drawdown(), BaselineEpisodeOutcome.Cost(), SkillEpisodeOutcome.Cost(), BaselineEpisodeOutcome.Risk(), SkillEpisodeOutcome.Risk(), BaselineEpisodeOutcome.Duration(), SkillEpisodeOutcome.Duration(), OnlineEpisodeTransitions); return(true); }
Программа рассчитывает DeltaJ = JSkill - JBase. Положительное значение говорит в пользу памяти. Отрицательное означает, что навыки ухудшили итог. Затем сигнал распределяется между элементами с накопленным Influence. Перед новой парой эпизодическая статистика очищается.
Флаг utility_mutated показывает, изменился ли банк фактически: успешный запуск OpenCL-кернела сам по себе не гарантирует обновления. Обновление отсутствует при нулевом Influence, нулевом DeltaJ или достижении неподвижной точки.
Счётчик закрытых обновлений изменяется только после фактической записи Utility.
bool D2SkillUpdateD2UtilityDelta(CNet &actor, const double delta_j, bool &applied) { applied = false; if((D2SkillD2ExecutionMode != D2_EVALUATE && D2SkillD2ExecutionMode != D2_ONLINE_CALIBRATION) || D2SkillD2Mode == D2Skill_D2_MODE_BASE) return(true); if(D2SkillD2UtilityMode != D2Skill_D2_UTILITY_PAIRED_HINDSIGHT || !MathIsValidNumber(delta_j)) ReturnFalse; if(!D2SkillApplyD2Utility(actor, delta_j, applied)) ReturnFalse; if(applied) D2SkillD2PairedUtilityUpdates++; return(true); }
bool D2SkillUpdateD2UtilityDelta(CNet &actor, const double delta_j) { bool applied = false; return(D2SkillUpdateD2UtilityDelta(actor, delta_j, applied)); }
Терминальный сигнал нормализуется относительно эталонного баланса. Затем он умножается на UtilityScale и ограничивается диапазоном от -1 до 1. Обновление получают только включённые банки.
В конце работы советник печатает контрольное сообщение. Строка PASS closed_updates=N подтверждает хотя бы одно фактическое терминальное обновление. Строка FAIL closed_updates=0 означает, что калибровка не состоялась. В таком состоянии переходить к тестированию на отложенном периоде нельзя.
Отдельная завершённая пара может не изменить банк. Это допустимо при нулевой разнице результата, нулевой массе Influence или достижении неподвижной точки. Ошибкой считается весь запуск без единого содержательного обновления.
Запрос на сохранение создаётся каждые 256 переходов. Сама контрольная точка записывается только на безопасной границе. Активная пара двух ветвей не разрывается посередине.
После успешной калибровки учебный цикл завершён. Остаётся проверить, помогла ли память на данных, которые не участвовали в обучении.
Тестирование
Итоговый тест отвечает на практический вопрос. Изменились ли доходность и риск после подключения D2Skill? Для сравнения используется один Forecast, один Актёр и один рыночный участок. Различается только режим банков.
Тест выполняет советник Test.mq5. Он работает в режиме вывода. Обратный проход и сохранение параметров отключены. Критики не участвуют в торговом решении.
Для модели D2Skill загружается пресет 04InferenceOOS.set. Стадия равна AUGMENTED, режим исполнения — D2_INFERENCE, а режим банков — FULL. Параметр UtilityAware включён. Параметр MinUtility равен −1. Это оставляет мягкое ранжирование без жёсткого запрета отрицательных навыков.
Контрольный прогон использует те же файлы и настройки рынка. Но банки переводятся в режим BASE. Поскольку обычные веса Актёра после базового обучения не менялись, разница между тестами относится к работе памяти.
Рабочий цикл советника расположен в методе OnTick.
void OnTick(void) { if(!IsNewBar()) return; if(!D2SkillRefreshLiveMarket(GetPointer(State), GetPointer(TimeState)) || !D2SkillMarket.Clear() || !D2SkillForwardForecastState(GetPointer(State))) { PrintFormat("%s -> %d market/forecast", __FUNCTION__, __LINE__); return; } double buy_value = 0, sell_value = 0; if(!D2SkillBuildLiveAccount(PreviousBalance, PreviousEquity, Rates[0].time, GetPointer(Account), buy_value, sell_value)) { PrintFormat("%s -> %d account", __FUNCTION__, __LINE__); return; } const ulong forward_started = GetMicrosecondCount(); if(!Actor.feedForward(GetPointer(Account), 1, false, GetPointer(D2SkillMarket), -1) || !ReadAction(Actor, GetPointer(Action))) { PrintFormat("%s -> %d actor/read", __FUNCTION__, __LINE__); return; } const ulong forward_elapsed = GetMicrosecondCount() - forward_started; OOSGpuBoundaryLatencyTotal += forward_elapsed; if(forward_elapsed > OOSGpuBoundaryLatencyMaximum) OOSGpuBoundaryLatencyMaximum = forward_elapsed; OOSGpuBoundaryLatencySamples++; if(!D2SkillExecuteAction(GetPointer(Action), buy_value, sell_value)) { PrintFormat("%s -> %d actor/execute", __FUNCTION__, __LINE__); return; } OOSTransitions++; if(IsExecutableOrder(double(Action[0]), double(Action[1]), double(Action[2])) || IsExecutableOrder(double(Action[3]), double(Action[4]), double(Action[5]))) OOSExecutableActions++; const double current_equity = AccountInfoDouble(ACCOUNT_EQUITY); OOSReward += current_equity - PreviousEquity; if(current_equity > OOSPeakEquity) OOSPeakEquity = current_equity; else if(OOSPeakEquity - current_equity > OOSMaximumDrawdown) OOSMaximumDrawdown = OOSPeakEquity - current_equity; PreviousBalance = AccountInfoDouble(ACCOUNT_BALANCE); PreviousEquity = current_equity; }
На каждом новом баре обновляется рыночное состояние. Затем строятся прогнозные сценарии и описание счёта. Актёр формирует действие, а советник исполняет его. Программа накапливает результат, просадку, число переходов и количество исполнимых действий. После теста модель не перезаписывается.
Оба прогона выполнены на одном отложенном участке 2026 года. Качество истории — 100%. В каждом случае обработано 3070 баров H1 и 18 903 539 тиков. Начальный депозит равен 1000 USD.
Сначала банки были отключены. Базовая модель совершила 156 сделок и получила 256,25 USD чистой прибыли. Доходность составила 25,63%. Profit Factor достиг 1,37, Recovery Factor — 2,75, коэффициент Шарпа — 2,42. Максимальная просадка по средствам составила 7,33%.

Рис. 1. Динамика баланса и средств базовой модели

Рис. 2. Результаты тестирования базовой модели
После стартовой просадки кривая перешла к росту. Во второй половине теста появились более длинные откаты. Тем не менее итог остался положительным. Загрузка депозита не превысила 1%.
Во втором прогоне были включены оба банка навыков и поиск с учётом Utility. Модель совершила 151 сделку и получила 284,36 USD чистой прибыли. Доходность выросла до 28,44%. Profit Factor составил 1,42, Recovery Factor — 3,01, коэффициент Шарпа — 2,68. Максимальная просадка по средствам равна 7,36%.

Рис. 3. Динамика баланса и средств модели D2Skill

Рис. 4. Результаты тестирования модели D2Skill
| Показатель | Базовая модель | D2Skill |
|---|---|---|
| Чистая прибыль | 256,25 USD | 284,36 USD |
| Доходность | 25,63% | 28,44% |
| Profit Factor | 1,37 | 1,42 |
| Recovery Factor | 2,75 | 3,01 |
| Коэффициент Шарпа | 2,42 | 2,68 |
| Просадка средств | 7,33% | 7,36% |
| Сделки | 156 | 151 |
| Прибыльные сделки | 53,21% | 51,66% |
| Средняя прибыль | 11,52 USD | 12,23 USD |
| Средний убыток | -9,59 USD | -9,17 USD |
D2Skill добавил 28,11 USD. Это 2,81 процентного пункта доходности и почти 11% к прибыли базовой модели. Число сделок при этом снизилось. Значит, преимущество не связано с ростом торговой активности.
Доля прибыльных сделок стала немного ниже. Но средняя прибыль выросла с 11,52 до 12,23 USD. Средний убыток сократился с 9,59 до 9,17 USD. Валовая прибыль почти не изменилась. Основной эффект дал меньший валовый убыток: 669,48 USD против 700,14 USD.
Наибольшая прибыльная сделка выросла с 30,33 до 38,67 USD. Максимальный отдельный убыток почти не изменился и остался около 16 USD. Это согласуется с ролью памяти. Она не обязательно меняет направление сделки. Чаще корректируются объём, сопровождение позиции и расположение защитных уровней.
Риск остался на прежнем уровне. Просадка баланса снизилась с 7,06 до 6,78%. Просадка средств изменилась с 7,33 до 7,36%. При этом Recovery Factor и коэффициент Шарпа выросли. Кривая D2Skill сохранила общую форму базового результата, но стала немного устойчивее.
Один отложенный интервал не доказывает универсальное преимущество метода. Нужны walk-forward проверки и другие инструменты. Но в данном эксперименте D2Skill улучшил прибыль и риск-скорректированные показатели при почти неизменной максимальной просадке.
Заключение
В этой статье мы завершили адаптацию D2Skill к алгоритмической торговле. Ранее были созданы отдельный навык, динамический банк и объект верхнего уровня. Теперь эти компоненты объединены в полный учебный контур на базе ORION. Два банка работают внутри Актёра, а прогнозная модель и Критики сохраняют прежнюю архитектуру.
Прогнозное пространство фиксируется до начала обучения политики. Базовая модель сначала учится работать без памяти. Затем банки накапливают повторяющиеся коррекции при замороженных весах Актёра. После этого парные траектории Base и Skill связывают Influence с финансовым результатом и обновляют Utility. Такое разделение сохраняет смысл латентных ключей и не смешивает формирование навыка с оценкой его пользы.
На отложенном участке базовая модель заработала 256,25 USD, а D2Skill — 284,36 USD. Profit Factor вырос с 1,37 до 1,42, Recovery Factor — с 2,75 до 3,01, коэффициент Шарпа — с 2,42 до 2,68. Максимальная просадка средств осталась около 7,3%. Дополнительный результат получен без роста числа сделок и при сопоставимой просадке. Основное улучшение связано с меньшим валовым убытком и более выгодным соотношением средней прибыли к среднему убытку.
Один тестовый период не позволяет судить об универсальности метода. Навыки могут потерять актуальность после смены волатильности, ликвидности или структуры движения. Поэтому модель ещё предстоит проверить в walk-forward режиме и на других инструментах. На выбранном OOS-участке сохранённые коррекции улучшили результат базовой политики. Их можно хранить отдельно, оценивать по завершённой финансовой траектории и возвращать в работу при близком рыночном контексте.
Ссылки
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | Trajectory.mqh | Общий модуль | Построение модели, режимы D2Skill и учебные функции |
| 2 | StudyForecast.mq5 | Советник | Подготовка прогнозной модели |
| 3 | Study.mq5 | Советник | Обучение базовой политики и формирование навыков |
| 4 | StudyOnline.mq5 | Советник | Терминальная калибровка Utility |
| 5 | Test.mq5 | Советник | Тест готовой модели на отложенном периоде |
| 6 | 01Base.set | Пресет | Базовое обучение |
| 7 | 02Formation.set | Пресет | Формирование Task Skills и Step Skills |
| 8 | 03UtilityCalibration.set | Пресет | Калибровка Utility |
| 9 | 04InferenceOOS.set | Пресет | Итоговый режим вывода |
| 10 | NeuroNet_D2Skill.mqh | Библиотека классов | Объекты CD2SkillItem, CD2SkillBank и CD2Skill |
| 11 | NeuroNet.cl | OpenCL-программа | Ядра поиска, обновления, жизненного цикла и Utility |
Проект представлен на forge.mql5.io/dng.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От начального до среднего уровня: Очереди, списки и деревья (VI)
Моделирование рынка: Position View (XVI)
Интеграция MQL5 с пакетами обработки данных (Часть 9): Адаптивная оценка волатильности на основе энтропии
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования