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

     1  package filters
     2  
     3  import (
     4  	"math"
     5  	"strconv"
     6  	"time"
     7  
     8  	"github.com/diadata-org/diadata/pkg/dia"
     9  	models "github.com/diadata-org/diadata/pkg/model"
    10  	log "github.com/sirupsen/logrus"
    11  )
    12  
    13  // FilterMA is the struct for a moving average filter implementing
    14  // the Filter interface.
    15  type FilterMA 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  	modified           bool
    25  	filterName         string
    26  	nativeDenomination bool
    27  	//max         float64
    28  	min float64
    29  }
    30  
    31  // NewFilterMA returns a moving average filter.
    32  // @currentTime is the begin time of the filtersBlock.
    33  func NewFilterMA(asset dia.Asset, exchange string, currentTime time.Time, memory int, nativeDenomination bool) *FilterMA {
    34  	filter := &FilterMA{
    35  		asset:              asset,
    36  		exchange:           exchange,
    37  		prices:             []float64{},
    38  		volumes:            []float64{},
    39  		currentTime:        currentTime,
    40  		memory:             memory,
    41  		filterName:         "MA" + strconv.Itoa(memory),
    42  		min:                -1,
    43  		nativeDenomination: nativeDenomination,
    44  	}
    45  	return filter
    46  }
    47  
    48  func (filter *FilterMA) Compute(trade dia.Trade) {
    49  	filter.compute(trade)
    50  }
    51  
    52  func (filter *FilterMA) compute(trade dia.Trade) {
    53  	filter.modified = true
    54  	if filter.lastTrade != (dia.Trade{}) {
    55  		if trade.Time.Before(filter.currentTime) {
    56  			log.Errorln("FilterMA: Ignoring Trade out of order ", filter.currentTime, trade.Time)
    57  			return
    58  		}
    59  	}
    60  	filter.fill(trade)
    61  	filter.lastTrade = trade
    62  }
    63  
    64  // fill fills up the 120 seconds slots with trades.
    65  func (filter *FilterMA) fill(trade dia.Trade) {
    66  	// filter.currentTime is the timestamp of the last filled trade.
    67  	// It is initialized with begin time of tradesblock upon creation of the filter.
    68  	diff := int(trade.Time.Sub(filter.currentTime).Seconds())
    69  	if diff >= 1 {
    70  		for diff >= 1 {
    71  			filter.processDataPoint(trade)
    72  			diff--
    73  		}
    74  	} else {
    75  		if diff == 0.0 {
    76  			if len(filter.prices) >= 1 {
    77  				/// Remove latest data point and update with newer
    78  				filter.prices = filter.prices[:len(filter.prices)-1]
    79  				filter.volumes = filter.volumes[:len(filter.volumes)-1]
    80  			}
    81  		}
    82  		filter.processDataPoint(trade)
    83  	}
    84  	filter.currentTime = time.Unix(int64(trade.Time.Unix()), 0)
    85  }
    86  
    87  func (filter *FilterMA) processDataPoint(trade dia.Trade) {
    88  	/// first remove extra value from buffer if already full
    89  	if len(filter.prices) >= filter.memory {
    90  		filter.prices = filter.prices[0 : filter.memory-1]
    91  		filter.volumes = filter.volumes[0 : filter.memory-1]
    92  	}
    93  	if !filter.nativeDenomination {
    94  		filter.prices = append(filter.prices, trade.EstimatedUSDPrice)
    95  	} else {
    96  		filter.prices = append(filter.prices, trade.Price)
    97  	}
    98  
    99  	filter.volumes = append([]float64{trade.Volume}, filter.volumes...)
   100  }
   101  
   102  func (filter *FilterMA) FinalCompute(t time.Time) float64 {
   103  	return filter.finalCompute(t)
   104  }
   105  
   106  func (filter *FilterMA) finalCompute(t time.Time) float64 {
   107  	if filter.lastTrade == (dia.Trade{}) {
   108  		return 0.0
   109  	}
   110  	filter.fill(filter.lastTrade)
   111  	var totalVolume float64
   112  	var totalPrice float64
   113  	for priceIndex, price := range filter.prices {
   114  		totalPrice += price * math.Abs(filter.volumes[priceIndex])
   115  		totalVolume += math.Abs(filter.volumes[priceIndex])
   116  	}
   117  	if filter.asset.Symbol == "USDT" || filter.asset.Symbol == "USDC" {
   118  		var nonweightedPrice float64
   119  		for _, price := range filter.prices {
   120  			nonweightedPrice += price
   121  		}
   122  	}
   123  	if totalVolume > 0 {
   124  		filter.value = totalPrice / totalVolume
   125  
   126  	}
   127  	if len(filter.prices) > 0 && len(filter.volumes) > 0 {
   128  		if !filter.nativeDenomination {
   129  			filter.prices = []float64{filter.lastTrade.EstimatedUSDPrice}
   130  		} else {
   131  			filter.prices = []float64{filter.lastTrade.Price}
   132  		}
   133  		filter.volumes = []float64{filter.lastTrade.Volume}
   134  	}
   135  	return filter.value
   136  }
   137  
   138  func (filter *FilterMA) FilterPointForBlock() *dia.FilterPoint {
   139  	return &dia.FilterPoint{
   140  		Asset: filter.asset,
   141  		Value: filter.value,
   142  		Name:  "MA" + strconv.Itoa(filter.memory),
   143  		Time:  filter.currentTime,
   144  	}
   145  }
   146  
   147  func (filter *FilterMA) filterPointForBlock() *dia.FilterPoint {
   148  	if filter.exchange != "" || filter.filterName != dia.FilterKing {
   149  		return nil
   150  	}
   151  	return &dia.FilterPoint{
   152  		Asset: filter.asset,
   153  		Value: filter.value,
   154  		Name:  "MA" + strconv.Itoa(filter.memory),
   155  		Time:  filter.currentTime,
   156  	}
   157  }
   158  
   159  func (filter *FilterMA) save(ds models.Datastore) error {
   160  	if filter.modified {
   161  		filter.modified = false
   162  		err := ds.SetFilter(filter.filterName, filter.asset, filter.exchange, filter.value, filter.currentTime)
   163  		if err != nil {
   164  			log.Errorln("FilterMA: Error:", err)
   165  		}
   166  		return err
   167  	}
   168  	return nil
   169  }