NetworkMomentum/Include/NetworkMomentum/NM_Engine.mqh
2026-07-30 03:34:33 +00:00

153 lines
6 KiB
MQL5

//+------------------------------------------------------------------+
//| NM_Engine.mqh |
//| MMQ — Muhammad Minhas Qamar |
//| www.mql5.com/en/articles/23763 |
//+------------------------------------------------------------------+
#property copyright "MMQ — Muhammad Minhas Qamar"
#property link "https://www.mql5.com/en/articles/23763"
#property version "1.00"
#property strict
#include <NetworkMomentum\NM_Config.mqh>
#include <NetworkMomentum\NM_Matrix.mqh>
#include <NetworkMomentum\NM_Data.mqh>
#include <NetworkMomentum\NM_Momentum.mqh>
#include <NetworkMomentum\NM_DDTW.mqh>
#include <NetworkMomentum\NM_GraphLearner.mqh>
//+------------------------------------------------------------------+
//| End-to-end network-momentum engine (Sections 3-5). |
//| |
//| This is where the pieces meet. Given a T x M block of scaled |
//| deltas and the matching oscillator tensor it produces the final |
//| per-symbol signal: |
//| |
//| 1. Ensemble adjacency (Equation 7). For each lookback window |
//| run DDTW to get V_w, learn A_w, then AVERAGE the raw A_w |
//| and normalise the average once (Equation 6). Averaging |
//| before normalising - not after - is what the paper does. |
//| |
//| 2. Propagation. For each speed k the network momentum is |
//| osc_k * A_norm^T, spilling each node's momentum along its |
//| edges to its neighbours. |
//| |
//| 3. Signal. Take the last bar of each speed's propagated |
//| momentum, pass it through the reverting-sigmoid response, |
//| and average across speeds. The sign of the result is the |
//| LONG / SHORT / FLAT decision. |
//| |
//| The adjacency in step 1 is the expensive part and depends only |
//| on the returns, so the live EA caches it and reruns steps 2-3 |
//| each bar, rebuilding the graph only every N days. |
//+------------------------------------------------------------------+
class CNMEngine
{
public:
//--- normalised ensemble adjacency from a T x M scaled-delta block.
static void EnsembleAdjacency(const NMMatrix &scaled_deltas,
const int &windows[],NMMatrix &A_norm);
//--- network momentum osc_k * A_norm^T, stored as K stacked blocks.
static void Propagate(const NMMatrix &osc,const NMMatrix &A_norm,
NMMatrix &net);
//--- final per-symbol signal from the propagated momentum's last bar.
static void Signal(const NMMatrix &net,const int T,double &signal[]);
};
//+------------------------------------------------------------------+
//| Normalised ensemble adjacency (Equations 6 and 7). |
//+------------------------------------------------------------------+
void CNMEngine::EnsembleAdjacency(const NMMatrix &scaled_deltas,
const int &windows[],NMMatrix &A_norm)
{
const int T=scaled_deltas.rows;
const int M=scaled_deltas.cols;
const int nw=ArraySize(windows);
NMMatrix A_sum;
A_sum.Init(M,M);
int used=0;
for(int wi=0;wi<nw;wi++)
{
int w=windows[wi];
if(w>T)
continue;
//--- last w rows of the returns block feed this window's detector.
NMMatrix block;
block.Init(w,M);
int start=T-w;
for(int r=0;r<w;r++)
for(int c=0;c<M;c++)
block.Set(r,c,scaled_deltas.Get(start+r,c));
NMMatrix V,A;
CNMDDTW::LeadLagMatrix(block,V);
CNMGraphLearner::LearnAdjacency(V,A);
for(int k=0;k<A_sum.Count();k++)
A_sum.data[k]+=A.data[k];
used++;
}
//--- average the raw adjacencies, then normalise the average once.
if(used>0)
{
double inv=1.0/used;
for(int k=0;k<A_sum.Count();k++)
A_sum.data[k]*=inv;
}
CNMGraphLearner::Normalize(A_sum,A_norm);
}
//+------------------------------------------------------------------+
//| Network momentum osc_k * A_norm^T for every speed block. |
//| osc is (K*T) x M; block k occupies rows k*T..k*T+T. Each row of |
//| a block is one bar's oscillator vector, post-multiplied by |
//| A_norm^T so entry m becomes sum_n osc[.,n] * A_norm[m,n]. |
//+------------------------------------------------------------------+
void CNMEngine::Propagate(const NMMatrix &osc,const NMMatrix &A_norm,
NMMatrix &net)
{
const int M=A_norm.rows;
const int rows=osc.rows;
net.Init(rows,M);
for(int r=0;r<rows;r++)
{
for(int m=0;m<M;m++)
{
double s=0.0;
for(int n=0;n<M;n++)
s+=osc.Get(r,n)*A_norm.Get(m,n);
net.Set(r,m,s);
}
}
}
//+------------------------------------------------------------------+
//| Final per-symbol signal (Definition 5.1 averaged over speeds). |
//| T is the per-speed block height, so row k*T + (T-1) is the last |
//| bar of speed k. The response is applied there and averaged |
//| across the K speeds into one number per symbol. |
//+------------------------------------------------------------------+
void CNMEngine::Signal(const NMMatrix &net,const int T,double &signal[])
{
const int M=net.cols;
const int K=net.rows/T;
ArrayResize(signal,M);
for(int m=0;m<M;m++)
{
double acc=0.0;
for(int k=0;k<K;k++)
{
int row=k*T+(T-1);
acc+=CNMMomentum::Response(net.Get(row,m));
}
signal[m]=acc/K;
}
}
//+------------------------------------------------------------------+