package name.abuchen.portfolio.checks.impl; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import name.abuchen.portfolio.checks.Check; import name.abuchen.portfolio.checks.Issue; import name.abuchen.portfolio.model.Account; import name.abuchen.portfolio.model.AccountTransaction; import name.abuchen.portfolio.model.AccountTransferEntry; import name.abuchen.portfolio.model.BuySellEntry; import name.abuchen.portfolio.model.Client; import name.abuchen.portfolio.model.Portfolio; import name.abuchen.portfolio.model.PortfolioTransaction; import name.abuchen.portfolio.model.PortfolioTransferEntry; public class CrossEntryCheck implements Check { @Override public List<Issue> execute(Client client) { return new CheckImpl(client).execute(); } private static class AccountEntry { Account account; AccountTransaction transaction; private AccountEntry(Account owner, AccountTransaction transaction) { this.account = owner; this.transaction = transaction; } } private static class PortfolioEntry { Portfolio portfolio; PortfolioTransaction transaction; private PortfolioEntry(Portfolio owner, PortfolioTransaction transaction) { this.portfolio = owner; this.transaction = transaction; } } private static class CheckImpl { private Client client; private List<AccountEntry> accountTransactions = new ArrayList<AccountEntry>(); private List<PortfolioEntry> portfolioTransactions = new ArrayList<PortfolioEntry>(); private List<Issue> issues = new ArrayList<Issue>(); public CheckImpl(Client client) { this.client = client; } public List<Issue> execute() { collectAccountTransactions(); collectPortfolioTransactions(); matchBuySell(); matchAccountTransfers(); matchPortfolioTransfers(); return issues; } private void collectAccountTransactions() { for (Account account : client.getAccounts()) { for (AccountTransaction t : account.getTransactions()) { if (t.getCrossEntry() != null) continue; switch (t.getType()) { case BUY: case SELL: case TRANSFER_IN: case TRANSFER_OUT: accountTransactions.add(new AccountEntry(account, t)); break; default: break; } } } } private void collectPortfolioTransactions() { for (Portfolio portfolio : client.getPortfolios()) { for (PortfolioTransaction t : portfolio.getTransactions()) { if (t.getCrossEntry() != null) continue; switch (t.getType()) { case BUY: case SELL: case TRANSFER_IN: case TRANSFER_OUT: portfolioTransactions.add(new PortfolioEntry(portfolio, t)); break; default: break; } } } } private void matchBuySell() { Iterator<AccountEntry> iterAccount = accountTransactions.iterator(); while (iterAccount.hasNext()) { AccountEntry suspect = iterAccount.next(); if (suspect.transaction.getType() != AccountTransaction.Type.BUY && suspect.transaction.getType() != AccountTransaction.Type.SELL) continue; if (suspect.transaction.getSecurity() == null) { issues.add(new BuySellMissingSecurityIssue(client, suspect.account, suspect.transaction)); iterAccount.remove(); continue; } PortfolioTransaction.Type neededType = PortfolioTransaction.Type.valueOf(suspect.transaction.getType() .name()); PortfolioEntry match = null; for (PortfolioEntry candidate : portfolioTransactions) { if (candidate.transaction.getType() != neededType) continue; if (!candidate.transaction.getDate().equals(suspect.transaction.getDate())) continue; if (candidate.transaction.getSecurity() != suspect.transaction.getSecurity()) continue; if (candidate.transaction.getAmount() != suspect.transaction.getAmount()) continue; match = candidate; break; } if (match == null) { issues.add(new MissingBuySellPortfolioIssue(client, suspect.account, suspect.transaction)); iterAccount.remove(); } else { BuySellEntry entry = new BuySellEntry(match.portfolio, suspect.account); entry.setCurrencyCode(match.transaction.getCurrencyCode()); entry.setType(match.transaction.getType()); entry.setDate(match.transaction.getDate()); entry.setSecurity(match.transaction.getSecurity()); entry.setShares(match.transaction.getShares()); entry.setAmount(match.transaction.getAmount()); entry.getPortfolioTransaction().addUnits(match.transaction.getUnits()); entry.insert(); match.portfolio.getTransactions().remove(match.transaction); suspect.account.getTransactions().remove(suspect.transaction); portfolioTransactions.remove(match); iterAccount.remove(); } } // create issues for any unmatched portfolio transaction Iterator<PortfolioEntry> iterPorfolio = portfolioTransactions.iterator(); while (iterPorfolio.hasNext()) { PortfolioEntry t = iterPorfolio.next(); if (t.transaction.getType() != PortfolioTransaction.Type.BUY && t.transaction.getType() != PortfolioTransaction.Type.SELL) continue; issues.add(new MissingBuySellAccountIssue(client, t.portfolio, t.transaction)); iterPorfolio.remove(); } } private void matchAccountTransfers() { Set<AccountEntry> matched = new HashSet<AccountEntry>(); for (AccountEntry suspect : accountTransactions) { if (matched.contains(suspect)) continue; AccountTransaction.Type neededType = null; if (suspect.transaction.getType() == AccountTransaction.Type.TRANSFER_IN) neededType = AccountTransaction.Type.TRANSFER_OUT; else if (suspect.transaction.getType() == AccountTransaction.Type.TRANSFER_OUT) neededType = AccountTransaction.Type.TRANSFER_IN; if (neededType == null) continue; AccountEntry match = null; for (AccountEntry candidate : accountTransactions) { if (matched.contains(candidate)) continue; if (candidate.account.equals(suspect.account)) continue; if (candidate.transaction.getType() != neededType) continue; if (!candidate.transaction.getDate().equals(suspect.transaction.getDate())) continue; if (candidate.transaction.getAmount() != suspect.transaction.getAmount()) continue; match = candidate; break; } if (match == null) { matched.add(suspect); issues.add(new MissingAccountTransferIssue(client, suspect.account, suspect.transaction)); } else { AccountTransferEntry crossentry = null; if (suspect.transaction.getType() == AccountTransaction.Type.TRANSFER_IN) crossentry = new AccountTransferEntry(match.account, suspect.account); else crossentry = new AccountTransferEntry(suspect.account, match.account); crossentry.setDate(match.transaction.getDate()); crossentry.setAmount(match.transaction.getAmount()); crossentry.setCurrencyCode(match.transaction.getCurrencyCode()); crossentry.insert(); suspect.account.getTransactions().remove(suspect.transaction); match.account.getTransactions().remove(match.transaction); matched.add(suspect); matched.add(match); } } accountTransactions.removeAll(matched); } private void matchPortfolioTransfers() { Set<PortfolioEntry> matched = new HashSet<PortfolioEntry>(); for (PortfolioEntry suspect : portfolioTransactions) { if (matched.contains(suspect)) continue; PortfolioTransaction.Type neededType = null; if (suspect.transaction.getType() == PortfolioTransaction.Type.TRANSFER_IN) neededType = PortfolioTransaction.Type.TRANSFER_OUT; else if (suspect.transaction.getType() == PortfolioTransaction.Type.TRANSFER_OUT) neededType = PortfolioTransaction.Type.TRANSFER_IN; if (neededType == null) continue; PortfolioEntry match = null; for (PortfolioEntry possibleMatch : portfolioTransactions) { if (matched.contains(possibleMatch)) continue; if (possibleMatch.portfolio.equals(suspect.portfolio)) continue; if (possibleMatch.transaction.getType() != neededType) continue; if (!possibleMatch.transaction.getDate().equals(suspect.transaction.getDate())) continue; if (!possibleMatch.transaction.getSecurity().equals(suspect.transaction.getSecurity())) continue; if (possibleMatch.transaction.getShares() != suspect.transaction.getShares()) continue; if (possibleMatch.transaction.getAmount() != suspect.transaction.getAmount()) continue; match = possibleMatch; break; } if (match == null) { matched.add(suspect); issues.add(new MissingPortfolioTransferIssue(client, suspect.portfolio, suspect.transaction)); } else { PortfolioTransferEntry crossentry = null; if (suspect.transaction.getType() == PortfolioTransaction.Type.TRANSFER_IN) crossentry = new PortfolioTransferEntry(match.portfolio, suspect.portfolio); else crossentry = new PortfolioTransferEntry(suspect.portfolio, match.portfolio); crossentry.setDate(match.transaction.getDate()); crossentry.setSecurity(match.transaction.getSecurity()); crossentry.setShares(match.transaction.getShares()); crossentry.setAmount(match.transaction.getAmount()); crossentry.setCurrencyCode(match.transaction.getCurrencyCode()); crossentry.insert(); suspect.portfolio.getTransactions().remove(suspect.transaction); match.portfolio.getTransactions().remove(match.transaction); matched.add(suspect); matched.add(match); } } portfolioTransactions.removeAll(matched); } } }