code.vegaprotocol.io/vega@v0.79.0/datanode/sqlstore/market_data.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package sqlstore
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"time"
    23  
    24  	"code.vegaprotocol.io/vega/datanode/entities"
    25  	"code.vegaprotocol.io/vega/datanode/metrics"
    26  	"code.vegaprotocol.io/vega/libs/ptr"
    27  	"code.vegaprotocol.io/vega/logging"
    28  	v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2"
    29  
    30  	"github.com/georgysavva/scany/pgxscan"
    31  	"github.com/jackc/pgx/v4"
    32  )
    33  
    34  var marketdataOrdering = TableOrdering{
    35  	ColumnOrdering{Name: "synthetic_time", Sorting: ASC},
    36  }
    37  
    38  type MarketData struct {
    39  	*ConnectionSource
    40  	columns    []string
    41  	marketData []*entities.MarketData
    42  }
    43  
    44  var ErrInvalidDateRange = errors.New("invalid date range, end date must be after start date")
    45  
    46  const selectMarketDataColumns = `synthetic_time, tx_hash, vega_time, seq_num,
    47  			market, mark_price, best_bid_price, best_bid_volume,
    48  			best_offer_price, best_offer_volume, best_static_bid_price, best_static_bid_volume,
    49  			best_static_offer_price, best_static_offer_volume, mid_price, static_mid_price,
    50  			open_interest, auction_end, auction_start, indicative_price, indicative_volume,
    51  			market_trading_mode, auction_trigger, extension_trigger, target_stake,
    52  			supplied_stake, price_monitoring_bounds, market_value_proxy, liquidity_provider_fee_shares,
    53  			market_state, next_mark_to_market, coalesce(market_growth, 0) as market_growth,
    54  			coalesce(last_traded_price, 0) as last_traded_price, product_data, liquidity_provider_sla, next_network_closeout, mark_price_type, mark_price_state, active_protocol_automated_purchase`
    55  
    56  func NewMarketData(connectionSource *ConnectionSource) *MarketData {
    57  	return &MarketData{
    58  		ConnectionSource: connectionSource,
    59  		columns: []string{
    60  			"synthetic_time", "tx_hash", "vega_time", "seq_num",
    61  			"market", "mark_price", "best_bid_price", "best_bid_volume",
    62  			"best_offer_price", "best_offer_volume", "best_static_bid_price", "best_static_bid_volume",
    63  			"best_static_offer_price", "best_static_offer_volume", "mid_price", "static_mid_price",
    64  			"open_interest", "auction_end", "auction_start", "indicative_price", "indicative_volume",
    65  			"market_trading_mode", "auction_trigger", "extension_trigger", "target_stake",
    66  			"supplied_stake", "price_monitoring_bounds", "market_value_proxy", "liquidity_provider_fee_shares",
    67  			"market_state", "next_mark_to_market", "market_growth", "last_traded_price", "product_data",
    68  			"liquidity_provider_sla", "next_network_closeout", "mark_price_type", "mark_price_state", "active_protocol_automated_purchase",
    69  		},
    70  	}
    71  }
    72  
    73  func (md *MarketData) Add(data *entities.MarketData) error {
    74  	md.marketData = append(md.marketData, data)
    75  	return nil
    76  }
    77  
    78  func (md *MarketData) Flush(ctx context.Context) ([]*entities.MarketData, error) {
    79  	rows := make([][]interface{}, 0, len(md.marketData))
    80  	for _, data := range md.marketData {
    81  		rows = append(rows, []interface{}{
    82  			data.SyntheticTime, data.TxHash, data.VegaTime, data.SeqNum,
    83  			data.Market, data.MarkPrice,
    84  			data.BestBidPrice, data.BestBidVolume, data.BestOfferPrice, data.BestOfferVolume,
    85  			data.BestStaticBidPrice, data.BestStaticBidVolume, data.BestStaticOfferPrice, data.BestStaticOfferVolume,
    86  			data.MidPrice, data.StaticMidPrice, data.OpenInterest, data.AuctionEnd,
    87  			data.AuctionStart, data.IndicativePrice, data.IndicativeVolume, data.MarketTradingMode,
    88  			data.AuctionTrigger, data.ExtensionTrigger, data.TargetStake, data.SuppliedStake,
    89  			data.PriceMonitoringBounds, data.MarketValueProxy, data.LiquidityProviderFeeShares, data.MarketState,
    90  			data.NextMarkToMarket, data.MarketGrowth, data.LastTradedPrice,
    91  			data.ProductData, data.LiquidityProviderSLA, data.NextNetworkCloseout, data.MarkPriceType, data.MarkPriceState, data.ActiveProtocolAutomatedPurchase,
    92  		})
    93  	}
    94  	defer metrics.StartSQLQuery("MarketData", "Flush")()
    95  	if rows != nil {
    96  		copyCount, err := md.CopyFrom(
    97  			ctx,
    98  			pgx.Identifier{"market_data"}, md.columns, pgx.CopyFromRows(rows),
    99  		)
   100  		if err != nil {
   101  			return nil, fmt.Errorf("failed to copy market data into database:%w", err)
   102  		}
   103  
   104  		if copyCount != int64(len(rows)) {
   105  			return nil, fmt.Errorf("copied %d market data rows into the database, expected to copy %d", copyCount, len(rows))
   106  		}
   107  	}
   108  
   109  	flushed := md.marketData
   110  	md.marketData = nil
   111  
   112  	return flushed, nil
   113  }
   114  
   115  func (md *MarketData) GetMarketDataByID(ctx context.Context, marketID string) (entities.MarketData, error) {
   116  	defer metrics.StartSQLQuery("MarketData", "GetMarketDataByID")()
   117  	md.log.Debug("Retrieving market data from Postgres", logging.String("market-id", marketID))
   118  
   119  	var marketData entities.MarketData
   120  	query := fmt.Sprintf("select %s from current_market_data where market = $1", selectMarketDataColumns)
   121  	return marketData, md.wrapE(pgxscan.Get(ctx, md.ConnectionSource, &marketData, query, entities.MarketID(marketID)))
   122  }
   123  
   124  func (md *MarketData) GetMarketsData(ctx context.Context) ([]entities.MarketData, error) {
   125  	md.log.Debug("Retrieving markets data from Postgres")
   126  
   127  	var marketData []entities.MarketData
   128  	query := fmt.Sprintf("select %s from current_market_data", selectMarketDataColumns)
   129  
   130  	defer metrics.StartSQLQuery("MarketData", "GetMarketsData")()
   131  	err := pgxscan.Select(ctx, md.ConnectionSource, &marketData, query)
   132  
   133  	return marketData, err
   134  }
   135  
   136  func (md *MarketData) GetHistoricMarketData(ctx context.Context, marketID string, start, end *time.Time, pagination entities.Pagination) ([]entities.MarketData, entities.PageInfo, error) {
   137  	if start != nil && end != nil && end.Before(*start) {
   138  		return nil, entities.PageInfo{}, ErrInvalidDateRange
   139  	}
   140  
   141  	switch p := pagination.(type) {
   142  	case entities.CursorPagination:
   143  		return md.getHistoricMarketData(ctx, marketID, start, end, p)
   144  	default:
   145  		panic("unsupported pagination")
   146  	}
   147  }
   148  
   149  func (md *MarketData) getHistoricMarketData(ctx context.Context, marketID string, start, end *time.Time, pagination entities.CursorPagination) ([]entities.MarketData, entities.PageInfo, error) {
   150  	defer metrics.StartSQLQuery("MarketData", "getHistoricMarketData")()
   151  	market := entities.MarketID(marketID)
   152  
   153  	selectStatement := fmt.Sprintf(`select %s from market_data`, selectMarketDataColumns)
   154  	args := make([]interface{}, 0)
   155  
   156  	var (
   157  		query    string
   158  		err      error
   159  		pageInfo entities.PageInfo
   160  	)
   161  
   162  	switch {
   163  	case start != nil && end != nil:
   164  		query = fmt.Sprintf(`%s where market = %s and vega_time between %s and %s`, selectStatement,
   165  			nextBindVar(&args, market),
   166  			nextBindVar(&args, *start),
   167  			nextBindVar(&args, *end),
   168  		)
   169  	case start != nil:
   170  		query = fmt.Sprintf(`%s where market = %s and vega_time >= %s`, selectStatement,
   171  			nextBindVar(&args, market),
   172  			nextBindVar(&args, *start))
   173  	case end != nil:
   174  		query = fmt.Sprintf(`%s where market = %s and vega_time <= %s`, selectStatement,
   175  			nextBindVar(&args, market),
   176  			nextBindVar(&args, *end))
   177  	default:
   178  		query = fmt.Sprintf(`%s where market = %s`, selectStatement,
   179  			nextBindVar(&args, market))
   180  		// We want to restrict to just the last price update so we can override the pagination and force it to return just the 1 result
   181  		first := ptr.From(int32(1))
   182  		if pagination, err = entities.NewCursorPagination(first, nil, nil, nil, true); err != nil {
   183  			return nil, pageInfo, err
   184  		}
   185  	}
   186  
   187  	query, args, err = PaginateQuery[entities.MarketDataCursor](query, args, marketdataOrdering, pagination)
   188  	if err != nil {
   189  		return nil, pageInfo, err
   190  	}
   191  	var pagedData []entities.MarketData
   192  
   193  	if err = pgxscan.Select(ctx, md.ConnectionSource, &pagedData, query, args...); err != nil {
   194  		return pagedData, pageInfo, err
   195  	}
   196  
   197  	pagedData, pageInfo = entities.PageEntities[*v2.MarketDataEdge](pagedData, pagination)
   198  
   199  	return pagedData, pageInfo, nil
   200  }