Files
mql5/Include/MyIncludes/MLRegressionTrend_Calculator.mqh
T

489 lines
17 KiB
Plaintext

//+------------------------------------------------------------------+
//| MLRegressionTrend_Calculator.mqh |
//| Headless Machine Learning Engine for RANSAC Regression |
//| Strict 100% Equivalent of LuxAlgo Pine Script |
//| Copyright 2026, xxxxxxxx|
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, xxxxxxxx"
#property version "2.20" // Enterprise Edition: Multi-Asset Pip & Point Normalized Inlier Thresholds
#property description "Mathematical engine for RANSAC Machine Learning Linear Regression Trend."
#ifndef ML_REGRESSION_TREND_CALCULATOR_MQH
#define ML_REGRESSION_TREND_CALCULATOR_MQH
#include <MyIncludes\HeikinAshi_Tools.mqh>
//--- Allowed Error Type Options (Pip-Normalized for Multi-Asset Uniformity)
#ifndef ENUM_ERROR_TYPE_DEFINED
#define ENUM_ERROR_TYPE_DEFINED
enum ENUM_ERROR_TYPE
{
ERROR_AUTO, // Auto: Multiplier of Average Candle Body (Scale-Invariant)
ERROR_FIXED_PIPS, // Fixed Pips: Auto-normalized across Forex, Metals & Indices
ERROR_FIXED_PRICE // Fixed Price: Raw Price Units (for Stocks/Futures)
};
#endif
//--- Output Structure for RANSAC Regression
struct SRansacResult
{
double slope; // a: Best regression slope
double intercept; // b: Best regression intercept
double threshold; // Dynamic inlier distance threshold
double rmse; // Root Mean Square Error (on ALL points, as in Pine Script)
double channel_width; // rmse * widthMult
int inliers_count; // Number of consensus inliers found
int total_points; // Window sample length (length)
double y_start; // Line start value (at n - length + 1)
double y_end; // Line end value (at n)
datetime t_start; // Start bar timestamp
datetime t_end; // End bar timestamp
};
//+==================================================================+
//| CLASS: CMLRegressionTrend_Calculator |
//+==================================================================+
class CMLRegressionTrend_Calculator
{
private:
int m_length;
double m_width_mult;
int m_min_inliers;
ENUM_ERROR_TYPE m_error_type;
double m_min_error;
int m_max_iter;
ENUM_APPLIED_PRICE_HA_ALL m_source_type;
// Persistent Price & Accumulator Buffers
double m_price[];
double m_cum_body[]; // Persistent ta.cum(abs(close - open)) equivalent
// Working Buffers for Local Window
double m_win_y[];
bool m_best_inliers_mask[];
bool m_curr_mask[];
datetime m_win_times[];
// Heikin Ashi Buffers
double m_ha_open[], m_ha_high[], m_ha_low[], m_ha_close[];
CHeikinAshi_Calculator m_ha_engine;
// Processed Output
SRansacResult m_result;
bool PrepareData(const int rates_total, const int start_index,
const double &open[], const double &high[],
const double &low[], const double &close[]);
public:
CMLRegressionTrend_Calculator(void);
~CMLRegressionTrend_Calculator(void);
bool Init(const int length, const double width_mult, const int min_inliers,
const ENUM_ERROR_TYPE error_type, const double min_error,
const int max_iter, const ENUM_APPLIED_PRICE_HA_ALL source);
bool Execute(const int rates_total, const int prev_calculated,
const datetime &time[], const double &open[],
const double &high[], const double &low[], const double &close[]);
bool GetResult(SRansacResult &out_res) const;
bool IsInlier(const int window_k) const;
double GetWindowPrice(const int window_k) const;
datetime GetWindowTime(const int window_k) const;
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CMLRegressionTrend_Calculator::CMLRegressionTrend_Calculator(void) :
m_length(100),
m_width_mult(2.0),
m_min_inliers(10),
m_error_type(ERROR_AUTO),
m_min_error(1.0),
m_max_iter(150),
m_source_type(PRICE_CLOSE_STD)
{
ArraySetAsSeries(m_price, false);
ArraySetAsSeries(m_cum_body, false);
ArraySetAsSeries(m_win_y, false);
ArraySetAsSeries(m_best_inliers_mask, false);
ArraySetAsSeries(m_curr_mask, false);
ArraySetAsSeries(m_win_times, false);
ArraySetAsSeries(m_ha_open, false);
ArraySetAsSeries(m_ha_high, false);
ArraySetAsSeries(m_ha_low, false);
ArraySetAsSeries(m_ha_close, false);
ZeroMemory(m_result);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLRegressionTrend_Calculator::~CMLRegressionTrend_Calculator(void)
{
ArrayFree(m_price);
ArrayFree(m_cum_body);
ArrayFree(m_win_y);
ArrayFree(m_best_inliers_mask);
ArrayFree(m_curr_mask);
ArrayFree(m_win_times);
ArrayFree(m_ha_open);
ArrayFree(m_ha_high);
ArrayFree(m_ha_low);
ArrayFree(m_ha_close);
}
//+------------------------------------------------------------------+
//| Initialization |
//+------------------------------------------------------------------+
bool CMLRegressionTrend_Calculator::Init(const int length, const double width_mult, const int min_inliers,
const ENUM_ERROR_TYPE error_type, const double min_error,
const int max_iter, const ENUM_APPLIED_PRICE_HA_ALL source)
{
m_length = (length < 5) ? 5 : length;
m_width_mult = (width_mult <= 0.0) ? 2.0 : width_mult;
m_min_inliers = (min_inliers < 1) ? 1 : min_inliers;
m_error_type = error_type;
m_min_error = (min_error <= 0.0) ? 1.0 : min_error;
m_max_iter = MathMin(MathMax(max_iter, 50), 2000);
m_source_type = source;
return true;
}
//+------------------------------------------------------------------+
//| Prepare Price Series & ta.cum Running Accumulator |
//+------------------------------------------------------------------+
bool CMLRegressionTrend_Calculator::PrepareData(const int rates_total, const int start_index,
const double &open[], const double &high[],
const double &low[], const double &close[])
{
if(ArraySize(m_price) != rates_total)
{
ArrayResize(m_price, rates_total, 2048);
ArrayResize(m_cum_body, rates_total, 2048);
ArraySetAsSeries(m_price, false);
ArraySetAsSeries(m_cum_body, false);
}
bool is_ha = (m_source_type <= PRICE_HA_CLOSE);
if(is_ha)
{
if(ArraySize(m_ha_open) != rates_total)
{
ArrayResize(m_ha_open, rates_total, 2048);
ArrayResize(m_ha_high, rates_total, 2048);
ArrayResize(m_ha_low, rates_total, 2048);
ArrayResize(m_ha_close, rates_total, 2048);
ArraySetAsSeries(m_ha_open, false);
ArraySetAsSeries(m_ha_high, false);
ArraySetAsSeries(m_ha_low, false);
ArraySetAsSeries(m_ha_close, false);
}
m_ha_engine.Calculate(rates_total, start_index, open, high, low, close,
m_ha_open, m_ha_high, m_ha_low, m_ha_close);
for(int i = start_index; i < rates_total; i++)
{
switch(m_source_type)
{
case PRICE_HA_OPEN:
m_price[i] = m_ha_open[i];
break;
case PRICE_HA_HIGH:
m_price[i] = m_ha_high[i];
break;
case PRICE_HA_LOW:
m_price[i] = m_ha_low[i];
break;
case PRICE_HA_MEDIAN:
m_price[i] = (m_ha_high[i] + m_ha_low[i]) * 0.5;
break;
case PRICE_HA_TYPICAL:
m_price[i] = (m_ha_high[i] + m_ha_low[i] + m_ha_close[i]) * 0.333333333333;
break;
case PRICE_HA_WEIGHTED:
m_price[i] = (m_ha_high[i] + m_ha_low[i] + 2.0 * m_ha_close[i]) * 0.25;
break;
case PRICE_HA_CLOSE:
default:
m_price[i] = m_ha_close[i];
break;
}
double body = MathAbs(close[i] - open[i]);
m_cum_body[i] = (i == 0) ? body : (m_cum_body[i - 1] + body);
}
}
else
{
for(int i = start_index; i < rates_total; i++)
{
switch(m_source_type)
{
case PRICE_OPEN_STD:
m_price[i] = open[i];
break;
case PRICE_HIGH_STD:
m_price[i] = high[i];
break;
case PRICE_LOW_STD:
m_price[i] = low[i];
break;
case PRICE_MEDIAN_STD:
m_price[i] = (high[i] + low[i]) * 0.5;
break;
case PRICE_TYPICAL_STD:
m_price[i] = (high[i] + low[i] + close[i]) * 0.333333333333;
break;
case PRICE_WEIGHTED_STD:
m_price[i] = (high[i] + low[i] + 2.0 * close[i]) * 0.25;
break;
case PRICE_CLOSE_STD:
default:
m_price[i] = close[i];
break;
}
double body = MathAbs(close[i] - open[i]);
m_cum_body[i] = (i == 0) ? body : (m_cum_body[i - 1] + body);
}
}
return true;
}
//+------------------------------------------------------------------+
//| Core Execution: Exact LuxAlgo Pine Script Algorithm |
//+------------------------------------------------------------------+
bool CMLRegressionTrend_Calculator::Execute(const int rates_total, const int prev_calculated,
const datetime &time[], const double &open[],
const double &high[], const double &low[], const double &close[])
{
int len = m_length;
if(rates_total < len)
return false;
if(ArraySize(m_win_y) != len)
{
ArrayResize(m_win_y, len);
ArrayResize(m_best_inliers_mask, len);
ArrayResize(m_curr_mask, len);
ArrayResize(m_win_times, len);
ArraySetAsSeries(m_win_y, false);
ArraySetAsSeries(m_best_inliers_mask, false);
ArraySetAsSeries(m_curr_mask, false);
ArraySetAsSeries(m_win_times, false);
}
int start_prep = (prev_calculated > 0) ? (prev_calculated - 1) : 0;
if(!PrepareData(rates_total, start_prep, open, high, low, close))
return false;
int win_start = rates_total - len;
// 1. Calculate Threshold with Normalized Pip/Tick Awareness
double threshold = m_min_error;
if(m_error_type == ERROR_AUTO)
{
double lifetime_avg_body = m_cum_body[rates_total - 1] / (double)rates_total;
threshold = lifetime_avg_body * m_min_error;
}
else
if(m_error_type == ERROR_FIXED_PIPS)
{
// Convert Pips to physical Price Units across all instruments
double pip_size = (_Digits == 3 || _Digits == 5) ? 10.0 * _Point : _Point;
threshold = m_min_error * pip_size;
}
else // ERROR_FIXED_PRICE
{
threshold = m_min_error; // Raw price units
}
if(threshold <= 0.0)
threshold = _Point;
// 2. Mirror local window data
for(int k = 0; k < len; k++)
{
m_win_y[k] = m_price[win_start + k];
m_win_times[k] = time[win_start + k];
}
// 3. DETERMINISTIC PRNG SEEDING:
uint deterministic_seed = (uint)time[win_start] ^ 0xA5A5A5A5;
MathSrand(deterministic_seed);
// 4. Exact LuxAlgo RANSAC Loop
int size = m_min_inliers;
double best_a = 0.0, best_b = 0.0;
bool model_found = false;
ArrayInitialize(m_best_inliers_mask, false);
for(int iter = 0; iter < m_max_iter; iter++)
{
int idx1 = (int)(MathRand() % len);
int idx2 = (int)(MathRand() % len);
if(idx1 == idx2)
continue;
double y1 = m_win_y[idx1];
double y2 = m_win_y[idx2];
double x1 = (double)idx1;
double x2 = (double)idx2;
double a_cand = (y2 - y1) / (x2 - x1);
double b_cand = y1 - a_cand * x1;
int inliers_count = 0;
for(int k = 0; k < len; k++)
{
double pred = a_cand * (double)k + b_cand;
if(MathAbs(m_win_y[k] - pred) < threshold)
{
m_curr_mask[k] = true;
inliers_count++;
}
else
{
m_curr_mask[k] = false;
}
}
if(inliers_count >= size)
{
double sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0;
for(int k = 0; k < len; k++)
{
if(m_curr_mask[k])
{
double x_val = (double)k;
double y_val = m_win_y[k];
sum_x += x_val;
sum_y += y_val;
sum_xy += x_val * y_val;
sum_x2 += x_val * x_val;
}
}
double m_count = (double)inliers_count;
double var_x = sum_x2 - (sum_x * sum_x) / m_count;
if(var_x > 1.0e-12)
{
double cov_xy = sum_xy - (sum_x * sum_y) / m_count;
best_a = cov_xy / var_x;
best_b = (sum_y - best_a * sum_x) / m_count;
model_found = true;
ArrayCopy(m_best_inliers_mask, m_curr_mask);
size = inliers_count;
}
}
}
// Fallback: OLS across entire window
if(!model_found)
{
double sum_x = 0, sum_y = 0, sum_xy = 0, sum_x2 = 0;
for(int k = 0; k < len; k++)
{
double x_val = (double)k;
double y_val = m_win_y[k];
sum_x += x_val;
sum_y += y_val;
sum_xy += x_val * y_val;
sum_x2 += x_val * x_val;
}
double m_count = (double)len;
double var_x = sum_x2 - (sum_x * sum_x) / m_count;
if(var_x > 1.0e-12)
{
best_a = (sum_xy - (sum_x * sum_y) / m_count) / var_x;
best_b = (sum_y - best_a * sum_x) / m_count;
for(int k = 0; k < len; k++)
m_best_inliers_mask[k] = (MathAbs(m_win_y[k] - (best_a * (double)k + best_b)) < threshold);
size = len;
}
}
// 5. Calculate RMSE on ALL POINTS in window
double sum_sq_err = 0.0;
for(int k = 0; k < len; k++)
{
double pred = best_a * (double)k + best_b;
double err = m_win_y[k] - pred;
sum_sq_err += err * err;
}
double rmse = MathSqrt(sum_sq_err / (double)len);
// Populate Output Result Structure
m_result.slope = best_a;
m_result.intercept = best_b;
m_result.threshold = threshold;
m_result.rmse = rmse;
m_result.channel_width = rmse * m_width_mult;
m_result.inliers_count = size;
m_result.total_points = len;
m_result.y_start = best_b;
m_result.y_end = best_a * (double)(len - 1) + best_b;
m_result.t_start = time[win_start];
m_result.t_end = time[rates_total - 1];
return true;
}
//+------------------------------------------------------------------+
//| Accessor Methods |
//+------------------------------------------------------------------+
bool CMLRegressionTrend_Calculator::GetResult(SRansacResult &out_res) const
{
if(m_result.total_points <= 0)
return false;
out_res = m_result;
return true;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
bool CMLRegressionTrend_Calculator::IsInlier(const int window_k) const
{
if(window_k >= 0 && window_k < m_length)
return m_best_inliers_mask[window_k];
return false;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double CMLRegressionTrend_Calculator::GetWindowPrice(const int window_k) const
{
if(window_k >= 0 && window_k < m_length)
return m_win_y[window_k];
return 0.0;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
datetime CMLRegressionTrend_Calculator::GetWindowTime(const int window_k) const
{
if(window_k >= 0 && window_k < m_length)
return m_win_times[window_k];
return 0;
}
#endif // ML_REGRESSION_TREND_CALCULATOR_MQH
//+------------------------------------------------------------------+