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

     1  package filters
     2  
     3  import (
     4  	"strconv"
     5  	"time"
     6  
     7  	"github.com/diadata-org/diadata/pkg/dia"
     8  	models "github.com/diadata-org/diadata/pkg/model"
     9  	log "github.com/sirupsen/logrus"
    10  )
    11  
    12  // FilterMAIR implements a trimmed moving average.
    13  // Outliers are eliminated using interquartile range.
    14  // see: https://en.wikipedia.org/wiki/Interquartile_range
    15  type FilterMAIR struct {
    16  	asset              dia.Asset
    17  	exchange           string
    18  	currentTime        time.Time
    19  	prices             []float64
    20  	volumes            []float64
    21  	lastTrade          dia.Trade
    22  	memory             int
    23  	value              float64
    24  	filterName         string
    25  	nativeDenomination bool
    26  	modified           bool
    27  }
    28  
    29  // NewFilterMAIR returns a FilterMAIR
    30  func NewFilterMAIR(asset dia.Asset, exchange string, currentTime time.Time, memory int, nativeDenomination bool) *FilterMAIR {
    31  	filter := &FilterMAIR{
    32  		asset:              asset,
    33  		exchange:           exchange,
    34  		prices:             []float64{},
    35  		volumes:            []float64{},
    36  		currentTime:        currentTime,
    37  		memory:             memory,
    38  		filterName:         "MAIR" + strconv.Itoa(memory),
    39  		nativeDenomination: nativeDenomination,
    40  	}
    41  	return filter
    42  }
    43  
    44  func (filter *FilterMAIR) Compute(trade dia.Trade) {
    45  	filter.compute(trade)
    46  }
    47  
    48  func (filter *FilterMAIR) compute(trade dia.Trade) {
    49  	filter.modified = true
    50  	if filter.lastTrade != (dia.Trade{}) {
    51  		if trade.Time.Before(filter.currentTime) {
    52  			log.Errorln("FilterMAIR: Ignoring Trade out of order ", filter.currentTime, trade.Time)
    53  			return
    54  		}
    55  	}
    56  	filter.fill(trade)
    57  	filter.lastTrade = trade
    58  }
    59  
    60  // fill fills up the 120 seconds slots with trades.
    61  func (filter *FilterMAIR) fill(trade dia.Trade) {
    62  	// filter.currentTime is the timestamp of the last filled trade.
    63  	// It is initialized with begin time of tradesblock upon creation of the filter.
    64  	diff := int(trade.Time.Sub(filter.currentTime).Seconds())
    65  	if diff >= 1 {
    66  		for diff >= 1 {
    67  			filter.processDataPoint(trade)
    68  			diff--
    69  		}
    70  	} else {
    71  		if diff == 0.0 {
    72  			if len(filter.prices) >= 1 {
    73  				/// Remove latest data point and update with newer
    74  				filter.prices = filter.prices[:len(filter.prices)-1]
    75  				filter.volumes = filter.volumes[:len(filter.volumes)-1]
    76  			}
    77  		}
    78  		filter.processDataPoint(trade)
    79  	}
    80  	filter.currentTime = time.Unix(int64(trade.Time.Unix()), 0)
    81  }
    82  
    83  func (filter *FilterMAIR) processDataPoint(trade dia.Trade) {
    84  	/// first remove extra value from buffer if already full
    85  	if len(filter.prices) >= filter.memory {
    86  		filter.prices = filter.prices[0 : filter.memory-1]
    87  		filter.volumes = filter.volumes[0 : filter.memory-1]
    88  	}
    89  	if !filter.nativeDenomination {
    90  		filter.prices = append(filter.prices, trade.EstimatedUSDPrice)
    91  	} else {
    92  		filter.prices = append(filter.prices, trade.Price)
    93  	}
    94  
    95  	filter.volumes = append(filter.volumes, trade.Volume)
    96  }
    97  
    98  func (filter *FilterMAIR) FinalCompute(t time.Time) float64 {
    99  	return filter.finalCompute(t)
   100  }
   101  
   102  func (filter *FilterMAIR) finalCompute(t time.Time) float64 {
   103  	if filter.lastTrade == (dia.Trade{}) {
   104  		return 0.0
   105  	}
   106  
   107  	if len(filter.prices) < 2 {
   108  		filter.value = filter.prices[0]
   109  		return filter.prices[0]
   110  	}
   111  
   112  	// Add the last trade again to compensate for the delay since measurement to EOB
   113  	// adopted behaviour from FilterMA
   114  	filter.processDataPoint(filter.lastTrade)
   115  	cleanPrices, bounds := removeOutliers(filter.prices)
   116  	mean, err := computeMean(cleanPrices, filter.volumes[bounds[0]:bounds[1]])
   117  	if err != nil {
   118  		return 0.0
   119  	}
   120  	filter.value = mean
   121  	// Reduce the filter values to the last recorded value for the next tradesblock.
   122  	if len(filter.prices) > 0 && len(filter.volumes) > 0 {
   123  		if !filter.nativeDenomination {
   124  			filter.prices = []float64{filter.lastTrade.EstimatedUSDPrice}
   125  		} else {
   126  			filter.prices = []float64{filter.lastTrade.Price}
   127  		}
   128  		filter.volumes = []float64{filter.lastTrade.Volume}
   129  	}
   130  	return filter.value
   131  }
   132  
   133  func (filter *FilterMAIR) FilterPointForBlock() *dia.FilterPoint {
   134  	return &dia.FilterPoint{
   135  		Asset: filter.asset,
   136  		Value: filter.value,
   137  		Name:  filter.filterName,
   138  		Time:  filter.currentTime,
   139  	}
   140  }
   141  
   142  func (filter *FilterMAIR) filterPointForBlock() *dia.FilterPoint {
   143  	if filter.exchange != "" || filter.filterName != dia.FilterKing {
   144  		return nil
   145  	}
   146  	return &dia.FilterPoint{
   147  		Asset: filter.asset,
   148  		Value: filter.value,
   149  		Name:  filter.filterName,
   150  		Time:  filter.currentTime,
   151  	}
   152  }
   153  
   154  func (filter *FilterMAIR) save(ds models.Datastore) error {
   155  	if filter.modified {
   156  		filter.modified = false
   157  		err := ds.SetFilter(filter.filterName, filter.asset, filter.exchange, filter.value, filter.currentTime)
   158  		if err != nil {
   159  			log.Errorln("FilterMAIR: Error:", err)
   160  		}
   161  
   162  		// Additionally, the price across exchanges is saved in influx as a quotation.
   163  		// This price is used for the estimation of quote tokens' prices in the tradesBlockService.
   164  		if filter.exchange == "" {
   165  			err = ds.SetAssetPriceUSD(filter.asset, filter.value, filter.currentTime)
   166  			if err != nil {
   167  				log.Errorln("FilterMAIR: Error:", err)
   168  			}
   169  		}
   170  		return err
   171  	}
   172  	return nil
   173  }