fr.cph.stock.entities.Portfolio.java Source code

Java tutorial

Introduction

Here is the source code for fr.cph.stock.entities.Portfolio.java

Source

/**
 * Copyright 2017 Carl-Philipp Harmant
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.cph.stock.entities;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import fr.cph.stock.entities.chart.IChart;
import fr.cph.stock.entities.chart.PieChart;
import fr.cph.stock.entities.chart.TimeChart;
import fr.cph.stock.entities.chart.TimeValueChart;
import fr.cph.stock.enumtype.Currency;
import fr.cph.stock.enumtype.MarketCapitalization;
import fr.cph.stock.exception.NotFoundException;
import fr.cph.stock.util.Constants;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.math.MathContext;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

/**
 * This class represents a portofolio that belongs to a user
 *
 * @author Carl-Philipp Harmant
 */
public class Portfolio {
    /**
     * Precision of calculation
     **/
    private final MathContext mathContext = MathContext.DECIMAL32;
    private static final int PERCENT = 100;
    @Getter
    @Setter
    private int id;
    @Getter
    @Setter
    private int userId;
    @Getter
    @Setter
    private Currency currency;
    @Getter
    @Setter
    private Double liquidity;
    @Getter
    @Setter
    private List<Equity> equities;
    @Getter
    @Setter
    private List<ShareValue> shareValues;
    @Getter
    @Setter
    private List<Account> accounts;
    @Getter
    @Setter
    private Map<String, List<Index>> indexes;

    // Calculated
    @Getter
    private Double totalQuantity;
    @Getter
    private Double averageUnitCostPrice;
    @Getter
    private Double averageQuotePrice;
    private Double totalValue;
    private Double totalPlusMinusValue;
    private Double yieldYear;
    private Double yieldYearPerc;
    private Double totalGain;
    private Date lastCompanyUpdate;
    /**
     * Best performance of the share value
     **/
    private Double maxShareValue;
    /**
     * Date of the best performance
     **/
    private Date maxShareValueDate;
    /**
     * Current share value gain
     **/
    private Double currentShareValuesGain;
    /**
     * Current share value gain in percentage
     **/
    private Double currentShareValuesGainPorcentage;
    /**
     * Current share value yield
     **/
    private Double currentShareValuesYield;
    /**
     * Current share value taxes
     **/
    private Double currentShareValuesTaxes;
    /**
     * Current share value taxes
     **/
    private Double currentShareValuesVolume;
    @Getter
    private Double totalVariation;
    @Getter
    private Double totalGainToday;
    /**
     * Map that represents data of sector chart
     **/
    private Map<String, Double> chartSectorData;
    /**
     * Map that represents data of capitalization chart
     **/
    private Map<String, Double> chartCapData;
    /**
     * Maps that represents data of share value (user + cac40 + s&p500
     **/
    private Map<Date, Double> chartShareValueData, chartShareValueData2, chartShareValueData3;
    /**
     * Chart objects
     **/
    private IChart timechart, timeValueChart, piechartsector, piechartcap;

    /**
     * Constructor
     */
    public Portfolio() {
        totalQuantity = 0d;
        averageUnitCostPrice = 0d;
        averageQuotePrice = 0d;
        totalValue = 0d;
        totalPlusMinusValue = 0d;
        yieldYear = 0d;
        totalGain = 0d;
        yieldYearPerc = 0d;
        liquidity = 0d;
        indexes = new HashMap<>();
    }

    /**
     * Get total value
     *
     * @return the total value
     */
    public final Double getTotalValue() {
        return totalValue + getLiquidity();
    }

    /**
     * Get total plus minus value
     *
     * @return the total plus minus value
     */
    public final Double getTotalPlusMinusValue() {
        return totalPlusMinusValue;
    }

    /**
     * Get total plus minus value in absolute
     *
     * @return the total plus minus value in absolute
     */
    public final Double getTotalPlusMinusValueAbsolute() {
        return Math.abs(totalPlusMinusValue);
    }

    /**
     * Get yield year
     *
     * @return the yield year
     */
    public final Double getYieldYear() {
        return yieldYear;
    }

