Recurrence Network Analysis (RNA) in MQL5: From Recurrence Matrices to Complex Networks
Contents
- Introduction
- RNA on a Chart
- From Recurrence Matrices to Complex Networks
- The Network Metrics
- Library Architecture
- CRNAMetrics: The Graph Metrics Engine
- CRNAWindow: Rolling Single-Series Network Analysis
- CJRNAWindow: Rolling Joint Network Analysis
- CRNA and CJRNA: The Facades
- The RNA Indicator
- The JRNA Indicator
- What's Next
- Conclusion
Introduction
The first three articles in this series built a progressively richer toolkit for recurrence analysis. The first article introduced single-series RQA: embed a time series, build a recurrence matrix, and extract twelve metrics describing determinism, laminarity, entropy, and trend. The second article extended this to Cross-Recurrence Quantification Analysis, comparing the phase-space trajectories of two different series. The third article added Joint Recurrence Quantification Analysis, detecting simultaneous recurrence in two systems by AND-ing their individual recurrence matrices.
All three articles treated the recurrence matrix as a grid of patterns: diagonal lines, vertical lines, isolated dots. The metrics counted these patterns. This article takes a fundamentally different perspective on the same data. Instead of scanning line structures, we reinterpret the recurrence matrix as the adjacency matrix of a complex network. Each time point becomes a node. Each recurrence becomes an edge. The result is a graph whose topology encodes the dynamical structure of the underlying system.
This reinterpretation is called Recurrence Network Analysis (RNA). It gives us access to the full toolkit of complex network theory: clustering coefficients, shortest path lengths, betweenness centrality, assortativity, and more. These graph-theoretic measures capture structural properties that the original RQA line-counting metrics cannot express. A recurrence matrix with few diagonal lines but tight clusters of interconnected nodes tells a different story than one with long diagonals but sparse clustering. RNA makes that distinction visible.
This article delivers five new files and updates one existing file. CRNAMetrics computes twelve graph metrics from any square recurrence matrix. CRNAWindow applies these metrics over a rolling window for a single series. CJRNAWindow does the same for two series using the joint recurrence matrix. Two indicators (RNA_Indicator.mq5 and JRNA_Indicator.mq5) plot five selected network metrics on a live chart. As with the previous articles, no trading strategy is built here. The goal is a tested analytical layer you can immediately incorporate into experiments.
RNA on a Chart
The image below shows the RNA indicator running on a live chart in MetaTrader 5. Five network metrics are plotted in a separate window: Average Clustering Coefficient (ACC), Transitivity, Normalized Average Path Length (NormAPL), Assortativity, and Average Betweenness Centrality. Each is computed from a rolling window of close prices, where each window's recurrence matrix is treated as a network.

Fig. 1. The RNA indicator on EURUSD H1, plotting five network metrics derived from rolling recurrence matrices
When ACC is high, recurrent states form tightly connected clusters: the market revisits groups of related states as a coherent unit. When Transitivity drops, those clusters fragment. When NormAPL is low, any two time points can be connected through a short chain of recurrences, indicating a "small world" structure in the dynamics. Assortativity measures whether highly connected nodes (frequently recurring states) tend to connect to each other. Average Betweenness identifies time points that act as bridges between otherwise separate clusters of recurrence.
The second indicator, JRNA_Indicator, produces the same five metrics but from the joint recurrence network of two symbols. It includes timestamp alignment and normalization, following the same approach as the CRQA indicator from the second article.

Fig. 2. The JRNA indicator comparing EURUSD with GBPUSD, plotting network metrics derived from rolling joint recurrence matrices
That is what the library produces. The rest of this article explains how.
From Recurrence Matrices to Complex Networks
The recurrence matrix R(i,j) built by CRQAMatrix is binary: each cell is either 1 (recurrent) or 0 (not recurrent). For self-recurrence, the matrix is square (N x N) and symmetric: R(i,j) = R(j,i). The main diagonal is always 1 because every state is identical to itself. These are exactly the properties of an adjacency matrix for an undirected, unweighted graph, with one exception: the main diagonal represents self-loops, which carry no information.
The reinterpretation is straightforward. Take the recurrence matrix, set the main diagonal to zero (removing self-loops), and read the result as a graph:
- Nodes: each embedded time point i = 0, 1, ..., N-1.
- Edges: an undirected edge between i and j exists if and only if R(i,j) = 1 and i != j.
This transformation costs nothing computationally. The data is the same. Only the interpretation changes. But that change in perspective opens up an entire field of mathematics. Complex network theory provides dozens of measures to characterize graph topology: clustering, information-flow efficiency, node centrality, small-world structure, and assortativity.

