Нейросети в трейдинге: От единственного прогноза к пространству рыночных сценариев (сценарное кросс-внимание)
Введение
Финансовый рынок редко предлагает единственное очевидное продолжение. После сильного движения цена может сохранить импульс, перейти в коррекцию, задержаться в диапазоне или развернуться. В момент принятия решения несколько вариантов могут выглядеть одинаково правдоподобно. Фактически реализуется только один из них.
Для трейдера это привычная ситуация. Неопределённость возникает не только в хаотичном рынке. Иногда текущая картина вполне понятна, но допускает разные продолжения. Рост после коррекции, пробой диапазона и возврат внутрь диапазона могут начинаться одинаково — с похожего набора баров. Различия становятся очевидными позднее, когда часть движения уже состоялась.
Большинство прогнозных моделей сводит неопределённость к одному числовому ответу. Модель строит единственную ожидаемую траекторию и сравнивает её с фактическим будущим. Такой подход удобен для обучения, но вынуждает сжимать несколько возможных исходов в один результат. Если одновременно правдоподобны продолжение роста и разворот, их среднее может показать слабое боковое движение. Формально ошибка уменьшается, однако полученная линия не описывает ни один из реальных вариантов.
Именно эта проблема стала отправной точкой нашей адаптации фреймворка ORION к финансовым рынкам. В исходной работе решается задача автономного вождения. Система должна понять дорожную сцену, учесть значимые события прошлого и построить точную числовую траекторию. Авторы обращают внимание на разрыв между пространством смыслового рассуждения и пространством действия, где результат выражается последовательностью координат.
ORION не заставляет семантическую модель сразу выдавать окончательную траекторию. Архитектура объединяет работу с долгосрочным контекстом, рассуждение о текущей сцене и отдельный генеративный планировщик. Для нашей задачи важен именно этот принцип, а не буквальный перенос автомобильной модели. Анализ рынка, память, прогноз и торговое действие должны оставаться частями единого контура, но не смешиваться раньше времени.
В первой статье мы перенесли эту идею в трейдинг и отказались от единственной прогнозной линии. Вместо неё модель формирует несколько самостоятельных рыночных сценариев. Каждый сценарий описывает возможную последовательность будущих состояний, получает априорную вероятность и оценку неопределённости. При этом сценарии должны специализироваться, а не превращаться в копии одного усреднённого прогноза.
Там же была проведена граница между авторским ORION и нашей адаптацией. Исходный фреймворк использует генеративный планировщик для мультимодальных автомобильных траекторий. Сценарные прототипы, разреженное распределение ответственности, отдельный роутер и торговая политика относятся уже к нашей рыночной архитектуре. От авторской работы мы сохраняем основной принцип: сложное представление ситуации не следует немедленно превращать в действие. Сначала нужно сформировать пространство возможных продолжений.
Практическим результатом первой статьи стал вычислительный фундамент сценарного прогноза. Модель строила несколько траекторий, оценивала их вероятность и неопределённость, а затем сравнивала с фактически реализовавшимся будущим. Разреженное распределение ответственности повышало вес наиболее подходящих вариантов, не заставляя все ветви одинаково следовать за одной целью.
Однако сценариям требовалась устойчивая внутренняя структура. Эту задачу мы решили во второй статье, где создали объект CScenarioCodebook — банк прототипов возможных рыночных траекторий. Один прототип со временем может закрепиться за продолжением импульса, другой — за коррекцией, третий — за переходом в диапазон. Эти роли не задаются вручную, а формируются в латентном пространстве в процессе обучения.
Кодовая книга хранит геометрию сценарного пространства, накапливает статистику использования и позволяет восстанавливать неактивные варианты. На её основе построен слой CNeuronScenarioForecast. Он сопоставляет текущее состояние рынка с прототипами, уточняет траектории, рассчитывает их априорные вероятности и оценивает неопределённость отдельных прогнозных состояний.
В результате модель получила законченный блок прогнозирования сценариев. На его выходе находится не одна линия, а несколько согласованных вариантов будущего. Каждый из них охватывает весь горизонт и все анализируемые последовательности. Цена и внутренние признаки образуют общую рыночную гипотезу, а не комбинируются независимо друг от друга.
Но даже полный сценарный прогноз ещё не отвечает на главный вопрос трейдера: что делать? Один и тот же вариант будущего имеет разное значение при отсутствии позиции, при открытой покупке и при удержании продажи. Ожидаемая коррекция может оставаться допустимой частью среднесрочного плана, но разрушать идею краткосрочной сделки.
Поэтому торговой политике недостаточно выбрать наиболее вероятный сценарий. Необходимо сопоставить каждый вариант будущего с состоянием счёта, открытыми позициями и фактическим временем. Нельзя преждевременно усреднять траектории или отбрасывать редкие варианты: именно они иногда содержат наиболее важную информацию о риске.
Дополнительную сложность создаёт последовательный характер решений. Актёр не должен начинать рассуждение заново на каждом баре. Небольшое встречное движение не обязано менять покупку на продажу, если оно остаётся допустимым внутри выбранного плана. Вместе с тем модель должна отказаться от прежней гипотезы, когда новые сценарии действительно указывают на смену режима.
В этой статье мы создадим промежуточный модуль между блоком прогнозирования сценариев и торговой политикой. Он отдельно рассмотрит каждый сценарий, определит его значение для текущего состояния торговой системы, свяжет новые варианты с накопленным контекстом поведения Актёра и подготовит компактное представление для последующего выбора операции.

