net.tradelib.core.Series.java Source code

Java tutorial

Introduction

Here is the source code for net.tradelib.core.Series.java

Source

// Copyright 2015 Ivan Popivanov
//
// 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
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// 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 net.tradelib.core;

import java.io.BufferedReader;
import java.io.FileReader;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BinaryOperator;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;

public class Series implements Cloneable {
    private List<LocalDateTime> index;
    List<List<Double>> data;

    // The names of the data columns
    private HashMap<String, Integer> columnNames;

    static public Series fromDailyCsv(String path, boolean header) throws Exception {
        return fromCsv(path, header, DateTimeFormatter.ofPattern("yyyy-MM-dd"), LocalTime.of(17, 0));
    }

    static public Series fromCsv(String path, boolean header, DateTimeFormatter dtf) throws Exception {
        return fromCsv(path, header, dtf, null);
    }

    static public Series fromCsv(String path, boolean header, DateTimeFormatter dtf, LocalTime lt)
            throws Exception {

        if (dtf == null) {
            if (lt == null)
                dtf = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
            else
                dtf = DateTimeFormatter.ISO_DATE;
        }

        // Parse and import the csv
        CSVFormat csvFmt = CSVFormat.DEFAULT.withCommentMarker('#').withIgnoreSurroundingSpaces();
        if (header)
            csvFmt = csvFmt.withHeader();
        CSVParser csv = csvFmt.parse(new BufferedReader(new FileReader(path)));

        int ncols = -1;
        Series result = null;
        double[] values = null;

        for (CSVRecord rec : csv.getRecords()) {
            if (result == null) {
                ncols = rec.size() - 1;
                values = new double[ncols];
                result = new Series(ncols);
            }

            for (int ii = 0; ii < ncols; ++ii) {
                values[ii] = Double.parseDouble(rec.get(ii + 1));
            }

            LocalDateTime ldt;
            if (lt != null) {
                ldt = LocalDate.parse(rec.get(0), dtf).atTime(lt);
            } else {
                ldt = LocalDateTime.parse(rec.get(0), dtf);
            }

            result.append(ldt, values);
        }

        if (header) {
            Map<String, Integer> headerMap = csv.getHeaderMap();
            result.clearNames();
            for (Map.Entry<String, Integer> me : headerMap.entrySet()) {
                if (me.getValue() > 0)
                    result.setName(me.getKey(), me.getValue() - 1);
            }
        }

        return result;
    }

    public Series(int ncols) {
        index = new ArrayList<LocalDateTime>();
        data = new ArrayList<List<Double>>(ncols);
        for (int ii = 0; ii < ncols; ++ii) {
            data.add(new ArrayList<Double>());
        }
        columnNames = new HashMap<String, Integer>();
    }

    public double get(int rowId, int colId) {
        return data.get(colId).get(rowId);
    }

    public double get(int rowId) {
        return get(rowId, 0);
    }

    public double get(int rowId, String colName) {
        return get(rowId, columnNames.get(colName));
    }

    public LocalDateTime getTimestamp(int rowId) {
        return index.get(rowId);
    }

    public double get(LocalDateTime ts, int colId) {
        int rowId = Collections.binarySearch(index, ts);
        if (rowId < 0) {
            throw new BadIndexException(
                    ts.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " is not in the index");
        }
        return data.get(colId).get(rowId);
    }

    public double get(LocalDateTime ts, String colName) {
        return get(ts, columnNames.get(colName));
    }

    public List<Double> getColumn(int colId) {
        return data.get(colId);
    }

    public List<Double> getColumn() {
        return data.get(0);
    }

    public void set(int row, double value) {
        data.get(0).set(row, value);
    }

    public void set(int row, int col, double value) {
        data.get(col).set(row, value);
    }

    public void set(int row, LocalDateTime ts) {
        index.set(row, ts);
    }

    public void set(int row, LocalDateTime ts, double... args) {
        set(row, ts);
        for (int ii = 0; ii < args.length; ++ii) {
            set(row, ii, args[ii]);
        }
    }

    public void setNames(String... names) {
        columnNames.clear();
        for (int ii = 0; ii < names.length; ++ii) {
            columnNames.put(names[ii], ii);
        }
    }

    public void setNames(Map<String, Integer> map) {
        columnNames.clear();
        columnNames.putAll(map);
    }

    public void setName(String name, int id) {
        columnNames.put(name, id);
    }

    public void clearNames() {
        columnNames.clear();
    }

    public int size() {
        return index.size();
    }

    public void append(LocalDateTime ts, double... args) {
        index.add(ts);
        int endLoop = Math.min(args.length, data.size());
        for (int ii = 0; ii < endLoop; ++ii) {
            data.get(ii).add(args[ii]);
        }
        for (int ii = endLoop; ii < data.size(); ++ii) {
            data.get(ii).add(args[args.length - 1]);
        }
    }

    public Series head(int rows) {
        if (rows < 0) {
            if (Math.abs(rows) > size())
                return null;
            rows = size() + rows;
        } else if (rows > size()) {
            return this;
        }
        Series result = new Series();
        result.index = index.subList(0, rows);
        result.data = new ArrayList<List<Double>>(data.size());
        for (int ii = 0; ii < data.size(); ++ii) {
            result.data.add(data.get(ii).subList(0, rows));
        }
        if (columnNames != null) {
            result.columnNames = new HashMap<String, Integer>(columnNames);
        }
        return result;
    }

