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

     1  package tradesEstimationService
     2  
     3  import (
     4  	"errors"
     5  	"math"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/diadata-org/diadata/pkg/dia"
    10  	models "github.com/diadata-org/diadata/pkg/model"
    11  	"github.com/sirupsen/logrus"
    12  )
    13  
    14  type nothing struct{}
    15  
    16  var log *logrus.Logger
    17  
    18  func init() {
    19  	log = logrus.New()
    20  }
    21  
    22  var (
    23  	stablecoins = map[string]interface{}{
    24  		"USDC": "",
    25  		"USDT": "",
    26  		"TUSD": "",
    27  		"DAI":  "",
    28  		"PAX":  "",
    29  		"BUSD": "",
    30  	}
    31  	tol = float64(0.1)
    32  )
    33  
    34  const (
    35  	priceFrame = 1000 * 120
    36  )
    37  
    38  type pricetime struct {
    39  	Price     float64
    40  	Timestamp time.Time
    41  }
    42  
    43  type TradesEstimationService struct {
    44  	shutdown     chan nothing
    45  	shutdownDone chan nothing
    46  	chanTrades   chan *dia.Trade
    47  	errorLock    sync.RWMutex
    48  	error        error
    49  	closed       bool
    50  	started      bool
    51  	priceCache   map[dia.Asset]pricetime
    52  	datastore    models.Datastore
    53  }
    54  
    55  func NewTradesEstimationService(datastore models.Datastore) *TradesEstimationService {
    56  	s := &TradesEstimationService{
    57  		shutdown:     make(chan nothing),
    58  		shutdownDone: make(chan nothing),
    59  		chanTrades:   make(chan *dia.Trade),
    60  		error:        nil,
    61  		started:      false,
    62  		priceCache:   make(map[dia.Asset]pricetime),
    63  		datastore:    datastore,
    64  	}
    65  	go s.mainLoop()
    66  	return s
    67  }
    68  
    69  // runs in a goroutine until s is closed
    70  func (s *TradesEstimationService) mainLoop() {
    71  	for {
    72  		select {
    73  		case <-s.shutdown:
    74  			log.Println("TradesBlockService shutting down")
    75  			s.cleanup(nil)
    76  			return
    77  		case t := <-s.chanTrades:
    78  			s.process(*t)
    79  		}
    80  	}
    81  }
    82  
    83  func (s *TradesEstimationService) process(t dia.Trade) {
    84  
    85  	var verifiedTrade bool
    86  	var price float64
    87  	var err error
    88  
    89  	// Price estimation can only be done for verified pairs.
    90  	if t.VerifiedPair {
    91  		if t.BaseToken.Address == "840" && t.BaseToken.Blockchain == dia.FIAT {
    92  			// All prices are measured in US-Dollar, so just price for base token == USD
    93  			t.EstimatedUSDPrice = t.Price
    94  			verifiedTrade = true
    95  		} else {
    96  			// Check if price cache is still valid:
    97  			_, ok := s.priceCache[t.BaseToken]
    98  			if ok && t.Time.Sub(s.priceCache[t.BaseToken].Timestamp) < time.Duration(priceFrame*time.Millisecond) {
    99  				price = s.priceCache[t.BaseToken].Price
   100  			} else {
   101  				// Look for historic price of base token at trade time...
   102  				price, err = s.datastore.GetAssetPriceUSD(t.BaseToken, dia.CRYPTO_ZERO_UNIX_TIME, t.Time)
   103  				s.priceCache[t.BaseToken] = pricetime{
   104  					Price:     price,
   105  					Timestamp: t.Time,
   106  				}
   107  			}
   108  			if err != nil {
   109  				log.Errorf("Cannot use trade %s. Can't find quotation for base token.", t.Pair)
   110  			} else {
   111  				if price > 0.0 {
   112  					t.EstimatedUSDPrice = t.Price * price
   113  					if t.EstimatedUSDPrice > 0 {
   114  						verifiedTrade = true
   115  					}
   116  				}
   117  			}
   118  		}
   119  	}
   120  
   121  	// If estimated price for stablecoin diverges too much ignore trade.
   122  	if _, ok := stablecoins[t.Symbol]; ok {
   123  		if math.Abs(t.EstimatedUSDPrice-1) > tol {
   124  			log.Errorf("price for stablecoin %s diverges by %v", t.Symbol, math.Abs(t.EstimatedUSDPrice-1))
   125  			verifiedTrade = false
   126  		}
   127  	}
   128  
   129  	if verifiedTrade {
   130  		err := s.datastore.SaveTradeInflux(&t)
   131  		if err != nil {
   132  			log.Error(err)
   133  		}
   134  	}
   135  }
   136  
   137  func (s *TradesEstimationService) ProcessTrade(trade *dia.Trade) {
   138  	s.chanTrades <- trade
   139  }
   140  
   141  func (s *TradesEstimationService) Close() error {
   142  	if s.closed {
   143  		return errors.New("TradesBlockService: Already closed")
   144  	}
   145  	close(s.shutdown)
   146  	<-s.shutdownDone
   147  	return s.error
   148  }
   149  
   150  // must only be called from mainLoop
   151  func (s *TradesEstimationService) cleanup(err error) {
   152  	s.errorLock.Lock()
   153  	defer s.errorLock.Unlock()
   154  	if err != nil {
   155  		s.error = err
   156  	}
   157  	s.closed = true
   158  	close(s.shutdownDone) // signal that shutdown is complete
   159  }