Article-22714-Volatility-Mo.../Include/slsqp_article/num_diff.mqh

836 righe
25 KiB
MQL5

2026-06-03 20:14:05 +02:00
//+------------------------------------------------------------------+
//| num_diff.mqh |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
//+------------------------------------------------------------------+
//| directional scheme options |
//+------------------------------------------------------------------+
enum ENUM_SCHEME_DIRECTION
{
SCHEME_1=0,//1 sided
SCHEME_2//2 sided
};
//+------------------------------------------------------------------+
//| num points of evaluation |
//+------------------------------------------------------------------+
enum ENUM_DIFF_POINTS
{
GRAD_POINT_2=0,//2-point
GRAD_POINT_3,//3-point
GRAD_POINT_CS,//complex
GRAD_POINT_CALLABLE//callable
};
//+------------------------------------------------------------------+
//| num points of evaluation |
//+------------------------------------------------------------------+
enum ENUM_HESS_DIFF_POINTS
{
HESS_POINT_2=0,//2-point
HESS_POINT_3,//3-point
HESS_POINT_CS,//complex
HESS_POINT_HESS_STRATEGY,//hessian update strategy
HESS_POINT_CALLABLE//callable
};
//+------------------------------------------------------------------+
//|struct objective function return |
//+------------------------------------------------------------------+
struct ObjReturn
{
double f;
vector g;
vector mf;
matrix mg;
ObjReturn(void)
{
f = double(0);
g = vector::Zeros(0);
mf = vector::Zeros(0);
mg = matrix::Zeros(0,0);
}
ObjReturn(ObjReturn& other)
{
f = other.f;
g = other.g;
mf = other.mf;
mg = other.mg;
}
void operator=(ObjReturn& other)
{
f = other.f;
g = other.g;
mf = other.mf;
mg = other.mg;
}
};
//+------------------------------------------------------------------+
//|IObjective provides the base interface for any function |
//|that will be provided to a minimizer routine |
//+------------------------------------------------------------------+
interface IObjective
{
//---the objective function
vector objective_function(vector& x);
ObjReturn fun_and_grad(vector& x);
};
//+------------------------------------------------------------------+
//|differentiation options |
//+------------------------------------------------------------------+
struct GradDiffOptions
{
ENUM_DIFF_POINTS method;
vector rel_step;
vector abs_step;
matrix bounds;
GradDiffOptions(void)
{
method = WRONG_VALUE;
rel_step = abs_step = vector::Zeros(0);
bounds = matrix::Zeros(0,0);
}
GradDiffOptions(ENUM_DIFF_POINTS m, vector& relstep, vector& absstep, matrix& bnds)
{
method = m;
rel_step = relstep;
abs_step = absstep;
bounds = bnds;
}
GradDiffOptions(GradDiffOptions& other)
{
method = other.method;
rel_step = other.rel_step;
abs_step = other.abs_step;
bounds = other.bounds;
}
void operator=(GradDiffOptions& other)
{
method = other.method;
rel_step = other.rel_step;
abs_step = other.abs_step;
bounds = other.bounds;
}
};
//+------------------------------------------------------------------+
//|differentiation options |
//+------------------------------------------------------------------+
struct HessDiffOptions
{
bool as_linear_operator;
ENUM_HESS_DIFF_POINTS method;
vector rel_step;
vector abs_step;
HessDiffOptions(void)
{
method = WRONG_VALUE;
as_linear_operator = false;
rel_step = abs_step = vector::Zeros(0);
}
HessDiffOptions(ENUM_HESS_DIFF_POINTS m, vector& relstep, vector& absstep, bool aslinearoperator)
{
method = m;
rel_step = relstep;
abs_step = absstep;
as_linear_operator = aslinearoperator;
}
HessDiffOptions(HessDiffOptions& other)
{
method = other.method;
rel_step = other.rel_step;
abs_step = other.abs_step;
as_linear_operator = other.as_linear_operator;
}
void operator=(HessDiffOptions& other)
{
method = other.method;
rel_step = other.rel_step;
abs_step = other.abs_step;
as_linear_operator = other.as_linear_operator;
}
};
//+---------------------------------------------------------------------------+
//|function objective representing the objective function and its derivatives.|
//+---------------------------------------------------------------------------+
class CFunctor:public IObjective
{
protected:
vector m_xp;
vector m_x;
ulong m_n;
matrix m_H;
int m_nfev,m_ngev,m_nhev;
bool m_fupdated,m_gupdated,m_hupdated;
double m_lowest_f,m_f;
vector m_lowest_x,m_g;
bool m_clip;
GradDiffOptions m_grad_options;
HessDiffOptions m_hess_options;
void update_fun(void)
{
if(!m_fupdated)
{
double fx = wrapped_fun(m_x);
if(fx<m_lowest_f)
{
m_lowest_f = fx;
m_lowest_x = m_x;
}
m_f = fx;
m_fupdated = true;
}
}
void update_grad(void)
{
if(!m_gupdated)
{
if(m_grad_options.method!=GRAD_POINT_CALLABLE)
update_fun();
vector ff(1);
ff[0] = m_f;
m_g = wrapped_grad(m_x,ff);
m_gupdated = true;
}
}
void update_hess(void)
{
if(!m_hupdated)
{
if(m_hess_options.method != HESS_POINT_CALLABLE)
{
update_grad();
m_H = wrapped_hess(m_x,m_g);
}
else
{
vector a = vector::Zeros(0);
m_H = wrapped_hess(m_x,a);
}
m_hupdated = true;
}
}
void update_x(vector& x)
{
m_x = x;
m_fupdated = m_hupdated = m_gupdated = false;
}
public:
CFunctor(void)
{
m_fupdated = m_hupdated = m_gupdated = false;
m_lowest_f = DBL_MAX;
m_lowest_x = m_g = vector::Zeros(0);
m_H = matrix::Zeros(0,0);
m_nfev = m_ngev = m_nhev = 0;
m_grad_options.method = GRAD_POINT_2;
m_hess_options.method = HESS_POINT_CALLABLE;
m_hess_options.as_linear_operator = true;
m_clip = false;
}
~CFunctor(void)
{
}
void setParams(GradDiffOptions& grad_opts, bool clip_to_bounds=true)
{
m_clip = clip_to_bounds;
m_grad_options = grad_opts;
}
void setClipOption(bool yes)
{
m_clip = yes;
}
void setGradOption(ENUM_DIFF_POINTS grad)
{
m_grad_options.method = grad;
}
void setAbsoluteStep(vector& epsilon)
{
m_grad_options.abs_step = epsilon;
m_hess_options.abs_step = epsilon;
}
void setBounds(matrix& finite_bounds)
{
m_grad_options.bounds = finite_bounds;
}
void setRelativeStep(vector& finite_diff_rel_step)
{
m_grad_options.rel_step = finite_diff_rel_step;
m_hess_options.rel_step = finite_diff_rel_step;
}
void setHessOption(ENUM_HESS_DIFF_POINTS hess)
{
m_hess_options.method = hess;
}
bool initialize(vector& x)
{
m_x = x;
m_xp = m_x;
m_n = x.Size();
if(m_grad_options.method != GRAD_POINT_CALLABLE && m_hess_options.method != HESS_POINT_CALLABLE)
{
Print(__FUNCTION__, "Whenever the gradient is estimated via "
"finite-differences, it is required that"
" the Hessian "
"be estimated using one of the "
"quasi-Newton strategies.");
return false;
}
double check = orig_fun(m_x);
if(MathClassify(check)!=FP_NORMAL)
{
Print(__FUNCTION__," check the implementation of the objective function, currently evaluates to an invalid number ");
return false;
}
if(m_grad_options.method == GRAD_POINT_CALLABLE)
{
vector a = grad_fun(m_x);
if(!a.Size())
{
Print(__FUNCTION__, " check the implementation of the overriden gradient function, currently evaluates to an empty vector ");
return false;
}
}
if(m_grad_options.bounds.Rows()!=ulong(m_n))
{
m_grad_options.bounds.Resize(m_n,2);
m_grad_options.bounds.Fill(double("inf"));
for(ulong i = 0; i<m_n; ++i)
m_grad_options.bounds[i,0]*=-1.0;
Print(__FUNCTION__," ","WARNING:Boundary constraints not properly specified.\nOverwritten to (-inf,inf) ");
}
update_fun();
update_grad();
if(m_hess_options.method == HESS_POINT_CALLABLE)
{
vector a = vector::Zeros(0);
m_H = wrapped_hess(x,a);
m_hupdated = true;
}
return true;
}
double wrapped_fun(vector& x)
{
m_nfev += 1;
if(m_clip)
{
matrix bnds = m_grad_options.bounds;
for(ulong i = 0; i<x.Size(); ++i)
{
if(x[i]<bnds[i,0])
x[i] = bnds[i,0];
else
if(x[i]>bnds[i,1])
x[i] = bnds[i,1];
}
}
vector copy = x;
return orig_fun(copy);
}
vector objective_function(vector& x)
{
vector r(1);
r[0] = wrapped_fun(x);
return r;
}
vector wrapped_grad(vector& x,vector& f0)
{
m_ngev += 1;
if(m_clip)
{
matrix bnds = m_grad_options.bounds;
for(ulong i = 0; i<x.Size(); ++i)
{
if(x[i]<bnds[i,0])
x[i] = bnds[i,0];
else
if(x[i]>bnds[i,1])
x[i] = bnds[i,1];
}
}
vector copy = x;
if(m_grad_options.method == GRAD_POINT_CALLABLE)
return grad_fun(copy);
IObjective* objective = GetPointer(this);
matrix ad = approx_derivative(objective,copy,f0,m_grad_options.method,m_grad_options.rel_step,m_grad_options.abs_step,m_grad_options.bounds);
return ad.Row(0);
}
matrix wrapped_hess(vector& x, vector& f0)
{
m_nhev += 1;
if(m_clip)
{
matrix bnds = m_grad_options.bounds;
for(ulong i = 0; i<x.Size(); ++i)
{
if(x[i]<bnds[i,0])
x[i] = bnds[i,0];
else
if(x[i]>bnds[i,1])
x[i] = bnds[i,1];
}
}
vector copy = x;
if(m_hess_options.method == HESS_POINT_CALLABLE)
return hess_fun(copy);
IObjective* objective = GetPointer(this);
return approx_derivative(objective,x,f0,m_grad_options.method,m_grad_options.rel_step,m_grad_options.abs_step,m_grad_options.bounds);
}
vector lower_bounds(void)
{
return m_grad_options.bounds.Col(0);
}
vector upper_bounds(void)
{
return m_grad_options.bounds.Col(1);
}
vector initial_params(void)
{
return m_xp;
}
virtual double orig_fun(vector& x)
{
return double("nan");
}
virtual vector grad_fun(vector& x)
{
return vector::Zeros(0);
}
virtual matrix hess_fun(vector& x)
{
return matrix::Zeros(0,0);
}
ObjReturn fun_and_grad(vector& x)
{
vector dif = MathAbs(m_x - x);
if(dif.Sum() >= DBL_EPSILON || dif.HasNan())
update_x(x);
update_fun();
update_grad();
ObjReturn out;
out.f = m_f;
out.g = m_g;
return out;
}
};
//+------------------------------------------------------------------+
//|base class representing function and its derivative |
//+------------------------------------------------------------------+
class CConstraints:public IObjective
{
protected:
int m_n,m_m;
bool m_clip,m_initialized;
GradDiffOptions m_options;
vector wrapped_func(vector& x)
{
if(!m_initialized)
return vector::Zeros(0);
if(m_clip)
{
matrix bnds = m_options.bounds;
for(ulong i = 0; i<x.Size(); ++i)
{
if(x[i]<bnds[i,0])
x[i] = bnds[i,0];
else
if(x[i]>bnds[i,1])
x[i] = bnds[i,1];
}
}
vector copy = x;
return orig_fun(copy);
}
matrix wrapped_grad(vector& x,vector& f0)
{
if(!m_initialized)
return matrix::Zeros(0,0);
if(m_clip)
{
matrix bnds = m_options.bounds;
for(ulong i = 0; i<x.Size(); ++i)
{
if(x[i]<bnds[i,0])
x[i] = bnds[i,0];
else
if(x[i]>bnds[i,1])
x[i] = bnds[i,1];
}
}
vector copy = x;
if(m_options.method == GRAD_POINT_CALLABLE)
return orig_grad(copy);
IObjective* fun = (IObjective*)GetPointer(this);
matrix ad = approx_derivative(fun,copy,f0,m_options.method,m_options.rel_step,m_options.abs_step,m_options.bounds);
return ad;
}
public:
CConstraints(void)
{
m_n = m_m = 0;
m_clip = true;
m_initialized = false;
m_options.method = GRAD_POINT_2;
}
~CConstraints(void)
{
}
bool initialize(vector& x)
{
if((m_m == 0 && m_n) || (int)x.Size()!=m_n)
{
Print(__FUNCTION__," ","Invalid property dimensions.", " n ", m_n, " vs ", x.Size());
return false;
}
vector check = orig_fun(x);
if(!check.Size() || check.HasNan() || MathClassify(fabs(check.Max())) == FP_INFINITE)
{
Print(__FUNCTION__," ","Check the implementation of the constraints function.");
return false;
}
if(m_options.method == GRAD_POINT_CALLABLE)
{
matrix a = orig_grad(x);
if(a.Rows()!=m_m || a.HasNan() || MathClassify(fabs(a.Max())) == FP_INFINITE)
{
Print(__FUNCTION__," ","Check the implementation of the overriden constraints gradient function.");
return false;
}
}
if(m_options.bounds.Rows()!=ulong(m_n))
{
m_options.bounds.Resize(m_n,2);
m_options.bounds.Fill(double("inf"));
for(int i = 0; i<m_n; ++i)
m_options.bounds[i,0]*=-1.0;
Print(__FUNCTION__, " ","WARNING:Boundary constraints not properly specified.\nOverwritten to (-inf,inf) ");
}
m_initialized = true;
return m_initialized;
}
void setParams(int m, int n, GradDiffOptions& opts, bool clip_to_bounds=true)
{
m_initialized = false;
m_n = fabs(n);
m_m = fabs(m);
m_clip = clip_to_bounds;
m_options = opts;
}
void setGradientOptions(GradDiffOptions& opts)
{
m_initialized = false;
m_options = opts;
}
void setBounds(matrix& bounds)
{
m_initialized = false;
m_options.bounds = bounds;
}
void setNumConstraints(int m)
{
m_initialized = false;
m_m = m;
}
void setDimension(int n)
{
m_initialized = false;
m_n = n;
}
vector objective_function(vector& x)
{
return wrapped_func(x);
}
ObjReturn fun_and_grad(vector& x)
{
ObjReturn out;
out.mf = objective_function(x);
out.mg = wrapped_grad(x,out.mf);
return out;
}
virtual vector orig_fun(vector& x)
{
return vector::Zeros(0);
}
virtual matrix orig_grad(vector& x)
{
return matrix::Zeros(0,0);
}
int numConstraints(void)
{
return m_m;
}
int dim(void)
{
return m_n;
}
vector lower_bounds(void)
{
return m_options.bounds.Col(0);
}
vector upper_bounds(void)
{
return m_options.bounds.Col(1);
}
};
//+----------------------------------------------------------------------------------+
//|Calculates relative EPS step to use for a given data type and numdiff step method.|
//+----------------------------------------------------------------------------------+
double eps_for_method(ENUM_DIFF_POINTS method)
{
switch(method)
{
case GRAD_POINT_2:
case GRAD_POINT_CS:
return pow(2.220446049250313e-16,0.5);
case GRAD_POINT_3:
return pow(2.220446049250313e-16,(1./3.));
};
return DBL_EPSILON;
}
//+---------------------------------------------------------------------------------+
//|Computes an absolute step from a relative step for finite difference calculation.|
//+---------------------------------------------------------------------------------+
vector compute_absolute_step(vector& rel_step,vector& x0, vector& f0, ENUM_DIFF_POINTS method)
{
vector signx0 = x0;
vector abs_step = signx0;
for(ulong i = 0; i<signx0.Size(); ++i)
{
if(x0[i] >= 0.)
signx0[i]=1.*2-1;
else
signx0[i] = 0.0*2-1;
}
double rstep = eps_for_method(method);
if(rel_step.Size()==0)
for(ulong i = 0; i<abs_step.Size(); ++i)
abs_step[i] = rstep*signx0[i]*MathMax(1.,fabs(x0[i]));
else
{
abs_step = rstep*signx0*MathAbs(x0);
vector dx = ((x0+abs_step) - x0);
for(ulong i = 0; i<abs_step.Size(); ++i)
if(dx[i] == 0.0)
abs_step[i] = rstep*signx0[i]*MathMax(1.,fabs(x0[i]));
}
return abs_step;
}
//+------------------------------------------------------------------+
//|Adjust final difference scheme to the presence of bounds. |
//+------------------------------------------------------------------+
vector adjust_scheme_to_bounds(vector& x0, vector& h, int num_steps,ENUM_SCHEME_DIRECTION scheme, vector& lb, vector& ub, vector &one_sided)
{
switch(scheme)
{
case SCHEME_1:
one_sided = vector::Ones(h.Size());
break;
case SCHEME_2:
one_sided = vector::Ones(h.Size());
h = MathAbs(h);
break;
}
bool all_true = true;
for(ulong i = 0; i<x0.Size(); ++i)
if(lb[i] != -double("inf") || ub[i] != double("inf"))
{
all_true = false;
break;
}
if(all_true)
return h;
vector h_total = h * double(num_steps);
vector h_adjusted = h;
vector lower_dist = x0 - lb;
vector upper_dist = ub - x0;
int forward,backward,fitting,violated,central, adjusted_central;
forward = backward = violated = fitting = central = false;
double x = 0.;
double min_dist = 0.;
switch(scheme)
{
case SCHEME_1:
{
for(ulong i = 0; i<h.Size(); ++i)
{
x = x0[i] + h_total[i];
violated = int(x<lb[i]|x>ub[i]);
fitting = int(fabs(h_total[i])<=MathMax(lower_dist[i],upper_dist[i]));
if(bool(violated & fitting))
h_adjusted[i]*=-1.;
forward = int((upper_dist[i] >= lower_dist[i]) & ~fitting);
if(forward)
h_adjusted[i] = upper_dist[i]/double(num_steps);
backward = int((upper_dist[i]<lower_dist[i]) & ~fitting);
if(backward)
h_adjusted[i] = -lower_dist[i]/double(num_steps);
}
}
break;
case SCHEME_2:
{
for(ulong i = 0; i<h.Size(); ++i)
{
central = int(((lower_dist[i]>=h_total[i]) & (upper_dist[i] >= h_total[i])));
forward = int(((upper_dist[i]>=lower_dist[i]) & ~central));
if(forward)
{
h_adjusted[i] = MathMin(h[i],0.5*upper_dist[i]/double(num_steps));
one_sided[i] = 1.;
}
backward = int(((upper_dist[i]<lower_dist[i]) & ~central));
if(backward)
{
h_adjusted[i] = -1.* MathMin(h[i],0.5*lower_dist[i]/double(num_steps));
one_sided[i] = 1.0;
}
min_dist = MathMin(upper_dist[i],lower_dist[i])/double(num_steps);
adjusted_central = int((~central & (fabs(h_adjusted[i])<=min_dist)));
if(adjusted_central)
{
h_adjusted[i] = min_dist;
one_sided[i] = 0.;
}
}
}
break;
}
return h_adjusted;
}
//+------------------------------------------------------------------+
//|dense difference |
//+------------------------------------------------------------------+
matrix dense_difference(IObjective* fun, vector& x0, vector& f0, vector& h, vector& use_one_sided, ENUM_DIFF_POINTS method)
{
ulong m = f0.Size();
ulong n = x0.Size();
matrix j_transposed = matrix::Zeros(n,m);
vector x1 = x0;
vector x2 = x0;
vector df = vector::Zeros(f0.Size());
for(ulong i = 0; i<h.Size(); ++i)
{
double dx = 1.e-12;
if(method == GRAD_POINT_2)
{
x1[i] += h[i];
dx = x1[i] - x0[i];
df = fun.objective_function(x1) - f0;
}
else
if(method == GRAD_POINT_3 && use_one_sided[i]!=0.0)
{
x1[i] += h[i];
x2[i] += 2. * h[i];
dx = x2[i] - x0[i];
df = -3.0 * f0 + 4 * fun.objective_function(x1) - fun.objective_function(x2);
}
else
if(method == GRAD_POINT_3 && use_one_sided[i]==0.0)
{
x1[i] -= h[i];
x2[i] += h[i];
dx = x2[i] - x1[i];
df = fun.objective_function(x2) - fun.objective_function(x1);
}
j_transposed.Row(df/dx,i);
x1[i] = x2[i] = x0[i];
}
return j_transposed.Transpose();
}
//+---------------------------------------------------------------------------------------+
//|Compute finite difference approximation of the derivatives of a vector-valued function.|
//+---------------------------------------------------------------------------------------+
matrix approx_derivative(IObjective* fun,vector& x0,vector &f0,ENUM_DIFF_POINTS method, vector &rel_step,vector &abs_step, matrix& bounds/*sparsity,as linear_operator*/)
{
if(CheckPointer(fun)==POINTER_INVALID)
{
Print(__FUNCTION__, " fun variable is an invalid pointer ");
return matrix::Zeros(0,0);
}
vector lb,ub;
lb = bounds.Col(0);
ub = bounds.Col(1);
if(lb.Size()!=x0.Size() ||ub.Size()!=x0.Size())
{
Print(__FUNCTION__, " inconsistent shaptes between bounds and x0 ");
return matrix::Zeros(0,0);
}
if(!f0.Size())
f0 = fun.objective_function(x0);
for(ulong i = 0; i<x0.Size(); ++i)
if(x0[i]<lb[i] || x0[i]>ub[i])
{
Print(__FUNCTION__, " x0 violates bound constraints ");
return matrix::Zeros(0,0);
}
vector h;
if(!abs_step.Size())
h = compute_absolute_step(rel_step,x0,f0,method);
else
{
h = abs_step;
vector signx0 = vector::Zeros(x0.Size());
for(ulong i = 0; i<x0.Size(); ++i)
{
if(x0[i]>=0.0)
signx0[i] = 1.0*2.-1.;
else
signx0[i] = 0.0*2.-1.;
if(((x0[i]+h[i]) - x0[i]) == 0.0)
h[i] = eps_for_method(method)*signx0[i]*MathMax(1.,fabs(x0[i]));
}
}
vector use_one_sided;
switch(method)
{
case GRAD_POINT_2:
h = adjust_scheme_to_bounds(x0,h,1,SCHEME_1,lb,ub,use_one_sided);
break;
case GRAD_POINT_3:
h = adjust_scheme_to_bounds(x0,h,1,SCHEME_2,lb,ub,use_one_sided);
break;
case GRAD_POINT_CS:
use_one_sided = vector::Zeros(x0.Size());
break;
}
return dense_difference(fun,x0,f0,h,use_one_sided,method);
}
//+------------------------------------------------------------------+