    /**
     * Get total gain
     *
     * @return the total gain
     */
    public final Double getTotalGain() {
        return totalGain;
    }

    /**
     * Get Yield per year in percentage
     *
     * @return a double
     */
    public final Double getYieldYearPerc() {
        return yieldYearPerc;
    }

    /**
     * Get last company update date
     *
     * @return a date
     */
    public final Date getLastCompanyUpdate() {
        if (lastCompanyUpdate != null) {
            return (Date) lastCompanyUpdate.clone();
        } else {
            return null;
        }
    }

    /**
     * Set last company ypdate date
     *
     * @param lastCompanyUpdate the date
     */
    public final void setLastCompanyUpdate(final Date lastCompanyUpdate) {
        if (lastCompanyUpdate != null) {
            this.lastCompanyUpdate = (Date) lastCompanyUpdate.clone();
        }
    }

    /**
     * This function generates all calculated field
     */
    public final void compute() {
        Double totalUnitCostPrice = 0d;
        Double totalAverageQuotePrice = 0d;
        Double totalOriginalValue = 0d;
        totalVariation = 0d;
        double totalValueStart = 0;
        totalGainToday = 0d;
        Date lastUpdate = null;
        if (equities != null) {
            for (final Equity equity : equities) {
                totalQuantity += equity.getQuantity();
                totalUnitCostPrice += equity.getUnitCostPrice();
                totalAverageQuotePrice += equity.getCompany().getQuote() * equity.getParity();
                totalValue += equity.getValue();
                totalOriginalValue += equity.getQuantity() * equity.getUnitCostPrice() * equity.getCurrentParity();
                yieldYear += equity.getYieldYear();
                totalGain += equity.getPlusMinusUnitCostPriceValue();
                if (equity.getCompany().getRealTime()) {
                    if (lastUpdate == null) {
                        lastUpdate = equity.getCompany().getLastUpdate();
                    } else {
                        if (equity.getCompany().getLastUpdate() != null
                                && lastUpdate.after(equity.getCompany().getLastUpdate())) {
                            lastUpdate = equity.getCompany().getLastUpdate();
                        }
                    }
                }
                if (equity.getCompany().getChange() != null) {
                    double valueStart = equity.getValue() / (equity.getCompany().getChange() / PERCENT + 1);
                    totalValueStart += valueStart;
                    totalGainToday += valueStart * equity.getCompany().getChange() / PERCENT;
                }

            }
            totalVariation = totalValueStart == 0 ? totalValueStart
                    : ((totalValueStart + totalGainToday) / totalValueStart - 1) * PERCENT;
            averageUnitCostPrice = totalUnitCostPrice / equities.size();
            averageQuotePrice = totalAverageQuotePrice / equities.size();
            totalPlusMinusValue = ((totalValue - totalOriginalValue) / totalOriginalValue) * PERCENT;
            yieldYearPerc = yieldYear / getTotalValue() * PERCENT;
            setLastCompanyUpdate(lastUpdate);
        }
    }

    public final Double getTotalGainTodayAbsolute() {
        return Math.abs(totalGainToday);
    }

    /**
     * Get chart sector data
     *
     * @return a map
     */
    protected final Map<String, Double> getChartSectorData() {
        if (chartSectorData == null) {
            final Map<String, Double> data = new HashMap<>();
            for (final Equity equity : getEquities()) {
                if (equity.getCompany().getFund()) {
                    addEquityValueToMap(data, Constants.FUND, equity);
                } else {
                    final String sector = equity.getCurrentSector();
                    if (StringUtils.isEmpty(sector)) {
                        addEquityValueToMap(data, Constants.UNKNOWN, equity);
                    } else {
                        addEquityValueToMap(data, sector, equity);
                    }
                }
            }
            chartSectorData = new TreeMap<>();
            chartSectorData.putAll(data);
        }
        return chartSectorData;
    }

    private void addEquityValueToMap(final Map<String, Double> data, final String key, final Equity equity) {
        if (data.containsKey(key)) {
            data.put(key, data.get(key) + equity.getValue());
        } else {
            data.put(key, equity.getValue());
        }
    }