Fig. 3. The recurrence matrix (left) reinterpreted as a complex network (right). Black cells become edges; the main diagonal is excluded
The same logic applies to the joint recurrence matrix JR(i,j) from the third article. JR is also square, symmetric, and binary. Setting its diagonal to zero gives the adjacency matrix of the joint recurrence network. An edge in this network means that both series simultaneously revisited their respective past states at those time indices. The network metrics then characterize the topology of synchronized recurrence between the two systems.
The CRNAMetrics class in this library works with any square symmetric binary matrix. It accepts either a CRQAMatrix directly (for single-series RNA) or a flat adjacency array (for joint or any other recurrence variant). This makes it reusable across all recurrence types in the library.
One important caveat: network metrics are computationally heavier than RQA line-counting metrics. Betweenness centrality requires running BFS from every node, costing O(N^2) per source and O(N^3) total. Triangle counting for transitivity is O(N^2 * k) where k is the average degree. For a window size of 50, N is approximately 50 nodes, so O(50^3) = 125,000 operations per window. This is manageable, but it scales cubically. For window sizes above 100, the cost becomes noticeable. The library is CPU-only for network metrics because BFS and triangle counting are inherently sequential.
The Network Metrics
CRNAMetrics computes twelve network metrics from the adjacency matrix. Each captures a different aspect of the graph's topology. The table below provides a compact reference. We expand on the most important ones afterward.
| Metric | Symbol | Formula | What It Measures |
|---|---|---|---|
| Average Degree | AvgDegree | sum(k_i) / N | Mean number of edges per node. Higher means more recurrences on average. |
| Max Degree | MaxDegree | max(k_i) | Most connected node. Identifies the time point most recurrent with the rest of the series. |
| Degree Std Dev | DegreeStd | std(k_i) | Spread of the degree distribution. High std means some states are much more recurrent than others. |
| Average Clustering Coefficient | ACC | mean(C_i), C_i = 2*links / (k_i*(k_i-1)) | Average local clustering. High ACC means recurrent states form tightly knit groups. |
| Transitivity | T | 3 * triangles / connected_triples | Global clustering. Fraction of connected triples that close into triangles. |
| Avg Path Length | APL | mean(d(i,j)) over reachable pairs | Average shortest path. Low APL means the network is tightly interconnected (small-world property). |
| Diameter | D | max(d(i,j)) | Longest shortest path. Maximum separation between any two reachable nodes. |
| Avg Betweenness | AvgBC | mean(BC_i), normalized | Mean betweenness centrality. High means many nodes act as bridges between clusters. |
| Max Betweenness | MaxBC | max(BC_i), normalized | Most central node. Identifies the time point most critical for network connectivity. |
| Avg Closeness | AvgCC | mean(closeness_i), Wasserman-Faust | Mean closeness centrality. High means nodes can reach others quickly on average. |
| Assortativity | r | Pearson correlation of degrees at edge endpoints | Degree-degree correlation. Positive: hubs connect to hubs. Negative: hubs connect to peripheral nodes. |
| Density | rho | 2|E| / (N*(N-1)) | Fraction of possible edges that exist. Equivalent to RR (recurrence rate) on the network. |
ACC (Average Clustering Coefficient) is perhaps the most distinctive network metric for recurrence analysis. For each node i, the local clustering coefficient C(i) measures what fraction of i's neighbors are also neighbors of each other. If node i is recurrent with nodes j, k, and l, and j-k and k-l are also recurrent with each other, then i sits in a tight cluster. ACC averages this across all nodes. In market terms, high ACC indicates that the price series visits groups of mutually similar states repeatedly. These are coherent dynamical regimes where the system circulates within a well-defined region of state space.
Transitivity is the global counterpart of ACC. While ACC averages local clustering, transitivity measures the global fraction of connected triples that form triangles: T = 3 * triangles / connected_triples. The factor of 3 accounts for the fact that each triangle contributes to three connected triples. Transitivity and ACC often move together, but they can diverge when the degree distribution is heterogeneous. A few high-degree hub nodes can inflate ACC without increasing the global triangle density.
Average Path Length (APL) measures how many edges separate two nodes on average, using shortest paths computed by BFS. Low APL means the recurrence network is compact: any two time points are connected through a short chain of intermediate recurrences. This is the hallmark of a "small-world" network. In market terms, low APL suggests that the system's states are broadly interconnected, with few isolated regimes. High APL (or effectively infinite when the network is disconnected) indicates fragmented dynamics with distinct, unreachable regimes.
Betweenness Centrality identifies nodes that act as bridges. For a node v, betweenness counts how many shortest paths between other pairs pass through v, normalized by the total number of such paths. Nodes with high betweenness are critical for network connectivity. In recurrence terms, a time point with high betweenness connects otherwise separate clusters of recurrence. It may correspond to a transitional state between market regimes.
Assortativity (Newman 2002) measures whether high-degree nodes prefer to connect to other high-degree nodes (positive assortativity) or to low-degree nodes (negative assortativity). In recurrence networks, positive assortativity means that frequently recurring states are recurrent with each other, forming a core of highly interconnected hub states. Negative assortativity means that hub states connect primarily to peripheral states that are not otherwise well-connected.
Library Architecture
The RNA extension adds three new files to the library. Unlike the previous extensions (CRQA, JRQA), RNA does not introduce a new type of recurrence matrix. Instead, it operates on the existing matrices (CRQAMatrix for single-series, CJRQAMatrix for joint) and extracts graph-theoretic measures from them. CRNAMetrics is the core engine. CRNAWindow and CJRNAWindow provide rolling analysis. Two facades (CRNA and CJRNA) in RQA.mqh wrap everything into single-call interfaces.

