Article-22610-Joint-Recurre.../Indicators/RQA/JRQA_Indicator.mq5

334 lines
11 KiB
MQL5
Raw Permalink Normal View History

2026-05-30 13:14:34 +05:00
//+------------------------------------------------------------------+
//| JRQA_Indicator.mq5 |
//| Joint-RQA Indicator using RQA Library |
//| Plots JRR, JDET, JLAM, JENTR as buffers in separate window |
//| Measures simultaneous recurrence between two symbols. |
//+------------------------------------------------------------------+
#property copyright "Hammad Dilber"
#property version "1.00"
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots 5
#property indicator_label1 "JRR"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2
#property indicator_label2 "JDET"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrLimeGreen
#property indicator_width2 2
#property indicator_label3 "JLAM"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrOrange
#property indicator_width3 2
#property indicator_label4 "JENTR"
#property indicator_type4 DRAW_LINE
#property indicator_color4 clrViolet
#property indicator_width4 1
#property indicator_label5 "JTREND"
#property indicator_type5 DRAW_LINE
#property indicator_color5 clrRed
#property indicator_width5 1
#include <RQA\RQA.mqh>
enum ENUM_JRQA_NORMALIZE
{
JRQA_NORM_NONE = 0, // None (raw prices)
JRQA_NORM_ZSCORE = 1, // Z-Score (recommended for cross-symbol)
JRQA_NORM_RETURNS = 2 // Log Returns
};
//--- Inputs
input string InpSymbolY = "GBPUSD"; // Second symbol to compare with
input int InpWindowSize = 50; // Rolling window size
input int InpStep = 1; // Step between windows
input int InpEmbDim = 1; // Embedding dimension
input int InpDelay = 1; // Time delay (tau)
input double InpEpsilon = 0.5; // Epsilon threshold (shared after normalization)
input ENUM_RQA_NORM InpNorm = RQA_NORM_EUCLIDEAN; // Distance norm
input int InpMinDiag = 2; // Min diagonal line length
input int InpMinVert = 2; // Min vertical line length
input ENUM_JRQA_NORMALIZE InpNormalize = JRQA_NORM_RETURNS; // Series normalization
//--- Buffers
double BufferJRR[];
double BufferJDET[];
double BufferJLAM[];
double BufferJENTR[];
double BufferJTREND[];
//--- Cached aligned data
double g_pricesX[];
double g_pricesY[];
int g_barMap[];
int g_validCount;
int g_lastAligned;
//+------------------------------------------------------------------+
//| Initialize indicator buffers and validate second symbol |
//+------------------------------------------------------------------+
int OnInit()
{
SetIndexBuffer(0, BufferJRR, INDICATOR_DATA);
SetIndexBuffer(1, BufferJDET, INDICATOR_DATA);
SetIndexBuffer(2, BufferJLAM, INDICATOR_DATA);
SetIndexBuffer(3, BufferJENTR, INDICATOR_DATA);
SetIndexBuffer(4, BufferJTREND, INDICATOR_DATA);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(4, PLOT_EMPTY_VALUE, EMPTY_VALUE);
IndicatorSetString(INDICATOR_SHORTNAME,
StringFormat("JRQA(%s & %s, W=%d)",
_Symbol, InpSymbolY, InpWindowSize));
g_validCount = 0;
g_lastAligned = 0;
if(InpSymbolY != _Symbol)
{
bool selected = SymbolSelect(InpSymbolY, true);
if(!selected)
{
PrintFormat("JRQA_Indicator: symbol %s not found in Market Watch", InpSymbolY);
return INIT_FAILED;
}
}
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Bulk-align second symbol's close prices to chart bars |
//+------------------------------------------------------------------+
bool AlignPrices(const datetime &timeX[], const double &closeX[],
int rates_total)
{
datetime timeY[];
double closeY[];
int copiedT = CopyTime(InpSymbolY, _Period, 0, rates_total, timeY);
if(copiedT <= 0)
return false;
int copiedC = CopyClose(InpSymbolY, _Period, 0, rates_total, closeY);
if(copiedC != copiedT)
return false;
ArrayResize(g_pricesX, rates_total);
ArrayResize(g_pricesY, rates_total);
ArrayResize(g_barMap, rates_total);
int jj = 0;
g_validCount = 0;
for(int ix = 0; ix < rates_total; ix++)
{
while(jj < copiedT && timeY[jj] < timeX[ix])
jj++;
if(jj < copiedT && timeY[jj] == timeX[ix])
{
g_pricesX[g_validCount] = closeX[ix];
g_pricesY[g_validCount] = closeY[jj];
g_barMap[g_validCount] = ix;
g_validCount++;
}
}
if(g_validCount > 0)
{
ArrayResize(g_pricesX, g_validCount);
ArrayResize(g_pricesY, g_validCount);
ArrayResize(g_barMap, g_validCount);
}
g_lastAligned = rates_total;
return g_validCount >= InpWindowSize;
}
//+------------------------------------------------------------------+
//| Normalize an array in-place using z-score |
//+------------------------------------------------------------------+
void NormalizeZScore(double &arr[], int len)
{
if(len < 2) return;
double sum = 0, sumSq = 0;
for(int i = 0; i < len; i++)
{ sum += arr[i]; sumSq += arr[i] * arr[i]; }
double mean = sum / len;
double var = sumSq / len - mean * mean;
double sd = (var > 1e-20) ? MathSqrt(var) : 1.0;
for(int i = 0; i < len; i++)
arr[i] = (arr[i] - mean) / sd;
}
//+------------------------------------------------------------------+
//| Convert to log returns in-place, shrinks array by 1 |
//+------------------------------------------------------------------+
int ToLogReturns(double &arr[], int len)
{
if(len < 2) return 0;
for(int i = 0; i < len - 1; i++)
arr[i] = (arr[i + 1] > 0 && arr[i] > 0) ? MathLog(arr[i + 1] / arr[i]) : 0.0;
int newLen = len - 1;
ArrayResize(arr, newLen);
return newLen;
}
//+------------------------------------------------------------------+
//| Apply normalization to aligned price arrays |
//+------------------------------------------------------------------+
void ApplyNormalization()
{
if(InpNormalize == JRQA_NORM_ZSCORE)
{
NormalizeZScore(g_pricesX, g_validCount);
NormalizeZScore(g_pricesY, g_validCount);
}
else if(InpNormalize == JRQA_NORM_RETURNS)
{
int newLenX = ToLogReturns(g_pricesX, g_validCount);
int newLenY = ToLogReturns(g_pricesY, g_validCount);
for(int i = 0; i < newLenX; i++)
g_barMap[i] = g_barMap[i + 1];
g_validCount = MathMin(newLenX, newLenY);
ArrayResize(g_pricesX, g_validCount);
ArrayResize(g_pricesY, g_validCount);
ArrayResize(g_barMap, g_validCount);
NormalizeZScore(g_pricesX, g_validCount);
NormalizeZScore(g_pricesY, g_validCount);
}
}
//+------------------------------------------------------------------+
//| Setup a CJRQAWindow with current input parameters |
//+------------------------------------------------------------------+
void SetupWindow(CJRQAWindow &win)
{
win.SetWindow(InpWindowSize, InpStep);
win.SetEpsilon(InpEpsilon);
win.SetEmbedding(InpEmbDim, InpDelay);
win.SetNorm(InpNorm);
win.SetMinLines(InpMinDiag, InpMinVert);
}
//+------------------------------------------------------------------+
//| Main calculation: align series, normalize, run JRQA windows |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(rates_total < InpWindowSize + 10)
return 0;
if(prev_calculated == rates_total)
return rates_total;
bool fullRecalc = (prev_calculated == 0 || g_lastAligned != prev_calculated);
if(fullRecalc)
{
if(!AlignPrices(time, close, rates_total))
{
PrintFormat("JRQA_Indicator: alignment failed for %s (need %d aligned)",
InpSymbolY, InpWindowSize);
return 0;
}
ApplyNormalization();
ArrayInitialize(BufferJRR, EMPTY_VALUE);
ArrayInitialize(BufferJDET, EMPTY_VALUE);
ArrayInitialize(BufferJLAM, EMPTY_VALUE);
ArrayInitialize(BufferJENTR, EMPTY_VALUE);
ArrayInitialize(BufferJTREND, EMPTY_VALUE);
CJRQAWindow win;
SetupWindow(win);
SJRQAWindowResult results[];
if(!win.Run(g_pricesX, g_validCount, g_pricesY, g_validCount, results))
{
Print("JRQA_Indicator: Run() failed");
return 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];
BufferJRR[bar] = results[k].metrics.JRR;
BufferJDET[bar] = results[k].metrics.JDET;
BufferJLAM[bar] = results[k].metrics.JLAM;
BufferJENTR[bar] = results[k].metrics.JENTR;
BufferJTREND[bar] = results[k].metrics.JTREND;
}
}
}
else
{
if(!AlignPrices(time, close, rates_total))
{
Print("JRQA_Indicator: cannot align data for ", InpSymbolY);
return prev_calculated;
}
ApplyNormalization();
int newWindows = (rates_total - prev_calculated) / InpStep + InpWindowSize / InpStep + 1;
int startWindow = MathMax(0, g_validCount - InpWindowSize - newWindows * InpStep);
int tailLen = g_validCount - startWindow;
if(tailLen < InpWindowSize)
return rates_total;
double tailX[], tailY[];
ArrayResize(tailX, tailLen);
ArrayResize(tailY, tailLen);
for(int i = 0; i < tailLen; i++)
{
tailX[i] = g_pricesX[startWindow + i];
tailY[i] = g_pricesY[startWindow + i];
}
CJRQAWindow win;
SetupWindow(win);
SJRQAWindowResult results[];
if(!win.Run(tailX, tailLen, tailY, tailLen, results))
return rates_total;
int nRes = ArraySize(results);
for(int k = 0; k < nRes; k++)
{
int lastValid = results[k].barIndex + InpWindowSize - 1 + startWindow;
if(lastValid < g_validCount)
{
int bar = g_barMap[lastValid];
BufferJRR[bar] = results[k].metrics.JRR;
BufferJDET[bar] = results[k].metrics.JDET;
BufferJLAM[bar] = results[k].metrics.JLAM;
BufferJENTR[bar] = results[k].metrics.JENTR;
BufferJTREND[bar] = results[k].metrics.JTREND;
}
}
}
return rates_total;
}