/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.marketdata.historical;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.threeten.bp.Duration;
import org.threeten.bp.Instant;
import org.threeten.bp.temporal.ChronoUnit;
import org.threeten.bp.temporal.TemporalUnit;
import com.google.common.collect.Maps;
import com.opengamma.engine.marketdata.MarketDataSnapshot;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.id.UniqueId;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.OpenGammaClock;
/**
* Snapshot composed of 3 underlying snapshots. This snapshot's values are derived by finding the difference between
* values in the first two snapshots and applying it to the value from the third snapshot. The change applied to the
* base value can be the proportional or absolute difference between the two other values.
*/
public class HistoricalShockMarketDataSnapshot implements MarketDataSnapshot {
/**
* The type of transformation to apply to the base value.
*/
public enum ShockType {
/**
* The shocked value is the base value * value2 / value1.
*/
PROPORTIONAL {
@Override
public Double shock(Double value1, Double value2, Double baseValue) {
return baseValue * value2 / value1;
}
},
/**
* The shocked value is the base value + value2 - value1.
*/
ABSOLUTE {
@Override
public Double shock(Double value1, Double value2, Double baseValue) {
return baseValue + value2 - value1;
}
};
/**
*
* @param value1 The first historical value
* @param value2 The second historical value
* @param baseValue The base value
* @return The base value shocked by the difference between the two historical values
*/
public abstract Double shock(Double value1, Double value2, Double baseValue);
}
private final ShockType _shockType;
private final MarketDataSnapshot _historicalSnapshot1;
private final MarketDataSnapshot _historicalSnapshot2;
private final MarketDataSnapshot _baseSnapshot;
public HistoricalShockMarketDataSnapshot(ShockType shockType,
MarketDataSnapshot historicalSnapshot1,
MarketDataSnapshot historicalSnapshot2,
MarketDataSnapshot baseSnapshot) {
ArgumentChecker.notNull(historicalSnapshot1, "historicalSnapshot1");
ArgumentChecker.notNull(historicalSnapshot2, "historicalSnapshot2");
ArgumentChecker.notNull(baseSnapshot, "baseSnapshot");
_shockType = shockType;
_historicalSnapshot1 = historicalSnapshot1;
_historicalSnapshot2 = historicalSnapshot2;
_baseSnapshot = baseSnapshot;
}
@Override
public UniqueId getUniqueId() {
// TODO this is nasty, see PLAT-4292
return UniqueId.of(MARKET_DATA_SNAPSHOT_ID_SCHEME, "HistoricalShockMarketDataSnapshot:" + getSnapshotTime());
}
@Override
public Instant getSnapshotTimeIndication() {
return _baseSnapshot.getSnapshotTimeIndication();
}
@Override
public void init() {
_historicalSnapshot1.init();
_historicalSnapshot2.init();
_baseSnapshot.init();
}
@Override
public void init(Set<ValueSpecification> values, long timeout, TimeUnit ucUnit) {
Instant start = OpenGammaClock.getInstance().instant();
TemporalUnit unit = convertUnit(ucUnit);
Duration remaining = Duration.of(timeout, unit);
_historicalSnapshot1.init(values, timeout, ucUnit);
Instant after1 = OpenGammaClock.getInstance().instant();
Duration duration1 = Duration.between(start, after1);
remaining = remaining.minus(duration1);
if (remaining.isNegative()) {
return;
}
_historicalSnapshot2.init(values, remaining.get(unit), ucUnit);
Instant after2 = OpenGammaClock.getInstance().instant();
Duration duration2 = Duration.between(after1, after2);
remaining = remaining.minus(duration2);
if (remaining.isNegative()) {
return;
}
_baseSnapshot.init(values, remaining.get(unit), ucUnit);
}
@Override
public boolean isInitialized() {
return _baseSnapshot.isInitialized();
}
@Override
public boolean isEmpty() {
return _historicalSnapshot1.isEmpty() || _historicalSnapshot2.isEmpty() || _baseSnapshot.isEmpty();
}
@Override
public Instant getSnapshotTime() {
return _baseSnapshot.getSnapshotTime();
}
@Override
public Object query(ValueSpecification specification) {
Object value1 = _historicalSnapshot1.query(specification);
Object value2 = _historicalSnapshot2.query(specification);
Object baseValue = _baseSnapshot.query(specification);
if (!(value1 instanceof Double) || !(value2 instanceof Double) || !(baseValue instanceof Double)) {
return baseValue;
}
return _shockType.shock((Double) value1, (Double) value2, (Double) baseValue);
}
@Override
public Map<ValueSpecification, Object> query(Set<ValueSpecification> specifications) {
Map<ValueSpecification, Object> values = Maps.newHashMapWithExpectedSize(specifications.size());
for (ValueSpecification specification : specifications) {
values.put(specification, query(specification));
}
return values;
}
private static TemporalUnit convertUnit(TimeUnit unit) {
switch (unit) {
case NANOSECONDS:
return ChronoUnit.NANOS;
case MICROSECONDS:
return ChronoUnit.MICROS;
case MILLISECONDS:
return ChronoUnit.MILLIS;
case SECONDS:
return ChronoUnit.SECONDS;
case MINUTES:
return ChronoUnit.MINUTES;
case HOURS:
return ChronoUnit.HOURS;
case DAYS:
return ChronoUnit.DAYS;
default:
throw new IllegalArgumentException("Unexpected unit " + unit);
}
}
}