package jtrade.util; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; /** * A simple circular buffer for doubles with some fancy stat methods * * @author jonkle * */ public class DoubleBuffer implements Iterable<Double> { private double[] items; private int takeIndex; // front pointer private int putIndex; // rear pointer private int count; public DoubleBuffer(int capacity) { items = new double[capacity]; } public DoubleBuffer(double[] data) { items = data.clone(); count = data.length; } /** * Circularly increment i. */ private int inc(int i) { return (++i == items.length) ? 0 : i; } /** * Adds a value to the end of the buffer. Fails if the buffer is full. * * @param value * @return True if successful */ public boolean add(double value) { if (count >= items.length) { throw new IllegalStateException("Buffer is full"); } items[putIndex] = value; putIndex = inc(putIndex); count++; return true; } /** * Tries to add a value to the end of the buffer. * * @param value * @return True if successful, false if the buffer is full. */ public boolean offer(double value) { if (count == items.length) { return false; } items[putIndex] = value; putIndex = inc(putIndex); count++; return true; } /** * Adds a value to the end of the buffer, if it is full the first value is * dropped silently. * * @param value * @return True if the first value was dropped, false if not. */ public boolean push(double value) { boolean removed = false; if (count >= items.length) { remove(); removed = true; } add(value); return removed; } /** * Sets the head value in the buffer * * @param value */ public void setHead(double value) { set(count - 1, value); } /** * Sets the tail value in the buffer * * @param value */ public void setTail(double value) { set(0, value); } /** * Sets a value in the buffer * * @param i * @param value */ public void set(int i, double value) { items[(takeIndex + i) % items.length] = value; } /** * Removes the first value from the buffer. * * @return */ public double remove() { if (count == 0) { throw new IllegalStateException("Buffer is empty"); } double o = items[takeIndex]; takeIndex = inc(takeIndex); count--; return o; } /** * Remove value at specified index. * * @param i * @return The value that was removed or NaN if no value was removed. */ public double remove(int i) { double o = Double.NaN; // if removing front item, just advance if (i == takeIndex) { o = items[takeIndex]; takeIndex = inc(takeIndex); } else { // slide over all others up through putIndex. while (true) { int nexti = inc(i); if (nexti != putIndex) { items[i] = items[nexti]; i = nexti; } else { o = items[i]; putIndex = i; break; } } } count--; return o; } /** * Returns the value at specified index, or throws exception if the index is * invalid. * * @param i * @return The value at i */ public double get(int i) { if (i >= count || i < 0) { throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + count); } return items[(takeIndex + i) % items.length]; } /** * Returns the value at specified index, or NaN if the index is invalid. * * @param i * @return The value at i */ public double peekAt(int i) { if (i >= count || i < 0) { return Double.NaN; } return items[(takeIndex + i) % items.length]; } /** * Returns the last (newest) value, or NaN if the buffer is empty. * * @return Last value */ public double last() { return peekAt(count - 1); } /** * Returns the first (oldest) value, or NaN if the buffer is empty. * * @return First value */ public double first() { return peekAt(0); } /** * Returns the current size of the buffer. * * @return Size of buffer. */ public int size() { return count; } /** * Returns the capacity of the buffer. * * @return Capacity of buffer. */ public int capacity() { return items.length; } /** * Returns true if the buffer is not empty. * * @return True if the buffer is not empty. */ public boolean isEmpty() { return count == 0; } /** * Returns true if the buffer is full. * * @return True if the buffer is full. */ public boolean isFull() { return count == items.length; } /** * Returns a copy of the buffer as a double array. * * @return Double array of the buffer */ public double[] toArray() { double[] array = new double[count]; int k = 0; int i = takeIndex; while (k < count) { array[k++] = items[i]; i = inc(i); } return array; } /** * Returns a copy of the buffer as a double array. * * @return Double array of the buffer */ public double[] toArray(int start, int end) { int len = end - start; if (start < 0) { throw new IndexOutOfBoundsException("Start: " + start); } if (len > count) { throw new IndexOutOfBoundsException("End: " + end + ", Size: " + count); } double[] array = new double[len]; int k = 0; int i = (takeIndex + start) % items.length; while (k < len) { array[k++] = items[i]; i = inc(i); } return array; } /** * Returns a string representation of the buffer. */ @Override public String toString() { if (count == 0) { return "[]"; } StringBuilder sb = new StringBuilder(); sb.append('['); int k = 0; int i = takeIndex; while (k++ < count) { sb.append(items[i]); if (k < count) { sb.append(", "); } i = inc(i); } return sb.append(']').toString(); } /** * Checks if a value exist in the buffer. * * @param o * @return True if value o exist in the buffer. */ public boolean contains(double o) { int k = 0; int i = takeIndex; while (k++ < count) { if (o == items[i] || (Double.isNaN(o) && Double.isNaN(items[i]))) { return true; } i = inc(i); } return false; } /** * Returns an iterator of the buffer. */ @Override public Iterator<Double> iterator() { return new Iterator<Double>() { int k = 0; int i = takeIndex; int j = -1; int c = count; @Override public boolean hasNext() { return k < c; } @Override public Double next() { if (k >= c) { throw new NoSuchElementException(); } double d = items[i]; k++; j = i; i = inc(i); return d; } @Override public void remove() { if (j < 0) { throw new IllegalStateException(); } DoubleBuffer.this.remove(j); i = DoubleBuffer.this.inc(j); } }; } /** * Returns a copy of the buffer as an array. * * @param a * @return */ @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < count) { a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), count); } int k = 0; int i = takeIndex; while (k < count) { a[k++] = (T) Double.valueOf(items[i]); i = inc(i); } return a; } /** * Removes a value from the buffer if it exists. * * @param d * @return True if a value was removed. */ public boolean remove(double d) { int i = takeIndex; int k = 0; while (true) { if (k++ >= count) { return false; } if (d == items[i] || (Double.isNaN(d) && Double.isNaN(items[i]))) { remove(i); return true; } i = inc(i); } } /** * Checks if all values exists in the buffer. * * @param col * @return True if all values exist. */ public boolean containsAll(Collection<? extends Number> col) { for (Iterator<? extends Number> e = col.iterator(); e.hasNext();) { if (!contains(e.next().doubleValue())) { return false; } } return true; } public boolean addAll(Collection<? extends Number> col) { boolean modified = false; for (Iterator<? extends Number> e = col.iterator(); e.hasNext();) { if (add(e.next().doubleValue())) modified = true; } return modified; } public boolean removeAll(Collection<? extends Number> c) { boolean modified = false; for (Iterator<? extends Number> e = c.iterator(); e.hasNext();) { if (c.contains(e.next().doubleValue())) { e.remove(); modified = true; } } return modified; } public boolean retainAll(Collection<? extends Number> col) { boolean modified = false; for (Iterator<? extends Number> e = iterator(); e.hasNext();) { if (!col.contains(e.next().doubleValue())) { e.remove(); modified = true; } } return modified; } /** * Clears the buffer of all values, resets size to 0. */ public void clear() { count = 0; putIndex = 0; takeIndex = 0; } /** * Returns the sum of all values in the buffer. * * @return Sum of all values */ public double sum() { if (count <= 1) { return peekAt(0); } double sum = 0.0; int k = 0; int i = takeIndex; while (k++ < count) { sum += items[i]; i = inc(i); } return sum; } /** * Returns the minimum value in the buffer. * * @return Minimum value in the buffer. */ public double min() { if (count <= 1) { return peekAt(0); } double min = Double.POSITIVE_INFINITY; int k = 0; int i = takeIndex; while (k++ < count) { min = Math.min(items[i], min); i = inc(i); } return min; } /** * Returns the maximum value in the buffer. * * @return Maximum value in the buffer. */ public double max() { if (count <= 1) { return peekAt(0); } double max = Double.NEGATIVE_INFINITY; int k = 0; int i = takeIndex; while (k++ < count) { max = Math.max(items[i], max); i = inc(i); } return max; } /** * Returns the mean of all values in the buffer. * * @return Mean of all values. */ public double mean() { return sum() / count; } /** * Returns the median of all values in the buffer. * * @return Median of all values. */ public double median2() { if (count <= 1) { return peekAt(0); } double[] d = toArray(); Arrays.sort(d); if ((d.length & 1) == 0) { int i = d.length / 2; return (d[i - 1] + d[i]) / 2; } return d[d.length / 2]; } /** * Returns the median of all values in the buffer. * * @return Median of all values. */ public double median() { if (count <= 1) { return peekAt(0); } double[] d = toArray(); int k = d.length / 2 + 1; Util.quickSelect(d, k); if ((d.length & 1) == 0) { // even return (d[k - 1] + d[k - 2]) / 2; } return d[k - 1]; } /** * Returns the variance of all values in the buffer. * * @return Variance of all values. */ public double var() { if (count == 0) { return Double.NaN; } if (count == 1) { return 0.0; } double mean = mean(); double sum = 0; int k = 0; int i = takeIndex; while (k++ < count) { double v = items[i] - mean; sum += v * v; i = inc(i); } return sum / (count - 1); } /** * Returns the standard deviation of all values in the buffer. * * @return Standard deviation of all values. */ public double std() { return Math.sqrt(var()); } /** * Linear regression of y = ax + b Returns coefficients to the regression line * "y=ax+b" from xs[] and ys[], and r2 Value * * @param xs * @param ys * @return double array with coefficients to the regression line "y=ax+b" and * r2 value */ public double[] lr() { double sumX = 0.0, sumY = 0.0, sumXX = 0.0, sumXY = 0.0; for (int x = 0, i = takeIndex; x < count; x++) { double y = items[i]; sumX += x; sumY += y; sumXX += x * x; sumXY += x * y; i = inc(i); } double det = sumXX * count - sumX * sumX; double a = (sumXY * count - sumY * sumX) / det; double b = (sumXX * sumY - sumX * sumXY) / det; double meanY = sumY / count; double meanError = 0.0, residual = 0.0; for (int x = 0, i = takeIndex; x < count; x++) { double y = items[i]; meanError += Math.pow(y - meanY, 2); residual += Math.pow(y - a * x - b, 2); i = inc(i); } double r2 = 1 - residual / meanError; return new double[] { a, b, r2 }; } /** * Returns the rise over run of the first and the last value in the buffer. * * @return Rise over run of buffer. */ public double slope() { if (count <= 1) { return Double.NaN; } return (last() - first()) / (count - 1); } /** * Returns the log return of the first and the last value in the buffer. * * @return Rise over run of buffer. */ public double logReturn() { if (count <= 1) { return Double.NaN; } return Math.log(last() / first()); } /** * Returns the log return of the first and the last value in the buffer. * * @return Rise over run of buffer. */ public double arithReturn() { if (count <= 1) { return Double.NaN; } double last = last(); return (last - first()) / last; } /** * Returns the percent rank of the specified value compared to all values in * the buffer. * * @return Percent rank of value. */ public double percentRank(double value) { final int denom = count - 1; double[] sorted = toArray(); Arrays.sort(sorted); if (value < sorted[0] || value > sorted[denom]) { return Double.NaN; } int index = Arrays.binarySearch(sorted, value); if (index < 0) { int r = (-index) - 2; double v0 = sorted[r]; double v1 = sorted[r + 1]; return (r + (value - v0) / (v1 - v0)) / denom; } return ((double) index) / denom; } public double percentRank() { return percentRank(last()); } }