/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.solutions.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import com.google.common.primitives.Doubles;
import com.opengamma.analytics.financial.model.volatility.surface.VolatilitySurface;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory;
import com.opengamma.analytics.math.interpolation.GridInterpolator2D;
import com.opengamma.analytics.math.interpolation.Interpolator1D;
import com.opengamma.analytics.math.interpolation.Interpolator1DFactory;
import com.opengamma.analytics.math.surface.InterpolatedDoublesSurface;
import com.opengamma.analytics.math.surface.NodalDoublesSurface;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.time.Tenor;
import au.com.bytecode.opencsv.CSVReader;
/**
* Utility class for volatility surfaces
*/
public final class VolUtils {
private VolUtils() { /* private constructor */ }
private static final Logger s_logger = LoggerFactory.getLogger(VolUtils.class);
/**
* Reads and parses surface data into in memory raw representation
* The format of the file is simple csv with a header row:
* name,strike,vol,maturity
* CALL_NK225,4000,258.68,31D
* CALL_NK225,4500,239.8297,31D
* PUT_NK225,16000,25.101,94D
* PUT_NK225,16250,24.457,94D
*
* or
* name,strike,price,maturity
* CALL_NK225,4000,15665,31D
*
* @param file the data to be parsed
* @return the parsed curve data by curve name
*/
public static Map<String, SurfaceRawData> parseSurface(String file) throws IOException {
s_logger.info("Creating curves from {}.", file);
Map<String, SurfaceRawData> curveData = new HashMap<>();
Reader curveReader = new BufferedReader(
new InputStreamReader(
new ClassPathResource(file).getInputStream()
)
);
try {
CSVReader csvReader = new CSVReader(curveReader);
String[] line;
csvReader.readNext(); // skip headers
while ((line = csvReader.readNext()) != null) {
String name = line[0];
String tenor = line[3];
if (tenor.endsWith("W")) {
int weeks = Integer.parseInt(tenor.substring(0, tenor.length() - 1));
tenor = (weeks * 7) + "D";
} else if (tenor.endsWith("M")) {
int months = Integer.parseInt(tenor.substring(0, tenor.length() - 1));
tenor = Math.round(months / 12d * 365) + "D";
} else if (tenor.endsWith("Y")) {
int years = Integer.parseInt(tenor.substring(0, tenor.length() - 1));
tenor = (years * 365) + "D";
}
Tenor tenorValue;
try {
tenorValue = Tenor.parse("P" + tenor);
} catch (NumberFormatException e) {
s_logger.error("Invalid tenor {} for {} in file {}. Input tenor values should contain and end with one of the following D, W, M and Y",
tenor, name, file);
continue;
}
String strike = line[1];
double strikeValue;
try {
strikeValue = Double.parseDouble(strike);
} catch (NumberFormatException e) {
s_logger.error("Invalid strike {} for {} at {}", strike, name, tenor);
continue;
}
String value = line[2];
double surfaceValue;
try {
surfaceValue = Double.parseDouble(value);
} catch (NumberFormatException e) {
s_logger.error("Invalid value {} for {} at {} and {}", value, name, tenor, strike);
continue;
}
if (!curveData.containsKey(name)) {
curveData.put(name, new SurfaceRawData());
}
curveData.get(name).add(tenorValue, strikeValue, surfaceValue);
}
} catch (IOException e) {
s_logger.error("Failed to parse curve data ", e);
}
return curveData;
}
/**
* Create instance of {@link VolatilitySurface}
* @param data the {@link SurfaceRawData} to create the surface from
* @return a VolatilitySurface
*/
public static VolatilitySurface createVolatilitySurface(SurfaceRawData data) {
Interpolator1D linearFlat =
CombinedInterpolatorExtrapolatorFactory.getInterpolator(Interpolator1DFactory.LINEAR,
Interpolator1DFactory.FLAT_EXTRAPOLATOR,
Interpolator1DFactory.FLAT_EXTRAPOLATOR);
GridInterpolator2D interpolator2D = new GridInterpolator2D(linearFlat, linearFlat);
double[] strikes = Doubles.toArray(data.getStrikes());
int points = strikes.length;
double[] times = new double[points];
double[] vols = new double[points];
List<Tenor> tenorList = data.getTimes();
List<Double> volList = data.getzData();
for (int i = 0; i < points; i++) {
Tenor tenor = tenorList.get(i);
times[i] = tenor.getPeriod().getDays() / 365d;
vols[i] = volList.get(i) / 100d;
}
InterpolatedDoublesSurface surface = InterpolatedDoublesSurface.from(times, strikes, vols, interpolator2D);
return new VolatilitySurface(surface);
}
/**
* Create instance of {@link NodalDoublesSurface}
* @param data the {@link SurfaceRawData} to create the surface from
* @return a VolatilitySurface
*/
public static NodalDoublesSurface createPriceSurface(SurfaceRawData data) {
double[] strikes = Doubles.toArray(data.getStrikes());
int points = strikes.length;
double[] times = new double[points];
double[] prices = new double[points];
List<Tenor> tenorList = data.getTimes();
List<Double> priceList = data.getzData();
for (int i = 0; i < points; i++) {
Tenor tenor = tenorList.get(i);
times[i] = tenor.getPeriod().getDays() / 365d;
prices[i] = priceList.get(i);
}
return new NodalDoublesSurface(times, strikes, prices);
}
/**
* Helper class to store raw curve data
*/
static class SurfaceRawData {
private List<Tenor> _times = new ArrayList<>();
private List<Double> _strikes = new ArrayList<>();
private List<Double> _zData = new ArrayList<>();
/**
* Add a curve point
*
* @param time tenor at this point - x
* @param strike value at this point - y
* @param point value at this point - z
*/
public void add(Tenor time, Double strike, Double point) {
ArgumentChecker.notNull(time, "time");
ArgumentChecker.notNull(strike, "strike");
ArgumentChecker.notNull(point, "point");
_times.add(time);
_strikes.add(strike);
_zData.add(point);
}
/**
* @return all time points available on the surface
*/
public List<Tenor> getTimes() {
return _times;
}
/**
* @return all strike points available on the surface
*/
public List<Double> getStrikes() {
return _strikes;
}
/**
* @return all points available on the surface
*/
public List<Double> getzData() {
return _zData;
}
}
}