Русский Português
preview
Neural Networks in Trading: A Spiking-Semantic Approach to Spatiotemporal Identification (Key Components)

Neural Networks in Trading: A Spiking-Semantic Approach to Spatiotemporal Identification (Key Components)

MetaTrader 5 — Trading systems |
137 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Introduction

In the world of algorithmic trading and financial analysis, the idea of seeing not just numbers but events has always captured the attention of researchers. Streams of quotes and orders, bursts of activity, crashes, and lulls — all of this resembles a living organism pulsing with the rhythm of global liquidity. However, most traditional models view this merely as statistics — a set of sequential values. They analyze the price, but they do not feel it. The ability to recognize meaning in changes has become the cornerstone of the modern approach to processing market data.

In the previous article in this series, we took an in-depth look at the S3CE-Net framework, which combines the principles of event-based perception and deep learning. This framework serves as a kind of bridge between two worlds: the biological and the computational. On the one hand, it inherits the speed and efficiency of spiking systems, in which information is transmitted in the form of infrequent but meaningful spikes. On the other hand, it preserves the structural depth of classical neural networks, which are capable of complex generalization and analysis.

The architecture is built on the SEW-ResNet framework, which ensures robust and efficient feature extraction without loss of information at depth. More sophisticated mechanisms are layered on top of it: the spatiotemporal attention mechanism SSAM and the STFS module. The first links events in a sequence, establishing causally grounded dependencies, while the second is responsible for the adaptive selection of features across time and space. All of this transforms S3CE-Net into a living perceptual system, where every spike carries meaning.

The essence of the architecture can be summed up briefly: it learns to see structure where others see noise. Due to its event-driven nature, the model operates not at fixed intervals but at the level of individual spikes, which allows it to better reflect the actual rhythm of the market. SSAM focuses attention on those parts of the analyzed data set where changes actually occur, while STFS adds flexibility and robustness, allowing the model to learn without excessive dependence on specific scenarios. This makes S3CE-Net a comprehensive system for semantic analysis of event streams.

The practical value of S3CE-Net in trading lies precisely in its event-driven nature. A model based on these principles does not react to every fluctuation, but only to micro-events that actually shift the market balance. A spike in volume, the appearance of a large order, a change in liquidity structure — all of these are interpreted as meaningful signals rather than random noise. The decisions made by the model are based on context and accumulated experience — on how the market had been breathing up to that point.

This approach becomes particularly valuable in situations where traditional methods are no longer reliable. Where indicators are overwhelmed by conflicting data and classical models begin to overfit, S3CE-Net maintains clarity of perception. Its attention is focused on the essentials. Thanks to this, it is able to analyze the past and capture the current state of the market with minimal delay — almost in step with the market’s own movement.

But we should not forget that behind every elegant idea lies rigorous engineering. To ensure that the model remains fast, stable, and adaptive, it requires a well-designed organization of computation, efficient use of memory, and strict adherence to causality in event processing. Therefore, practical implementation becomes not merely a test of the theory, but a crucial stage in the framework's evolution. It helps us understand how viable the idea is outside of a laboratory setting and whether it can be turned into a practical analytical tool for traders.

Thus, theory gradually begins to breathe. And perhaps the very boundary that distinguishes artificial intelligence from a simple algorithm lies precisely in the model’s ability to capture the rhythm and momentum of the financial flow. We are continuing our work to transform S3CE-Net into a tool capable of sensing the market.

The author’s visualization of the S3CE-Net framework is shown below.

Author’s Visualization of the S3CE-Net Framework



SSAM Module

In the practical section of our previous work, we stopped at a crucial stage: implementing the forward- and backward-pass algorithms for the SSAM module within the OpenCL program. It is there, in the OpenCL kernels, that the foundation of the attention mechanism’s behavior is formed — a mechanism capable of working with a stream of spiking events under strict temporal causality. This was a necessary step, because the computational power is concentrated precisely at the OpenCL level: computing attention matrices, applying masks, and propagating gradients.

