package name.abuchen.portfolio.snapshot; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; import java.util.List; import name.abuchen.portfolio.math.IRR; import name.abuchen.portfolio.model.Account; import name.abuchen.portfolio.model.AccountTransaction; import name.abuchen.portfolio.model.AccountTransaction.Type; import name.abuchen.portfolio.model.Client; import name.abuchen.portfolio.model.Portfolio; import name.abuchen.portfolio.model.PortfolioTransaction; import name.abuchen.portfolio.model.Transaction; import name.abuchen.portfolio.money.CurrencyConverter; import name.abuchen.portfolio.money.Values; import name.abuchen.portfolio.util.Interval; public class ClientIRRYield { public static ClientIRRYield create(Client client, ClientSnapshot snapshotStart, ClientSnapshot snapshotEnd) { Interval interval = Interval.of(snapshotStart.getTime(), snapshotEnd.getTime()); List<Transaction> transactions = new ArrayList<Transaction>(); collectAccountTransactions(client, interval, transactions); collectPortfolioTransactions(client, interval, transactions); Collections.sort(transactions, new Transaction.ByDate()); List<LocalDate> dates = new ArrayList<LocalDate>(); List<Double> values = new ArrayList<Double>(); collectDatesAndValues(interval, snapshotStart, snapshotEnd, transactions, dates, values); double irr = IRR.calculate(dates, values); return new ClientIRRYield(irr); } private double irr; private ClientIRRYield(double irr) { this.irr = irr; } public double getIrr() { return irr; } private static void collectPortfolioTransactions(Client client, Interval interval, List<Transaction> transactions) { for (Portfolio portfolio : client.getPortfolios()) { portfolio.getTransactions().stream() // .filter(t -> interval.contains(t.getDate())) // .forEach(t -> { switch (t.getType()) { case TRANSFER_IN: case TRANSFER_OUT: case DELIVERY_INBOUND: case DELIVERY_OUTBOUND: transactions.add(t); break; case BUY: case SELL: break; default: throw new UnsupportedOperationException(); } }); } } private static void collectAccountTransactions(Client client, Interval interval, List<Transaction> transactions) { for (Account account : client.getAccounts()) { account.getTransactions().stream() // .filter(t -> interval.contains(t.getDate())) // .forEach(t -> { switch (t.getType()) { case DEPOSIT: case REMOVAL: case TRANSFER_IN: case TRANSFER_OUT: transactions.add(t); break; case BUY: case SELL: case FEES: case FEES_REFUND: case TAXES: case DIVIDENDS: case INTEREST: case INTEREST_CHARGE: case TAX_REFUND: break; default: throw new UnsupportedOperationException(); } }); } } private static void collectDatesAndValues(Interval interval, ClientSnapshot snapshotStart, ClientSnapshot snapshotEnd, List<Transaction> transactions, List<LocalDate> dates, List<Double> values) { CurrencyConverter converter = snapshotStart.getCurrencyConverter(); dates.add(interval.getStart()); // snapshots are always in target currency, no conversion needed values.add(-snapshotStart.getMonetaryAssets().getAmount() / Values.Amount.divider()); for (Transaction t : transactions) { dates.add(t.getDate()); if (t instanceof AccountTransaction) { AccountTransaction at = (AccountTransaction) t; long amount = converter.convert(t.getDate(), t.getMonetaryAmount()).getAmount(); if (at.getType() == Type.DEPOSIT || at.getType() == Type.TRANSFER_IN) amount = -amount; values.add(amount / Values.Amount.divider()); } else if (t instanceof PortfolioTransaction) { PortfolioTransaction pt = (PortfolioTransaction) t; long amount = converter.convert(t.getDate(), t.getMonetaryAmount()).getAmount(); if (pt.getType() == PortfolioTransaction.Type.DELIVERY_INBOUND || pt.getType() == PortfolioTransaction.Type.TRANSFER_IN) amount = -amount; values.add(amount / Values.Amount.divider()); } else { throw new UnsupportedOperationException(); } } dates.add(interval.getEnd()); values.add(snapshotEnd.getMonetaryAssets().getAmount() / Values.Amount.divider()); } }