/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.marketdata.random;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Duration;
import org.threeten.bp.Instant;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.core.marketdatasnapshot.SnapshotDataBundle;
import com.opengamma.engine.marketdata.AbstractMarketDataProvider;
import com.opengamma.engine.marketdata.MarketDataListener;
import com.opengamma.engine.marketdata.MarketDataPermissionProvider;
import com.opengamma.engine.marketdata.MarketDataProvider;
import com.opengamma.engine.marketdata.MarketDataSnapshot;
import com.opengamma.engine.marketdata.availability.MarketDataAvailabilityProvider;
import com.opengamma.engine.marketdata.spec.MarketDataSpecification;
import com.opengamma.engine.marketdata.spec.RandomizingMarketDataSpecification;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.UniqueId;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.OpenGammaClock;
/**
* TODO document the slightly awkward design because of the requirements:
* provider needs to know what's been updated so it can notify the listeners
* only the snapshot knows the IDs of the data in the underlying snapshot
*/
/* package */ class RandomizingMarketDataProvider extends AbstractMarketDataProvider {
private static final Logger s_logger = LoggerFactory.getLogger(RandomizingMarketDataProvider.class);
private static final Timer s_timer = new Timer();
private final MarketDataProvider _underlying;
private final RandomizingMarketDataSpecification _marketDataSpec;
// TODO does this need to be a class map? will it ever have to deal with subtyping?
private final Map<Class<?>, Randomizer<?>> _randomizers = ImmutableMap.<Class<?>, Randomizer<?>>of(
Double.class, new DoubleRandomizer(),
SnapshotDataBundle.class, new SnapshotDataBundleRandomizer());
/** Lock used to protect the two value maps. */
private final Object _valuesLock = new Object();
/** Values from the underlying provider, repopulated each time a snapshot is queried. */
private final Map<ValueSpecification, Object> _values = Maps.newHashMap();
/** Randomized values derived from {@link #_values}, repopulated each time the randomized task executes. */
private final Map<ValueSpecification, Object> _randomizedValues = Maps.newHashMap();
public RandomizingMarketDataProvider(RandomizingMarketDataSpecification spec, MarketDataProvider underlying) {
ArgumentChecker.notNull(underlying, "underlying");
ArgumentChecker.notNull(spec, "spec");
_marketDataSpec = spec;
_underlying = underlying;
_underlying.addListener(new Listener());
scheduleRandomizingTask();
}
@Override
public void addListener(MarketDataListener listener) {
_underlying.addListener(listener);
}
@Override
public void removeListener(MarketDataListener listener) {
_underlying.removeListener(listener);
}
@Override
public void subscribe(ValueSpecification valueSpecification) {
_underlying.subscribe(valueSpecification);
}
@Override
public void subscribe(Set<ValueSpecification> valueSpecifications) {
_underlying.subscribe(valueSpecifications);
}
@Override
public void unsubscribe(ValueSpecification valueSpecification) {
_underlying.unsubscribe(valueSpecification);
}
@Override
public void unsubscribe(Set<ValueSpecification> valueSpecifications) {
_underlying.unsubscribe(valueSpecifications);
}
@Override
public MarketDataAvailabilityProvider getAvailabilityProvider(MarketDataSpecification marketDataSpec) {
return _underlying.getAvailabilityProvider(marketDataSpec);
}
@Override
public MarketDataPermissionProvider getPermissionProvider() {
return _underlying.getPermissionProvider();
}
@Override
public boolean isCompatible(MarketDataSpecification marketDataSpec) {
return _marketDataSpec.equals(marketDataSpec);
}
// TODO document assumption - this is called once with all market data and the single arg version isn't used
@Override
public RandomizingMarketDataSnapshot snapshot(MarketDataSpecification marketDataSpec) {
if (!(marketDataSpec instanceof RandomizingMarketDataSpecification)) {
throw new IllegalArgumentException("Expected RandomizingMarketDataSpecification, got " + marketDataSpec);
}
RandomizingMarketDataSpecification randomizingSpec = (RandomizingMarketDataSpecification) marketDataSpec;
MarketDataSnapshot underlyingSnapshot = _underlying.snapshot(randomizingSpec.getUnderlying());
return new RandomizingMarketDataSnapshot(underlyingSnapshot);
}
@Override
public Duration getRealTimeDuration(Instant fromInstant, Instant toInstant) {
return _underlying.getRealTimeDuration(fromInstant, toInstant);
}
/**
* @param value The value to randomize
* @return The randomized object or null if its value wasn't changed
*/
private Object randomize(Object value) {
@SuppressWarnings("unchecked")
Randomizer<Object> randomizer = (Randomizer<Object>) _randomizers.get(value.getClass());
if (randomizer == null) {
return null;
} else {
return randomizer.randomize(value);
}
}
/**
* Schedules a new {@link RandomizingTask} to run after a delay of
* {@link RandomizingMarketDataSpecification#getAverageCycleInterval()} +/-50%
*/
private void scheduleRandomizingTask() {
s_timer.schedule(new RandomizingTask(), ((long) (_marketDataSpec.getAverageCycleInterval() * (0.5 + Math.random()))));
}
/**
* Populates a map of randomized values by traversing the previous set of snapshot values and randomly permuting
* a random subset.
*/
private void randomizeSnapshot() {
Set<ValueSpecification> updatedSpecs = Sets.newHashSet();
synchronized (_valuesLock) {
s_logger.debug("Randomizing snapshot");
_randomizedValues.clear();
for (Map.Entry<ValueSpecification, Object> entry : _values.entrySet()) {
ValueSpecification spec = entry.getKey();
Object value = entry.getValue();
Object randomizedValue = randomize(value);
if (randomizedValue != null) {
_randomizedValues.put(spec, randomizedValue);
s_logger.debug("Created random value {} for spec {}", randomizedValue, spec);
updatedSpecs.add(spec);
}
}
}
valuesChanged(updatedSpecs);
s_logger.debug("Notified listeners of updates to specs {}", updatedSpecs);
scheduleRandomizingTask();
}
private double randomizeDouble(double value) {
double signum = (Math.random() < 0.5) ? -1 : 1; // TODO can I get rid of the extra call to Math.random()?
return value * (1 + signum * Math.random() * (double) _marketDataSpec.getMaxPercentageChange() / 100d);
}
private class RandomizingMarketDataSnapshot implements MarketDataSnapshot {
private final MarketDataSnapshot _underlying;
private volatile Instant _snapshotTime;
/* package */ RandomizingMarketDataSnapshot(MarketDataSnapshot underlying) {
ArgumentChecker.notNull(underlying, "underlying");
_underlying = underlying;
}
@Override
public UniqueId getUniqueId() {
// TODO this is nasty, see PLAT-4292
return UniqueId.of(MARKET_DATA_SNAPSHOT_ID_SCHEME, "RandomizingMarketDataSnapshot:" + getSnapshotTime());
}
@Override
public Instant getSnapshotTimeIndication() {
return _snapshotTime == null ? OpenGammaClock.getInstance().instant() : _snapshotTime;
}
@Override
public void init() {
_underlying.init();
_snapshotTime = OpenGammaClock.getInstance().instant();
}
@Override
public void init(Set<ValueSpecification> values, long timeout, TimeUnit unit) {
_underlying.init(values, timeout, unit);
_snapshotTime = OpenGammaClock.getInstance().instant();
}
@Override
public boolean isInitialized() {
return _underlying.isInitialized();
}
@Override
public boolean isEmpty() {
return _underlying.isEmpty();
}
@Override
public Instant getSnapshotTime() {
return _snapshotTime;
}
@Override
public Object query(ValueSpecification specification) {
throw new UnsupportedOperationException("This method is never used and not supported");
}
@Override
public Map<ValueSpecification, Object> query(Set<ValueSpecification> specifications) {
Map<ValueSpecification, Object> values;
synchronized (_valuesLock) {
Map<ValueSpecification, Object> underlyingValues = _underlying.query(specifications);
values = Maps.newHashMap();
_values.clear();
for (Map.Entry<ValueSpecification, Object> entry : underlyingValues.entrySet()) {
// store the underlying values so the randomizing task can use them in its next cycle
ValueSpecification spec = entry.getKey();
Object underlyingValue = entry.getValue();
_values.put(spec, underlyingValue);
Object value;
// if there is a randomized value for a spec use that instead of the underlying value
if (_randomizedValues.containsKey(spec)) {
value = _randomizedValues.get(spec);
s_logger.debug("Using randomized value {} for spec {}", value, spec);
} else {
value = underlyingValue;
}
values.put(spec, value);
}
}
return values;
}
}
/**
* Listener that receives notifications from the underlying provider and forwards them to listeners attached to
* this provider.
*/
private class Listener implements MarketDataListener {
@Override
public void subscriptionsSucceeded(Collection<ValueSpecification> specifications) {
RandomizingMarketDataProvider.this.subscriptionsSucceeded(specifications);
}
@Override
public void subscriptionFailed(ValueSpecification specification, String msg) {
RandomizingMarketDataProvider.this.subscriptionFailed(specification, msg);
}
@Override
public void subscriptionStopped(ValueSpecification specification) {
RandomizingMarketDataProvider.this.subscriptionStopped(specification);
}
@Override
public void valuesChanged(Collection<ValueSpecification> specifications) {
RandomizingMarketDataProvider.this.valuesChanged(specifications);
}
}
/**
* Task to asynchronously invoke {@link #randomizeSnapshot()}.
*/
private class RandomizingTask extends TimerTask {
@Override
public void run() {
randomizeSnapshot();
}
}
private interface Randomizer<T> {
/**
* Possibly randomizes a value, returning the value if it was randomized, null if not
* @param value The value to randomize
* @return The randomized value, null if it wasn't randomized
*/
T randomize(T value);
}
/**
* Randomizes a double value.
*/
private class DoubleRandomizer implements Randomizer<Double> {
@Override
public Double randomize(Double value) {
if (Math.random() > _marketDataSpec.getUpdateProbability()) {
return null;
}
double signum = (Math.random() < 0.5) ? -1 : 1;
return value * (1 + signum * Math.random() * (double) _marketDataSpec.getMaxPercentageChange() / 100d);
}
}
/**
* Randomly chooses and permutes values in a {@link SnapshotDataBundle}.
*/
private class SnapshotDataBundleRandomizer implements Randomizer<SnapshotDataBundle> {
@Override
public SnapshotDataBundle randomize(SnapshotDataBundle value) {
SnapshotDataBundle randomBundle = new SnapshotDataBundle();
boolean randomized = false;
for (Map.Entry<ExternalIdBundle, Double> entry : value.getDataPointSet()) {
double newValue;
if (Math.random() > _marketDataSpec.getUpdateProbability()) {
newValue = entry.getValue();
} else {
newValue = randomizeDouble(entry.getValue());
randomized = true;
}
randomBundle.setDataPoint(entry.getKey(), newValue);
}
if (randomized) {
return randomBundle;
} else {
return null;
}
}
}
/*private class VolatilitySurfaceDataRandomizer implements Randomizer<VolatilitySurfaceData<Object, Object>> {
@Override
public VolatilitySurfaceData<Object, Object> randomize(VolatilitySurfaceData<Object, Object> value) {
// TODO implement randomize()
throw new UnsupportedOperationException("randomize not implemented");
}
}
private class VolatilityCubeDataRandomizer implements Randomizer<VolatilityCubeData> {
@Override
public VolatilityCubeData randomize(VolatilityCubeData value) {
// TODO implement randomize()
throw new UnsupportedOperationException("randomize not implemented");
}
}*/
}