Today we are moving up a level — to the main program structure, where the logic governing the interaction between all the framework's modules is defined. Here, we create the CNeuronSSAM class, which serves as a full-fledged representation of the spiking attention layer within the architecture. While on the OpenCL side we were dealing with pure mathematics, we are now talking about an architectural composition that combines low-level computations with high-level neural logic.

class CNeuronSSAM    :  public CNeuronSpikeActivation
  {
protected:
   uint                 iUnits;
   uint                 iHeads;
   uint                 iDimension;
   bool                 bMaskFuture;
   CNeuronSpikeConv     cQKV;
   CParams              cDiaganalBias;
   int                  ciScore;
   CNeuronBaseOCL       cMHAttentionOut;
   CNeuronSpikeConv     cW0;
   CNeuronBaseOCL       cResidual;
   CNeuronBatchNormOCL  cNorm;
   //---
   virtual bool      SpikeMHAttention(void);
   virtual bool      SpikeMHAttentionGrad(void);
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSSAM(void) {};
                    ~CNeuronSSAM(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint units, uint window, uint heads, uint dimension_k,
                          bool mask_future, ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override const  {  return defNeuronSSAM;   }
   //--- methods for working with files
   virtual bool      Save(const int file_handle) override;
   virtual bool      Load(const int file_handle) override;
   virtual void      SetOpenCL(COpenCLMy *obj)   override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      TrainMode(bool flag) override;
   virtual bool      Clear(void) override;
  };

The class inherits the basic functionality from CNeuronSpikeActivation, preserving the general mechanism of spiking neuron activation. However, it also introduces its own hierarchy of components, each responsible for a specific aspect of spatiotemporal attention. The key parameters are declared in the protected section:

  • iUnits — the dimensionality of the sequence being analyzed,
  • iHeads — the number of attention heads,
  • iDimension — the vector length of a single element of the internal entities,
  • bMaskFuture — a masking flag that indicates whether the model can look into the future when constructing an attention map.

At the heart of the class is a set of internal objects through which the entire computation flow passes. The cQKV object is responsible for generating a triplet of internal-entity tensors Q, K, and V; the base neural layer cMHAttentionOut accumulates the result of multi-head attention; and cResidual implements standard residual connection and normalization techniques.

However, cDiaganalBias holds a special place — a component that adjusts attention in the spatial projection. It implements the idea of Intra-Frame Relative Position Bias, proposed by the authors of the S3CE-Net framework — a mechanism for relative positional bias within a frame that enables the model to better understand local relationships between tokens.

From a practical standpoint, this bias serves several functions at once. First, it stabilizes the computations, preventing diffuse attention at large spatial dimensions. Second, it gives the model a contextual understanding of local structures — the model learns to identify patterns characteristic of nearby elements without losing the global perspective. Finally, in a market data context where tokens can represent price levels, volume clusters, or segments of the order flow, cDiaganalBias helps the algorithm focus specifically on concentrations of the greatest activity. After all, market events that are close in price or time are rarely independent of one another. And it is precisely this dependency that becomes the fuel for learnable attention.

The main advantage of the CNeuronSSAM class design is that all of its internal components are declared statically. This decision, which at first glance appears to be purely technical, actually offers significant architectural advantages.

First, the static layout eliminates the need for dynamic memory allocation and deallocation. The class constructor and destructor can remain empty. No risk of memory leaks, fragmentation, or accidental access to uninitialized pointers. Each internal object exists throughout the entire life cycle of the class instance, creating an extremely stable structure that is fully controlled at compile time.

Second, the static organization significantly increases the determinism of computations — an extremely important property when working with OpenCL and spiking models. Here, every clock cycle and every operation matters. The absence of dynamic memory allocation ensures that initialization and execution times always remain predictable, and that the model's architecture does not depend on the state of memory at startup. This is particularly valuable when developing trading algorithms, where the stability and accuracy of computations directly affect the reliability of the decisions made.

In addition, static declaration fits perfectly with the concept of low-level optimization inherent in OpenCL. Unlike dynamically created objects, static instances can be associated in advance with specific GPU buffers, minimizing overhead with each device access. This not only speeds up the layer but also prevents the typical delays that occur when computation contexts are frequently created and destroyed.

Thus, the static structure of the internal design of CNeuronSSAM is not merely a matter of convenience. This is a well-considered architectural solution that ensures stability, predictability, and maximum efficiency in the computational process.

On this solid, statically calibrated foundation, the entire subsequent initialization process unfolds, implemented in the Init method, which turns the class into a full-fledged component of the S3CE-Net framework, ready for real-time operation.

bool CNeuronSSAM::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                       uint units, uint window, uint heads, uint dimension_k,
                       bool mask_future, ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeActivation::Init(numOutputs, myIndex, open_cl, units * window, optimization_type, batch))
      return false;

