Нейросети в трейдинге: Единый взгляд на пространство и время (Окончание)
Введение
Финансовые рынки можно сравнить с живым организмом, где каждое движение цены напоминает биение пульса, а каждая новость или макроэкономическое решение — удар сердца, запускающий волны перемен по всему телу системы. Они дышат, колеблются, растут и падают в сложном ритме, который отражает поведение миллионов участников. В такой среде аналитик и трейдер сталкиваются с задачей не просто улавливать мгновенные импульсы, но и прогнозировать более длительные тенденции, часто скрытые под поверхностным шумом. Здесь на первый план выходит работа с пространственно-временными данными. Ведь любая рыночная ситуация развивается одновременно в двух измерениях: по оси времени и по оси цены, отражающей пространство торговых решений.
Фреймворк Extralonger нацелен преодолеть барьеры традиционных моделей и вывести прогнозирование на новый уровень. Его ключевое преимущество — способность уверенно работать на экстремально длинных горизонтах. Для сравнения, подавляющее большинство существующих алгоритмов ограничены минутными или часовыми интервалами. В таких рамках можно отследить кратковременные импульсы или локальные тренды, но невозможно выстроить системное видение, позволяющее заглянуть за горизонт и увидеть формирование крупных движений. Extralonger же открывает путь к долгосрочному прогнозированию, сохраняя точность и стабильность даже там, где другие методы теряют ориентиры.
Это качество особенно важно в торговле. Для трейдера, работающего на внутридневных таймфреймах, каждая минута может быть решающей. Но ещё более ценно понимание того, как будет вести себя рынок завтра или через несколько дней. Для инвестора знание будущих тенденций на горизонте недели и более становится инструментом управления рисками и построения стратегий. Extralonger объединяет эти два подхода, позволяя рассматривать рынок одновременно вблизи и вдали. Как путешественник использует лупу для изучения деталей карты, а подзорную трубу — для оценки удалённых контуров местности.
Второе важнейшее преимущество фреймворка — высокая вычислительная эффективность. Проблема классических методов заключается в раздельном анализе временного и пространственного измерения. Анализируя динамику временных рядов, алгоритм вынужден повторять вычисления для каждой рыночной точки или инструмента, а при обработке пространственных связей он повторяет операции во временной плоскости. Такое дублирование процедуры быстро раздувает ресурсоёмкость. Модель требует всё больше памяти и времени, что ограничивает длину прогнозируемого горизонта. В условиях финансовых рынков, где скорость реакции на изменение цен критична, подобные ограничения превращаются в серьёзный барьер.
Extralonger решает эту задачу принципиально новым способом. Его основа — концепция Unified Spatial-Temporal Representation — единое пространственно-временное представление. Эта идея навеяна теорией относительности Эйнштейна. Так же как в физике пространство и время образуют единый континуум, в финансовых данных момент времени нельзя рассматривать в отрыве от ценового уровня, а локальную рыночную структуру — без учёта временного контекста. Таким образом, каждый элемент данных содержит одновременно и временную, и пространственную информацию. Это снимает необходимость в повторных вычислениях и сокращает сложность модели на порядок.
Как следствие, Extralonger обеспечивает колоссальные преимущества:
- сокращение потребления памяти,
- ускорение обучения,
- повышение скорости прогнозирования.
В терминах практического применения это значит, что модель может обучаться и работать даже в средах с ограниченными вычислительными ресурсами.
На архитектурном уровне фреймворк Extralonger строится вокруг трёх параллельных маршрутов обработки информации: темпорального, пространственного и смешанного. Каждый из них играет особую роль. Темпоральный маршрут анализирует последовательность рыночных событий и ищет закономерности в динамике цен. Пространственный маршрут рассматривает сеть взаимосвязей между инструментами и активами, выявляя скрытые корреляции и структуры. Смешанный маршрут объединяет оба подхода, создавая целостную картину.
Особое место в Extralonger занимает модуль Global-Local Spatial Transformer, который можно сравнить с двойным объективом аналитика. С одной стороны, глобальное внимание позволяет учитывать связи между отдалёнными элементами рынка. С другой — локальное внимание сосредоточено на ближайших связях, таких как краткосрочные корреляции между соседними валютами внутри одной торговой сессии. Такое сочетание позволяет модели одинаково успешно работать с крупными тенденциями и с локальными импульсами.
Ещё одно уникальное качество Extralonger — полный рецептивный диапазон. Это значит, что модель способна одновременно учитывать всю доступную историю и связывать её с любым текущим событием. В отличие от алгоритмов, ограниченных скользящим окном или фиксированным временным шагом, Extralongerвидит рынок в его целостности. Он может сопоставить поведение цены сегодня с событиями недельной давности или обнаружить долгосрочную зависимость, которая проявляется только на больших временных интервалах. Для финансовых рынков это особенно ценно, ведь многие движения вызваны не отдельными событиями, а накоплением факторов, которые постепенно складываются и воплощаются в масштабных трендах.
Авторская визуализация фреймворка Extralonger представлена ниже.

