Article-20589-Volatility-Mo.../Include/Arch/Univariate/mean.mqh

2271 lignes
80 Kio
MQL5

//+------------------------------------------------------------------+
//| mean.mqh |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#include"volatility.mqh"
//+------------------------------------------------------------------+
//| Abstract base class for mean models |
//+------------------------------------------------------------------+
class CArchModel: public CObject
{
protected:
ArchParameters m_model_spec;
vector m_params,m_fit_indices,m_backcast;
vector m_fit_y;
matrix m_fit_x,m_fit_regressors;
double m_scale;
ulong m_nobs;
ulong m_holdback;
ulong m_maxlags;
ulong m_num_params;
bool m_converged,m_rescale,m_constant,m_rotated;
string m_name;
matrix m_regressors,m_lags,m_var_bounds;
bool m_delete_vp,m_delete_dist;
CDistribution *m_distribution;
CVolatilityProcess *m_vp;
bool m_initialized;
vector _get_epsilon(vector &x, double s, ulong n, double epsilon = EMPTY_VALUE)
{
vector out;
if(epsilon == EMPTY_VALUE)
out = pow(DBL_EPSILON,1./s)*np::maximum(MathAbs(x),0.1);
else
{
out = vector::Zeros(n);
out.Fill(epsilon);
}
return out;
}
matrix approx_fprime(vector &x,vector& sigma2, vector& backcast, matrix& varbounds, bool individual=false,bool centered = false, double epsilon = EMPTY_VALUE)
{
ulong n = x.Size();
vector f0 = _loglikelihood(x,sigma2,backcast,varbounds,individual);
matrix grad = matrix::Zeros(n,f0.Size());
vector ei = vector::Zeros(n);
vector eps,row;
if(!centered)
{
eps = _get_epsilon(x,2.,n,epsilon);
for(ulong i = 0; i<n; ++i)
{
ei[i] = eps[i];
row = (_loglikelihood(x+ei,sigma2,backcast,varbounds,individual) - f0)/eps[i];
ei[i] = 0.0;
grad.Row(row,i);
}
}
else
{
eps = _get_epsilon(x,3,n,epsilon)/2.;
for(ulong i = 0; i<n; ++i)
{
ei[i] = eps[i];
row = (_loglikelihood(x+ei,sigma2,backcast,varbounds,individual) - _loglikelihood(x-ei,sigma2,backcast,varbounds,individual))/(2. * eps[i]);
ei[i] = 0.0;
grad.Row(row,i);
}
}
return grad.Transpose();
}
matrix approx_hess(vector &x,vector& sigma2, vector& backcast, matrix& varbounds, bool individual=false,double epsilon = EMPTY_VALUE)
{
ulong n = x.Size();
vector h = _get_epsilon(x,4,n,epsilon);
matrix ee;
ee.Diag(h);
matrix hess = h.Outer(h);
for(ulong i = 0; i<n; ++i)
{
for(ulong j = i; j<n; ++j)
{
vector temp = (_loglikelihood(x+ee.Row(i)+ee.Row(j),sigma2,backcast,varbounds,individual) -
_loglikelihood(x+ee.Row(i)-ee.Row(j),sigma2,backcast,varbounds,individual) -
(_loglikelihood(x-ee.Row(i)+ee.Row(j),sigma2,backcast,varbounds,individual) -
_loglikelihood(x-ee.Row(i)-ee.Row(j),sigma2,backcast,varbounds,individual)))/(4.*hess[i,j]);
hess[i,j] = temp[0];
hess[j,i] = hess[i,j];
}
}
return hess;
}
void _checkscale(vector& resids)
{
double ogscale = resids.Var();
double scale = ogscale;
double rescale = 1.;
while((scale>=1000.|| scale<0.1) && scale>0.0)
{
if(scale<1.0)
rescale*=10.0;
else
rescale/=10.0;
scale = ogscale*pow(rescale,2.0);
}
if(rescale == 1.0)
return;
if(!m_rescale)
{
Print(__FUNCTION__, " y is poorly scaled, which may affect convergence of the optimizer when estimating the model parameters.\n","Calculated scale is ",ogscale," . Parameter estimation works better when this value is between 1 and 1000. The recommended rescaling is ",rescale," * y ");
return;
}
m_scale = rescale;
}
virtual void _scalechanged(void)
{
return;
}
virtual double _r2(vector& params)
{
return EMPTY_VALUE;
}
virtual vector _fit_no_arch_normal_errors_params(void)
{
return vector::Zeros(0);
}
double _static_gaussian_loglikelihood(vector &resids_)
{
ulong nobs_ = resids_.Size();
double sigma2 = resids_.Dot(resids_)/double(nobs_);
double ll = -0.5 * double(nobs_) * log(2.0 * M_PI);
ll -= 0.5*double(nobs_)*log(sigma2);
ll -= 0.5*double(nobs_);
return ll;
}
bool _parse_parameters(vector &x, vector& out[])
{
ulong km = m_num_params;
ulong kv = m_vp.numParams();
ArrayResize(out,3);
out[0] = (km)?np::sliceVector(x,0,long(km)):vector::Zeros(0);
out[1] = (kv)?np::sliceVector(x,long(km),long(km+kv)):vector::Zeros(0);
out[2] = ((km+kv)<x.Size())?np::sliceVector(x,long(km+kv)):vector::Zeros(0);
return true;
}
vector _loglikelihood(vector& parameters, vector& sigma2, vector& backcast, matrix& varbounds, bool individual=false)
{
vector prs[];
_parse_parameters(parameters,prs);
vector resids = resids(prs[0],EMPTY_VECTOR,EMPTY_MATRIX);
sigma2 = m_vp.computeVariance(prs[1],resids,sigma2,backcast,varbounds);
vector llf = m_distribution.loglikelihood(prs[2],resids,sigma2,individual);
return (-1.*llf);
}
virtual void _adjust_sample(long& first, long& last)
{
return;
}
virtual void deinitialize(void)
{
if(m_delete_vp && CheckPointer(m_vp) == POINTER_DYNAMIC)
delete m_vp;
m_vp = NULL;
if(m_delete_dist && CheckPointer(m_distribution) == POINTER_DYNAMIC)
delete m_distribution;
m_distribution = NULL;
}
public:
CArchModel(void):m_vp(NULL),
m_initialized(false),
m_distribution(NULL),
m_nobs(0),
m_holdback(0),
m_converged(false),
m_rescale(false),
m_constant(true),
m_delete_vp(true),
m_delete_dist(true),
m_scale(1.0),
m_name(NULL),
m_fit_y(vector::Zeros(0)),
m_params(vector::Zeros(0)),
m_fit_indices(vector::Zeros(0)),
m_fit_regressors(matrix::Zeros(0,0)),
m_fit_x(matrix::Zeros(0,0)),
m_regressors(matrix::Zeros(0,0))
{
}
ulong nobs(void) { return m_nobs; }
ulong hold_back(void) { return m_holdback; }
bool converged(void) { return m_converged;}
bool rescaled(void) { return m_rescale; }
double get_scale(void) { return m_scale; }
vector get_y(void) { return m_model_spec.observations; }
vector get_params(void) { return m_params; }
ArchParameters get_specification(void) { return m_model_spec; }
virtual bool Save(const int file_handle) override
{
if(file_handle!=INVALID_HANDLE)
{
//---
if(FileWriteLong(file_handle,-1)==sizeof(long))
{
//---
if(FileWriteInteger(file_handle,int(m_params.Size()),INT_VALUE)!=INT_VALUE)
return(false);
//---
for(ulong i = 0; i<m_params.Size(); ++i)
{
if(FileWriteDouble(file_handle,m_params[i])!=sizeof(double))
{
Print(__FUNCTION__, " file write error ", GetLastError());
return false;
}
}
//---
if(FileWriteInteger(file_handle,int(m_model_spec.mean_model_type),INT_VALUE)!=INT_VALUE ||
FileWriteLong(file_handle,long(m_model_spec.holdout_size))!=sizeof(long) ||
FileWriteInteger(file_handle,int(m_model_spec.is_rescale_enabled),INT_VALUE)!=INT_VALUE ||
FileWriteInteger(file_handle,int(m_model_spec.include_constant),INT_VALUE)!=INT_VALUE ||
FileWriteInteger(file_handle,int(m_model_spec.use_har_rotation),INT_VALUE)!=INT_VALUE)
return(false);
//---
if(FileWriteDouble(file_handle,m_model_spec.scaling_factor)!=sizeof(double))
return(false);
//---
if(FileWriteLong(file_handle,long(m_model_spec.mean_lags.Size()))!=sizeof(long))
return(false);
//---
for(ulong i = 0; i<m_model_spec.mean_lags.Size(); ++i)
{
if(FileWriteDouble(file_handle,m_model_spec.mean_lags[i])!=sizeof(double))
{
Print(__FUNCTION__, " file write error ", GetLastError());
return false;
}
}
//---
if(FileWriteInteger(file_handle,int(m_model_spec.vol_model_type),INT_VALUE)!=INT_VALUE)
return(false);
//---
if(FileWriteInteger(file_handle,int(m_model_spec.vol_rng_seed),INT_VALUE)!=INT_VALUE ||
FileWriteLong(file_handle,m_model_spec.sample_start_idx)!=sizeof(long) ||
FileWriteLong(file_handle,m_model_spec.sample_end_idx)!=sizeof(long) ||
FileWriteLong(file_handle,long(m_model_spec.min_bootstrap_sims))!=INT_VALUE ||
FileWriteLong(file_handle,long(m_model_spec.garch_p))!=sizeof(long) ||
FileWriteLong(file_handle,long(m_model_spec.garch_o))!=sizeof(long) ||
FileWriteLong(file_handle,long(m_model_spec.garch_q))!=sizeof(long))
return(false);
//---
if(FileWriteDouble(file_handle,m_model_spec.vol_power)!=sizeof(double))
return false;
//---
if(FileWriteInteger(file_handle,int(m_model_spec.dist_type),INT_VALUE)!=INT_VALUE)
return(false);
//---
if(FileWriteInteger(file_handle,int(m_model_spec.dist_rng_seed),INT_VALUE)!=INT_VALUE)
return(false);
//---
if(FileWriteLong(file_handle,long(m_model_spec.observations.Size()))!=sizeof(long))
return(false);
//---
for(ulong i = 0; i<m_model_spec.observations.Size(); ++i)
{
if(FileWriteDouble(file_handle,m_model_spec.observations[i])!=sizeof(double))
{
Print(__FUNCTION__, " file write error ", GetLastError());
return false;
}
}
//---
if(FileWriteLong(file_handle,long(m_model_spec.exog_data.Rows()))!=sizeof(long))
return(false);
//---
if(FileWriteLong(file_handle,long(m_model_spec.exog_data.Cols()))!=sizeof(long))
return(false);
//---
for(ulong i = 0; i<m_model_spec.exog_data.Rows(); ++i)
{
for(ulong j = 0; j<m_model_spec.exog_data.Cols(); ++j)
{
if(FileWriteDouble(file_handle,m_model_spec.exog_data[i,j])!=sizeof(double))
{
Print(__FUNCTION__, " file write error ", GetLastError());
return false;
}
}
}
//---
return true;
}
}
return false;
}
virtual bool Load(const int file_handle) override
{
if(file_handle!=INVALID_HANDLE)
{
//---
long ff = FileReadLong(file_handle);
if(ff == -1)
{
//---
ResetLastError();
ulong size = ulong(FileReadLong(file_handle));
if(size)
{
m_params.Resize(size);
m_converged = true;
for(ulong i = 0; i<m_params.Size(); ++i)
m_params[i]=FileReadDouble(file_handle);
if(GetLastError())
{
Print(__FUNCTION__, " possible read error ", GetLastError());
return false;
}
}
else
{
m_converged = false;
m_params = vector::Zeros(0);
}
//---
m_model_spec.mean_model_type = ENUM_MEAN_MODEL(FileReadInteger(file_handle,INT_VALUE));
m_model_spec.holdout_size = ulong(FileReadLong(file_handle));
m_model_spec.is_rescale_enabled = bool(FileReadInteger(file_handle,INT_VALUE));
m_model_spec.include_constant = bool(FileReadInteger(file_handle,INT_VALUE));
m_model_spec.use_har_rotation = bool(FileReadInteger(file_handle,INT_VALUE));
//---
if(GetLastError())
{
Print(__FUNCTION__, " possible read error ", GetLastError());
return false;
}
m_model_spec.scaling_factor=FileReadDouble(file_handle);
//---
size = ulong(FileReadLong(file_handle));
if(size)
{
m_model_spec.mean_lags.Resize(size);
for(ulong i = 0; i<m_model_spec.mean_lags.Size(); ++i)
m_model_spec.mean_lags[i]=FileReadDouble(file_handle);
}
else
{
m_model_spec.mean_lags = vector::Zeros(0);
}
//---
if(GetLastError())
{
Print(__FUNCTION__, " possible read error ", GetLastError());
return false;
}
m_model_spec.vol_model_type = ENUM_VOLATILITY_MODEL(FileReadInteger(file_handle,INT_VALUE));
m_model_spec.vol_rng_seed = (FileReadInteger(file_handle,INT_VALUE));
m_model_spec.sample_start_idx = long(FileReadLong(file_handle));
m_model_spec.sample_end_idx = long(FileReadLong(file_handle));
m_model_spec.min_bootstrap_sims = ulong(FileReadLong(file_handle));
m_model_spec.garch_p = ulong(FileReadLong(file_handle));
m_model_spec.garch_o = ulong(FileReadLong(file_handle));
m_model_spec.garch_q = ulong(FileReadLong(file_handle));
m_model_spec.vol_power = FileReadDouble(file_handle);
//---
m_model_spec.dist_type = ENUM_DISTRIBUTION_MODEL(FileReadInteger(file_handle,INT_VALUE));
m_model_spec.dist_rng_seed = FileReadInteger(file_handle,INT_VALUE);
//---
if(GetLastError())
{
Print(__FUNCTION__, " possible read error ", GetLastError());
return false;
}
size = ulong(FileReadLong(file_handle));
if(size)
{
m_model_spec.observations.Resize(size);
for(ulong i = 0; i<m_model_spec.observations.Size(); ++i)
{
m_model_spec.observations[i] = FileReadDouble(file_handle);
}
}
else
m_model_spec.observations = vector::Zeros(0);
//---
long rws = FileReadLong(file_handle);
long cls = FileReadLong(file_handle);
m_model_spec.exog_data = matrix::Zeros(rws,cls);
if(m_model_spec.exog_data.Rows())
{
//---
for(ulong i = 0; i<m_model_spec.exog_data.Rows(); ++i)
{
for(ulong j = 0; j<m_model_spec.exog_data.Cols(); ++j)
{
m_model_spec.exog_data[i,j] = FileReadDouble(file_handle);
}
}
}
if(GetLastError())
{
Print(__FUNCTION__, " possible read error ", GetLastError());
return false;
}
//---
return true;
}
}
return false;
}
virtual bool is_initialized(void) { return m_initialized; }
~CArchModel(void)
{
deinitialize();
}
CVolatilityProcess *volatility(void)
{
return m_vp;
}
CDistribution *distribution(void)
{
return m_distribution;
}
ENUM_MEAN_MODEL mean_model_type(void)
{
return m_model_spec.mean_model_type;
}
ENUM_VOLATILITY_MODEL volatility_process_type(void)
{
return m_vp.volatilityprocess();
}
ENUM_DISTRIBUTION_MODEL distribution_type(void)
{
return m_distribution.distribution_type();
}
vector objective(vector& parameters, vector& sigma2, vector &backcast, matrix& varbounds, bool individual=false)
{
return _loglikelihood(parameters,sigma2,backcast,varbounds,individual);
}
matrix jacobian(vector& parameters, vector& sigma2, vector &backcast, matrix& varbounds, bool individual=false,bool centered=false, double eps=EMPTY_VALUE)
{
return approx_fprime(parameters,sigma2,backcast,varbounds,individual,centered,eps);
}
virtual vector starting_values(void)
{
return _fit_no_arch_normal_errors_params();
}
virtual vector resids(vector& params, vector& y, matrix& regressors)
{
return vector::Zeros(0);
}
virtual matrix compute_param_cov(vector& params, vector& backcast, bool robust = true)
{
vector sv_ = starting_values();
vector fy = vector::Zeros(0);
matrix fr = matrix::Zeros(0,0);
vector resids_ = resids(sv_,fy,fr);
matrix vb = m_vp.varianceBounds(resids_);
ulong nobs = resids_.Size();
if(!backcast.Size() && !m_backcast.Size())
{
backcast = m_vp.backCast(resids_);
m_backcast = backcast;
}
else
if(!backcast.Size())
backcast = m_backcast;
vector sgma2=vector::Zeros(nobs);
matrix hess = approx_hess(params,sgma2,backcast,vb,false);
hess/=double(nobs);
matrix inv_hess = hess.Inv();
if(robust)
{
matrix scores = approx_fprime(params,sgma2,backcast,vb,true);
matrix score_cov = scores.Transpose().Cov();
return (inv_hess.MatMul(score_cov).MatMul(inv_hess))/double(nobs);
}
else
return inv_hess/double(nobs);
}
virtual Constraints constraints(void)
{
Constraints out;
return out;
}
virtual matrix bounds(void)
{
vector temp = {AL_NEGINF,AL_POSINF};
return np::repeat_vector_as_rows_cols(temp,m_num_params,false);
}
virtual string name(void)
{
return m_name;
}
virtual ArchForecast forecast(vector& params,matrix& x[],ulong horizon = 1, long start = -1, ENUM_FORECAST_METHOD method=FORECAST_ANALYTIC, ulong simulations=1000, uint seed=0)
{
ArchForecast out;
return out;
}
};
//+------------------------------------------------------------------+
//| arch model fixed result |
//+------------------------------------------------------------------+
struct ArchModelFixedResult
{
public:
double loglikelihood;
vector params;
vector conditional_volatility;
ulong nobs;
vector resid;
ArchModelFixedResult(void)
{
loglikelihood = EMPTY_VALUE;
nobs = 0;
params = resid = conditional_volatility = vector::Zeros(0);
}
ArchModelFixedResult(ArchModelFixedResult &other)
{
loglikelihood = other.loglikelihood;
nobs = other.nobs;
params = other.params;
resid = other.resid;
conditional_volatility = other.conditional_volatility;
}
ArchModelFixedResult(vector &_params, vector &_resid, vector &_volatility, vector &_depvar, double _loglikelihood)
{
loglikelihood = _loglikelihood;
params = _params;
resid = _resid;
conditional_volatility = _volatility;
}
~ArchModelFixedResult(void)
{
}
void operator=(ArchModelFixedResult &other)
{
loglikelihood = other.loglikelihood;
nobs = other.nobs;
params = other.params;
resid = other.resid;
conditional_volatility = other.conditional_volatility;
}
double aic(void)
{
return -2 * loglikelihood + 2 * double(num_params());
}
ulong num_params(void)
{
return params.Size();
}
double bic(void)
{
return -2.*loglikelihood+log(nobs)*double(num_params());
}
vector std_resid(void)
{
return resid/conditional_volatility;
}
};
//+------------------------------------------------------------------+
//| arch model result |
//+------------------------------------------------------------------+
struct ArchModelResult: public ArchModelFixedResult
{
public:
long fit_indices[2];
matrix param_cov;
double r2;
ENUM_COVAR_TYPE cov_type;
ArchModelResult(void)
{
fit_indices[0] = fit_indices[1] = 0;
param_cov = matrix::Zeros(0,0);
r2 = EMPTY_VALUE;
cov_type = WRONG_VALUE;
}
ArchModelResult(vector& _params,matrix &_param_cov,double _r2, vector& _resid, vector & _volatility, ENUM_COVAR_TYPE _cov_type, double _loglikelihood, long fit_start,long fit_stop)
{
loglikelihood = _loglikelihood;
params = _params;
param_cov = _param_cov;
resid = _resid;
conditional_volatility = _volatility;
fit_indices[0] = fit_start;
fit_indices[1] = fit_stop;
r2 = _r2;
cov_type = _cov_type;
}
~ArchModelResult(void)
{
}
ArchModelResult(ArchModelResult &other)
{
loglikelihood = other.loglikelihood;
nobs = other.nobs;
params = other.params;
resid = other.resid;
conditional_volatility = other.conditional_volatility;
fit_indices[0] = other.fit_indices[0];
fit_indices[1] = other.fit_indices[1];
r2 = other.r2;
cov_type = other.cov_type;
param_cov = other.param_cov;
}
void operator=(ArchModelResult &other)
{
loglikelihood = other.loglikelihood;
nobs = other.nobs;
params = other.params;
resid = other.resid;
conditional_volatility = other.conditional_volatility;
fit_indices[0] = other.fit_indices[0];
fit_indices[1] = other.fit_indices[1];
r2 = other.r2;
cov_type = other.cov_type;
param_cov = other.param_cov;
}
matrix parameter_covariance(CArchModel &archmodel)
{
vector fitted_params = archmodel.get_params();
if(cov_type == COVAR_ROBUST)
param_cov = archmodel.compute_param_cov(fitted_params,EMPTY_VECTOR);
else
param_cov = archmodel.compute_param_cov(fitted_params,EMPTY_VECTOR,false);
return param_cov;
}
matrix conf_int(double alpha = 0.05)
{
double cv = CAlglib::InvNormalCDF(1.0-alpha/2.0);
vector se = std_err();
matrix out = matrix::Zeros(params.Size(),2);
out.Col(params - cv *se,0);//lower
out.Col(params + cv *se,1);//upper
return out;
}
double rsquared_adj(void)
{
return 1.0 - ((1.0-r2)*double(nobs-1)/double(nobs-num_params()));
}
vector std_err(void)
{
return sqrt(param_cov.Diag());
}
vector pvalues(void)
{
vector pvals = tvalues();
for(ulong i = 0; i<pvals.Size(); ++i)
pvals[i] = CAlglib::NormalCDF(-1.0*MathAbs(pvals[i]))*2.0;
return pvals;
}
vector tvalues(void)
{
return params/std_err();
}
};
//+------------------------------------------------------------------+
//| The Lagrange multiplier test |
//+------------------------------------------------------------------+
WaldTestStatistic archlmtest(vector& residuals,vector& conditional_volatility,ulong lags,bool standardized = false)
{
WaldTestStatistic out;
vector resids = residuals;
if(standardized)
resids = resids/conditional_volatility;
if(resids.HasNan())
{
vector nresids = vector::Zeros(resids.Size() - resids.HasNan());
for(ulong i = 0, k = 0; i<resids.Size(); ++i)
if(MathClassify(resids[i]) == FP_NAN)
continue;
else
nresids[k++] = resids[i];
resids = nresids;
}
int nobs = (int)resids.Size();
vector resid2 = MathPow(resids,2.0);
if(!lags)
lags = ulong(ceil(12.0*pow(nobs/100.0,1.0/4.0)));
lags = MathMax(MathMin(resids.Size()/2 - 1,lags),1);
if(resid2.Size()<3)
{
Print(__FUNCTION__, " Test requires at least 3 non-nan observations ");
return out;
}
matrix matres = matrix::Zeros(resid2.Size(),1);
matres.Col(resid2,0);
matrix lag[];
if(!lagmat(matres,lag,lags,TRIM_BOTH,ORIGINAL_SEP))
{
Print(__FUNCTION__, " lagmat failed ");
return out;
}
matrix lagout;
if(!addtrend(lag[0],lagout,TREND_CONST_ONLY,true,HAS_CONST_SKIP))
{
Print(__FUNCTION__, " addtrend failed ");
return out;
}
lag[0] = lagout;
OLS ols;
if(!ols.Fit(lag[1].Col(0),lag[0]))
{
Print(__FUNCTION__, " OLS fitting failed ");
return out;
}
out.stat = nobs*ols.Rsqe();
if(standardized)
{
out.null = "Standardized residuals are homoskedastic";
out.alternative = "Standardized residuals are conditionally heteroskedastic";
}
else
{
out.null = "Residuals are homoskedastic";
out.alternative = "Residuals are conditionally heteroskedastic";
}
out.df = lags;
return out;
}
//+------------------------------------------------------------------+
//| helper |
//+------------------------------------------------------------------+
bool _forecast_pad(int count, matrix &forecasts[], matrix &out[])
{
ArrayResize(out,count+(int)forecasts.Size());
matrix fill = matrix::Zeros(forecasts[0].Rows(), forecasts[0].Cols());
fill.Fill(double("nan"));
for(int i = 0; i<count; ++i)
out[i] = fill;
for(int i = count; i<int(out.Size()); ++i)
out[i] = forecasts[i-count];
return true;
}
//+------------------------------------------------------------------+
//| helper |
//+------------------------------------------------------------------+
matrix _forecast_pad(int count, matrix &forecasts)
{
matrix fill = matrix::Zeros(count+forecasts.Rows(), forecasts.Cols());
fill.Fill(double("nan"));
for(ulong i = (ulong)count; i<fill.Rows(); ++i)
fill.Row(forecasts.Row(ulong(i-count)),i);
return fill;
}
//+------------------------------------------------------------------+
//| helper |
//+------------------------------------------------------------------+
vector _forecast_pad(int count, vector &forecasts)
{
vector fill = vector::Zeros(count+forecasts.Size());
fill.Fill(double("nan"));
for(ulong i = (ulong)count; i<fill.Size(); ++i)
fill[i] = forecasts[ulong(i-count)];
return fill;
}
//+------------------------------------------------------------------+
//| Generate mean forecasts from an AR-X model |
//+------------------------------------------------------------------+
matrix _ar_forecast(vector& y, ulong horizon, ulong start_index, double constant, vector & arp, matrix &x[], vector &exogp)
{
ulong t = y.Size();
ulong p = arp.Size();
ulong first,last;
vector col;
matrix fcasts = matrix::Zeros(t - start_index,p+horizon);
for(ulong i = 0; i<p; ++i)
{
first = start_index - p + i + 1;
last = t - p + i + 1;
col = np::sliceVector(y,long(first),long(last));
fcasts.Col(col,i);
}
vector arp_rev = arp;
np::reverseVector(arp_rev);
matrix mlt;
vector mat;
for(ulong i = p; i<horizon+p; ++i)
{
mlt = ((i-p)!=i)?np::sliceMatrixCols(fcasts,long(i-p),long(i)):matrix::Zeros(0,0);
mat = (arp_rev.Size()&&mlt.Cols())?mlt.MatMul(arp_rev):vector::Zeros(fcasts.Rows());
fcasts.Col(constant+mat,i);
if(x.Size())
{
mlt = matrix::Zeros(x[0].Rows(),x.Size());
for(uint k = 0; k<x.Size(); ++k)
mlt.Col(x[k].Col(i-p),k);
col = fcasts.Col(i);
col += mlt.MatMul(exogp);
fcasts.Col(col,i);
}
}
fcasts = np::sliceMatrixCols(fcasts,long(p));
return fcasts;
}
//+------------------------------------------------------------------+
//| impulse |
//+------------------------------------------------------------------+
vector _ar_to_impulse(ulong steps,vector& params)
{
ulong p = params.Size();
vector impulse = vector::Zeros(steps);
impulse[0] = 1.;
if(p == 0)
return impulse;
long k, st;
vector rparams,imp;
for(long i = 1; i<long(steps); ++i)
{
k = fmin(long(p) -1, i - 1);
st = fmax(i - long(p),0);
imp = np::sliceVector(impulse,st,i);
rparams = np::sliceVector(params,k,END_REVERSE,STEP_REVERSE);
if(imp.Size()==rparams.Size() && imp.Size())
impulse[i] = imp.Dot(rparams);
}
return impulse;
}
//+------------------------------------------------------------------+
//| Heterogeneous Autoregression (HAR) |
//+------------------------------------------------------------------+
class HARX: public CArchModel
{
protected:
//+------------------------------------------------------------------+
//|jacobian fvec implementation |
//+------------------------------------------------------------------+
class CObjectiveJacFVec: public CNDimensional_Jac
{
private:
vector m_sigma,m_bc;
matrix m_vb;
HARX* m_obj;
bool m_centered,m_individual;
double m_epsilon;
public:
CObjectiveJacFVec(void) {}
~CObjectiveJacFVec(void)
{
m_obj = NULL;
}
void SetParams(vector& s, vector& bc, matrix &vb, HARX &obj, bool individual=false, bool center = false, double ep=EMPTY_VALUE)
{
m_sigma = s;
m_bc = bc;
m_vb = vb;
m_obj = GetPointer(obj);
m_individual = individual;
m_centered = center;
m_epsilon = ep;
}
virtual void Jac(double &x[],double &fi[],CMatrixDouble &jac,CObject &obj) override
{
vector temp;
temp.Assign(x);
vector r = m_obj.objective(temp,m_sigma,m_bc,m_vb);
fi[0] = r[0];
matrix mjac = m_obj.jacobian(temp,m_sigma,m_bc,m_vb,m_individual,m_centered,m_epsilon);
jac = mjac;
}
virtual void Jac(CRowDouble &x,CRowDouble &fi,CMatrixDouble &jac,CObject &obj) override
{
vector xv = x.ToVector();
vector temp = m_obj.objective(xv,m_sigma,m_bc,m_vb);
fi = temp;
matrix mjac = m_obj.jacobian(xv,m_sigma,m_bc,m_vb,m_individual,m_centered,m_epsilon);
jac = mjac;
}
};
int m_extra_simulation_params;
virtual double _r2(vector &params) override
{
vector y = m_fit_y;
bool constant = false;
matrix x = m_fit_regressors;
if(x.Cols())
constant = (m_constant||implicit_constant(x));
if(constant)
{
if(x.Cols() ==1)
return 0.0;
y = y - y.Mean();
}
double tss = y.Dot(y);
if(tss<=0.)
return AL_NaN;
vector e = resids(params,EMPTY_VECTOR,EMPTY_MATRIX);
return 1.-(e.Dot(e))/tss;
}
bool _adjust_sample(long first_obs, long last_obs)
{
first_obs+=long(m_holdback);
if(last_obs<= first_obs)
{
Print(__FUNCTION__, " invalid indices specified ");
return false;
}
vector f = {double(first_obs),double(last_obs)};
m_fit_indices = f;
m_fit_y = adjust_y();
m_fit_x = adjust_x();
m_fit_regressors = (m_regressors.Rows())?np::sliceMatrixRows(m_regressors,long(m_fit_indices[0]),long(m_fit_indices[1])):matrix::Zeros(0,0);
m_vp.start(long(first_obs));
m_vp.stop(long(last_obs));
return true;
}
vector adjust_y(void)
{
if(m_model_spec.observations.Size())
return np::sliceVector(m_model_spec.observations,long(m_fit_indices[0]),long(m_fit_indices[1]));
return vector::Zeros(0);
}
matrix adjust_x(bool rows = true)
{
if(m_model_spec.exog_data.Rows())
{
if(rows)
return np::sliceMatrixRows(m_model_spec.exog_data,long(m_fit_indices[0]),long(m_fit_indices[1]));
else
return np::sliceMatrixCols(m_model_spec.exog_data,long(m_fit_indices[0]),long(m_fit_indices[1]));
}
else
return matrix::Zeros(0,0);
}
vector _fit_no_arch_normal_errors_params(void)
{
vector y = m_fit_y;
ulong nobs = y.Size();
if(nobs<m_num_params)
{
Print(__FUNCTION__, " insufficient data ");
return vector::Zeros(0);
}
matrix x = m_fit_regressors;
if(x.Cols())
{
ResetLastError();
matrix xpinv = np::pinv(x);
if(xpinv.HasNan())
{
Print(__FUNCTION__, " PInv() returned invalid number ", GetLastError());
return vector::Zeros(0);
}
return np::matmul(xpinv,y);
}
else
return vector::Zeros(0);
}
ArchModelResult _fit_no_arch_normal_errors(ENUM_COVAR_TYPE cov_type = COVAR_ROBUST)
{
ArchModelResult out;
matrix x = m_fit_regressors;
vector y = m_fit_y;
vector fitted,rparams;
fitted = rparams = vector::Zeros(0);
matrix xpxi = matrix::Zeros(0,0);
if(x.Cols())
{
matrix xpinv = np::pinv(x);
rparams = np::matmul(xpinv,y);
matrix xt = (x.Transpose().MatMul(x))/double(y.Size());
xpxi = xt.Inv();
fitted = x.MatMul(rparams);
}
vector e = np::minus(y,fitted);
double sigma2 = e.Dot(e)/double(y.Size());
vector params = rparams;
rparams.Resize(params.Size()+1);
rparams[params.Size()] = sigma2;
matrix hessian = matrix::Zeros(m_num_params+1,m_num_params+1);
np::matrixCopy(hessian,-1.*xpxi,0,long(m_num_params),1,0,long(m_num_params));
hessian[hessian.Rows()-1,hessian.Cols()-1] = -1.;
matrix param_cov = matrix::Zeros(0,0);
string nm;
switch(cov_type)
{
case COVAR_CLASSIC:
param_cov = sigma2 *(-1.*hessian);
param_cov[m_num_params,m_num_params] = 2.*pow(sigma2,2.);
param_cov/=double(y.Size());
nm = "classic_ols";
break;
case COVAR_ROBUST:
{
matrix scores = matrix::Zeros(y.Size(),m_num_params+1);
for(ulong i =0; i<m_num_params; ++i)
scores.Col(e*x.Col(i),i);
scores.Col(pow(e,2.)-sigma2,scores.Cols()-1);
matrix scorescov = scores.Transpose().MatMul(scores)/double(y.Size());
param_cov = hessian.MatMul(scorescov);
param_cov = param_cov.MatMul(hessian);
param_cov/=double(y.Size());
nm = "white";
}
break;
}
double r2 = _r2(params);
vector resids_ = vector::Zeros(m_model_spec.observations.Size());
resids_.Fill(AL_NaN);
np::vectorCopy(resids_,e,long(m_fit_indices[0]),long(m_fit_indices[1]));
vector vol = vector::Zeros(resids_.Size());
vol.Fill(AL_NaN);
double ss = sqrt(sigma2);
for(ulong i = ulong(m_fit_indices[0]); i<ulong(m_fit_indices[1]); vol[i] = ss, ++i);
double ll = _static_gaussian_loglikelihood(e);
m_params = out.params = rparams;
m_converged = (m_params.Size()>0);
out.param_cov = param_cov;
out.r2 = r2;
out.resid = resids_;
out.loglikelihood = ll;
out.fit_indices[0] = long(m_fit_indices[0]);
out.fit_indices[1] = long(m_fit_indices[1]);
return out;
}
bool _init_model(void)
{
ResetLastError();
if(m_model_spec.mean_lags.Size())
{
if(!_reformat_lags())
return false;
m_maxlags = ulong(m_model_spec.mean_lags.Max());
if(!m_holdback)
m_holdback = m_maxlags;
else
{
if(m_holdback<m_maxlags)
{
Print(__FUNCTION__, " hold_back is less then the minimum number given the lags selected");
m_holdback = m_maxlags;
}
}
}
if(!_check_specification())
return false;
ulong nobs_orig = m_model_spec.observations.Size();
vector reg_constant=vector::Zeros(0);
matrix reg_lags = matrix::Zeros(0,0);
if(m_constant)
reg_constant = vector::Ones(nobs_orig);
if(m_lags.Rows() && nobs_orig)
{
double maxlag = m_lags.Max();
matrix ymat(m_model_spec.observations.Size(),1);
ymat.Col(m_model_spec.observations,0);
matrix lagarray[];
if(!lagmat(ymat,lagarray,ulong(maxlag),TRIM_FORWARD,ORIGINAL_EX))
return false;
reg_lags = matrix::Zeros(nobs_orig,m_lags.Cols());
for(ulong i = 0; i<m_lags.Cols(); ++i)
{
matrix temp = np::sliceMatrixCols(lagarray[0],long(m_lags[0,i]),long(m_lags[1,i]));
vector vtemp = temp.Mean(1);
reg_lags.Col(vtemp,i);
}
}
m_regressors = matrix::Zeros(nobs_orig,ulong(nobs_orig>0)*(ulong(m_constant)+reg_lags.Cols()+m_model_spec.exog_data.Cols()));
if(m_constant && nobs_orig)
m_regressors.Col(reg_constant,0);
if(reg_lags.Cols())
for(ulong i = 0; i<reg_lags.Cols(); ++i)
m_regressors.Col(reg_lags.Col(i),i+ulong(m_constant));
if(m_model_spec.exog_data.Cols())
for(ulong i = 0; i<m_model_spec.exog_data.Cols(); ++i)
m_regressors.Col(m_model_spec.exog_data.Col(i),i+ulong(m_constant)+reg_lags.Cols());
if(GetLastError()!=0)
{
Print(__FUNCTION__, " error ", GetLastError());
return false;
}
return true;
}
virtual void _scalechanged(void) override
{
if(!_init_model())
Print(__FUNCTION__, " error ");
}
bool _check_specification(void)
{
if(m_model_spec.exog_data.Rows())
{
if(m_model_spec.exog_data.Rows()!=m_model_spec.observations.Size())
{
Print(__FUNCTION__, " regressors shape does not match input data ");
return false;
}
}
return true;
}
bool _reformat_lags(void)
{
if(!m_lags.Rows())
return true;
matrix lags = m_lags;
if(lags.Rows()==1)
{
if(lags.Min()<=0)
{
Print(__FUNCTION__, " invalid lags input ");
return false;
}
vector row = lags.Row(0);
row = np::unique(row);
matrix temp = matrix::Zeros(2,row.Size());
if(!temp.Row(row,0) || !temp.Row(row,1))
return false;
if(m_rotated)
{
vector v = np::sliceVector(row,0,long(row.Size()-1));
for(ulong i = 1; i<temp.Cols(); temp[0,i] = v[i-1], ++i);
temp[0,0] = 0.;
}
else
temp.Row(vector::Zeros(temp.Cols()),0);
m_lags = temp;
}
else
{
vector mins = lags.Min(1);
if(lags.Min()<=0 || mins[1]<mins[0])
{
Print(__FUNCTION__, " invalid lags input ");
return false;
}
if(!np::reverseMatrixRows(lags) || !np::sort(lags,true))
return false;
matrix testmat = matrix::Zeros(lags.Cols(),(ulong)lags.Max());
vector subtract = {1.,0.};
matrix sub = np::repeat_vector_as_rows_cols(subtract,lags.Cols(),false);
lags = lags-sub;
for(ulong i = 0; i<lags.Cols(); ++i)
np::matrixFill(testmat,1.0,long(i),long(i+1),STEP,long(lags[0,i]),long(lags[1,i]));
ulong rank = np::matrix_rank(testmat);
if(rank != lags.Cols())
{
Print(__FUNCTION__," lags contains redundant entries ");
return false;
}
if(m_rotated)
Print(__FUNCTION__, " Rotation is not available for this model specification ");
m_lags = lags;
}
return true;
}
bool _reformat_forecast_x(ulong start, ulong horizon, matrix& x[], matrix& out[])
{
if(!x.Size())
{
if(!m_model_spec.exog_data.Rows())
return true;
else
{
Print(__FUNCTION__, " error x function variable should not be empty since model specified with exogenous variables ");
return false;
}
}
else
if(!m_model_spec.exog_data.Rows())
{
Print(__FUNCTION__, " x is not None but the model does not contain any exogenous variables.");
return false;
}
ulong nx = m_model_spec.exog_data.Cols();
if(x.Size()!=uint(nx))
{
Print(__FUNCTION__, " the number of elements in x should match number of columns of m_model_spec.exog_data property ");
return false;
}
if(x[0].Cols()!=horizon)
{
Print(__FUNCTION__, " the number of values passed does not match the forizon of the forecasts ");
return false;
}
if(x[0].Rows()!=(m_model_spec.observations.Size()-start))
{
Print(__FUNCTION__," the shape of x does not have the proper dimensions ");
return false;
}
ArrayResize(out,x.Size());
if(x[0].Rows()>(m_model_spec.observations.Size()-start))
{
for(uint i = 0; i<x.Size(); ++i)
out[i] = np::sliceMatrixRows(x[i],long(start));
}
else
{
for(uint i = 0; i<x.Size(); ++i)
out[i] = x[i];
}
return true;
}
vector _har_to_ar(vector &params)
{
if(!m_maxlags)
return np::sliceVector(params,0,long(m_constant));
vector har = np::sliceVector(params,long(m_constant));
vector ar = vector::Zeros(m_maxlags);
vector temp;
for(ulong i = 0; i<m_lags.Cols(); ++i)
{
for(ulong j = ulong(m_lags[0,i]); j<ulong(m_lags[1,i]); ++j)
ar[j] += har[i]/(m_lags[1,i] - m_lags[0,i]);
}
if(m_constant)
{
vector a(ar.Size()+1);
a[0] = params[0];
for(ulong i = 1; i<a.Size(); a[i] = ar[i-1], ++i);
ar = a;
}
return ar;
}
vector _simulate_mean(vector &parameters, matrix &x, vector &errs, vector& initial_values, vector& cond_var)
{
ulong max_lag = (!m_lags.Rows())?0:(ulong)m_lags.Max();
ulong nobs_and_burn = errs.Size();
vector y = vector::Zeros(nobs_and_burn);
vector iv;
ulong iv_size = initial_values.Size();
if(iv_size && iv_size!=max_lag)
{
Print(__FUNCTION__, " initial_value has the wrong shape ");
return vector::Zeros(0);
}
else
if(!iv_size)
iv.Resize(max_lag);
else
iv = initial_values;
for(ulong i = 0; i<max_lag; y[i] = iv[i],++i);
ulong kx = x.Cols();
ulong ind;
vector temp,col;
for(ulong t = max_lag; t<nobs_and_burn; ++t)
{
ind = 0;
if(m_constant)
{
y[t] = parameters[ind];
ind+=1;
}
for(ulong lag = 0; lag<m_lags.Cols(); ++lag)
{
col = m_lags.Col(lag);
temp = np::sliceVector(y,long(t - (ulong)col[1]), long(t - ulong(col[0])));
y[t] += parameters[ind] * temp.Mean();
ind+=1;
}
for(ulong i = 0; i<kx; ++i)
{
y[t] += parameters[ind] * x[t,i];
ind += 1;
}
y[t] += errs[t];
}
return y;
}
ArchModelResult _fit_parameterless_model(ENUM_COVAR_TYPE cv, vector& backcast)
{
ArchModelResult out;
vector y = m_fit_y;
vector params = vector::Zeros(0);
matrix param_cov = matrix::Zeros(0,0);
ulong first_obs = (ulong)m_fit_indices[0];
ulong last_obs = (ulong)m_fit_indices[1];
vector resids_final = vector::Zeros(y.Size());
if(!np::vectorCopy(resids_final,y,first_obs,last_obs))
{
Print(__FUNCTION__, " failed vector copy ");
return out;
}
return out;
matrix var_bounds = m_vp.varianceBounds(y);
vector vol = vector::Zeros(y.Size());
m_vp.computeVariance(params,y,vol,backcast,var_bounds);
vol = sqrt(vol);
vector vol_final = vector::Zeros(y.Size());
vol_final.Fill(AL_NaN);
if(!np::vectorCopy(vol_final,vol,first_obs,last_obs))
{
Print(__FUNCTION__, " failed vector copy ");
return out;
}
double r2 = _r2(params);
vector ll = -1.*_loglikelihood(params,pow(vol,2)*vector::Ones(ulong(m_fit_indices[1] - m_fit_indices[0])),backcast,var_bounds);
out.params = params;
out.param_cov = param_cov;
out.r2 = r2;
out.resid = resids_final;
out.conditional_volatility = vol_final;
out.cov_type = cv;
out.loglikelihood = ll[0];
out.fit_indices[0] = long(m_fit_indices[0]);
out.fit_indices[1] = long(m_fit_indices[1]);
return out;
}
bool _initialize(ArchParameters &vol_dist_params)
{
if(m_model_spec.mean_model_type!=WRONG_VALUE && vol_dist_params.mean_model_type!=WRONG_VALUE && vol_dist_params.mean_model_type!=m_model_spec.mean_model_type)
{
m_initialized = false;
Print(__FUNCTION__" Incorrect initialization of mean model. \nMake sure input parameters correspond with the correct class ");
return false;
}
if(m_model_spec.mean_model_type!=WRONG_VALUE && vol_dist_params.mean_model_type==WRONG_VALUE)
vol_dist_params.mean_model_type = m_model_spec.mean_model_type;
m_model_spec = vol_dist_params;
switch(m_model_spec.mean_model_type)
{
case MEAN_CONSTANT:
m_model_spec.mean_lags = vector::Zeros(0);
m_name = "Constant Mean";
m_model_spec.include_constant = true;
m_model_spec.use_har_rotation = false;
break;
case MEAN_AR:
m_model_spec.use_har_rotation = false;
m_model_spec.exog_data = matrix::Zeros(0,0);
m_name = "AR";
break;
case MEAN_ARX:
m_model_spec.use_har_rotation = false;
m_name ="AR-X";
break;
case MEAN_HAR:
m_model_spec.exog_data = matrix::Zeros(0,0);
m_name = "HAR";
break;
case MEAN_HARX:
m_name = "HAR-X";
break;
case MEAN_ZERO:
m_model_spec.exog_data=matrix::Zeros(0,0);
m_model_spec.mean_lags = vector::Zeros(0);
m_model_spec.include_constant = false;
m_model_spec.use_har_rotation = false;
m_name = "Zero Mean";
break;
default:
m_name = "HAR-X";
break;
}
m_fit_indices = vector::Zeros(2);
m_fit_indices[1] = (m_model_spec.observations.Size())?double(m_model_spec.observations.Size()):-1.;
if(m_model_spec.mean_lags.Size())
{
switch(m_model_spec.mean_model_type)
{
case MEAN_AR:
case MEAN_ARX:
m_lags = matrix::Zeros(2,m_model_spec.mean_lags.Size());
m_lags.Row(m_model_spec.mean_lags,0);
m_lags.Row(m_model_spec.mean_lags,1);
break;
case MEAN_HAR:
case MEAN_HARX:
m_lags = matrix::Zeros(1,m_model_spec.mean_lags.Size());
m_lags.Row(m_model_spec.mean_lags,0);
break;
}
}
else
m_lags = matrix::Zeros(0,0);
m_constant = m_model_spec.include_constant;
m_rescale = m_model_spec.is_rescale_enabled;
m_rotated = m_model_spec.use_har_rotation;
m_holdback = m_model_spec.holdout_size;
m_initialized = _init_model();
if(!m_initialized)
return false;
m_num_params = num_params();
if(CheckPointer(m_vp)==POINTER_DYNAMIC)
delete m_vp;
m_vp = NULL;
switch(vol_dist_params.vol_model_type)
{
case VOL_CONST:
m_vp = new CConstantVariance(m_model_spec.vol_rng_seed,m_model_spec.min_bootstrap_sims);
break;
case VOL_ARCH:
m_vp = new CArchProcess(m_model_spec.garch_p,m_model_spec.vol_rng_seed,m_model_spec.min_bootstrap_sims);
break;
case VOL_GARCH:
m_vp = new CGarchProcess(m_model_spec.garch_p,m_model_spec.garch_q,m_model_spec.vol_rng_seed,m_model_spec.min_bootstrap_sims);
break;
default:
m_vp = new CConstantVariance(m_model_spec.vol_rng_seed,m_model_spec.min_bootstrap_sims);
break;
}
if(CheckPointer(m_distribution)==POINTER_DYNAMIC)
delete m_distribution;
m_distribution = NULL;
switch(vol_dist_params.dist_type)
{
case DIST_NORMAL:
m_distribution = new CNormal();
break;
default:
m_distribution = new CNormal();
break;
}
m_initialized = (
m_distribution!=NULL &&
m_vp!=NULL &&
m_vp.is_initialized() &&
m_distribution.initialize(m_model_spec.dist_init_params,
m_model_spec.dist_rng_seed)
);
return m_initialized;
}
public:
HARX(void):m_extra_simulation_params(0)
{
m_model_spec.mean_model_type = MEAN_HARX;
}
HARX(ArchParameters& model_specification)
{
model_specification.mean_model_type = MEAN_HARX;
initialize(model_specification);
}
~HARX(void)
{
deinitialize();
}
bool initialize(ArchParameters& vol_dist_params)
{
return _initialize(vol_dist_params);
}
matrix get_x(void) { return m_model_spec.exog_data; }
matrix get_regressors(void) { return m_regressors; }
vector get_y(void) { return m_model_spec.observations; }
virtual vector resids(vector &params, vector &y, matrix &regressors) override
{
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return EMPTY_VECTOR;
}
vector vec;
if(!y.Size())
{
vec = m_fit_regressors.MatMul(params);
return np::minus(m_fit_y,vec);
}
else
{
vec = regressors.MatMul(params);
return np::minus(y,vec);
}
}
virtual ulong num_params(void)
{
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return 0;
}
return m_regressors.Cols();
}
bool set_volatility_process(CVolatilityProcess* &vp)
{
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return false;
}
if(CheckPointer(vp) == POINTER_INVALID)
{
Print(__FUNCTION__, " invalid pointer ");
return false;
}
if(!vp.is_initialized())
{
Print(__FUNCTION__, " volatility process has not been initialized ");
return false;
}
if(CheckPointer(m_vp) == POINTER_DYNAMIC)
delete m_vp;
m_vp = NULL;
m_model_spec.vol_model_type = vp.volatilityprocess();
m_vp = vp;
m_delete_vp = false;
return true;
}
bool set_distribution(CDistribution* &dist)
{
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return false;
}
if(CheckPointer(dist) == POINTER_INVALID)
{
Print(__FUNCTION__, " invalid pointer ");
return false;
}
if(!dist.is_initialized())
{
Print(__FUNCTION__, " distribution has not been initialized ");
return false;
}
if(CheckPointer(m_distribution) == POINTER_DYNAMIC)
delete m_distribution;
m_distribution = NULL;
m_model_spec.dist_type = dist.distribution_type();
m_distribution = dist;
m_delete_dist = false;
return true;
}
virtual matrix simulate(vector &params, ulong nobs, ulong burn, vector &initial_vals,matrix &x, vector &initial_vals_vol)
{
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return EMPTY_MATRIX;
}
ulong kx = x.Cols();
ulong mc = 0;
if(kx && x.Rows()<nobs+burn)
{
Print(__FUNCTION__, " x must have nobs + burn rows");
return matrix::Zeros(0,0);
}
if(m_model_spec.mean_lags.Size())
mc = ulong(m_constant)+m_lags.Cols()+kx+m_extra_simulation_params;
ulong vc = m_vp.numParams();
ulong dc = m_distribution.numParams();
ulong numparams = mc+vc+dc;
if(params.Size()!=numparams)
{
Print(__FUNCTION__, " params has the wrong number of elements.");
return matrix::Zeros(0,0);
}
vector allparams[];
if(!_parse_parameters(params,allparams))
return matrix::Zeros(0,0);
BootstrapRng rng(m_distribution.distribution_type(),allparams[2],(int)m_distribution.randomState());
matrix sim_data = m_vp.simulate(allparams[1],nobs+burn,rng,burn,initial_vals_vol.Size()?initial_vals_vol[0]:0.0);
vector errors = sim_data.Row(0);
errors = np::sliceVector(errors,long(burn));
vector vol = sqrt(sim_data.Row(1));
vol = np::sliceVector(vol,long(burn));
vector y = _simulate_mean(allparams[0],x,errors,initial_vals,sim_data.Row(1));
y = np::sliceVector(y,long(burn));
matrix out = matrix::Zeros(y.Size(),3);
if(!out.Col(y,0) || !out.Col(vol,1) || !out.Col(errors,2))
{
Print(__FUNCTION__, " ", GetLastError());
return matrix::Zeros(0,0);
}
return out;
}
ArchForecast forecast(ulong horizon = 1, long start = -1, ENUM_FORECAST_METHOD method=FORECAST_ANALYTIC, ulong simulations=1000, uint seed=0)
{
return forecast(EMPTY_VECTOR,EMPTY_MATRIX_ARRAY,horizon,start,method,simulations,seed);
}
ArchForecast forecast(matrix& x[],ulong horizon = 1, long start = -1, ENUM_FORECAST_METHOD method=FORECAST_ANALYTIC, ulong simulations=1000, uint seed=0)
{
return forecast(EMPTY_VECTOR,x,horizon,start,method,simulations,seed);
}
virtual ArchForecast forecast(vector& params, matrix& x[],ulong horizon = 1, long start = -1, ENUM_FORECAST_METHOD method=FORECAST_ANALYTIC, ulong simulations=1000, uint seed=0)
{
ArchForecast out;
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return out;
}
if(!m_fit_y.Size())
{
long from = 0;
long to = long(m_model_spec.observations.Size());
if(!_adjust_sample(from,to))
return out;
}
long earliest = long(m_fit_indices[0]);
long default_start = long(m_fit_indices[1]);
default_start = MathMax(0,default_start-1);
long start_index = (start>-1 && start<=default_start)?start:default_start;
if(start_index<(earliest-1))
{
Print(__FUNCTION__," Due to backcasting and/or data availability start cannot be less "\
"than the index of the largest value in the right-hand-side "\
"variables used to fit the first observation.");
return out;
}
vector allparams[];
if(!CArchModel::_parse_parameters(params.Size()?params:m_params,allparams))
{
Print(__FUNCTION__, " failed to parse the parameters ");
return out;
}
vector res = resids(allparams[0],vector::Zeros(0),matrix::Zeros(0,0));
vector bc = m_vp.backCast(res);
vector ys = np::sliceVector(m_model_spec.observations,long(earliest));
matrix xs = np::sliceMatrixRows(m_regressors,long(earliest));
vector full_res = resids(allparams[0],ys,xs);
matrix vb = m_vp.varianceBounds(full_res,2.);
BootstrapRng Rng(m_distribution.distribution_type(),allparams[2],(int)m_distribution.randomState());
long vstart = MathMax(0,start_index - earliest);
VarianceForecast vfcast = m_vp.forecast(allparams[1],full_res,bc,vb,Rng,seed,vstart,horizon,method,simulations);
if(!vfcast.forecasts.Cols())
return out;
matrix var_fcasts = vfcast.forecasts;
if(start_index<earliest)
var_fcasts = _forecast_pad(int(earliest-start_index),var_fcasts);
vector arp = _har_to_ar(allparams[0]);
ulong nexog = m_model_spec.exog_data.Cols();
vector exog_p = (nexog)?np::sliceVector(allparams[0],-1*long(nexog)):vector::Zeros(0);
double constant = m_constant?arp[0]:0.0;
vector dynp = (arp.Size()>ulong(m_constant))?np::sliceVector(arp,long(m_constant)):vector::Zeros(0);
matrix expected_x[];
if(!_reformat_forecast_x(ulong(start_index),horizon,x,expected_x))
{
Print(__FUNCTION__, " reformating error ");
return out;
}
matrix mean_fcast = _ar_forecast(m_model_spec.observations,horizon,start_index,constant,dynp,expected_x,exog_p);
if(!mean_fcast.Cols())
return out;
vector impulse = _ar_to_impulse(horizon,dynp);
matrix longrun_var_fcasts = var_fcasts;
matrix tempm;
vector tempv,lrf;
for(ulong i = 0; i<horizon; ++i)
{
tempm = np::sliceMatrixCols(var_fcasts,0,long(i+1));
tempv = np::sliceVector(impulse,long(i),END_REVERSE,STEP_REVERSE);
tempv = pow(tempv,2.);
lrf = tempm.MatMul(tempv);
longrun_var_fcasts.Col(lrf,i);
}
matrix variance_paths[],mean_paths[];
matrix shocks[],long_run_variance_paths[];
if(method == FORECAST_BOOTSTRAP || method == FORECAST_SIMULATION)
{
ArrayResize(variance_paths,vfcast.forecastpaths.Size());
for(uint i = 0; i<variance_paths.Size(); ++i)
variance_paths[i] = vfcast.forecastpaths[i];
ArrayResize(shocks,vfcast.shocks.Size());
for(uint i = 0; i<shocks.Size(); ++i)
shocks[i] = vfcast.shocks[i];
if(start_index<earliest)
{
_forecast_pad(int(earliest-start_index),variance_paths,variance_paths);
_forecast_pad(int(earliest-start_index),shocks,shocks);
}
ArrayResize(long_run_variance_paths,variance_paths.Size());
for(uint i = 0; i<variance_paths.Size(); ++i)
long_run_variance_paths[i] = vfcast.forecastpaths[i];
matrix imps;
vector _impulses;
for(ulong i = 0; i<horizon; ++i)
{
_impulses=np::sliceVector(impulse,long(i),END_REVERSE,STEP_REVERSE);
imps = matrix::Zeros(_impulses.Size(),1);
_impulses = pow(_impulses,2.);
imps.Col(_impulses,0);
for(uint k = 0; k<variance_paths.Size(); ++k)
{
tempm = np::sliceMatrixCols(variance_paths[k],BEGIN,long(i+1));
tempm = tempm.MatMul(imps);
long_run_variance_paths[k].Col(tempm.Col(0),i);
}
}
ulong t = m_model_spec.observations.Size();
ArrayResize(mean_paths,shocks.Size());
for(uint i =0; i<mean_paths.Size(); ++i)
mean_paths[i] = matrix::Zeros(shocks[0].Rows(),m_maxlags+horizon);
vector dynp_rev = dynp;
np::reverseVector(dynp_rev);
for(ulong i = start_index; i<t; ++i)
{
ulong ploc = i - start_index;
tempv = np::sliceVector(m_model_spec.observations,long(i-m_maxlags+1),long(i+1));
tempm = np::repeat_vector_as_rows_cols(tempv,mean_paths[ploc].Rows());
np::matrixCopy(mean_paths[ploc],tempm,BEGIN,END,STEP,BEGIN,long(m_maxlags));
for(ulong j = 0; j<horizon; ++j)
{
tempm = np::sliceMatrixCols(mean_paths[ploc],long(j),long(m_maxlags+j));
tempv = constant + tempm.MatMul(dynp_rev) +shocks[ploc].Col(j);
mean_paths[ploc].Col(tempv,m_maxlags+j);
if(expected_x.Size())
{
tempv = vector::Zeros(mean_paths[ploc].Rows());
for(uint z = 0; z<expected_x.Size(); ++z)
tempv[z] = expected_x[z][ploc,j];
double dotproduct = tempv.Dot(exog_p);
for(ulong k = 0; k<mean_paths[ploc].Rows(); ++k)
mean_paths[ploc][k,m_maxlags+j] += dotproduct;
}
}
}
for(uint i = 0; i<mean_paths.Size(); ++i)
mean_paths[i] = np::sliceMatrixCols(mean_paths[i],long(m_maxlags));
}
return ArchForecast(start_index,mean_fcast,longrun_var_fcasts,var_fcasts,mean_paths,shocks,long_run_variance_paths,variance_paths);
}
ArchModelFixedResult fix(vector& params,long first_obs = 0, long last_obs = -1)
{
ArchModelFixedResult out;
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return out;
}
if(!_adjust_sample(first_obs,last_obs))
return out;
vector sv = starting_values();
vector res = resids(sv,EMPTY_VECTOR,EMPTY_MATRIX);
vector sigma2 = vector::Zeros(res.Size());
vector back_cast= m_vp.backCast(res);
m_backcast = back_cast;
matrix vb = m_vp.varianceBounds(res);
vector logl= _loglikelihood(params,sigma2,back_cast,vb)*(-1.0);
vector parameters[];
if(!_parse_parameters(params,parameters))
return out;
vector vol = vector::Zeros(res.Size());
m_vp.computeVariance(parameters[1],res,vol,back_cast,vb);
vol = sqrt(vol);
first_obs = long(m_fit_indices[0]);
last_obs = long(m_fit_indices[1]);
vector res_final = vector::Zeros(res.Size());
res_final.Fill(AL_NaN);
np::vectorCopy(res_final,res,long(first_obs),long(last_obs));
vector vol_final = vector::Zeros(vol.Size());
vol_final.Fill(AL_NaN);
np::vectorCopy(vol_final,vol,long(first_obs),long(last_obs));
out.params = params;
out.resid = res_final;
out.conditional_volatility = vol_final;
out.loglikelihood = logl[0];
return out;
}
ArchModelResult fit(double scaling = 1.0, uint maxits = 0,ENUM_COVAR_TYPE cov_type = COVAR_ROBUST, long first = 0, long last = -1, double tol = 1e-9, bool guardsmoothness=false, double gradient_test_step = 0.0)
{
return fit(EMPTY_VECTOR,EMPTY_VECTOR,scaling,maxits,cov_type,first,last,tol,guardsmoothness,gradient_test_step);
}
ArchModelResult fit(vector& startingvalues,vector& backcast,double scaling = 1.0, uint maxits = 0,ENUM_COVAR_TYPE cov_type = COVAR_ROBUST, long first = 0, long last = -1, double tol = 1e-9, bool guardsmoothness=false, double gradient_test_step = 0.0)
{
ArchModelResult out;
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return out;
}
if(!m_model_spec.observations.Size())
{
Print(__FUNCTION__," Cannot estimate model without data.");
return out;
}
vector offsets(3);
offsets[0] = (double)m_num_params;
offsets[1] = (double)m_vp.numParams();
offsets[2] = (double)m_distribution.numParams();
double total_params = offsets.Sum();
if(last<0 || last<first)
last = long(m_model_spec.observations.Size());
if(!_adjust_sample(first,last))
return out;
bool hascloseform = (offsets[2] == 0.0 && m_vp.closeForm() && m_vp.volatilityprocess()==VOL_CONST);
vector sv = starting_values();
vector res = resids(sv,vector::Zeros(0),matrix::Zeros(0,0));
_checkscale(res);
if(m_scale!=1.)
{
m_model_spec.observations = m_model_spec.observations*m_scale;
_scalechanged();
_adjust_sample(first,last);
sv = starting_values();
res = resids(sv,vector::Zeros(0),matrix::Zeros(0,0));
}
vector bcast;
if(!backcast.Size())
bcast = m_vp.backCast(res);
else
bcast = m_vp.backCastTransform(backcast);
if(hascloseform)
return _fit_no_arch_normal_errors(cov_type);
if(total_params == 0.0)
return _fit_parameterless_model(cov_type,bcast);
vector sigma2 = vector::Zeros(res.Size());
m_backcast = bcast;
vector sv_v = m_vp.startingValues(res);
m_var_bounds = m_vp.varianceBounds(res);
matrix vb = m_var_bounds;
m_vp.computeVariance(sv_v,res,sigma2,bcast,vb);
vector std_res = res/sqrt(sigma2);
Constraints cst[3];
cst[0] = constraints();
cst[1] = m_vp.constraints();
cst[2] = m_distribution.constraints();
vector num_cons(3);
for(uint i = 0; i<cst.Size(); ++i)
num_cons[i] = (double)cst[i]._one.Rows();
matrix a = matrix::Zeros(ulong(num_cons.Sum()),ulong(total_params)+1);
vector b = vector::Zeros(ulong(num_cons.Sum()));
long ren,cen,rst,ct;
vector nc,of;
for(uint i = 0; i<3; ++i)
{
nc = np::sliceVector(num_cons,0,long(i+1));
of = np::sliceVector(offsets,0,long(i+1));
ren = long(nc.Sum());
cen = long(of.Sum());
rst = ren - long(num_cons[i]);
ct = cen - long(offsets[i]);
if(ren-rst>0)
{
np::matrixCopy(a,cst[i]._one,rst,ren,STEP,ct,cen);
np::vectorCopy(b,cst[i]._two,rst,ren);
}
}
a.Col(b,ulong(total_params));
b = vector::Ones(a.Rows());
matrix bound[3];
bound[0] = bounds();
bound[1] = m_vp.bounds(res);
bound[2] = m_distribution.bounds(res);
matrix all_bounds(2,bound[0].Cols()+bound[1].Cols()+bound[2].Cols());
long cols = 0;
for(uint i = 0; i<bound.Size(); ++i)
{
if(bound[i].Cols())
{
cols+=(i)?long(bound[i-1].Cols()):0;
np::matrixCopyCols(all_bounds,bound[i],cols,long(cols+bound[i].Cols()));
}
}
if(startingvalues.Size())
{
bool valid = (startingvalues.Size()==ulong(total_params));
matrix aa = np::sliceMatrixCols(a,0,long(a.Cols()-1));
vector bb = a.Col(a.Cols()-1);
if(aa.Rows()&&valid)
{
vector temp = aa.MatMul(startingvalues) - bb;
valid = (valid && temp.Min()>0.0);
}
for(uint i = 0; i<startingvalues.Size(); ++i)
valid = (valid && all_bounds[0,i]<=startingvalues[i] && startingvalues[i]<=all_bounds[1,i]);
if(!valid)
startingvalues = EMPTY_VECTOR;
}
if(!startingvalues.Size())
{
sv = starting_values();
ulong ss = sv.Size();
vector temp = m_distribution.startingValues(std_res);
sv.Resize(ss+sv_v.Size()+temp.Size());
if(sv_v.Size())
np::vectorCopy(sv,sv_v,long(ss),long(ss+sv_v.Size()));
if(temp.Size())
np::vectorCopy(sv,temp,long(ss+sv_v.Size()));
}
else
sv = startingvalues;
CMinNLCState optim_state;
CRowDouble _initial_values = sv;
CRowDouble _ones = vector::Ones(sv.Size());
if(scaling>0.0)
_ones*=scaling;
CRowDouble bl = all_bounds.Row(0);
CRowDouble bu = all_bounds.Row(1);
CMatrixDouble _constraints = a;
CAlglib::MinNLCCreate(_initial_values,optim_state);
CAlglib::MinNLCSetCond(optim_state,tol,int(maxits));
CAlglib::MinNLCSetScale(optim_state,_ones);
CAlglib::MinNLCSetAlgoSLP(optim_state);
CAlglib::MinNLCSetBC(optim_state,bl,bu);
CRowInt intones;
intones.Resize(_constraints.Rows());
intones.Fill(1);
CAlglib::MinNLCSetLC(optim_state,_constraints,intones,0);
if(guardsmoothness)
CAlglib::MinNLCOptGuardSmoothness(optim_state);
if(gradient_test_step>0.0)
CAlglib::MinNLCOptGuardGradient(optim_state,gradient_test_step);
CMinNLCReport rep;
CRowDouble solution;
//--- set up other objective function arguments
CObjectiveJacFVec fvec;
fvec.SetParams(sigma2,bcast,vb,this);
//---
CNDimensional_Rep f_rep;
CObject object;
//---optimize
CAlglib::MinNLCOptimize(optim_state,fvec,f_rep,object);
//---get results of optimization
CAlglib::MinNLCResults(optim_state,solution,rep);
//---check for failed convergence
if(rep.m_terminationtype<0)
{
Print(__FUNCTION__," failed convergence ");
m_converged = false;
return out;
}
else
m_converged = true;
//---
vector allparams[];
//---
m_params = solution.ToVector();
//--- loglikelihood for estimated parameters
vector llh = _loglikelihood(m_params,sigma2,bcast,vb);
//---
_parse_parameters(m_params,allparams);
//---
res = resids(allparams[0],EMPTY_VECTOR,EMPTY_MATRIX);
vector vol = vector::Zeros(res.Size());
//---
m_vp.computeVariance(allparams[1],res,vol,bcast,vb);
//---
vol = sqrt(vol);
//---
double r2 = _r2(allparams[0]);
//---
long f_obs,l_obs;
f_obs = (long)m_fit_indices[0];
l_obs = (long)m_fit_indices[1];
//---
vector res_final,vol_final;
res_final = vol_final = m_model_spec.observations;
res_final.Fill(AL_NaN);
vol_final = res_final;
//---
np::vectorCopy(res_final,res,f_obs,l_obs);
np::vectorCopy(vol_final,vol,f_obs,l_obs);
matrix pcov = compute_param_cov(m_params,bcast,(cov_type==COVAR_ROBUST));
return ArchModelResult(m_params,pcov,r2,res_final,vol_final,cov_type,-1.0*llh[0],f_obs,l_obs);
}
};
//+------------------------------------------------------------------+
//| HAR model |
//+------------------------------------------------------------------+
class HAR: public HARX
{
public:
HAR(void)
{
m_model_spec.mean_model_type = MEAN_HAR;
}
HAR(ArchParameters& model_specification)
{
model_specification.mean_model_type = MEAN_HAR;
initialize(model_specification);
}
~HAR(void)
{
deinitialize();
}
};
//+------------------------------------------------------------------+
//| The constant mean model |
//+------------------------------------------------------------------+
class ConstantMean: public HARX
{
public:
ConstantMean(void)
{
m_model_spec.mean_model_type = MEAN_CONSTANT;
}
ConstantMean(ArchParameters& model_specification)
{
model_specification.mean_model_type = MEAN_CONSTANT;
initialize(model_specification);
}
~ConstantMean(void)
{
deinitialize();
}
virtual ulong num_params(void) override
{
return 1;
}
virtual vector resids(vector& params, vector& y, matrix& regressors) override
{
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return EMPTY_VECTOR;
}
if(!y.Size())
return np::minus(m_fit_y,params);
return np::minus(y,params);
}
virtual matrix simulate(vector &params, ulong nobs, ulong burn, vector &initial_vals,matrix &x, vector &initial_vals_vol)
{
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return EMPTY_MATRIX;
}
if(initial_vals.Size() || x.Cols())
{
Print(__FUNCTION__, " Both initial value and x must be none when simulating a constant mean process.");
return matrix::Zeros(0,0);
}
vector allparams[];
if(!_parse_parameters(params,allparams))
{
Print(__FUNCTION__, " failed to parse parameters ");
return matrix::Zeros(0,0);
}
BootstrapRng Rng(m_distribution.distribution_type(),allparams[2],(int)m_distribution.randomState());
matrix sim_values = m_vp.simulate(allparams[1],nobs+burn,Rng,burn,initial_vals_vol.Size()?initial_vals_vol[0]:0.0);
vector ers = sim_values.Row(0);
vector y = ers+allparams[0];
vector vol = sqrt(sim_values.Row(1));
y = np::sliceVector(y,long(burn));
vol = np::sliceVector(vol,long(burn));
ers = np::sliceVector(ers,long(burn));
matrix out(y.Size(),3);
if(!out.Col(y,0) ||!out.Col(vol,1) ||!out.Col(ers,2))
{
Print(__FUNCTION__, " error ", GetLastError());
return matrix::Zeros(0,0);
}
return out;
}
};
//+------------------------------------------------------------------+
//|The zero mean model |
//+------------------------------------------------------------------+
class ZeroMean: public HARX
{
public:
ZeroMean(void)
{
m_model_spec.mean_model_type = MEAN_ZERO;
}
ZeroMean(ArchParameters& model_specification)
{
model_specification.mean_model_type = MEAN_ZERO;
initialize(model_specification);
}
~ZeroMean(void)
{
deinitialize();
}
virtual ulong num_params(void) override
{
return 0;
}
virtual vector resids(vector& params, vector& y, matrix& regressors) override
{
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return EMPTY_VECTOR;
}
if(!y.Size())
return m_fit_y;
return y;
}
virtual matrix simulate(vector &params, ulong nobs, ulong burn, vector &initial_vals,matrix &x, vector &initial_vals_vol)
{
if(!is_initialized())
{
Print(__FUNCTION__, " object instance was not successfully initialized ");
return EMPTY_MATRIX;
}
if(initial_vals.Size() || x.Cols())
{
Print(__FUNCTION__, " Both initial value and x must be none when simulating a constant mean process.");
return matrix::Zeros(0,0);
}
vector allparams[];
if(!_parse_parameters(params,allparams))
{
Print(__FUNCTION__, " failed to parse parameters ");
return matrix::Zeros(0,0);
}
BootstrapRng Rng(m_distribution.distribution_type(),allparams[2],(int)m_distribution.randomState());
matrix sim_values = m_vp.simulate(allparams[1],nobs+burn,Rng,burn,initial_vals_vol[0]);
vector ers = sim_values.Row(0);
vector y = ers;
vector vol = sqrt(sim_values.Row(1));
y = np::sliceVector(y,long(burn));
vol = np::sliceVector(vol,long(burn));
ers = np::sliceVector(ers,long(burn));
matrix out(y.Size(),3);
if(!out.Col(y,0) ||!out.Col(vol,1) ||!out.Col(ers,2))
{
Print(__FUNCTION__, " error ", GetLastError());
return matrix::Zeros(0,0);
}
return out;
}
};
//+------------------------------------------------------------------+
//| The AR model |
//+------------------------------------------------------------------+
class AR: public HARX
{
public:
AR(void)
{
m_model_spec.mean_model_type = MEAN_AR;
}
AR(ArchParameters& model_specification)
{
model_specification.mean_model_type = MEAN_AR;
initialize(model_specification);
}
~AR(void)
{
deinitialize();
}
};
//+------------------------------------------------------------------+
//| The AR-X model |
//+------------------------------------------------------------------+
class ARX: public HARX
{
public:
ARX(void)
{
m_model_spec.mean_model_type = MEAN_ARX;
}
ARX(ArchParameters& model_specification)
{
model_specification.mean_model_type = MEAN_ARX;
initialize(model_specification);
}
~ARX(void)
{
deinitialize();
}
};
//+------------------------------------------------------------------