All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.deephaven.numerics.movingaverages.ByEma Maven / Gradle / Ivy

The newest version!
//
// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
//
package io.deephaven.numerics.movingaverages;

import io.deephaven.base.verify.Require;
import io.deephaven.function.Basic;
import io.deephaven.time.DateTimeUtils;

import java.io.Serializable;
import java.time.Instant;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * An EMA which can compute aggregated EMAs without grouping and then ungrouping.
 */
public abstract class ByEma implements Serializable {

    private static final long serialVersionUID = -6541641502689292655L;

    public static class Key implements Serializable {
        private static final long serialVersionUID = 8481816497504935854L;
        final Object[] values;

        public Key(Object... values) {
            this.values = values;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            Key key = (Key) o;
            return Arrays.equals(values, key.values);
        }

        @Override
        public int hashCode() {
            return values != null ? Arrays.hashCode(values) : 0;
        }

        @Override
        public String toString() {
            return "Key{" +
                    "values=" + (values == null ? null : Arrays.asList(values)) +
                    '}';
        }
    }

    public enum BadDataBehavior {
        BD_RESET(true, false, true), BD_SKIP(false, false, false), BD_PROCESS(false, true, false);

        private final boolean reset;
        private final boolean process;
        private final boolean returnNan;

        BadDataBehavior(boolean reset, boolean process, boolean returnNan) {
            this.reset = reset;
            this.process = process;
            this.returnNan = returnNan;
        }
    }

    private final BadDataBehavior nullBehavior;
    private final BadDataBehavior nanBehavior;
    private final Map emas = new HashMap<>();

    protected ByEma(BadDataBehavior nullBehavior, BadDataBehavior nanBehavior) {
        this.nullBehavior = nullBehavior;
        this.nanBehavior = nanBehavior;

        Require.neqNull(nullBehavior, "nullBehavior");
        Require.neqNull(nanBehavior, "nanBehavior");
    }

    // Engine automatic type conversion takes care of converting all non-double nulls into double nulls so we don't have
    // to duplicate the null checking for each type.

    public synchronized double update(double value) {
        return update(value, (Object) null);
    }

    public synchronized double update(double value, Object... by) {
        return update(Long.MIN_VALUE, value, by);
    }

    public synchronized double update(Instant timestamp, double value) {
        return update(timestamp, value, (Object) null);
    }

    public synchronized double update(Instant timestamp, double value, Object... by) {
        return update(DateTimeUtils.epochNanos(timestamp), value, by);
    }

    public synchronized double update(long timestampNanos, double value, Object... by) {
        return updateInternal(timestampNanos, value, Basic.isNull(value), Double.isNaN(value), by);
    }

    private static boolean resetEma(boolean isNull, BadDataBehavior nullBehavior, boolean isNaN,
            BadDataBehavior nanBehavior) {
        return (isNull && nullBehavior.reset) || (isNaN && nanBehavior.reset);
    }

    private static boolean returnNan(boolean isNull, BadDataBehavior nullBehavior, boolean isNaN,
            BadDataBehavior nanBehavior) {
        return (isNull && nullBehavior.returnNan) || (isNaN && nanBehavior.returnNan);
    }

    private static boolean processSample(boolean isNull, BadDataBehavior nullBehavior, boolean isNaN,
            BadDataBehavior nanBehavior) {
        return (!isNull && !isNaN) || (isNull && nullBehavior.process) || (isNaN && nanBehavior.process);
    }

    private synchronized double updateInternal(long timestampNanos, double value, boolean isNull, boolean isNaN,
            Object... by) {
        Key key = new Key(by);

        AbstractMa ema = emas.get(key);

        if (ema == null || resetEma(isNull, nullBehavior, isNaN, nanBehavior)) {
            ema = createEma(key);
            emas.put(key, ema);
        }

        if (processSample(isNull, nullBehavior, isNaN, nanBehavior)) {
            ema.processDouble(timestampNanos, value);
        }

        return returnNan(isNull, nullBehavior, isNaN, nanBehavior) ? Double.NaN : ema.getCurrent();
    }

    protected abstract AbstractMa createEma(Key key);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy