package name.abuchen.portfolio.snapshot; import java.text.MessageFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.List; import name.abuchen.portfolio.Messages; import name.abuchen.portfolio.model.Account; import name.abuchen.portfolio.model.Client; import name.abuchen.portfolio.model.Portfolio; import name.abuchen.portfolio.model.Transaction.Unit; import name.abuchen.portfolio.money.CurrencyConverter; import name.abuchen.portfolio.money.Money; import name.abuchen.portfolio.util.Dates; import name.abuchen.portfolio.util.Interval; /* package */class ClientIndex extends PerformanceIndex { /* package */ ClientIndex(Client client, CurrencyConverter converter, ReportingPeriod reportInterval) { super(client, converter, reportInterval); } /* package */void calculate(List<Exception> warnings) { Interval interval = getReportInterval().toInterval(); // the actual interval should not extend into the future if (interval.getEnd().isAfter(LocalDate.now())) { LocalDate start = interval.getStart(); LocalDate end = LocalDate.now(); if (start.isAfter(end)) start = end; interval = Interval.of(start, end); } // reported via forum: if the user selects as 'since' date something in // the future, then #getDays will return something negative. Ensure the // 'size' is at least 1 which will create an empty ClientIndex int size = Math.max(1, (int) interval.getDays() + 1); dates = new LocalDate[size]; totals = new long[size]; delta = new double[size]; accumulated = new double[size]; transferals = new long[size]; taxes = new long[size]; dividends = new long[size]; interest = new long[size]; interestCharge = new long[size]; collectTransferalsAndTaxes(interval); // first value = reference value dates[0] = interval.getStart(); delta[0] = 0; accumulated[0] = 0; ClientSnapshot snapshot = ClientSnapshot.create(getClient(), getCurrencyConverter(), dates[0]); long valuation = totals[0] = snapshot.getMonetaryAssets().getAmount(); // calculate series int index = 1; LocalDate date = interval.getStart().plusDays(1); while (date.compareTo(interval.getEnd()) <= 0) { dates[index] = date; snapshot = ClientSnapshot.create(getClient(), getCurrencyConverter(), dates[index]); long thisValuation = totals[index] = snapshot.getMonetaryAssets().getAmount(); long thisDelta = thisValuation - transferals[index] - valuation; if (valuation == 0) { delta[index] = 0; if (thisDelta != 0d) { if (transferals[index] != 0) delta[index] = (double) thisDelta / (double) transferals[index]; else warnings.add(new RuntimeException(MessageFormat.format(Messages.MsgDeltaWithoutAssets, thisDelta, date.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM))))); } } else { delta[index] = (double) thisDelta / (double) valuation; } accumulated[index] = ((accumulated[index - 1] + 1) * (delta[index] + 1)) - 1; date = date.plusDays(1); valuation = thisValuation; index++; } } protected void addValue(long[] array, String currencyCode, long value, Interval interval, LocalDate time) { if (value == 0) return; int ii = Dates.daysBetween(interval.getStart(), time); if (!currencyCode.equals(getCurrencyConverter().getTermCurrency())) array[ii] += getCurrencyConverter().convert(time, Money.of(currencyCode, value)).getAmount(); else array[ii] += value; } private void collectTransferalsAndTaxes(Interval interval) { for (Account account : getClient().getAccounts()) { account.getTransactions() // .stream() // .filter(t -> !t.getDate().isBefore(interval.getStart()) && !t.getDate().isAfter(interval.getEnd())) .forEach(t -> { // NOSONAR switch (t.getType()) { case DEPOSIT: addValue(transferals, t.getCurrencyCode(), t.getAmount(), interval, t.getDate()); break; case REMOVAL: addValue(transferals, t.getCurrencyCode(), -t.getAmount(), interval, t.getDate()); break; case TAXES: addValue(taxes, t.getCurrencyCode(), t.getAmount(), interval, t.getDate()); break; case TAX_REFUND: addValue(taxes, t.getCurrencyCode(), -t.getAmount(), interval, t.getDate()); break; case DIVIDENDS: addValue(taxes, t.getCurrencyCode(), t.getUnitSum(Unit.Type.TAX).getAmount(), interval, t.getDate()); addValue(dividends, t.getCurrencyCode(), t.getAmount(), interval, t.getDate()); break; case INTEREST: addValue(interest, t.getCurrencyCode(), t.getAmount(), interval, t.getDate()); break; case INTEREST_CHARGE: addValue(interest, t.getCurrencyCode(), -t.getAmount(), interval, t.getDate()); addValue(interestCharge, t.getCurrencyCode(), t.getAmount(), interval, t.getDate()); break; default: // do nothing break; } }); } for (Portfolio portfolio : getClient().getPortfolios()) { portfolio.getTransactions() // .stream() // .filter(t -> !t.getDate().isBefore(interval.getStart()) && !t.getDate().isAfter(interval.getEnd())) .forEach(t -> { // collect taxes addValue(taxes, t.getCurrencyCode(), t.getUnitSum(Unit.Type.TAX).getAmount(), // interval, t.getDate()); // collect transferals switch (t.getType()) { case DELIVERY_INBOUND: addValue(transferals, t.getCurrencyCode(), t.getAmount(), interval, t.getDate()); break; case DELIVERY_OUTBOUND: addValue(transferals, t.getCurrencyCode(), -t.getAmount(), interval, t.getDate()); break; default: break; } }); } } }