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  }