com.opengamma.web.analytics.blotter.OtcTradeBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.web.analytics.blotter.OtcTradeBuilder.java

Source

/**
 * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.web.analytics.blotter;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.joda.beans.BeanBuilder;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.beans.Property;
import org.joda.convert.StringConvert;
import org.threeten.bp.ZonedDateTime;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.opengamma.core.security.Security;
import com.opengamma.core.security.SecurityLink;
import com.opengamma.financial.security.FinancialSecurity;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.master.portfolio.ManageablePortfolio;
import com.opengamma.master.portfolio.ManageablePortfolioNode;
import com.opengamma.master.portfolio.PortfolioDocument;
import com.opengamma.master.portfolio.PortfolioMaster;
import com.opengamma.master.portfolio.PortfolioSearchRequest;
import com.opengamma.master.portfolio.PortfolioSearchResult;
import com.opengamma.master.position.ManageablePosition;
import com.opengamma.master.position.ManageableTrade;
import com.opengamma.master.position.PositionDocument;
import com.opengamma.master.position.PositionMaster;
import com.opengamma.master.security.ManageableSecurity;
import com.opengamma.master.security.ManageableSecurityLink;
import com.opengamma.master.security.SecurityDocument;
import com.opengamma.master.security.SecurityMaster;
import com.opengamma.master.security.SecuritySearchRequest;
import com.opengamma.master.security.SecuritySearchResult;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.OpenGammaClock;

/**
 * Builds and saves trades, securities and underlying securities for OTC securities.
 * TODO the use of VersionCorrection.LATEST in this class is incorrect
 * the weak link between trades, positions and securities is a problem for OTCs because in reality they're a single
 * atomic object. the problem at the moment is there's no way to know which version of the security is being modified
 * given the unique ID of the trade. therefore it's not possible to detect any concurrent modification of securities,
 * the last update wins. there are various potential fixes but it might not be worth doing before the imminent refactor
 * of trades, positions and securities.
 */
