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) {
  
  				double omega	= variables[0];
  				double alpha	= variables[1];
  				double beta		= variables[2];
  				double theta	= variables[3];
  				double mu			= variables[4];
  				double phi			= variables[5];
  
  				double logLikelihood = getLogLikelihoodForParameters(variables);
  
  				// 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;
  
  				return logLikelihood;
  			}
  
  		}
  		final GARCHMaxLikelihoodFunction objectiveFunction = new GARCHMaxLikelihoodFunction();
  
  		// Create a guess for the solver
  		final double[] guessParameters = new double[parameterGuess.length];
  		System.arraycopy(parameterGuess, 0, guessParameters, 0, parameterGuess.length);
  
  		if(guess != null) {
  			// A guess was provided, use that one
  			guessParameters[0]	= (Double)guess.get("Omega");
  			guessParameters[1]	= (Double)guess.get("Alpha");
  			guessParameters[2]	= (Double)guess.get("Beta");
  			guessParameters[3]	= (Double)guess.get("Theta");
  			guessParameters[4]	= (Double)guess.get("Mu");
  			guessParameters[5]	= (Double)guess.get("Phi");
  		}
  
  
  		// Seek optimal parameter configuration
  		LevenbergMarquardt lm = new LevenbergMarquardt(guessParameters, new double[] { 1000.0 }, 100*maxIterations, 2) {
  			private static final long serialVersionUID = -8844232820888815090L;
  
  			@Override
  			public void setValues(double[] parameters, double[] values) {
  				values[0] = objectiveFunction.value(parameters);
  			}
  		};
  
  		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.optim.nonlinear.scalar.noderiv.CMAESOptimizer optimizer2 = new org.apache.commons.math3.optim.nonlinear.scalar.noderiv.CMAESOptimizer(maxIterations, Double.POSITIVE_INFINITY, true, 0, 0, new MersenneTwister(3141), false, new SimplePointChecker<org.apache.commons.math3.optim.PointValuePair>(0, 0))
  			{
  				@Override
  				public double computeObjectiveValue(double[] params) {
  					return objectiveFunction.value(params);
  				}
  
  				/* (non-Javadoc)
  				 * @see org.apache.commons.math3.optim.nonlinear.scalar.MultivariateOptimizer#getGoalType()
  				 */
  				@Override
  				public org.apache.commons.math3.optim.nonlinear.scalar.GoalType getGoalType() {
  					// TODO Auto-generated method stub
  					return org.apache.commons.math3.optim.nonlinear.scalar.GoalType.MAXIMIZE;
  				}
  
  				/* (non-Javadoc)
  				 * @see org.apache.commons.math3.optim.BaseMultivariateOptimizer#getStartPoint()
  				 */
  				@Override
  				public double[] getStartPoint() {
  					return guessParameters;
  				}
  
  				/* (non-Javadoc)
  				 * @see org.apache.commons.math3.optim.BaseMultivariateOptimizer#getLowerBound()
  				 */
  				@Override
  				public double[] getLowerBound() {
  					return lowerBound;
  				}
  
  				/* (non-Javadoc)
  				 * @see org.apache.commons.math3.optim.BaseMultivariateOptimizer#getUpperBound()
  				 */
  				@Override
  				public double[] getUpperBound() {
  					return upperBound;
  				}
  			};
  
  			try {
  				org.apache.commons.math3.optim.PointValuePair result = optimizer2.optimize(
  						new org.apache.commons.math3.optim.nonlinear.scalar.noderiv.CMAESOptimizer.PopulationSize((int) (4 + 3 * Math.log(guessParameters.length))),
  						new org.apache.commons.math3.optim.nonlinear.scalar.noderiv.CMAESOptimizer.Sigma(parameterStep)
  						);
  				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		= bestParameters[0];
  		double alpha		= bestParameters[1];
  		double beta			= bestParameters[2];
  		double theta		= bestParameters[3];
  		double mu			= bestParameters[4];
  		double phi			= bestParameters[5];
  
  		double[] quantiles		= {0.005, 0.01, 0.02, 0.05, 0.5};
  		double[] quantileValues	= getQuantilPredictionsForParameters(bestParameters, quantiles);
  
  		Map<String, Object> results = new HashMap<>();
  		results.put("parameters", bestParameters);
  		results.put("Omega", omega);
  		results.put("Alpha", alpha);
  		results.put("Beta", beta);
  		results.put("Theta", theta);
  		results.put("Mu", mu);
  		results.put("Phi", phi);
  		results.put("Szenarios", this.getSzenarios(bestParameters));
  		results.put("Likelihood", this.getLogLikelihoodForParameters(bestParameters));
  		results.put("Vol", Math.sqrt(this.getLastResidualForParameters(bestParameters)));
  		results.put("Quantile=05%", quantileValues[0]);
  		results.put("Quantile=1%", quantileValues[1]);
  		results.put("Quantile=2%", quantileValues[2]);
  		results.put("Quantile=5%", quantileValues[3]);
  		results.put("Quantile=50%", quantileValues[4]);
  		return results;
  	}