/**
* 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.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.LocalDate;
import org.threeten.bp.format.DateTimeParseException;
import com.google.common.collect.Lists;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.component.tool.AbstractTool;
import com.opengamma.core.config.ConfigSource;
import com.opengamma.core.config.impl.ConfigItem;
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.engine.view.ViewProcessor;
import com.opengamma.engine.view.listener.ViewResultListener;
import com.opengamma.financial.tool.ToolContext;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.scripts.Scriptable;
/**
* Tool for running simulations defined in Groovy DSL scripts.
*/
@Scriptable
public class SimulationTool extends AbstractTool<ToolContext> {
private static final Logger s_logger = LoggerFactory.getLogger(SimulationTool.class);
/** Command line option for view definition name. */
private static final String VIEW_DEF_NAME_OPTION = "v";
/** Command line option for whether to execute in batch mode. */
private static final String BATCH_MODE_OPTION = "b";
/** Command line option for the class name of an implementation of ViewResultListener. */
private static final String RESULT_LISTENER_CLASS_OPTION = "r";
/** Command line option for the location of the Groovy script that defines the simulation. */
private static final String SIMULATION_SCRIPT_OPTION = "s";
/** Command line option for the location of the Groovy script that defines the simulation parameters. */
private static final String PARAMETER_SCRIPT_OPTION = "p";
/** Command line option for the names of the market data sources used for running the view. */
private static final String MARKET_DATA_OPTION = "m";
public static void main(final String[] args) {
new SimulationTool().initAndRun(args, ToolContext.class);
System.exit(0);
}
@Override
protected void doRun() throws Exception {
ViewProcessor viewProcessor = getToolContext().getViewProcessor();
ConfigSource configSource = getToolContext().getConfigSource();
String viewDefName = getCommandLine().getOptionValue(VIEW_DEF_NAME_OPTION);
boolean batchMode = getCommandLine().hasOption(BATCH_MODE_OPTION);
ViewResultListener listener;
if (getCommandLine().hasOption(RESULT_LISTENER_CLASS_OPTION)) {
String listenerClass = getCommandLine().getOptionValue(RESULT_LISTENER_CLASS_OPTION);
listener = instantiate(listenerClass, ViewResultListener.class);
} else {
listener = null;
}
String[] marketDataSpecStrs = getCommandLine().getOptionValues(MARKET_DATA_OPTION);
List<MarketDataSpecification> marketDataSpecs = Lists.newArrayListWithCapacity(marketDataSpecStrs.length);
for (String marketDataSpecStr : marketDataSpecStrs) {
try {
marketDataSpecs.add(MarketDataSpecificationParser.parse(marketDataSpecStr));
} catch (IllegalArgumentException e) {
s_logger.warn(MarketDataSpecificationParser.getUsageMessage());
throw e;
}
}
Map<String, Object> paramValues;
if (getCommandLine().hasOption(PARAMETER_SCRIPT_OPTION)) {
String paramScript = getCommandLine().getOptionValue(PARAMETER_SCRIPT_OPTION);
ScenarioDslParameters params = new ScenarioDslParameters(FileUtils.readFileToString(new File(paramScript)));
paramValues = params.getParameters();
} else {
paramValues = null;
}
String simulationScript = getCommandLine().getOptionValue(SIMULATION_SCRIPT_OPTION);
Simulation simulation = SimulationUtils.createSimulationFromDsl(simulationScript, paramValues);
VersionCorrection viewDefVersionCorrection = VersionCorrection.LATEST;
Collection<ConfigItem<ViewDefinition>> viewDefs =
configSource.get(ViewDefinition.class, viewDefName, viewDefVersionCorrection);
if (viewDefs.isEmpty()) {
throw new IllegalStateException("View definition " + viewDefName + " not found");
}
ConfigItem<ViewDefinition> viewDef = viewDefs.iterator().next();
UniqueId viewDefId = viewDef.getUniqueId();
s_logger.info("Running simulation using script {}, view '{}', market data {}, batch mode {}",
simulationScript, viewDefName, marketDataSpecs, batchMode);
simulation.run(viewDefId, marketDataSpecs, batchMode, listener, viewProcessor);
}
@SuppressWarnings("unchecked")
private <T> T instantiate(String className, Class<T> expectedType) {
Class<?> supplierClass;
try {
supplierClass = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new OpenGammaRuntimeException("Failed to create instance of class " + className, e);
}
if (!expectedType.isAssignableFrom(supplierClass)) {
throw new IllegalArgumentException("Class " + className + " doesn't implement " + expectedType.getName());
}
try {
return (T) supplierClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new OpenGammaRuntimeException("Failed to instantiate " + supplierClass.getName(), e);
}
}
@Override
protected Options createOptions(boolean mandatoryConfigResource) {
Options options = super.createOptions(mandatoryConfigResource);
Option viewDefNameOption = new Option(VIEW_DEF_NAME_OPTION, true, "View definition name");
viewDefNameOption.setRequired(true);
viewDefNameOption.setArgName("viewdefname");
options.addOption(viewDefNameOption);
Option marketDataOption = new Option(MARKET_DATA_OPTION, true, "Market data source names");
marketDataOption.setRequired(true);
marketDataOption.setArgName("marketdata");
options.addOption(marketDataOption);
Option simulationScriptOption = new Option(SIMULATION_SCRIPT_OPTION, true, "Simulation script location");
simulationScriptOption.setRequired(true);
simulationScriptOption.setArgName("simulationscript");
options.addOption(simulationScriptOption);
Option paramScriptOption = new Option(PARAMETER_SCRIPT_OPTION, true, "Simulation parameters script location");
paramScriptOption.setArgName("simulationparameters");
options.addOption(paramScriptOption);
Option batchModeOption = new Option(BATCH_MODE_OPTION, false, "Run in batch mode");
batchModeOption.setArgName("batchmode");
options.addOption(batchModeOption);
Option resultListenerClassOption = new Option(RESULT_LISTENER_CLASS_OPTION, true, "Result listener class " +
"implementing ViewResultListener");
resultListenerClassOption.setArgName("resultlistenerclass");
options.addOption(resultListenerClassOption);
return options;
}
}
/* package */ class MarketDataSpecificationParser {
private static final String LIVE = "live";
private static final String SNAPSHOT = "snapshot";
private static final String FIXED_HISTORICAL = "fixedhistorical";
private static final String LATEST_HISTORICAL = "latesthistorical";
/**
* Parses a string to produce a {@link MarketDataSpecification}. See the output of {@link #getUsageMessage()}
* for examples.
* @param specStr String representation of a {@link MarketDataSpecification}
* @return A {@link MarketDataSpecification} instance built from the string
* @throws IllegalArgumentException If the string can't be parsed
*/
/* package */ static MarketDataSpecification parse(String specStr) {
if (specStr.startsWith(LIVE)) {
return createLiveSpec(removePrefix(specStr, LIVE));
} else {
if (specStr.startsWith(SNAPSHOT)) {
return createSnapshotSpec(removePrefix(specStr, SNAPSHOT));
} else if (specStr.startsWith(FIXED_HISTORICAL)) {
return createFixedHistoricalSpec(removePrefix(specStr, FIXED_HISTORICAL));
} else if (specStr.startsWith(LATEST_HISTORICAL)) {
return createLatestHistoricalSpec(removePrefix(specStr, LATEST_HISTORICAL));
} else {
throw new IllegalArgumentException("Market data must be one of 'live', 'fixedhistorical', 'latesthistorical'" +
" or 'snapshot'");
}
}
}
private static MarketDataSpecification createLatestHistoricalSpec(String specStr) {
if (specStr.isEmpty()) {
return new LatestHistoricalMarketDataSpecification();
}
if (!specStr.startsWith(":")) {
throw new IllegalArgumentException(specStr + " doesn't match 'latesthistorical[:time series rating]'");
}
String timeSeriesRating = specStr.substring(1).trim();
if (timeSeriesRating.isEmpty()) {
throw new IllegalArgumentException(specStr + " doesn't match 'latesthistorical[:time series rating]'");
}
return new LatestHistoricalMarketDataSpecification(timeSeriesRating);
}
private static MarketDataSpecification createFixedHistoricalSpec(String specStr) {
if (!specStr.startsWith(":")) {
throw new IllegalArgumentException(specStr + " doesn't match 'fixedhistorical:timestamp[,time series rating]'");
}
String[] strings = specStr.split(",");
LocalDate date;
try {
date = LocalDate.parse(strings[0].substring(1).trim());
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Unknown date format", e);
}
if (strings.length > 1) {
String timeSeriesRating = strings[1].trim();
if (timeSeriesRating.isEmpty()) {
throw new IllegalArgumentException(specStr + " doesn't match 'fixedhistorical:timestamp[,time series rating]'");
}
return new FixedHistoricalMarketDataSpecification(timeSeriesRating, date);
} else {
return new FixedHistoricalMarketDataSpecification(date);
}
}
// TODO accept 'snapshot name/timestamp'? friendlier but requires looking up data from the server
private static MarketDataSpecification createSnapshotSpec(String specStr) {
if (!specStr.startsWith(":")) {
throw new IllegalArgumentException(specStr + " doesn't match 'snapshot:snapshot ID'");
}
String id = specStr.substring(1).trim();
return new UserMarketDataSpecification(UniqueId.parse(id));
}
private static MarketDataSpecification createLiveSpec(String specStr) {
if (!specStr.startsWith(":")) {
throw new IllegalArgumentException(specStr + " doesn't match 'live:source name'");
}
String sourceName = specStr.substring(1).trim();
if (sourceName.isEmpty()) {
throw new IllegalArgumentException(specStr + " doesn't match 'live:source name'");
}
return new LiveMarketDataSpecification(sourceName);
}
private static String removePrefix(String specStr, String prefix) {
return specStr.substring(prefix.length(), specStr.length());
}
/* package */ static String getUsageMessage() {
return "Examples of valid market data strings:\n" +
"live:Data provider name\n" +
"latesthistorical\n" +
"latesthistorical:Time series rating name\n" +
"fixedhistorical:2011-08-03\n" +
"fixedhistorical:2011-08-03,Time series rating name\n" +
"snapshot:DbSnp~1234";
}
}