    /**
     * Get chart share value data
     *
     * @return a map
     */
    protected final Map<Date, Double> getChartShareValueData() {
        Map<Date, Double> data = new HashMap<>();
        List<ShareValue> shareValuess = getShareValues();
        int max = shareValuess.size();
        double base = shareValuess.get(max - 1).getShareValue();
        for (int i = max - 1; i != -1; i--) {
            ShareValue temp = shareValuess.get(i);
            Double value = temp.getShareValue() * PERCENT / base;
            data.put(temp.getDate(), value);
        }
        chartShareValueData = new TreeMap<>();
        chartShareValueData.putAll(data);
        return chartShareValueData;
    }

    /**
     * Generate chart share value data
     */
    protected final void getChartShareValueData2() {
        if (chartShareValueData2 == null && chartShareValueData3 == null) {
            Map<Date, Double> data = new HashMap<>();
            Map<Date, Double> data2 = new HashMap<>();
            List<ShareValue> shareValuess = getShareValues();
            int max = shareValuess.size();
            for (int i = max - 1; i != -1; i--) {
                ShareValue temp = shareValuess.get(i);
                Double value = temp.getPortfolioValue();
                Double liqui = temp.getLiquidities();
                data.put(temp.getDate(), value);
                if (liqui != null) {
                    data2.put(temp.getDate(), liqui);
                }
            }
            chartShareValueData2 = new TreeMap<>();
            chartShareValueData2.putAll(data);
            chartShareValueData3 = new TreeMap<>();
            chartShareValueData3.putAll(data2);
        }
    }

    /**
     * Get chart capitalization data
     *
     * @return a map
     */
    protected final Map<String, Double> getChartCapData() {
        if (chartCapData == null) {
            Map<String, Double> data = new HashMap<>();
            for (Equity equity : getEquities()) {
                if (!equity.getCompany().getFund()) {
                    MarketCapitalization marketCap = equity.getMarketCapitalizationType();
                    if (marketCap == null) {
                        addEquityValueToMap(data, Constants.UNKNOWN, equity);
                    } else {
                        addEquityValueToMap(data, marketCap.getValue(), equity);
                    }
                } else {
                    addEquityValueToMap(data, Constants.UNKNOWN, equity);
                }
            }
            chartCapData = new TreeMap<>();
            chartCapData.putAll(data);
        }
        return chartCapData;
    }

    /**
     * Get a list of yahoo id representing equities that do not have real time data
     *
     * @return a list of yahoo id
     */
    public final List<String> getCompaniesYahooIdRealTime() {
        return getEquities().stream().filter(equity -> equity.getCompany().getRealTime())
                .map(equity -> equity.getCompany().getYahooId()).collect(Collectors.toList());
    }

    /**
     * Add indexes
     *
     * @param indexes a list of indexes
     */
    public final void addIndexes(final List<Index> indexes) {
        if (indexes.size() > 0) {
            String index = indexes.get(0).getYahooId();
            this.indexes.put(index, indexes);
        }
    }

    /**
     * Get portfolio review. An horrible function.
     *
     * @return a string
     */
    public final String getPortfolioReview() {
        final StringBuilder sb = new StringBuilder();
        sb.append("<table class=\"shareValueTableDetails\">");
        for (final Equity equity : getEquities()) {
            sb.append("<tr><td width=200px><b>").append(equity.getCurrentName()).append("</b></td><td width=180px>")
                    .append(equity.getQuantity()).append(" * ").append(equity.getCompany().getQuote());
            if (equity.getCompany().getCurrency() != getCurrency()) {
                sb.append(" * ").append(getCurrency().getParity(equity.getCompany().getCurrency()));
            }
            sb.append("</td><td>").append(equity.getValue()).append(" (").append(getCurrency().getCode())
                    .append(")</td></tr>");
        }
        sb.append("<tr><td colspan=3><b>Liquidity:</b> ").append(getLiquidity()).append(" (")
                .append(getCurrency().getCode()).append(")</td></tr>");
        sb.append("<tr><td colspan=3><b>Total:</b> ")
                .append(new BigDecimal(getTotalValue(), mathContext).doubleValue()).append(" (")
                .append(getCurrency().getCode()).append(")</td></tr>");
        sb.append("</table>");
        return sb.toString();
    }

