/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.integration.marketdata.manipulator.dsl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Collection;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.threeten.bp.LocalDate;
import org.threeten.bp.format.DateTimeParseException;
import com.google.common.collect.Lists;
import com.opengamma.core.marketdatasnapshot.impl.ManageableMarketDataSnapshot;
import com.opengamma.core.position.impl.SimplePosition;
import com.opengamma.core.position.impl.SimpleTrade;
import com.opengamma.core.security.Security;
import com.opengamma.core.security.SecurityLink;
import com.opengamma.core.security.SecuritySource;
import com.opengamma.core.security.impl.SimpleSecurityLink;
import com.opengamma.engine.marketdata.spec.FixedHistoricalMarketDataSpecification;
import com.opengamma.engine.marketdata.spec.LatestHistoricalMarketDataSpecification;
import com.opengamma.engine.marketdata.spec.LiveMarketDataSpecification;
import com.opengamma.engine.marketdata.spec.MarketDataSpecification;
import com.opengamma.engine.marketdata.spec.UserMarketDataSpecification;
import com.opengamma.engine.view.ViewDefinition;
import com.opengamma.id.UniqueId;
import com.opengamma.id.UniqueIdentifiable;
import com.opengamma.id.VersionCorrection;
import com.opengamma.integration.server.RemoteServer;
import com.opengamma.master.marketdatasnapshot.MarketDataSnapshotMaster;
import com.opengamma.master.marketdatasnapshot.MarketDataSnapshotSearchRequest;
import com.opengamma.master.marketdatasnapshot.MarketDataSnapshotSearchResult;
import com.opengamma.master.position.ManageablePosition;
import com.opengamma.master.position.ManageableTrade;
import com.opengamma.master.security.ManageableSecurityLink;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
/**
* Runs a set of scenarios defined in a Groovy DSL script against a running server and returns the results.
*/
public class StandAloneScenarioRunner {
private StandAloneScenarioRunner() {
}
// TODO allow a default server to be picked up from config or the registry. or maybe supplied as an argument
/**
* Runs a set of scenarios defined in a Groovy DSL script against a running server.
* @param scriptFileName The file containing the scenario definitions
* @return The results of running the scenarios
* @throws IOException If the script file can't be found or read
* @throws IllegalArgumentException If any data in the script is invalid or missing
*/
public static List<ScenarioResultModel> runScenarioScript(String scriptFileName) throws IOException {
return runScenarioScript(new File(scriptFileName));
}
/**
* Runs a set of scenarios defined in a Groovy DSL script against a running server.
* @param scriptFile The file containing the scenario definitions
* @return The results of running the scenarios
* @throws IOException If the script file can't be found or read
* @throws IllegalArgumentException If any data in the script is invalid or missing
*/
public static List<ScenarioResultModel> runScenarioScript(File scriptFile) throws IOException {
StandAloneScenarioScript script = runScript(scriptFile);
// pull the data out of the script and check it's valid and complete
ViewDelegate viewDelegate = script.getViewDelegate();
String viewName = viewDelegate.getName();
String serverUrl = viewDelegate.getServer();
List<MarketDataDelegate.MarketDataSpec> marketDataSpecs = viewDelegate.getMarketDataDelegate().getSpecifications();
validateScript(viewName, serverUrl, marketDataSpecs);
ScenarioListener listener;
List<SimpleResultModel> results;
// connect to the server and execute the scenarios
try (RemoteServer server = RemoteServer.create(serverUrl)) {
ViewDefinition viewDef = server.getConfigSource().getLatestByName(ViewDefinition.class, viewName);
if (viewDef == null) {
throw new IllegalArgumentException("No view definition found with name " + viewName);
}
List<MarketDataSpecification> dataSpecs = convertMarketData(marketDataSpecs, server.getMarketDataSnapshotMaster());
Simulation simulation = script.getSimulation();
listener = new ScenarioListener(simulation.getScenarioNames());
simulation.run(viewDef.getUniqueId(), dataSpecs, false, listener, server.getViewProcessor());
results = listener.getResults();
populateSecurities(results, server.getSecuritySource());
}
List<ScenarioResultModel> scenarioResults = Lists.newArrayListWithCapacity(results.size());
for (SimpleResultModel result : results) {
scenarioResults.add(new ScenarioResultModel(result, script.getScenarioParameters(result.getCycleName())));
}
return scenarioResults;
}
private static void populateSecurities(List<SimpleResultModel> results, SecuritySource securitySource) {
for (SimpleResultModel resultModel : results) {
for (UniqueIdentifiable uniqueIdentifiable : resultModel.getTargets()) {
SecurityLink securityLink = getSecurityLink(uniqueIdentifiable);
if (securityLink != null) {
Security security = loadSecurity(securityLink, securitySource);
if (security != null) {
setSecurityLink(uniqueIdentifiable, security);
}
}
}
}
}
private static Security loadSecurity(SecurityLink securityLink, SecuritySource securitySource) {
Collection<Security> securities;
if (!securityLink.getExternalId().isEmpty()) {
securities = securitySource.get(securityLink.getExternalId());
if (!securities.isEmpty()) {
return securities.iterator().next();
}
}
if (securityLink.getObjectId() != null) {
return securitySource.get(securityLink.getObjectId(), VersionCorrection.LATEST);
}
return null;
}
private static SecurityLink getSecurityLink(Object target) {
if (target instanceof ManageablePosition) {
return ((ManageablePosition) target).getSecurityLink();
} else if (target instanceof SimplePosition) {
return ((SimplePosition) target).getSecurityLink();
} else if (target instanceof ManageableTrade) {
return ((ManageableTrade) target).getSecurityLink();
} else if (target instanceof SimpleTrade) {
return ((SimpleTrade) target).getSecurityLink();
} else {
return null;
}
}
private static void setSecurityLink(Object positionOrTrade, Security security) {
if (positionOrTrade instanceof ManageablePosition) {
((ManageablePosition) positionOrTrade).setSecurityLink(ManageableSecurityLink.of(security));
} else if (positionOrTrade instanceof ManageableTrade) {
((ManageableTrade) positionOrTrade).setSecurityLink(ManageableSecurityLink.of(security));
} else if (positionOrTrade instanceof SimplePosition) {
((SimplePosition) positionOrTrade).setSecurityLink(SimpleSecurityLink.of(security));
} else if (positionOrTrade instanceof SimpleTrade) {
((SimpleTrade) positionOrTrade).setSecurityLink(SimpleSecurityLink.of(security));
}
}
private static StandAloneScenarioScript runScript(File scriptFile) throws IOException {
CompilerConfiguration config = new CompilerConfiguration();
config.setScriptBaseClass(StandAloneScenarioScript.class.getName());
GroovyShell shell = new GroovyShell(config);
StandAloneScenarioScript script;
try (Reader reader = new BufferedReader(new FileReader(scriptFile))) {
script = (StandAloneScenarioScript) shell.parse(reader, scriptFile.getAbsolutePath());
}
Binding binding = new Binding();
SimulationUtils.registerAliases(binding);
script.setBinding(binding);
script.run();
return script;
}
private static void validateScript(String viewName,
String serverUrl,
List<MarketDataDelegate.MarketDataSpec> marketDataSpecs) {
if (StringUtils.isEmpty(viewName)) {
throw new IllegalArgumentException("A view name must be specified");
}
if (StringUtils.isEmpty(serverUrl)) {
throw new IllegalArgumentException("A server must be specified");
}
if (marketDataSpecs == null || marketDataSpecs.isEmpty()) {
throw new IllegalArgumentException("Market data must be specified to run the view");
}
}
/**
* Converts the market data specified by the script into a format usable by the engine.
* This is necessary because snapshot data sources must be created with a {@link UniqueId} but we
* want to refer to them by name in the script.
* @param marketDataSpecs Market data sources defined in the script
* @param snapshotMaster For converting snapshot names to IDs
* @return Market data specifications suitable for the engine
*/
private static List<MarketDataSpecification> convertMarketData(List<MarketDataDelegate.MarketDataSpec> marketDataSpecs,
MarketDataSnapshotMaster snapshotMaster) {
List<MarketDataSpecification> specifications = Lists.newArrayListWithCapacity(marketDataSpecs.size());
for (MarketDataDelegate.MarketDataSpec spec : marketDataSpecs) {
MarketDataSpecification specification;
switch (spec.getType()) {
case LIVE:
specification = LiveMarketDataSpecification.of(spec.getSpec());
break;
case FIXED_HISTORICAL:
LocalDate date;
try {
date = LocalDate.parse(spec.getSpec());
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Historical market data date isn't in a valid format. Expected format " +
"'yyyy-MM-dd', value: " + spec.getSpec());
}
specification = new FixedHistoricalMarketDataSpecification(date);
break;
case LATEST_HISTORICAL:
specification = new LatestHistoricalMarketDataSpecification();
break;
case SNAPSHOT:
MarketDataSnapshotSearchRequest searchRequest = new MarketDataSnapshotSearchRequest();
searchRequest.setName(spec.getSpec());
MarketDataSnapshotSearchResult searchResult = snapshotMaster.search(searchRequest);
List<ManageableMarketDataSnapshot> snapshots = searchResult.getSnapshots();
if (snapshots.isEmpty()) {
throw new IllegalArgumentException("No snapshot found named " + spec.getSpec());
}
if (snapshots.size() > 1) {
throw new IllegalArgumentException("Multiple snapshots found named " + spec.getSpec());
}
specification = UserMarketDataSpecification.of(snapshots.get(0).getUniqueId());
break;
default:
throw new IllegalArgumentException("Unexpected market data type: " + spec.getType());
}
specifications.add(specification);
}
return specifications;
}
}