The Init method is responsible for creating a ready-to-use instance of the neural block and linking all of its internal elements together. The method starts by calling the method of the same name in the parent class, which sets the basic parameters of the layer.

Next, the internal architecture of the layer is configured. The iHeads and iDimension parameters define the configuration of multi-head attention.

   iHeads = (uint)MathMax(heads, 1);
   iDimension = (uint)MathMax(dimension_k, 1);
   iUnits = units;
   bMaskFuture = mask_future;

The values are passed through the MathMax function to rule out the possibility of zero dimensions. This is especially important when generating buffers on the OpenCL side.

The next step is to initialize the internal components — the very building blocks that make up the layer's logic. First, a cQKV object is created, which is responsible for generating the three basic attention tensors — Query, Key, and Value. Its size is defined as the triple product of the number of heads and the dimension of the vectors, which ensures the correct distribution of parameters across all attention subspaces.

   uint index = 0;
   if(!cQKV.Init(0, index, OpenCL, window, window, 3 * iHeads * iDimension, iUnits, 1, optimization, iBatch))
      return false;

Next, cDiaganalBias is initialized, creating a structure for storing and computing the positional bias matrix — the very same Intra-Frame Relative Position Bias that adds an intelligent correction for the relative positions of elements within the frame. This block can be thought of as fine-tuning the attention: it allows the model to sense the proximity of tokens in space and adjust its focus based on their relative positions.

   index++;
   if(!cDiaganalBias.Init(0, index, OpenCL, iUnits, optimization, iBatch))
      return false;
   cDiaganalBias.SetActivationFunction(None);
   CBufferFloat* temp=cDiaganalBias.getWeightsParams();
   if(!temp ||
      !temp.Fill(0))
     return false;

At the initial stage, the cDiaganalBias object is initialized with zero values, which makes perfect sense for the model’s first startup phase. At this point, the neural network does not yet have any knowledge of the relative positions of the elements within the frame — it is blind to the internal geometry of the input space. Zero initialization provides a clean starting point where all relative biases are equal, and the attention mechanism operates solely through the basic tensors Q, K, and V.

The creation of the ciScore buffer deserves special attention, as it reserves a memory region for storing intermediate similarity values between queries and keys exclusively on the OpenCL context side. This buffer is used as temporary storage when calculating attention weights. Checking the result ensures that the memory has actually been allocated; otherwise, initialization is aborted — this check makes the process reliable and controlled.

   ciScore = OpenCL.AddBuffer(sizeof(float) * iUnits * iUnits * iHeads, CL_MEM_READ_WRITE);
   if(ciScore == INVALID_HANDLE)
      return false;

Next, the remaining modules in the layer are connected in sequence. The cMHAttentionOut object is responsible for aggregating the results of multi-head attention; it is initialized with a total dimension of iUnits * iDimension * iHeads and disables the activation function to preserve the raw values after the attention operations.

   index++;
   if(!cMHAttentionOut.Init(0, index, OpenCL, iUnits * iDimension * iHeads, optimization, iBatch))
      return false;
   cMHAttentionOut.SetActivationFunction(None);

This is followed by cW0 — a linear transformation module applied to the attention output. Its parameters are trained so that it can flexibly adapt the dimensionality of the result for subsequent computations.

   index++;
   if(!cW0.Init(0, index, OpenCL, iHeads * iDimension, iHeads * iDimension, window, iUnits, 1, optimization, iBatch))
      return false;

