docs: @Override
  	public Map<String, Object> getBestParameters(Map<String, Object> guess) {
  
  		// Create the objective function for the solver
  		class GARCHMaxLikelihoodFunction implements MultivariateFunction, Serializable {
  
  			private static final long serialVersionUID = 7072187082052755854L;
  
  			@Override
  			public double value(double[] variables) {
  				/*
  				 * Transform variables: The solver variables are in (-\infty, \infty).
  				 * We transform the variable to the admissible domain for GARCH, that is
  				 * omega > 0, 0 < alpha < 1, 0 < beta < (1-alpha), displacement > lowerBoundDisplacement  ??????
  				 * ???? usually for GARCH the restrictions are written like omega > 0, alpha > 0, beta > 0, and alpha + beta < 1
  				 */
  				double omega	= Math.exp(variables[0]);
  				double mucorr	= Math.exp(-Math.exp(-variables[1]));
  				double muema	= Math.exp(-Math.exp(-variables[2]));
  				double beta		= mucorr * muema;
  				double alpha	= mucorr - beta;
  				//				double alpha = 1.0/(1.0+Math.exp(-variables[1]));
  				//				double beta = (1.0-alpha)*1.0/(1.0+Math.exp(-variables[2]));
  				double displacementNormed = 1.0/(1.0+Math.exp(-variables[3]));
  				double displacement = (upperBoundDisplacement-lowerBoundDisplacement)*displacementNormed+lowerBoundDisplacement;
  
  				double logLikelihood = getLogLikelihoodForParameters(omega,alpha,beta,displacement);
  
  				// Penalty to prevent solver from hitting the bounds
  				logLikelihood -= Math.max(1E-30-omega,0)/1E-30;
  				logLikelihood -= Math.max(1E-30-alpha,0)/1E-30;
  				logLikelihood -= Math.max((alpha-1)+1E-30,0)/1E-30;
  				logLikelihood -= Math.max(1E-30-beta,0)/1E-30;
  				logLikelihood -= Math.max((beta-1)+1E-30,0)/1E-30;
  				logLikelihood -= Math.max(1E-30-displacementNormed,0)/1E-30;
  				logLikelihood -= Math.max((displacementNormed-1)+1E-30,0)/1E-30;
  
  				return logLikelihood;
  			}
  
  		}
  		final GARCHMaxLikelihoodFunction objectiveFunction = new GARCHMaxLikelihoodFunction();
  
  		// Create a guess for the solver
  		double guessOmega = 1.0;
  		double guessAlpha = 0.2;
  		double guessBeta = 0.2;
  		double guessDisplacement = (lowerBoundDisplacement + upperBoundDisplacement) / 2.0;
  		if(guess != null) {
  			// A guess was provided, use that one
  			guessOmega			= (Double)guess.get("Omega");
  			guessAlpha			= (Double)guess.get("Alpha");
  			guessBeta			= (Double)guess.get("Beta");
  			guessDisplacement	= (Double)guess.get("Displacement");
  		}
  
  		// Constrain guess to admissible range
  		guessOmega			= restrictToOpenSet(guessOmega, 0.0, Double.MAX_VALUE);
  		guessAlpha			= restrictToOpenSet(guessAlpha, 0.0, 1.0);
  		guessBeta			= restrictToOpenSet(guessBeta, 0.0, 1.0-guessAlpha);
  		guessDisplacement	= restrictToOpenSet(guessDisplacement, lowerBoundDisplacement, upperBoundDisplacement);
  
  		double guessMucorr	= guessAlpha + guessBeta;
  		double guessMuema	= guessBeta / (guessAlpha+guessBeta);
  
  		// Transform guess to solver coordinates
  		double[] guessParameters = new double[4];
  		guessParameters[0] = Math.log(guessOmega);
  		guessParameters[1] = -Math.log(-Math.log(guessMucorr));
  		guessParameters[2] = -Math.log(-Math.log(guessMuema));
  		guessParameters[3] = -Math.log(1.0/((guessDisplacement-lowerBoundDisplacement)/(upperBoundDisplacement-lowerBoundDisplacement))-1.0);
  
  		// Seek optimal parameter configuration
  		Optimizer lm = new LevenbergMarquardt(guessParameters, new double[] { 1000 }, 10*maxIterations, 2) {
  			private static final long serialVersionUID = 8030873619034187741L;
  
  			@Override
  			public void setValues(double[] arg0, double[] arg1) {
  				arg1[0] = objectiveFunction.value(arg0);
  			}
  		};
  
  		double[] bestParameters = null;
  
  		boolean isUseLM = false;
  
  		if(isUseLM) {
  			try {
  				lm.run();
  			} catch (SolverException e1) {
  				// TODO Auto-generated catch block
  				e1.printStackTrace();
  			}
  			bestParameters = lm.getBestFitParameters();
  		}
  		else {
  			org.apache.commons.math3.optimization.direct.CMAESOptimizer optimizer2 = new org.apache.commons.math3.optimization.direct.CMAESOptimizer();
  
  			try {
  				PointValuePair result = optimizer2.optimize(
  						maxIterations,
  						objectiveFunction,
  						GoalType.MAXIMIZE,
  						guessParameters
  						);
  				bestParameters = result.getPoint();
  			} catch(org.apache.commons.math3.exception.MathIllegalStateException e) {
  				System.out.println("Solver failed");
  				bestParameters = guessParameters;
  			}
  		}
  
  		// Transform parameters to GARCH parameters
  		double omega	= Math.exp(bestParameters[0]);
  		double mucorr	= Math.exp(-Math.exp(-bestParameters[1]));
  		double muema	= Math.exp(-Math.exp(-bestParameters[2]));
  		double beta		= mucorr * muema;
  		double alpha	= mucorr - beta;
  		double displacementNormed = 1.0/(1.0+Math.exp(-bestParameters[3]));
  		double displacement = (upperBoundDisplacement-lowerBoundDisplacement)*displacementNormed+lowerBoundDisplacement;
  
  		double[] quantiles		= {0.01, 0.05, 0.5};
  		double[] quantileValues	= getQuantilPredictionsForParameters(omega, alpha, beta, displacement, quantiles);
  
  		Map<String, Object> results = new HashMap<>();
  		results.put("Omega", omega);
  		results.put("Alpha", alpha);
  		results.put("Beta", beta);
  		results.put("Displacement", displacement);
  		results.put("Szenarios", this.getSzenarios(omega, alpha, beta, displacement));
  		results.put("Likelihood", this.getLogLikelihoodForParameters(omega, alpha, beta, displacement));
  		results.put("Vol", Math.sqrt(this.getLastResidualForParameters(omega, alpha, beta, displacement)));
  		results.put("Quantile=1%", quantileValues[0]);
  		results.put("Quantile=5%", quantileValues[1]);
  		results.put("Quantile=50%", quantileValues[2]);
  		//		System.out.println(results.get("Likelihood") + "\t" + Arrays.toString(bestParameters));
  		return results;
  	}