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

     1  package models
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/diadata-org/diadata/pkg/dia"
    11  	"github.com/jackc/pgx/v4"
    12  )
    13  
    14  // GetExchangePair returns the unique exchange pair given by @exchange and @foreignname from postgres.
    15  // It also returns the underlying pair if existent.
    16  // If @caseSensitive is false case of @foreignname is ignored.
    17  func (rdb *RelDB) GetExchangePair(exchange string, foreignname string, caseSensitive bool) (dia.ExchangePair, error) {
    18  	var (
    19  		exchangepair       dia.ExchangePair
    20  		verified           bool
    21  		query              string
    22  		decimalsQuoteAsset sql.NullInt64
    23  		decimalsBaseAsset  sql.NullInt64
    24  	)
    25  
    26  	exchangepair.Exchange = exchange
    27  
    28  	if caseSensitive {
    29  		query = fmt.Sprintf(`
    30  			SELECT ep.symbol,ep.foreignname,ep.verified,a.symbol,a.name,a.address,a.blockchain,a.decimals,b.symbol,b.name,b.address,b.blockchain,b.decimals
    31  			FROM %s ep
    32  			INNER JOIN %s a
    33  			ON ep.id_quotetoken=a.asset_id
    34  			INNER JOIN %s b
    35  			ON ep.id_basetoken=b.asset_id
    36  			WHERE exchange=$1 AND foreignname=$2`,
    37  			exchangepairTable,
    38  			assetTable,
    39  			assetTable,
    40  		)
    41  	} else {
    42  		query = fmt.Sprintf(`
    43  			SELECT ep.symbol,ep.foreignname,ep.verified,a.symbol,a.name,a.address,a.blockchain,a.decimals,b.symbol,b.name,b.address,b.blockchain,b.decimals
    44  			FROM %s ep
    45  			INNER JOIN %s a
    46  			ON ep.id_quotetoken=a.asset_id
    47  			INNER JOIN %s b
    48  			ON ep.id_basetoken=b.asset_id
    49  			WHERE exchange=$1 AND foreignname ILIKE $2`,
    50  			exchangepairTable,
    51  			assetTable,
    52  			assetTable,
    53  		)
    54  	}
    55  	err := rdb.postgresClient.QueryRow(context.Background(), query, exchange, foreignname).Scan(
    56  		&exchangepair.Symbol,
    57  		&exchangepair.ForeignName,
    58  		&verified,
    59  		&exchangepair.UnderlyingPair.QuoteToken.Symbol,
    60  		&exchangepair.UnderlyingPair.QuoteToken.Name,
    61  		&exchangepair.UnderlyingPair.QuoteToken.Address,
    62  		&exchangepair.UnderlyingPair.QuoteToken.Blockchain,
    63  		&decimalsQuoteAsset,
    64  		&exchangepair.UnderlyingPair.BaseToken.Symbol,
    65  		&exchangepair.UnderlyingPair.BaseToken.Name,
    66  		&exchangepair.UnderlyingPair.BaseToken.Address,
    67  		&exchangepair.UnderlyingPair.BaseToken.Blockchain,
    68  		&decimalsBaseAsset,
    69  	)
    70  	if err != nil {
    71  		return dia.ExchangePair{}, err
    72  	}
    73  	if decimalsQuoteAsset.Valid {
    74  		exchangepair.UnderlyingPair.QuoteToken.Decimals = uint8(decimalsQuoteAsset.Int64)
    75  	}
    76  	if decimalsQuoteAsset.Valid {
    77  		exchangepair.UnderlyingPair.BaseToken.Decimals = uint8(decimalsBaseAsset.Int64)
    78  	}
    79  
    80  	exchangepair.Verified = verified
    81  	return exchangepair, nil
    82  }
    83  
    84  // SetExchangePair adds @pair to exchangepair table.
    85  // If cache==true, it is also cached into redis
    86  func (rdb *RelDB) SetExchangePair(exchange string, pair dia.ExchangePair, cache bool) error {
    87  	var query string
    88  	query = fmt.Sprintf(`
    89  		INSERT INTO %s (symbol,foreignname,exchange) 
    90  		SELECT $1,$2,$3 
    91  		WHERE NOT EXISTS (SELECT 1 FROM %s WHERE symbol=$1 AND foreignname=$2 AND exchange=$3)`,
    92  		exchangepairTable,
    93  		exchangepairTable,
    94  	)
    95  	_, err := rdb.postgresClient.Exec(context.Background(), query, pair.Symbol, pair.ForeignName, exchange)
    96  	if err != nil {
    97  		return err
    98  	}
    99  	basetokenID, err := rdb.GetAssetID(pair.UnderlyingPair.BaseToken)
   100  	if err != nil {
   101  		log.Error(err)
   102  	}
   103  	quotetokenID, err := rdb.GetAssetID(pair.UnderlyingPair.QuoteToken)
   104  	if err != nil {
   105  		log.Error(err)
   106  	}
   107  	if basetokenID != "" {
   108  		query = fmt.Sprintf("UPDATE %s SET id_basetoken='%s' WHERE foreignname='%s' AND exchange='%s'", exchangepairTable, basetokenID, pair.ForeignName, exchange)
   109  		_, err = rdb.postgresClient.Exec(context.Background(), query)
   110  		if err != nil {
   111  			return err
   112  		}
   113  	}
   114  	if quotetokenID != "" {
   115  		query = fmt.Sprintf("UPDATE %s SET id_quotetoken='%s' WHERE foreignname='%s' AND exchange='%s'", exchangepairTable, quotetokenID, pair.ForeignName, exchange)
   116  		_, err = rdb.postgresClient.Exec(context.Background(), query)
   117  		if err != nil {
   118  			return err
   119  		}
   120  	}
   121  	query = fmt.Sprintf("UPDATE %s SET verified='%v' WHERE foreignname='%s' AND exchange='%s'", exchangepairTable, pair.Verified, pair.ForeignName, exchange)
   122  	_, err = rdb.postgresClient.Exec(context.Background(), query)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	if cache {
   127  		err = rdb.SetExchangePairCache(exchange, pair)
   128  		if err != nil {
   129  			log.Errorf("setting pair %s to redis for exchange %s: %v", pair.ForeignName, exchange, err)
   130  		}
   131  	}
   132  	return nil
   133  }
   134  
   135  // GetExchangePairSeparator returns the separator that is used as notation for an exchange pair.
   136  // Examples: BTC-USDT, BTCUSDT, BTC/USDT.
   137  func (rdb *RelDB) GetExchangePairSeparator(exchange string) (string, error) {
   138  	var (
   139  		foreignname      string
   140  		symbolQuoteAsset string
   141  		symbolBaseAsset  string
   142  	)
   143  	query := fmt.Sprintf(`
   144  		SELECT ep.symbol,a.symbol,ep.foreignname 
   145  		FROM %s ep
   146  		INNER JOIN %s a
   147  		ON ep.id_basetoken=a.asset_id
   148  		WHERE exchange=$1
   149  		LIMIT 1`,
   150  		exchangepairTable,
   151  		assetTable,
   152  	)
   153  	err := rdb.postgresClient.QueryRow(context.Background(), query, exchange).Scan(&symbolQuoteAsset, &symbolBaseAsset, &foreignname)
   154  	if err != nil {
   155  		return "", err
   156  	}
   157  	if len(strings.Split(foreignname, symbolQuoteAsset)) < 2 {
   158  		return "", errors.New("not enough data.")
   159  	}
   160  	if len(strings.Split(strings.Split(foreignname, symbolQuoteAsset)[1], symbolBaseAsset)) < 1 {
   161  		return "", errors.New("not enough data.")
   162  	}
   163  	separator := strings.Split(strings.Split(foreignname, symbolQuoteAsset)[1], symbolBaseAsset)[0]
   164  	return separator, nil
   165  }
   166  
   167  // GetExchangePairSymbols returns all foreign names on @exchange from exchangepair table.
   168  func (rdb *RelDB) GetExchangePairSymbols(exchange string) (pairs []dia.ExchangePair, err error) {
   169  	query := fmt.Sprintf("SELECT symbol,foreignname FROM %s WHERE exchange=$1", exchangepairTable)
   170  	var rows pgx.Rows
   171  	rows, err = rdb.postgresClient.Query(context.Background(), query, exchange)
   172  	if err != nil {
   173  		return
   174  	}
   175  	defer rows.Close()
   176  
   177  	for rows.Next() {
   178  		pair := dia.ExchangePair{Exchange: exchange}
   179  		err = rows.Scan(&pair.Symbol, &pair.ForeignName)
   180  		if err != nil {
   181  			return
   182  		}
   183  		pairs = append(pairs, pair)
   184  	}
   185  	return
   186  }
   187  
   188  // GetExchangePairs returns all pairs on a (centralized) @exchange.
   189  func (rdb *RelDB) GetPairsForExchange(exchange dia.Exchange, filterVerified bool, verified bool) ([]dia.ExchangePair, error) {
   190  	var (
   191  		pairs []dia.ExchangePair
   192  		rows  pgx.Rows
   193  		err   error
   194  	)
   195  	exchangeType := GetExchangeType(exchange)
   196  	if exchangeType != "CEX" {
   197  		err = errors.New("query only feasible for centralized exchanges.")
   198  		return pairs, err
   199  	}
   200  
   201  	query := fmt.Sprintf(`
   202  		SELECT  a.symbol,a.name,a.address,a.blockchain,a.decimals,b.symbol,b.name,b.address,b.blockchain,b.decimals,e.verified,e.foreignname
   203  		FROM %s e 
   204  		INNER JOIN %s a 
   205  		ON e.id_quotetoken=a.asset_id 
   206  		INNER JOIN %s b 
   207  		ON e.id_basetoken=b.asset_id 
   208  		WHERE e.exchange=$1`,
   209  		exchangepairTable,
   210  		assetTable,
   211  		assetTable,
   212  	)
   213  	if filterVerified {
   214  		query += " AND e.verified=$2"
   215  	}
   216  
   217  	if filterVerified {
   218  		rows, err = rdb.postgresClient.Query(context.Background(), query, exchange.Name, verified)
   219  	} else {
   220  		rows, err = rdb.postgresClient.Query(context.Background(), query, exchange.Name)
   221  	}
   222  	if err != nil {
   223  		return pairs, err
   224  	}
   225  	defer rows.Close()
   226  
   227  	for rows.Next() {
   228  		var (
   229  			pair          dia.ExchangePair
   230  			quoteDecimals sql.NullInt64
   231  			baseDecimals  sql.NullInt64
   232  		)
   233  
   234  		err := rows.Scan(
   235  			&pair.UnderlyingPair.QuoteToken.Symbol,
   236  			&pair.UnderlyingPair.QuoteToken.Name,
   237  			&pair.UnderlyingPair.QuoteToken.Address,
   238  			&pair.UnderlyingPair.QuoteToken.Blockchain,
   239  			&quoteDecimals,
   240  			&pair.UnderlyingPair.BaseToken.Symbol,
   241  			&pair.UnderlyingPair.BaseToken.Name,
   242  			&pair.UnderlyingPair.BaseToken.Address,
   243  			&pair.UnderlyingPair.BaseToken.Blockchain,
   244  			&baseDecimals,
   245  			&pair.Verified,
   246  			&pair.ForeignName,
   247  		)
   248  		if err != nil {
   249  			return pairs, err
   250  		}
   251  		if quoteDecimals.Valid {
   252  			pair.UnderlyingPair.QuoteToken.Decimals = uint8(quoteDecimals.Int64)
   253  		}
   254  		if baseDecimals.Valid {
   255  			pair.UnderlyingPair.BaseToken.Decimals = uint8(baseDecimals.Int64)
   256  		}
   257  		pair.Exchange = exchange.Name
   258  		pair.Symbol = pair.UnderlyingPair.QuoteToken.Symbol
   259  
   260  		pairs = append(pairs, pair)
   261  	}
   262  
   263  	return pairs, nil
   264  }
   265  
   266  func (rdb *RelDB) GetPairsForAsset(asset dia.Asset, filterVerified bool, verified bool) ([]dia.ExchangePair, error) {
   267  	var (
   268  		pairs []dia.ExchangePair
   269  		rows  pgx.Rows
   270  		err   error
   271  	)
   272  
   273  	query := fmt.Sprintf(`
   274  		SELECT  a.symbol,a.name,a.address,a.blockchain,a.decimals,b.symbol,b.name,b.address,b.blockchain,b.decimals,e.verified,e.foreignname,e.exchange
   275  		FROM %s e 
   276  		INNER JOIN %s a 
   277  		ON e.id_quotetoken=a.asset_id 
   278  		INNER JOIN %s b 
   279  		ON e.id_basetoken=b.asset_id 
   280  		WHERE ((a.address=$1 and a.blockchain=$2) OR (b.address=$3 and b.blockchain=$4))`,
   281  		exchangepairTable,
   282  		assetTable,
   283  		assetTable,
   284  	)
   285  	if filterVerified {
   286  		query += " AND e.verified=$5"
   287  	}
   288  	if filterVerified {
   289  		rows, err = rdb.postgresClient.Query(context.Background(), query, asset.Address, asset.Blockchain, asset.Address, asset.Blockchain, verified)
   290  	} else {
   291  		rows, err = rdb.postgresClient.Query(context.Background(), query, asset.Address, asset.Blockchain, asset.Address, asset.Blockchain)
   292  	}
   293  	if err != nil {
   294  		return pairs, err
   295  	}
   296  	defer rows.Close()
   297  
   298  	for rows.Next() {
   299  		var (
   300  			pair          dia.ExchangePair
   301  			quoteDecimals sql.NullInt64
   302  			baseDecimals  sql.NullInt64
   303  		)
   304  
   305  		err := rows.Scan(
   306  			&pair.UnderlyingPair.QuoteToken.Symbol,
   307  			&pair.UnderlyingPair.QuoteToken.Name,
   308  			&pair.UnderlyingPair.QuoteToken.Address,
   309  			&pair.UnderlyingPair.QuoteToken.Blockchain,
   310  			&quoteDecimals,
   311  			&pair.UnderlyingPair.BaseToken.Symbol,
   312  			&pair.UnderlyingPair.BaseToken.Name,
   313  			&pair.UnderlyingPair.BaseToken.Address,
   314  			&pair.UnderlyingPair.BaseToken.Blockchain,
   315  			&baseDecimals,
   316  			&pair.Verified,
   317  			&pair.ForeignName,
   318  			&pair.Exchange,
   319  		)
   320  		if err != nil {
   321  			return pairs, err
   322  		}
   323  		if quoteDecimals.Valid {
   324  			pair.UnderlyingPair.QuoteToken.Decimals = uint8(quoteDecimals.Int64)
   325  		}
   326  		if baseDecimals.Valid {
   327  			pair.UnderlyingPair.BaseToken.Decimals = uint8(baseDecimals.Int64)
   328  		}
   329  		pair.Symbol = pair.UnderlyingPair.QuoteToken.Symbol
   330  
   331  		pairs = append(pairs, pair)
   332  	}
   333  
   334  	return pairs, nil
   335  }
   336  
   337  // GetExchangepairsByAsset returns all exchangepairs on @exchange where @asset is quotetoken for
   338  // @basetoken=false and basetoken otherwise.
   339  func (rdb *RelDB) GetExchangepairsByAsset(asset dia.Asset, exchange string, basetoken bool) (exchangepairs []dia.ExchangePair, err error) {
   340  	var (
   341  		secondExchangeQuery string
   342  		baseQuoteQuery      string
   343  	)
   344  	if exchange == dia.BinanceExchange {
   345  		secondExchangeQuery = fmt.Sprintf(" OR ep.exchange='%s' OR ep.exchange='%s' ", dia.Binance2Exchange, dia.Binance3Exchange)
   346  	}
   347  	if exchange == dia.BKEXExchange {
   348  		secondExchangeQuery = fmt.Sprintf(" OR ep.exchange='%s'", dia.BKEX2Exchange)
   349  	}
   350  	if basetoken {
   351  		baseQuoteQuery = " b.address=$2 AND b.blockchain=$3 "
   352  	} else {
   353  		baseQuoteQuery = " a.address=$2 AND a.blockchain=$3 "
   354  	}
   355  	query := fmt.Sprintf(`
   356  		SELECT a.symbol,a.name,a.address,a.blockchain,a.decimals,b.symbol,b.name,b.address,b.blockchain,b.decimals,ep.foreignname,ep.exchange
   357  		FROM %s ep
   358  		INNER JOIN %s a
   359  		ON ep.id_quotetoken=a.asset_id
   360  		INNER JOIN %s b
   361  		ON ep.id_basetoken=b.asset_id
   362  		WHERE (ep.exchange=$1 %s)
   363  		AND %s
   364  		`,
   365  		exchangepairTable,
   366  		assetTable,
   367  		assetTable,
   368  		secondExchangeQuery,
   369  		baseQuoteQuery,
   370  	)
   371  	rows, err := rdb.postgresClient.Query(context.Background(), query, exchange, asset.Address, asset.Blockchain)
   372  	if err != nil {
   373  		return
   374  	}
   375  	defer rows.Close()
   376  
   377  	for rows.Next() {
   378  		var (
   379  			pair          dia.ExchangePair
   380  			quoteDecimals sql.NullInt64
   381  			baseDecimals  sql.NullInt64
   382  		)
   383  
   384  		err := rows.Scan(
   385  			&pair.UnderlyingPair.QuoteToken.Symbol,
   386  			&pair.UnderlyingPair.QuoteToken.Name,
   387  			&pair.UnderlyingPair.QuoteToken.Address,
   388  			&pair.UnderlyingPair.QuoteToken.Blockchain,
   389  			&quoteDecimals,
   390  			&pair.UnderlyingPair.BaseToken.Symbol,
   391  			&pair.UnderlyingPair.BaseToken.Name,
   392  			&pair.UnderlyingPair.BaseToken.Address,
   393  			&pair.UnderlyingPair.BaseToken.Blockchain,
   394  			&baseDecimals,
   395  			&pair.ForeignName,
   396  			&pair.Exchange,
   397  		)
   398  		if err != nil {
   399  			return exchangepairs, err
   400  		}
   401  		if quoteDecimals.Valid {
   402  			pair.UnderlyingPair.QuoteToken.Decimals = uint8(quoteDecimals.Int64)
   403  		}
   404  		if baseDecimals.Valid {
   405  			pair.UnderlyingPair.BaseToken.Decimals = uint8(baseDecimals.Int64)
   406  		}
   407  		pair.Symbol = pair.UnderlyingPair.QuoteToken.Symbol
   408  
   409  		exchangepairs = append(exchangepairs, pair)
   410  	}
   411  
   412  	return exchangepairs, nil
   413  }
   414  
   415  // GetNumPairs returns the number of exchangepairs/pools on @exchange.
   416  func (rdb *RelDB) GetNumPairs(exchange dia.Exchange) (numPairs int, err error) {
   417  
   418  	exchangeType := GetExchangeType(exchange)
   419  	switch exchangeType {
   420  	case "CEX":
   421  		pairs, err := rdb.GetExchangePairSymbols(exchange.Name)
   422  		if err != nil {
   423  			return len(pairs), err
   424  		}
   425  		numPairs = len(pairs)
   426  	case "DEX":
   427  		pools, err := rdb.GetAllPoolAddrsExchange(exchange.Name, float64(0))
   428  		if err != nil {
   429  			return len(pools), err
   430  		}
   431  		numPairs = len(pools)
   432  	}
   433  
   434  	return
   435  }
   436  
   437  // GetAllExchangeAssets returns all assets traded as quotetoken on a CEX.
   438  func (rdb *RelDB) GetAllExchangeAssets(verified bool) (assets []dia.Asset, err error) {
   439  	query := fmt.Sprintf(`
   440  		SELECT DISTINCT (a.address,a.blockchain), a.symbol,a.name,a.decimals FROM %s a
   441  		INNER JOIN %s ep
   442  		ON a.asset_id=ep.id_quotetoken
   443  		WHERE ep.verified=$1
   444  		`,
   445  		assetTable,
   446  		exchangepairTable,
   447  	)
   448  	var rows pgx.Rows
   449  	rows, err = rdb.postgresClient.Query(context.Background(), query, verified)
   450  	if err != nil {
   451  		return
   452  	}
   453  	defer rows.Close()
   454  
   455  	for rows.Next() {
   456  		var (
   457  			asset    dia.Asset
   458  			tmp      []interface{}
   459  			decimals sql.NullInt64
   460  		)
   461  
   462  		err = rows.Scan(&tmp, &asset.Symbol, &asset.Name, &decimals)
   463  		if err != nil {
   464  			return
   465  		}
   466  		if decimals.Valid {
   467  			asset.Decimals = uint8(decimals.Int64)
   468  		}
   469  		if len(tmp) == 2 {
   470  			asset.Address = tmp[0].(string)
   471  			asset.Blockchain = tmp[1].(string)
   472  		}
   473  		assets = append(assets, asset)
   474  	}
   475  	return
   476  }