/**
* 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.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.threeten.bp.Instant;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Table;
import com.opengamma.core.position.PortfolioNode;
import com.opengamma.core.position.Position;
import com.opengamma.core.position.impl.SimplePosition;
import com.opengamma.core.position.impl.SimpleTrade;
import com.opengamma.core.security.Security;
import com.opengamma.engine.value.ComputedValueResult;
import com.opengamma.id.UniqueIdentifiable;
import com.opengamma.master.portfolio.ManageablePortfolioNode;
import com.opengamma.master.position.ManageablePosition;
import com.opengamma.master.position.ManageableTrade;
/**
* Writes the results of running scenarios to a tab delimited text file.
* There are two possible formats:
* <ul>
* <li>Short format. All values calculated for a position / scenario are in the same row.</li>
* <li>Long format. There is a separate row for every calculated value. i.e. If there are n values calculated
* in the view there will be n rows per position / scenario. This is intended to be easy to use when creating
* pivot tables and using filtering in Excel.</li>
* </ul>
* The long format is used by default.
*/
public class ScenarioResultsWriter {
// TODO refactor to give access to List<List<String>> of the results
// TODO make this configurable? use a CSV writing library?
/* package */ static final String DELIMITER = "\t";
private ScenarioResultsWriter() {
}
/**
* Writes a set of scenario results in long tab delimited format.
* There is a separate row for every calculated value. i.e. If there are n values calculated in the view there
* will be n rows per position / scenario. This is intended to be easy to use when creating pivot tables and
* using filtering in Excel.
* @param allScenarioResults The results
* @param fileName The file to write to
* @throws IOException If the results can't be written
*/
public static void writeLongFormat(List<ScenarioResultModel> allScenarioResults, String fileName) throws IOException {
try (Writer writer = new BufferedWriter(new FileWriter(fileName))) {
writeLongFormat(allScenarioResults, writer);
}
}
/**
* Writes a set of scenario results in long tab delimited format.
* There is a separate row for every calculated value. i.e. If there are n values calculated in the view there
* will be n rows per position / scenario. This is intended to be easy to use when creating pivot tables and
* using filtering in Excel.
* @param allScenarioResults The results
* @param appendable For writing the results
* @throws IOException If the results can't be written
*/
public static void writeLongFormat(List<ScenarioResultModel> allScenarioResults, Appendable appendable) throws IOException {
if (allScenarioResults.isEmpty()) {
appendable.append("NO RESULTS");
return;
}
ScenarioResultModel firstResults = allScenarioResults.get(0);
ImmutableList.Builder<List<String>> rows = ImmutableList.builder();
// write the header ----------------------------------------
// this assumes all scenarios have the same number of parameters which should always be true
ImmutableList.Builder<String> headerRow = ImmutableList.builder();
headerRow.addAll(metadataHeader(firstResults.getParameters().size()));
// there is one column of calculated data for all results and another column to say what the results are
headerRow.add("ResultName").add("ResultValue");
rows.add(headerRow.build());
// write the row results ----------------------------------------
for (ScenarioResultModel scenarioResults : allScenarioResults) {
SimpleResultModel simpleResults = scenarioResults.getResults();
Table<Integer, Integer, Object> resultsTable = simpleResults.getResults();
for (Map.Entry<Integer, Map<Integer, Object>> entry : resultsTable.rowMap().entrySet()) {
int rowIndex = entry.getKey();
Map<Integer, Object> rowValues = entry.getValue();
Iterator<String> colItr = simpleResults.getColumnNames().iterator();
// loop over every column in the results and add a row
for (Object value : rowValues.values()) {
UniqueIdentifiable target = simpleResults.getTargets().get(rowIndex);
// we're only interested in positions. nodes contain aggregate values and the whole point of this report
// is to allow arbitrary reaggregation. so any aggregate values would have to be filtered out to avoid
// confusing the results. and trade amounts are included in positions anyway
if (target instanceof Position || target instanceof ManageablePosition) {
ImmutableList.Builder<String> row = ImmutableList.builder();
row.addAll(metadataColumns(scenarioResults, target));
row.add(colItr.next()).add(getStringValue((ComputedValueResult) value));
rows.add(row.build());
}
}
}
}
// TODO use a CSV writer?
for (List<String> row : rows.build()) {
appendable.append(StringUtils.join(row, DELIMITER)).append("\n");
}
}
/**
* Writes a set of scenario results in short tab delimited format.
* All values calculated for a position / scenario are in the same row. There is a column for each result in
* the view.
* @param allScenarioResults The results
* @param fileName The file to write to
* @throws IOException If the file can't be written
*/
public static void writeShortFormat(List<ScenarioResultModel> allScenarioResults, String fileName) throws IOException {
try (Writer writer = new BufferedWriter(new FileWriter(fileName))) {
writeShortFormat(allScenarioResults, writer);
}
}
/**
* Writes a set of scenario results in short tab delimited format.
* All values calculated for a position / scenario are in the same row. There is a column for each result in
* the view.
* @param allScenarioResults The results
* @param appendable For writing the results
* @throws IOException If the results can't be written
*/
public static void writeShortFormat(List<ScenarioResultModel> allScenarioResults, Appendable appendable) throws IOException {
if (allScenarioResults.isEmpty()) {
appendable.append("NO RESULTS");
return;
}
ScenarioResultModel firstResults = allScenarioResults.get(0);
ImmutableList.Builder<List<String>> rows = ImmutableList.builder();
// write the header ----------------------------------------
// this assumes all scenarios have the same number of parameters which should always be true
ImmutableList.Builder<String> headerRow = ImmutableList.builder();
headerRow.addAll(metadataHeader(firstResults.getParameters().size()));
List<String> columnNames = firstResults.getResults().getColumnNames();
// there is one column of calculated data for each column in the results table
for (String columnName : columnNames) {
headerRow.add(columnName);
}
rows.add(headerRow.build());
// write the row results ----------------------------------------
for (ScenarioResultModel scenarioResults : allScenarioResults) {
SimpleResultModel simpleResults = scenarioResults.getResults();
Table<Integer, Integer, Object> resultsTable = simpleResults.getResults();
for (Map.Entry<Integer, Map<Integer, Object>> entry : resultsTable.rowMap().entrySet()) {
int rowIndex = entry.getKey();
UniqueIdentifiable target = simpleResults.getTargets().get(rowIndex);
// we're only interested in positions. nodes contain aggregate values and the whole point of this report
// is to allow arbitrary reaggregation. so any aggregate values would have to be filtered out to avoid
// confusing the results. and trade amounts are included in positions anyway
if (target instanceof Position || target instanceof ManageablePosition) {
ImmutableList.Builder<String> row = ImmutableList.builder();
Map<Integer, Object> rowValues = entry.getValue();
row.addAll(metadataColumns(scenarioResults, target));
for (Object value : rowValues.values()) {
row.add(getStringValue((ComputedValueResult) value));
}
rows.add(row.build());
}
}
}
// TODO use a CSV writer?
for (List<String> row : rows.build()) {
appendable.append(StringUtils.join(row, DELIMITER)).append("\n");
}
}
/**
* Returns {@link ComputedValueResult#getValue() computedValueResult.getValue()} as a string or the empty string
* if {@code computedValueResult} or its value are null
* @param computedValueResult A computed value
* @return {@code computedValueResult.getValue().toString()} or the empty string if {@code computedValueResult}
* or its value are null
*/
private static String getStringValue(ComputedValueResult computedValueResult) {
if (computedValueResult == null) {
return "";
}
Object value = computedValueResult.getValue();
if (value == null) {
return "";
}
return value.toString();
}
private static List<String> metadataHeader(int paramCount) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
builder.add("ScenarioName").add("ValuationTime").add("Type").add("Description").add("PositionId");
for (int i = 1; i <= paramCount; i++) {
builder.add("ParamName" + i).add("ParamValue" + i);
}
return builder.build();
}
/**
* @return Formats the data in the specified row up to but not including the calculated results
*/
private static List<String> metadataColumns(ScenarioResultModel scenarioResults, UniqueIdentifiable target) {
String scenarioName = scenarioResults.getScenarioName();
SimpleResultModel simpleResults = scenarioResults.getResults();
Instant valuationTime = simpleResults.getValuationTime();
ImmutableList.Builder<String> builder = ImmutableList.builder();
builder
.add(scenarioName)
.add(valuationTime.toString())
.add(getType(target))
.add(getDescription(target))
.add(target.getUniqueId().toString());
for (Map.Entry<String, Object> entry : scenarioResults.getParameters().entrySet()) {
String paramName = entry.getKey();
Object paramValue = entry.getValue();
builder.add(paramName).add(paramValue.toString());
}
return builder.build();
}
private static String getType(UniqueIdentifiable target) {
if (target instanceof PortfolioNode || target instanceof ManageablePortfolioNode) {
return "PortfolioNode";
}
Security security = getSecurity(target);
if (security == null) {
return target.getClass().getSimpleName();
}
String simpleName = security.getClass().getSimpleName();
if (simpleName.endsWith("Security")) {
return simpleName.substring(0, simpleName.length() - 8);
}
return simpleName;
}
private static String getDescription(UniqueIdentifiable target) {
if (target instanceof PortfolioNode) {
return ((PortfolioNode) target).getName();
}
if (target instanceof ManageablePortfolioNode) {
return ((ManageablePortfolioNode) target).getName();
}
Security security = getSecurity(target);
if (security != null && !StringUtils.isEmpty(security.getName())) {
return security.getName();
}
if (target instanceof ManageablePosition) {
return ((ManageablePosition) target).getName();
}
return "";
}
private static Security getSecurity(Object positionOrTrade) {
if (positionOrTrade instanceof ManageablePosition) {
return ((ManageablePosition) positionOrTrade).getSecurity();
} else if (positionOrTrade instanceof SimplePosition) {
return ((SimplePosition) positionOrTrade).getSecurity();
} else if (positionOrTrade instanceof ManageableTrade) {
return ((ManageableTrade) positionOrTrade).getSecurity();
} else if (positionOrTrade instanceof SimpleTrade) {
return ((SimpleTrade) positionOrTrade).getSecurity();
} else {
return null;
}
}
}