    public Series tail(int rows) {
        if (rows < 0)
            rows = size() + rows;
        Series result = new Series();
        result.index = index.subList(size() - rows, size());
        result.data = new ArrayList<List<Double>>(data.size());
        for (int ii = 0; ii < data.size(); ++ii) {
            result.data.add(data.get(ii).subList(size() - rows, size()));
        }
        if (columnNames != null) {
            result.columnNames = new HashMap<String, Integer>(columnNames);
        }
        return result;
    }

    private TreeMap<LocalDateTime, Integer> buildDailyIndex() {
        TreeMap<LocalDateTime, Integer> result = new TreeMap<LocalDateTime, Integer>();
        int ii = 0;
        while (ii < size()) {
            LocalDate ld = index.get(ii).toLocalDate();
            int jj = ii + 1;
            for (; jj < size(); ++jj) {
                if (!index.get(jj).toLocalDate().equals(ld)) {
                    break;
                }
            }
            --jj;

            result.put(ld.atStartOfDay(), jj - ii + 1);

            ii = jj + 1;
        }

        return result;
    }

    public Series toDaily(BinaryOperator<Double> accumulator) {
        return toDaily(0.0, accumulator);
    }

    public Series toDaily(double identity, BinaryOperator<Double> accumulator) {
        TreeMap<LocalDateTime, Integer> newIndex = buildDailyIndex();
        Series result = new Series();
        int numUnique = newIndex.size();
        result.index = new ArrayList<LocalDateTime>(numUnique);
        result.data = new ArrayList<List<Double>>(data.size());
        for (int ii = 0; ii < data.size(); ++ii) {
            result.data.add(new ArrayList<Double>(newIndex.size()));
        }

        int currentIndex = 0;
        Iterator<Map.Entry<LocalDateTime, Integer>> iter = newIndex.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<LocalDateTime, Integer> entry = iter.next();
            result.append(entry.getKey(), 0.0);
            int lastIndex = result.size() - 1;
            for (int jj = 0; jj < data.size(); ++jj) {
                result.set(lastIndex, data.get(jj).subList(currentIndex, currentIndex + entry.getValue()).stream()
                        .reduce(identity, accumulator));
            }
            currentIndex += entry.getValue();
            //         result.append(entry.getKey(), 0.0);
            //         int lastIndex = result.size() - 1;
            //         for(int ii = 0; ii < entry.getValue(); ++ii, ++currentIndex) {
            //            for(int jj = 0; jj < data.size(); ++jj) {
            //               result.set(lastIndex, jj, result.get(lastIndex, jj) + get(currentIndex, jj));
            //            }
            //         }
        }

        if (columnNames != null) {
            result.columnNames = new HashMap<String, Integer>(columnNames);
        }

        return result;
    }

    public void print(DateTimeFormatter dtf) {
        for (int ii = 0; ii < size(); ++ii) {
            System.out.print(index.get(ii).format(dtf) + ": ");
            System.out.format("%,.2f", data.get(0).get(ii));
            for (int jj = 1; jj < data.size(); ++jj) {
                System.out.format(", %,.2f", data.get(jj).get(ii));
            }
            System.out.println();
        }
    }

    public void print() {
        print(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    }

    public void print(String pattern) {
        print(DateTimeFormatter.ofPattern(pattern));
    }

    private Series() {
    }

    public int columns() {
        return data.size();
    }

    public boolean isOrdered() {
        for (int ii = 1; ii < index.size(); ++ii) {
            if (index.get(ii).isBefore(index.get(ii - 1)))
                return false;
        }
        return true;
    }

    /**
     * @brief Clones (deep copy) of the object.
     * 
     * @return The copy.
     */
    public Series clone() {
        Series result = new Series();

        result.index = new ArrayList<LocalDateTime>(index);

        result.data = new ArrayList<List<Double>>(data.size());
        for (int ii = 0; ii < data.size(); ++ii) {
            ArrayList<Double> column = new ArrayList<Double>();
            column.addAll(data.get(ii));
            result.data.add(column);
        }

        if (columnNames != null) {
            result.columnNames = new HashMap<String, Integer>(columnNames);
        }

        return result;
    }

    /**
     * @brief Shifts the series k-periods down.
     * 
     * k can be negative (shift to the left) or negative (shift to the right).
     * NAs are appended/prepended to the series.
     * 
     * @return The lagged (shifted) time series.
     */
    public Series lag(int k) {
        if (k < 0) {
            int ii = 0;
            int jj = -k;
            for (; jj < index.size(); ++ii, ++jj) {
                for (int col = 0; col < data.size(); ++col) {
                    data.get(col).set(ii, data.get(col).get(jj));
                }
            }

            // Set to NA for the rest
            for (; ii < index.size(); ++ii) {
                for (int col = 0; col < data.size(); ++col) {
                    data.get(col).set(ii, Double.NaN);
                }
            }
        } else if (k > 0) {
            int ii = size() - 1;
            int jj = ii - k;
            for (; jj >= 0; --ii, --jj) {
                for (int col = 0; col < data.size(); ++col) {
                    data.get(col).set(ii, data.get(col).get(jj));
                }
            }

            // Set to NA for the rest
            for (; ii >= 0; --ii) {
                for (int col = 0; col < data.size(); ++col) {
                    data.get(col).set(ii, Double.NaN);
                }
            }
        }

        return this;
    }

    /**
     * @brief Shifts the series 1-period down.
     * 
     * NAs are appended to the series.
     * 
     * @return The lagged (shifted) time series.
     */
    public Series lag() {
        return lag(1);
    }
}