153 lines
6 KiB
MQL5
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;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|