The cResidual component provides a residual connection that returns part of the original signal to the layer’s output, helping the model avoid vanishing gradients during deep training. For cResidual, the activation function is explicitly disabled, and a shared gradient with cW0 is additionally set — this allows training to be synchronized across layers and maintains stability during backpropagation.

   index++;
   if(!cResidual.Init(0, index, OpenCL, Neurons(), optimization, iBatch))
      return false;
   cResidual.SetActivationFunction(None);
   if(!cResidual.SetGradient(cW0.getGradient(), true))
      return false;

The final step initializes cNorm, which stabilizes the distribution of values at the layer output, eliminating any potential imbalances caused by dynamic changes in attention, and prepares the data for spiking activation using the parent class.

   index++;
   if(!cNorm.Init(0, index, OpenCL, Neurons(), iBatch, optimization))
      return false;
//---
   return true;
  }

Each of these components is assigned its own index, a shared OpenCL object, and optimization parameters, forming a clearly structured hierarchy managed by the main class. Once all iterations of the algorithm have been successfully completed, the method terminates, and the CNeuronSSAM instance is ready for use — all buffers, connections, and computation chains have been configured, and the layer is fully integrated into the model structure.

The feedforward algorithm is implemented in the feedForward method. Its operation illustrates how information about spiking events passes through all levels of the attention module, including positional biases and residual connections.

bool CNeuronSSAM::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cQKV.FeedForward(NeuronOCL))
      return false;

In the first step, the tensors Q, K, and V are constructed, which provide the basis for computing multi-head attention. This is a crucial step: without properly structured queries, keys, and values, further aggregation of information is not possible.

Next, optimized bias parameters are generated. In other words, working values are created that will be used to adjust the attention weights.

   if(bTrain)
      if(!cDiaganalBias.FeedForward())
         return false;

It is important to note that this operation is performed only in training mode (bTrain == true). In production mode, once the model has been trained, the bias parameters remain static and are already stored in the object's results buffer. This approach minimizes overhead when working in real time.

The next step calls SpikeMHAttention, which is essentially a wrapper around the kernel of the same name in the OpenCL program. Its task is to prepare the kernel and enqueue it for execution. This is where all the necessary buffers are created, parameters are set, and references to Q, K, V, as well as to the cDiaganalBias positional biases, are passed.

   if(!SpikeMHAttention())
      return false;

All computational work, including multi-head attention, weight normalization, and incorporation of Intra-Frame Relative Position Bias, takes place exclusively on the GPU, leveraging parallel execution capabilities. This approach allows large tensors and complex attention operations to be processed with high performance, leaving the CPU free to manage the data flow and monitor the model's state.

This is followed by a linear transformation that adapts the output of the multi-head attention to the size of the input data and prepares the values for the residual connection.

   if(!cW0.FeedForward(cMHAttentionOut.AsObject()))
      return false;

The signals from the two information streams are then summed.

   if(!SumAndNormilize(NeuronOCL.getOutput(), cW0.getOutput(), cResidual.getOutput(),
                       Neurons() / iUnits, false, 0, 0, 0, 1))
      return false;

Next, the obtained values must be prepared for spiking activation. We maintain an Element-Wise approach: each cell of the output tensor is processed independently, which allows precise control over spiking dynamics.

To keep the values within a comparable range and prevent signal smearing, the cNorm batch normalization layer is used. It stabilizes the data distribution after the values of two information streams are summed, keeping them within a range suitable for subsequent activation.

   if(!cNorm.FeedForward(cResidual.AsObject()))
      return false;

After normalization, the controlled signal is passed to the method of the same name in the parent class, where adaptive spiking activation operations are performed.

   return CNeuronSpikeActivation::feedForward(cNorm.AsObject());
  }

This approach allows the SSAM module to integrate correctly into the overall network, passing spiking events further along the chain while maintaining high accuracy and stable operation of the attention mechanism.

