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 }