github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/exchange-scrapers/InfluxScraper.go (about) 1 package scrapers 2 3 import ( 4 "context" 5 "errors" 6 "strconv" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/diadata-org/diadata/pkg/dia" 12 "github.com/diadata-org/diadata/pkg/dia/helpers/kafkaHelper" 13 models "github.com/diadata-org/diadata/pkg/model" 14 "github.com/diadata-org/diadata/pkg/utils" 15 "github.com/segmentio/kafka-go" 16 ) 17 18 var ( 19 filtersblockDoneTopic int 20 ) 21 22 const ( 23 // One hour batches (notation in nanoseconds) 24 batchDuration = "121000000000" 25 tradesReadMeasurement = "tradesOld" 26 ) 27 28 type InfluxScraper struct { 29 // signaling channels for session initialization and finishing 30 //initDone chan nothing 31 shutdown chan nothing 32 shutdownDone chan nothing 33 // error handling; to read error or closed, first acquire read lock 34 // only cleanup method should hold write lock 35 errorLock sync.RWMutex 36 error error 37 run bool 38 // used to keep track of trading pairs that we subscribed to 39 pairScrapers map[string]*InfluxPairScraper 40 exchangeName string 41 chanTrades chan *dia.Trade 42 measurement string 43 batchDuration int64 44 db *models.DB 45 fbsDoneReader *kafka.Reader 46 } 47 48 // NewGateIOScraper returns a new GateIOScraper for the given pair 49 func NewInfluxScraper(scrape bool) *InfluxScraper { 50 51 log.Info("make new Influx scraper...") 52 db, err := models.NewDataStore() 53 if err != nil { 54 log.Fatal("datastore: ", err) 55 } 56 influxURL := utils.Getenv("INFLUXURL", "") 57 measurement := utils.Getenv("INFLUX_TRADES_MEASUREMENT", tradesReadMeasurement) 58 batchDurationEnv := utils.Getenv("BATCH_DURATION", batchDuration) 59 batchDurationInt, err := strconv.ParseInt(batchDurationEnv, 10, 64) 60 if err != nil { 61 log.Fatal("parse batch duration ", err) 62 } 63 db.SetInfluxClient(influxURL) 64 65 // Make a kafka reader that listens to ok from the filtersblockservice 66 filtersblockDoneTopic = kafkaHelper.TopicFiltersBlockDone 67 fbsDoneReader := kafkaHelper.NewReaderNextMessage(filtersblockDoneTopic) 68 69 s := &InfluxScraper{ 70 shutdown: make(chan nothing), 71 shutdownDone: make(chan nothing), 72 pairScrapers: make(map[string]*InfluxPairScraper), 73 exchangeName: "Influx", 74 error: nil, 75 chanTrades: make(chan *dia.Trade), 76 measurement: measurement, 77 db: db, 78 batchDuration: batchDurationInt, 79 fbsDoneReader: fbsDoneReader, 80 } 81 if scrape { 82 go s.mainLoop() 83 } 84 85 return s 86 } 87 88 // runs in a goroutine until s is closed 89 func (s *InfluxScraper) mainLoop() { 90 log.Info("enter main loop") 91 var timeInit time.Time 92 var timeInitInt int64 93 var err error 94 // Either take first timestamp from env var or take first trade time from DB. 95 timeInitString := utils.Getenv("TIME_INIT", "") 96 if timeInitString == "" { 97 log.Info("get first trade date...") 98 timeInit, err = s.db.GetFirstTradeDate(s.measurement) 99 if err != nil { 100 log.Error("get first trade date: ", err) 101 } 102 log.Info("got first trade date: ", timeInit) 103 } else { 104 timeInitInt, err = strconv.ParseInt(timeInitString, 10, 64) 105 if err != nil { 106 log.Fatal("parse init time: ", err) 107 } 108 timeInit = time.Unix(timeInitInt, 0) 109 } 110 111 // After @waitForFBSSeconds new trades are collected in order to successfully produce a new filtersblock. 112 waitForFBSSecondsString := utils.Getenv("WAIT_FOR_FBS_SECONDS", "30") 113 waitForFBSSeconds, err := strconv.ParseInt(waitForFBSSecondsString, 10, 64) 114 if err != nil { 115 log.Error("parse batch processing time string: ", err) 116 } 117 118 // final time is the last timestamp of trades exported from d2. 119 timeFinalString := utils.Getenv("TIME_FINAL", "1636618800") 120 timeFinalInt, err := strconv.ParseInt(timeFinalString, 10, 64) 121 if err != nil { 122 log.Fatal("parse final time: ", err) 123 } 124 timeFinal := time.Unix(timeFinalInt, 0) 125 starttime := timeInit 126 endtime := starttime.Add(time.Duration(s.batchDuration)) 127 128 // Initial run. 129 starttime, endtime = s.collectTrades(starttime, endtime) 130 131 // Context initiates the collection of new trades if the fbs does not 132 // return a signal after @waitForFBSSeconds seconds. 133 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(waitForFBSSeconds)*time.Second) 134 135 // Iterate until @timeFinal is reached. 136 for { 137 _, err := s.fbsDoneReader.ReadMessage(ctx) 138 if err != nil { 139 if errors.Is(err, ctx.Err()) { 140 log.Info("system stalled. collect new trades.") 141 starttime, endtime = s.collectTrades(starttime, endtime) 142 cancel() 143 ctx, cancel = context.WithTimeout(context.Background(), time.Duration(waitForFBSSeconds)*time.Second) 144 } else { 145 log.Error("read ok message from filtersblockservice: ", err.Error()) 146 } 147 } else if starttime.Before(timeFinal) { 148 starttime, endtime = s.collectTrades(starttime, endtime) 149 cancel() 150 ctx, cancel = context.WithTimeout(context.Background(), time.Duration(waitForFBSSeconds)*time.Second) 151 } else { 152 log.Info("done with iteration through trades. last timestamp: ", endtime) 153 time.Sleep(120 * time.Hour) 154 } 155 } 156 } 157 158 func (s *InfluxScraper) collectTrades(starttime time.Time, endtime time.Time) (newstarttime time.Time, newendtime time.Time) { 159 t0 := time.Now() 160 trades, err := s.db.GetOldTradesFromInflux(s.measurement, "", true, starttime, endtime) 161 if err != nil { 162 if strings.Contains(err.Error(), "no trades in time range") { 163 log.Warnf("%v: %v -- %v", err, starttime, endtime) 164 } else { 165 log.Error("get trades from influx: ", err) 166 return 167 } 168 } 169 log.Infof("got %d trades in time range %v -- %v", len(trades), starttime, endtime) 170 log.Info("time passed for get old trades: ", time.Since(t0)) 171 for i := range trades { 172 // Reverse Uniswap Trades. 173 var trade dia.Trade 174 if (trades[i].Source == "Uniswap" || trades[i].Source == "SushiSwap") && (trades[i].QuoteToken.Address == "0x0000000000000000000000000000000000000000" && trades[i].QuoteToken.Blockchain == "Ethereum") { 175 tSwapped, err := dia.SwapTrade(trades[i]) 176 if err == nil { 177 trade = tSwapped 178 } else { 179 continue 180 } 181 } else { 182 trade = trades[i] 183 } 184 s.chanTrades <- &trade 185 // log.Info("got trade: ", trades[i]) 186 } 187 log.Infof("fetched %d trades from influx.", len(trades)) 188 return endtime, endtime.Add(time.Duration(s.batchDuration)) 189 } 190 191 // ScrapePair returns a PairScraper that can be used to get trades for a single pair from 192 // this APIScraper 193 func (s *InfluxScraper) ScrapePair(pair dia.ExchangePair) (PairScraper, error) { 194 ps := &InfluxPairScraper{ 195 parent: s, 196 pair: pair, 197 } 198 return ps, nil 199 } 200 201 func (s *InfluxScraper) NormalizePair(pair dia.ExchangePair) (dia.ExchangePair, error) { 202 return dia.ExchangePair{}, nil 203 } 204 205 // FetchTickerData collects all available information on an asset traded on GateIO 206 func (s *InfluxScraper) FillSymbolData(symbol string) (asset dia.Asset, err error) { 207 asset.Symbol = symbol 208 return asset, nil 209 } 210 211 // FetchAvailablePairs returns a list with all available trade pairs 212 func (s *InfluxScraper) FetchAvailablePairs() (pairs []dia.ExchangePair, err error) { 213 return 214 } 215 216 func (s *InfluxScraper) Channel() chan *dia.Trade { 217 return s.chanTrades 218 } 219 220 func (s *InfluxScraper) Close() error { 221 // close the pair scraper channels 222 s.run = false 223 for _, pairScraper := range s.pairScrapers { 224 pairScraper.closed = true 225 } 226 227 close(s.shutdown) 228 <-s.shutdownDone 229 return nil 230 } 231 232 // GateIOPairScraper implements PairScraper for GateIO 233 type InfluxPairScraper struct { 234 parent *InfluxScraper 235 pair dia.ExchangePair 236 closed bool 237 } 238 239 // Close stops listening for trades of the pair associated with s 240 func (ps *InfluxPairScraper) Close() error { 241 ps.closed = true 242 return nil 243 } 244 245 // Error returns an error when the channel Channel() is closed 246 // and nil otherwise 247 func (ps *InfluxPairScraper) Error() error { 248 s := ps.parent 249 s.errorLock.RLock() 250 defer s.errorLock.RUnlock() 251 return s.error 252 } 253 254 // Pair returns the pair this scraper is subscribed to 255 func (ps *InfluxPairScraper) Pair() dia.ExchangePair { 256 return ps.pair 257 }