After the forward pass is performed and the predicted values are obtained, the backpropagation stage begins, during which the prediction must be compared with the ground-truth data and the gradients distributed among all layer components. This task is handled by the calcInputGradients method.

bool CNeuronSSAM::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!NeuronOCL)
      return false;

The method begins by checking whether the received pointer to the NeuronOCL source data object is valid. It then calls the method of the same name in the parent class. At this stage, the spiking activation gradients are propagated down to the batch normalization layer.

   if(!CNeuronSpikeActivation::calcInputGradients(cNorm.AsObject()))
      return false;

The error is then passed to the residual-connection management object. And thanks to swapping the pointers to the error-gradient data buffers, the resulting values are automatically passed to the multi-head attention value aggregation object.

   if(!cResidual.CalcHiddenGradients(cNorm.AsObject()))
      return false;

The next step is to call SpikeMHAttentionGrad, where backpropagation through multi-head spiking attention is performed.

   if(!SpikeMHAttentionGrad())
      return false;

This takes into account all attention weights, QKV tensors, and cDiaganalBias positional biases. This stage is critically important: it is what allows the attention parameters to be adjusted based on the local and global structure of the signal being analyzed.

After that, the gradients are passed back to the input data. First, we distribute the error from the Q, K, and V components.

   if(!NeuronOCL.CalcHiddenGradients(cQKV.AsObject()))
      return false;

Next, the values need to be added along the residual path. There are two possible scenarios here, depending on whether the source data object has an activation function. If there is no activation function, the residual-path gradients are passed directly and summed with the previously accumulated values.

   if(NeuronOCL.Activation() == None)
     {
      if(!SumAndNormilize(NeuronOCL.getGradient(), cResidual.getGradient(),
                          NeuronOCL.getGradient(), Neurons() / iUnits, false, 0, 0, 0, 1))
         return false;
     }
   else
     {
      if(!DeActivation(NeuronOCL.getOutput(), cResidual.getPrevOutput(),
                       cResidual.getGradient(), NeuronOCL.Activation()))
         return false;
      if(!SumAndNormilize(NeuronOCL.getGradient(), cResidual.getPrevOutput(),
                          NeuronOCL.getGradient(), Neurons() / iUnits, false, 0, 0, 0, 1))
         return false;
     }
//---
   return true;
  }

If an activation function is present, the error gradients along the residual path are first adjusted by the derivative of the activation function being used. Only then are the gradients summed.

Ultimately, the calcInputGradients method distributes the error across all components of the process: from spiking activation through normalization, residual connections, linear transformations, and multi-head attention, all the way to the sequence being analyzed and positional bias. This mechanism ensures that all layer parameters are updated correctly during training and allows the model to gradually improve the accuracy of its predictions while maintaining the stability and consistency of its computations.

The actual optimization of the parameters is performed in the updateInputWeights method. There is no complex redistribution logic or manual calculation of new values here: all trainable parameters are already contained within the class's internal components. The optimization process involves sequentially calling the methods of the same name in these components, each of which independently updates its weights and buffers based on the gradients it receives.

bool CNeuronSSAM::updateInputWeights(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cQKV.UpdateInputWeights(NeuronOCL))
      return false;
   if(!cDiaganalBias.UpdateInputWeights())
      return false;
   if(!cW0.UpdateInputWeights(cMHAttentionOut.AsObject()))
      return false;
   if(!cNorm.UpdateInputWeights(cResidual.AsObject()))
      return false;
//---
   return CNeuronSpikeActivation::updateInputWeights(cNorm.AsObject());
  }

This architecture ensures modularity and simplifies code maintenance. Each block is responsible only for its own parameters, while the class controls the order of updates and ensures data consistency.

As a result, the updateInputWeights method acts as a coordinating node that sequentially triggers the optimization of all internal structures, ensuring a consistent and stable model update. This approach fully adheres to the principle of encapsulation: the details of training are hidden within the components, and the top level controls only their synchronization and order of execution.

The complete source code for the CNeuronSSAM class, along with all of its methods, is available in the attachment to the article. It allows readers not only to understand the logic behind how the layer works on a conceptual level, but also, if they wish, to use the ready-made code for experimentation or adapt it to their own needs.


