/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.fxforward;
import java.util.LinkedList;
import javax.inject.Inject;
import org.threeten.bp.LocalDate;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.opengamma.financial.currency.CurrencyPair;
import com.opengamma.financial.security.fx.FXForwardSecurity;
import com.opengamma.sesame.CurrencyPairsFn;
import com.opengamma.sesame.Environment;
import com.opengamma.sesame.TimeSeriesReturnConverter;
import com.opengamma.sesame.marketdata.HistoricalMarketDataFn;
import com.opengamma.sesame.pnl.HistoricalPnLFXConverterFn;
import com.opengamma.timeseries.date.localdate.ImmutableLocalDateDoubleTimeSeries;
import com.opengamma.timeseries.date.localdate.LocalDateDoubleTimeSeries;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.MultipleCurrencyAmount;
import com.opengamma.util.money.UnorderedCurrencyPair;
import com.opengamma.util.result.Result;
import com.opengamma.util.time.LocalDateRange;
public class DiscountingFXForwardSpotPnLSeriesFn implements FXForwardPnLSeriesFn {
private final FXForwardCalculatorFn _calculatorProvider;
private final CurrencyPairsFn _currencyPairsFn;
/**
* The requested currency for this P&L series. If not supplied, then the
* output will be in the base currency of the currency pair corresponding
* to the FX Forward's currencies.
*/
private final Optional<Currency> _outputCurrency;
private final Boolean _useHistoricalSpot;
private final HistoricalPnLFXConverterFn _pnlFXConverterFn;
/**
* The time-series converter.
*/
private final TimeSeriesReturnConverter _timeSeriesConverter;
/**
* The market data function.
*/
private final HistoricalMarketDataFn _historicalMarketDataFn;
/**
* How big a timeseries result is required.
*/
private final LocalDateRange _dateRange;
@Inject
public DiscountingFXForwardSpotPnLSeriesFn(FXForwardCalculatorFn calculatorProvider,
CurrencyPairsFn currencyPairsFn,
Optional<Currency> outputCurrency,
Boolean useHistoricalSpot,
LocalDateRange dateRange,
HistoricalMarketDataFn historicalMarketDataFn,
TimeSeriesReturnConverter timeSeriesConverter,
HistoricalPnLFXConverterFn pnlFXConverterFn) {
_calculatorProvider = calculatorProvider;
_currencyPairsFn = currencyPairsFn;
_outputCurrency = outputCurrency;
_useHistoricalSpot = useHistoricalSpot;
_dateRange = dateRange;
_historicalMarketDataFn = historicalMarketDataFn;
_timeSeriesConverter = timeSeriesConverter;
_pnlFXConverterFn = pnlFXConverterFn;
}
@Override
public Result<LocalDateDoubleTimeSeries> calculatePnlSeries(Environment env,
FXForwardSecurity security) {
final Currency payCurrency = security.getPayCurrency();
final Currency receiveCurrency = security.getReceiveCurrency();
final UnorderedCurrencyPair pair = UnorderedCurrencyPair.of(payCurrency, receiveCurrency);
final Result<CurrencyPair> cpResult = _currencyPairsFn.getCurrencyPair(pair);
final Result<FXForwardCalculator> calculatorResult = _calculatorProvider.generateCalculator(env, security);
if (calculatorResult.isSuccess()) {
final MultipleCurrencyAmount currencyExposure = calculatorResult.getValue().calculateCurrencyExposure(env);
if (cpResult.isSuccess()) {
final CurrencyPair currencyPair = cpResult.getValue();
//take one week off the start date. this ensures that the start of the underlying
//price series will provide at least one business day of data before the required start of
// the return series. the resulting return series is trimmed so that first day of PnL =
//start date.
LocalDate adjustedStart = _dateRange.getStartDateInclusive().minusWeeks(1);
LocalDateRange adjustedRange = LocalDateRange.of(adjustedStart, _dateRange.getEndDateInclusive(), true);
Result<LocalDateDoubleTimeSeries> fxSeries = _historicalMarketDataFn.getFxRates(env, currencyPair, adjustedRange);
if (!fxSeries.isSuccess()) {
return Result.failure(fxSeries);
}
LocalDateDoubleTimeSeries returnSeries = trimSeries(fxSeries.getValue());
LocalDateDoubleTimeSeries fxSpotReturnSeries = _timeSeriesConverter.convert(returnSeries);
final Currency baseCurrency = currencyPair.getBase();
final double exposure = currencyExposure.getAmount(currencyPair.getCounter());
if (!_useHistoricalSpot) {
Result<LocalDateDoubleTimeSeries> spotConvertedSeriesResult = _pnlFXConverterFn.convertToSpotRate(env, currencyPair, fxSpotReturnSeries);
if (!spotConvertedSeriesResult.isSuccess()) {
return Result.failure(spotConvertedSeriesResult);
}
fxSpotReturnSeries = spotConvertedSeriesResult.getValue();
}
if (conversionIsRequired(baseCurrency)) {
CurrencyPair outputPair = CurrencyPair.of(baseCurrency, _outputCurrency.get());
final Result<LocalDateDoubleTimeSeries> conversionSeriesResult =
_historicalMarketDataFn.getFxRates(env, outputPair, adjustedRange);
if (conversionSeriesResult.isSuccess()) {
final LocalDateDoubleTimeSeries conversionSeries = conversionSeriesResult.getValue();
final LocalDateDoubleTimeSeries convertedSeries = conversionSeries.multiply(exposure);
return Result.success(convertedSeries.multiply(fxSpotReturnSeries));
} else {
return Result.failure(conversionSeriesResult);
}
} else {
return Result.success(fxSpotReturnSeries.multiply(exposure));
}
} else {
return Result.failure(cpResult);
}
} else {
return Result.failure(calculatorResult);
}
}
private boolean conversionIsRequired(final Currency baseCurrency) {
// No output currency property or it's the same as base
return _outputCurrency.or(baseCurrency) != baseCurrency;
}
private LocalDateDoubleTimeSeries trimSeries(LocalDateDoubleTimeSeries ts) {
LinkedList<LocalDate> dates = Lists.newLinkedList();
LinkedList<Double> values = Lists.newLinkedList();
for (int j = ts.size() - 1; j >= 0; j--) {
LocalDate date = ts.getTimeAtIndex(j);
Double value = ts.getValueAtIndex(j);
dates.addFirst(date);
values.addFirst(value);
if (date.isBefore(_dateRange.getStartDateInclusive())) {
break;
}
}
return ImmutableLocalDateDoubleTimeSeries.of(dates, values);
}
}