/* package */ class OtcTradeBuilder extends AbstractTradeBuilder {

    /** Type name for OTC trades used in the data sent to the client. */
    /* package */ static final String TRADE_TYPE_NAME = "OtcTrade";

    /* package */ OtcTradeBuilder(PositionMaster positionMaster, PortfolioMaster portfoioMaster,
            SecurityMaster securityMaster, Set<MetaBean> metaBeans, StringConvert stringConvert) {
        super(positionMaster, portfoioMaster, securityMaster, metaBeans, stringConvert);
    }

    /* package */ UniqueId addTrade(BeanDataSource tradeData, BeanDataSource securityData,
            BeanDataSource underlyingData, UniqueId nodeId) {
        /*
        validate:
          underlying is present
          underlying type is correct
        */
        ManageableSecurity underlying = buildUnderlying(underlyingData);
        ManageableSecurity security;
        if (underlying == null) {
            security = BlotterUtils.buildSecurity(securityData);
        } else {
            ManageableSecurity savedUnderlying = getSecurityMaster().add(new SecurityDocument(underlying))
                    .getSecurity();
            security = buildSecurity(securityData, savedUnderlying);
        }
        ManageableSecurity savedSecurity = getSecurityMaster().add(new SecurityDocument(security)).getSecurity();
        ManageableTrade trade = buildTrade(tradeData);
        trade.setSecurityLink(new ManageableSecurityLink(savedSecurity.getUniqueId()));
        ManageablePosition position = new ManageablePosition();
        position.setQuantity(BigDecimal.ONE);
        position.setSecurityLink(new ManageableSecurityLink(trade.getSecurityLink()));
        position.setTrades(Lists.newArrayList(trade));
        ManageablePosition savedPosition = getPositionMaster().add(new PositionDocument(position)).getPosition();
        ManageableTrade savedTrade = savedPosition.getTrades().get(0);

        PortfolioSearchRequest searchRequest = new PortfolioSearchRequest();
        searchRequest.addNodeObjectId(nodeId.getObjectId());
        PortfolioSearchResult searchResult = getPortfolioMaster().search(searchRequest);
        ManageablePortfolio portfolio = searchResult.getSinglePortfolio();
        ManageablePortfolioNode node = findNode(portfolio, nodeId);
        node.addPosition(savedPosition.getUniqueId());
        getPortfolioMaster().update(new PortfolioDocument(portfolio));
        return savedTrade.getUniqueId();
    }

    /* package */ UniqueId updatePosition(UniqueId positionId, BeanDataSource tradeData,
            BeanDataSource securityData, BeanDataSource underlyingData) {
        ManageableTrade trade = buildTrade(tradeData);
        ManageablePosition position = getPositionMaster().get(positionId).getPosition();
        ManageableSecurity previousSecurity = loadSecurity(position.getSecurityLink());
        return updateSecuritiesAndPosition(securityData, underlyingData, trade, previousSecurity, positionId);
    }

    /* package */ UniqueId updateTrade(BeanDataSource tradeData, BeanDataSource securityData,
            BeanDataSource underlyingData) {
        if (!TRADE_TYPE_NAME.equals(tradeData.getBeanTypeName())) {
            throw new IllegalArgumentException("Can only build trades of type " + TRADE_TYPE_NAME + ", type name = "
                    + tradeData.getBeanTypeName());
        }
        /*
        validate:
          underlying is present
          underlying type is correct
          security type hasn't changed
          trade ID is versioned
        */
        ManageableTrade trade = buildTrade(tradeData);
        ManageableTrade previousTrade = getPositionMaster().getTrade(trade.getUniqueId());
        ManageableSecurity previousSecurity = loadSecurity(previousTrade.getSecurityLink());
        UniqueId previousPositionId = previousTrade.getParentPositionId();
        return updateSecuritiesAndPosition(securityData, underlyingData, trade, previousSecurity,
                previousPositionId);
    }

    private ManageableSecurity loadSecurity(SecurityLink securityLink) {
        if (securityLink.getObjectId() != null) {
            return getSecurityMaster().get(securityLink.getObjectId(), VersionCorrection.LATEST).getSecurity();
        } else if (securityLink.getExternalId() != null) {
            ExternalIdBundle idBundle = securityLink.getExternalId();
            SecuritySearchResult searchResult = getSecurityMaster().search(new SecuritySearchRequest(idBundle));
            if (searchResult.getSecurities().isEmpty()) {
                throw new IllegalArgumentException("No security found for ID bundle " + idBundle);
            }
            return searchResult.getFirstSecurity();
        } else {
            throw new IllegalArgumentException("No IDs in security link " + securityLink);
        }
    }

    private UniqueId updateSecuritiesAndPosition(BeanDataSource securityData, BeanDataSource underlyingData,
            ManageableTrade trade, ManageableSecurity previousSecurity, UniqueId positionId) {
        // need the previous underlying so we don't lose the ID bundle, the data doesn't contain it
        ExternalIdBundle previousUnderlyingIdBundle;
        if (previousSecurity instanceof FinancialSecurity) {
            UnderlyingSecurityVisitor visitor = new UnderlyingSecurityVisitor(VersionCorrection.LATEST,
                    getSecurityMaster());
            ManageableSecurity previousUnderlying = ((FinancialSecurity) previousSecurity).accept(visitor);
            if (previousUnderlying != null) {
                previousUnderlyingIdBundle = previousUnderlying.getExternalIdBundle();
            } else {
                previousUnderlyingIdBundle = ExternalIdBundle.EMPTY;
            }
        } else {
            previousUnderlyingIdBundle = ExternalIdBundle.EMPTY;
        }
        ManageableSecurity underlying = buildUnderlying(underlyingData, previousUnderlyingIdBundle);
        ManageableSecurity security;
        if (underlying == null) {
            security = BlotterUtils.buildSecurity(securityData, previousSecurity.getExternalIdBundle());
        } else {
            // need to set the unique ID to the ID from the previous version, securities aren't allowed to change
            // any changes in the security data are interpreted as edits to the security
            ManageableSecurity previousUnderlying = getUnderlyingSecurity(previousSecurity,
                    VersionCorrection.LATEST);
            validateSecurity(underlying, previousUnderlying);
            underlying.setUniqueId(previousUnderlying.getUniqueId());
            ManageableSecurity savedUnderlying = getSecurityMaster().update(new SecurityDocument(underlying))
                    .getSecurity();
            security = buildSecurity(securityData, savedUnderlying, previousSecurity.getExternalIdBundle());
        }
        // need to set the unique ID to the ID from the previous version, securities aren't allowed to change
        // any changes in the security data are interpreted as edits to the security
        validateSecurity(security, previousSecurity);
        security.setUniqueId(previousSecurity.getUniqueId());
        ManageableSecurity savedSecurity = getSecurityMaster().update(new SecurityDocument(security)).getSecurity();
        trade.setSecurityLink(new ManageableSecurityLink(savedSecurity.getUniqueId()));
        ManageablePosition position = getPositionMaster().get(positionId).getPosition();
        position.setTrades(Lists.newArrayList(trade));
        ManageablePosition savedPosition = getPositionMaster().update(new PositionDocument(position)).getPosition();
        ManageableTrade savedTrade = savedPosition.getTrades().get(0);
        return savedTrade.getUniqueId();
    }

    private ManageableSecurity getUnderlyingSecurity(ManageableSecurity security,
            VersionCorrection versionCorrection) {
        if (security instanceof FinancialSecurity) {
            UnderlyingSecurityVisitor visitor = new UnderlyingSecurityVisitor(versionCorrection,
                    getSecurityMaster());
            return ((FinancialSecurity) security).accept(visitor);
        } else {
            return null;
        }
    }

    /**
     * Checks that the new and old versions of a security have the same type and if the new version specifies an ID
     * it is the same as the old ID.
     * @param newVersion The new version of the security
     * @param previousVersion The previous version of the security
     */
    private static void validateSecurity(ManageableSecurity newVersion, ManageableSecurity previousVersion) {
        if (!newVersion.getClass().equals(previousVersion.getClass())) {
            throw new IllegalArgumentException("Security type cannot change, new version " + newVersion + ", "
                    + "previousVersion: " + previousVersion);
        }
        // TODO this should check for equality between the IDs but that's not working ATM
        // needs to be part of the bigger fix for the problem caused by the weak links between the different parts
        // of OTC trades
        if (newVersion.getUniqueId() != null
                && !newVersion.getUniqueId().equalObjectId(previousVersion.getUniqueId())) {
            throw new IllegalArgumentException("Cannot update a security with a different ID, " + "new ID: "
                    + newVersion.getUniqueId() + ", " + "previous ID: " + previousVersion.getUniqueId());
        }
    }

    private ManageableTrade buildTrade(BeanDataSource tradeData) {
        ManageableTrade.Meta meta = ManageableTrade.meta();
        BeanBuilder<? extends ManageableTrade> tradeBuilder = tradeBuilder(tradeData, meta.uniqueId(),
                meta.tradeDate(), meta.tradeTime(), meta.premium(), meta.premiumCurrency(), meta.premiumDate(),
                meta.premiumTime());
        tradeBuilder.set(meta.attributes(), tradeData.getMapValues(meta.attributes().name()));
        tradeBuilder.set(meta.quantity(), BigDecimal.ONE);
        // the link needs to be non-null but the real ID can't be set until the security has been created later
        tradeBuilder.set(meta.securityLink(), new ManageableSecurityLink());
        String counterparty = (String) tradeData.getValue(COUNTERPARTY);
        if (StringUtils.isEmpty(counterparty)) {
            counterparty = DEFAULT_COUNTERPARTY;
        }
        tradeBuilder.set(meta.counterpartyExternalId(), ExternalId.of(CPTY_SCHEME, counterparty));
        return tradeBuilder.build();
    }

    // TODO move these to a separate class that only extracts data, also handles securities and underlyings
    /**
     * Extracts trade data and populates a data sink.
     * @param trade The trade
     * @param sink The sink that should be populated with the trade data
     */
    /* package */ void extractTradeData(ManageableTrade trade, BeanDataSink<?> sink) {
        sink.setValue("type", TRADE_TYPE_NAME);
        extractPropertyData(trade.uniqueId(), sink);
        extractPropertyData(trade.tradeDate(), sink);
        extractPropertyData(trade.tradeTime(), sink);
        extractPropertyData(trade.premium(), sink);
        extractPropertyData(trade.premiumCurrency(), sink);
        extractPropertyData(trade.premiumDate(), sink);
        extractPropertyData(trade.premiumTime(), sink);
        sink.setMap(trade.attributes().name(), trade.getAttributes());
        // this shouldn't be necessary as counterparty ID isn't nullable but there's a bug in the implementation of
        // ManageableTrade which allows null values
        ExternalId counterpartyId = trade.getCounterpartyExternalId();
        String counterpartyValue;
        if (counterpartyId != null) {
            counterpartyValue = counterpartyId.getValue();
        } else {
            counterpartyValue = null;
        }
        sink.setValue(COUNTERPARTY, counterpartyValue);
    }

    private void extractPropertyData(Property<?> property, BeanDataSink<?> sink) {
        sink.setValue(property.name(),
                getStringConvert().convertToString(property.metaProperty().get(property.bean())));
    }

    /**
     * Creates a builder for a {@link ManageableTrade} and sets the simple properties from the data source.
     * @param tradeData The trade data
     * @param properties The trade properties to set
     * @return A builder with property values set from the trade data
     */
    private BeanBuilder<? extends ManageableTrade> tradeBuilder(BeanDataSource tradeData,
            MetaProperty<?>... properties) {
        BeanBuilder<? extends ManageableTrade> builder = ManageableTrade.meta().builder();
        for (MetaProperty<?> property : properties) {
            builder.set(property, getStringConvert().convertFromString(property.propertyType(),
                    (String) tradeData.getValue(property.name())));
        }
        return builder;
    }

    private FinancialSecurity buildSecurity(BeanDataSource securityData, Security underlying) {
        return buildSecurity(securityData, underlying, ExternalIdBundle.EMPTY);
    }

    private FinancialSecurity buildSecurity(BeanDataSource securityData, Security underlying,
            ExternalIdBundle idBundle) {
        ArgumentChecker.notNull(underlying, "underlying");
        BeanDataSource dataSource;
        ExternalId underlyingId = getUnderlyingId(underlying);
        // TODO would it be better to just return the bean builder from the visitor and handle this property manually?
        // TODO would have to use a different property for every security with underlyingId, there's no common supertype with it
        if (underlyingId == null) {
            throw new IllegalArgumentException("Unable to get underlying ID for security " + underlying);
        }
        // TODO I'm not keen on this, it doesn't smell great
        dataSource = new PropertyReplacingDataSource(securityData, "underlyingId", underlyingId.toString());
        return BlotterUtils.buildSecurity(dataSource, idBundle);
    }

    private FinancialSecurity buildUnderlying(BeanDataSource underlyingData) {
        return buildUnderlying(underlyingData, ExternalIdBundle.EMPTY);
    }

    private FinancialSecurity buildUnderlying(BeanDataSource underlyingData, ExternalIdBundle idBundle) {
        if (underlyingData == null) {
            return null;
        }
        return BlotterUtils.buildSecurity(underlyingData, idBundle);
    }

    private ExternalId getUnderlyingId(Security underlying) {
        ExternalId underlyingId;
        if (underlying instanceof FinancialSecurity) {
            underlyingId = ((FinancialSecurity) underlying).accept(new ExternalIdVisitor(getSecurityMaster()));
        } else {
            underlyingId = null;
        }
        return underlyingId;
    }

    // TODO different versions for OTC / non OTC
    // the horror... make this go away TODO move to the TradeBuilers? they create the trades
    /* package */ static Map<String, Object> tradeStructure() {
        Map<String, Object> structure = Maps.newHashMap();
        List<Map<String, Object>> properties = Lists.newArrayList();
        properties.add(property("uniqueId", true, true, typeInfo("string", "UniqueId")));
        properties.add(property("counterparty", false, false, typeInfo("string", "")));
        properties.add(property("tradeDate", true, false, typeInfo("string", "LocalDate")));
        properties.add(property("tradeTime", true, false, typeInfo("string", "OffsetTime")));
        properties.add(property("premium", true, false, typeInfo("number", "")));
        properties.add(property("premiumCurrency", true, false, typeInfo("string", "Currency")));
        properties.add(property("premiumDate", true, false, typeInfo("string", "LocalDate")));
        properties.add(property("premiumTime", true, false, typeInfo("string", "OffsetTime")));
        properties.add(attributesProperty());
        structure.put("type", TRADE_TYPE_NAME);
        structure.put("properties", properties);
        structure.put("now", ZonedDateTime.now(OpenGammaClock.getInstance()));
        return structure;
    }
}