/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.integration.marketdata.manipulator.dsl;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Instant;
import org.threeten.bp.ZonedDateTime;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.engine.function.EmptyFunctionParameters;
import com.opengamma.engine.function.FunctionParameters;
import com.opengamma.engine.marketdata.manipulator.CompositeMarketDataSelector;
import com.opengamma.engine.marketdata.manipulator.DistinctMarketDataSelector;
import com.opengamma.engine.marketdata.manipulator.ScenarioDefinition;
import com.opengamma.engine.marketdata.spec.MarketDataSpecification;
import com.opengamma.engine.view.ViewProcessor;
import com.opengamma.engine.view.client.ViewClient;
import com.opengamma.engine.view.execution.ArbitraryViewCycleExecutionSequence;
import com.opengamma.engine.view.execution.ExecutionFlags;
import com.opengamma.engine.view.execution.ExecutionOptions;
import com.opengamma.engine.view.execution.ViewCycleExecutionOptions;
import com.opengamma.engine.view.execution.ViewCycleExecutionSequence;
import com.opengamma.engine.view.execution.ViewExecutionFlags;
import com.opengamma.engine.view.execution.ViewExecutionOptions;
import com.opengamma.engine.view.listener.ViewResultListener;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.util.ArgumentChecker;
/**
* A collection of {@link Scenario}s, each of which modifies the market data in a single calculation cycle.
*/
public class Simulation {
private static final Logger s_logger = LoggerFactory.getLogger(Simulation.class);
/** The simulation name. */
private final String _name; // TODO this needs to be passed to the results somehow
/** The scenarios in this simulation, keyed by name. */
private final Map<String, Scenario> _scenarios = Maps.newLinkedHashMap();
/** The default calculation configuration name for scenarios. */
private Set<String> _calcConfigNames;
/** The default valuation time for scenarios. */
private Instant _valuationTime;
/** The default resolver version correction for scenarios. */
private VersionCorrection _resolverVersionCorrection;
/** The name of the base scenario (i.e. containing no transformations) */
private String _baseScenarioName;
/**
* Creates a new simulation with a calculation configuration name of "Default", valuation time of {@code Instant.now()}
* and resolver version correction of {@link VersionCorrection#LATEST}.
* @param name The simulation name
*/
public Simulation(String name) {
ArgumentChecker.notEmpty(name, "name");
_name = name;
}
/**
* Creates a new simulation, specifying the default values to use for its scenarios
* @param name The simulation name
* @param calcConfigNames The default calculation configuration name for scenarios
* @param valuationTime The default valuation time for scenarios
* @param resolverVersionCorrection The default resolver version correction for scenarios
*/
public Simulation(String name, Instant valuationTime, VersionCorrection resolverVersionCorrection, String... calcConfigNames) {
ArgumentChecker.notEmpty(name, "name");
ArgumentChecker.notNull(valuationTime, "valuationTime");
ArgumentChecker.notNull(resolverVersionCorrection, "resolverVersionCorrection");
_name = name;
if (calcConfigNames.length > 0) {
_calcConfigNames = ImmutableSet.copyOf(calcConfigNames);
} else {
_calcConfigNames = null;
}
_valuationTime = valuationTime;
_resolverVersionCorrection = resolverVersionCorrection;
}
/* package */ Set<DistinctMarketDataSelector> allSelectors() {
// TODO check for empty scenarios
Set<DistinctMarketDataSelector> selectors = Sets.newHashSet();
for (Scenario scenario : _scenarios.values()) {
selectors.addAll(scenario.createDefinition().getDefinitionMap().keySet());
}
return Collections.unmodifiableSet(selectors);
}
/**
* Builds cycle execution options for each scenario in this simulation.
* @param baseOptions Base set of options
* @param allSelectors This simulation's selectors
* @return Execution options for each scenario in this simulation
*/
/* package */ List<ViewCycleExecutionOptions> cycleExecutionOptions(ViewCycleExecutionOptions baseOptions,
Set<DistinctMarketDataSelector> allSelectors) {
List<ViewCycleExecutionOptions> options = Lists.newArrayListWithCapacity(_scenarios.size());
for (Scenario scenario : _scenarios.values()) {
ScenarioDefinition definition = scenario.createDefinition();
Map<DistinctMarketDataSelector, FunctionParameters> scenarioParams = definition.getDefinitionMap();
Map<DistinctMarketDataSelector, FunctionParameters> params = Maps.newHashMap();
params.putAll(scenarioParams);
// if a selector isn't used by a particular scenario then it needs to have a no-op manipulator. if it didn't
// then the manipulator from the previous scenario would be used
Set<DistinctMarketDataSelector> unusedSelectors = Sets.difference(allSelectors, params.keySet());
for (DistinctMarketDataSelector unusedSelector : unusedSelectors) {
params.put(unusedSelector, EmptyFunctionParameters.INSTANCE);
}
ViewCycleExecutionOptions scenarioOptions = baseOptions.copy()
.setFunctionParameters(params)
.setValuationTime(scenario.getValuationTime())
.setResolverVersionCorrection(scenario.getResolverVersionCorrection())
.create();
options.add(scenarioOptions);
}
return options;
}
/**
* Returns the scenario with the given name. If no scenario exists with the specified name it is created and
* initialized with default the simulation's default values for calculation configuration, valuation time and
* resolver version correction.
* @param name The scenario name
* @return The scenario.
* TODO check the name isn't the base scenario name and throw IAE
*/
public Scenario scenario(String name) {
ArgumentChecker.notEmpty(name, "name");
if (name.equals(_baseScenarioName)) {
throw new IllegalArgumentException("Can't add scenario named " + name + ", a base scenario exists with " +
"that name");
}
if (_scenarios.containsKey(name)) {
return _scenarios.get(name);
} else {
Scenario scenario = new Scenario(this, name);
_scenarios.put(name, scenario);
return scenario;
}
}
/**
* Creates a base scenario with the given name. A base scenario has no transformations defined.
* @param name The name of the base scenario
* @return This simulation
* @throws IllegalStateException If the base scenario name has already been set
* @throws IllegalArgumentException If there is already a non-base scenario with the specified name
*/
public Simulation baseScenarioName(String name) {
ArgumentChecker.notEmpty(name, "name");
if (_baseScenarioName != null) {
throw new IllegalStateException("Base scenario already defined with name " + _baseScenarioName);
}
if (_scenarios.containsKey(name)) {
throw new IllegalArgumentException("Cannot add a base scenario named " + name + ", a scenario already exists " +
"with that name");
}
Scenario base = new Scenario(this, name);
_scenarios.put(name, base);
_baseScenarioName = name;
return this;
}
/**
* Sets the calculation configuration name to which the scenarios will apply.
* @param calcConfigNames The calculation configuration name to which the scenarios will apply
* @return This simulation
* @throws IllegalStateException If the calculation configuration names have already been set
*/
public Simulation calculationConfigurations(String... calcConfigNames) {
ArgumentChecker.notEmpty(calcConfigNames, "calcConfigNames");
if (_calcConfigNames != null) {
throw new IllegalStateException("Calculation configuration names are already set");
}
_calcConfigNames = ImmutableSet.copyOf(calcConfigNames);
return this;
}
/**
* Sets the {@link VersionCorrection} used when resolving positions and portfolios
* @param versionCorrection The version/correction used when resolving positions and portfolios
* @return This simulation
*/
public Simulation resolverVersionCorrection(VersionCorrection versionCorrection) {
ArgumentChecker.notNull(versionCorrection, "versionCorrection");
if (_resolverVersionCorrection != null) {
throw new IllegalStateException("Resolver version correction has already been set");
}
_resolverVersionCorrection = versionCorrection;
return this;
}
/**
* Sets the valuation time used in the calculations
* @param valuationTime The valuation time used in the calculations
* @return This simulation
* @throws IllegalStateException If the valuation time has already been set
*/
public Simulation valuationTime(Instant valuationTime) {
ArgumentChecker.notNull(valuationTime, "valuationTime");
if (_valuationTime != null) {
throw new IllegalStateException("Valuation time has already been set");
}
_valuationTime = valuationTime;
return this;
}
/**
* Sets the valuation time used in the calculations
* @param valuationTime The valuation time used in the calculations
* @return This simulation
* @throws IllegalStateException If the valuation time has already been set
*/
public Simulation valuationTime(ZonedDateTime valuationTime) {
ArgumentChecker.notNull(valuationTime, "valuationTime");
return valuationTime(valuationTime.toInstant());
}
/**
* Executes this simulation on a running server.
* @param viewDefId The ID of the view definition to use
* @param marketDataSpecs The market data to use when running the view
* @param batchMode Whether to run the simulation using batch mode
* @param listener Listener that is notified as the simulation runs
* @param viewProcessor View process that will be used to execute the simulation
*/
public void run(UniqueId viewDefId,
List<MarketDataSpecification> marketDataSpecs,
boolean batchMode,
ViewResultListener listener,
ViewProcessor viewProcessor) {
ViewClient viewClient = viewProcessor.createViewClient(UserPrincipal.getTestUser());
try {
Set<DistinctMarketDataSelector> allSelectors = allSelectors();
ViewCycleExecutionOptions baseOptions =
ViewCycleExecutionOptions
.builder()
.setMarketDataSpecifications(marketDataSpecs)
.setMarketDataSelector(CompositeMarketDataSelector.of(allSelectors))
.create();
List<ViewCycleExecutionOptions> cycleOptions = cycleExecutionOptions(baseOptions, allSelectors);
ViewCycleExecutionSequence sequence = new ArbitraryViewCycleExecutionSequence(cycleOptions);
EnumSet<ViewExecutionFlags> executionFlags = ExecutionFlags.none().awaitMarketData().runAsFastAsPossible().get();
ViewExecutionOptions executionOptions;
if (listener != null) {
viewClient.setResultListener(listener);
}
if (batchMode) {
executionOptions = ExecutionOptions.batch(sequence, baseOptions);
} else if (listener != null) {
executionOptions = ExecutionOptions.of(sequence, executionFlags);
} else {
s_logger.warn("Not running in batch mode and no listener specified, the results would be ignored. Exiting.");
return;
}
s_logger.info("Attaching to view process, view def ID {}, execution options {}", viewDefId, executionOptions);
viewClient.attachToViewProcess(viewDefId, executionOptions, true);
try {
viewClient.waitForCompletion();
} catch (InterruptedException e) {
s_logger.warn("Interrupted waiting for ViewClient to complete", e);
}
} finally {
viewClient.shutdown();
}
}
/* package */ Set<String> getCalcConfigNames() {
return _calcConfigNames;
}
/* package */ Instant getValuationTime() {
return _valuationTime;
}
/* package */ VersionCorrection getResolverVersionCorrection() {
return _resolverVersionCorrection;
}
/* package */ Map<String, Scenario> getScenarios() {
return Collections.unmodifiableMap(_scenarios);
}
@Override
public int hashCode() {
return Objects.hash(_name, _scenarios, _calcConfigNames, _valuationTime, _resolverVersionCorrection, _baseScenarioName);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final Simulation other = (Simulation) obj;
return Objects.equals(this._name, other._name) &&
Objects.equals(this._scenarios, other._scenarios) &&
Objects.equals(this._calcConfigNames, other._calcConfigNames) &&
Objects.equals(this._valuationTime, other._valuationTime) &&
Objects.equals(this._resolverVersionCorrection, other._resolverVersionCorrection) &&
Objects.equals(this._baseScenarioName, other._baseScenarioName);
}
@Override
public String toString() {
return "Simulation [" +
"_scenarios=" + _scenarios +
", _calcConfigNames=" + _calcConfigNames +
", _valuationTime=" + _valuationTime +
", _resolverVersionCorrection=" + _resolverVersionCorrection +
"]";
}
}