package name.abuchen.portfolio.snapshot; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.List; import name.abuchen.portfolio.model.Client; import name.abuchen.portfolio.model.Security; import name.abuchen.portfolio.model.SecurityPrice; 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 SecurityIndex extends PerformanceIndex { /* package */ SecurityIndex(Client client, CurrencyConverter converter, ReportingPeriod reportInterval) { super(client, converter, reportInterval); } /* package */void calculate(PerformanceIndex clientIndex, Security security) { List<SecurityPrice> prices = security.getPrices(); if (prices.isEmpty()) { initEmpty(clientIndex); return; } // prices only include historical quotes, not the latest quote. Merge // the latest quote into the list if necessary prices = security.getPricesIncludingLatest(); Interval actualInterval = clientIndex.getActualInterval(); LocalDate firstPricePoint = prices.get(0).getTime(); if (firstPricePoint.isAfter(actualInterval.getEnd())) { initEmpty(clientIndex); return; } LocalDate startDate = clientIndex.getFirstDataPoint().orElse(actualInterval.getEnd()); if (firstPricePoint.isAfter(startDate)) startDate = firstPricePoint; LocalDate endDate = actualInterval.getEnd(); LocalDate lastPricePoint = prices.get(prices.size() - 1).getTime(); if (lastPricePoint.isBefore(endDate)) endDate = lastPricePoint; int size = (int) ChronoUnit.DAYS.between(startDate, endDate) + 1; if (size <= 0) { initEmpty(clientIndex); return; } // needs currency conversion if // a) the currency of the security is not null // (otherwise it is an index) // b) the term currency differs from the currency of the security CurrencyConverter converter = security.getCurrencyCode() != null && !security.getCurrencyCode().equals(clientIndex.getCurrencyConverter().getTermCurrency()) ? clientIndex.getCurrencyConverter() : null; dates = new LocalDate[size]; delta = new double[size]; accumulated = new double[size]; transferals = new long[size]; totals = new long[size]; final double adjustment = clientIndex.getAccumulatedPercentage()[Dates.daysBetween(actualInterval.getStart(), startDate)]; // first value = reference value dates[0] = startDate; delta[0] = 0; accumulated[0] = adjustment; long valuation = totals[0] = convert(converter, security, startDate); // calculate series int index = 1; LocalDate date = startDate.plusDays(1); while (date.compareTo(endDate) <= 0) { dates[index] = date; long thisValuation = totals[index] = convert(converter, security, date); long thisDelta = thisValuation - valuation; delta[index] = (double) thisDelta / (double) valuation; accumulated[index] = ((accumulated[index - 1] + 1 - adjustment) * (delta[index] + 1)) - 1 + adjustment; date = date.plusDays(1); valuation = thisValuation; index++; } } private long convert(CurrencyConverter converter, Security security, LocalDate date) { SecurityPrice price = security.getSecurityPrice(date); if (converter == null) return price.getValue(); // use the picked date for currency conversion, not the date of the // quote. This could differ for example on weekends. return converter.convert(date, Money.of(security.getCurrencyCode(), price.getValue())).getAmount(); } private void initEmpty(PerformanceIndex clientIndex) { LocalDate startDate = clientIndex.getFirstDataPoint().orElse(clientIndex.getActualInterval().getStart()); dates = new LocalDate[] { startDate }; delta = new double[] { 0d }; accumulated = new double[] { 0d }; transferals = new long[] { 0 }; totals = new long[] { 0 }; } }