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 }