Наш проект развивается как последовательное движение от простого к сложному, от отдельных элементов — к целостной архитектуре. Вначале мы сосредоточились на концептуальной основе Extralonger и сделали первые практические шаги к её реализации средствами MQL5. На этом этапе мы реализовали модули пространственного и временного кодирования, которые позволили привязать данные к рыночной структуре и сформировать основу для дальнейшей интеграции.
Затем перешли к архитектурному оформлению фреймворка. Если раньше мы работали скорее с кирпичами, то теперь стали возводить стены и перекрытия. Мы подробно рассмотрели реализацию модуля Global-Local Spatial Transformer. На этом этапе у нас появилась возможность связать отдельные элементы в целостный вычислительный процесс. Другими словами, мы начали формировать ту самую архитектуру наблюдения, которая в дальнейшем позволит охватить рынок в его многомерности.
Эти два шага можно сравнить с подготовкой сцены для большого спектакля. Сначала мы расставили декорации, определили, где будут расположены ключевые элементы, затем вывели на сцену первых актёров — модули обработки данных. И теперь перед нами открывается возможность показать зрителю весь замысел постановки. Мы переходим к самому алгоритму, к сердцу Extralonger. Именно здесь, на пересечении глобально-локального внимания, единого представления и трёх параллельных маршрутов, раскрывается уникальность подхода.
Объект верхнего уровня
На вершине всей иерархии нашей реализации подходов фреймворка Extralonger находится объект CNeuronExtralonger, который можно рассматривать как сердце архитектуры, объединяющее ключевые модули в единый вычислительный цикл. Если ранее рассмотренные классы и блоки отвечали за отдельные элементы конструкции, то здесь мы имеем дело с центральным узлом, который связывает всё воедино.
class CNeuronExtralonger : public CNeuronMHAttentionPooling { protected: CLayer cProjectionT; CLayer cTimeModule; CLayer cSpatialModule; CLayer cMixModule; CNeuronBaseOCL cConcatResults; //--- virtual bool feedForward(CNeuronBaseOCL *NeuronOCL) override {return false;} virtual bool feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) override; virtual bool updateInputWeights(CNeuronBaseOCL *NeuronOCL) override; virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL) override {return false;} virtual bool calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None ) override; public: CNeuronExtralonger(void) {}; ~CNeuronExtralonger(void) {}; //--- virtual bool Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint time_steps_in, uint time_steps_out, uint variables, uint dimension, uint emb_dimension, uint period1, uint frame1, uint period2, uint frame2, uint layers, uint heads, uint dimension_k, uint m_units, float sparse, ENUM_OPTIMIZATION optimization_type, uint batch); //--- virtual int Type(void) const { return defNeuronExtralonger; } //--- methods for working with files virtual bool Save(int const file_handle) override; virtual bool Load(int const file_handle) override; //--- virtual bool WeightsUpdate(CNeuronBaseOCL *source, float tau) override; virtual void SetOpenCL(COpenCLMy *obj) override; virtual void SetActivationFunction(ENUM_ACTIVATION value) override { }; };
По своей структуре CNeuronExtralonger наследуется от класса CNeuronMHAttentionPooling, что сразу указывает на его функциональную направленность. В основе обработки данных остаётся механизм многоголового внимания с механизмом агрегирования, а поверх него накладываются дополнительные уровни, реализующие уникальные идеи Extralonger.
И здесь мы подходим к важному отличию нашей реализации от авторского решения. В оригинальном варианте Extralonger коэффициенты акцентов для трёх маршрутов потока данных (временного, пространственного и смешанного) задаются жёстко и остаются фиксированными. Такой подход удобен своей простотой, но в условиях изменчивых финансовых рынков он неизбежно ведёт к потере гибкости. В одних ситуациях ключевую роль играют краткосрочные временные паттерны, в других — глобальные пространственные корреляции, а иногда решающее значение имеет их комбинация. Фиксированные коэффициенты не способны динамически учитывать этот сдвиг акцентов, что снижает точность прогнозов.
Именно поэтому в нашей реализации мы пошли дальше и внедрили адаптивный механизм Attention Pooling, заимствованный из фреймворка R-MAT. Его суть заключается в том, что весовые коэффициенты для маршрутов не определяются заранее, а вычисляются в процессе работы модели. Каждый новый сигнал исходных данных получает собственное распределение внимания между временным, пространственным и смешанным модулями. Благодаря этому, система становится чувствительной к рыночному контексту. В фазе спокойной консолидации усиливается роль временного анализа, при внешних потрясениях большее значение приобретают пространственные связи, а в моменты высокой волатильности выходит на первый план смешанный маршрут.
Такое решение превращает CNeuronExtralonger в гораздо более живой инструмент. В отличие от статической схемы оригинала, где распределение внимания всегда одинаково, адаптивный Attention Pooling делает систему гибкой и самонастраивающейся. Она ведёт себя как опытный трейдер, который умеет менять акценты в зависимости от ситуации.
Внутри объекта сосредоточены четыре ключевых компонента, которые представляют собой динамические массивы. В них собираются последовательности нейронных слоёв внутренних информационных потоков. На первый взгляд может показаться странным создание четырех внутренних модулей. Ведь в архитектуре Extralonger упоминается только три магистрали — временная, пространственная и смешанная. Здесь важно отметить одну особенность. Магистрали временного и смешанного анализа используют один и тот же блок подготовки данных. Чтобы избежать дублирования и повысить управляемость, мы решили выделить блок подготовки данных в отдельный самостоятельный компонент — cProjectionT. По сути, это общий шлюз, который обеспечивает согласованное преобразование исходных последовательностей в эмбеддинги, пригодные для дальнейшей работы.
При этом магистраль пространственного анализа мы объединили с соответствующим модулем подготовки данных в единый блок — cSpatialModule. Такой шаг продиктован спецификой пространственных зависимостей. Здесь предварительная обработка данных и сама модель настолько тесно связаны, что их целесообразно рассматривать как одну внутреннюю структуру.
Таким образом, у нас получаются четыре компонента:
- cProjectionT — блок подготовки данных для временной и смешанной магистралей,
- cTimeModule — модель временного анализа,
- cSpatialModule — объединённая модель подготовки данных и пространственного анализа,
- cMixModule — смешанный маршрут, интегрирующий временные и пространственные признаки.
Именно такая организация позволяет сохранить логику оригинальной архитектуры Extralonger, но при этом сделать реализацию более модульной и удобной в сопровождении.
От общего контура архитектуры мы постепенно переходим к её внутреннему наполнению, и именно здесь открывается настоящая сложность объекта. На верхнем уровне мы видим лишь четыре динамических массива — подготовительный блок временной проекции, временной модуль, пространственный модуль и смешанный маршрут. Но за этим аккуратным фасадом скрывается целая экосистема более мелких компонентов: свёрточных и нормализующих слоёв, транспонирующих преобразователей, обучаемых эмбеддингов и блоков внимания. Они не существуют сами по себе, каждый из них играет строго отведённую роль, а их взаимодействие формирует живой поток информации, который проходит сквозь CNeuronExtralonger.
Весь этот внутренний мир создаётся в методе Init. Можно сказать, что именно здесь закладываются структура и логика поведения модели. Инициализация начинается с вызова одноименного метода родительского класса, обеспечивающего общую схему многоголового Attention Pooling.
bool CNeuronExtralonger::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint time_steps_in, uint time_steps_out, uint variables, uint dimension, uint emb_dimension, uint period1, uint frame1, uint period2, uint frame2, uint layers, uint heads, uint dimension_k, uint m_units, float sparse, ENUM_OPTIMIZATION optimization_type, uint batch) { if(!CNeuronMHAttentionPooling::Init(numOutputs, myIndex, open_cl, variables, time_steps_out, 3, optimization_type, batch)) return false;
А далее начинается самое интересное. Шаг за шагом мы собираем сложный организм, где каждый слой добавляет собственный оттенок в общую картину. Но вначале небольшая подготовительная работа — объявим ряд локальных переменных для временного хранения указателей на объекты.
CNeuronBatchNormOCL *norm = NULL; CNeuronConvOCL *conv = NULL; CNeuronTransposeOCL *transp = NULL; CNeuronLearnabledPE *lnoise = NULL; CNeuronSpatialEmbedding *semb = NULL; CNeuronTempEmbedding *temb = NULL; CNeuronMLMHAttentionOCL *att = NULL; CNeuronGlobalLocalAttention *glatt = NULL;
Первым на сцену выходит блок подготовки исходных данных для магистралей временного и смешанного анализа. Он словно настройщик, который подготавливает инструменты перед концертом. Объект позиционного кодирования добавляет в поток исходных данных обучаемое смещение, которое авторы фреймворка назвали обучаемым шумом. Здесь важно подчеркнуть: это не случайная примесь, а осознанная попытка встроить в модель способность воспринимать временной ритм рынка. Данные о ценах и объёмах никогда не бывают чистыми, они всегда колышутся под действием микроколебаний. Встраивание обучаемого шума превращает эту особенность в инструмент: модель начинает лучше отличать закономерности от хаоса и выстраивать своё восприятие времени на основе реальных рыночных ритмов.
//--- Time projection cProjectionT.Clear(); cProjectionT.SetOpenCL(OpenCL); int index = 0; lnoise = new CNeuronLearnabledPE(); if(!lnoise || !lnoise.Init(0, index, OpenCL, time_steps_in * variables, optimization, iBatch) || !cProjectionT.Add(lnoise)) { DeleteObj(lnoise); return false; }
Сразу за ним следует свёрточный слой, который выполняет особую роль. Он преобразует каждый временной шаг в эмбеддинг, содержащий сведения обо всех анализируемых признаках именно в этот момент времени. По сути, на выходе формируется компактное и информативное представление, которое становится своеобразным снимком состояния рынка для каждого шага. Цена, объёмы, индикаторы и другие переменные складываются в единый вектор. И таким образом модель получает возможность работать не с разрозненными числами, а с цельными представлениями, отражающими весь спектр наблюдений во времени.
index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, variables, variables, dimension, time_steps_in, 1, optimization, iBatch) || !cProjectionT.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None);
Следующий шаг — временной эмбеддинг, где ряды получают дополнительное измерение, отражающее циклы и периоды. Архитектура закладывает сюда два типа повторяемости: краткосрочные и более протяжённые, связанные с дневными и недельными ритмами рынка. Это похоже на то, как музыкант чувствует такт и размер. Независимо от мелодии всегда существует ритмическая основа, без которой невозможно построить композицию. В финансовых данных это выражается в смене торговых сессий, сезонных колебаниях или регулярных фазах активности крупных участников. Временной эмбеддинг буквально вшивает эту ритмику в данные, делая модель чувствительной к тому, что повторяется изо дня в день или из недели в неделю.
index++; temb = new CNeuronTempEmbedding(); uint half_emb = (emb_dimension + 1) / 2; if(!temb || !temb.Init(0, index, OpenCL, time_steps_in, dimension, half_emb, period1, frame1, dimension - half_emb, period2, frame2, optimization, iBatch) || !cProjectionT.Add(temb)) { DeleteObj(temb); return false; }
Завершает подготовительный блок нормализация. Её можно сравнить с выравниванием полотна перед нанесением красок. Если поверхность неровная, рисунок получится искажённым. Нормализация снимает случайные перекосы, приводит данные к устойчивому распределению и тем самым задаёт основу для дальнейшей работы более сложных модулей.
index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, temb.Neurons(), iBatch, optimization) || !cProjectionT.Add(norm)) { DeleteObj(norm); return false; }
Когда данные покидают подготовительный блок, они оказываются в руках модуля временного анализа. Здесь вступает в игру многоголовое внимание, которое раскрывает сразу несколько параллельных перспектив. Оно ищет связи между моментами времени, разделёнными десятками шагов, и учится замечать паттерны, недоступные простому линейному взгляду.
//--- Time Module cTimeModule.Clear(); cTimeModule.SetOpenCL(OpenCL); index++; att = new CNeuronMLMHAttentionOCL(); if(!att || !att.Init(0, index, OpenCL, dimension + emb_dimension, dimension_k, heads, time_steps_in, layers, optimization, iBatch) || !cTimeModule.Add(att)) { DeleteObj(att); return false; }
Вслед за этим, подключается блок проекции данных на заданный горизонт планирования. Его можно сравнить с мостом, соединяющим историю с будущим. Он включает несколько свёрточных слоёв, которые преобразуют эмбеддинги в мультимодальную последовательность временного ряда и одновременно изменяют длину последовательности, растягивая её на требуемый горизонт планирования. Эти слои действуют как линзы с разной глубиной фокусировки: одни подчёркивают локальные движения, другие — выстраивают широкую перспективу.
index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension, variables, time_steps_in, 1, optimization, iBatch) || !cTimeModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(TANH); index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, time_steps_in, variables, optimization, iBatch) || !cTimeModule.Add(transp)) { DeleteObj(transp); return false; } index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, time_steps_out, variables, 1, optimization, iBatch) || !cTimeModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(SoftPlus); index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out, variables, 1, optimization, iBatch) || !cTimeModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None); index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) || !cTimeModule.Add(norm)) { DeleteObj(norm); return false; } index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, variables, time_steps_out, optimization, iBatch) || !cTimeModule.Add(transp)) { DeleteObj(transp); return false; }
На выходе мы получаем уже не просто набор эмбеддингов, а прогнозную траекторию, адаптированную под горизонт планирования.
Параллельно начинает работать смешанный модуль, и именно здесь данные проходят особенно насыщенное путешествие. Его роль уникальна: соединить временные и пространственные признаки, превратив их в единый поток информации. Первым в работу вступает классический блок многоголового внимания, который анализирует временную последовательность в чистом виде. Он ищет закономерности, переклички и повторяющиеся мотивы, которые скрыты в структуре последовательности, и тем самым создаёт прочный каркас для дальнейшего анализа.
//--- Mix Module cMixModule.Clear(); cMixModule.SetOpenCL(OpenCL); uint att_layers = (layers + 1) / 2; index++; att = new CNeuronMLMHAttentionOCL(); if(!att || !att.Init(0, index, OpenCL, dimension + emb_dimension, dimension_k, heads, time_steps_in, att_layers, optimization, iBatch) || !cMixModule.Add(att)) { DeleteObj(att); return false; }
Затем данные проходят через транспонирование, своего рода смену перспективы. Если вначале мы смотрели на ряды в разрезе временных шагов, то теперь внимание смещается в сторону признаков, и именно в этой новой системе координат включается модуль глобально-локального внимания. Здесь балансируются две противоположности: глобальный взгляд, охватывающий весь рынок, и локальный фокус, позволяющий заметить едва уловимые связи между отдельными инструментами или моментальными всплесками. Это сочетание особенно важно в финансовом контексте: рынок может неделями двигаться в рамках крупного тренда, но при этом отдельные активы внезапно меняют динамику из-за новостей или локальных событий.
index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, time_steps_in, dimension + emb_dimension, optimization, iBatch) || !cMixModule.Add(transp)) { DeleteObj(transp); return false; } for(uint i = (att_layers == layers ? 0 : att_layers - 1); i < layers; i++) { index++; glatt = new CNeuronGlobalLocalAttention(); if(!glatt || !glatt.Init(0, index, OpenCL, dimension + emb_dimension, time_steps_in, dimension_k, heads, m_units, sparse, optimization, iBatch) || !cMixModule.Add(glatt)) { DeleteObj(glatt); return false; } }
Финальным аккордом выступает блок прогнозирования, который принимает на себя задачу перевода всей накопленной информации в форму, пригодную для планирования на заданный горизонт. Несколько свёрточных слоёв в этом блоке преобразуют эмбеддинги в последовательность прогнозных значений, согласованную с длиной требуемого окна. Именно здесь абстрактные представления превращаются в конкретный результат — прогноз, отражающий как глобальный фон, так и локальные особенности.
index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, time_steps_out, dimension + emb_dimension, 1, optimization, iBatch) || !cMixModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(SoftPlus); index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out, dimension + emb_dimension, 1, optimization, iBatch) || !cMixModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(TANH); index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, dimension + emb_dimension, time_steps_out, optimization, iBatch) || !cMixModule.Add(transp)) { DeleteObj(transp); return false; } index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension, variables, time_steps_out, 1, optimization, iBatch) || !cMixModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None); index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) || !cMixModule.Add(norm)) { DeleteObj(norm); return false; }
В результате смешанный модуль работает как лаборатория синтеза: сначала фиксируются временные закономерности, затем они накладываются на пространственные связи, а итоговый сигнал проходит через блок прогнозирования и становится полноценным выходом, в котором сплетаются сразу несколько уровней анализа.
Завершающий этап внутреннего путешествия данных — пространственный модуль. Он отвечает за то, чтобы рынок перестал восприниматься как набор независимых рядов. Вначале здесь формируются пространственные эмбеддинги, которые фиксируют долгосрочные связи между инструментами, секторами и индексами.
//--- Spatial Module cSpatialModule.Clear(); cSpatialModule.SetOpenCL(OpenCL); index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, time_steps_in, variables, optimization, iBatch) || !cSpatialModule.Add(transp)) { DeleteObj(transp); return false; } index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_in, time_steps_in, dimension, variables, 1, optimization, iBatch) || !cSpatialModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None); index++; semb = new CNeuronSpatialEmbedding(); if(!semb || !semb.Init(0, index, OpenCL, variables, dimension, emb_dimension, optimization, iBatch) || !cSpatialModule.Add(semb)) { DeleteObj(semb); return false; }
На их основе блоки глобально-локального внимания поочередно выстраивают широкую картину корреляций и детальные связи между конкретными активами. Можно сказать, что этот модуль превращает финансовый рынок в живую сеть, где каждое звено связано с другими, и именно эта сеть становится объектом анализа.
index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, semb.Neurons(), iBatch, optimization) || !cSpatialModule.Add(norm)) { DeleteObj(norm); return false; } for(uint i = 0; i < layers; i++) { index++; glatt = new CNeuronGlobalLocalAttention(); if(!glatt || !glatt.Init(0, index, OpenCL, dimension + emb_dimension, variables, dimension_k, heads, m_units, sparse, optimization, iBatch) || !cSpatialModule.Add(glatt)) { DeleteObj(glatt); return false; } }
И аналогично двум выше представленным магистралям, результаты анализа направляются в блок прогнозирования, который аккуратно упаковывает всю информацию в форму, пригодную для планирования на заданный период.
index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, dimension + emb_dimension, dimension + emb_dimension, time_steps_out, variables, 1, optimization, iBatch) || !cSpatialModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(SoftPlus); index++; conv = new CNeuronConvOCL(); if(!conv || !conv.Init(0, index, OpenCL, time_steps_out, time_steps_out, time_steps_out, variables, 1, optimization, iBatch) || !cSpatialModule.Add(conv)) { DeleteObj(conv); return false; } conv.SetActivationFunction(None); index++; norm = new CNeuronBatchNormOCL(); if(!norm || !norm.Init(0, index, OpenCL, conv.Neurons(), iBatch, optimization) || !cMixModule.Add(norm)) { DeleteObj(norm); return false; } index++; transp = new CNeuronTransposeOCL(); if(!transp || !transp.Init(0, index, OpenCL, variables, time_steps_out, optimization, iBatch) || !cMixModule.Add(transp)) { DeleteObj(transp); return false; }
Все три маршрута (временной, смешанный и пространственный) сходятся в точке конкатенации. Здесь объединяются их результаты, и именно на этом этапе включается наш ключевой механизм — адаптивный Attention Pooling. В отличие от авторской версии с фиксированными весами, мы даём маршрутам возможность самим договариваться о том, чья роль важнее в данный момент. В результате, итоговый сигнал — это не застывшая конструкция, а гибкий инструмент, который дышит в такт рынку.
index++; if(!cConcatResults.Init(0, index, OpenCL, 3 * variables * time_steps_out, optimization, iBatch)) return false; cConcatResults.SetActivationFunction(None); //--- return true; }
Так метод Init превращает сухую последовательность вызовов в живую архитектуру, где каждое звено несёт смысловую нагрузку, а их взаимодействие рождает уникальный инструмент анализа. Здесь рождается организм под названием CNeuronExtralonger, и каждая его деталь работает на общий результат.
После инициализации объекта, мы переходим к построению алгоритма прямого прохода, который реализован в методе feedForward. Сигнатура метода сама по себе говорит о многом. В параметрах он получает указатель на объект исходных данных NeuronOCL и дополнительный буфер SecondInput, содержащий временные метки анализируемой последовательности.
bool CNeuronExtralonger::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) { CNeuronBaseOCL *prev = NeuronOCL; CNeuronBaseOCL *current = NULL;
Первой внутренней операцией является объявление двух локальных переменных. Одной из них присваивается указатель на объект исходных данных. Это стандартный приём для сквозной передачи: prev играет роль последнего доступного результата, который передаётся следующему слою. А current служит временной переменной, в которую на каждой итерации помещается указатель на обрабатываемый слой. Такой подход позволяет линейно строить цепочку слоёв, избегая излишних копирований и держа контроль над последовательностью вычислений.
Далее запускается цикл по контейнеру подготовки исходных данных cProjectionT. Для каждой проекции берём указатель на текущий объект и сразу проверяем его актуальность. После чего вызываем метод прямого прохода объекта. В случае возникновения ошибки на любом из этапов, метод немедленно завершает работу с результатом false. Это паттерн fail-fast: лучше остановиться при первой проблеме, чем продолжать на испорченных данных.
//--- Time projection for(int i = 0; i < cProjectionT.Total(); i++) { current = cProjectionT[i]; if(!current || !current.FeedForward(prev, SecondInput)) return false; prev = current; }
Важно отметить, что в этом блоке метод FeedForward внутренних объектов принимает два аргумента: предыдущий результат и временные метки SecondInput. Ведь для генерации временных эмбеддингов потребуется дополнительный контекст.
Если всё прошло успешно, переменная prev обновляется указателем на current, чтобы следующая итерация работала с выходом только что обработанного слоя.
Когда проекции завершены, управление переходит во временной модуль. Цикл по контейнеру cTimeModule выглядит почти так же, но здесь метод FeedForward внутренних объектов вызывается уже с одним аргументом. Это говорит о том, что временной модуль обрабатывает результирующую последовательность без дополнительного буфера.
//--- Time Module for(int i = 0; i < cTimeModule.Total(); i++) { current = cTimeModule[i]; if(!current || !current.FeedForward(prev)) return false; prev = current; }
Если какой-то слой временного модуля отсутствует, или его FeedForward сигнализирует об ошибке, мы опять же прерываем обработку и возвращаем false.
Затем фокус смещается на смешанный модуль. Но здесь следует вспомнить, что данный модуль так же работает с результатами предварительной обработки данных, поэтому перед началом цикла в переменную prev записываем указатель на последний слой контейнера cProjectionT. Временная и смешанная ветви идут параллельными путями, обрабатывая одни и те же исходные признаки по-разному.
//--- Mix Module prev = cProjectionT[-1]; for(int i = 0; i < cMixModule.Total(); i++) { current = cMixModule[i]; if(!current || !current.FeedForward(prev)) return false; prev = current; }
Далее для каждого элемента cMixModule выполняется та же проверка актуальности указателя и вызов метода прямого прохода, с последующим обновлением указателя в prev. Любая ошибка снова приводит к немедленному завершению работы и возврату false.
Пространственный модуль организован отдельно и получает в качестве исходных данных NeuronOCL. Это означает, что Spatial-ветвь анализирует исходный сигнал параллельно другим ветвям, не наследуя промежуточные преобразования. После последовательного запуска слоёв cSpatialModule, с привычной fail-fast логикой и обновлением prev.
//--- Spatial Module prev = NeuronOCL; for(int i = 0; i < cSpatialModule.Total(); i++) { current = cSpatialModule[i]; if(!current || !current.FeedForward(prev)) return false; prev = current; }
В результате, мы получаем три завершённые ветви: временную, смешанную и пространственную. Следующий этап — конкатенация. Собираем выходы трёх ветвей в единый буфер, формируя окно и задавая итоговую размерность. Если конкатенация не удалась — метод возвращает false.
//--- Concatenate if(!Concat(cTimeModule[-1].getOutput(), cMixModule[-1].getOutput(), cSpatialModule[-1].getOutput(), cConcatResults.getOutput(), iWindow, iWindow, iWindow, iUnits)) return false; //--- return CNeuronMHAttentionPooling::feedForward(cConcatResults.AsObject()); }
После успешной конкатенации остаётся передать агрегированный результат в модуль Attention Pooling.
Архитектурно видно, что система построена как набор параллельных путей, каждый из которых исследует исходные данные с собственной целью: проекции подготавливают временные представления, временной модуль анализирует последовательность, смешанный комбинирует проекции, Spatial фокусируется на пространственных признаках. Такая схема похожа на оркестр: у каждого инструмента своя партия, а дирижёр — Attention Pooling, который сводит всё воедино.
Метод распределения градиентов ошибки во многом повторяет структуру прямого прохода, но с той разницей, что здесь данные идут в обратном направлении, и нужно аккуратно суммировать потоки ошибок.
bool CNeuronExtralonger::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput, CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) { if(!NeuronOCL) return false; //--- if(!CNeuronMHAttentionPooling::calcInputGradients(cConcatResults.AsObject())) return false;
Сначала проверяем актуальность указателя на объект исходных данных. Если он пустой, то метод сразу возвращает false. Затем вызывается одноименный метод родительского класса. Этот шаг позволяет спустить градиент ошибки, полученный от последующего нейронного слоя, на уровень конкатенированного буфер. После этого выполняется распределение полученных значений обратно на три ветви — временную, смешанную и пространственную. Здесь важно, что ошибка, собранная в общем буфере, разделяется строго в соответствии с архитектурой модели. Каждая ветвь получила свой корректный градиент. Если какая-либо из операций завершается неудачей — процесс прерывается.
//--- DeConcatenate if(!cTimeModule[-1] || !cMixModule[-1] || !cSpatialModule[-1] || !DeConcat(cTimeModule[-1].getGradient(), cMixModule[-1].getGradient(), cSpatialModule[-1].getGradient(), cConcatResults.getOutput(), iWindow, iWindow, iWindow, iUnits)) return false;
Дальше градиенты начинают распространяться по модулям в обратном порядке. Сначала пространственная магистраль. Цикл идёт от конца к началу, каждый текущий элемент берёт ссылку на последующий слой и вызывает метод распределения градиента ошибки внутреннего объекта. Ошибка аккуратно проталкивается назад, слой за слоем.
CNeuronBaseOCL *next = NULL; CNeuronBaseOCL *current = NULL; //--- Spatial Module for(int i = cSpatialModule.Total() - 1; i >= 0; i--) { current = (i > 0 ? cSpatialModule[i - 1] : NeuronOCL); next = cSpatialModule[i]; if(!current || !current.CalcHiddenGradients(next)) return false; }
Та же логика повторяется для смешанного модуля, только здесь в качестве опорной точки используется последний элемент проекций по времени. Это отражает параллельную структуру модели: разные ветви возвращают ошибки по своим каналам, но всегда синхронно.
//--- Mix Module for(int i = cMixModule.Total() - 1; i >= 0; i--) { current = (i > 0 ? cMixModule[i - 1] : cProjectionT[-1]); next = cMixModule[i]; if(!current || !current.CalcHiddenGradients(next)) return false; }
Особый интерес представляет временной модуль. Здесь стоит обратить внимание, что он передает градиент ошибки на последний слой модуля подготовки данных. И именно в него мы только что передали значения от смешанного модуля. Поэтому перед осуществлением обратного прохода модуля временного анализа, мы сохраняем указатель на текущий буфер градиентов ошибки с сохраненными значениями в локальной переменной temp. А в объект передаем указатель на свободный буфер.
//--- Time Module CBufferFloat *temp = current.getGradient(); if(!current.SetGradient(current.getPrevOutput(), false)) return false; for(int i = cTimeModule.Total() - 1; i >= 0; i--) { current = (i > 0 ? cTimeModule[i - 1] : cProjectionT[-1]); next = cTimeModule[i]; if(!current || !current.CalcHiddenGradients(next)) return false; } if(!SumAndNormilize(temp, current.getGradient(), temp, 1, false, 0, 0, 0, 1) || !current.SetGradient(temp, false)) return false;
Далее в цикле, опять же от конца к началу, опускаются градиенты по модулю. И после завершения обратного прохода по временной ветви суммируем значения, полученные по двум информационным потокам и возвращаем указатели на буферы данных в исходное состояние.
Последний шаг — обработка временных проекций. Здесь градиент спускается до уровня объекта исходных данных. Однако хочу напомнить, что мы уже передали туда градиенты ошибки от пространственной магистрали. Поэтому повторяем трюк с подменой буфера данных и затем осуществляем операции распределения градиента ошибки.
//--- Time projection temp = NeuronOCL.getGradient(); if(!NeuronOCL.SetGradient(NeuronOCL.getPrevOutput(), false)) return false; for(int i = cProjectionT.Total() - 1; i >= 0; i--) { current = (i > 0 ? cProjectionT[i - 1] : NeuronOCL); next = cProjectionT[i]; if(!current || !current.CalcHiddenGradients(next, SecondInput, SecondGradient, SecondActivation)) return false; } if(!SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1) || !NeuronOCL.SetGradient(temp, false)) return false; //--- return true; }
Таким образом, метод аккуратно распределяет градиенты по трём параллельным ветвям — Spatial, Mix и Temporal. В каждой ветви обратный проход идёт слой за слоем в обратном порядке, а на стыках ветвей ошибки складываются. Это отражает ту же идею архитектуры, что и в прямом проходе: данные идут по параллельным каналам, но в итоге сводятся в общий результат.
Полный код класса со всеми методами приведён во вложении, что позволяет рассмотреть общую структуру и внутреннюю механику реализации.
Архитектура модели
После того как мы завершаем построение всех необходимых объектов для реализации фреймворка Extralonger, работа логично переходит к описанию архитектуры самой модели. Здесь важно подчеркнуть: мы не ограничиваемся задачей прогноза временных рядов, как это сделано в авторской реализации. Наша цель шире и практичнее — мы создаём полноценного торгового робота, который способен самостоятельно принимать решения и выполнять торговые операции на рынке.
В такой постановке задача прогнозирования ценовых рядов перестаёт быть конечной целью и выступает лишь как часть системы, а именно в качестве Энкодера состояния окружающей среды. Своего рода сенсор, который преобразует данные рынка в форму, понятную алгоритму. Сохраняя концепцию Актёр–Критик, мы формируем три функциональные модели: Энкодер, Актёр и Критик.
Для описания их архитектуры используется метод CreateDescriptions. В нём аккуратно создаются и инициализируются массивы описания слоёв для каждой из трёх частей модели.
bool CreateDescriptions(CArrayObj *&encoder, CArrayObj *&actor, CArrayObj *&critic ) { //--- CLayerDescription *descr; //--- if(!encoder) { encoder = new CArrayObj(); if(!encoder) return false; } if(!actor) { actor = new CArrayObj(); if(!actor) return false; } if(!critic) { critic = new CArrayObj(); if(!critic) return false; }
Код начинается с проверки полученных указателей на массивы. Если какой-то из них ещё не существует, то создаётся новый. Такой шаг обеспечивает надёжность — мы гарантируем, что дальнейшее наполнение описаний не вызовет сбоев из-за отсутствия структуры.
Далее массивы очищаются, и начинается поэтапное построение слоёв Энкодера. Первым добавляется слой исходных данных.
//--- Encoder encoder.Clear(); //--- Input layer if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronBaseOCL; uint prev_count = descr.count = (HistoryBars * BarDescr); descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { delete descr; return false; }
Следующим шагом идёт слой нормализации с добавлением шума, который помогает улучшить устойчивость обучения за счёт стохастических искажений.
//--- layer 1 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronBatchNormWithNoise; descr.count = prev_count; descr.batch = BatchSize; descr.activation = None; descr.optimization = ADAM; if(!encoder.Add(descr)) { delete descr; return false; }
Затем создаётся слой добавление признаков разности первого порядка. Его задача — сформировать временные срезы, связывая значения баров между собой. На выходе этот слой формирует удвоенное количество признаков, поскольку соединяются сами значения и их разности.
//--- layer 2 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronConcatDiff; prev_count = descr.count = HistoryBars; descr.layers = BarDescr; descr.step = 1; descr.batch = BatchSize; descr.optimization = ADAM; descr.activation = None; if(!encoder.Add(descr)) { delete descr; return false; } uint prev_out = descr.layers*2 ;
Особый интерес представляет следующий слой — defNeuronExtralonger. Он задаёт архитектуру созданного нами объекта верхнего уровня CNeuronExtralonger. По существу это весь комплекс фреймворка Extralonger. В нем мы указываем все необходимые параметры: временные окон, глубину прогноза, короткий и длинный периоды, а также размерность скрытых признаков. Этот блок превращает Энкодер в интеллектуальный фильтр, который связывает историю и будущее в едином представлении.
//--- layer 3 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronExtralonger; { uint temp[] = {HistoryBars, // Time steps history NForecast, // Time steps forecast ShortPeriod, // Period 2 LongPeriod, // Period 2 BarDescr/2 // M units }; if(ArrayCopy(descr.units, temp) < (int)temp.Size()) return false; } prev_count = descr.units[1]; descr.window = prev_out; // Variables descr.window_out = EmbeddingSize; // Inside Dimension { uint temp[] = {EmbeddingSize, // Embedding Dimension PeriodSeconds(PERIOD_H1), // Frame 1 PeriodSeconds(PERIOD_D1), // Frame 2 2*EmbeddingSize/NHeads }; if(ArrayCopy(descr.windows, temp) < (int)temp.Size()) return false; } descr.layers=2; descr.step=NHeads; descr.probability=0.3f; descr.optimization=ADAM; descr.batch=BatchSize; descr.activation = None; if(!encoder. Add(descry)) { delete descry; return false; } uint window=descr.window; uint count=prev_count;
На выходе из модуля Extralonger мы получаем уже сформированный блок прогнозных значений, готовый к дальнейшему использованию. Но важно помнить, что ещё до подачи данных в этот модуль, мы обогащали их признаками первой разности. Такая техника позволила сделать ряды более информативными, но одновременно увеличила размерность данных.
Казалось бы, самый простой способ решить эту проблему — просто отбросить лишние признаки. Однако такой путь привёл бы к потере информации, ради которой и вводилась первая разность. Вместо упрощённого решения мы используем свёрточный слой, который не просто сокращает размерность, но и отбирает наиболее значимые локальные закономерности. Благодаря этому, выход из модуля проходит тонкую фильтрацию: данные становятся компактными, но сохраняют весь спектр существенных характеристик, необходимых для принятия торговых решений.
//--- layer 4 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronConvOCL; descr.count = prev_count; descr.window = prev_out; descr.step = prev_out; prev_out = descr.window_out = BarDescr; descr.activation = TANH; descr.optimization = ADAM; if(!encoder.Add(descr)) { delete descr; return false; }
Заключающим штрихом Энкодера состояния окружающей среды становится слой обратного денормирования (defNeuronRevInDenormOCL). Этот слой возвращает значения в масштаб, привычный для анализируемых данных, устраняя смещение, которое возникло в процессе кодирования и нормализации. Таким образом, результат становится сопоставимым с реальными рыночными величинами.
//--- layer 5 if(!(descr = new CLayerDescription())) return false; descr.type = defNeuronRevInDenormOCL; descr.count = prev_count * prev_out; descr.layers = 1; if(!encoder.Add(descr)) { delete descr; return false; }
Архитектура моделей Актёра и Критика полностью заимствована из наших предыдущих работ и не подвергалась каким-либо изменениям. Именно поэтому мы не будем останавливаться на её подробном разборе в данной статье. Для полноты картины читателю доступно полное описание архитектуры всех обучаемых моделей во вложении, где можно проследить каждую деталь реализации и убедиться в целостности построенного решения.
Тестирование
Обучение модели — это полноценная торговая подготовка. Прежде чем выпускать её на реальный рынок, мы тщательно обкатывали стратегию на исторических данных. Первый этап — офлайн-обучение — был проведён на выборке валютной пары EURUSD с таймфреймом H1 за период с Января 2024 по Июнь 2025 года. Этот отрезок оказался насыщенным и разнообразным: спокойные фазы боковиков чередовались с резкими трендовыми движениями, а на фоне новостных публикаций вспыхивала высокая волатильность. Такое сочетание условий позволило модели научиться различать широкий спектр рыночных сценариев и вырабатывать устойчивые торговые решения без потери ориентира в сложных ситуациях.
Когда первый этап подготовки был завершен, мы перешли ко второму — тонкой онлайн-настройке в тестере стратегий MetaTrader 5. Здесь данные поступали в режиме реального времени, свеча за свечой, и модель осваивала динамику работы на потоке. Училась держать стабильность на фоне шума, справляться с низкой ликвидностью и не сбиваться при внезапных ценовых всплесках. Этот этап стал своего рода доводкой стратегии. Он не менял каркас, построенный на истории, но помогал адаптировать его к реальным условиям и снижал риск переобучения.
Финальная проверка была проведена на данных за Июль 2025 года — они не использовались ранее и были абсолютно новыми для модели. Все параметры, полученные на предыдущих этапах, загрузились без изменений. Такой чистый тест позволил объективно оценить способность модели к обобщению, без каких-либо подгонок или корректировок.
Результаты тестирования представлены ниже.


Результаты тестирования позволяют оценить модель в реальных рыночных условиях без каких-либо подгонок. Начнём с ключевых цифр. При стартовом депозите в 100 долларов итоговый чистый результат составил 3.02 доллара, то есть баланс вырос на три процента за месяц тестирования. Общая прибыль достигла 27.26 доллара, убытки — 24.24 доллара. Profit Factor равен 1.12. Это говорит о том, что прибыльные сделки слегка превалируют над убыточными.
Однако видно и слабое место — фактор восстановления равен всего 0.22. А значит, после серий убытков модель восстанавливает капитал медленно. Максимальная просадка по балансу составила 12.75% — достаточно заметная величина, но всё же не критическая для полностью автоматизированной торговли. Среднее математическое ожидание сделки оказалось скромным — 0.06 доллара, зато показатель Sharpe Ratio (3.04) указывает на хорошее соотношение доходности к риску при учёте волатильности.
Интересна и статистика сделок. Всего их было 50, из них 28 коротких (с результативностью 53.57%) и 22 длинных (выигрыш лишь в 36.36% случаев). В целом, доля прибыльных сделок составила 46%. Наибольшая прибыль в отдельной сделке — 6.70 доллара, максимальный убыток — 4.73 доллара. Серии тоже показали характер: подряд удавалось выиграть до пяти сделок, а проиграть — максимум три.
График кривой капитала подтверждает картину цифр. Первые дни июля сопровождались колебаниями и серией просадок, затем баланс начал постепенно расти. В середине месяца появилось несколько удачных серий сделок, позволивших вывести капитал в положительную область. Под конец июля наблюдалась стабилизация и удержание результата, без новых глубоких провалов.
Таким образом, тест показал, что модель способна приносить умеренную прибыль, сохраняя баланс между риском и доходностью. Однако период тестирования довольно мал, и для реальной эксплуатации потребуется дальнейшая оптимизация. Прежде всего снижение просадки и повышение доли прибыльных сделок. Но главное, что система выдержала чистое испытание на новых данных и продемонстрировала способность к обобщению, что является ключевым критерием качества алгоритмической модели.
И не будем забывать, что обучение трансформеров довольно сложный процесс и требует обширных обучающих выборок.
Заключение
В ходе работы мы убедились, что предложенные подходы фреймворка Extralonger способны органично интегрировать пространственные и временные факторы, формируя устойчивую основу для анализа рыночной динамики.
Тестирование подтвердило практическую эффективность алгоритмов, а гибкость архитектуры позволяет адаптировать систему под различные горизонты прогнозирования и классы финансовых инструментов. Реализация средствами MQL5 показала, что классические подходы в сочетании с современными нейросетевыми методами дают результат, сопоставимый с более сложными системами, но при этом остаются предельно прозрачными и управляемыми.
Ссылки
- Extralonger: Toward a Unified Perspective of Spatial-Temporal Factors for Extra-Long-Term Traffic Forecasting
- Другие статьи серии
Программы, используемые в статье
| # | Имя | Тип | Описание |
|---|---|---|---|
| 1 | Study.mq5 | Советник | Советник офлайн обучения моделей |
| 2 | StudyOnline.mq5 | Советник | Советник онлайн обучения моделей |
| 3 | Test.mq5 | Советник | Советник для тестирования модели |
| 4 | Trajectory.mqh | Библиотека класса | Структура описания состояния системы и архитектуры моделей |
| 5 | NeuroNet.mqh | Библиотека класса | Библиотека классов для создания нейронной сети |
| 6 | NeuroNet.cl | Библиотека | Библиотека кода OpenCL-программы |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От новичка до эксперта: Советник Reporting EA - Настройка рабочего процесса
От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (VI) — Стратегия пост-новостной торговли
Алгоритм оптимизации динго — Dingo Optimization Algorithm (DOA)
Арбитражная алготорговля на теории графов
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования