com.opengamma.web.analytics.formatting.VolatilitySurfaceDataFormatter.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.web.analytics.formatting.VolatilitySurfaceDataFormatter.java

Source

/**
 * 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;
    }
}