/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.impl.volatility.smile;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.Arrays;
import java.util.BitSet;
import java.util.function.Function;
import org.slf4j.Logger;
import org.testng.annotations.Test;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.array.DoubleMatrix;
import com.opengamma.strata.math.impl.differentiation.VectorFieldFirstOrderDifferentiator;
import com.opengamma.strata.math.impl.statistics.leastsquare.LeastSquareResults;
import com.opengamma.strata.math.impl.statistics.leastsquare.LeastSquareResultsWithTransform;
import cern.jet.random.engine.MersenneTwister;
import cern.jet.random.engine.RandomEngine;
/**
* Test case for smile model fitters.
*
* @param <T> the smile model data
*/
@Test
public abstract class SmileModelFitterTest<T extends SmileModelData> {
protected static double TIME_TO_EXPIRY = 7.0;
protected static double F = 0.03;
private static RandomEngine UNIFORM = new MersenneTwister();
protected static double[] STRIKES = new double[] {0.005, 0.01, 0.02, 0.03, 0.04, 0.05, 0.07, 0.1};
protected double[] _cleanVols;
protected double[] _noisyVols;
protected double[] _errors;
protected VolatilityFunctionProvider<T> _model;
protected SmileModelFitter<T> _fitter;
protected SmileModelFitter<T> _nosiyFitter;
protected double _chiSqEps = 1e-6;
protected double _paramValueEps = 1e-6;
abstract Logger getlogger();
abstract VolatilityFunctionProvider<T> getModel();
abstract T getModelData();
abstract SmileModelFitter<T> getFitter(
double forward,
double[] strikes,
double timeToExpiry,
double[] impliedVols,
double[] error,
VolatilityFunctionProvider<T> model);
abstract double[][] getStartValues();
abstract double[] getRandomStartValues();
abstract BitSet[] getFixedValues();
public SmileModelFitterTest() {
VolatilityFunctionProvider<T> model = getModel();
T data = getModelData();
int n = STRIKES.length;
_noisyVols = new double[n];
_errors = new double[n];
_cleanVols = new double[n];
Arrays.fill(_errors, 1e-4);
for (int i = 0; i < n; i++) {
_cleanVols[i] = model.volatility(F, STRIKES[i], TIME_TO_EXPIRY, data);
_noisyVols[i] = _cleanVols[i] + UNIFORM.nextDouble() * _errors[i];
}
_fitter = getFitter(F, STRIKES, TIME_TO_EXPIRY, _cleanVols, _errors, model);
_nosiyFitter = getFitter(F, STRIKES, TIME_TO_EXPIRY, _noisyVols, _errors, model);
}
@SuppressWarnings("unused")
public void testExactFit() {
double[][] start = getStartValues();
BitSet[] fixed = getFixedValues();
int nStartPoints = start.length;
ArgChecker.isTrue(fixed.length == nStartPoints);
for (int trys = 0; trys < nStartPoints; trys++) {
LeastSquareResultsWithTransform results = _fitter.solve(DoubleArray.copyOf(start[trys]), fixed[trys]);
DoubleArray res = results.getModelParameters();
assertEquals(0.0, results.getChiSq(), _chiSqEps);
int n = res.size();
T data = getModelData();
assertEquals(data.getNumberOfParameters(), n);
for (int i = 0; i < n; i++) {
assertEquals(data.getParameter(i), res.get(i), _paramValueEps);
}
}
}
public void testNoisyFit() {
double[][] start = getStartValues();
BitSet[] fixed = getFixedValues();
int nStartPoints = start.length;
ArgChecker.isTrue(fixed.length == nStartPoints);
for (int trys = 0; trys < nStartPoints; trys++) {
LeastSquareResultsWithTransform results = _nosiyFitter.solve(DoubleArray.copyOf(start[trys]), fixed[trys]);
DoubleArray res = results.getModelParameters();
double eps = 1e-2;
assertTrue(results.getChiSq() < 7);
int n = res.size();
T data = getModelData();
assertEquals(data.getNumberOfParameters(), n);
for (int i = 0; i < n; i++) {
assertEquals(data.getParameter(i), res.get(i), eps);
}
}
}
public void timeTest() {
long start = 0;
int hotspotWarmupCycles = 200;
int benchmarkCycles = 1000;
int nStarts = getStartValues().length;
for (int i = 0; i < hotspotWarmupCycles; i++) {
testNoisyFit();
}
start = System.nanoTime();
for (int i = 0; i < benchmarkCycles; i++) {
testNoisyFit();
}
long time = System.nanoTime() - start;
getlogger().info("time per fit: " + ((double) time) / benchmarkCycles / nStarts + "ms");
}
public void horribleMarketDataTest() {
double forward = 0.0059875;
double[] strikes = new double[] {0.0012499999999999734, 0.0024999999999999467, 0.003750000000000031,
0.0050000000000000044, 0.006249999999999978, 0.007499999999999951, 0.008750000000000036,
0.010000000000000009, 0.011249999999999982, 0.012499999999999956, 0.01375000000000004, 0.015000000000000013,
0.016249999999999987, 0.01749999999999996, 0.018750000000000044,
0.020000000000000018, 0.02124999999999999, 0.022499999999999964, 0.02375000000000005, 0.025000000000000022,
0.026249999999999996, 0.02749999999999997, 0.028750000000000053,
0.030000000000000027};
double expiry = 0.09041095890410959;
double[] vols = new double[] {2.7100433855959642, 1.5506135190088546, 0.9083977239618538, 0.738416513934868,
0.8806973450124451, 1.0906290439592792, 1.2461975189027226, 1.496275983572826,
1.5885915338673156, 1.4842142974195722, 1.7667347426399058, 1.4550288621444052, 1.0651798188736166,
1.143318270172714, 1.216215092528441, 1.2845258218014657, 1.3488224665755535,
1.9259326343836376, 1.9868728791190922, 2.0441767092857317, 2.0982583238541026, 2.1494622372820675,
2.198020785622251, 2.244237863291375};
int n = strikes.length;
double[] errors = new double[n];
Arrays.fill(errors, 0.01); //1% error
SmileModelFitter<T> fitter = getFitter(forward, strikes, expiry, vols, errors, getModel());
LeastSquareResults best = null;
BitSet fixed = new BitSet();
for (int i = 0; i < 5; i++) {
double[] start = getRandomStartValues();
// int nStartPoints = start.length;
LeastSquareResults lsRes = fitter.solve(DoubleArray.copyOf(start), fixed);
if (best == null) {
best = lsRes;
} else {
if (lsRes.getChiSq() < best.getChiSq()) {
best = lsRes;
}
}
}
if (best != null) {
assertTrue(best.getChiSq() < 24000); //average error 31.6% - not a good fit, but the data is horrible
}
}
public void testJacobian() {
T data = getModelData();
int n = data.getNumberOfParameters();
double[] temp = new double[n];
for (int i = 0; i < n; i++) {
temp[i] = data.getParameter(i);
}
DoubleArray x = DoubleArray.copyOf(temp);
testJacobian(x);
}
// random test to be turned off
@Test(enabled = false)
public void testRandomJacobian() {
for (int i = 0; i < 10; i++) {
double[] temp = getRandomStartValues();
DoubleArray x = DoubleArray.copyOf(temp);
try {
testJacobian(x);
} catch (AssertionError e) {
System.out.println("Jacobian test failed at " + x.toString());
throw e;
}
}
}
private void testJacobian(DoubleArray x) {
int n = x.size();
Function<DoubleArray, DoubleArray> func = _fitter.getModelValueFunction();
Function<DoubleArray, DoubleMatrix> jacFunc = _fitter.getModelJacobianFunction();
VectorFieldFirstOrderDifferentiator differ = new VectorFieldFirstOrderDifferentiator();
Function<DoubleArray, DoubleMatrix> jacFuncFD = differ.differentiate(func);
DoubleMatrix jac = jacFunc.apply(x);
DoubleMatrix jacFD = jacFuncFD.apply(x);
int rows = jacFD.rowCount();
int cols = jacFD.columnCount();
assertEquals(_cleanVols.length, rows);
assertEquals(n, cols);
assertEquals(rows, jac.rowCount());
assertEquals(cols, jac.columnCount());
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
assertEquals(jacFD.get(i, j), jac.get(i, j), 2e-2);
}
}
}
}