/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.model.pnl;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.threeten.bp.Clock;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Period;
import org.threeten.bp.ZonedDateTime;
import com.google.common.collect.Iterables;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.financial.schedule.HolidayDateRemovalFunction;
import com.opengamma.analytics.financial.schedule.Schedule;
import com.opengamma.analytics.financial.schedule.ScheduleCalculatorFactory;
import com.opengamma.analytics.financial.schedule.TimeSeriesSamplingFunction;
import com.opengamma.analytics.financial.schedule.TimeSeriesSamplingFunctionFactory;
import com.opengamma.analytics.financial.timeseries.util.TimeSeriesDifferenceOperator;
import com.opengamma.core.historicaltimeseries.HistoricalTimeSeries;
import com.opengamma.core.id.ExternalSchemes;
import com.opengamma.core.position.Position;
import com.opengamma.core.security.Security;
import com.opengamma.core.value.MarketDataRequirementNames;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.function.AbstractFunction;
import com.opengamma.engine.function.CompiledFunctionDefinition;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.FunctionExecutionContext;
import com.opengamma.engine.function.FunctionInputs;
import com.opengamma.engine.target.ComputationTargetType;
import com.opengamma.engine.value.ComputedValue;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValuePropertyNames;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueRequirementNames;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.financial.OpenGammaCompilationContext;
import com.opengamma.financial.OpenGammaExecutionContext;
import com.opengamma.financial.analytics.DoubleLabelledMatrix2D;
import com.opengamma.financial.analytics.model.CalculationPropertyNamesAndValues;
import com.opengamma.financial.analytics.model.InstrumentTypeProperties;
import com.opengamma.financial.analytics.model.InterpolatedDataProperties;
import com.opengamma.financial.analytics.model.forex.ConventionBasedFXRateFunction;
import com.opengamma.financial.analytics.model.forex.ForexVisitors;
import com.opengamma.financial.analytics.model.forex.option.black.FXOptionBlackFunction;
import com.opengamma.financial.analytics.timeseries.DateConstraint;
import com.opengamma.financial.analytics.timeseries.HistoricalTimeSeriesBundle;
import com.opengamma.financial.analytics.timeseries.HistoricalTimeSeriesFunctionUtils;
import com.opengamma.financial.analytics.volatility.surface.ConfigDBVolatilitySurfaceDefinitionSource;
import com.opengamma.financial.analytics.volatility.surface.ConfigDBVolatilitySurfaceSpecificationSource;
import com.opengamma.financial.analytics.volatility.surface.SurfaceInstrumentProvider;
import com.opengamma.financial.analytics.volatility.surface.VolatilitySurfaceDefinition;
import com.opengamma.financial.analytics.volatility.surface.VolatilitySurfaceSpecification;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.financial.convention.calendar.MondayToFridayCalendar;
import com.opengamma.financial.currency.CurrencyPair;
import com.opengamma.financial.currency.CurrencyPairs;
import com.opengamma.financial.security.FinancialSecurity;
import com.opengamma.financial.security.option.FXOptionSecurity;
import com.opengamma.financial.security.option.NonDeliverableFXOptionSecurity;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.timeseries.DoubleTimeSeries;
import com.opengamma.util.async.AsynchronousExecution;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.UnorderedCurrencyPair;
/**
*
*/
public class FXOptionBlackVegaPnLFunction extends AbstractFunction {
private static final HolidayDateRemovalFunction HOLIDAY_REMOVER = HolidayDateRemovalFunction.getInstance();
private static final Calendar WEEKEND_CALENDAR = new MondayToFridayCalendar("Weekend");
private static final TimeSeriesDifferenceOperator DIFFERENCE = new TimeSeriesDifferenceOperator();
private ConfigDBVolatilitySurfaceDefinitionSource _volatilitySurfaceDefinitionSource;
private ConfigDBVolatilitySurfaceSpecificationSource _volatilitySurfaceSpecificationSource;
@Override
public void init(final FunctionCompilationContext context) {
_volatilitySurfaceDefinitionSource = ConfigDBVolatilitySurfaceDefinitionSource.init(context, this);
_volatilitySurfaceSpecificationSource = ConfigDBVolatilitySurfaceSpecificationSource.init(context, this);
}
@Override
public CompiledFunctionDefinition compile(final FunctionCompilationContext context, final Instant atInstant) {
final CurrencyPairs currencyPairs = OpenGammaCompilationContext.getCurrencyPairsSource(context).getCurrencyPairs(CurrencyPairs.DEFAULT_CURRENCY_PAIRS);
return new Compiled(currencyPairs);
}
/**
* The compiled form.
*/
protected class Compiled extends AbstractInvokingCompiledFunction {
private final CurrencyPairs _currencyPairs;
public Compiled(final CurrencyPairs currencyPairs) {
_currencyPairs = currencyPairs;
}
// CompiledFunctionDefinition
@Override
public ComputationTargetType getTargetType() {
return ComputationTargetType.POSITION;
}
@Override
public boolean canApplyTo(final FunctionCompilationContext context, final ComputationTarget target) {
final Security security = target.getPosition().getSecurity();
return security instanceof FXOptionSecurity || security instanceof NonDeliverableFXOptionSecurity;
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) {
final FinancialSecurity security = (FinancialSecurity) target.getPosition().getSecurity();
final Currency putCurrency = security.accept(ForexVisitors.getPutCurrencyVisitor());
final Currency callCurrency = security.accept(ForexVisitors.getCallCurrencyVisitor());
final CurrencyPair currencyPair = _currencyPairs.getCurrencyPair(putCurrency, callCurrency);
final String currencyBase = currencyPair.getBase().getCode(); // The base currency
final ValueProperties properties = createValueProperties().with(ValuePropertyNames.CALCULATION_METHOD, CalculationPropertyNamesAndValues.BLACK_METHOD)
.withAny(FXOptionBlackFunction.PUT_CURVE).withAny(FXOptionBlackFunction.PUT_CURVE_CALC_CONFIG).withAny(FXOptionBlackFunction.CALL_CURVE)
.withAny(FXOptionBlackFunction.CALL_CURVE_CALC_CONFIG).withAny(ValuePropertyNames.SURFACE).withAny(InterpolatedDataProperties.X_INTERPOLATOR_NAME)
.withAny(InterpolatedDataProperties.LEFT_X_EXTRAPOLATOR_NAME).withAny(InterpolatedDataProperties.RIGHT_X_EXTRAPOLATOR_NAME).with(ValuePropertyNames.CURRENCY, currencyBase)
.withAny(ValuePropertyNames.SAMPLING_PERIOD).withAny(ValuePropertyNames.SCHEDULE_CALCULATOR).withAny(ValuePropertyNames.SAMPLING_FUNCTION)
.with(ValuePropertyNames.PROPERTY_PNL_CONTRIBUTIONS, ValueRequirementNames.VEGA_QUOTE_MATRIX).get();
return Collections.singleton(new ValueSpecification(ValueRequirementNames.PNL_SERIES, target.toSpecification(), properties));
}
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) {
final ValueProperties constraints = desiredValue.getConstraints();
final Set<String> putCurveNames = constraints.getValues(FXOptionBlackFunction.PUT_CURVE);
if (putCurveNames == null || putCurveNames.size() != 1) {
return null;
}
final Set<String> putCurveCalculationConfigs = constraints.getValues(FXOptionBlackFunction.PUT_CURVE_CALC_CONFIG);
if (putCurveCalculationConfigs == null || putCurveCalculationConfigs.size() != 1) {
return null;
}
final Set<String> callCurveNames = constraints.getValues(FXOptionBlackFunction.CALL_CURVE);
if (callCurveNames == null || callCurveNames.size() != 1) {
return null;
}
final Set<String> callCurveCalculationConfigs = constraints.getValues(FXOptionBlackFunction.CALL_CURVE_CALC_CONFIG);
if (callCurveCalculationConfigs == null || callCurveCalculationConfigs.size() != 1) {
return null;
}
final Set<String> surfaceNames = constraints.getValues(ValuePropertyNames.SURFACE);
if (surfaceNames == null || surfaceNames.size() != 1) {
return null;
}
final Set<String> interpolatorNames = constraints.getValues(InterpolatedDataProperties.X_INTERPOLATOR_NAME);
if (interpolatorNames == null || interpolatorNames.size() != 1) {
return null;
}
final Set<String> leftExtrapolatorNames = constraints.getValues(InterpolatedDataProperties.LEFT_X_EXTRAPOLATOR_NAME);
if (leftExtrapolatorNames == null || leftExtrapolatorNames.size() != 1) {
return null;
}
final Set<String> rightExtrapolatorNames = constraints.getValues(InterpolatedDataProperties.RIGHT_X_EXTRAPOLATOR_NAME);
if (rightExtrapolatorNames == null || rightExtrapolatorNames.size() != 1) {
return null;
}
final Set<String> samplingPeriods = constraints.getValues(ValuePropertyNames.SAMPLING_PERIOD);
if (samplingPeriods == null || samplingPeriods.size() != 1) {
return null;
}
final FinancialSecurity security = (FinancialSecurity) target.getPosition().getSecurity();
final Currency putCurrency = security.accept(ForexVisitors.getPutCurrencyVisitor());
final Currency callCurrency = security.accept(ForexVisitors.getCallCurrencyVisitor());
final UnorderedCurrencyPair currencies = UnorderedCurrencyPair.of(putCurrency, callCurrency);
final String surfaceName = Iterables.getOnlyElement(surfaceNames);
final String samplingPeriod = Iterables.getOnlyElement(samplingPeriods);
final CurrencyPair currencyPair = _currencyPairs.getCurrencyPair(putCurrency, callCurrency);
final String vegaResultCurrency = getResultCurrency(target, currencyPair);
final String currencyBase = currencyPair.getBase().getCode(); // The base currency
final ValueRequirement vegaMatrixRequirement = new ValueRequirement(ValueRequirementNames.VEGA_QUOTE_MATRIX, ComputationTargetSpecification.of(security), ValueProperties.builder()
.with(ValuePropertyNames.CALCULATION_METHOD, CalculationPropertyNamesAndValues.BLACK_METHOD).with(FXOptionBlackFunction.PUT_CURVE, Iterables.getOnlyElement(putCurveNames))
.with(FXOptionBlackFunction.PUT_CURVE_CALC_CONFIG, Iterables.getOnlyElement(putCurveCalculationConfigs))
.with(FXOptionBlackFunction.CALL_CURVE, Iterables.getOnlyElement(callCurveNames))
.with(FXOptionBlackFunction.CALL_CURVE_CALC_CONFIG, Iterables.getOnlyElement(callCurveCalculationConfigs)).with(ValuePropertyNames.SURFACE, surfaceName)
.with(InterpolatedDataProperties.X_INTERPOLATOR_NAME, Iterables.getOnlyElement(interpolatorNames))
.with(InterpolatedDataProperties.LEFT_X_EXTRAPOLATOR_NAME, Iterables.getOnlyElement(leftExtrapolatorNames))
.with(InterpolatedDataProperties.RIGHT_X_EXTRAPOLATOR_NAME, Iterables.getOnlyElement(rightExtrapolatorNames)).with(ValuePropertyNames.CURRENCY, vegaResultCurrency).get());
final ValueRequirement surfaceHTSRequirement = getVolatilitySurfaceHTSRequirement(currencies, surfaceName, samplingPeriod);
final Set<ValueRequirement> requirements = new HashSet<>();
requirements.add(vegaMatrixRequirement);
requirements.add(surfaceHTSRequirement);
if (!currencyBase.equals(vegaResultCurrency)) {
requirements.add(ConventionBasedFXRateFunction.getHistoricalTimeSeriesRequirement(putCurrency, callCurrency));
}
return requirements;
}
// FunctionInvoker
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues)
throws AsynchronousExecution {
final Object vegaMatrixObject = inputs.getValue(ValueRequirementNames.VEGA_QUOTE_MATRIX);
if (vegaMatrixObject == null) {
throw new OpenGammaRuntimeException("Could not get vega matrix");
}
final Object volatilityHTSObject = inputs.getValue(ValueRequirementNames.VOLATILITY_SURFACE_HISTORICAL_TIME_SERIES);
if (volatilityHTSObject == null) {
throw new OpenGammaRuntimeException("Could not get historical time series for volatilities");
}
final DoubleLabelledMatrix2D vegaMatrix = (DoubleLabelledMatrix2D) vegaMatrixObject;
final HistoricalTimeSeriesBundle timeSeriesBundle = (HistoricalTimeSeriesBundle) volatilityHTSObject;
final Clock snapshotClock = executionContext.getValuationClock();
final LocalDate now = ZonedDateTime.now(snapshotClock).toLocalDate();
final ValueRequirement desiredValue = Iterables.getOnlyElement(desiredValues);
final String surfaceName = desiredValue.getConstraint(ValuePropertyNames.SURFACE);
final Position position = target.getPosition();
final FinancialSecurity security = (FinancialSecurity) position.getSecurity();
final Currency putCurrency = security.accept(ForexVisitors.getPutCurrencyVisitor());
final Currency callCurrency = security.accept(ForexVisitors.getCallCurrencyVisitor());
final UnorderedCurrencyPair currencyPair = UnorderedCurrencyPair.of(putCurrency, callCurrency);
final VolatilitySurfaceDefinition<Object, Object> definition = getSurfaceDefinition(currencyPair, surfaceName);
final VolatilitySurfaceSpecification specification = getSurfaceSpecification(currencyPair, surfaceName);
final Period samplingPeriod = getSamplingPeriod(desiredValue.getConstraint(ValuePropertyNames.SAMPLING_PERIOD));
final LocalDate startDate = now.minus(samplingPeriod);
final String scheduleCalculatorName = desiredValue.getConstraint(ValuePropertyNames.SCHEDULE_CALCULATOR);
final Schedule scheduleCalculator = getScheduleCalculator(scheduleCalculatorName);
final String samplingFunctionName = desiredValue.getConstraint(ValuePropertyNames.SAMPLING_FUNCTION);
final TimeSeriesSamplingFunction samplingFunction = getSamplingFunction(samplingFunctionName);
final LocalDate[] schedule = HOLIDAY_REMOVER.getStrippedSchedule(scheduleCalculator.getSchedule(startDate, now, true, false), WEEKEND_CALENDAR);
DoubleTimeSeries<?> vegaPnL = getPnLSeries(definition, specification, timeSeriesBundle, vegaMatrix, now, schedule, samplingFunction);
vegaPnL = vegaPnL.multiply(position.getQuantity().doubleValue());
final CurrencyPairs currencyPairs = OpenGammaExecutionContext.getCurrencyPairsSource(executionContext).getCurrencyPairs(CurrencyPairs.DEFAULT_CURRENCY_PAIRS);
final CurrencyPair baseCounterPair = currencyPairs.getCurrencyPair(putCurrency, callCurrency);
final String vegaResultCurrency = getResultCurrency(target, baseCounterPair);
final String currencyBase = baseCounterPair.getBase().getCode();
if (!currencyBase.equals(vegaResultCurrency)) {
final Object spotFXObject = inputs.getValue(ValueRequirementNames.HISTORICAL_FX_TIME_SERIES);
if (spotFXObject == null) {
throw new OpenGammaRuntimeException("Could not get spot FX time series");
}
final DoubleTimeSeries<?> spotFX = ((HistoricalTimeSeries) spotFXObject).getTimeSeries();
vegaPnL = vegaPnL.multiply(spotFX);
}
final ValueSpecification spec = new ValueSpecification(ValueRequirementNames.PNL_SERIES, target.toSpecification(), desiredValue.getConstraints());
return Collections.singleton(new ComputedValue(spec, vegaPnL));
}
}
private ValueRequirement getVolatilitySurfaceHTSRequirement(final UnorderedCurrencyPair currencies, final String surfaceName, final String samplingPeriod) {
return HistoricalTimeSeriesFunctionUtils.createVolatilitySurfaceHTSRequirement(currencies, surfaceName, InstrumentTypeProperties.FOREX, MarketDataRequirementNames.MARKET_VALUE, null,
DateConstraint.VALUATION_TIME.minus(samplingPeriod), true, DateConstraint.VALUATION_TIME, true);
}
private VolatilitySurfaceDefinition<Object, Object> getSurfaceDefinition(final UnorderedCurrencyPair currencyPair, final String definitionName) {
final String fullDefinitionName = definitionName + "_" + currencyPair.getUniqueId().getValue();
final VolatilitySurfaceDefinition<Object, Object> definition = (VolatilitySurfaceDefinition<Object, Object>) _volatilitySurfaceDefinitionSource.getDefinition(fullDefinitionName,
InstrumentTypeProperties.FOREX);
if (definition == null) {
throw new OpenGammaRuntimeException("Could not get volatility surface definition named " + fullDefinitionName + " for instrument type " + InstrumentTypeProperties.FOREX);
}
return definition;
}
private VolatilitySurfaceSpecification getSurfaceSpecification(final UnorderedCurrencyPair currencyPair, final String specificationName) {
final String fullSpecificationName = specificationName + "_" + currencyPair.getUniqueId().getValue();
final VolatilitySurfaceSpecification specification = _volatilitySurfaceSpecificationSource.getSpecification(fullSpecificationName, InstrumentTypeProperties.FOREX);
if (specification == null) {
throw new OpenGammaRuntimeException("Could not get volatility surface specification named " + fullSpecificationName);
}
return specification;
}
private Period getSamplingPeriod(final String samplingPeriodName) {
return Period.parse(samplingPeriodName);
}
private Schedule getScheduleCalculator(final String scheduleCalculatorName) {
return ScheduleCalculatorFactory.getScheduleCalculator(scheduleCalculatorName);
}
private TimeSeriesSamplingFunction getSamplingFunction(final String samplingFunctionName) {
return TimeSeriesSamplingFunctionFactory.getFunction(samplingFunctionName);
}
private DoubleTimeSeries<?> getPnLSeries(final VolatilitySurfaceDefinition<Object, Object> definition, final VolatilitySurfaceSpecification specification,
final HistoricalTimeSeriesBundle timeSeriesBundle, final DoubleLabelledMatrix2D vegaMatrix, final LocalDate endDate, final LocalDate[] schedule,
final TimeSeriesSamplingFunction samplingFunction) {
final SurfaceInstrumentProvider<Object, Object> provider = (SurfaceInstrumentProvider<Object, Object>) specification.getSurfaceInstrumentProvider();
final double[][] vegas = vegaMatrix.getValues();
if (vegas.length != definition.getYs().length || vegas[0].length != definition.getXs().length) {
throw new OpenGammaRuntimeException("Vega matrix not the same shape as that in definition");
}
DoubleTimeSeries<?> vegaPnL = null;
int i = 0;
for (final Object x : definition.getXs()) {
int j = 0;
for (final Object y : definition.getYs()) {
ExternalId id = provider.getInstrument(x, y, endDate);
if (id.getScheme().equals(ExternalSchemes.BLOOMBERG_TICKER_WEAK)) {
id = ExternalSchemes.bloombergTickerSecurityId(id.getValue());
}
final ExternalIdBundle identifier = ExternalIdBundle.of(id);
final HistoricalTimeSeries tsForTicker = timeSeriesBundle.get(MarketDataRequirementNames.MARKET_VALUE, identifier);
if (tsForTicker == null) {
throw new OpenGammaRuntimeException("Could not get identifier / vol series for " + id);
}
final DoubleTimeSeries<?> volHistory = DIFFERENCE.evaluate(samplingFunction.getSampledTimeSeries(tsForTicker.getTimeSeries(), schedule));
final double vega = vegas[j][i] / 100;
if (vegaPnL == null) {
vegaPnL = volHistory.multiply(vega);
} else {
vegaPnL = vegaPnL.add(volHistory.multiply(vega));
}
j++;
}
i++;
}
return vegaPnL;
}
private String getResultCurrency(final ComputationTarget target, final CurrencyPair currencyPair) {
final FinancialSecurity security = (FinancialSecurity) target.getPosition().getSecurity();
final Currency putCurrency = security.accept(ForexVisitors.getPutCurrencyVisitor());
final Currency callCurrency = security.accept(ForexVisitors.getCallCurrencyVisitor());
Currency ccy;
if (putCurrency.equals(currencyPair.getBase())) {
ccy = callCurrency;
} else {
ccy = putCurrency;
}
return ccy.getCode();
}
}