//+------------------------------------------------------------------+ //| 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]]; } //+------------------------------------------------------------------+