Fig. 4. RNA library architecture. CRNAMetrics is the shared engine that operates on any square adjacency matrix
| File | Class | Responsibility |
|---|---|---|
| RNAMetrics.mqh | CRNAMetrics | Graph metrics engine. Computes degree, clustering, transitivity, BFS-based path metrics, betweenness (Brandes), closeness, and assortativity from a flat adjacency matrix. Works with any square recurrence matrix. |
| RNAWindow.mqh | CRNAWindow | Rolling RNA for a single series. Slides a window, builds CRQAMatrix per window, passes it to CRNAMetrics, and stores the results. CPU-only. |
| JRNAWindow.mqh | CJRNAWindow | Rolling JRNA for two series. Slides a window, builds CJRQAMatrix per window, extracts the adjacency matrix, and passes it to CRNAMetrics. CPU-only. |
| RQA.mqh | CRNA, CJRNA | High-level facades. CRNA chains CRQAMatrix + CRNAMetrics for single-series network analysis. CJRNA chains CJRQAMatrix + CRNAMetrics for joint network analysis. |
One struct supports the classes:
- SRNAResult holds all twelve network metric values plus a Reset() method. This struct is shared across both RNA and JRNA because the graph metrics are identical regardless of whether the adjacency matrix came from a standard recurrence plot or a joint recurrence plot.
- SRNAWindowResult pairs an SRNAResult with a bar index, identifying which window the metrics belong to. Also shared between CRNAWindow and CJRNAWindow.
CRNAMetrics has two entry points: Compute() accepts a CRQAMatrix directly (convenience wrapper for single-series RNA), and ComputeFromAdj() accepts a raw flat char[] adjacency array (used by CJRNAWindow and any other source of adjacency data). Both paths converge on the same internal computation pipeline.
To use the library, you still need only one include:
#include <RQA\RQA.mqh> This now gives you access to all standard RQA classes, cross-RQA classes, joint-RQA classes, and the new RNA/JRNA classes. The naming convention continues: RNA classes use C + RNA prefix (CRNAMetrics, CRNAWindow), facades use CRNA and CJRNA, and the result struct uses S + RNA prefix (SRNAResult).
CRNAMetrics: The Graph Metrics Engine
CRNAMetrics is the core of this article. It takes a flat adjacency matrix (a char[] array of length N*N where A[i*N+j] != 0 means an edge exists between nodes i and j) and computes all twelve network metrics. The class has no state. All computation happens in the Compute() or ComputeFromAdj() call, and results are written into an SRNAResult struct.
The SRNAResult Struct
All metrics are stored in a single struct with a Reset() method that zeros everything out. This is the data contract between CRNAMetrics and the rest of the library.
struct SRNAResult { double AvgDegree; double MaxDegree; double DegreeStd; double AvgClustering; double Transitivity; double AvgPathLength; double Diameter; double AvgBetweenness; double MaxBetweenness; double AvgCloseness; double Assortativity; double Density; void Reset() { AvgDegree=0; MaxDegree=0; DegreeStd=0; AvgClustering=0; Transitivity=0; AvgPathLength=0; Diameter=0; AvgBetweenness=0; MaxBetweenness=0; AvgCloseness=0; Assortativity=0; Density=0; } };
Compare this with SRQAResult from the first article or SCRQAResult from the second. The structure is the same pattern: a flat struct with named fields and a Reset() method. But the fields are entirely different. SRQAResult holds diagonal and vertical line statistics. SRNAResult holds graph topology statistics. They describe the same underlying matrix from two different mathematical perspectives.
The Convenience Wrapper
The Compute() method provides a direct path from a CRQAMatrix to network metrics. It extracts the adjacency matrix (setting the main diagonal to zero) and delegates to ComputeFromAdj().
//+------------------------------------------------------------------+ //| Convenience wrapper: extract CRQAMatrix into flat adjacency | //+------------------------------------------------------------------+ bool CRNAMetrics::Compute(const CRQAMatrix &mat, SRNAResult &result) const { int N = mat.Size(); if(N < 3) { Print("CRNAMetrics::Compute: network too small (min 3 nodes)"); return false; } char adj[]; ArrayResize(adj, N * N); for(int i = 0; i < N; i++) for(int j = 0; j < N; j++) adj[i * N + j] = (char)((i != j && mat.Get(i, j)) ? 1 : 0); return ComputeFromAdj(adj, N, result); }
The condition i != j ensures self-loops are excluded. The CRQAMatrix stores the full N x N matrix including the main diagonal (which is always 1 in self-recurrence), but the network adjacency matrix must have zeros on the diagonal. The resulting char[] array is passed to ComputeFromAdj(), which runs the complete metrics pipeline.
Node Degree Computation
The degree of a node is simply the number of edges connected to it. For an adjacency matrix, this is the row sum. ComputeDeg() computes degrees for all nodes in a single pass.
//+------------------------------------------------------------------+ //| Compute node degrees (row sums; self-loops already excluded) | //+------------------------------------------------------------------+ void CRNAMetrics::ComputeDeg(const char &A[], int N, int °rees[]) const { ArrayResize(degrees, N); ArrayInitialize(degrees, 0); for(int i = 0; i < N; i++) { int off = i * N; for(int j = 0; j < N; j++) if(A[off + j] != 0) degrees[i]++; } }
The degree array is used by nearly every subsequent computation: clustering, transitivity, assortativity, and the final aggregate statistics. Computing it once upfront avoids redundant work.
Local Clustering Coefficient
The clustering coefficient of node i measures how densely interconnected its neighbors are. For a node with degree k, the maximum possible edges among its k neighbors is k*(k-1)/2. The clustering coefficient is the ratio of actual edges among neighbors to this maximum.
//+------------------------------------------------------------------+ //| Local clustering coefficient per node | //| C(i) = 2 * links_among_neighbors / (k * (k-1)) | //+------------------------------------------------------------------+ void CRNAMetrics::ComputeClust(const char &A[], int N, const int °rees[], double &cc[]) const { ArrayResize(cc, N); ArrayInitialize(cc, 0.0); int neighbors[]; ArrayResize(neighbors, N); for(int i = 0; i < N; i++) { int k = degrees[i]; if(k < 2) continue; int nCnt = 0; int off = i * N; for(int j = 0; j < N; j++) if(A[off + j] != 0) neighbors[nCnt++] = j; int links = 0; for(int a = 0; a < nCnt; a++) for(int b = a + 1; b < nCnt; b++) if(A[neighbors[a] * N + neighbors[b]] != 0) links++; cc[i] = (2.0 * links) / ((double)k * (k - 1)); } }
For each node i with degree k >= 2, the method first collects all k neighbors into a local array. Then it checks every pair of neighbors (a, b) to see if they are connected. The count of such links, divided by the maximum possible k*(k-1)/2, gives C(i). The factor of 2 in the numerator compensates for the division by k*(k-1) instead of k*(k-1)/2. Nodes with fewer than 2 neighbors get a clustering coefficient of 0, since no triangle can form around them. The cost per node is O(k^2), so the total cost depends on the degree distribution. For dense networks this can approach O(N^2) per node.
Transitivity (Global Clustering)
Transitivity counts all triangles in the network and divides by the total number of connected triples. A connected triple is a path of length 2 (three nodes, two edges). A triangle is a closed triple (all three edges present).
//+------------------------------------------------------------------+ //| Transitivity (global clustering coefficient) | //| T = 3 * triangles / connected_triples | //+------------------------------------------------------------------+ double CRNAMetrics::ComputeTrans(const char &A[], int N, const int °rees[]) const { long triangles = 0; for(int i = 0; i < N; i++) for(int j = i + 1; j < N; j++) if(A[i * N + j] != 0) for(int k = j + 1; k < N; k++) if(A[i * N + k] != 0 && A[j * N + k] != 0) triangles++; long triples = 0; for(int i = 0; i < N; i++) triples += (long)degrees[i] * (degrees[i] - 1) / 2; if(triples == 0) return 0.0; return (double)(3 * triangles) / triples; }
The triangle counting uses a triple nested loop over ordered triples (i < j < k). For each triple where all three edges exist (i-j, i-k, j-k), the triangle count increments. The connected triples count is computed from the degree sequence: each node with degree k contributes k*(k-1)/2 connected triples centered on it. The factor of 3 in the numerator is because each triangle contains exactly three connected triples (one centered on each vertex), so the ratio 3*triangles/triples gives the fraction of triples that are closed. The worst-case complexity is O(N^3), but the early exit on the i-j edge check makes it much faster for sparse networks.
BFS (Breadth-First Search)
BFS is the building block for path-based metrics. It computes the shortest path distance from a single source node to all reachable nodes in O(N + |E|) time, where |E| is the number of edges.
//+------------------------------------------------------------------+ //| BFS from a single source node on flat adjacency | //+------------------------------------------------------------------+ void CRNAMetrics::BFSArr(const char &A[], int start, int N, int &dist[]) const { ArrayInitialize(dist, -1); dist[start] = 0; int queue[]; ArrayResize(queue, N); int qF = 0, qB = 0; queue[qB++] = start; while(qF < qB) { int u = queue[qF++]; int off = u * N; for(int v = 0; v < N; v++) { if(dist[v] != -1 || A[off + v] == 0) continue; dist[v] = dist[u] + 1; queue[qB++] = v; } } }
The implementation uses a flat array as a circular queue (qF is the front pointer, qB is the back pointer). Unreachable nodes retain their initial distance of -1, which allows the caller to detect disconnected components. The dist[] array is pre-allocated to size N by the caller and reused across multiple BFS calls to avoid repeated allocation. This is a standard textbook BFS with O(N^2) worst-case per invocation (because the adjacency check scans all N neighbors for each node), which is the price of using an adjacency matrix rather than an adjacency list.
Path Metrics and Closeness Centrality
ComputePaths() runs BFS from every node to compute the average shortest path length, diameter, and closeness centrality for each node. The average path length is the mean over all reachable pairs. The diameter is the maximum distance observed. Closeness uses the Wasserman-Faust normalization that handles disconnected graphs: closeness(s) = (reachable^2) / ((N-1) * sumDist), where reachable is the number of nodes reachable from s and sumDist is the total distance to those nodes. This normalization ensures that nodes in small connected components get low closeness rather than artificially high values.
Betweenness Centrality: The Brandes Algorithm
Betweenness centrality is the most computationally expensive metric in the suite. For each node v, it counts how many shortest paths between other pairs of nodes pass through v. The naive approach would require computing all-pairs shortest paths and then checking each path for intermediaries, but Brandes (2001) showed that betweenness can be computed in O(N^2) time for unweighted graphs by accumulating dependency scores during BFS traversals.
//+------------------------------------------------------------------+ //| Betweenness centrality: Brandes algorithm (unweighted) | //+------------------------------------------------------------------+ void CRNAMetrics::ComputeBC(const char &A[], int N, double &bc[]) const { ArrayResize(bc, N); ArrayInitialize(bc, 0.0); int sigma[], dist[], stack[], queue[]; double delta[]; int predStore[], predCnt[]; ArrayResize(sigma, N); ArrayResize(dist, N); ArrayResize(delta, N); ArrayResize(stack, N); ArrayResize(queue, N); ArrayResize(predStore, N * N); ArrayResize(predCnt, N); for(int s = 0; s < N; s++) { ArrayInitialize(sigma, 0); sigma[s] = 1; ArrayInitialize(dist, -1); dist[s] = 0; ArrayInitialize(delta, 0.0); ArrayInitialize(predCnt, 0); int sTop = 0, qF = 0, qB = 0; queue[qB++] = s; while(qF < qB) { int v = queue[qF++]; int off = v * N; stack[sTop++] = v; for(int w = 0; w < N; w++) { if(A[off + w] == 0) continue; if(dist[w] == -1) { dist[w] = dist[v] + 1; queue[qB++] = w; } if(dist[w] == dist[v] + 1) { sigma[w] += sigma[v]; predStore[w * N + predCnt[w]] = v; predCnt[w]++; } } } while(sTop > 0) { int w = stack[--sTop]; for(int p = 0; p < predCnt[w]; p++) { int v = predStore[w * N + p]; delta[v] += ((double)sigma[v] / sigma[w]) * (1.0 + delta[w]); } if(w != s) bc[w] += delta[w]; } } for(int i = 0; i < N; i++) bc[i] /= 2.0; }
The algorithm has two phases per source node s. The forward phase is a modified BFS that simultaneously discovers shortest-path distances (dist[]), counts the number of shortest paths to each node (sigma[]), and records predecessors on shortest paths (predStore[]). As each node v is discovered, it is pushed onto a stack. The backward phase processes nodes in reverse BFS order (popping from the stack). For each node w, it distributes its dependency score to its predecessors: delta[v] += (sigma[v] / sigma[w]) * (1 + delta[w]). This formula computes the fraction of shortest paths from s through w that also pass through v, accumulated over all target nodes. After processing all source nodes, the final division by 2 corrects for the undirected graph counting each path twice (once from each endpoint).
The predStore array uses a flat N×N layout: predStore[w*N + k] stores the k-th predecessor of node w. This avoids dynamic resizing within the inner loop. The total memory cost is O(N^2) for predStore, which is acceptable for the network sizes we work with (typically N < 200). The time complexity is O(N^2) per source node and O(N^3) total, making it the dominant cost in the metrics pipeline.
Degree Assortativity
Assortativity measures the Pearson correlation between the degrees of nodes at both endpoints of each edge. It was introduced by Newman (2002) and is computed in a single pass over all edges.
//+------------------------------------------------------------------+ //| Degree assortativity coefficient (Newman 2002) | //| Pearson correlation of degrees at both ends of each edge. | //+------------------------------------------------------------------+ double CRNAMetrics::ComputeAssort(const char &A[], int N, const int °rees[]) const { double sumProd = 0; double sumSum = 0; double sumSqSum = 0; long M = 0; for(int i = 0; i < N; i++) for(int j = i + 1; j < N; j++) if(A[i * N + j] != 0) { double ki = (double)degrees[i]; double kj = (double)degrees[j]; sumProd += ki * kj; sumSum += ki + kj; sumSqSum += ki * ki + kj * kj; M++; } if(M == 0) return 0.0; double invM = 1.0 / M; double term1 = sumProd * invM; double half = sumSum * 0.5 * invM; double term2 = half * half; double term3 = sumSqSum * 0.5 * invM; double denom = term3 - term2; if(MathAbs(denom) < 1e-12) return 0.0; return (term1 - term2) / denom; }
The method iterates over all edges (i < j to avoid double-counting), accumulating three running sums: the product of endpoint degrees, the sum of endpoint degrees, and the sum of squared endpoint degrees. These accumulators allow the Pearson correlation to be computed in closed form without a second pass. M counts the total number of edges. The result ranges from -1 (perfectly disassortative: high-degree nodes connect only to low-degree nodes) to +1 (perfectly assortative: nodes connect only to nodes of similar degree). Financial recurrence networks typically show moderate positive assortativity.
The Main Pipeline: ComputeFromAdj
ComputeFromAdj() is the central orchestrator. It calls each sub-computation in sequence and assembles the final result.
//+------------------------------------------------------------------+ //| Core computation from flat adjacency char[N*N] | //+------------------------------------------------------------------+ bool CRNAMetrics::ComputeFromAdj(const char &adj[], int N, SRNAResult &result) const { result.Reset(); if(N < 3) return false; //--- Degrees int degrees[]; ComputeDeg(adj, N, degrees); double sumDeg = 0, sumDeg2 = 0; int maxDeg = 0; for(int i = 0; i < N; i++) { sumDeg += degrees[i]; sumDeg2 += (double)degrees[i] * degrees[i]; if(degrees[i] > maxDeg) maxDeg = degrees[i]; } result.AvgDegree = sumDeg / N; result.MaxDegree = (double)maxDeg; result.DegreeStd = MathSqrt(MathMax(0.0, sumDeg2 / N - result.AvgDegree * result.AvgDegree)); result.Density = sumDeg / ((double)N * (N - 1)); //--- Clustering double cc[]; ComputeClust(adj, N, degrees, cc); double sumClust = 0; for(int i = 0; i < N; i++) sumClust += cc[i]; result.AvgClustering = sumClust / N; //--- Transitivity result.Transitivity = ComputeTrans(adj, N, degrees); //--- Path metrics + Closeness double closeness[]; ComputePaths(adj, N, result.AvgPathLength, result.Diameter, closeness); double sumClose = 0; for(int i = 0; i < N; i++) sumClose += closeness[i]; result.AvgCloseness = sumClose / N; //--- Betweenness (Brandes, then normalize) double betweenness[]; ComputeBC(adj, N, betweenness); double bcNorm = (N > 2) ? 2.0 / ((double)(N - 1) * (N - 2)) : 1.0; double sumBC = 0, maxBC = 0; for(int i = 0; i < N; i++) { double nbc = betweenness[i] * bcNorm; sumBC += nbc; if(nbc > maxBC) maxBC = nbc; } result.AvgBetweenness = sumBC / N; result.MaxBetweenness = maxBC; //--- Assortativity result.Assortativity = ComputeAssort(adj, N, degrees); return true; }
The pipeline proceeds in five stages: degrees, clustering, transitivity, path metrics (which includes closeness), and betweenness. Assortativity is computed last because it only depends on degrees and the adjacency matrix. The betweenness normalization factor 2/((N-1)*(N-2)) scales raw betweenness to the [0, 1] range, where 1 would mean every shortest path in the network passes through that node. The density computation is sumDeg / (N*(N-1)), which is equivalent to 2|E| / (N*(N-1)) because sumDeg = 2|E| for an undirected graph (each edge contributes 1 to two nodes' degrees).
CRNAWindow: Rolling Single-Series Network Analysis
CRNAWindow applies RNA over a rolling window on a single time series. At each window position, it slices the series, builds a recurrence matrix via CRQAMatrix, passes it to CRNAMetrics, and stores the result. The design mirrors CRQAWindow from the first article, with CRNAMetrics replacing CRQAMetrics as the computation engine.
The Run Method
//+------------------------------------------------------------------+ //| Slide window, build recurrence matrix, compute network metrics | //+------------------------------------------------------------------+ bool CRNAWindow::Run(const double &series[], int seriesLen, SRNAWindowResult &results[]) { if(seriesLen < m_windowSize) { Print("CRNAWindow::Run: series shorter than window"); return false; } CRQAMatrix mat; CRNAMetrics metrics; int numWindows = (seriesLen - m_windowSize) / m_step + 1; ArrayResize(results, numWindows); double slice[]; ArrayResize(slice, m_windowSize); int idx = 0; for(int start = 0; start + m_windowSize <= seriesLen; start += m_step) { for(int k = 0; k < m_windowSize; k++) slice[k] = series[start + k]; double eps = (m_epsilon > 0.0) ? m_epsilon : 0.1; results[idx].barIndex = start; if(mat.Build(slice, m_windowSize, eps, m_embDim, m_delay, m_norm)) metrics.Compute(mat, results[idx].metrics); else results[idx].metrics.Reset(); idx++; } ArrayResize(results, idx); return true; }
Each iteration copies a window slice, builds the recurrence matrix, and computes all twelve network metrics. The CRQAMatrix and CRNAMetrics objects are reused across iterations. If the matrix build fails for a particular window, the metrics are reset to zero and the loop continues. The cost per window is dominated by the network metrics computation: O(N^3) for betweenness and path lengths, where N is the number of embedded vectors in each window (typically windowSize minus the embedding overhead).
CRNAWindow is CPU-only. Unlike CCRQAWindow, which offloads matrix construction to the GPU, the network-metric computations here do not benefit from GPU parallelism. BFS, triangle counting, and the Brandes algorithm are inherently sequential within each invocation. The matrix construction itself (O(N^2) per window) is a small fraction of the total cost compared to the network metrics (O(N^3) per window), so GPU-accelerating only the matrix build would not produce a meaningful speedup.
Static Extractors
Static extractors follow the same pattern as in all previous window modules. There is one for each commonly used metric: ExtractAvgClustering, ExtractTransitivity, ExtractAvgPathLength, ExtractAssortativity, ExtractAvgBetweenness, and ExtractDensity.
void CRNAWindow::ExtractAvgClustering(const SRNAWindowResult &r[], double &out[]) { int n = ArraySize(r); ArrayResize(out, n); for(int i = 0; i < n; i++) out[i] = r[i].metrics.AvgClustering; }
Each extractor copies one metric field from the results array into a plain double array. This keeps data extraction separate from computation, allowing the indicator or script to select only the metrics it needs.
CJRNAWindow: Rolling Joint Network Analysis
CJRNAWindow extends the network analysis to two time series by using the joint recurrence matrix. At each window position, it slices both series, builds a CJRQAMatrix (which AND-s the two individual recurrence matrices), extracts the adjacency matrix (excluding self-loops), and passes it to CRNAMetrics::ComputeFromAdj(). The result struct is the same SRNAResult used by CRNAWindow, because the graph metrics are identical regardless of how the adjacency matrix was produced.
The Run Method
//+------------------------------------------------------------------+ //| Slide window, build joint RP, extract adjacency, compute metrics | //+------------------------------------------------------------------+ bool CJRNAWindow::Run(const double &seriesX[], int lenX, const double &seriesY[], int lenY, SRNAWindowResult &results[]) { int minLen = MathMin(lenX, lenY); if(minLen < m_windowSize) { Print("CJRNAWindow::Run: series shorter than window"); return false; } CJRQAMatrix mat; CRNAMetrics metrics; int numWindows = (minLen - m_windowSize) / m_step + 1; ArrayResize(results, numWindows); double sliceX[], sliceY[]; ArrayResize(sliceX, m_windowSize); ArrayResize(sliceY, m_windowSize); int idx = 0; for(int start = 0; start + m_windowSize <= minLen; start += m_step) { for(int k = 0; k < m_windowSize; k++) { sliceX[k] = seriesX[start + k]; sliceY[k] = seriesY[start + k]; } results[idx].barIndex = start; if(!mat.Build(sliceX, sliceY, m_windowSize, m_epsilonX, m_epsilonY, m_embDim, m_delay, m_norm)) { results[idx].metrics.Reset(); idx++; continue; } //--- Extract adjacency matrix (no self-loops) int N = mat.Size(); char adj[]; ArrayResize(adj, N * N); for(int i = 0; i < N; i++) for(int j = 0; j < N; j++) adj[i * N + j] = (char)((i != j && mat.Get(i, j)) ? 1 : 0); metrics.ComputeFromAdj(adj, N, results[idx].metrics); idx++; } ArrayResize(results, idx); return true; }
There are two key differences from CRNAWindow. First, the method takes two series and uses MathMin(lenX, lenY) as the effective length. Both series must be time-aligned before being passed in. Second, after building the CJRQAMatrix, the method explicitly extracts the adjacency matrix into a flat char[] array. CJRQAMatrix::Get(i, j) returns the joint recurrence value, which is already binary and symmetric. The extraction loop sets adj[i*N+j] = 1 only when i != j and mat.Get(i, j) is true, matching exactly the same adjacency extraction done by the CJRNA facade.
CJRNAWindow supports separate epsilon values for the two series via its overloaded SetEpsilon() method: SetEpsilon(double eps) sets both to the same value, while SetEpsilon(double epsX, double epsY) allows independent thresholds. This is the same approach used by CJRQAWindow in the third article.
The static extractors are identical in structure to those in CRNAWindow: ExtractAvgClustering, ExtractTransitivity, ExtractAvgPathLength, ExtractAssortativity, ExtractAvgBetweenness, and ExtractDensity.
CRNA and CJRNA: The Facades
Two facades wrap the network analysis modules into single-call interfaces. CRNA handles single-series RNA. CJRNA handles joint RNA for two series. Both live in RQA.mqh alongside the existing CRQA, CCRQA, and CJRQA facades.
CRNA: Single-Series Network Analysis
The CRNA facade chains CRQAMatrix and CRNAMetrics into one object. It supports the same epsilon configuration as the CRQA facade: you can set a fixed epsilon with SetEpsilon() or use automatic selection with SetEpsilonAuto().
CRNA rna; rna.SetEmbedding(2, 1); rna.SetNorm(RQA_NORM_EUCLIDEAN); rna.SetEpsilonAuto(EPSILON_RR_TARGET, 0.05); if(rna.Compute(close, copied)) rna.PrintSummary();
The Compute() method first runs epsilon auto-selection if configured, then builds the recurrence matrix, and finally computes all twelve network metrics. After Compute() returns true, you can access individual metrics through named accessors: AvgClustering(), Transitivity(), AvgPathLength(), AvgBetweenness(), Assortativity(), and others. The PrintSummary() method outputs all metrics to the Experts log.
//+------------------------------------------------------------------+ //| Build recurrence matrix and compute all network metrics | //+------------------------------------------------------------------+ bool CRNA::Compute(const double &series[], int N) { m_computed = false; m_result.Reset(); if(N < 4) { Print("CRNA::Compute: series too short (min 4 bars)"); return false; } double eps = m_epsilon; if(m_epsilonMethod != EPSILON_FIXED) eps = CRQAEpsilon::Select(series, N, m_epsilonMethod, m_epsilonParam); m_epsilon = eps; if(!m_matrix.Build(series, N, eps, m_embDim, m_delay, m_norm)) return false; if(!m_metrics.Compute(m_matrix, m_result)) return false; m_computed = true; return true; }
The structure is identical to the CRQA::Compute() method from the first article, with CRNAMetrics replacing CRQAMetrics. The same CRQAEpsilon strategies are available: EPSILON_FIXED, EPSILON_STD_FRACTION, EPSILON_RANGE_FRACTION, and EPSILON_RR_TARGET.
CJRNA: Joint Network Analysis
The CJRNA facade chains CJRQAMatrix and CRNAMetrics. It takes two series of equal length and supports separate epsilon values for each series.
CJRNA jrna; jrna.SetEpsilon(0.05, 0.50); jrna.SetEmbedding(2, 1); if(jrna.Compute(closeX, closeY, len)) jrna.PrintSummary();
Internally, CJRNA::Compute() builds the joint recurrence matrix, extracts the adjacency matrix (excluding self-loops), and passes it to CRNAMetrics::ComputeFromAdj(). The adjacency extraction is the same loop seen in CJRNAWindow: adj[i*N+j] = 1 if i != j and JR(i,j) = 1.
The RNA Indicator
RNA_Indicator.mq5 plots five network metrics in a separate window beneath the price chart: Average Clustering Coefficient (ACC), Transitivity, Normalized Average Path Length (NormAPL), Assortativity, and Average Betweenness. These five were selected because they capture distinct aspects of network topology: local clustering, global clustering, connectivity, degree correlation, and centrality.
Buffer and Plot Setup
#property indicator_separate_window #property indicator_buffers 5 #property indicator_plots 5 #property indicator_label1 "ACC" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 #property indicator_label2 "Transitivity" #property indicator_type2 DRAW_LINE #property indicator_color2 clrLimeGreen #property indicator_width2 2 #property indicator_label3 "NormAPL" #property indicator_type3 DRAW_LINE #property indicator_color3 clrOrange #property indicator_width3 2 #property indicator_label4 "Assortativity" #property indicator_type4 DRAW_LINE #property indicator_color4 clrViolet #property indicator_width4 1 #property indicator_label5 "AvgBetweenness" #property indicator_type5 DRAW_LINE #property indicator_color5 clrRed #property indicator_width5 1
Five buffers, five plots. The color scheme follows the series convention: blue and green for the primary metrics (ACC and Transitivity), orange for the path metric, violet and red for the secondary metrics. The width is 2 for the three most important metrics and 1 for the supplementary ones.
Input Parameters
#include <RQA\RQA.mqh> input int InpWindowSize = 50; input int InpStep = 1; input int InpEmbDim = 1; input int InpDelay = 1; input double InpEpsilon = 0.0; input double InpEpsilonParam = 0.05; input ENUM_EPSILON_METHOD InpEpsMethod = EPSILON_RR_TARGET; input ENUM_RQA_NORM InpNorm = RQA_NORM_EUCLIDEAN;
The parameters match the standard RQA indicator from the first article. The default configuration uses auto-epsilon targeting 5% recurrence rate. When InpEpsilon is 0.0 (the default), the indicator calls CRQAEpsilon::Select() on the full price series to determine the threshold. When a positive value is provided, it uses that as a fixed epsilon.
APL Normalization
Raw average path length is measured in hops (edges). For a network with N nodes, the maximum possible shortest path is N-1. To make APL comparable across different window sizes, the indicator normalizes it by dividing by (N-1), where N is the number of embedded vectors in each window. This puts NormAPL in the range [0, 1], where 0 would mean all nodes are directly connected and 1 would mean the network is a linear chain.
int embN = InpWindowSize - (InpEmbDim - 1) * InpDelay; double normFactor = (embN > 1) ? 1.0 / (embN - 1) : 1.0; //--- Inside the results loop: BufferNormAPL[bar] = results[k].metrics.AvgPathLength * normFactor;
This normalization is applied only to the APL buffer. All other metrics are plotted at their natural scale. ACC and Transitivity are already in [0, 1] by definition. Assortativity ranges from [-1, 1]. Betweenness is normalized to [0, 1] during computation.
The OnCalculate Logic
The indicator uses incremental computation. On a full recalculation (first load), it clears all buffers, runs CRNAWindow on the entire price series, and maps results to chart bars. On subsequent ticks, it computes only the tail windows covering new bars.
int OnCalculate(const int rates_total, const int prev_calculated, ...) { if(rates_total < InpWindowSize + 10) return 0; if(prev_calculated == rates_total) return rates_total; bool fullRecalc = (prev_calculated == 0); int computeFrom; if(fullRecalc) { computeFrom = 0; ArrayInitialize(BufferACC, EMPTY_VALUE); ArrayInitialize(BufferTrans, EMPTY_VALUE); ArrayInitialize(BufferNormAPL, EMPTY_VALUE); ArrayInitialize(BufferAssort, EMPTY_VALUE); ArrayInitialize(BufferBetween, EMPTY_VALUE); } else computeFrom = MathMax(0, prev_calculated - InpWindowSize); //--- Setup and run CRNAWindow CRNAWindow win; win.SetWindow(InpWindowSize, InpStep); win.SetEmbedding(InpEmbDim, InpDelay); win.SetNorm(InpNorm); //--- Auto-epsilon from full series if(InpEpsilon > 0.0) win.SetEpsilon(InpEpsilon); else { double fullSeries[]; ArrayResize(fullSeries, rates_total); for(int i = 0; i < rates_total; i++) fullSeries[i] = close[i]; double autoEps = CRQAEpsilon::Select(fullSeries, rates_total, InpEpsMethod, InpEpsilonParam); win.SetEpsilon(autoEps); } //--- Extract prices and run int computeLen = rates_total - computeFrom; double prices[]; ArrayResize(prices, computeLen); for(int i = 0; i < computeLen; i++) prices[i] = close[computeFrom + i]; SRNAWindowResult results[]; if(!win.Run(prices, computeLen, results)) return prev_calculated; //--- Map results to chart bars int nRes = ArraySize(results); for(int k = 0; k < nRes; k++) { int bar = results[k].barIndex + computeFrom + InpWindowSize - 1; if(bar >= 0 && bar < rates_total) { BufferACC[bar] = results[k].metrics.AvgClustering; BufferTrans[bar] = results[k].metrics.Transitivity; BufferNormAPL[bar] = results[k].metrics.AvgPathLength * normFactor; BufferAssort[bar] = results[k].metrics.Assortativity; BufferBetween[bar] = results[k].metrics.AvgBetweenness; } } return rates_total; }
Results are assigned to the last bar of each window (barIndex + computeFrom + InpWindowSize - 1), consistent with the bar assignment convention used in all previous indicators in this series. The short name displayed in the indicator window includes the window size, embedding dimension, and delay: RNA(W=50, m=1, tau=1).

Fig. 5. The RNA indicator settings dialog showing epsilon, window size, embedding parameters, and distance norm
The JRNA Indicator
JRNA_Indicator.mq5 extends the network analysis to two symbols. It produces the same five metrics (ACC, Transitivity, NormAPL, Assortativity, AvgBetweenness) but computes them from the joint recurrence network of the chart symbol and a user-specified second symbol. Like the CRQA indicator from the second article, it includes timestamp alignment, built-in normalization, and incremental computation.
Input Parameters and Normalization
enum ENUM_JRNA_NORMALIZE { JRNA_NORM_NONE = 0, // None (raw prices) JRNA_NORM_ZSCORE = 1, // Z-Score (recommended for cross-symbol) JRNA_NORM_RETURNS = 2 // Log Returns }; input string InpSymbolY = "GBPUSD"; input int InpWindowSize = 50; input int InpStep = 1; input int InpEmbDim = 1; input int InpDelay = 1; input double InpEpsilon = 0.5; input ENUM_RQA_NORM InpNorm = RQA_NORM_EUCLIDEAN; input ENUM_JRNA_NORMALIZE InpNormalize = JRNA_NORM_RETURNS;
The normalization enum matches the one used in the CRQA indicator. The default mode, JRNA_NORM_RETURNS, converts prices to log returns and then z-scores the result. This makes the epsilon parameter interpretable in standard-deviation units regardless of the instruments' price levels. An epsilon of 0.5 means "within half a standard deviation of the normalized series." This is the same default used by the CRQA and JRQA indicators. Note that unlike the single-series RNA indicator, the JRNA indicator uses a single shared epsilon for both series. Because both series are normalized to the same scale (zero mean, unit variance), a single epsilon is appropriate.
Timestamp Alignment
The alignment logic is identical to the CRQA and JRQA indicators. It uses CopyTime and CopyClose to fetch the second symbol's data, then merge-joins both datetime arrays to find bars where both symbols have data at the exact same timestamp. Only matched bars are included in the analysis. The g_barMap array maps aligned indices back to chart bar positions for plotting.
The OnCalculate Logic
On a full recalculation, the indicator aligns prices, applies normalization, clears all buffers, creates a CJRNAWindow, runs it on the aligned and normalized data, and maps results back to chart bars. The incremental path realigns and renormalizes the full history (because normalization depends on global statistics) but computes only the tail windows.
if(fullRecalc) { if(!AlignPrices(time, close, rates_total)) { Print("JRNA_Indicator: alignment failed"); return 0; } ApplyNormalization(); ArrayInitialize(BufferACC, EMPTY_VALUE); ArrayInitialize(BufferTrans, EMPTY_VALUE); ArrayInitialize(BufferNormAPL, EMPTY_VALUE); ArrayInitialize(BufferAssort, EMPTY_VALUE); ArrayInitialize(BufferBetween, EMPTY_VALUE); CJRNAWindow win; SetupWindow(win); SRNAWindowResult results[]; if(!win.Run(g_pricesX, g_validCount, g_pricesY, g_validCount, results)) { Print("JRNA_Indicator: Run() failed"); return 0; } int embN = InpWindowSize - (InpEmbDim - 1) * InpDelay; double normFactor = (embN > 1) ? 1.0 / (embN - 1) : 1.0; int nRes = ArraySize(results); for(int k = 0; k < nRes; k++) { int lastValid = results[k].barIndex + InpWindowSize - 1; if(lastValid < g_validCount) { int bar = g_barMap[lastValid]; BufferACC[bar] = results[k].metrics.AvgClustering; BufferTrans[bar] = results[k].metrics.Transitivity; BufferNormAPL[bar] = results[k].metrics.AvgPathLength * normFactor; BufferAssort[bar] = results[k].metrics.Assortativity; BufferBetween[bar] = results[k].metrics.AvgBetweenness; } } }
The bar mapping uses g_barMap[lastValid] to translate the aligned data index back to the actual chart bar position, exactly as in the CRQA and JRQA indicators. Results are assigned to the last bar of each window. The short name includes both symbols: JRNA(EURUSD & GBPUSD, W=50).

Fig. 6. The JRNA indicator settings dialog showing the second symbol, epsilon, normalization mode, and embedding parameters

Fig. 7. The JRNA indicator comparing GBPUSD with EURUSD, plotting network metrics derived from rolling joint recurrence matrices
What's Next
The library now covers four perspectives on recurrence: single-series RQA (line counting), cross-recurrence CRQA (state-space comparison between two series), joint recurrence JRQA (simultaneous self-recurrence in two systems), and now recurrence network analysis RNA (graph topology of recurrence patterns). Each perspective extracts different information from the same underlying data.
The natural next step is to put these tools to work in a trading context. Possible directions include:
- Using rolling network metrics as features in a regime classification model. ACC and Transitivity change character between trending, ranging, and volatile regimes.
- Combining RQA metrics (DET, LAM, ENTR) with RNA metrics (ACC, APL, Betweenness) from the same recurrence matrix to build a richer feature set that captures both line structure and graph topology.
- Comparing single-series RNA metrics with joint RNA metrics (JRNA) to detect when two instruments' network topologies diverge, signaling a breakdown in their shared dynamics.
- Using betweenness centrality spikes to identify transitional time points between market regimes, which may correspond to trade entry or exit signals.
- Monitoring assortativity changes to detect shifts in the market's internal correlation structure: positive assortativity (hub-hub connections) versus negative assortativity (hub-peripheral) may characterize different market phases.
The library was designed for extensibility. The CRNAMetrics engine accepts any square adjacency matrix through ComputeFromAdj(), so it can be combined with future recurrence matrix types without modification. Adding new graph metrics (eigenvector centrality, modularity, rich-club coefficient) would extend ComputeFromAdj() and SRNAResult without changing any existing class interfaces.
Conclusion
Recurrence Network Analysis reinterprets the recurrence matrix as a complex network and extracts graph-theoretic measures that capture structural properties invisible to traditional RQA line-counting. This article presented a complete MQL5 implementation consisting of five new files and one updated existing file:
- CRNAMetrics computes twelve graph metrics from any square adjacency matrix: degree statistics (AvgDegree, MaxDegree, DegreeStd), clustering (ACC, Transitivity), path metrics (APL, Diameter), centrality (AvgBetweenness, MaxBetweenness, AvgCloseness), and structural measures (Assortativity, Density). It uses BFS for shortest paths and the Brandes algorithm for betweenness centrality.
- CRNAWindow applies network analysis over a rolling window on a single time series, building a recurrence matrix per window and computing all twelve metrics.
- CJRNAWindow extends rolling network analysis to two series by building joint recurrence matrices per window, extracting their adjacency matrices, and passing them to CRNAMetrics.
- CRNA provides a high-level facade for single-series network analysis, chaining CRQAMatrix and CRNAMetrics with support for automatic epsilon selection.
- CJRNA provides a high-level facade for joint network analysis, chaining CJRQAMatrix and CRNAMetrics with support for separate epsilon values per series.
The two indicators (RNA_Indicator and JRNA_Indicator) plot five selected network metrics in real time: Average Clustering Coefficient, Transitivity, Normalized Average Path Length, Assortativity, and Average Betweenness. The JRNA indicator includes timestamp alignment and built-in normalization for cross-symbol analysis.
All modules are backward-compatible with the existing library. A single include of RQA.mqh gives access to everything: standard RQA, cross-RQA, joint-RQA, and now recurrence network analysis. The previous articles in this series cover the foundation: the first article for single-series RQA, the second for CRQA, and the third for JRQA. The ZIP file contains the full library code from previous articles.
| # | File name | Type | Description |
|---|---|---|---|
| 1 | RQA.mqh | Include | Updated main include file with CRNA and CJRNA facades added alongside existing CRQA, CCRQA, and CJRQA facades |
| 2 | RNAMetrics.mqh | Include | Graph metrics engine: degree, clustering, BFS, betweenness (Brandes), closeness, assortativity |
| 3 | RNAWindow.mqh | Include | Rolling window RNA for a single series (CPU-only) |
| 4 | JRNAWindow.mqh | Include | Rolling window JRNA for two series using joint recurrence matrices (CPU-only) |
| 5 | RNA_Indicator.mq5 | Indicator | Single-market network indicator plotting ACC, Transitivity, NormAPL, Assortativity, and AvgBetweenness |
| 6 | JRNA_Indicator.mq5 | Indicator | Two-market network indicator with timestamp alignment, normalization, and the same five network metrics |
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Exploring Regression Models for Causal Inference and Trading
How to Detect and Normalize Chart Objects in MQL5 (Part 2): Collecting and Structuring Data from Complex Analytical Objects
CSV Data Analysis (Part 1): CSV Export Engine for MQL5 Multi-Core Optimizations
MQL5 Trading Tools (Part 35): Adding Channel, Pitchfork, Gann, and Fibonacci Tools to the Canvas Drawing Layer
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use