    /**
     * Get pie chart sector
     *
     * @return the chart
     */
    public final IChart getPieChartSector() {
        if (piechartsector == null) {
            final Map<String, Double> map = getChartSectorData();
            piechartsector = new PieChart(map);
            piechartsector.generate();
        }
        return piechartsector;
    }

    /**
     * Get pie chart capitalization
     *
     * @return the chart
     */
    public final IChart getPieChartCap() {
        if (piechartcap == null) {
            final Map<String, Double> map = getChartCapData();
            piechartcap = new PieChart(map);
            piechartcap.generate();
        }
        return piechartcap;
    }

    /**
     * Get time value chart
     *
     * @return the chart
     */
    public final IChart getTimeValueChart() {
        if (timeValueChart == null && shareValues.size() > 0) {
            getChartShareValueData2();
            timeValueChart = new TimeValueChart(chartShareValueData2, chartShareValueData3,
                    shareValues.get(0).getDate());
            timeValueChart.generate();
        }
        return timeValueChart;
    }

    /**
     * Get time chart
     *
     * @return the chart
     */
    public final IChart getTimeChart() {
        if (timechart == null && shareValues.size() > 0) {
            Map<Date, Double> map = getChartShareValueData();
            // Modifying first element of each to make it pretty in chart
            timechart = new TimeChart(map, indexes, shareValues.get(0).getDate());
            timechart.generate();
        }
        return timechart;
    }

    /**
     * Get one account with its name
     *
     * @param name the account name
     * @return the account
     */
    public final Optional<Account> getAccount(final String name) {
        return accounts == null ? Optional.empty()
                : accounts.stream().filter(account -> account.getName().equals(name)).findFirst();
    }

    /**
     * Get one account with its id
     *
     * @param id the id
     * @return the account
     */
    public final Optional<Account> getAccount(final int id) {
        return accounts == null ? Optional.empty()
                : accounts.stream().filter(account -> account.getId() == id).findFirst();
    }

    /**
     * Get the first account created
     *
     * @return the account
     */
    public final Optional<Account> getFirstAccount() {
        return accounts == null ? Optional.empty()
                : accounts.stream().filter(account -> !account.getDel()).findFirst();
    }

    /**
     * Get a string containing the list of sector
     *
     * @return a string
     */
    public final String getHTMLSectorByCompanies() {
        final Map<String, List<Equity>> map = getSectorByCompanies();
        return extractHTMLfromMap(map);
    }

    protected Map<String, List<Equity>> getSectorByCompanies() {
        final Map<String, List<Equity>> map = new TreeMap<>();
        List<Equity> companies;
        for (final Equity equity : getEquities()) {
            if (equity.getCompany().getFund()) {
                companies = map.getOrDefault(Constants.FUND, new ArrayList<>());
                companies.add(equity);
                map.putIfAbsent(Constants.FUND, companies);
            } else {
                final String sector = equity.getCurrentSector();
                if (StringUtils.isEmpty(sector)) {
                    companies = map.getOrDefault(Constants.UNKNOWN, new ArrayList<>());
                    companies.add(equity);
                    map.putIfAbsent(Constants.UNKNOWN, companies);
                } else {
                    companies = map.getOrDefault(sector, new ArrayList<>());
                    companies.add(equity);
                    map.putIfAbsent(sector, companies);
                }
            }
        }
        return map;
    }

    private String extractHTMLfromMap(final Map<String, List<Equity>> map) {
        final StringBuilder res = new StringBuilder("var companies = [");
        boolean addComma = false;
        for (final Entry<String, List<Equity>> entry : map.entrySet()) {
            if (addComma) {
                res.append(",");
            }
            addComma = true;
            res.append("'");
            entry.getValue().forEach(equity -> res.append(" - ").append(equity.getCurrentName()).append("<br>"));
            res.append("'");
        }
        res.append("];");
        return res.toString();
    }

