/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.web.analytics.formatting;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Period;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.core.marketdatasnapshot.VolatilitySurfaceData;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.financial.analytics.volatility.surface.BloombergFXOptionVolatilitySurfaceInstrumentProvider.FXVolQuoteType;
import com.opengamma.util.time.DateUtils;
import com.opengamma.util.time.Tenor;
import com.opengamma.util.tuple.FirstThenSecondPairComparator;
import com.opengamma.util.tuple.Pair;
import com.opengamma.web.server.conversion.LabelFormatter;
/**
* Formatter.
*/
@SuppressWarnings("rawtypes")
/* package */ class VolatilitySurfaceDataFormatter extends AbstractFormatter<VolatilitySurfaceData> {
protected VolatilitySurfaceDataFormatter() {
super(VolatilitySurfaceData.class);
addFormatter(new Formatter<VolatilitySurfaceData>(Format.EXPANDED) {
@SuppressWarnings("unchecked")
@Override
Object format(VolatilitySurfaceData value, ValueSpecification valueSpec, Object inlineKey) {
return formatExpanded(value);
}
});
}
@Override
public String formatCell(VolatilitySurfaceData value, ValueSpecification valueSpec, Object inlineKey) {
int xSize = value.getUniqueXValues().size();
int ySize = Sets.newHashSet(value.getYs()).size();
return "Volatility Surface (" + xSize + " x " + ySize + ")";
}
@SuppressWarnings("unchecked")
private <X, Y> Map<String, Object> formatExpanded(VolatilitySurfaceData<X, Y> surface) {
// the x and y values won't necessarily be unique and won't necessarily map to a rectangular grid
// this projects them onto a grid and inserts nulls where there's no data available
Set<X> xVals = surface.getUniqueXValues();
Y[] yValues = surface.getYs();
Set<Y> yVals;
if (yValues.length > 0 && yValues[0] instanceof Pair) {
//TODO emcleod This nastiness is here because ObjectsPair is now (2013/5/13) no longer Comparable
Pair<Object, Object> pair = (Pair) yValues[0];
if (pair.getFirst() instanceof Integer && pair.getSecond() instanceof FXVolQuoteType) {
FirstThenSecondPairComparator<Integer, FXVolQuoteType> comparator = new FirstThenSecondPairComparator<>();
Set sortedSet = new TreeSet(comparator);
sortedSet.addAll(Arrays.asList(surface.getYs()));
yVals = (Set<Y>) sortedSet;
} else {
throw new UnsupportedOperationException("Cannot handle pairs of type " + pair);
}
} else {
yVals = Sets.newTreeSet((Iterable) Arrays.asList(surface.getYs()));
}
Map<String, Object> results = Maps.newHashMap();
results.put(SurfaceFormatterUtils.X_LABELS, getAxisLabels(xVals));
results.put(SurfaceFormatterUtils.Y_LABELS, getAxisLabels(yVals));
if (isPlottable(surface)) {
return formatForPlotting(surface, xVals, yVals, results);
} else {
return formatForGrid(surface, xVals, yVals, results);
}
}
/**
* Formats the surface data for display in a grid of text.
* @param surface The surface data
* @return The data formatted for display as text
*/
private <X, Y> Map<String, Object> formatForGrid(VolatilitySurfaceData<X, Y> surface,
Set<X> xVals,
Set<Y> yVals,
Map<String, Object> baseResults) {
List<List<Double>> vol = Lists.newArrayListWithCapacity(yVals.size());
for (Y yVal : yVals) {
List<Double> volVals = Lists.newArrayListWithCapacity(xVals.size());
for (X xVal : xVals) {
Double volatility = surface.getVolatility(xVal, yVal);
volVals.add(volatility);
}
vol.add(volVals);
}
Map<String, Object> results = Maps.newHashMap(baseResults);
results.put(LabelledMatrix2DFormatter.MATRIX, vol);
results.put(LabelledMatrix2DFormatter.X_LABELS, SurfaceFormatterUtils.getAxisLabels(xVals));
results.put(LabelledMatrix2DFormatter.Y_LABELS, SurfaceFormatterUtils.getAxisLabels(yVals));
return results;
}
/**
* Formats the surface data for display in the 3D surface viewer.. Returns a map containing the x-axis labels
* and values, y-axis labels and values, axis titles and volatility values. The lists of axis labels are sorted and
* have no duplicate values (which isn't necessarily true of the underlying data). The volatility data list contains
* a value for every combination of x and y values. If there is no corresponding value in the underlying data the
* volatility value will be null.
* <p>
* The axis values are numeric values which correspond to the axis labels. It is unspecified what they
* actually represent but their relative sizes show the relationship between the label values.
* This allows the labels to be properly laid out on the plot axes.
* <p>
* Not all volatility surfaces can be sensibly plotted as a surface and in that case the axis labels can't
* be converted to a meaningful numeric value. For these surfaces one or both of the axis values will be missing
* and the UI shouldn't attempt to plot the surface.
*
* @param surface The surface
* @return {xLabels: [...],
* xValues: [...],
* xTitle: "X Axis Title",
* yLabels: [...],
* yValues: [...],
* yTitle: "Y Axis Title",
* vol: [x0y0, x1y0,... , x0y1, x1y1,...]}
*/
private <X, Y> Map<String, Object> formatForPlotting(VolatilitySurfaceData<X, Y> surface,
Set<X> xVals,
Set<Y> yVals,
Map<String, Object> baseResults) {
Map<String, Object> results = Maps.newHashMap(baseResults);
// the x and y values won't necessarily be unique and won't necessarily map to a rectangular grid
// this projects them onto a grid and inserts nulls where there's no data available
// numeric values corresponding to the axis labels to help with plotting the surface
List<Number> xAxisValues = Lists.newArrayListWithCapacity(xVals.size());
List<Number> yAxisValues = Lists.newArrayListWithCapacity(yVals.size());
List<Double> vol = Lists.newArrayListWithCapacity(xVals.size() * yVals.size());
for (Y yVal : yVals) {
for (X xVal : xVals) {
vol.add(surface.getVolatility(xVal, yVal));
}
yAxisValues.add(getAxisValue(yVal));
}
for (Object xVal : xVals) {
xAxisValues.add(getAxisValue(xVal));
}
results.put(SurfaceFormatterUtils.Y_VALUES, yAxisValues);
results.put(SurfaceFormatterUtils.X_VALUES, xAxisValues);
results.put(SurfaceFormatterUtils.X_TITLE, surface.getXLabel());
results.put(SurfaceFormatterUtils.Y_TITLE, surface.getYLabel());
results.put(SurfaceFormatterUtils.VOL, vol);
return results;
}
private List<String> getAxisLabels(Collection values) {
List<String> labels = Lists.newArrayListWithCapacity(values.size());
for (Object value : values) {
labels.add(LabelFormatter.format(value));
}
return labels;
}
/**
* Returns a numeric value corresponding to a point on volatility surface axis to help with plotting the surface.
* @param axisValue A point on the axis
* @return A numeric value corresponding to the value or null if there's no meaningful numeric value
*/
private Number getAxisValue(Object axisValue) {
if (axisValue instanceof Number) {
return (Number) axisValue;
} else if (axisValue instanceof LocalDate) {
return ((LocalDate) axisValue).toEpochDay();
} else if (axisValue instanceof Tenor) {
Period period = ((Tenor) axisValue).getPeriod();
return DateUtils.estimatedDuration(period).getSeconds();
}
return null;
}
/**
* Returns {@link DataType#UNKNOWN UNKNOWN} because the format type can be differ for different instances of
* {@link VolatilitySurfaceData} depending on the axis types. The type for a given surface instance can
* be obtained from {@link #getDataTypeForValue}
* @return {@link DataType#UNKNOWN}
*/
@Override
public DataType getDataType() {
return DataType.UNKNOWN;
}
/**
* If the axis values can be sensibly converted to numbers this returns {@link DataType#SURFACE_DATA}, if not
* it returns {@link DataType#LABELLED_MATRIX_2D}.
* @param surfaceData The surface data
* @return The format type for the surface data, {@link DataType#SURFACE_DATA} or
* {@link DataType#LABELLED_MATRIX_2D} depending on the axis types of the data
*/
@Override
public DataType getDataTypeForValue(VolatilitySurfaceData surfaceData) {
if (isPlottable(surfaceData)) {
return DataType.SURFACE_DATA;
} else {
return DataType.LABELLED_MATRIX_2D;
}
}
/**
* Returns true if the surface data can be sensibly plotted.
*
* @param surfaceData the surface data
* @return true if the data can be sensibly plotted
*/
private boolean isPlottable(VolatilitySurfaceData surfaceData) {
Object[] xVals = surfaceData.getXs();
Object[] yVals = surfaceData.getYs();
if (xVals.length == 0) {
return false;
}
if (yVals.length == 0) {
return false;
}
if (getAxisValue(xVals[0]) == null || getAxisValue(yVals[0]) == null) {
return false;
}
return true;
}
}