217 lines
8.9 KiB
MQL5
217 lines
8.9 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| ar.mqh |
|
||
|
|
//| Copyright 2025, MetaQuotes Ltd. |
|
||
|
|
//| https://www.mql5.com |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||
|
|
#property link "https://www.mql5.com"
|
||
|
|
#include"..\..\np.mqh"
|
||
|
|
#include"..\..\Regression\OLS.mqh"
|
||
|
|
#include"mean.mqh"
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Information criteria enum for model selection optimization levels|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
enum ENUM_INFO_CRITERION
|
||
|
|
{
|
||
|
|
AIC=0, // Akaike Information Criterion: Penalizes parameters linearly (2*k)
|
||
|
|
BIC // Bayesian Information Criterion: Penalizes parameters logarithmically (k*ln(N)), favoring parsimony
|
||
|
|
};
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Bits view implementation: Deconstructs numeric vector values |
|
||
|
|
//| into custom byte-chunk integer representations for masking flags |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
matrix bitview(vector& data, int basis, int view)
|
||
|
|
{
|
||
|
|
if(!data.Size())
|
||
|
|
return matrix::Zeros(0,0);
|
||
|
|
|
||
|
|
// Allocate container workspace rows to match data size
|
||
|
|
matrix out = matrix::Zeros(data.Size(), basis / view);
|
||
|
|
int value = 0;
|
||
|
|
int rem = 0;
|
||
|
|
|
||
|
|
for(ulong i = 0; i < out.Rows(); ++i)
|
||
|
|
{
|
||
|
|
// Isolate the upper bit segment shifted by the designated view byte width block
|
||
|
|
value = int(fabs(data[i])) / int(pow(2, 8 * view));
|
||
|
|
|
||
|
|
// Calculate the modulus remainder mapping directly to the initial column index
|
||
|
|
rem = (int)MathMod(int(fabs(data[i])), int(pow(2, 8 * view)));
|
||
|
|
out[i, 0] = rem;
|
||
|
|
|
||
|
|
// Extract sub-byte chunks iteratively across columns until bit states reduce to 0
|
||
|
|
for(ulong j = 1; j < out.Cols(); ++j)
|
||
|
|
{
|
||
|
|
if(value)
|
||
|
|
{
|
||
|
|
out[i, j] = (value < int(pow(2, 8 * view))) ? value : MathMod(value, int(pow(2, 8 * view)));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
break; // Abort early if the remaining bit value is fully drained
|
||
|
|
|
||
|
|
value /= (int)pow(2, 8 * view); // Right shift bits context down to processing space
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Unpacks the byte-chunks from bitview into an explicit binary matrix|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
matrix unpackbits(matrix& bview, ulong bits)
|
||
|
|
{
|
||
|
|
if(!bits || bits < bview.Cols() || !bview.Cols())
|
||
|
|
return matrix::Zeros(0,0);
|
||
|
|
|
||
|
|
matrix out = matrix::Zeros(bview.Rows(), bits);
|
||
|
|
ulong factor = bits / bview.Cols();
|
||
|
|
ulong k = 0;
|
||
|
|
|
||
|
|
for(ulong i = 0; i < out.Rows(); ++i)
|
||
|
|
{
|
||
|
|
for(ulong j = 0; j < bview.Cols(); ++j)
|
||
|
|
{
|
||
|
|
ulong value = ulong(bview[i, j]);
|
||
|
|
// Extract individual bit states from right to left using the standard base-2 modulus operation
|
||
|
|
for(long k = 7; k > -1; --k)
|
||
|
|
{
|
||
|
|
if(value)
|
||
|
|
out[i, j * 8 + k] = MathMod(value, 2); // Map remainder (0 or 1) onto active coordinate flags
|
||
|
|
else
|
||
|
|
break; // Optimization fallback if structural bit allocations reach 0
|
||
|
|
|
||
|
|
value /= 2;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Autoregressive AR-X(p) optimal model lag order selection routine |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
vector ar_select_order(vector& endog, matrix& exog, ulong maxlag, bool use_constant=true, bool global_search = false, ENUM_INFO_CRITERION ic = BIC, ulong holdback=0, ulong period=0)
|
||
|
|
{
|
||
|
|
ulong nexog = exog.Cols();
|
||
|
|
|
||
|
|
// Configure data structural vectors into base optimization specs
|
||
|
|
ArchParameters modelparams;
|
||
|
|
modelparams.y = endog;
|
||
|
|
modelparams.x = exog;
|
||
|
|
modelparams.mean_constant = use_constant;
|
||
|
|
|
||
|
|
// Instantiate an AR-X linear structure to cleanly map out target regressors
|
||
|
|
ARX model;
|
||
|
|
if(!model.initialize(modelparams))
|
||
|
|
return vector::Zeros(0);
|
||
|
|
|
||
|
|
vector y = endog;
|
||
|
|
matrix x = model.get_regressors(); // Resolves internal matrix container layout containing all lags and raw variables
|
||
|
|
|
||
|
|
// Pin down the starting index offset for lag regressors inside the global variables space
|
||
|
|
ulong base_col = x.Cols() - nexog - maxlag;
|
||
|
|
|
||
|
|
vector sel = vector::Ones(x.Cols()); // Boolean selector mask tracking active features columns
|
||
|
|
vector lags[]; // Dynamic tracker mapping checked lag indexes variants
|
||
|
|
double ics[]; // Container recording evaluation metric scores output per loop step
|
||
|
|
int index[]; // Index tracker used for sorting sorting loops
|
||
|
|
matrix xx;
|
||
|
|
OLS ols;
|
||
|
|
|
||
|
|
// --- APPROACH A: Nested Sequential Search (Nested Lag Sets) ---
|
||
|
|
if(!global_search)
|
||
|
|
{
|
||
|
|
// Blank out candidate lag features space completely prior to sequential loop expansion entries
|
||
|
|
np::fillVector(sel, 0.0, long(base_col), long(base_col + maxlag));
|
||
|
|
ArrayResize(lags, int(maxlag + 1));
|
||
|
|
|
||
|
|
// Incrementally expand the lookback length from 0 to maxlag (testing nested subsets: e.g., [1], [1,2], [1,2,3])
|
||
|
|
for(ulong i = 0; i < maxlag + 1; ++i)
|
||
|
|
{
|
||
|
|
index.Push(int(i));
|
||
|
|
np::fillVector(sel, 1., long(base_col), long(base_col + i)); // Activate columns up to lag step i
|
||
|
|
|
||
|
|
if(!sel.Sum())
|
||
|
|
continue;
|
||
|
|
|
||
|
|
// Filter out deactivated components to form a clean regression payload matrix
|
||
|
|
xx = matrix::Zeros(y.Size(), ulong(sel.Sum()));
|
||
|
|
for(ulong k = 0, kk = 0; k < sel.Size(); ++k)
|
||
|
|
{
|
||
|
|
if(sel[k])
|
||
|
|
xx.Col(x.Col(k), kk++);
|
||
|
|
}
|
||
|
|
|
||
|
|
lags[i] = np::range(int(i + 1), 1); // Record sequentially assigned active lag steps arrays
|
||
|
|
|
||
|
|
// Fit the candidate linear model using standard Ordinary Least Squares
|
||
|
|
if(!ols.Fit(y, xx))
|
||
|
|
return vector::Zeros(0);
|
||
|
|
|
||
|
|
// Record chosen statistical criteria optimization score metric
|
||
|
|
ics.Push(ic == AIC ? ols.Aic() : ols.Bic());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// --- APPROACH B: Combinatorial Global Search (All Possible Subsets) ---
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Generate a sequence of integers up to 2^maxlag representing every possible lag combination
|
||
|
|
vector b = np::arange(ulong(pow(2, maxlag)));
|
||
|
|
matrix bview = bitview(b, 4, 1);
|
||
|
|
matrix bits = unpackbits(bview, 32); // Convert integers to a dense binary mask grid matrix
|
||
|
|
matrix bb;
|
||
|
|
|
||
|
|
// Reverse bit columns alignment chunks to correct for byte ordering anomalies
|
||
|
|
for(ulong i = 0; i < 4; ++i)
|
||
|
|
{
|
||
|
|
bb = np::sliceMatrixCols(bits, long(8 * i), long(8 * (i + 1)));
|
||
|
|
np::reverseMatrixCols(bb);
|
||
|
|
np::matrixCopyCols(bits, bb, long(8 * i), long(8 * (i + 1)));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Extract the isolated lookback target validation search mask space matrix
|
||
|
|
matrix masks = np::sliceMatrixCols(bits, 0, long(maxlag));
|
||
|
|
ArrayResize(lags, int(masks.Rows()));
|
||
|
|
|
||
|
|
// Loop across each unique combinatorial combination pattern row
|
||
|
|
for(ulong i = 0; i < masks.Rows(); ++i)
|
||
|
|
{
|
||
|
|
index.Push(int(i));
|
||
|
|
// Apply the unique combinatorial configuration row mask to the feature selector vector
|
||
|
|
np::vectorCopy(sel, masks.Row(i), long(base_col), long(base_col + maxlag));
|
||
|
|
|
||
|
|
if(!sel.Sum())
|
||
|
|
continue; // Skip evaluation if no lag components are selected
|
||
|
|
|
||
|
|
xx = matrix::Zeros(y.Size(), ulong(sel.Sum()));
|
||
|
|
|
||
|
|
// Extract valid active columns to prepare the current iteration matrix setup
|
||
|
|
for(ulong k = 0, kk = 0; k < sel.Size(); ++k)
|
||
|
|
{
|
||
|
|
if(sel[k])
|
||
|
|
xx.Col(x.Col(k), kk++);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Allocate a custom tracking vector mapping out exactly which lags are active in this trial
|
||
|
|
lags[i] = vector::Zeros(ulong(masks.Row(i).Sum()));
|
||
|
|
for(ulong j = 0, k = 0; j < lags[i].Size(); ++j)
|
||
|
|
if(masks[i, j])
|
||
|
|
lags[i][k++] = double(j + 1);
|
||
|
|
|
||
|
|
// Execute OLS regression step
|
||
|
|
if(!ols.Fit(y, xx))
|
||
|
|
return vector::Zeros(0);
|
||
|
|
|
||
|
|
ics.Push(ic == AIC ? ols.Aic() : ols.Bic());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- Step 3: Identify and Return the Globally Optimal Model Layout ---
|
||
|
|
// Sort evaluation log score outputs in ascending order (lower values signal optimal balance)
|
||
|
|
MathQuickSortAscending(ics, index, 0, int(ics.Size() - 1));
|
||
|
|
|
||
|
|
// Return the lag profile matching the lowest information criteria score value (index position [0])
|
||
|
|
return lags[index[0]];
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|