github.com/diadata-org/diadata@v1.4.593/pkg/model/db.go (about)

     1  package models
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/diadata-org/diadata/pkg/dia/helpers/db"
    11  
    12  	"github.com/diadata-org/diadata/pkg/dia"
    13  	"github.com/go-redis/redis"
    14  	clientInfluxdb "github.com/influxdata/influxdb1-client/v2"
    15  )
    16  
    17  type Datastore interface {
    18  	SetInfluxClient(url string)
    19  	SetBatchFiatPriceInflux(fqs []*FiatQuotation) error
    20  	SetSingleFiatPriceRedis(fiatQuotation *FiatQuotation) error
    21  
    22  	GetLatestSupply(string, *RelDB) (*dia.Supply, error)
    23  	GetSupplyCache(asset dia.Asset) (dia.Supply, error)
    24  	GetSupply(string, time.Time, time.Time, *RelDB) ([]dia.Supply, error)
    25  	SetSupply(supply *dia.Supply) error
    26  	GetSupplyInflux(dia.Asset, time.Time, time.Time) ([]dia.Supply, error)
    27  
    28  	SetDiaTotalSupply(totalSupply float64) error
    29  	GetDiaTotalSupply() (float64, error)
    30  	SetDiaCirculatingSupply(circulatingSupply float64) error
    31  	GetDiaCirculatingSupply() (float64, error)
    32  
    33  	GetSymbols(exchange string) ([]string, error)
    34  	GetLastTradeTimeForExchange(asset dia.Asset, exchange string) (*time.Time, error)
    35  	SetLastTradeTimeForExchange(asset dia.Asset, exchange string, t time.Time) error
    36  	GetFirstTradeDate(table string) (time.Time, error)
    37  	SaveTradeInflux(t *dia.Trade) error
    38  	SaveTradeInfluxToTable(t *dia.Trade, table string) error
    39  	GetTradeInflux(dia.Asset, string, time.Time, time.Duration) (*dia.Trade, error)
    40  	SaveFilterInflux(filter string, asset dia.Asset, exchange string, value float64, t time.Time) error
    41  	GetFilterAllExchanges(filter string, address string, blockchain string, starttime time.Time, endtime time.Time) ([]AssetQuotation, error)
    42  	GetLastTrades(asset dia.Asset, exchange string, timestamp time.Time, maxTrades int, fullAsset bool) ([]dia.Trade, error)
    43  	GetAllTrades(t time.Time, maxTrades int) ([]dia.Trade, error)
    44  
    45  	GetTradesByExchangesFull(asset dia.Asset, baseAssets []dia.Asset, exchanges []string, returnBasetoken bool, startTime, endTime time.Time, maxTrades int) ([]dia.Trade, error)
    46  	GetTradesByExchangesAndBaseAssets(asset dia.Asset, baseassets []dia.Asset, exchanges []string, startTime time.Time, endTime time.Time, maxTrades int) ([]dia.Trade, error)
    47  
    48  	GetTradesByExchangesBatchedFull(asset dia.Asset, baseAssets []dia.Asset, exchanges []string, returnBasetoken bool, startTimes, endTimes []time.Time, maxTrades int) ([]dia.Trade, error)
    49  	GetTradesByExchangesBatched(asset dia.Asset, baseAssets []dia.Asset, exchanges []string, startTimes, endTimes []time.Time, maxTrades int) ([]dia.Trade, error)
    50  	GetxcTradesByExchangesBatched(quoteassets []dia.Asset, exchanges []string, startTimes []time.Time, endTimes []time.Time) ([]dia.Trade, error)
    51  
    52  	GetTradesByExchangepairs(exchangepairMap map[string][]dia.Pair, exchangepoolMap map[string][]string, starttime time.Time, endtime time.Time) ([]dia.Trade, error)
    53  	GetTradesByFeedSelection(feedselection []dia.FeedSelection, starttimes []time.Time, endtimes []time.Time, limit int) ([]dia.Trade, error)
    54  	GetAggregatedFeedSelection(feedselection []dia.FeedSelection, starttime time.Time, endtime time.Time, tradeVolumeThreshold float64) ([]dia.FeedSelectionAggregated, error)
    55  
    56  	GetActiveExchangesAndPairs(address string, blockchain string, numTradesThreshold int64, starttime time.Time, endtime time.Time) (map[string][]dia.Pair, map[string]int64, error)
    57  	GetOldTradesFromInflux(table string, exchange string, verified bool, timeInit, timeFinal time.Time) ([]dia.Trade, error)
    58  	CopyInfluxMeasurements(dbOrigin string, dbDestination string, tableOrigin string, tableDestination string, timeInit time.Time, timeFinal time.Time) (int64, error)
    59  
    60  	Flush() error
    61  	ExecuteRedisPipe() error
    62  	FlushRedisPipe() error
    63  	GetFilterPoints(filter string, exchange string, symbol string, scale string, starttime time.Time, endtime time.Time) (*Points, error)
    64  	GetFilterPointsAsset(filter string, exchange string, address string, blockchain string, starttime time.Time, endtime time.Time) (*Points, error)
    65  	SetFilter(filterName string, asset dia.Asset, exchange string, value float64, t time.Time) error
    66  	GetLastPriceBefore(asset dia.Asset, filter string, exchange string, timestamp time.Time) (Price, error)
    67  	SetAvailablePairs(exchange string, pairs []dia.ExchangePair) error
    68  	GetAvailablePairs(exchange string) ([]dia.ExchangePair, error)
    69  	SetCurrencyChange(cc *Change) error
    70  	GetCurrencyChange() (*Change, error)
    71  
    72  	// Volume methods
    73  	GetVolumeInflux(asset dia.Asset, exchange string, starttime time.Time, endtime time.Time) (*float64, error)
    74  	Get24HoursAssetVolume(asset dia.Asset) (*float64, error)
    75  	Get24HoursExchangeVolume(exchange string) (*float64, error)
    76  	GetNumTradesExchange24H(exchange string) (int64, error)
    77  	GetNumTrades(exchange string, address string, blockchain string, starttime time.Time, endtime time.Time) (int64, error)
    78  	GetNumTradesSeries(asset dia.Asset, exchange string, starttime time.Time, endtime time.Time, grouping string) ([]int64, error)
    79  	GetVolumesAllExchanges(asset dia.Asset, starttime time.Time, endtime time.Time) (exchVolumes dia.ExchangeVolumesList, err error)
    80  	GetExchangePairVolumes(asset dia.Asset, starttime time.Time, endtime time.Time, threshold float64) (map[string][]dia.PairVolume, error)
    81  	GetExchangePairVolume(ep dia.ExchangePair, pooladdress string, starttime time.Time, endtime time.Time, threshold float64) (float64, int64, error)
    82  
    83  	// New Asset pricing methods: 23/02/2021
    84  	SetAssetPriceUSD(asset dia.Asset, price float64, timestamp time.Time) error
    85  	GetAssetPriceUSD(asset dia.Asset, starttime time.Time, endtime time.Time) (float64, error)
    86  	GetAssetPriceUSDLatest(asset dia.Asset) (price float64, err error)
    87  	SetAssetQuotation(quotation *AssetQuotation) error
    88  	GetAssetQuotation(asset dia.Asset, starttime time.Time, endtime time.Time) (*AssetQuotation, error)
    89  	GetAssetQuotations(asset dia.Asset, starttime time.Time, endtime time.Time) ([]AssetQuotation, error)
    90  	GetAssetQuotationLatest(asset dia.Asset, starttime time.Time) (*AssetQuotation, error)
    91  	GetSortedAssetQuotations(assets []dia.Asset) ([]AssetQuotation, error)
    92  	AddAssetQuotationsToBatch(quotations []*AssetQuotation) error
    93  	SetAssetQuotationCache(quotation *AssetQuotation, check bool) (bool, error)
    94  	GetAssetQuotationCache(asset dia.Asset) (*AssetQuotation, error)
    95  	GetAssetPriceUSDCache(asset dia.Asset) (price float64, err error)
    96  	GetTopAssetByMcap(symbol string, relDB *RelDB) (dia.Asset, error)
    97  	GetTopAssetByVolume(symbol string, relDB *RelDB) (topAsset dia.Asset, err error)
    98  	GetAssetsWithVOLInflux(timeInit time.Time) ([]dia.Asset, error)
    99  	GetOldestQuotation(asset dia.Asset) (AssetQuotation, error)
   100  
   101  	// DEX Pool  methods
   102  	SavePoolInflux(p dia.Pool) error
   103  	GetPoolInflux(poolAddress string, starttime time.Time, endtime time.Time) ([]dia.Pool, error)
   104  	GetPoolLiquiditiesUSD(p *dia.Pool, priceCache map[string]float64)
   105  
   106  	// Market Measures
   107  	GetAssetsMarketCap(asset dia.Asset) (float64, error)
   108  
   109  	// Interest rates' methods
   110  	SetInterestRate(ir *InterestRate) error
   111  	GetInterestRate(symbol, date string) (*InterestRate, error)
   112  	GetInterestRateRange(symbol, dateInit, dateFinal string) ([]*InterestRate, error)
   113  	GetRatesMeta() (RatesMeta []InterestRateMeta, err error)
   114  	GetCompoundedIndex(symbol string, date time.Time, daysPerYear int, rounding int) (*InterestRate, error)
   115  	GetCompoundedIndexRange(symbol string, dateInit, dateFinal time.Time, daysPerYear int, rounding int) ([]*InterestRate, error)
   116  	GetCompoundedAvg(symbol string, date time.Time, calDays, daysPerYear int, rounding int) (*InterestRate, error)
   117  	GetCompoundedAvgRange(symbol string, dateInit, dateFinal time.Time, calDays, daysPerYear int, rounding int) ([]*InterestRate, error)
   118  	GetCompoundedAvgDIARange(symbol string, dateInit, dateFinal time.Time, calDays, daysPerYear int, rounding int) ([]*InterestRate, error)
   119  
   120  	// Foreign quotation methods
   121  	SaveForeignQuotationInflux(fq ForeignQuotation) error
   122  	GetForeignQuotationInflux(symbol, source string, timestamp time.Time) (ForeignQuotation, error)
   123  	GetForeignPriceYesterday(symbol, source string) (float64, error)
   124  	GetForeignSymbolsInflux(source string) ([]string, error)
   125  
   126  	SetVWAPFirefly(foreignName string, value float64, timestamp time.Time) error
   127  	GetVWAPFirefly(foreignName string, starttime time.Time, endtime time.Time) ([]float64, []time.Time, error)
   128  
   129  	SaveIndexEngineTimeInflux(map[string]string, map[string]interface{}, time.Time) error
   130  	GetBenchmarkedIndexValuesInflux(string, time.Time, time.Time) (BenchmarkedIndex, error)
   131  	// Token methods
   132  	// SaveTokenDetailInflux(tk Token) error
   133  	// GetTokenDetailInflux(symbol, source string, timestamp time.Time) (Token, error)
   134  	// GetCurentTotalSupply(symbol, source string) (float64, error)
   135  
   136  	// Stock methods
   137  	SetStockQuotation(sq StockQuotation) error
   138  	GetStockQuotation(source string, symbol string, timeInit time.Time, timeFinal time.Time) ([]StockQuotation, error)
   139  	GetStockSymbols() (map[Stock]string, error)
   140  
   141  	SetOracleConfigCache(dia.OracleConfig) error
   142  	GetOracleConfigCache(string) (dia.OracleConfig, error)
   143  }
   144  
   145  const (
   146  	influxMaxPointsInBatch = 5000
   147  	// timeOutRedisOneBlock   = 60 * 3 * time.Second
   148  )
   149  
   150  type DB struct {
   151  	redisClient         *redis.Client
   152  	redisPipe           redis.Pipeliner
   153  	influxClient        clientInfluxdb.Client
   154  	influxBatchPoints   clientInfluxdb.BatchPoints
   155  	influxPointsInBatch int
   156  }
   157  
   158  var EscapeReplacer = strings.NewReplacer("\n", `\n`)
   159  
   160  const (
   161  	influxDbName                      = "dia"
   162  	influxDbTradesTable               = "trades"
   163  	influxDbFiltersTable              = "filters"
   164  	influxDbFiatQuotationsTable       = "fiat"
   165  	influxDbSupplyTable               = "supplies"
   166  	influxDbDEXPoolTable              = "DEXPools"
   167  	influxDbStockQuotationsTable      = "stockquotations"
   168  	influxDBAssetQuotationsTable      = "assetQuotations"
   169  	influxDbBenchmarkedIndexTableName = "benchmarkedIndexValues"
   170  	influxDbVwapFireflyTable          = "vwapFirefly"
   171  
   172  	influxDBDefaultURL = "http://influxdb:8086"
   173  )
   174  
   175  // queryInfluxDB convenience function to query the database.
   176  func queryInfluxDB(clnt clientInfluxdb.Client, cmd string) (res []clientInfluxdb.Result, err error) {
   177  	res, err = queryInfluxDBName(clnt, influxDbName, cmd)
   178  	return
   179  }
   180  
   181  // queryInfluxDBName is a wrapper for queryInfluxDB that allows for queries on the database with name @dbName.
   182  func queryInfluxDBName(clnt clientInfluxdb.Client, dbName string, cmd string) (res []clientInfluxdb.Result, err error) {
   183  	q := clientInfluxdb.Query{
   184  		Command:  cmd,
   185  		Database: dbName,
   186  	}
   187  	if response, err := clnt.Query(q); err == nil {
   188  		if response.Error() != nil {
   189  			return res, response.Error()
   190  		}
   191  		res = response.Results
   192  	} else {
   193  		return res, err
   194  	}
   195  	return res, nil
   196  }
   197  
   198  func NewDataStore() (*DB, error) {
   199  	return NewDataStoreWithOptions(true, true)
   200  }
   201  func NewInfluxDataStore() (*DB, error) {
   202  	return NewDataStoreWithOptions(false, true)
   203  }
   204  
   205  func NewRedisDataStore() (*DB, error) {
   206  	return NewDataStoreWithOptions(true, false)
   207  }
   208  
   209  func NewDataStoreWithoutInflux() (*DB, error) {
   210  	return NewDataStoreWithOptions(true, false)
   211  }
   212  
   213  func NewDataStoreWithoutRedis() (*DB, error) {
   214  	return NewDataStoreWithOptions(false, true)
   215  }
   216  
   217  func NewDataStoreWithOptions(withRedis bool, withInflux bool) (*DB, error) {
   218  	var (
   219  		influxClient      clientInfluxdb.Client
   220  		influxBatchPoints clientInfluxdb.BatchPoints
   221  		redisClient       *redis.Client
   222  		redisPipe         redis.Pipeliner
   223  	)
   224  
   225  	if withRedis {
   226  		redisClient = db.GetRedisClient()
   227  		redisPipe = redisClient.TxPipeline()
   228  	}
   229  	if withInflux {
   230  		var err error
   231  		influxClient = db.GetInfluxClient(influxDBDefaultURL)
   232  		influxBatchPoints = createBatchInflux()
   233  		_, err = queryInfluxDB(influxClient, fmt.Sprintf("CREATE DATABASE %s", influxDbName))
   234  		if err != nil {
   235  			log.Errorln("queryInfluxDB CREATE DATABASE", err)
   236  		}
   237  	}
   238  	return &DB{redisClient, redisPipe, influxClient, influxBatchPoints, 0}, nil
   239  }
   240  
   241  // SetInfluxClient resets influx's client url to @url.
   242  func (datastore *DB) SetInfluxClient(url string) {
   243  	datastore.influxClient = db.GetInfluxClient(url)
   244  }
   245  
   246  func createBatchInflux() clientInfluxdb.BatchPoints {
   247  	bp, err := clientInfluxdb.NewBatchPoints(clientInfluxdb.BatchPointsConfig{
   248  		Database:  influxDbName,
   249  		Precision: "ns",
   250  	})
   251  	if err != nil {
   252  		log.Errorln("NewBatchPoints", err)
   253  	}
   254  	return bp
   255  }
   256  
   257  func (datastore *DB) Flush() error {
   258  	var err error
   259  	if datastore.influxBatchPoints != nil {
   260  		err = datastore.WriteBatchInflux()
   261  	}
   262  	return err
   263  }
   264  
   265  func (datastore *DB) WriteBatchInflux() (err error) {
   266  	err = datastore.influxClient.Write(datastore.influxBatchPoints)
   267  	if err != nil {
   268  		log.Errorln("WriteBatchInflux", err)
   269  		return
   270  	}
   271  	datastore.influxPointsInBatch = 0
   272  	datastore.influxBatchPoints = createBatchInflux()
   273  	return
   274  }
   275  
   276  func (datastore *DB) addPoint(pt *clientInfluxdb.Point) {
   277  	datastore.influxBatchPoints.AddPoint(pt)
   278  	datastore.influxPointsInBatch++
   279  
   280  	if datastore.influxPointsInBatch >= influxMaxPointsInBatch {
   281  		err := datastore.WriteBatchInflux()
   282  		if err != nil {
   283  			log.Error("write influx batch: ", err)
   284  		}
   285  	}
   286  }
   287  
   288  func (datastore *DB) ExecuteRedisPipe() (err error) {
   289  	// TO DO: Handle first return value for read requests.
   290  	_, err = datastore.redisPipe.Exec()
   291  	return
   292  }
   293  
   294  func (datastore *DB) FlushRedisPipe() error {
   295  	return datastore.redisPipe.Discard()
   296  }
   297  
   298  // CopyInfluxMeasurements copies entries from measurement @tableOrigin in database @dbOrigin into @tableDestination in database @dbDestination.
   299  // It takes into account all data ranging from @timeInit until @timeFinal.
   300  func (datastore *DB) CopyInfluxMeasurements(dbOrigin string, dbDestination string, tableOrigin string, tableDestination string, timeInit time.Time, timeFinal time.Time) (numCopiedRows int64, err error) {
   301  	queryString := "select * into %s..%s from %s..%s where time>%d and time<=%d group by *"
   302  	query := fmt.Sprintf(queryString, dbDestination, tableDestination, dbOrigin, tableOrigin, timeInit.UnixNano(), timeFinal.UnixNano())
   303  	res, err := queryInfluxDB(datastore.influxClient, query)
   304  	if err != nil {
   305  		return
   306  	}
   307  	if len(res) > 0 && len(res[0].Series) > 0 {
   308  		numCopiedRows, err = res[0].Series[0].Values[0][1].(json.Number).Int64()
   309  		if err != nil {
   310  			return
   311  		}
   312  	}
   313  	return
   314  }
   315  
   316  func (datastore *DB) SetVWAPFirefly(foreignName string, value float64, timestamp time.Time) error {
   317  	tags := map[string]string{
   318  		"foreignName": EscapeReplacer.Replace(foreignName),
   319  	}
   320  	fields := map[string]interface{}{
   321  		"value": value,
   322  	}
   323  	pt, err := clientInfluxdb.NewPoint(influxDbVwapFireflyTable, tags, fields, timestamp)
   324  	if err != nil {
   325  		log.Errorln("new filter influx:", err)
   326  	} else {
   327  		datastore.addPoint(pt)
   328  	}
   329  
   330  	err = datastore.WriteBatchInflux()
   331  	if err != nil {
   332  		log.Errorln("Write influx batch: ", err)
   333  	}
   334  
   335  	return err
   336  }
   337  
   338  func (datastore *DB) GetVWAPFirefly(foreignName string, starttime time.Time, endtime time.Time) (values []float64, timestamps []time.Time, err error) {
   339  
   340  	influxQuery := "SELECT value FROM %s WHERE time > %d AND time <= %d AND foreignName = '%s' ORDER BY DESC"
   341  	q := fmt.Sprintf(influxQuery, influxDbVwapFireflyTable, starttime.UnixNano(), endtime.UnixNano(), foreignName)
   342  	res, err := queryInfluxDB(datastore.influxClient, q)
   343  	if err != nil {
   344  		return
   345  	}
   346  	if len(res) > 0 && len(res[0].Series) > 0 {
   347  		for i := 0; i < len(res[0].Series[0].Values); i++ {
   348  			var value float64
   349  			var timestamp time.Time
   350  			timestamp, err = time.Parse(time.RFC3339, res[0].Series[0].Values[i][0].(string))
   351  			if err != nil {
   352  				return
   353  			}
   354  			value, err = res[0].Series[0].Values[i][1].(json.Number).Float64()
   355  			if err != nil {
   356  				return
   357  			}
   358  
   359  			values = append(values, value)
   360  			timestamps = append(timestamps, timestamp)
   361  		}
   362  	} else {
   363  		err = errors.New("no data available in given time range")
   364  		return
   365  	}
   366  	return
   367  }