    /**
     * Get a string containing the list of capitalization
     *
     * @return a string
     */
    public final String getHTMLCapByCompanies() {
        final Map<String, List<Equity>> map = getGapByCompanies();
        return extractHTMLfromMap(map);
    }

    protected Map<String, List<Equity>> getGapByCompanies() {
        final Map<String, List<Equity>> map = new TreeMap<>();
        List<Equity> companies;
        for (final Equity equity : getEquities()) {
            if (equity.getMarketCapitalizationType().getValue() == null || equity.getCompany().getFund()) {
                equity.setMarketCapitalizationType(MarketCapitalization.UNKNOWN);
            }
            companies = map.getOrDefault(equity.getMarketCapitalizationType().getValue(), new ArrayList<>());
            companies.add(equity);
            map.put(equity.getMarketCapitalizationType().getValue(), companies);
        }
        return map;
    }

    /**
     * This function generates the share value info (because it muse be calculated each time)
     */
    private void generateShareValueInfo() {
        List<ShareValue> shareValuess = getShareValues();
        if (shareValuess.size() != 0 && maxShareValue == null && maxShareValueDate == null
                && currentShareValuesYield == null && currentShareValuesTaxes == null
                && currentShareValuesVolume == null && currentShareValuesGain == null
                && currentShareValuesGainPorcentage == null) {
            ShareValue lastShareValue = shareValuess.get(0);
            ShareValue firstShareValue = shareValuess.get(shareValuess.size() - 1);
            double liquidityMov = 0;
            currentShareValuesYield = 0d;
            currentShareValuesTaxes = 0d;
            currentShareValuesVolume = 0d;

            double max = shareValuess.get(0).getShareValue();
            Date date = shareValuess.get(0).getDate();
            for (int i = 0; i <= shareValuess.size() - 1; i++) {
                ShareValue sv = shareValuess.get(i);
                double current = sv.getShareValue();
                final Account account = getAccount(sv.getAccount().getName())
                        .orElseThrow(() -> new NotFoundException(sv.getAccount().getName()));
                if (current > max) {
                    max = current;
                    date = sv.getDate();
                }
                if (sv.getLiquidityMovement() != null && i != shareValuess.size() - 1) {
                    liquidityMov += sv.getLiquidityMovement() * account.getParity();
                }
                if (sv.getYield() != null) {
                    currentShareValuesYield += sv.getYield() * account.getParity();
                }
                if (sv.getTaxe() != null) {
                    currentShareValuesTaxes += sv.getTaxe() * account.getParity();
                }
                if (sv.getBuy() != null) {
                    currentShareValuesVolume += sv.getBuy() * account.getParity();
                }
                if (sv.getSell() != null) {
                    currentShareValuesVolume += sv.getBuy() * account.getParity();
                }
            }
            // log.info("Liquidity Movement: " + liquidityMov);
            double depart = liquidityMov + firstShareValue.getPortfolioValue();
            double fin = lastShareValue.getPortfolioValue();
            // log.info("Valeur portefeuille dpart: " + depart +
            // " ( Liquidity Movement + valeur de dpart soit " + liquidityMov + " + "
            // + firstShareValue.getPortfolioValue() + ")");
            // log.info("Valeur portefeuille arrive: " + fin);
            // log.info("Dividendes recues: " + currentShareValuesYield +
            // " (dja incluse dans le portefeuille d'arrive");
            // log.info("Gain (arrive - dpart): " + (fin - depart));
            // log.info("Gain en pourcentage (arrive - dpart) / dpart * 100: " + (fin -
            // depart)
            // / depart * 100);

            currentShareValuesGain = fin - depart;

            double last = lastShareValue.getShareValue();
            double first = firstShareValue.getShareValue();
            currentShareValuesGainPorcentage = last * PERCENT / first - PERCENT;
            maxShareValue = max;
            maxShareValueDate = date;
        }
    }

    /**
     * Get max share value
     *
     * @return a double
     */
    public final Double getMaxShareValue() {
        generateShareValueInfo();
        return maxShareValue;
    }

