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

     1  package models
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/diadata-org/diadata/pkg/dia"
    11  	"github.com/go-redis/redis"
    12  	clientInfluxdb "github.com/influxdata/influxdb1-client/v2"
    13  )
    14  
    15  // SetFilter stores a filter point
    16  func (datastore *DB) SetFilter(filter string, asset dia.Asset, exchange string, value float64, t time.Time) error {
    17  	// return datastore.SaveFilterInflux(filter, asset, exchange, value, t)
    18  	err := datastore.SaveFilterInflux(filter, asset, exchange, value, t)
    19  	if err != nil {
    20  		return err
    21  	}
    22  	err = datastore.setZSETValue(getKeyFilterZSET(getKey(filter, asset, exchange)), value, t.Unix(), BiggestWindow)
    23  	if err != nil {
    24  		return err
    25  	}
    26  	return err
    27  }
    28  
    29  func (datastore *DB) GetFilterPointsAsset(filter string, exchange string, address string, blockchain string, starttime time.Time, endtime time.Time) (*Points, error) {
    30  
    31  	exchangeQuery := "AND exchange='" + exchange + "' "
    32  
    33  	q := fmt.Sprintf("SELECT time,address,blockchain,exchange,filter,symbol,value FROM %s"+
    34  		" WHERE filter='%s' %s AND address='%s' and blockchain='%s' AND time>%d and time<=%d ORDER BY DESC",
    35  		influxDbFiltersTable, filter, exchangeQuery, address, blockchain, starttime.UnixNano(), endtime.UnixNano())
    36  
    37  	res, err := queryInfluxDB(datastore.influxClient, q)
    38  	if err != nil {
    39  		log.Errorln("GetFilterPoints", err)
    40  	}
    41  
    42  	log.Info("GetFilterPoints query: ", q, " returned ", len(res))
    43  
    44  	return &Points{
    45  		DataPoints: res,
    46  	}, err
    47  }
    48  
    49  // GetFilterPoints returns filter points from either a specific exchange or all exchanges.
    50  // symbol is mapped to the underlying asset with biggest market cap.
    51  func (datastore *DB) GetFilterPoints(filter string, exchange string, symbol string, scale string, starttime time.Time, endtime time.Time) (*Points, error) {
    52  	relDB, err := NewRelDataStore()
    53  	if err != nil {
    54  		log.Errorln("NewDataStore:", err)
    55  	}
    56  	// First get asset with @symbol with largest volume.
    57  	// topAsset, err := db.GetTopAssetByVolume(symbol, relDB)
    58  	sortedAssets, err := relDB.GetTopAssetByVolume(symbol)
    59  	if err != nil {
    60  		log.Error(err)
    61  		return &Points{}, err
    62  	}
    63  	if len(sortedAssets) == 0 {
    64  		return nil, errors.New("no traded assets found")
    65  	}
    66  	topAsset := sortedAssets[0]
    67  
    68  	exchangeQuery := "and exchange='" + exchange + "' "
    69  	table := ""
    70  	//	5m 30m 1h 4h 1d 1w
    71  	if scale != "" {
    72  		if filter == "VOL120" {
    73  			table = "a_year.filters_sum_"
    74  		} else {
    75  			table = "a_year.filters_mean_"
    76  		}
    77  		table = table + scale
    78  	} else {
    79  		table = influxDbFiltersTable
    80  	}
    81  
    82  	q := fmt.Sprintf("SELECT time,exchange,filter,symbol,value FROM %s"+
    83  		" WHERE filter='%s' %sand address='%s' and blockchain='%s' and time>%d and time<%d ORDER BY DESC",
    84  		table, filter, exchangeQuery, topAsset.Address, topAsset.Blockchain, starttime.UnixNano(), endtime.UnixNano())
    85  
    86  	res, err := queryInfluxDB(datastore.influxClient, q)
    87  	if err != nil {
    88  		log.Errorln("GetFilterPoints", err)
    89  	}
    90  
    91  	log.Info("GetFilterPoints query: ", q, " returned ", len(res))
    92  
    93  	return &Points{
    94  		DataPoints: res,
    95  	}, err
    96  }
    97  
    98  func (datastore *DB) GetFilter(filter string, topAsset dia.Asset, scale string, starttime time.Time, endtime time.Time) ([]dia.FilterPoint, error) {
    99  	var allFilters []dia.FilterPoint
   100  	table := ""
   101  	//	5m 30m 1h 4h 1d 1w
   102  	if scale != "" {
   103  		if filter == "VOL120" {
   104  			table = "a_year.filters_sum_"
   105  		} else {
   106  			table = "a_year.filters_mean_"
   107  		}
   108  		table = table + scale
   109  	} else {
   110  		table = influxDbFiltersTable
   111  	}
   112  
   113  	q := fmt.Sprintf("SELECT last(*) FROM %s"+
   114  		" WHERE filter='%s' and address='%s' and blockchain='%s' and time>%d and time<%d and allExchanges=true group by time(1d) fill(previous) ORDER BY DESC",
   115  		table, filter, topAsset.Address, topAsset.Blockchain, starttime.UnixNano(), endtime.UnixNano())
   116  
   117  	res, err := queryInfluxDB(datastore.influxClient, q)
   118  	if err != nil {
   119  		log.Errorln("GetFilterPoints", err)
   120  	}
   121  	log.Info("GetFilterPoints query: ", q, " returned ", len(res))
   122  
   123  	if len(res) > 0 && len(res[0].Series) > 0 {
   124  		for i := 0; i < len(res[0].Series[0].Values); i++ {
   125  
   126  			var filterpoint dia.FilterPoint
   127  
   128  			filterpoint.Time, err = time.Parse(time.RFC3339, res[0].Series[0].Values[i][0].(string))
   129  			if err != nil {
   130  				return allFilters, err
   131  			}
   132  			// if res[0].Series[0].Values[i][1] != nil {
   133  			// 	filterpoint.Exchange = res[0].Series[0].Values[i][1].(string)
   134  			// }
   135  			// if res[0].Series[0].Values[i][2] != nil {
   136  			// 	filterpoint.Name = res[0].Series[0].Values[i][2].(string)
   137  			// }
   138  			// if res[0].Series[0].Values[i][3] != nil {
   139  			// 	filterpoint.Asset.Symbol = res[0].Series[0].Values[i][3].(string)
   140  			// }
   141  			if res[0].Series[0].Values[i][2] != nil {
   142  				filterpoint.Value, err = res[0].Series[0].Values[i][2].(json.Number).Float64()
   143  			} else {
   144  				log.Errorln("res[0].Series[0].Values[i][2]", res[0].Series[0].Values[i][2])
   145  			}
   146  			if err != nil {
   147  				return allFilters, err
   148  			}
   149  			allFilters = append(allFilters, filterpoint)
   150  		}
   151  	} else {
   152  		return allFilters, errors.New("no filter points in time range")
   153  	}
   154  
   155  	return allFilters, err
   156  }
   157  
   158  // GetFilterAllExchanges returns a slice of quotations for each exchange the asset given by
   159  // @address and @blockchain has a filter value in the given time-range.
   160  // It returns the most recent filter value in the given time-range for each exchange resp.
   161  func (datastore *DB) GetFilterAllExchanges(
   162  	filter string,
   163  	address string,
   164  	blockchain string,
   165  	starttime time.Time,
   166  	endtime time.Time,
   167  ) (assetQuotations []AssetQuotation, err error) {
   168  	q := fmt.Sprintf(`
   169  	SELECT time,address,blockchain,symbol,value
   170  	FROM %s
   171  	WHERE filter='%s'
   172  	AND allExchanges=false
   173  	AND address='%s'
   174  	AND blockchain='%s' 
   175  	AND time>%d
   176  	AND time<=%d
   177  	GROUP BY "exchange"
   178  	ORDER BY DESC
   179  	LIMIT 1`,
   180  		influxDbFiltersTable, filter, address, blockchain, starttime.UnixNano(), endtime.UnixNano())
   181  
   182  	res, err := queryInfluxDB(datastore.influxClient, q)
   183  	if err != nil {
   184  		log.Errorln("GetFilterPoints", err)
   185  		return
   186  	}
   187  
   188  	if len(res) > 0 && len(res[0].Series) > 0 {
   189  		for _, row := range res[0].Series {
   190  			var aq AssetQuotation
   191  			aq.Source = row.Tags["exchange"]
   192  			aq.Price, err = row.Values[0][4].(json.Number).Float64()
   193  			if err != nil {
   194  				log.Warn("parse price: ", err)
   195  			}
   196  			aq.Asset = dia.Asset{Address: address, Blockchain: blockchain, Symbol: row.Values[0][3].(string)}
   197  			aq.Time, err = time.Parse(time.RFC3339, row.Values[0][0].(string))
   198  			if err != nil {
   199  				log.Warn("parse time: ", err)
   200  			}
   201  			assetQuotations = append(assetQuotations, aq)
   202  		}
   203  	}
   204  
   205  	return
   206  }
   207  
   208  func getKey(filter string, asset dia.Asset, exchange string) string {
   209  	key := filter + "_" + asset.Blockchain + "_" + asset.Address
   210  	if exchange != "" {
   211  		key = key + "_" + exchange
   212  	}
   213  	return key
   214  }
   215  
   216  func getKeyFilterZSET(key string) string {
   217  	return "dia_" + key + "_ZSET"
   218  }
   219  
   220  func getKeyFilterSymbolAndExchangeZSET(filter string, asset dia.Asset, exchange string) string {
   221  	if exchange == "" {
   222  		return "dia_" + filter + "_" + asset.Blockchain + "_" + asset.Address + "_ZSET"
   223  	} else {
   224  		return "dia_" + filter + "_" + asset.Blockchain + "_" + asset.Address + "_ZSET"
   225  	}
   226  }
   227  
   228  // SaveFilterInflux stores a filter point in influx.
   229  func (datastore *DB) SaveFilterInflux(filter string, asset dia.Asset, exchange string, value float64, t time.Time) error {
   230  	// Create a point and add to batch
   231  	tags := map[string]string{
   232  		"filter":     filter,
   233  		"symbol":     EscapeReplacer.Replace(asset.Symbol),
   234  		"address":    EscapeReplacer.Replace(asset.Address),
   235  		"blockchain": asset.Blockchain,
   236  		"exchange":   exchange,
   237  	}
   238  	fields := map[string]interface{}{
   239  		"value":        value,
   240  		"allExchanges": exchange == "",
   241  	}
   242  	pt, err := clientInfluxdb.NewPoint(influxDbFiltersTable, tags, fields, t)
   243  	if err != nil {
   244  		log.Errorln("new filter influx:", err)
   245  	} else {
   246  		datastore.addPoint(pt)
   247  	}
   248  	return err
   249  }
   250  
   251  func (datastore *DB) setZSETValue(key string, value float64, unixTime int64, maxWindow int64) error {
   252  	if datastore.redisClient == nil {
   253  		return nil
   254  	}
   255  	member := strconv.FormatFloat(value, 'f', -1, 64) + " " + strconv.FormatInt(unixTime, 10)
   256  
   257  	err := datastore.redisPipe.ZAdd(key, redis.Z{
   258  		Score:  float64(unixTime),
   259  		Member: member,
   260  	}).Err()
   261  	log.Debug("SetZSETValue ", key, member, unixTime)
   262  	if err != nil {
   263  		log.Errorf("Error: %v on SetZSETValue %v\n", err, key)
   264  	}
   265  	// purging old values
   266  	err = datastore.redisPipe.ZRemRangeByScore(key, "-inf", "("+strconv.FormatInt(unixTime-maxWindow, 10)).Err()
   267  	if err != nil {
   268  		log.Errorf("Error: %v on SetZSETValue %v\n", err, key)
   269  	}
   270  	if err = datastore.redisPipe.Expire(key, TimeOutRedis).Err(); err != nil {
   271  		log.Error(err)
   272  	} //TODO put two commands together ?
   273  	return err
   274  }
   275  
   276  func (datastore *DB) getZSETValue(key string, atUnixTime int64) (float64, error) {
   277  
   278  	result := 0.0
   279  	max := strconv.FormatInt(atUnixTime, 10)
   280  	vals, err := datastore.redisClient.ZRangeByScoreWithScores(key, redis.ZRangeBy{
   281  		Min: "-inf",
   282  		Max: max,
   283  	}).Result()
   284  	log.Debug(key, "vals: %v on getZSETValue maxScore: %v", vals, max)
   285  	if err == nil {
   286  		if len(vals) > 0 {
   287  			_, err = fmt.Sscanf(vals[len(vals)-1].Member.(string), "%f", &result)
   288  			if err != nil {
   289  				log.Error(err)
   290  			}
   291  			log.Debugf("returned value: %v", result)
   292  		} else {
   293  			err = errors.New("getZSETValue no value found")
   294  		}
   295  	}
   296  	return result, err
   297  }
   298  
   299  func (datastore *DB) getZSETLastValue(key string) (float64, int64, error) {
   300  	value := 0.0
   301  	var unixTime int64
   302  	vals, err := datastore.redisClient.ZRange(key, -1, -1).Result()
   303  	log.Debug(key, "on getZSETLastValue:", vals)
   304  	if err == nil {
   305  		if len(vals) == 1 {
   306  			_, err = fmt.Sscanf(vals[0], "%f %d", &value, &unixTime)
   307  			if err != nil {
   308  				log.Error(err)
   309  			}
   310  			log.Debugf("returned value: %v", value)
   311  		} else {
   312  			err = errors.New("getZSETLastValue no value found")
   313  			log.Errorln("Error: on getZSETLastValue", err, key)
   314  		}
   315  	}
   316  	return value, unixTime, err
   317  }