Integration with the Spiking Version of ResNeXt

The next step in our work will be to integrate the SSAM module into the previously implemented SEW-ResNeXt algorithm. The authors' description of the S3CE-Net framework emphasizes that multiple attention modules can be integrated between SEW-ResNet blocks at different levels. This approach naturally fits into the structure of sequential models without the need to create additional modules or frameworks.

However, it is worth noting here that our implementation of the SEW-ResNeXt block uses two parallel Bottleneck blocks that analyze the input data in different representations: temporal and spatial. Each of these streams carries unique information. Temporal analysis makes it possible to capture signal dynamics and identify patterns in the sequence of events, while spatial analysis captures local relationships between data elements in the current slice.

Given this architecture, it would be logical to integrate the SSAM module into each information branch, providing separate attention processing in the spatial and temporal representations. This approach will allow us to harness the power of spiking multi-head attention in various contexts without disrupting the model’s overall structure, and may improve its accuracy by independently adapting attention weights for each type of information.

Consequently, integrating SSAM into both branches of the SEW‑ResNeXt block becomes a natural extension of the S3CE‑Net architectural concept, providing flexibility, robustness, and the ability to accurately capture the spatiotemporal dependencies in the analyzed data.

It is within the framework of this concept that the new CNeuronSSAMResNeXtBlock module is created, inheriting its basic functionality from CNeuronSpikeResNeXtBlock.

class CNeuronSSAMResNeXtBlock   :  public CNeuronSpikeResNeXtBlock
  {
protected:
   CNeuronSSAM        caSSAM[2];
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronSSAMResNeXtBlock(void) {};
                    ~CNeuronSSAMResNeXtBlock(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint chanels_in, uint chanels_out, uint units_in,
                          uint units_out, uint group_size, uint groups,
                          uint heads, uint dimension_k,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void)   override const   {  return defNeuronSSAMResNeXtBlock;   }
   //--- methods for working with files
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      TrainMode(bool flag) override;
   virtual bool      Clear(void) override;
  };

Only the caSSAM array is declared in the protected section of the class; it represents two instances of the SSAM module, one for each branch of input data analysis. All the other components we need are inherited from the parent class.

The class constructor and destructor remain empty because all internal components are static. They are initialized and configured in the Init method, which fully deploys the block, creates all internal objects, and configures them.

bool CNeuronSSAMResNeXtBlock::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                   uint chanels_in, uint chanels_out, uint units_in,
                                   uint units_out, uint group_size, uint groups,
                                   uint heads, uint dimension_k,
                                   ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronSpikeResNeXtBlock::Init(numOutputs, myIndex, open_cl, chanels_in, chanels_out,
                                      units_in, units_out, group_size, groups, optimization_type, batch))
      return false;

The method algorithm begins by initializing the SEW‑ResNeXt base block, setting the module's key parameters: the number of input and output channels, the number of sequence elements, the group size, and the optimization strategy. This ensures that the original Bottleneck block is configured correctly before attention is added.

Next, two SSAM modules are initialized, one for each information stream of the block. The first instance operates on the outputs of the Bottleneck block in the time dimension, applying future-step masking (mask_future = true).

   if(!caSSAM[0].Init(0, 0, OpenCL, units_out, chanels_out, heads, dimension_k, true, optimization, iBatch))
      return false;

The second instance processes the results of the spatial Bottleneck block. Here, we no longer use masking (mask_future = false).

   if(!caSSAM[1].Init(0, 0, OpenCL, chanels_out, units_out, heads, dimension_k, false, optimization, iBatch))
      return false;

Next, it is worth recalling that, to reduce data-copy operations between buffers during initialization of the parent class, we synchronized the pointers to the gradient buffers of the first Bottleneck block and the residual-connection management object. We, however, need to insert the corresponding SSAM module between them. Taking advantage of the equality of the tensors at the module input and output, we simply swap the pointers to the error-gradient buffers between the modules.

   CBufferFloat* temp = caBottleneck[0].getGradient();
   if(!caBottleneck[0].SetGradient(caSSAM[0].getGradient(), false))
      return false;
   if(!caSSAM[0].SetGradient(temp, false))
      return false;
