github.com/diadata-org/diadata@v1.4.593/internal/pkg/filtersBlockService/Filter.go (about)

     1  package filters
     2  
     3  import (
     4  	"errors"
     5  	"math"
     6  	"sort"
     7  	"time"
     8  
     9  	"github.com/diadata-org/diadata/pkg/dia"
    10  	models "github.com/diadata-org/diadata/pkg/model"
    11  )
    12  
    13  // Filter interface defines a filter's methods processing trades from the tradesBlockService.
    14  type Filter interface {
    15  	compute(trade dia.Trade)
    16  	finalCompute(t time.Time) float64
    17  	filterPointForBlock() *dia.FilterPoint
    18  	save(ds models.Datastore) error
    19  }
    20  
    21  func RemoveOutliers(samples []float64, scale float64) ([]float64, []int) {
    22  	return removeOutliersScaled(samples, scale)
    23  }
    24  
    25  func removeOutliers(samples []float64) ([]float64, []int) {
    26  	return removeOutliersScaled(samples, float64(1.5))
    27  }
    28  
    29  // RemoveOutliersScaled Cleans a data set it accordance to the acceptable range within interquartile range.
    30  // It returns the cleaned data slice plus a slice of lower and upper index bounds.
    31  func removeOutliersScaled(samples []float64, scale float64) ([]float64, []int) {
    32  	var indexBounds []int
    33  	if len(samples) == 0 || len(samples) == 1 {
    34  		return samples, indexBounds
    35  	}
    36  	Q1, Q3 := computeQuartiles(samples)
    37  	IQR := Q3 - Q1
    38  	lowerBound := Q1 - scale*IQR
    39  	upperBound := Q3 + scale*IQR
    40  	lowerIndex := 0
    41  	upperIndex := len(samples)
    42  	for index, value := range samples {
    43  		if value < lowerBound {
    44  			lowerIndex = index + 1
    45  		} else if value > upperBound {
    46  			upperIndex = index
    47  			break
    48  		}
    49  	}
    50  	indexBounds = append(indexBounds, lowerIndex)
    51  	indexBounds = append(indexBounds, upperIndex)
    52  	return samples[lowerIndex:upperIndex], indexBounds
    53  }
    54  
    55  // computeMean returns the weighted mean of @samples with @weights.
    56  // Special case of non-weighted mean is obtained by setting weights to constant 1-slice.
    57  func computeMean(samples []float64, weights []float64) (mean float64, err error) {
    58  	var totalPrice float64
    59  	var totalVolume float64
    60  	length := float64(len(samples))
    61  	if length == 0 {
    62  		return 0, nil
    63  	}
    64  	if length != float64(len(weights)) {
    65  		return 0, errors.New("computeMean: samples and weights not of same size")
    66  	}
    67  	for index, s := range samples {
    68  		totalPrice += s * math.Abs(weights[index])
    69  		totalVolume += math.Abs(weights[index])
    70  	}
    71  	mean = totalPrice / totalVolume
    72  	return
    73  }
    74  
    75  // ------------ Auxilliary functions for removeOutliers -------------
    76  
    77  func computeQuartiles(samples []float64) (Q1 float64, Q3 float64) {
    78  	sort.Float64s(samples)
    79  	var length = len(samples)
    80  	if length > 0 {
    81  		if length%2 == 0 {
    82  			Q1 = computeMedian(samples[0 : length/2])
    83  			Q3 = computeMedian(samples[length/2 : length])
    84  		} else {
    85  			Q1 = computeMedian(samples[0:int(float64(length/2))])
    86  			Q3 = computeMedian(samples[int(float64(length/2))+1 : length])
    87  		}
    88  	}
    89  	return
    90  }
    91  
    92  func computeMedian(samples []float64) (median float64) {
    93  	var length = len(samples)
    94  	if length > 0 {
    95  		sort.Float64s(samples)
    96  		if length%2 == 0 {
    97  			median = (samples[length/2-1] + samples[length/2]) / 2
    98  		} else {
    99  			median = samples[(length+1)/2-1]
   100  		}
   101  	}
   102  	return
   103  }