org.pau.assetmanager.viewmodel.stocks.HistoricalStocksValuesDownloader.java Source code

Java tutorial

Introduction

Here is the source code for org.pau.assetmanager.viewmodel.stocks.HistoricalStocksValuesDownloader.java

Source

/**
 * This file is part of Pau's Asset Manager Project.
 *
 * Pau's Asset Manager Project is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Pau's Asset Manager Project is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Pau's Asset Manager Project.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.pau.assetmanager.viewmodel.stocks;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.pau.assetmanager.business.DaoFunction;
import org.pau.assetmanager.business.HistoricalStockValuesBusiness;
import org.pau.assetmanager.entities.HistoricalStockValue;
import org.pau.assetmanager.utils.AssetManagerRuntimeException;

import com.google.common.base.Optional;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;

public class HistoricalStocksValuesDownloader {

    private static final String DATE = "Date";
    private static final String OPEN = "Open";
    private static final String HIGH = "High";
    private static final String LOW = "Low";
    private static final String CLOSE = "Close";
    private static final String VOLUME = "Volume";
    private static final String ADJ_CLOSE = "Adj Close";

    private static final List<String> HEADERS = Lists.newArrayList(DATE, OPEN, HIGH, LOW, CLOSE, VOLUME, ADJ_CLOSE);

    public static final String EURO = "EUR=X";
    private static final String POUND = "GBP=X";

    public static final Map<String, String> LABEL_TO_CURRENCY_MAP = MyMaps.<String, String>newMap(
            Sets.<Entry<String, String>>newHashSet(MyEntries.<String, String>newEntry("L", POUND),

                    MyEntries.<String, String>newEntry("*", EURO)));

    //   MyEntries.<String, String>newEntry("L", "JPY=X"),

    private static class MyMaps {
        private static <K, V> Map<K, V> newMap(Collection<Entry<K, V>> entries) {
            Map<K, V> newHashMap = Maps.newHashMap();
            for (Entry<K, V> currentEntry : entries) {
                newHashMap.put(currentEntry.getKey(), currentEntry.getValue());
            }
            return newHashMap;
        }
    }

    private synchronized static void updateCurrencyHistoricalValues() {
        // get the minimun date for stocks
        Calendar minumDateCalendar = GregorianCalendar.getInstance();
        String query = "select min(date) as min_date from Annotation "
                + "left join StockExpensesAnnotation sea using (id) "
                + "left join StockIncomeAnnotation sia using(id) "
                + "where sia.id is not null or sea.id is not null";

        Optional<Object> stocksTimeInformation = DaoFunction.<Object>nativeQuerySimpleUniqueFunction().apply(query);
        if (stocksTimeInformation.isPresent()) {
            Date minimumDate = (Date) stocksTimeInformation.get();
            minumDateCalendar.setTime(minimumDate);
        }
        Integer currentStartYear = minumDateCalendar.get(Calendar.YEAR);
        Calendar calendar = GregorianCalendar.getInstance();
        Date currentDate = new Date();
        calendar.setTime(currentDate);
        Integer currentEndYear = calendar.get(Calendar.YEAR);
        for (String symbol : LABEL_TO_CURRENCY_MAP.values()) {
            for (int currentYear = currentStartYear; currentYear <= currentEndYear; currentYear++) {
                HistoricalStockValuesBusiness.saveOrUpdate(symbol, currentYear);
            }
        }
    }

    private static class MyEntries {
        private static <K, V> SimpleEntry<K, V> newEntry(K key, V value) {
            return new SimpleEntry<K, V>(key, value);
        }
    }

    public synchronized static void updateStocksHistoricalValuesAsync() {
        Runnable runnableStocksUpdater = new Runnable() {

            @Override
            public void run() {
                HistoricalStocksValuesDownloader.updateStocksHistoricalValues();
            }
        };

        Thread threadStocksUpdater = new Thread(runnableStocksUpdater);
        threadStocksUpdater.start();
    }

    public synchronized static void updateStocksHistoricalValues() {
        Calendar calendar = GregorianCalendar.getInstance();
        Date endDate = new Date();
        // get all values and time ranges
        String query = "select min(date) as min_date, concept from Annotation "
                + "left join StockExpensesAnnotation sea using (id) "
                + "left join StockIncomeAnnotation sia using(id) "
                + "where sia.id is not null or sea.id is not null group by concept";
        // remove all the values that were collected the same month as
        // the month they are related to
        //      HistoricalStockValuesBusiness
        //            .removeHistoricalValuesCollectedInTheirMonth();
        // insert the lacking values
        List<Object[]> stocksTimeInformation = DaoFunction.<Object[]>nativeQuerySimpleListFunction().apply(query);
        for (Object[] currentStockTimeInformation : stocksTimeInformation) {
            calendar.setTime(endDate);
            Integer currentEndYear = calendar.get(Calendar.YEAR);
            Date currentStartYearDate = (Date) currentStockTimeInformation[0];
            calendar.setTime(currentStartYearDate);
            Integer currentStartYear = calendar.get(Calendar.YEAR);
            String symbol = (String) currentStockTimeInformation[1];
            // get the historical values we have in the database for that symbol
            for (int currentYear = currentStartYear; currentYear <= currentEndYear; currentYear++) {
                HistoricalStockValuesBusiness.saveOrUpdate(symbol, currentYear);
            }
        }
        updateCurrencyHistoricalValues();
    }

    public static List<HistoricalStockValue> getHistoricalStockValuesForYear(String symbol, Integer year) {
        try {
            return getHistoricalStockValues(symbol, year, year);
        } catch (Throwable throwable) {
            // TODO: log this
            throwable.printStackTrace();
        }
        return new LinkedList<HistoricalStockValue>();
    }

    private static List<HistoricalStockValue> getHistoricalStockValues(String symbol, Integer startYear,
            Integer finalYear) {
        List<HistoricalStockValue> historicalStockValues = new LinkedList<HistoricalStockValue>();
        Table<Integer, Integer, Double> yearToMonthToValueTable = getYearToMonthToValueTable(symbol, startYear,
                finalYear);
        for (Integer currentYear : yearToMonthToValueTable.rowKeySet()) {
            Map<Integer, Double> monthToValue = yearToMonthToValueTable.row(currentYear);
            for (Integer currentMonth : monthToValue.keySet()) {
                Double value = monthToValue.get(currentMonth);
                HistoricalStockValue historicalStockValue = new HistoricalStockValue();
                historicalStockValue.setCollectionDate(new Date());
                historicalStockValue.setMonth(currentMonth);
                historicalStockValue.setYear(currentYear);
                historicalStockValue.setSymbol(symbol);
                historicalStockValue.setNumericalValue(Math.round(value * 100.0));
                historicalStockValues.add(historicalStockValue);
            }
        }
        return historicalStockValues;
    }

    private static Table<Integer, Integer, Double> getYearToMonthToValueTable(String symbol, Integer startYear,
            Integer finalYear) {
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Calendar calendar = GregorianCalendar.getInstance();
            Table<Integer, Integer, Double> yearMonthValueTable = HashBasedTable.<Integer, Integer, Double>create();
            Table<Integer, Integer, Integer> yearMonthTimesTable = HashBasedTable
                    .<Integer, Integer, Integer>create();
            List<Map<String, String>> csvContents = getContents(symbol, startYear, finalYear);
            for (Map<String, String> fieldToValueMap : csvContents) {
                Double open = Double.parseDouble(fieldToValueMap.get(OPEN));
                Double close = Double.parseDouble(fieldToValueMap.get(OPEN));
                Double averagePrice = (open + close) / 2.0;
                Date date = simpleDateFormat.parse(fieldToValueMap.get(DATE));
                calendar.setTime(date);
                Integer year = calendar.get(Calendar.YEAR);
                Integer month = calendar.get(Calendar.MONTH);
                Integer day = calendar.get(Calendar.DAY_OF_MONTH);
                Double currentPrice = yearMonthValueTable.get(year, month);
                if (currentPrice == null) {
                    currentPrice = averagePrice;
                } else {
                    currentPrice += averagePrice;
                }
                yearMonthValueTable.put(year, month, currentPrice);
                Integer times = yearMonthTimesTable.get(year, month);
                if (times == null) {
                    times = 1;
                } else {
                    times++;
                }
                yearMonthTimesTable.put(year, month, times);
            }
            for (Integer year : yearMonthTimesTable.rowKeySet()) {
                for (Integer month : yearMonthTimesTable.row(year).keySet()) {
                    yearMonthValueTable.put(year, month, yearMonthValueTable.get(year, month)
                            / yearMonthTimesTable.get(year, month).doubleValue());
                }
            }
            return yearMonthValueTable;
        } catch (ParseException e) {
            throw new AssetManagerRuntimeException("Error parsing date from CSV file for '" + symbol + "' between '"
                    + startYear + "' and '" + finalYear + "'.", e);
        }
    }

    private static List<Map<String, String>> getContents(String symbol, Integer startYear, Integer finalYear) {
        String url = "http://ichart.finance.yahoo.com/table.csv?s=" + symbol + "&a=0&b=1&c=" + startYear
                + "&d=11&e=31&f=" + finalYear;
        try {
            URL urlObject = new URL(url);
            HttpURLConnection httpURLConnection = (HttpURLConnection) urlObject.openConnection();
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.setRequestProperty("User-Agent",
                    "Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405");
            int responseCode = httpURLConnection.getResponseCode();
            if (responseCode != 200) {
                throw new AssetManagerRuntimeException(
                        "Error code '" + responseCode + "' when requesting to '" + url + "'");
            }
            try (BufferedReader bufferReader = new BufferedReader(
                    new InputStreamReader(httpURLConnection.getInputStream()));) {
                String currentLine = null;
                List<Map<String, String>> csvContents = new LinkedList<Map<String, String>>();
                if ((currentLine = bufferReader.readLine()) != null) {
                    String[] headers = currentLine.split(",");
                    List<String> actualHeadersInCVS = new ArrayList<String>(Arrays.asList(headers));
                    if (!actualHeadersInCVS.equals(HEADERS)) {
                        throw new RuntimeException(String.format(
                                "CVS header %s is not consistent with the expected list of headers %s", currentLine,
                                HEADERS.toString()));
                    }
                }

                while ((currentLine = bufferReader.readLine()) != null) {
                    Map<String, String> fieldToValueMap = new HashMap<String, String>();
                    String[] valuesInLine = currentLine.split(",");
                    for (int valueIndex = 0; valueIndex < valuesInLine.length; valueIndex++) {
                        fieldToValueMap.put(HEADERS.get(valueIndex), valuesInLine[valueIndex]);
                    }
                    csvContents.add(fieldToValueMap);
                }
                return csvContents;
            }
        } catch (IOException ioException) {
            throw new AssetManagerRuntimeException("Error downloading stocks for '" + symbol + "' between '"
                    + startYear + "' and '" + finalYear + "'.", ioException);
        }
    }

}