//---
   return true;
  }

This exchange ensures correct error propagation between blocks and allows both components to adapt their parameters.

As a result, the Init method creates a fully operational module in which SEW‑ResNeXt and SSAM are integrated into a single computational pipeline, while gradients and buffers are organized to maintain training stability and maximum efficiency.

The forward-pass algorithm is implemented entirely from scratch, since integrating the two SSAM modules into the middle of the block makes it impossible to use the parent class method of the same name. Data flow through the entire chain sequentially, with each branch processed according to its specific characteristics and position within the architecture.

bool CNeuronSSAMResNeXtBlock::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!caTranspose[0].FeedForward(NeuronOCL))
      return false;

First, the input signal passes through the transpose layer caTranspose[0], preparing the data for parallel processing and aligning it along the spatial dimension. Next, sequential Bottleneck SEW-ResNeXt blocks process the input stream: the first analyzes the raw data directly as a time series.

   if(!caBottleneck[0].FeedForward(NeuronOCL))
      return false;

The second receives the transposed signal, enabling separate perception of the information in different representations.

   if(!caBottleneck[1].FeedForward(caTranspose[0].AsObject()))
      return false;

At this stage, the SSAM modules are connected: each of the two instances processes its own stream — either temporal or spatial. The first SSAM applies masking of future steps to adjust temporal dynamics, while the second generates an attention map for the spatial representation.

   for(int i = 0; i < 2; i++)
      if(!caSSAM[i].FeedForward(caBottleneck[i].AsObject()))
         return false;

The output of the second module is further processed through a transpose layer, which ensures proper alignment before it is combined with the residual signal cResidual.

   if(!caTranspose[1].FeedForward(caSSAM[1].AsObject()))
      return false;

Next, the outputs of SSAM and the residual path are summed. The result is stored in the buffer of the first layer of the Element-Wise module of the cG function. This ensures the coordinated consolidation of all information streams and stabilizes the values before they are passed on to the subsequent elements of the block.

   if(!cResidual.FeedForward(NeuronOCL))
      return false;
//---
   CNeuronBaseOCL* current = cG[0];
   if(!current)
      return false;
   if(!SumAndNormilize(caSSAM[0].getOutput(), caTranspose[1].getOutput(),
                       current.getOutput(), caTranspose[1].GetCount(), false, 0, 0, 0, 1) ||
      !SumAndNormilize(current.getOutput(), cResidual.getOutput(),
                       current.getOutput(), caTranspose[1].GetCount(), false, 0, 0, 0, 1))
      return false;

The data then sequentially passes through all the remaining elements of the cG function, where each object receives a signal from the previous step and performs its own forward pass.

   for(int i = 1; i < cG.Total(); i++)
     {
      current = cG[i];
      if(!current ||
         !current.FeedForward(cG[i - 1]))
         return false;
     }

Finally, the normalized and aggregated signal is passed to the method of the same name in the parent class to apply adaptive spiking activation.

   return CNeuronSpikeActivation::feedforward(cG[-1]);
  }

This approach fully preserves the block’s modularity and structural integrity, while ensuring correct processing and integration of temporal and spatial data streams, as well as effective use of the potential of spiking multi-head attention.

Similar changes have also been made to the backward-pass methods, which account for the integration of the two SSAM modules and their interaction with the SEW-ResNeXt branches. For clarity and a better understanding of the block’s structure and operation, we encourage you to explore these methods on your own.

It is worth noting that the principle remains the same: gradients are propagated sequentially through each component — the Bottleneck block, the SSAM modules, the transpose layers, and the residual layers. The parameters are updated one by one using the methods of the same name on the internal objects.

Overall, CNeuronSSAMResNeXtBlock acts as an integration layer that seamlessly combines SEW-ResNeXt and SSAM, allowing it to account for both local and global spatiotemporal dependencies in the data simultaneously without compromising the model’s modular structure. The complete code for the class and all of its methods is provided in the attachment.

