/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.view;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.ObjectUtils;
import org.fudgemsg.FudgeField;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.MutableFudgeMsg;
import org.fudgemsg.mapping.FudgeDeserializer;
import org.fudgemsg.mapping.FudgeSerializer;
import org.threeten.bp.LocalDate;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.holiday.HolidaySource;
import com.opengamma.engine.function.FunctionExecutionContext;
import com.opengamma.engine.marketdata.historical.HistoricalShockMarketDataSnapshot.ShockType;
import com.opengamma.engine.marketdata.spec.FixedHistoricalMarketDataSpecification;
import com.opengamma.engine.marketdata.spec.HistoricalShockMarketDataSpecification;
import com.opengamma.engine.marketdata.spec.MarketDataSpecification;
import com.opengamma.engine.view.ViewDefinition;
import com.opengamma.engine.view.execution.ArbitraryViewCycleExecutionSequence;
import com.opengamma.engine.view.execution.ViewCycleExecutionOptions;
import com.opengamma.engine.view.execution.ViewCycleExecutionSequence;
import com.opengamma.financial.OpenGammaExecutionContext;
import com.opengamma.financial.analytics.timeseries.DateConstraint;
import com.opengamma.financial.convention.HolidaySourceCalendarAdapter;
import com.opengamma.id.UniqueId;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
/**
* Target for {@link HistoricalViewEvaluationFunction} which ensures that an execution sequence is constructed which matches the function's expectations.
*/
public class HistoricalViewEvaluationTarget extends ViewEvaluationTarget {
/**
* Fudge field containing the start date
*/
protected static final String START_DATE_FIELD = "startDate";
/**
* Fudge field containing whether the start date is inclusive.
*/
protected static final String INCLUDE_START_FIELD = "includeStart";
/**
* Fudge field containing the end date.
*/
protected static final String END_DATE_FIELD = "endDate";
/**
* Fudge field containing whether the end date is inclusive.
*/
protected static final String INCLUDE_END_FIELD = "includeEnd";
/**
* Repeated Fudge field, each containing a currency ISO code for the compound holiday calendar to use.
*/
protected static final String CURRENCY_CALENDAR_FIELD = "currencyCalendar";
/**
* Fudge field containing the market data mode.
*/
protected static final String MARKET_DATA_MODE_FIELD = "marketDataMode";
/**
* Creates a new target.
*
* @param user the user the view is created for, not null.
* @param startDate the start date as a {@link DateConstraint} encoded string, not null
* @param includeStart whether to include the start date in the evaluation.
* @param endDate the end date as a {@link DateConstraint} encoded string, not null
* @param includeEnd whether to include the end date in the evaluation.
* @param currencyCalendars the currencies for which to obtain a compound holiday calendar in order to obtain a valid sequence of dates, may be null
* @param marketDataMode the mode in which market data should be obtained, not null
*/
public HistoricalViewEvaluationTarget(UserPrincipal user, String startDate, boolean includeStart, String endDate,
boolean includeEnd, Set<Currency> currencyCalendars, HistoricalViewEvaluationMarketDataMode marketDataMode) {
super(user, new HistoricalSequence(startDate, includeStart, endDate, includeEnd, currencyCalendars, marketDataMode));
}
protected HistoricalViewEvaluationTarget(ViewDefinition viewDefinition, final HistoricalSequence sequence) {
super(viewDefinition, sequence);
}
private HistoricalViewEvaluationTarget(FudgeDeserializer deserializer, FudgeMsg message) {
this(deserializer, message,
message.getString(START_DATE_FIELD),
message.getBoolean(INCLUDE_START_FIELD),
message.getString(END_DATE_FIELD),
message.getBoolean(INCLUDE_END_FIELD),
getCurrencyCalendars(deserializer, message),
deserializer.fieldValueToObject(HistoricalViewEvaluationMarketDataMode.class, message.getByName(MARKET_DATA_MODE_FIELD)));
}
private HistoricalViewEvaluationTarget(FudgeDeserializer deserializer, FudgeMsg message, String startDate,
boolean includeStart, String endDate, boolean includeEnd, Set<Currency> currencyCalendars, HistoricalViewEvaluationMarketDataMode marketDataMode) {
super(deserializer, message, new HistoricalSequence(startDate, includeStart, endDate, includeEnd, currencyCalendars, marketDataMode));
}
protected HistoricalViewEvaluationTarget(final HistoricalViewEvaluationTarget copyFrom, final UniqueId uid) {
super(copyFrom, uid);
}
@Override
protected ViewEvaluationTarget createUnion(final ViewDefinition newViewDefinition) {
return new HistoricalViewEvaluationTarget(newViewDefinition, (HistoricalSequence) getExecutionSequence());
}
private static Set<Currency> getCurrencyCalendars(FudgeDeserializer deserializer, FudgeMsg message) {
Set<Currency> currencies = new HashSet<Currency>();
for (FudgeField ccyField : message.getAllByName(CURRENCY_CALENDAR_FIELD)) {
Currency ccy = deserializer.fieldValueToObject(Currency.class, ccyField);
currencies.add(ccy);
}
return !currencies.isEmpty() ? currencies : null;
}
private static final class HistoricalSequence implements ViewCycleExecutionSequenceDescriptor {
/**
* The description of the start date.
*/
private final String _startDateDescriptor;
/**
* Indicates whether to include the start date.
*/
private final boolean _includeStart;
/**
* The description of the end date.
*/
private final String _endDateDescriptor;
/**
* Indicates whether to include the end date.
*/
private final boolean _includeEnd;
/**
* The currencies for which to obtain a compound calendar in order to generate the correct sequence of dates, null to ignore holidays.
*/
private final Set<Currency> _currencyCalendars;
/**
* The historical market data type
*/
private final HistoricalViewEvaluationMarketDataMode _marketDataMode;
public HistoricalSequence(String startDateDescriptor, boolean includeStart, String endDateDescriptor,
boolean includeEnd, Set<Currency> currencyCalendars, HistoricalViewEvaluationMarketDataMode marketDataMode) {
ArgumentChecker.notNull(startDateDescriptor, "startDate");
ArgumentChecker.notNull(endDateDescriptor, "endDate");
ArgumentChecker.notNull(marketDataMode, "marketDataMode");
_startDateDescriptor = startDateDescriptor;
_includeStart = includeStart;
_endDateDescriptor = endDateDescriptor;
_includeEnd = includeEnd;
_currencyCalendars = currencyCalendars;
_marketDataMode = marketDataMode;
}
// ViewCycleExecutionSequenceDescriptor
@Override
public ViewCycleExecutionSequence createSequence(FunctionExecutionContext executionContext) {
LocalDate startDate = DateConstraint.evaluate(executionContext, _startDateDescriptor);
LocalDate endDate = DateConstraint.evaluate(executionContext, _endDateDescriptor);
if (!_includeStart) {
startDate = startDate.plusDays(1);
}
if (!_includeEnd) {
endDate = endDate.minusDays(1);
}
HolidaySourceCalendarAdapter calendar;
if (_currencyCalendars != null) {
Currency[] currencies = _currencyCalendars.toArray(new Currency[_currencyCalendars.size()]);
HolidaySource holidaySource = OpenGammaExecutionContext.getHolidaySource(executionContext);
calendar = new HolidaySourceCalendarAdapter(holidaySource, currencies);
} else {
calendar = null;
}
List<ViewCycleExecutionOptions> executionSequence = new LinkedList<ViewCycleExecutionOptions>();
LocalDate previousWorkingDate = null;
LocalDate currentDate = startDate;
while (!currentDate.isAfter(endDate)) {
if (calendar == null || calendar.isWorkingDay(currentDate)) {
MarketDataSpecification marketDataSpec = createMarketDataSpec(previousWorkingDate, currentDate, LocalDate.now(executionContext.getValuationClock()));
if (marketDataSpec != null) {
ViewCycleExecutionOptions executionOptions = ViewCycleExecutionOptions.builder()
.setMarketDataSpecification(marketDataSpec)
.create();
executionSequence.add(executionOptions);
}
previousWorkingDate = currentDate;
}
currentDate = currentDate.plusDays(1);
}
return new ArbitraryViewCycleExecutionSequence(executionSequence);
}
private MarketDataSpecification createMarketDataSpec(LocalDate previousHistoricalDate, LocalDate historicalDate, LocalDate valuationDate) {
FixedHistoricalMarketDataSpecification historicalDateSpec = new FixedHistoricalMarketDataSpecification(historicalDate);
switch (_marketDataMode) {
case HISTORICAL:
return historicalDateSpec;
case RELATIVE_SHOCK:
if (previousHistoricalDate == null) {
return null;
}
FixedHistoricalMarketDataSpecification valuationDateSpec = new FixedHistoricalMarketDataSpecification(valuationDate);
FixedHistoricalMarketDataSpecification previousHistoricalDateSpec = new FixedHistoricalMarketDataSpecification(previousHistoricalDate);
return new HistoricalShockMarketDataSpecification(ShockType.PROPORTIONAL, previousHistoricalDateSpec, historicalDateSpec, valuationDateSpec);
default:
throw new OpenGammaRuntimeException("Unsupported market data mode: " + _marketDataMode);
}
}
// Object
@Override
public int hashCode() {
int hc = 1;
hc += (hc << 4) + ObjectUtils.hashCode(_startDateDescriptor);
hc += (hc << 4) + (_includeStart ? 1 : 0);
hc += (hc << 4) + ObjectUtils.hashCode(_endDateDescriptor);
hc += (hc << 4) + (_includeEnd ? 1 : 0);
hc += (hc << 4) + (_currencyCalendars != null ? _currencyCalendars.hashCode() : 0);
hc += (hc << 4) + _marketDataMode.hashCode();
return hc;
}
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof HistoricalSequence)) {
return false;
}
final HistoricalSequence other = (HistoricalSequence) o;
return ObjectUtils.equals(_startDateDescriptor, other._startDateDescriptor)
&& (_includeStart == other._includeStart)
&& ObjectUtils.equals(_endDateDescriptor, other._endDateDescriptor)
&& (_includeEnd == other._includeEnd)
&& ObjectUtils.equals(_currencyCalendars, other._currencyCalendars)
&& (_marketDataMode == other._marketDataMode);
}
}
public String getStartDate() {
return ((HistoricalSequence) getExecutionSequence())._startDateDescriptor;
}
public boolean isIncludeStart() {
return ((HistoricalSequence) getExecutionSequence())._includeStart;
}
public String getEndDate() {
return ((HistoricalSequence) getExecutionSequence())._endDateDescriptor;
}
public boolean isIncludeEnd() {
return ((HistoricalSequence) getExecutionSequence())._includeEnd;
}
public Set<Currency> getCurrencyCalendars() {
return ((HistoricalSequence) getExecutionSequence())._currencyCalendars;
}
public HistoricalViewEvaluationMarketDataMode getMarketDataMode() {
return ((HistoricalSequence) getExecutionSequence())._marketDataMode;
}
@Override
public HistoricalViewEvaluationTarget withUniqueId(final UniqueId uid) {
return new HistoricalViewEvaluationTarget(this, uid);
}
@Override
protected void toFudgeMsgImpl(FudgeSerializer serializer, MutableFudgeMsg message) {
super.toFudgeMsgImpl(serializer, message);
}
@Override
protected void serializeExecutionSequence(FudgeSerializer serializer, MutableFudgeMsg message) {
// More efficient to recreate the execution sequence rather than serializing it
message.add(START_DATE_FIELD, getStartDate());
message.add(INCLUDE_START_FIELD, isIncludeStart());
message.add(END_DATE_FIELD, getEndDate());
message.add(INCLUDE_END_FIELD, isIncludeEnd());
if (getCurrencyCalendars() != null) {
for (Currency ccy : getCurrencyCalendars()) {
serializer.addToMessage(message, CURRENCY_CALENDAR_FIELD, null, ccy);
}
}
serializer.addToMessage(message, MARKET_DATA_MODE_FIELD, null, getMarketDataMode());
}
public static HistoricalViewEvaluationTarget fromFudgeMsg(FudgeDeserializer deserializer, FudgeMsg message) {
return new HistoricalViewEvaluationTarget(deserializer, message);
}
}