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 }