/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.integration.tool.portfolio.xml.v1_0.conversion; import java.math.BigDecimal; import java.util.Arrays; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.opengamma.core.security.Security; import com.opengamma.id.ExternalId; import com.opengamma.integration.tool.portfolio.xml.PortfolioPosition; import com.opengamma.integration.tool.portfolio.xml.XmlExternalIdValidator; import com.opengamma.integration.tool.portfolio.xml.v1_0.jaxb.AdditionalCashflow; import com.opengamma.integration.tool.portfolio.xml.v1_0.jaxb.IdWrapper; import com.opengamma.integration.tool.portfolio.xml.v1_0.jaxb.ListedSecurityDefinition; import com.opengamma.integration.tool.portfolio.xml.v1_0.jaxb.MonetaryAmount; import com.opengamma.integration.tool.portfolio.xml.v1_0.jaxb.Portfolio; import com.opengamma.integration.tool.portfolio.xml.v1_0.jaxb.Position; import com.opengamma.integration.tool.portfolio.xml.v1_0.jaxb.Trade; import com.opengamma.master.position.ManageablePosition; import com.opengamma.master.position.ManageableTrade; import com.opengamma.master.security.ManageableSecurity; /** * Converts a portfolio from the JAXB extracted objects to the standard * system objects. */ public class PortfolioConverter { private static final Logger s_logger = LoggerFactory.getLogger(PortfolioConverter.class); /** * The portfolio to be converted. */ private final Portfolio _portfolio; private final XmlExternalIdValidator _xmlExternalIdValidator; public PortfolioConverter(Portfolio portfolio, XmlExternalIdValidator xmlExternalIdValidator) { _portfolio = portfolio; _xmlExternalIdValidator = xmlExternalIdValidator; } /** * Get the set of manageable positions for this portfolio. Note that this may add in positions * which were not in the original xml file e.g. where a set of trades were specified but no * positions, each trade will be added to a new position. * * @return the positions, not null */ public Iterable<PortfolioPosition> getPositions() { s_logger.info("Starting to process root portfolio: {}", _portfolio.getName()); return processPortfolio(_portfolio, true, null); } /** * Produce the collection of positions held in a portfolio. A portfolio may consist of any combination of: * <ul> * <li>recursively nested portfolios</li> * <li>positions (which may or may not contain trades)</li> * <li>trades</li> * </ul> * and this method will produce a List of PortfolioPosition objects by examining the above elements. * If portfolios are nested, this method will be called recursively noting the portfolio path as the * portfolio hieracrhy is decended. Note that this may add in positions which were not in the original * xml file e.g. where a set of trades were specified but no * positions, each trade will be added to a new position. * * @param portfolio the portfolio to be examined * @param isRoot indicates if the portfolio has no parent i.e. a root node * @param parentPath the path to the portfolio (an array of the names of the portfolio's ancestors). * @return the positions held by the portfolio */ private List<PortfolioPosition> processPortfolio(Portfolio portfolio, boolean isRoot, String[] parentPath) { s_logger.info("Processing portfolio: {}", portfolio.getName()); List<PortfolioPosition> managedPositions = Lists.newArrayList(); // This is needed as we don't want the name of the root portfolio to appear in the path. So for // a root portfolio we want an empty path, for a child of the root we want just the portfolio name etc. String[] portfolioPath = isRoot ? new String[0] : extendPath(parentPath, portfolio.getName()); for (Portfolio nestedPortfolio : nullCheckIterable(portfolio.getPortfolios())) { managedPositions.addAll(processPortfolio(nestedPortfolio, false, portfolioPath)); } for (Position position : nullCheckIterable(portfolio.getPositions())) { IdWrapper positionExternalId = position.getExternalSystemId(); String positionId = positionExternalId != null ? positionExternalId.toExternalId().toString() : "AUTO-CREATED"; s_logger.debug("Extracting position: [{}]", positionId); List<Trade> trades = position.getTrades(); BigDecimal tradeTotalQuantity = BigDecimal.ZERO; // If we have a security defined on the position then we need to // check it matches the one from the trades (if there was one) ManageableSecurity[] positionSecurity = extractSecurityFromPosition(position.getListedSecurityDefinition()); for (Trade trade : nullCheckIterable(trades)) { s_logger.debug("Extracting trade: {} for position {}", trade.getExternalSystemId().getExternalId(), positionId); ManageableSecurity[] tradeSecurity = extractSecurityFromTrade(trade, trades.size()); if (positionSecurity == null) { positionSecurity = tradeSecurity; } else if (!Arrays.equals(positionSecurity, tradeSecurity)) { throw new PortfolioParsingException("Security must be the same for all trades grouped into a position - " + "position has security: [" + positionSecurity[0] + "] but found trade with: [" + tradeSecurity[0] + "]"); } tradeTotalQuantity = tradeTotalQuantity.add(trade.getQuantity()); } if (positionSecurity != null) { managedPositions.add(createPortfolioPosition(position, positionSecurity, portfolioPath, tradeTotalQuantity)); } else { throw new PortfolioParsingException("No security specified on either trades or position"); } } // These trades have not been supplied under positions, but directly in a portfolio for (Trade trade : nullCheckIterable(portfolio.getTrades())) { // TODO we probably want logic to allow for the aggregation of trades into positions but for now we'll create a position per trade ManageableSecurity[] security = extractSecurityFromTrade(trade, 1); managedPositions.add(createPortfolioPosition(trade, security, portfolioPath)); } return managedPositions; } /** * Create a new array which is a copy of the path array with * the name parameter appended at the end. * * @param path the path array to be extended * @param name the element to append to the array * @return the extended array */ private String[] extendPath(String[] path, String name) { int oldLength = path.length; String[] extended = Arrays.copyOf(path, oldLength + 1); extended[oldLength] = name; return extended; } private ManageableSecurity[] extractSecurityFromPosition(ListedSecurityDefinition listedSecurityDefinition) { if (listedSecurityDefinition != null) { s_logger.debug("Extracting securities for position"); return listedSecurityDefinition.getSecurityExtractor().extract(); } else { return null; } } private ManageableSecurity[] extractSecurityFromTrade(Trade trade, int tradesSize) { if (tradesSize > 1 && !trade.canBePositionAggregated()) { throw new PortfolioParsingException("Trade type [" + trade.getClass() + "] cannot be aggregated into positions"); } s_logger.debug("Extracting securities for trade: [{}]", trade.getExternalSystemId().toExternalId()); TradeSecurityExtractor<?> extractor = trade.getSecurityExtractor(); return extractor.extractSecurities(); } private <T> Iterable<T> nullCheckIterable(Iterable<T> iterable) { return iterable == null ? ImmutableList.<T>of() : iterable; } private PortfolioPosition createPortfolioPosition(Position position, ManageableSecurity[] security, String[] parentPath, BigDecimal tradeQuantity) { return new PortfolioPosition(convertPosition(position, security[0], tradeQuantity), security, parentPath); } private PortfolioPosition createPortfolioPosition(Trade trade, ManageableSecurity[] security, String[] parentPath) { return new PortfolioPosition(convertTradeToPosition(trade, security[0]), security, parentPath); } private ManageablePosition convertTradeToPosition(Trade trade, ManageableSecurity security) { ManageablePosition manageablePosition = new ManageablePosition(trade.getQuantity(), security.getExternalIdBundle()); manageablePosition.addTrade(convertTrade(trade, security)); return manageablePosition; } private ManageablePosition convertPosition(Position position, Security security, BigDecimal tradeQuantity) { // If the position is supplying a quantity, then we should use that // rather than the total quantity obtained from the trades BigDecimal positionQuantity = position.getQuantity(); ManageablePosition manageablePosition = new ManageablePosition( positionQuantity != null ? positionQuantity : tradeQuantity, security.getExternalIdBundle()); IdWrapper externalSystemId = position.getExternalSystemId(); if (externalSystemId != null) { manageablePosition.setProviderId(externalSystemId.toExternalId()); } List<Trade> trades = position.getTrades(); for (Trade trade : nullCheckIterable(trades)) { manageablePosition.addTrade(convertTrade(trade, security)); } manageablePosition.setAttributes(position.getAdditionalAttributes()); return manageablePosition; } private ManageableTrade convertTrade(Trade trade, Security security) { ManageableTrade manageableTrade = new ManageableTrade(trade.getQuantity(), security.getExternalIdBundle(), trade.getTradeDate(), null, trade.getCounterparty().toExternalId()); ExternalId externalId = trade.getExternalSystemId().toExternalId(); _xmlExternalIdValidator.validateExternalId(externalId, trade.getId()); manageableTrade.setProviderId(externalId); for (AdditionalCashflow cashflow : nullCheckIterable(trade.getAdditionalCashflows())) { if (cashflow.getCashflowType() == AdditionalCashflow.CashflowType.PREMIUM) { MonetaryAmount monetaryAmount = cashflow.getMonetaryAmount(); manageableTrade.setPremium(monetaryAmount.getAmount().doubleValue()); manageableTrade.setPremiumCurrency(monetaryAmount.getCurrency()); manageableTrade.setPremiumDate(cashflow.getCashflowDate()); } } manageableTrade.setAttributes(trade.getAdditionalAttributes()); return manageableTrade; } }