    /**
     * Get max share value date
     *
     * @return a double
     */
    public final Date getMaxShareValueDate() {
        generateShareValueInfo();
        return maxShareValueDate != null ? (Date) maxShareValueDate.clone() : null;
    }

    /**
     * Get current share value yield
     *
     * @return a double
     */
    public final Double getCurrentShareValuesYield() {
        generateShareValueInfo();
        return currentShareValuesYield;
    }

    /**
     * Get current share value taxes
     *
     * @return a double
     */
    public final Double getCurrentShareValuesTaxes() {
        generateShareValueInfo();
        return currentShareValuesTaxes;
    }

    /**
     * Get current share values volume
     *
     * @return a double
     */
    public final Double getCurrentShareValuesVolume() {
        generateShareValueInfo();
        return currentShareValuesVolume;
    }

    /**
     * Get current share values gain
     *
     * @return a double
     */
    public final Double getCurrenShareValuesGain() {
        generateShareValueInfo();
        return currentShareValuesGain;
    }

    /**
     * Get current share values gain in Percentage
     *
     * @return a double
     */
    public final Double getCurrenShareValuesGainPorcentage() {
        generateShareValueInfo();
        return currentShareValuesGainPorcentage;
    }

    /**
     * Get a view of the object in json
     *
     * @return a JSONObject
     */
    public final JsonObject getJSONObject() {
        JsonObject json = new JsonObject();
        json.addProperty("id", getId());
        json.addProperty("userId", getUserId());
        json.add(Constants.CURRENCY, getCurrency().getJSONObject());
        json.addProperty(Constants.LIQUIDITY, getLiquidity());
        json.addProperty("lastUpdate", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(getLastCompanyUpdate()));
        json.addProperty("totalValue", getTotalValue());
        json.addProperty("totalGain", getTotalGain());
        json.addProperty("totalPlusMinusValue", getTotalPlusMinusValue());
        json.addProperty("yieldYear", getYieldYear());
        json.addProperty("yieldYearPerc", getYieldYearPerc());
        JsonArray equitiess = new JsonArray();
        for (Equity e : getEquities()) {
            equitiess.add(e.getJSONObject());
        }
        json.add("equities", equitiess);
        JsonArray shareValuess = new JsonArray();
        int i = 0;
        while (i < this.shareValues.size() && i < 11) {
            shareValuess.add(this.shareValues.get(i).getJSONObject());
            i++;
        }
        json.add("shareValues", shareValuess);
        JsonArray accs = new JsonArray();
        for (final Account acc : this.accounts) {
            accs.add(acc.getJSONObject());
        }
        json.add("accounts", accs);
        JsonObject jsonPerf = new JsonObject();
        jsonPerf.addProperty("gain", currentShareValuesGain);
        jsonPerf.addProperty("performance", currentShareValuesGainPorcentage);
        jsonPerf.addProperty("yield", currentShareValuesYield);
        jsonPerf.addProperty("taxes", currentShareValuesTaxes);
        json.add("performance", jsonPerf);

        if (getTimeChart() != null) {
            TimeChart timeChart = (TimeChart) getTimeChart();
            json.addProperty("chartShareValueColors", timeChart.getColors());
            json.addProperty("chartShareValueData", timeChart.getData());
            Date date = timeChart.getDate();
            String formatDate = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(date);
            json.addProperty("chartShareValueDate", formatDate);
            json.addProperty("chartShareValueDraw", timeChart.getDraw());
        }

        PieChart pieChart = (PieChart) getPieChartSector();
        json.addProperty("chartSectorData", pieChart.getData());
        json.addProperty("chartSectorTitle", pieChart.getTitle());
        json.addProperty("chartSectorDraw", pieChart.getDraw());
        json.addProperty("chartSectorCompanies", getHTMLSectorByCompanies());

        pieChart = (PieChart) getPieChartCap();
        json.addProperty("chartCapData", pieChart.getData());
        json.addProperty("chartCapTitle", pieChart.getTitle());
        json.addProperty("chartCapDraw", pieChart.getDraw());
        json.addProperty("chartCapCompanies", getHTMLCapByCompanies());

        json.addProperty("totalVariation", totalVariation);

        return json;
    }
}