Связь рыночных сценариев с торговым решением
На предыдущем этапе мы получили пространство возможного будущего. Модель формирует несколько согласованных сценариев, оценивает их вероятность и показывает участки повышенной неопределённости. Однако прогноз и торговое решение описывают разные стороны задачи. Сценарий говорит, как может развиваться рынок, а решение всегда относится к конкретному состоянию счёта.
Ожидаемый рост цены может стать основанием для новой покупки, подтверждением уже открытой длинной позиции или предупреждением для трейдера, который удерживает продажу. Содержание прогноза остаётся прежним, но его практический смысл меняется. Поэтому между блоком прогнозирования и торговой политикой нужен отдельный уровень, который сопоставит возможное будущее с текущим положением торговой системы.
В таком сопоставлении выделяются три части контекста. Состояние счёта показывает доступный капитал и уже принятую нагрузку. Открытые позиции описывают направление, объём, текущий результат и установленные уровни защиты. Фактическая метка времени помогает учитывать торговую сессию, день недели, начало или конец месяца, периоды публикации отчётности и другие календарные закономерности временного ряда.
Эти части отвечают на разные вопросы, поэтому их целесообразно представить отдельными запросами к прогнозу. Баланс определяет допустимость нового риска. Позиции показывают, как возможное движение повлияет на уже принятые решения. Временная метка помогает оценить сценарий с учётом текущего календарного режима.
Одного набора запросов недостаточно. Открытая покупка имеет разный смысл при продолжении роста, глубокой коррекции и развороте. В первом случае важен потенциал удержания сделки, во втором — допустимая глубина встречного движения, в третьем — сохранность исходного плана. Следовательно, каждый сценарий должен получить собственную интерпретацию торгового контекста.
Не менее важно сохранить целостность прогнозных вариантов. Если объединить все будущие состояния в одну последовательность, механизм внимания сможет взять ближайший рост из одного сценария, коррекцию из другого и восстановление из третьего. Каждый фрагмент существует в прогнозе, но их сочетание не описывает ни одного сформированного варианта.
Для трейдера такая конструкция бесполезна. Торговый план зависит не только от конечного направления, но и от пути к нему. Небольшая коррекция и глубокая просадка могут завершиться в одной точке, однако требуют разных объёмов, защитных уровней и правил сопровождения. Поэтому сценарии следует анализировать раздельно, сохраняя каждый как законченную гипотезу развития рынка.
Внутри одного сценария важно видеть всю траекторию. Модель должна учитывать совместное изменение цены и остальных признаков на всём горизонте. При поиске входа важнее может быть ближайший участок, при сопровождении позиции — максимальное встречное движение, а при выборе защитного уровня — наиболее неблагоприятная часть пути.
Оценка неопределённости дополняет содержание сценария. Она не должна автоматически уменьшать объём или удалять отдельные состояния. Её задача — показать, насколько фактическое движение может отклониться от ожидаемого пути. Два сценария способны завершаться ростом, но один предполагает ровное движение, а второй — широкий разброс во время коррекции. Направление одинаково, требования к риску различаются.
Априорная вероятность решает другую задачу. На первый взгляд логично сразу усилить основной вариант и ослабить остальные. Но маловероятный разворот может быть важнее для открытой позиции, чем наиболее вероятное продолжение тренда. Если уменьшить его вклад до анализа, модель рискует не распознать угрозу.
Сначала каждый сценарий сопоставляется с торговым контекстом. Затем его вероятность регулирует вклад в общее решение. Первый этап отвечает на вопрос, что этот вариант означает для счёта и позиций. Второй определяет, насколько сильно его следует учитывать среди остальных продолжений.
Для Актёра этого всё ещё недостаточно. Его решения образуют последовательность. Если каждый бар воспринимать как совершенно новую ситуацию, небольшое встречное движение может вызвать закрытие позиции, следующий бар — повторный вход, а затем новую смену направления. Отдельные действия будут выглядеть допустимо, но общий план исчезнет.
Практикующий трейдер обычно допускает определённое отклонение цены и пересматривает решение только тогда, когда рынок выходит за границы исходной гипотезы. Поэтому Актёру нужна память не просто о предыдущей операции, а о том, как ранее были интерпретированы рынок, торговый контекст и возможные варианты будущего.
Новые сценарии следует сопоставлять с этим накопленным представлением. Если прогноз продолжает прежнюю картину, модель получает основание сохранить последовательность поведения. Если структура сценариев заметно изменилась, прежний контекст теряет актуальность. Такая память не удерживает позицию искусственно, а помогает отделить обычный шум от содержательной смены режима.
При этом история не должна выбирать один сценарий заранее. Каждый вариант остаётся самостоятельным до завершающего объединения. Актёр может продолжать основной план и одновременно отслеживать альтернативы. Критику такая память не требуется: он оценивает конкретное действие в текущем состоянии и не формирует собственную последовательность решений.
После независимого анализа, а для Актёра — ещё и после сопоставления с историей, сценарии можно объединить. Мы не усредняем будущие цены. Объединяются уже интерпретированные ответы на торговые вопросы. Несколько согласованных вариантов усиливают общий контекст, а противоречия между ними сохраняют неоднозначность и могут привести к более осторожному действию.
Отдельные запросы нужны и Критику. Он получает тот же контекст, но оценивает уже предложенную операцию. Покупка и продажа не являются зеркальными действиями: они по-разному взаимодействуют с открытой позицией, доступной маржой и набором прогнозных сценариев. Поэтому оценку обоих направлений следует сохранить раздельной до формирования итогового значения функции качества.
Подход фиксирует границу ответственности между блоками: прогнозный модуль формирует варианты продолжения и оценивает их свойства, а торговая политика интерпретирует их применительно к состоянию счёта и открытым позициям. Благодаря этому прогнозный модуль можно обучать на фактическом будущем, не подстраивая его представление непосредственно под текущую ошибку Актёра.
В результате торговая политика получает компактное представление состояния счёта, позиций и времени, обогащённое информацией о возможном будущем. Для Критика к нему добавляется оценка предлагаемой покупки и продажи. Такой уровень сохраняет логику ORION: между пониманием ситуации и числовым действием остаётся отдельное пространство, где возможные траектории получают практический смысл.
Объект сценарного кросс-внимания
Архитектурный замысел реализован в классе CNeuronScenarioCrossAttention. Объект получает торговый контекст и готовый сценарный прогноз, сопоставляет каждый вариант будущего с запросами Актёра или Критика, а затем возвращает компактное представление для следующих слоёв торговой политики.
class CNeuronScenarioCrossAttention : public CNeuronMSRes { protected: uint iVariables; uint iScenarios; uint iHorizon; uint iDimension; uint iQueries; uint iStackSize; uint iTopK; bool bCritic; CNeuronMultiWindowsConvOCL cQueryProjection; CNeuronBaseOCL cQueryGradient; CNeuronConvOCL cWz; CNeuronConvOCL cWu; CNeuronBaseOCL cTokenSum; CNeuronLearnabledPE cTokenPE; CNeuronTransposeRCDOCL cTokensTranspose; CNeuronBaseOCL cKV; CNeuronBaseOCL cKVGradK; CNeuronBaseOCL cKVGradV; CCrossMHFlashAttention cAttention; CNeuronTransposeRCDOCL cAttentionTranspose; CNeuronBaseOCL cHeadGate; CNeuronTransposeRCDOCL cHeadGateTranspose; CNeuronConvOCL cOutputProjection; CNeuronConvOCL cQueryResidual; CNeuronBaseOCL cMHAOutput; //--- Actor-only MPI continuity. The scenario axis remains intact through //--- cScenarioHistory; Pi is applied only afterwards in cHeadGate. CNeuronAddToStack cHistoryStack; CNeuronMomADTTM cHistoryTTM; CNeuronConvOCL cHistoryTokenProjection; CCrossMHFlashAttention cHistoryMarket; CNeuronCrossAttention cHistoryContext; CNeuronCrossAttention cScenarioHistory; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override { ReturnFalse; } virtual bool feedForwardSecond(CNeuronBaseOCL *NeuronOCL, CNeuronBaseOCL *Forecast) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override { ReturnFalse; } virtual bool calcInputGradientsSecond(CNeuronBaseOCL *NeuronOCL, CNeuronBaseOCL *Forecast) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override { ReturnFalse; } virtual bool updateInputWeightsSecond(CNeuronBaseOCL *NeuronOCL, CNeuronBaseOCL *Forecast) override; bool InitHolders(uint myIndex, ENUM_OPTIMIZATION optimization_type, uint batch); public: CNeuronScenarioCrossAttention(void) : iVariables(0), iScenarios(0), iHorizon(0), iDimension(0), iQueries(0), iStackSize(0), iTopK(0), bCritic(false) {}; ~CNeuronScenarioCrossAttention(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint scenarios = 21, uint horizon = 12, uint latent = 16, bool critic = false, ENUM_OPTIMIZATION optimization_type = ADAM, uint batch = 1, uint variables = 1, uint stack_size = 1, uint top_k = 1); virtual int Type(void) override const { return defNeuronScenarioCrossAttention; } virtual bool Save(const int file_handle) override; virtual bool Load(const int file_handle) override; bool Load(const int file_handle, COpenCLMy *open_cl); virtual void SetOpenCL(COpenCLMy *obj) override; virtual void TrainMode(bool flag) override; virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; virtual bool Clear(void) override; uint Variables(void) const { return iVariables; } uint Scenarios(void) const { return iScenarios; } uint Horizon(void) const { return iHorizon; } uint Latent(void) const { return iDimension; } uint Queries(void) const { return iQueries; } uint HistorySize(void) const { return iStackSize; } uint HistoryTopK(void) const { return iTopK; } bool IsCritic(void) const { return bCritic; } CNeuronConvOCL *GetWz(void) { return GetPointer(cWz); } CNeuronConvOCL *GetWu(void) { return GetPointer(cWu); } CBufferFloat *GetHeadContexts(void) { return (bCritic ? cAttention.getOutput() : cScenarioHistory.getOutput()); } bool HasHistory(void) const { return !bCritic; } CNeuronAddToStack *GetHistoryStack(void) { return (bCritic ? NULL : GetPointer(cHistoryStack)); } };
Класс наследуется от CNeuronMSRes. Родительский объект имеет однопоточную архитектуру и не объединяет торговый контекст с прогнозом. Его роль начинается после внимания: CNeuronMSRes выполняет спайковый аналог блока FeedForward классического Transformer и добавляет остаточную связь. Двухпоточная обработка реализована уже в производном классе через специализированные методы с суффиксом Second; обычные однопоточные варианты намеренно заблокированы.
Поля iVariables, iScenarios, iHorizon и iDimension фиксируют контракт с прогнозным слоем: количество рыночных последовательностей, число сценариев, горизонт и размерность латентного состояния. Актёр формирует три запроса — по балансу, позициям и времени. Критик добавляет запросы для покупки и продажи, поэтому использует пять. Параметры iStackSize и iTopK относятся только к памяти Актёра.
Такое совпадение размеров принципиально. Слой не пытается угадать физическую раскладку входного буфера по его общей длине. Он заранее знает, сколько сценариев, переменных и шагов горизонта должен получить. Это снижает риск скрытой ошибки, когда два буфера имеют одинаковое число элементов, но описывают разные структуры данных.
Метод инициализации выполняет проверку обязательных размеров и OpenCL-контекста.
bool CNeuronScenarioCrossAttention::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint scenarios, uint horizon, uint latent, bool critic, ENUM_OPTIMIZATION optimization_type, uint batch, uint variables, uint stack_size, uint top_k) { if(!open_cl || variables == 0 || scenarios == 0 || horizon == 0 || latent == 0 || stack_size == 0 || top_k == 0 || top_k > scenarios) ReturnFalse;
После проверки сохраняем конфигурацию и сразу определяем режим объекта.
iVariables = variables; iScenarios = scenarios; iHorizon = horizon; iDimension = latent; iStackSize = stack_size; iTopK = top_k; bCritic = critic; iQueries = (bCritic ? 5 : 3);
Затем инициализируем базовый CNeuronMSRes. Его выход содержит по одному вектору размерности iDimension для каждого торгового запроса. Все собственные компоненты сценарного внимания создаются во вспомогательном методе InitHolders.
if(!CNeuronMSRes::Init(numOutputs, myIndex, open_cl, iQueries, iDimension, 1, optimization_type, batch) || !InitHolders(myIndex, optimization_type, batch)) ReturnFalse; SetActivationFunction(None); return true; }
В InitHolders сначала задаём структуру торгового контекста. Четыре первых значения описывают баланс, следующие пять — позиции, ещё четыре — фактическую метку времени. Для Критика добавляются два окна по три параметра, соответствующие операциям Buy и Sell. Здесь же рассчитываем общее число прогнозных токенов и размер исторического контекста.
bool CNeuronScenarioCrossAttention::InitHolders(uint myIndex, ENUM_OPTIMIZATION optimization_type, uint batch) { uint state_windows[]; ArrayResize(state_windows, bCritic ? 5 : 3); state_windows[0] = 4; state_windows[1] = 5; state_windows[2] = 4; if(bCritic) { state_windows[3] = 3; state_windows[4] = 3; } const uint tokens = iScenarios * iVariables * iHorizon; const uint mpi_dimension = iQueries * iDimension; const uint mpi_heads = 4; const uint mpi_key = mpi_dimension / mpi_heads;
Объект cQueryProjection обрабатывает окна раздельно, но за один проход. Каждое окно разворачивается сразу на всю сценарную ширину. Поэтому один и тот же торговый контекст получает отдельное представление для каждого варианта будущего, а не копируется после общей проекции.
Для практического трейдинга это означает, что модель не задаёт прогнозу один общий вопрос вроде «рынок вырастет или упадёт». Она отдельно выясняет, что каждый вариант означает для доступного капитала, уже открытых позиций и текущего календарного режима. В режиме Критика к этим вопросам добавляется согласованность конкретных параметров покупки и продажи.
if(!cQueryProjection.Init(0, index++, OpenCL, state_windows, iScenarios * iDimension, 1, 1, optimization_type, batch)) ReturnFalse; if(!cQueryGradient.Init(0, index++, OpenCL, iQueries * iScenarios * iDimension, optimization_type, batch)) ReturnFalse;
Прогнозный токен объединяет две составляющие. cWz проецирует латентное состояние, а cWu переводит скалярную логарифмическую оценку неопределённости в ту же размерность. Их сумма описывает не только ожидаемое состояние, но и надёжность соответствующего участка траектории.
if(!cWz.Init(0, index++, OpenCL, iDimension, iDimension, iDimension, tokens, optimization_type, batch)) ReturnFalse; if(!cWu.Init(0, index++, OpenCL, 1, 1, iDimension, tokens, optimization_type, batch)) ReturnFalse; if(!cTokenSum.Init(0, index++, OpenCL, tokens * iDimension, optimization_type, batch)) ReturnFalse;
К объединённым признакам добавляется обучаемое позиционное кодирование. Оно позволяет различать сценарии, переменные и шаги горизонта даже при близких латентных значениях. После этого тензор перестраивается так, чтобы каждая голова внимания получила только токены своего сценария.
if(!cTokenPE.Init(0, index++, OpenCL, tokens * iDimension, optimization_type, batch)) ReturnFalse; if(!cTokensTranspose.Init(0, index++, OpenCL, iScenarios, iVariables * iHorizon, iDimension, optimization_type, batch)) ReturnFalse;
Подготовленные признаки используются одновременно как ключи и значения, поэтому дважды записываются в общий буфер cKV. Число голов cAttention равно числу сценариев, а внутри каждой головы доступны все переменные и шаги соответствующей траектории. Такое размещение сохраняет границы между вариантами будущего.
if(!cKV.Init(0, index++, OpenCL, 2 * tokens * iDimension, optimization_type, batch)) ReturnFalse; if(!cKVGradK.Init(0, index++, OpenCL, tokens * iDimension, optimization_type, batch)) ReturnFalse; if(!cKVGradV.Init(0, index++, OpenCL, tokens * iDimension, optimization_type, batch)) ReturnFalse; if(!cAttention.Init(0, index++, OpenCL, iDimension, iScenarios, false, iQueries, iVariables * iHorizon, optimization_type, batch)) ReturnFalse; if(!cAttentionTranspose.Init(0, index++, OpenCL, iQueries, iScenarios, iDimension, optimization_type, batch)) ReturnFalse;
После внимания результаты переводятся в сценарную раскладку. Буфер cHeadGate предназначен для последующего применения априорных вероятностей. Они не участвуют в первоначальном распределении внимания: сначала каждый сценарий проходит полноценный анализ.
if(!cHeadGate.Init(0, index++, OpenCL, iQueries * iScenarios * iDimension, optimization_type, batch)) ReturnFalse; if(!cHeadGateTranspose.Init(0, index++, OpenCL, iScenarios, iQueries, iDimension, optimization_type, batch)) ReturnFalse;
Затем выходная проекция объединяет сценарные ответы одного запроса и уменьшает их общую ширину до iDimension. Параллельная ветвь cQueryResidual сохраняет исходный смысл запроса. Их сумма образует вход для унаследованного блока CNeuronMSRes.
if(!cOutputProjection.Init(0, index++, OpenCL, iScenarios * iDimension, iScenarios * iDimension, iDimension, iQueries, 1, optimization_type, batch)) ReturnFalse; if(!cQueryResidual.Init(0, index++, OpenCL, iScenarios * iDimension, iScenarios * iDimension, iDimension, iQueries, 1, optimization_type, batch)) ReturnFalse; if(!cMHAOutput.Init(0, index++, OpenCL, iQueries * iDimension, optimization_type, batch)) ReturnFalse;
Для Актёра создаётся дополнительный контур преемственности. Стек хранит предыдущий итоговый выход слоя, а не сырые котировки или список операций. Это компактное представление того, как модель ранее связала торговый контекст с прогнозными сценариями.
//--- The Actor keeps scenario-major contexts until the final Pi gate. TTM //--- sees K candidates of width Q*D, while the stack holds the previous final //--- Cross output of the same layer. if(!bCritic) { if(!cHistoryStack.Init(0, index++, OpenCL, iStackSize, mpi_dimension, 1, optimization_type, batch)) { PrintFormat("ScenarioCross Init: history_stack S=%u D=%u",iStackSize,mpi_dimension); ReturnFalse; } if(!cHistoryTTM.Init(0, index++, OpenCL, mpi_dimension, iScenarios, iStackSize, iTopK, optimization_type, batch)) { PrintFormat("ScenarioCross Init: history_ttm K=%u S=%u D=%u",iScenarios,iStackSize,mpi_dimension); ReturnFalse; }
cHistoryTTM сопоставляет текущие сценарные контексты с накопленной историей. Затем прогнозные токены подготавливаются для внимания, которое связывает предыдущие состояния Актёра с актуальным прогнозом.
//--- Flash attention receives preprojected Key/Value pairs. Unlike the //--- generic cross-attention kernel it tiles an arbitrary token sequence //--- through a local group bounded by the device capability. if(!cHistoryTokenProjection.Init(0, index++, OpenCL, iDimension, iDimension, 2 * mpi_dimension, tokens, optimization_type, batch)) { PrintFormat("ScenarioCross Init: history_tokens T=%u D=%u",tokens,mpi_dimension); ReturnFalse; } if(!cHistoryMarket.Init(0, index++, OpenCL, mpi_key, mpi_heads, false, iStackSize, tokens, optimization_type, batch)) { PrintFormat("ScenarioCross Init: history_market Q=%u K=%u D=%u",iStackSize,tokens,mpi_dimension); ReturnFalse; } if(!cHistoryContext.Init(0, index++, OpenCL, mpi_dimension, mpi_key, mpi_heads, 1, mpi_dimension, iStackSize, optimization_type, batch)) { PrintFormat("ScenarioCross Init: history_context S=%u D=%u",iStackSize,mpi_dimension); ReturnFalse; } if(!cScenarioHistory.Init(0, index++, OpenCL, mpi_dimension, mpi_key, mpi_heads, iScenarios, mpi_dimension, 1, optimization_type, batch)) { PrintFormat("ScenarioCross Init: scenario_refine K=%u D=%u",iScenarios,mpi_dimension); ReturnFalse; } }
Полученный исторический контекст уточняет каждый текущий сценарий отдельно. Сценарная ось сохраняется до вероятностного гейта: память не выбирает один вариант и не создаёт среднюю траекторию. Критик остаётся без внутренней истории и использует непосредственный результат первого внимания.
cQueryGradient.SetActivationFunction(None); cWz.SetActivationFunction(None); cWu.SetActivationFunction(None); cTokenSum.SetActivationFunction(None); cKV.SetActivationFunction(None); cKVGradK.SetActivationFunction(None); cKVGradV.SetActivationFunction(None); cHeadGate.SetActivationFunction(None); cMHAOutput.SetActivationFunction(None); cOutputProjection.SetActivationFunction(None); cQueryResidual.SetActivationFunction(None); cHistoryTokenProjection.SetActivationFunction(None); return true; }
В конце инициализации для технических буферов и линейных проекций отключаются дополнительные функции активации. Нелинейная обработка выполняется механизмами внимания и родительским спайковым блоком.
Прямой проход реализован в методе feedForwardSecond. Первый аргумент содержит торговый контекст, второй указывает на объект CNeuronScenarioForecast. В начале проверяются типы и размеры входов: Актёр ожидает 13 значений, Критик — 19.
bool CNeuronScenarioCrossAttention::feedForwardSecond(CNeuronBaseOCL *NeuronOCL, CNeuronBaseOCL *Forecast) { if(!OpenCL || !NeuronOCL || !Forecast || Forecast.Type() != defNeuronScenarioForecast || NeuronOCL.Neurons() != int(bCritic ? 19 : 13)) ReturnFalse;
Передача объекта CNeuronScenarioForecast (а не разрозненных копий данных) сохраняет единый контракт между частями модели. Слой получает основной тензор сценариев, оценки неопределённости и вероятности из одного источника и может сразу проверить их согласованность. При этом торговый контур использует готовый прогноз как входные данные и не меняет его внутреннюю специализацию.
CNeuronScenarioForecast *forecast = Forecast; if(forecast.Variables() != iVariables || forecast.Scenarios() != iScenarios || forecast.Horizon() != iHorizon || forecast.Dimension() != iDimension || Forecast.Neurons() != int(iScenarios * iVariables * iHorizon * iDimension) || !forecast.GetLogU() || forecast.GetLogU().Total() != int(iScenarios * iVariables * iHorizon) || !forecast.GetPi() || forecast.GetPi().Total() != int(iScenarios)) ReturnFalse;
После приведения второго входа к типу прогнозного слоя проверяем его контракт. Количество переменных, сценариев, шагов горизонта и латентная размерность должны совпадать с настройками текущего объекта. Отдельно контролируются размеры основного выхода, логарифмов неопределённости и вектора вероятностей.
if(!cQueryProjection.FeedForward(NeuronOCL))
ReturnFalse;
Сначала формируются сценарные запросы. Параллельно проецируются латентные состояния и неопределённость, их результаты складываются и дополняются позиционным кодированием. После транспонирования одно и то же подготовленное представление помещается в части ключей и значений общего буфера. Все операции выполняются на стороне OpenCL.
if(!cWz.FeedForward(Forecast)) ReturnFalse; if(!cWu.FeedForwardBuffer(forecast.GetLogU())) ReturnFalse; if(!SumAndNormalize(cWz.getOutput(), cWu.getOutput(), cTokenSum.getOutput(), 1, false, 0, 0, 0, 1)) ReturnFalse; if(!cTokenPE.FeedForward(cTokenSum.AsObject())) ReturnFalse; if(!cTokensTranspose.FeedForward(cTokenPE.AsObject())) ReturnFalse; if(!Concat(cTokensTranspose.getOutput(), cTokensTranspose.getOutput(), cKV.getOutput(), iScenarios * iDimension, iScenarios * iDimension, iVariables * iHorizon)) ReturnFalse;
Далее запускается сценарное кросс-внимание. Каждая голова получает свои запросы и токены только одного варианта будущего. Внутри неё внимание распределяется по всем переменным и шагам траектории, после чего ответы группируются по сценариям.
if(!cAttention.FeedForward(cQueryProjection.AsObject(), cKV.getOutput())) ReturnFalse; if(!cAttentionTranspose.FeedForward(cAttention.AsObject())) ReturnFalse;
На следующем этапе пути Актёра и Критика различаются. Критик не хранит историю, поэтому его сценарные контексты сразу масштабируются априорными вероятностями. Актёр сначала помещает предыдущий выход слоя в стек, сопоставляет историю с текущими сценариями и актуальными прогнозными токенами, а затем уточняет каждый вариант относительно полученного контекста.
//--- Critic is stateless. Actor first derives a shared history context from //--- all ungated K scenario contexts, then refines each scenario separately. if(!bCritic) { if(!cHistoryStack.FeedForward(GetPointer(this))) ReturnFalse; if(!cHistoryTTM.FeedForward(cAttentionTranspose.AsObject(), cHistoryStack.getOutput())) ReturnFalse; if(!cHistoryTokenProjection.FeedForward(cTokenPE.AsObject())) ReturnFalse; if(!cHistoryMarket.FeedForward(cHistoryStack.AsObject(), cHistoryTokenProjection.getOutput())) ReturnFalse; if(!cHistoryContext.FeedForward(cHistoryTTM.AsObject(), cHistoryMarket.getOutput())) ReturnFalse; if(!cScenarioHistory.FeedForward(cAttentionTranspose.AsObject(), cHistoryContext.getOutput())) ReturnFalse; if(!ScalarToVector(forecast.GetPi(), cScenarioHistory.getOutput(), cHeadGate.getOutput(), iQueries * iDimension)) ReturnFalse; } else if(!ScalarToVector(forecast.GetPi(), cAttentionTranspose.getOutput(), cHeadGate.getOutput(), iQueries * iDimension)) ReturnFalse;
Вероятности применяются только после этого уточнения. Одна вероятность масштабирует всё представление соответствующего сценария, но не меняет внутреннее распределение внимания. Благодаря такому порядку даже редкий вариант успевает показать своё значение для открытой позиции или торгового риска.
После вероятностного гейта обе ветви снова сходятся. Результаты возвращаются к структуре торговых запросов и поступают в общую выходную проекцию.
if(!cHeadGateTranspose.FeedForward(cHeadGate.AsObject())) ReturnFalse; if(!cOutputProjection.FeedForward(cHeadGateTranspose.AsObject())) ReturnFalse;
Параллельно исходные запросы проходят остаточную проекцию. После сложения ветвей получаем результат сценарного внимания с первой остаточной связью.
if(!cQueryResidual.FeedForward(cQueryProjection.AsObject())) ReturnFalse; if(!SumAndNormalize(cOutputProjection.getOutput(), cQueryResidual.getOutput(), cMHAOutput.getOutput(), 1, true, 0, 0, 0, 1)) ReturnFalse;
Завершает проход вызов родительского класса.
if(!CNeuronMSRes::feedForward(cMHAOutput.AsObject())) ReturnFalse; return true; }
Полученный выход остаётся компактным и не заставляет последующие слои снова работать со всем прогнозным тензором. Это важно не только для вычислительной эффективности. К этому моменту сценарии уже выполнили свою роль: каждый вариант был сопоставлен с торговыми запросами, проверен относительно истории Актёра и взвешен своей вероятностью. Дальнейшей политике нужен итог этого анализа, а не повторный доступ к исходным траекториям.
CNeuronMSRes выполняет спайковый вариант FeedForward-блока и формирует вторую остаточную связь. На выходе Актёр получает три обогащённых представления — баланса, позиций и фактического времени. Критик дополнительно получает контексты покупки и продажи. Сценарная ось к этому моменту свёрнута, но итоговые признаки сохраняют информацию о траекториях, неопределённости, вероятностях и, для Актёра, связи с предыдущими решениями.
Заключение
В этой статье мы продолжили адаптацию идей фреймворка ORION к финансовым рынкам и решили задачу, поставленную во введении: связали пространство возможных траекторий с конкретным торговым состоянием. Сам сценарный прогноз показывает, как может развиваться рынок, но не учитывает состояние счёта, открытые позиции и календарный контекст. Новый модуль заполняет этот разрыв.
Сценарное кросс-внимание рассматривает каждый вариант будущего отдельно и сохраняет целостность его траектории. Неопределённость включается в содержание прогнозных токенов, а априорная вероятность применяется только после содержательного анализа. Для Актёра дополнительно учитывается история предыдущих решений, что помогает отличать обычный рыночный шум от реальной смены режима. Критик остаётся без внутренней памяти и оценивает предлагаемое действие в текущем состоянии.
В практической части создан объект CNeuronScenarioCrossAttention, рассмотрены его инициализация и прямой проход. Полученный слой переводит набор прогнозных сценариев в компактные представления баланса, позиций, времени и оцениваемых операций. Тем самым архитектура получила связующее звено между прогнозным модулем и торговой политикой: возможное будущее больше не сводится к одной усреднённой линии, а становится основой последовательного торгового решения.
Ссылки
- ORION: A Holistic End-to-End Autonomous Driving Framework by Vision-Language Instructed Action Generation
- Другие статьи серии
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | Study.mq5 | Советник | Советник офлайн-обучения моделей |
| 2 | StudyOnline.mq5 | Советник | Советник онлайн-обучения моделей |
| 3 | StudyStateEncoder.mq5 | Советник | Советник офлайн-обучения прогнозной модели |
| 4 | Test.mq5 | Советник | Советник для тестирования модели |
| 5 | Trajectory.mqh | Библиотека класса | Структура описания состояния системы и архитектуры моделей |
| 6 | NeuroNet.mqh | Библиотека класса | Библиотека классов для создания нейронной сети |
| 7 | NeuroNet.cl | Библиотека | Библиотека кода OpenCL‑программы |
Проект представлен на forge.mql5.io/dng.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Ручное тестирование на исторических данных в тестере стратегий MetaTrader 5 с кнопками Buy/Sell на графике
Возможности Мастера MQL5, которые вам нужно знать (Часть 77): Использование индикаторов Gator и A/D (Accumulation/Distribution)
Возможности Мастера MQL5, которые вам нужно знать (Часть 78): Стратегии на основе осцилляторов Gator и A/D для повышения устойчивости на рынке
Валютный граф и справедливая цена 28 пар через DFS по кросс-путям
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования