package edu.brown.cs.student.term.parsing; import edu.brown.cs.student.term.hub.Holder; import edu.brown.cs.student.term.trade.Trade; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.ArrayList; import java.util.List; /** * This class represents the transaction data from the xml file. */ public class Transaction { private Document doc; private final List trades; private final String personName; private final int id; private final String ticker; /** * Constructor that represents the transaction from the document. * @param document The document parsed from the xml file. */ public Transaction(Document document) { // TODO: add log comments System.err.println("LOG: Entered constructor for: " + getClass()); doc = document; trades = new ArrayList<>(); personName = personName(); id = id(); ticker = ticker(); // There are two types of transactions within the xml - derivative and non derivative. // We only look at non-derivative NodeList nonDerivative = document.getElementsByTagName("nonDerivativeTransaction"); System.err.println("LOG: Called processTransaction in constructor for: " + getClass()); processTransactions(nonDerivative); } /** * Takes a transaction as a node list, then processes and stores them into trades. * @param tradesAsNodes The trades within the transaction as a nodelist. */ private void processTransactions(NodeList tradesAsNodes) { int numTrades = tradesAsNodes.getLength(); for(int i = 0; i < numTrades; i++) { // Convert to an element to extract specific info. // I know there is a cast, but this how it is done and is checked with the assertion. Node trade = tradesAsNodes.item(i); assert trade.getNodeType() == Node.ELEMENT_NODE; Element tradeValues = (Element) trade; // This stages the params for the trade. // TODO: update with real timestamp double ts = getDate(tradeValues).hashCode(); int isBuy = getIsBuy(tradeValues); int numShares = getNumShares(tradeValues); // TODO: update id? unique for each holder? Holder holder = new Holder(id, personName); double pricePerShare = getPriceShares(tradeValues); if (pricePerShare != 0 && numShares != -1 && pricePerShare != -1 && isBuy != -1) { trades.add(new Trade(id, ticker, ts, isBuy, numShares, holder, pricePerShare)); } } doc = null; } /** * Extracts the id of the transaction from the document. * @return The id. */ private int id() { // Id of transaction NodeList idNode = doc.getElementsByTagName("rptOwnerCik"); // TODO: add error parsing return Integer.parseInt(idNode.item(0).getTextContent()); } /** * Extracts the person's name of the transaction from the document. * @return The person's name. */ private String personName() { // Name of person NodeList nameNode = doc.getElementsByTagName("rptOwnerName"); return nameNode.item(0).getTextContent(); } /** * Extracts the security ticker of the transaction from the document * @return The ticker. */ private String ticker() { // Ticker of security NodeList idNode = doc.getElementsByTagName("issuerTradingSymbol"); return idNode.item(0).getTextContent(); } public String getValueFromNode(NodeList dataHolder) { // Convert to an element to extract the value form. // I know there is a cast, but this how it is done and is checked with the assertion. Node dataNode = dataHolder.item(0); assert dataNode.getNodeType() == Node.ELEMENT_NODE; Element data = (Element) dataNode; // Only one field within the value holding the value... NodeList value = data.getElementsByTagName("value"); if (value.getLength() == 0) { // If market trade, some values don't have fields... return ""; } return value.item(0).getTextContent(); } /** * Extracts the trade date from a trade node. * @param values The value array representing the trade. * @return The date in mm-dd-yyyy format as a string. */ public String getDate(Element values) { NodeList date = values.getElementsByTagName("transactionDate"); return getValueFromNode(date); } /** * Extracts the number of shares moved from the trade. * @param values The value array representing the trade. * @return The number of shares. */ public int getNumShares(Element values) { NodeList numShares = values.getElementsByTagName("transactionShares"); String num = getValueFromNode(numShares); num = num.equals("") ? "" : num; try { // Some ints have training zeros, so need to parse Double then truncate. return (int) Double.parseDouble(num); } catch (NumberFormatException e) { System.err.println("WARNING: Num shares in XML bad format: " + num); return -1; } } /** * Extracts the price each share cost from the trade. * @param values The value array representing the trade. * @return The price of each share. */ public double getPriceShares(Element values) { NodeList priceShares = values.getElementsByTagName("transactionPricePerShare"); String num = getValueFromNode(priceShares); num = num.equals("") ? "" : num; try { return Double.parseDouble(num); } catch (NumberFormatException e) { System.err.println("WARNING: Price shares in XML bad format: " + num); return -1; } } /** * Determines whether the shares in the trade were bought or sold. * @param values The value array representing the trade. * @return True if they were bought (acquired). False, if sold. */ public int getIsBuy(Element values) { NodeList isBuy = values.getElementsByTagName("transactionAcquiredDisposedCode"); String value = getValueFromNode(isBuy); if (value.equals("A")) { return 1; } else if (value.equals("D")) { return 0; } else { System.err.println("WARNING: Acquire code not recognized: " + value); return -1; } } /** * Returns the list of trades that occurred within the transaction. * @return A list of trade objects. */ public List getTrades() { return trades; } /** * Accessor method that returns the id of the transaction. * @return The id. */ public int getId() { return id; } /** * Accessor method that returns the person's name who did the transaction. * @return The name of whom did the transaction. */ public String getPersonName() { return personName; } }