We had a productive day today. It is time to take a short break to let our thoughts settle and absorb what we've learned. In the next article, we will continue where we left off, bringing the work to its logical conclusion, including a mandatory verification of the implemented approaches using real historical data. This will allow us to evaluate the model's effectiveness under conditions that are as close as possible to real-world scenarios and to verify that all components have been integrated correctly.


Conclusion

In this article, we continued our work on implementing the approaches proposed by the authors of the S3CE-Net framework, with a particular focus on integrating the SSAM module into the SEW-ResNeXt block. This integration enables the creation of a powerful combined block in which spiking multi-head attention operates alongside SEW-ResNeXt residual blocks, providing a deep and accurate analysis of the temporal and spatial characteristics of the input data.

The framework's architectural flexibility and modularity allow the block to be adapted to various signal processing scenarios. Each component retains its autonomy. The Bottleneck block analyzes the input data in its respective representations. SSAM modules adjust the attention map in each stream, while transpose and residual layers ensure a coherent integration of information. This approach ensures that the model can simultaneously account for local dependencies and global patterns, resulting in highly accurate forecasts and robustness to data noise.


References


Programs used in the article

# Name Type Description
1 Study.mq5 Expert Advisor Expert Advisor for offline model training
2 StudyOnline.mq5 Expert Advisor Expert Advisor for online model training
3 Test.mq5 Expert Advisor Expert Advisor for testing the model
4 Trajectory.mqh Class library Structure for describing the system state and model architecture
5 NeuroNet.mqh Class library Class library for building a neural network
6 NeuroNet.cl Library OpenCL program code library

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/19956

Attached files |
MQL5.zip (3193.9 KB)
Online False Discovery Rate Control: Putting an Error Budget on a Live Signal Stream Online False Discovery Rate Control: Putting an Error Budget on a Live Signal Stream
The article addresses online false discovery rate control for streaming signals, where fixed thresholds inflate noise discoveries. We port LORD3, LORD++ and SAFFRON into MQL5, construct a causal bootstrap p‑value source without a resolution floor, and verify correctness before market data. Dependence stress tests, a budget/level indicator and a two-arm Strategy Tester show how to configure, monitor and throttle streams to keep the acted-on noise fraction within target.
Building a Prop Firm Compliance Monitor in MQL5 (Part 2): Daily Sessions and Restart-Safe Starting Balances Building a Prop Firm Compliance Monitor in MQL5 (Part 2): Daily Sessions and Restart-Safe Starting Balances
Establishes persistent daily-session management for Prop Firm Guard in MetaTrader 5. The EA derives the session identity from the trading-server date, account login, and server, persists state in SQLite, and adds a confirmation step for the daily starting balance. A timer detects trading-day changes and restores the confirmed baseline after EA removal or terminal restarts, ensuring a stable reference for daily risk checks.
The MQL5 Standard Library Explorer (Part 17): Building a Reusable Portfolio Manager with CArrayObj The MQL5 Standard Library Explorer (Part 17): Building a Reusable Portfolio Manager with CArrayObj
We present a multi‑symbol EA structure that moves symbol‑specific state into independently initialized CStrategyNode objects and manages them with CPortfolioManager. Using CArrayObj with FreeMode, Add(), At(), Delete(), Total(), and Clear(), the manager ensures safe ownership and isolation, while timer‑driven routing and completed‑bar gates keep processing deterministic. This lets you scale portfolios, control risk per node, and avoid duplicated event handling.
Building Your Personal Expert Advisor (Part 7): Risk Management VI (Final)—Consolidating the Risk Framework Building Your Personal Expert Advisor (Part 7): Risk Management VI (Final)—Consolidating the Risk Framework
The article consolidates trade, basket, and account risk into a modular framework for MQL5 EAs. Risk logic moves into reusable classes and shared types, leaving the EA with its inputs, signal, configuration, and call sequence. The result is a shorter strategy file and a second EA that reuses the basket tier and the shared conversions.