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

     1  package models
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/diadata-org/diadata/pkg/dia"
    13  	clientInfluxdb "github.com/influxdata/influxdb1-client/v2"
    14  )
    15  
    16  // SaveTradeInflux stores a trade in influx. Flushed when more than maxPoints in batch.
    17  // Wrapper around SaveTradeInfluxToTable.
    18  func (datastore *DB) SaveTradeInflux(t *dia.Trade) error {
    19  	return datastore.SaveTradeInfluxToTable(t, influxDbTradesTable)
    20  }
    21  
    22  // SaveTradeInfluxToTable stores a trade in influx into @table.
    23  // Flushed when more than maxPoints in batch.
    24  func (datastore *DB) SaveTradeInfluxToTable(t *dia.Trade, table string) error {
    25  
    26  	// Create a point and add to batch
    27  	tags := map[string]string{
    28  		"symbol":               EscapeReplacer.Replace(t.Symbol),
    29  		"pair":                 t.Pair,
    30  		"exchange":             t.Source,
    31  		"verified":             strconv.FormatBool(t.VerifiedPair),
    32  		"quotetokenaddress":    t.QuoteToken.Address,
    33  		"basetokenaddress":     t.BaseToken.Address,
    34  		"quotetokenblockchain": t.QuoteToken.Blockchain,
    35  		"basetokenblockchain":  t.BaseToken.Blockchain,
    36  		"pooladdress":          t.PoolAddress,
    37  	}
    38  	fields := map[string]interface{}{
    39  		"price":             t.Price,
    40  		"volume":            t.Volume,
    41  		"estimatedUSDPrice": t.EstimatedUSDPrice,
    42  		"foreignTradeID":    t.ForeignTradeID,
    43  	}
    44  
    45  	pt, err := clientInfluxdb.NewPoint(table, tags, fields, t.Time)
    46  	if err != nil {
    47  		log.Errorln("NewTradeInflux:", err)
    48  	} else {
    49  		datastore.addPoint(pt)
    50  	}
    51  
    52  	return err
    53  }
    54  
    55  // GetTradeInflux returns the latest trade of @asset on @exchange before @timestamp in the time-range [endtime-window, endtime].
    56  func (datastore *DB) GetTradeInflux(asset dia.Asset, exchange string, endtime time.Time, window time.Duration) (*dia.Trade, error) {
    57  	starttime := endtime.Add(-window)
    58  	retval := dia.Trade{}
    59  	var q string
    60  	if exchange != "" {
    61  		queryString := "SELECT estimatedUSDPrice,\"exchange\",foreignTradeID,\"pair\",price,\"symbol\",volume FROM %s WHERE quotetokenaddress='%s' AND quotetokenblockchain='%s' AND exchange='%s' AND time >= %d AND time < %d ORDER BY DESC LIMIT 1"
    62  		q = fmt.Sprintf(queryString, influxDbTradesTable, asset.Address, asset.Blockchain, exchange, starttime.UnixNano(), endtime.UnixNano())
    63  	} else {
    64  		queryString := "SELECT estimatedUSDPrice,\"exchange\",foreignTradeID,\"pair\",price,\"symbol\",volume FROM %s WHERE quotetokenaddress='%s' AND quotetokenblockchain='%s' AND time >= %d AND time < %d ORDER BY DESC LIMIT 1"
    65  		q = fmt.Sprintf(queryString, influxDbTradesTable, asset.Address, asset.Blockchain, starttime.UnixNano(), endtime.UnixNano())
    66  	}
    67  
    68  	/// TODO
    69  	res, err := queryInfluxDB(datastore.influxClient, q)
    70  	if err != nil {
    71  		return &retval, err
    72  	}
    73  	if len(res) > 0 && len(res[0].Series) > 0 {
    74  		for i := 0; i < len(res[0].Series[0].Values); i++ {
    75  			retval.Time, err = time.Parse(time.RFC3339, res[0].Series[0].Values[i][0].(string))
    76  			if err != nil {
    77  				return &retval, err
    78  			}
    79  			retval.EstimatedUSDPrice, err = res[0].Series[0].Values[i][1].(json.Number).Float64()
    80  			if err != nil {
    81  				return &retval, err
    82  			}
    83  			retval.Source = res[0].Series[0].Values[i][2].(string)
    84  			retval.ForeignTradeID = res[0].Series[0].Values[i][3].(string)
    85  			retval.Pair = res[0].Series[0].Values[i][4].(string)
    86  			retval.Price, err = res[0].Series[0].Values[i][5].(json.Number).Float64()
    87  			if err != nil {
    88  				return &retval, err
    89  			}
    90  			retval.Symbol = res[0].Series[0].Values[i][6].(string)
    91  			retval.Volume, err = res[0].Series[0].Values[i][7].(json.Number).Float64()
    92  			if err != nil {
    93  				return &retval, err
    94  			}
    95  		}
    96  	} else {
    97  		return &retval, errors.New("parsing trade from database")
    98  	}
    99  	return &retval, nil
   100  }
   101  
   102  // GetOldTradesFromInflux returns all recorded trades from @table done on @exchange between @timeInit and @timeFinal
   103  // where the time interval is closed on the left and open on the right side.
   104  // If @exchange is empty, trades across all exchanges are returned.
   105  // If @verified is true, address and blockchain are also parsed for both assets.
   106  func (datastore *DB) GetOldTradesFromInflux(table string, exchange string, verified bool, timeInit, timeFinal time.Time) ([]dia.Trade, error) {
   107  	allTrades := []dia.Trade{}
   108  	var queryString, query, addQueryString string
   109  	if verified {
   110  		addQueryString = ",\"quotetokenaddress\",\"basetokenaddress\",\"quotetokenblockchain\",\"basetokenblockchain\",\"verified\""
   111  	}
   112  	if exchange == "" {
   113  		queryString = "SELECT estimatedUSDPrice,\"exchange\",foreignTradeID,\"pair\",price,\"symbol\",volume" +
   114  			addQueryString +
   115  			" FROM %s WHERE time>=%d and time<%d order by asc"
   116  		query = fmt.Sprintf(queryString, table, timeInit.UnixNano(), timeFinal.UnixNano())
   117  	} else {
   118  		queryString = "SELECT estimatedUSDPrice,\"exchange\",foreignTradeID,\"pair\",price,\"symbol\",volume" +
   119  			addQueryString +
   120  			" FROM %s WHERE exchange='%s' and time>=%d and time<%d order by asc"
   121  		query = fmt.Sprintf(queryString, table, exchange, timeInit.UnixNano(), timeFinal.UnixNano())
   122  	}
   123  	res, err := queryInfluxDB(datastore.influxClient, query)
   124  	if err != nil {
   125  		log.Error("influx query: ", err)
   126  		return allTrades, err
   127  	}
   128  
   129  	log.Info("query: ", query)
   130  
   131  	if len(res) > 0 && len(res[0].Series) > 0 {
   132  		for i := 0; i < len(res[0].Series[0].Values); i++ {
   133  			var trade dia.Trade
   134  			trade.Time, err = time.Parse(time.RFC3339, res[0].Series[0].Values[i][0].(string))
   135  			if err != nil {
   136  				return allTrades, err
   137  			}
   138  			trade.EstimatedUSDPrice, err = res[0].Series[0].Values[i][1].(json.Number).Float64()
   139  			if err != nil {
   140  				return allTrades, err
   141  			}
   142  			if res[0].Series[0].Values[i][2] != nil {
   143  				trade.Source = res[0].Series[0].Values[i][2].(string)
   144  			}
   145  			if res[0].Series[0].Values[i][3] != nil {
   146  				trade.ForeignTradeID = res[0].Series[0].Values[i][3].(string)
   147  			}
   148  			if res[0].Series[0].Values[i][4] != nil {
   149  				trade.Pair = res[0].Series[0].Values[i][4].(string)
   150  			}
   151  			trade.Price, err = res[0].Series[0].Values[i][5].(json.Number).Float64()
   152  			if err != nil {
   153  				return allTrades, err
   154  			}
   155  			if res[0].Series[0].Values[i][6] == nil {
   156  				continue
   157  			}
   158  			if res[0].Series[0].Values[i][6] != nil {
   159  				trade.Symbol = res[0].Series[0].Values[i][6].(string)
   160  			}
   161  			trade.Volume, err = res[0].Series[0].Values[i][7].(json.Number).Float64()
   162  			if err != nil {
   163  				return allTrades, err
   164  			}
   165  			if verified {
   166  				if res[0].Series[0].Values[i][8] != nil {
   167  					trade.QuoteToken.Address = res[0].Series[0].Values[i][8].(string)
   168  				}
   169  				if res[0].Series[0].Values[i][9] != nil {
   170  					trade.BaseToken.Address = res[0].Series[0].Values[i][9].(string)
   171  				}
   172  				if res[0].Series[0].Values[i][10] != nil {
   173  					trade.QuoteToken.Blockchain = res[0].Series[0].Values[i][10].(string)
   174  				}
   175  				if res[0].Series[0].Values[i][11] != nil {
   176  					trade.BaseToken.Blockchain = res[0].Series[0].Values[i][11].(string)
   177  				}
   178  				verifiedPair, ok := res[0].Series[0].Values[i][12].(string)
   179  				if ok {
   180  					trade.VerifiedPair, err = strconv.ParseBool(verifiedPair)
   181  					if err != nil {
   182  						log.Error("parse verified pair boolean: ", err)
   183  					}
   184  				}
   185  			}
   186  			allTrades = append(allTrades, trade)
   187  		}
   188  	} else {
   189  		return allTrades, errors.New("no trades in time range")
   190  	}
   191  	return allTrades, nil
   192  }
   193  
   194  // parseFullTrade parses a trade as retreived from influx. If fullAsset=true blockchain and address of
   195  // the corresponding asset is returned as well.
   196  func parseFullTrade(row []interface{}) *dia.Trade {
   197  	if len(row) > 13 {
   198  		t, err := time.Parse(time.RFC3339, row[0].(string))
   199  		if err == nil {
   200  
   201  			var estimatedUSDPrice float64
   202  			v, o := row[1].(json.Number)
   203  			if o {
   204  				estimatedUSDPrice, _ = v.Float64()
   205  			} else {
   206  				log.Errorln("error on parsing row 1", row)
   207  			}
   208  
   209  			source, o := row[2].(string)
   210  			if !o {
   211  				log.Errorln("error on parsing row 2", row)
   212  			}
   213  
   214  			foreignTradeID, o := row[3].(string)
   215  			if !o {
   216  				log.Errorln("error on parsing row 3", row)
   217  			}
   218  
   219  			pair, o := row[4].(string)
   220  			if !o {
   221  				log.Errorln("error on parsing row 4", row)
   222  			}
   223  
   224  			var price float64
   225  			v, o = row[5].(json.Number)
   226  			if o {
   227  				price, _ = v.Float64()
   228  			} else {
   229  				log.Errorln("error on parsing row 5", row)
   230  			}
   231  
   232  			symbol, o := row[6].(string)
   233  			if !o {
   234  				log.Errorln("error on parsing row 6", row)
   235  			}
   236  
   237  			var volume float64
   238  			v, o = row[7].(json.Number)
   239  			if o {
   240  				volume, _ = v.Float64()
   241  			} else {
   242  				log.Errorln("error on parsing row 7", row)
   243  			}
   244  			var verified bool
   245  			ver, ok := row[8].(string)
   246  			if ok {
   247  				if ver == "true" {
   248  					verified = true
   249  				}
   250  			}
   251  			basetokenblockchain, o := row[9].(string)
   252  			if !o {
   253  				log.Errorln("error on parsing row 9", row)
   254  			}
   255  			basetokenaddress, o := row[10].(string)
   256  			if !o {
   257  				log.Errorln("error on parsing row 10", row)
   258  			}
   259  			quotetokenblockchain, o := row[11].(string)
   260  			if !o {
   261  				log.Errorln("error on parsing row 11", row)
   262  			}
   263  			quotetokenaddress, o := row[12].(string)
   264  			if !o {
   265  				log.Errorln("error on parsing row 12", row)
   266  			}
   267  			pooladdress, _ := row[13].(string)
   268  
   269  			trade := dia.Trade{
   270  				Symbol:            symbol,
   271  				Pair:              pair,
   272  				QuoteToken:        dia.Asset{Address: quotetokenaddress, Blockchain: quotetokenblockchain},
   273  				BaseToken:         dia.Asset{Address: basetokenaddress, Blockchain: basetokenblockchain},
   274  				PoolAddress:       pooladdress,
   275  				Time:              t,
   276  				Source:            source,
   277  				EstimatedUSDPrice: estimatedUSDPrice,
   278  				Price:             price,
   279  				Volume:            volume,
   280  				ForeignTradeID:    foreignTradeID,
   281  				VerifiedPair:      verified,
   282  			}
   283  
   284  			return &trade
   285  		}
   286  
   287  	}
   288  	return nil
   289  }
   290  
   291  // parseTrade parses a trade as retreived from influx. If fullAsset=true blockchain and address of
   292  // the corresponding asset is returned as well.
   293  func parseTrade(row []interface{}, fullBasetoken bool) *dia.Trade {
   294  	if len(row) > 10 {
   295  		t, err := time.Parse(time.RFC3339, row[0].(string))
   296  		if err == nil {
   297  
   298  			var estimatedUSDPrice float64
   299  			v, o := row[1].(json.Number)
   300  			if o {
   301  				estimatedUSDPrice, _ = v.Float64()
   302  			} else {
   303  				log.Errorln("error on parsing row 1", row)
   304  			}
   305  
   306  			source, o := row[2].(string)
   307  			if !o {
   308  				log.Errorln("error on parsing row 2", row)
   309  			}
   310  
   311  			foreignTradeID, o := row[3].(string)
   312  			if !o {
   313  				log.Errorln("error on parsing row 3", row)
   314  			}
   315  
   316  			pair, o := row[4].(string)
   317  			if !o {
   318  				log.Errorln("error on parsing row 4", row)
   319  			}
   320  
   321  			var price float64
   322  			v, o = row[5].(json.Number)
   323  			if o {
   324  				price, _ = v.Float64()
   325  			} else {
   326  				log.Errorln("error on parsing row 5", row)
   327  			}
   328  
   329  			symbol, o := row[6].(string)
   330  			if !o {
   331  				log.Errorln("error on parsing row 6", row)
   332  			}
   333  
   334  			var volume float64
   335  			v, o = row[7].(json.Number)
   336  			if o {
   337  				volume, _ = v.Float64()
   338  			} else {
   339  				log.Errorln("error on parsing row 7", row)
   340  			}
   341  			var verified bool
   342  			ver, ok := row[8].(string)
   343  			if ok {
   344  				if ver == "true" {
   345  					verified = true
   346  				}
   347  			}
   348  			trade := dia.Trade{
   349  				Symbol:            symbol,
   350  				Pair:              pair,
   351  				Time:              t,
   352  				Source:            source,
   353  				EstimatedUSDPrice: estimatedUSDPrice,
   354  				Price:             price,
   355  				Volume:            volume,
   356  				ForeignTradeID:    foreignTradeID,
   357  				VerifiedPair:      verified,
   358  			}
   359  
   360  			if fullBasetoken {
   361  				basetokenblockchain, o := row[9].(string)
   362  				if !o {
   363  					log.Errorln("error on parsing row 9", row)
   364  				}
   365  				basetokenaddress, o := row[10].(string)
   366  				if !o {
   367  					log.Errorln("error on parsing row 10", row)
   368  				}
   369  				trade.BaseToken.Blockchain = basetokenblockchain
   370  				trade.BaseToken.Address = basetokenaddress
   371  			}
   372  
   373  			return &trade
   374  		}
   375  
   376  	}
   377  	return nil
   378  }
   379  
   380  func (datastore *DB) GetTradesByExchangesAndBaseAssets(asset dia.Asset, baseassets []dia.Asset, exchanges []string, startTime, endTime time.Time, maxTrades int) ([]dia.Trade, error) {
   381  	return datastore.GetTradesByExchangesFull(asset, baseassets, exchanges, false, startTime, endTime, maxTrades)
   382  }
   383  
   384  func (datastore *DB) GetTradesByExchangesFull(
   385  	asset dia.Asset,
   386  	baseassets []dia.Asset,
   387  	exchanges []string,
   388  	returnBasetoken bool,
   389  	startTime time.Time,
   390  	endTime time.Time,
   391  	maxTrades int,
   392  ) ([]dia.Trade, error) {
   393  	var r []dia.Trade
   394  	subQuery := ""
   395  	subQueryBase := ""
   396  	if len(exchanges) > 0 {
   397  		for _, exchange := range exchanges {
   398  			subQuery = subQuery + fmt.Sprintf("%s|", exchange)
   399  		}
   400  		subQuery = "AND exchange =~ /" + strings.TrimRight(subQuery, "|") + "/"
   401  
   402  		if len(baseassets) > 0 {
   403  			for i, baseasset := range baseassets {
   404  				if i == 0 {
   405  					subQueryBase = subQueryBase + fmt.Sprintf(` AND ((basetokenaddress='%s' AND basetokenblockchain='%s')`, baseasset.Address, baseasset.Blockchain)
   406  
   407  				} else {
   408  					subQueryBase = subQueryBase + fmt.Sprintf(` OR (basetokenaddress='%s' AND basetokenblockchain='%s')`, baseasset.Address, baseasset.Blockchain)
   409  				}
   410  			}
   411  			subQueryBase = subQueryBase + ") "
   412  		}
   413  	}
   414  	query := fmt.Sprintf(`
   415  	SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress 
   416  	FROM %s 
   417  	WHERE (quotetokenaddress='%s' and quotetokenblockchain='%s') %s %s 
   418  	AND estimatedUSDPrice > 0 
   419  	AND time > %d AND time <= %d `,
   420  		influxDbTradesTable, asset.Address, asset.Blockchain, subQuery, subQueryBase, startTime.UnixNano(), endTime.UnixNano())
   421  	if maxTrades > 0 {
   422  		query += fmt.Sprintf("ORDER BY DESC LIMIT %d ", maxTrades)
   423  	}
   424  	log.Info("query: ", query)
   425  	res, err := queryInfluxDB(datastore.influxClient, query)
   426  	if err != nil {
   427  		return r, err
   428  	}
   429  
   430  	if len(res) > 0 && len(res[0].Series) > 0 {
   431  		for _, row := range res[0].Series[0].Values {
   432  			t := parseTrade(row, returnBasetoken)
   433  			if t != nil {
   434  				r = append(r, *t)
   435  			}
   436  		}
   437  	} else {
   438  		return nil, fmt.Errorf("no trades found")
   439  	}
   440  	return r, nil
   441  }
   442  
   443  // GetTradesByExchangesBatched executes multiple select queries on the trades table in one batch.
   444  // The time ranges of the queries are given by the intervals [startTimes[i], endTimes[i]].
   445  func (datastore *DB) GetTradesByExchangesBatched(
   446  	quoteasset dia.Asset,
   447  	baseassets []dia.Asset,
   448  	exchanges []string,
   449  	startTimes []time.Time,
   450  	endTimes []time.Time,
   451  	maxTrades int,
   452  ) ([]dia.Trade, error) {
   453  	return datastore.GetTradesByExchangesBatchedFull(quoteasset, baseassets, exchanges, false, startTimes, endTimes, maxTrades)
   454  }
   455  
   456  // GetTradesByExchangesBatchedFull executes multiple select queries on the trades table in one batch.
   457  // The time ranges of the queries are given by the intervals [startTimes[i], endTimes[i]].
   458  func (datastore *DB) GetTradesByExchangesBatchedFull(
   459  	quoteasset dia.Asset,
   460  	baseassets []dia.Asset,
   461  	exchanges []string,
   462  	returnBasetoken bool,
   463  	startTimes []time.Time,
   464  	endTimes []time.Time,
   465  	maxTrades int,
   466  ) ([]dia.Trade, error) {
   467  
   468  	var r []dia.Trade
   469  	if len(startTimes) != len(endTimes) {
   470  		return []dia.Trade{}, errors.New("number of start times must equal number of end times.")
   471  	}
   472  	var query string
   473  	for i := range startTimes {
   474  		subQuery := ""
   475  		subQueryBase := ""
   476  		if len(exchanges) > 0 {
   477  			for _, exchange := range exchanges {
   478  				subQuery = subQuery + fmt.Sprintf("%s|", exchange)
   479  			}
   480  			subQuery = "and exchange =~ /" + strings.TrimRight(subQuery, "|") + "/"
   481  		}
   482  
   483  		if len(baseassets) > 0 {
   484  			for i, baseasset := range baseassets {
   485  				if i == 0 {
   486  					subQueryBase = subQueryBase + fmt.Sprintf(` and ((basetokenaddress='%s' and basetokenblockchain='%s')`, baseasset.Address, baseasset.Blockchain)
   487  
   488  				} else {
   489  					subQueryBase = subQueryBase + fmt.Sprintf(` or (basetokenaddress='%s' and basetokenblockchain='%s')`, baseasset.Address, baseasset.Blockchain)
   490  				}
   491  
   492  			}
   493  			subQueryBase = subQueryBase + ") "
   494  
   495  		}
   496  		query = query + fmt.Sprintf(`
   497  		SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress 
   498  		FROM %s 
   499  		WHERE (quotetokenaddress='%s' AND quotetokenblockchain='%s') %s %s 
   500  		AND estimatedUSDPrice > 0 
   501  		AND time > %d AND time <= %d ; `,
   502  			influxDbTradesTable, quoteasset.Address, quoteasset.Blockchain, subQuery, subQueryBase, startTimes[i].UnixNano(), endTimes[i].UnixNano())
   503  	}
   504  	log.Info("query: ", query)
   505  	res, err := queryInfluxDB(datastore.influxClient, query)
   506  	if err != nil {
   507  		return r, err
   508  	}
   509  
   510  	if len(res) > 0 {
   511  		for i := range res {
   512  			if len(res[i].Series) > 0 {
   513  				log.Infof("parse %v trades...", len(res[i].Series[0].Values))
   514  				for _, row := range res[i].Series[0].Values {
   515  					t := parseTrade(row, returnBasetoken)
   516  					if t != nil {
   517  						r = append(r, *t)
   518  					}
   519  				}
   520  				log.Info("...done parsing.")
   521  			}
   522  		}
   523  	} else {
   524  		log.Errorf("Empty response GetTradesByExchangesBatched for %s \n", quoteasset.Symbol)
   525  		return nil, fmt.Errorf("no trades found")
   526  	}
   527  
   528  	return r, nil
   529  }
   530  
   531  // GetxcTradesByExchangesBatched executes multiple select queries on the trades table in one batch.
   532  // The time ranges of the queries are given by the intervals [startTimes[i], endTimes[i]].
   533  func (datastore *DB) GetxcTradesByExchangesBatched(
   534  	quoteassets []dia.Asset,
   535  	exchanges []string,
   536  	startTimes []time.Time,
   537  	endTimes []time.Time,
   538  ) ([]dia.Trade, error) {
   539  
   540  	var r []dia.Trade
   541  	if len(startTimes) != len(endTimes) {
   542  		return []dia.Trade{}, errors.New("number of start times must equal number of end times.")
   543  	}
   544  	var query string
   545  	for i := range startTimes {
   546  		subQueryExchanges := ""
   547  		subQueryAssets := ""
   548  		if len(exchanges) > 0 {
   549  			for _, exchange := range exchanges {
   550  				subQueryExchanges = subQueryExchanges + fmt.Sprintf("%s|", exchange)
   551  			}
   552  			subQueryExchanges = "AND exchange =~ /" + strings.TrimRight(subQueryExchanges, "|") + "/"
   553  		}
   554  
   555  		if len(quoteassets) > 0 {
   556  			for i, quoteasset := range quoteassets {
   557  				if i == 0 {
   558  					subQueryAssets = subQueryAssets + fmt.Sprintf(` AND ((quotetokenaddress='%s' AND quotetokenblockchain='%s')`, quoteasset.Address, quoteasset.Blockchain)
   559  
   560  				} else {
   561  					subQueryAssets = subQueryAssets + fmt.Sprintf(` OR (quotetokenaddress='%s' AND quotetokenblockchain='%s')`, quoteasset.Address, quoteasset.Blockchain)
   562  				}
   563  
   564  			}
   565  			subQueryAssets = subQueryAssets + ") "
   566  
   567  		}
   568  		query = query + fmt.Sprintf(`
   569  		SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress
   570  		FROM %s 
   571  		WHERE estimatedUSDPrice > 0 
   572  		AND time > %d AND time <= %d 
   573  		%s %s ;`,
   574  			influxDbTradesTable, startTimes[i].UnixNano(), endTimes[i].UnixNano(), subQueryExchanges, subQueryAssets)
   575  	}
   576  	res, err := queryInfluxDB(datastore.influxClient, query)
   577  	if err != nil {
   578  		return r, err
   579  	}
   580  
   581  	if len(res) > 0 {
   582  		for i := range res {
   583  			if len(res[i].Series) > 0 {
   584  				log.Infof("parse %v trades...", len(res[i].Series[0].Values))
   585  				for _, row := range res[i].Series[0].Values {
   586  					t := parseTrade(row, false)
   587  					if t != nil {
   588  						r = append(r, *t)
   589  					}
   590  				}
   591  				log.Info("...done parsing.")
   592  			}
   593  		}
   594  	} else {
   595  		log.Error("Empty response GetxcTradesByExchangesBatched")
   596  		return nil, fmt.Errorf("no trades found")
   597  	}
   598  
   599  	return r, nil
   600  }
   601  
   602  // GetTradesByFeedSelection returns all trades with restrictions given by the struct @feedselection.
   603  func (datastore *DB) GetTradesByFeedSelection(
   604  	feedselection []dia.FeedSelection,
   605  	starttimes []time.Time,
   606  	endtimes []time.Time,
   607  	limit int,
   608  ) ([]dia.Trade, error) {
   609  	var (
   610  		query string
   611  		r     []dia.Trade
   612  	)
   613  
   614  	if len(starttimes) != len(endtimes) {
   615  		return []dia.Trade{}, errors.New("number of start times must equal number of end times.")
   616  	}
   617  
   618  	for i := range starttimes {
   619  		query += fmt.Sprintf(`
   620  		SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress,quotetokenblockchain,quotetokenaddress,pooladdress  
   621  		FROM %s 
   622  		WHERE ( `,
   623  			influxDbTradesTable,
   624  		)
   625  
   626  		// --------------------- Iterate over assets. ---------------------
   627  		for i, item := range feedselection {
   628  			if i > 0 {
   629  				query += " OR "
   630  			}
   631  			//  ---------------------Iterate over exchanges. ---------------------
   632  			var exchangeQuery string
   633  			for j, exchangepairs := range item.Exchangepairs {
   634  				if j == 0 {
   635  					exchangeQuery += " AND ("
   636  				} else {
   637  					exchangeQuery += " OR  "
   638  				}
   639  
   640  				// --------------------- Iterate over pairs/pools. ---------------------
   641  				var pairsQuery string
   642  				if exchangepairs.Exchange.Centralized {
   643  					for k, pair := range exchangepairs.Pairs {
   644  						if k == 0 {
   645  							pairsQuery += " AND ("
   646  						} else {
   647  							pairsQuery += " OR "
   648  						}
   649  						pairsQuery += fmt.Sprintf(`
   650  							( quotetokenaddress='%s' AND quotetokenblockchain='%s' AND basetokenaddress='%s' and basetokenblockchain='%s')
   651  							`,
   652  							pair.QuoteToken.Address,
   653  							pair.QuoteToken.Blockchain,
   654  							pair.BaseToken.Address,
   655  							pair.BaseToken.Blockchain,
   656  						)
   657  					}
   658  				} else {
   659  					for k, pool := range exchangepairs.Pools {
   660  						if k == 0 {
   661  							pairsQuery += " AND ("
   662  						} else {
   663  							pairsQuery += " OR "
   664  						}
   665  						pairsQuery += fmt.Sprintf(" pooladdress='%s' ", pool.Address)
   666  					}
   667  				}
   668  				if len(exchangepairs.Pairs) > 0 || len(exchangepairs.Pools) > 0 {
   669  					pairsQuery += " ) "
   670  				}
   671  
   672  				if exchangepairs.Exchange.Name != "" {
   673  					exchangeQuery += fmt.Sprintf(`(exchange='%s' %s)`, exchangepairs.Exchange.Name, pairsQuery)
   674  				} else {
   675  					// Take into account trades on all exchanges.
   676  					exchangeQuery += fmt.Sprintf(`exchange=~/./ %s`, pairsQuery)
   677  				}
   678  			}
   679  			if len(item.Exchangepairs) > 0 {
   680  				exchangeQuery += " ) "
   681  			}
   682  
   683  			// Main query for trades by asset.
   684  			query += fmt.Sprintf(`
   685  			( (quotetokenaddress='%s' AND quotetokenblockchain='%s') %s ) 
   686  			`,
   687  				item.Asset.Address,
   688  				item.Asset.Blockchain,
   689  				exchangeQuery,
   690  			)
   691  		}
   692  
   693  		// The bracket closes the main statement from the first WHERE clause.
   694  		var limitQuery string
   695  		if len(starttimes) == 1 && limit > 0 {
   696  			limitQuery = fmt.Sprintf(" ORDER BY DESC LIMIT %v", limit)
   697  		}
   698  		query += fmt.Sprintf(`
   699  		 )	
   700  		AND estimatedUSDPrice > 0
   701  		AND time > %d
   702  		AND time < %d %s;`,
   703  			starttimes[i].UnixNano(),
   704  			endtimes[i].UnixNano(),
   705  			limitQuery,
   706  		)
   707  	}
   708  
   709  	res, err := queryInfluxDB(datastore.influxClient, query)
   710  	if err != nil {
   711  		return r, err
   712  	}
   713  
   714  	if len(res) > 0 {
   715  		for i := range res {
   716  			if len(res[i].Series) > 0 {
   717  				log.Infof("parse %v trades...", len(res[i].Series[0].Values))
   718  				for _, row := range res[i].Series[0].Values {
   719  					t := parseFullTrade(row)
   720  					if t != nil {
   721  						r = append(r, *t)
   722  					}
   723  				}
   724  				log.Info("...done parsing.")
   725  			}
   726  		}
   727  	} else {
   728  		return nil, fmt.Errorf("No trades found.")
   729  	}
   730  
   731  	return r, nil
   732  }
   733  
   734  // GetAggregatedFeedSelection returns aggregated quantities with restrictions given by the struct @feedselection.
   735  func (datastore *DB) GetAggregatedFeedSelection(
   736  	feedselection []dia.FeedSelection,
   737  	starttime time.Time,
   738  	endtime time.Time,
   739  	tradeVolumeThreshold float64,
   740  ) ([]dia.FeedSelectionAggregated, error) {
   741  	var (
   742  		query                   string
   743  		feedSelectionAggregated []dia.FeedSelectionAggregated
   744  	)
   745  
   746  	if starttime.After(endtime) {
   747  		return feedSelectionAggregated, errors.New("starttime is after endtime.")
   748  	}
   749  
   750  	query += fmt.Sprintf(`
   751  		SELECT SUM(abs),COUNT(multiplication),LAST(multiplication)
   752  		FROM (
   753  			SELECT ABS(estimatedUSDPrice*volume),estimatedUSDPrice AS multiplication
   754  			FROM %s 
   755  			WHERE ( `,
   756  		influxDbTradesTable,
   757  	)
   758  
   759  	// --------------------- Iterate over assets. ---------------------
   760  	for i, item := range feedselection {
   761  		if i > 0 {
   762  			query += " OR "
   763  		}
   764  		//  ---------------------Iterate over exchanges. ---------------------
   765  		var exchangeQuery string
   766  		for j, exchangepairs := range item.Exchangepairs {
   767  			if j == 0 {
   768  				exchangeQuery += " AND ("
   769  			} else {
   770  				exchangeQuery += " OR  "
   771  			}
   772  
   773  			// --------------------- Iterate over pairs/pools. ---------------------
   774  			var pairsQuery string
   775  			if exchangepairs.Exchange.Centralized {
   776  				for k, pair := range exchangepairs.Pairs {
   777  					if k == 0 {
   778  						pairsQuery += " AND ("
   779  					} else {
   780  						pairsQuery += " OR "
   781  					}
   782  					pairsQuery += fmt.Sprintf(`
   783  							( quotetokenaddress='%s' AND quotetokenblockchain='%s' AND basetokenaddress='%s' and basetokenblockchain='%s')
   784  							`,
   785  						pair.QuoteToken.Address,
   786  						pair.QuoteToken.Blockchain,
   787  						pair.BaseToken.Address,
   788  						pair.BaseToken.Blockchain,
   789  					)
   790  				}
   791  			} else {
   792  				for k, pool := range exchangepairs.Pools {
   793  					if k == 0 {
   794  						pairsQuery += " AND ("
   795  					} else {
   796  						pairsQuery += " OR "
   797  					}
   798  					pairsQuery += fmt.Sprintf(" pooladdress='%s' ", pool.Address)
   799  				}
   800  			}
   801  			if len(exchangepairs.Pairs) > 0 || len(exchangepairs.Pools) > 0 {
   802  				pairsQuery += " ) "
   803  			}
   804  
   805  			if exchangepairs.Exchange.Name != "" {
   806  				exchangeQuery += fmt.Sprintf(`(exchange='%s' %s)`, exchangepairs.Exchange.Name, pairsQuery)
   807  			} else {
   808  				// Take into account trades on all exchanges.
   809  				exchangeQuery += fmt.Sprintf(`exchange=~/./ %s`, pairsQuery)
   810  			}
   811  		}
   812  		if len(item.Exchangepairs) > 0 {
   813  			exchangeQuery += " ) "
   814  		}
   815  
   816  		// Main query for trades by asset.
   817  		query += fmt.Sprintf(`
   818  			( (quotetokenaddress='%s' AND quotetokenblockchain='%s') %s ) 
   819  			`,
   820  			item.Asset.Address,
   821  			item.Asset.Blockchain,
   822  			exchangeQuery,
   823  		)
   824  	}
   825  
   826  	query += fmt.Sprintf(`	
   827  		)
   828  		AND estimatedUSDPrice > 0
   829  		AND time >= %d
   830  		AND time < %d)
   831  		WHERE multiplication>%v `,
   832  		starttime.UnixNano(),
   833  		endtime.UnixNano(),
   834  		tradeVolumeThreshold,
   835  	)
   836  	query += ` GROUP BY "exchange","quotetokenaddress","quotetokenblockchain","basetokenaddress","basetokenblockchain","pooladdress","symbol","pair"`
   837  
   838  	res, err := queryInfluxDB(datastore.influxClient, query)
   839  	if err != nil {
   840  		return feedSelectionAggregated, err
   841  	}
   842  
   843  	if len(res) > 0 && len(res[0].Series) > 0 {
   844  		for _, row := range res[0].Series {
   845  			if len(row.Values[0]) > 1 {
   846  				var fsa dia.FeedSelectionAggregated
   847  				fsa.Exchange = row.Tags["exchange"]
   848  				fsa.Quotetoken.Address = row.Tags["quotetokenaddress"]
   849  				fsa.Quotetoken.Blockchain = row.Tags["quotetokenblockchain"]
   850  				fsa.Basetoken.Address = row.Tags["basetokenaddress"]
   851  				fsa.Basetoken.Blockchain = row.Tags["basetokenblockchain"]
   852  				// Parse symbol of basetoken
   853  				pairSymbols, err := dia.GetPairSymbols(dia.ExchangePair{
   854  					Symbol:      row.Tags["symbol"],
   855  					ForeignName: row.Tags["pair"],
   856  					Exchange:    row.Tags["exchange"],
   857  				})
   858  				if err != nil {
   859  					log.Error("Get pair symbols: ", err)
   860  				} else if len(pairSymbols) > 1 {
   861  					fsa.Quotetoken.Symbol = strings.ToUpper(pairSymbols[0])
   862  					fsa.Basetoken.Symbol = strings.ToUpper(pairSymbols[1])
   863  				}
   864  				fsa.Pooladdress = row.Tags["pooladdress"]
   865  				fsa.Volume, err = row.Values[0][1].(json.Number).Float64()
   866  				if err != nil {
   867  					log.Error("cast float64: ", err)
   868  				}
   869  				tradescount, err := row.Values[0][2].(json.Number).Int64()
   870  				if err != nil {
   871  					log.Error("cast int64: ", err)
   872  				}
   873  				fsa.TradesCount = int32(tradescount)
   874  				fsa.LastPrice, err = row.Values[0][3].(json.Number).Float64()
   875  				if err != nil {
   876  					log.Error("cast float64: ", err)
   877  				}
   878  				fsa.Starttime = starttime
   879  				fsa.Endtime = endtime
   880  				feedSelectionAggregated = append(feedSelectionAggregated, fsa)
   881  			}
   882  		}
   883  	} else {
   884  		return feedSelectionAggregated, fmt.Errorf("No trades found.")
   885  	}
   886  
   887  	// Sort response by volume.
   888  	sort.Slice(feedSelectionAggregated, func(m, n int) bool {
   889  		return feedSelectionAggregated[m].Volume > feedSelectionAggregated[n].Volume
   890  	})
   891  
   892  	return feedSelectionAggregated, nil
   893  }
   894  
   895  // GetTradesByExchangepairs returns all trades where either of the following is fulfilled.
   896  // 1. The exchange is a key of @exchangepairMap AND the pair is in the corresponding slice @[]dia.Pair.
   897  // 2. The exchange is a key of @exchangepoolMap AND the pool is in the corresponding slice @[]string.
   898  func (datastore *DB) GetTradesByExchangepairs(exchangepairMap map[string][]dia.Pair, exchangepoolMap map[string][]string, starttime time.Time, endtime time.Time) ([]dia.Trade, error) {
   899  	var (
   900  		query string
   901  		r     []dia.Trade
   902  	)
   903  
   904  	query = fmt.Sprintf(`
   905  		SELECT time,estimatedUSDPrice,exchange,foreignTradeID,pair,price,symbol,volume,verified,basetokenblockchain,basetokenaddress,quotetokenblockchain,quotetokenaddress  
   906  		FROM %s 
   907  		WHERE ( `,
   908  		influxDbTradesTable,
   909  	)
   910  
   911  	// Iterate over centralized exchanges.
   912  	var CEXCount int
   913  	for exchange := range exchangepairMap {
   914  		if CEXCount != 0 {
   915  			query += " OR "
   916  		}
   917  
   918  		// If, in addition to exchanges, pairs are also given, make pairs subquery for each exchange.
   919  		var pairsQuery string
   920  		if len(exchangepairMap) > 0 {
   921  			pairsQuery += " AND ( "
   922  			for i, pair := range exchangepairMap[exchange] {
   923  				if i != 0 {
   924  					pairsQuery += " OR "
   925  				}
   926  				pairsQuery += fmt.Sprintf(`
   927  				( quotetokenaddress='%s' AND quotetokenblockchain='%s' AND basetokenaddress='%s' and basetokenblockchain='%s')
   928  				`,
   929  					pair.QuoteToken.Address,
   930  					pair.QuoteToken.Blockchain,
   931  					pair.BaseToken.Address,
   932  					pair.BaseToken.Blockchain,
   933  				)
   934  			}
   935  			pairsQuery += " ) "
   936  		}
   937  
   938  		// Main query for trades by exchange.
   939  		query += fmt.Sprintf(" ( exchange='%s' %s ) ", exchange, pairsQuery)
   940  		CEXCount++
   941  	}
   942  
   943  	// Iterate over decentralized exchanges.
   944  	var DEXCount int
   945  	for exchange := range exchangepoolMap {
   946  		if DEXCount != 0 || len(exchangepairMap) > 0 {
   947  			query += " OR "
   948  		}
   949  
   950  		// If, in addition to exchanges, pools are also given, make pool subquery for each exchange.
   951  		var poolsQuery string
   952  		if len(exchangepairMap) > 0 {
   953  			poolsQuery += " AND ( "
   954  			for i, pooladdress := range exchangepoolMap[exchange] {
   955  				if i != 0 {
   956  					poolsQuery += " OR "
   957  				}
   958  				poolsQuery += fmt.Sprintf("( pooladdress='%s' )", pooladdress)
   959  			}
   960  			poolsQuery += " ) "
   961  		}
   962  
   963  		// Main query for trades by exchange.
   964  		query += fmt.Sprintf(" ( exchange='%s' %s ) ", exchange, poolsQuery)
   965  		DEXCount++
   966  	}
   967  
   968  	// The bracket closes the main statement from the first WHERE clause.
   969  	query += fmt.Sprintf(`
   970  		 )	
   971  		AND estimatedUSDPrice > 0
   972  		AND time > %d
   973  		AND time < %d`,
   974  		starttime.UnixNano(),
   975  		endtime.UnixNano(),
   976  	)
   977  
   978  	log.Info("query: ", query)
   979  	res, err := queryInfluxDB(datastore.influxClient, query)
   980  	if err != nil {
   981  		return r, err
   982  	}
   983  
   984  	if len(res) > 0 {
   985  		for i := range res {
   986  			if len(res[i].Series) > 0 {
   987  				log.Infof("parse %v trades...", len(res[i].Series[0].Values))
   988  				for _, row := range res[i].Series[0].Values {
   989  					t := parseFullTrade(row)
   990  					if t != nil {
   991  						r = append(r, *t)
   992  					}
   993  				}
   994  				log.Info("...done parsing.")
   995  			}
   996  		}
   997  	} else {
   998  		log.Error("Empty response GetxcTradesByExchangesBatched")
   999  		return nil, fmt.Errorf("no trades found")
  1000  	}
  1001  
  1002  	return r, nil
  1003  }
  1004  
  1005  // GetAllTrades returns at most @maxTrades trades from influx with timestamp > @t. Only used by replayInflux option.
  1006  func (datastore *DB) GetAllTrades(t time.Time, maxTrades int) ([]dia.Trade, error) {
  1007  	var r []dia.Trade
  1008  	// TO DO: Substitute select * with precise statment select estimatedUSDPrice, source,...
  1009  	q := fmt.Sprintf("SELECT time, estimatedUSDPrice, exchange, foreignTradeID, pair, price,symbol, volume,verified,basetokenblockchain,basetokenaddress  FROM %s WHERE time > %d LIMIT %d", influxDbTradesTable, t.Unix()*1000000000, maxTrades)
  1010  	log.Debug(q)
  1011  	res, err := queryInfluxDB(datastore.influxClient, q)
  1012  	if err != nil {
  1013  		log.Errorln("GetAllTrades", err)
  1014  		return r, err
  1015  	}
  1016  	if len(res) > 0 && len(res[0].Series) > 0 {
  1017  		for _, row := range res[0].Series[0].Values {
  1018  			t := parseTrade(row, false)
  1019  			log.Errorln("row trade parseTrade", row)
  1020  			if t != nil {
  1021  				r = append(r, *t)
  1022  			}
  1023  		}
  1024  	} else {
  1025  		log.Error("Empty response GetAllTrades")
  1026  	}
  1027  	return r, nil
  1028  }
  1029  
  1030  // GetLastTrades returns the last @maxTrades of @asset on @exchange before @timestamp.
  1031  // If exchange is empty string it returns trades from all exchanges.
  1032  // If fullAsset=true, blockchain and address of both involved assets is returned as well
  1033  func (datastore *DB) GetLastTrades(asset dia.Asset, exchange string, timestamp time.Time, maxTrades int, fullAsset bool) ([]dia.Trade, error) {
  1034  	var (
  1035  		r           []dia.Trade
  1036  		queryString string
  1037  		q           string
  1038  	)
  1039  
  1040  	if exchange == "" {
  1041  		queryString = `
  1042  		SELECT estimatedUSDPrice,"exchange",foreignTradeID,"pair",price,"symbol",volume,"verified","basetokenblockchain","basetokenaddress" 
  1043  		FROM %s 
  1044  		WHERE time<%d 
  1045  		AND time>%d-10d 
  1046  		AND quotetokenaddress='%s' 
  1047  		AND quotetokenblockchain='%s' 
  1048  		AND estimatedUSDPrice>0 
  1049  		ORDER BY DESC LIMIT %d
  1050  		`
  1051  		q = fmt.Sprintf(queryString, influxDbTradesTable, timestamp.UnixNano(), timestamp.UnixNano(), asset.Address, asset.Blockchain, maxTrades)
  1052  	} else if (dia.Asset{}) == asset {
  1053  		queryString = `
  1054  		SELECT estimatedUSDPrice,"exchange",foreignTradeID,"pair",price,"symbol",volume,"verified","basetokenblockchain","basetokenaddress" 
  1055  		FROM %s 
  1056  		WHERE time<%d 
  1057  		AND time>%d-10d 
  1058  		AND exchange='%s' 
  1059  		AND estimatedUSDPrice>0 
  1060  		ORDER BY DESC LIMIT %d
  1061  		`
  1062  		q = fmt.Sprintf(queryString, influxDbTradesTable, timestamp.UnixNano(), timestamp.UnixNano(), exchange, maxTrades)
  1063  	} else {
  1064  		queryString = `
  1065  		SELECT estimatedUSDPrice,"exchange",foreignTradeID,"pair",price,"symbol",volume,"verified","basetokenblockchain","basetokenaddress" 
  1066  		FROM %s 
  1067  		WHERE time<%d
  1068  		AND time>%d-10d 
  1069  		AND exchange='%s' 
  1070  		AND quotetokenaddress='%s' 
  1071  		AND quotetokenblockchain='%s' 
  1072  		AND estimatedUSDPrice>0 
  1073  		ORDER BY DESC LIMIT %d
  1074  		`
  1075  		q = fmt.Sprintf(queryString, influxDbTradesTable, timestamp.UnixNano(), timestamp.UnixNano(), exchange, asset.Address, asset.Blockchain, maxTrades)
  1076  	}
  1077  
  1078  	res, err := queryInfluxDB(datastore.influxClient, q)
  1079  	if err != nil {
  1080  		log.Errorln("GetLastTrades", err)
  1081  		return r, err
  1082  	}
  1083  
  1084  	if len(res) > 0 && len(res[0].Series) > 0 {
  1085  		for _, row := range res[0].Series[0].Values {
  1086  			t := parseTrade(row, fullAsset)
  1087  			if t != nil {
  1088  				t.QuoteToken = asset
  1089  				r = append(r, *t)
  1090  			}
  1091  		}
  1092  	} else {
  1093  		err = fmt.Errorf("Empty response for %s on %s", asset.Symbol, exchange)
  1094  		log.Error(err)
  1095  		return r, err
  1096  	}
  1097  	return r, nil
  1098  }
  1099  
  1100  // GetNumTradesExchange24H returns the number of trades on @exchange in the last 24 hours.
  1101  func (datastore *DB) GetNumTradesExchange24H(exchange string) (numTrades int64, err error) {
  1102  	endtime := time.Now()
  1103  	return datastore.GetNumTrades(exchange, "", "", endtime.AddDate(0, 0, -1), endtime)
  1104  }
  1105  
  1106  // GetNumTrades returns the number of trades on @exchange for asset with @address and @blockchain in the given time-range.
  1107  // If @address and @blockchain are empty, it returns all trades on @exchange in the given-time range.
  1108  func (datastore *DB) GetNumTrades(exchange string, address string, blockchain string, starttime time.Time, endtime time.Time) (numTrades int64, err error) {
  1109  	var q string
  1110  
  1111  	if address != "" && blockchain != "" {
  1112  		queryString := `
  1113  	SELECT COUNT(*) 
  1114  	FROM %s 
  1115  	WHERE exchange='%s' 
  1116  	AND quotetokenaddress='%s' AND quotetokenblockchain='%s' 
  1117  	AND time > %d AND time<= %d
  1118  	`
  1119  		q = fmt.Sprintf(queryString, influxDbTradesTable, exchange, address, blockchain, starttime.UnixNano(), endtime.UnixNano())
  1120  	} else {
  1121  		queryString := `
  1122  	SELECT COUNT(*) 
  1123  	FROM %s 
  1124  	WHERE exchange='%s' 
  1125  	AND time > %d AND time<= %d
  1126  	`
  1127  		q = fmt.Sprintf(queryString, influxDbTradesTable, exchange, starttime.UnixNano(), endtime.UnixNano())
  1128  	}
  1129  
  1130  	res, err := queryInfluxDB(datastore.influxClient, q)
  1131  	if err != nil {
  1132  		log.Errorln("GetNumTrades ", err)
  1133  		return
  1134  	}
  1135  
  1136  	if len(res) > 0 && len(res[0].Series) > 0 {
  1137  		num, ok := res[0].Series[0].Values[0][1].(json.Number)
  1138  		if ok {
  1139  			numTrades, err = num.Int64()
  1140  			if err != nil {
  1141  				return numTrades, err
  1142  			}
  1143  		}
  1144  	}
  1145  
  1146  	return
  1147  }
  1148  
  1149  // GetNumTradesSeries returns a time-series of number of trades in the respective time-ranges.
  1150  // If pair is the empty string, trades are identified by address/blockchain.
  1151  // @grouping defines the time-ranges in the notation of influx such as 30s, 40m, 2h,...
  1152  func (datastore *DB) GetNumTradesSeries(
  1153  	asset dia.Asset,
  1154  	exchange string,
  1155  	starttime time.Time,
  1156  	endtime time.Time,
  1157  	grouping string,
  1158  ) (numTrades []int64, err error) {
  1159  	var query string
  1160  	selectQuery := "SELECT COUNT(price) FROM %s "
  1161  	midQuery := "WHERE quotetokenaddress='%s' AND quotetokenblockchain='%s' AND time<=%d AND time>%d "
  1162  	endQuery := "GROUP BY time(%s) ORDER BY ASC"
  1163  	exchangeQuery := "AND exchange='%s' "
  1164  	if exchange != "" {
  1165  		query = fmt.Sprintf(selectQuery+midQuery+exchangeQuery+endQuery,
  1166  			influxDbTradesTable,
  1167  			exchange,
  1168  			asset.Address,
  1169  			asset.Blockchain,
  1170  			endtime.UnixNano(),
  1171  			starttime.UnixNano(),
  1172  			grouping,
  1173  		)
  1174  	} else {
  1175  		query = fmt.Sprintf(selectQuery+midQuery+endQuery,
  1176  			influxDbTradesTable,
  1177  			asset.Address,
  1178  			asset.Blockchain,
  1179  			endtime.UnixNano(),
  1180  			starttime.UnixNano(),
  1181  			grouping,
  1182  		)
  1183  	}
  1184  
  1185  	res, err := queryInfluxDB(datastore.influxClient, query)
  1186  	if err != nil {
  1187  		return
  1188  	}
  1189  	if len(res) > 0 && len(res[0].Series) > 0 {
  1190  		for _, val := range res[0].Series[0].Values {
  1191  			num, ok := val[1].(json.Number)
  1192  			if ok {
  1193  				aux, err := num.Int64()
  1194  				if err != nil {
  1195  					return numTrades, err
  1196  				}
  1197  				numTrades = append(numTrades, aux)
  1198  			}
  1199  		}
  1200  	}
  1201  	return
  1202  }
  1203  
  1204  func (datastore *DB) GetFirstTradeDate(table string) (time.Time, error) {
  1205  	var query string
  1206  	queryString := "SELECT \"exchange\",price FROM %s  where time<now() order by asc limit 1"
  1207  	query = fmt.Sprintf(queryString, table)
  1208  
  1209  	res, err := queryInfluxDB(datastore.influxClient, query)
  1210  	if err != nil {
  1211  		return time.Time{}, err
  1212  	}
  1213  	if len(res) > 0 && len(res[0].Series) > 0 {
  1214  		return time.Parse(time.RFC3339, res[0].Series[0].Values[0][0].(string))
  1215  	}
  1216  	return time.Time{}, errors.New("no trade found")
  1217  
  1218  }
  1219  
  1220  func getKeyLastTradeTimeForExchange(asset dia.Asset, exchange string) string {
  1221  	if exchange == "" {
  1222  		return "dia_TLT_" + asset.Blockchain + "_" + asset.Address
  1223  
  1224  	} else {
  1225  		return "dia_TLT_" + asset.Blockchain + "_" + asset.Address + "_" + exchange
  1226  	}
  1227  }
  1228  
  1229  func (datastore *DB) GetLastTradeTimeForExchange(asset dia.Asset, exchange string) (*time.Time, error) {
  1230  	key := getKeyLastTradeTimeForExchange(asset, exchange)
  1231  	t, err := datastore.redisClient.Get(key).Result()
  1232  	if err != nil {
  1233  		log.Errorln("Error: on GetLastTradeTimeForExchange", err, key)
  1234  		return nil, err
  1235  	}
  1236  	i64, err := strconv.ParseInt(t, 10, 64)
  1237  	if err == nil {
  1238  		t2 := time.Unix(i64, 0)
  1239  		return &t2, nil
  1240  	} else {
  1241  		return nil, err
  1242  	}
  1243  }
  1244  
  1245  func (datastore *DB) SetLastTradeTimeForExchange(asset dia.Asset, exchange string, t time.Time) error {
  1246  	if datastore.redisClient == nil {
  1247  		return nil
  1248  	}
  1249  	key := getKeyLastTradeTimeForExchange(asset, exchange)
  1250  	log.Debug("setting ", key, t)
  1251  	err := datastore.redisPipe.Set(key, t.Unix(), TimeOutRedis).Err()
  1252  	if err != nil {
  1253  		log.Printf("Error: %v on SetLastTradeTimeForExchange %v\n", err, asset.Symbol)
  1254  	}
  1255  	return err
  1256  }