code.vegaprotocol.io/vega@v0.79.0/core/integration/steps/the_markets.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 steps 17 18 import ( 19 "context" 20 "fmt" 21 "strings" 22 "time" 23 24 "code.vegaprotocol.io/vega/core/collateral" 25 "code.vegaprotocol.io/vega/core/datasource" 26 "code.vegaprotocol.io/vega/core/datasource/external/signedoracle" 27 "code.vegaprotocol.io/vega/core/integration/steps/market" 28 "code.vegaprotocol.io/vega/core/netparams" 29 "code.vegaprotocol.io/vega/core/types" 30 "code.vegaprotocol.io/vega/libs/num" 31 "code.vegaprotocol.io/vega/libs/ptr" 32 proto "code.vegaprotocol.io/vega/protos/vega" 33 34 "github.com/cucumber/godog" 35 ) 36 37 func TheMarketsUpdated( 38 config *market.Config, 39 executionEngine Execution, 40 existing []types.Market, 41 netparams *netparams.Store, 42 table *godog.Table, 43 ) ([]types.Market, error) { 44 rows := parseMarketsUpdateTable(table) 45 // existing markets to update 46 validByID := make(map[string]*types.Market, len(existing)) 47 for i := range existing { 48 m := existing[i] 49 validByID[m.ID] = &existing[i] 50 } 51 updates := make([]types.UpdateMarket, 0, len(rows)) 52 updated := make([]*types.Market, 0, len(rows)) 53 for _, row := range rows { 54 upd := marketUpdateRow{row: row} 55 // check if market exists 56 current, ok := validByID[upd.id()] 57 if !ok { 58 return nil, fmt.Errorf("unknown market id %s", upd.id()) 59 } 60 61 mUpdate, err := marketUpdate(config, current, upd) 62 if err != nil { 63 return existing, err 64 } 65 66 updates = append(updates, mUpdate) 67 updated = append(updated, current) 68 } 69 if err := updateMarkets(updated, updates, executionEngine); err != nil { 70 return nil, err 71 } 72 // we have been using pointers internally, so we should be returning the accurate state here. 73 return existing, nil 74 } 75 76 func TheMarkets( 77 config *market.Config, 78 executionEngine Execution, 79 collateralEngine *collateral.Engine, 80 netparams *netparams.Store, 81 now time.Time, 82 table *godog.Table, 83 ) ([]types.Market, error) { 84 rows := parseMarketsTable(table) 85 markets := make([]types.Market, 0, len(rows)) 86 87 for _, row := range rows { 88 mRow := marketRow{row: row} 89 isPerp := mRow.isPerp() 90 if !isPerp { 91 // check if we have a perp counterpart for this oracle, if so, swap to that 92 if oName := mRow.oracleConfig(); oName != config.OracleConfigs.CheckName(oName) { 93 isPerp = true 94 } 95 } 96 var mkt types.Market 97 if isPerp { 98 mkt = newPerpMarket(config, mRow) 99 } else { 100 mkt = newMarket(config, mRow) 101 } 102 markets = append(markets, mkt) 103 } 104 105 if err := enableMarketAssets(markets, collateralEngine); err != nil { 106 return nil, err 107 } 108 109 if err := enableVoteAsset(collateralEngine); err != nil { 110 return nil, err 111 } 112 113 for i, row := range rows { 114 if err := executionEngine.SubmitMarket(context.Background(), &markets[i], "proposerID", now); err != nil { 115 return nil, fmt.Errorf("couldn't submit market(%s): %v", markets[i].ID, err) 116 } 117 // only start opening auction if the market is explicitly marked to leave opening auction now 118 if !row.HasColumn("is passed") || row.Bool("is passed") { 119 if err := executionEngine.StartOpeningAuction(context.Background(), markets[i].ID); err != nil { 120 return nil, fmt.Errorf("could not start opening auction for market %s: %v", markets[i].ID, err) 121 } 122 } 123 } 124 return markets, nil 125 } 126 127 func TheSuccesorMarketIsEnacted(sID string, markets []types.Market, exec Execution) error { 128 for _, mkt := range markets { 129 if mkt.ID == sID { 130 parent := mkt.ParentMarketID 131 if err := exec.SucceedMarket(context.Background(), sID, parent); err != nil { 132 return fmt.Errorf("couldn't enact the successor market %s (parent: %s): %v", sID, parent, err) 133 } 134 return nil 135 } 136 } 137 return fmt.Errorf("couldn't enact successor market %s - no such market ID", sID) 138 } 139 140 func updateMarkets(markets []*types.Market, updates []types.UpdateMarket, executionEngine Execution) error { 141 for i, mkt := range markets { 142 if err := executionEngine.UpdateMarket(context.Background(), mkt); err != nil { 143 return fmt.Errorf("couldn't update market(%s) - updates %#v: %+v", mkt.ID, updates[i], err) 144 } 145 } 146 return nil 147 } 148 149 func enableMarketAssets(markets []types.Market, collateralEngine *collateral.Engine) error { 150 assetsToEnable := map[string]struct{}{} 151 for _, mkt := range markets { 152 assets, _ := mkt.GetAssets() 153 assetsToEnable[assets[0]] = struct{}{} 154 } 155 for assetToEnable := range assetsToEnable { 156 err := collateralEngine.EnableAsset(context.Background(), types.Asset{ 157 ID: assetToEnable, 158 Details: &types.AssetDetails{ 159 Quantum: num.DecimalOne(), 160 Symbol: assetToEnable, 161 }, 162 }) 163 if err != nil && err != collateral.ErrAssetAlreadyEnabled { 164 return fmt.Errorf("couldn't enable asset(%s): %v", assetToEnable, err) 165 } 166 } 167 return nil 168 } 169 170 func enableVoteAsset(collateralEngine *collateral.Engine) error { 171 voteAsset := types.Asset{ 172 ID: "VOTE", 173 Details: &types.AssetDetails{ 174 Name: "VOTE", 175 Symbol: "VOTE", 176 Decimals: 5, 177 Source: &types.AssetDetailsBuiltinAsset{ 178 BuiltinAsset: &types.BuiltinAsset{ 179 MaxFaucetAmountMint: num.NewUint(10), 180 }, 181 }, 182 }, 183 } 184 185 err := collateralEngine.EnableAsset(context.Background(), voteAsset) 186 if err != nil { 187 if err != collateral.ErrAssetAlreadyEnabled { 188 return fmt.Errorf("couldn't enable asset(%s): %v", voteAsset.ID, err) 189 } 190 } 191 return nil 192 } 193 194 // marketUpdate return the UpdateMarket type just for clear error reporting and sanity checks ATM. 195 func marketUpdate(config *market.Config, existing *types.Market, row marketUpdateRow) (types.UpdateMarket, error) { 196 update := types.UpdateMarket{ 197 MarketID: existing.ID, 198 Changes: &types.UpdateMarketConfiguration{}, 199 } 200 liqStrat := existing.LiquidationStrategy 201 if ls, ok := row.liquidationStrat(); ok { 202 lqs, err := config.LiquidationStrat.Get(ls) 203 if err != nil { 204 panic(err) 205 } 206 if liqStrat, err = types.LiquidationStrategyFromProto(lqs); err != nil { 207 panic(err) 208 } 209 } 210 update.Changes.LiquidationStrategy = liqStrat 211 existing.LiquidationStrategy = liqStrat 212 213 // product update 214 if oracle, ok := row.oracleConfig(); ok { 215 // update product -> use type switch even though currently only futures exist 216 switch ti := existing.TradableInstrument.Instrument.Product.(type) { 217 case *types.InstrumentFuture: 218 oracleSettlement, err := config.OracleConfigs.GetFuture(oracle, "settlement data") 219 if err != nil { 220 panic(err) 221 } 222 oracleTermination, err := config.OracleConfigs.GetFuture(oracle, "trading termination") 223 if err != nil { 224 panic(err) 225 } 226 // we probably want to X-check the current spec, and make sure only filters + pubkeys are changed 227 settleSpec := datasource.FromOracleSpecProto(oracleSettlement.Spec) 228 termSpec := datasource.FromOracleSpecProto(oracleTermination.Spec) 229 settlementDecimals := config.OracleConfigs.GetSettlementDataDP(oracle) 230 filters := settleSpec.Data.GetFilters() 231 futureUp := &types.UpdateFutureProduct{ 232 QuoteName: ti.Future.QuoteName, 233 DataSourceSpecForSettlementData: *datasource.NewDefinition( 234 datasource.ContentTypeOracle, 235 ).SetOracleConfig( 236 &signedoracle.SpecConfiguration{ 237 Signers: settleSpec.Data.GetSigners(), 238 Filters: filters, 239 }, 240 ), 241 DataSourceSpecForTradingTermination: *datasource.NewDefinition( 242 datasource.ContentTypeOracle, 243 ).SetOracleConfig( 244 &signedoracle.SpecConfiguration{ 245 Signers: settleSpec.Data.GetSigners(), 246 Filters: filters, 247 }, 248 ), 249 DataSourceSpecBinding: datasource.SpecBindingForFutureFromProto(&proto.DataSourceSpecToFutureBinding{ 250 SettlementDataProperty: oracleSettlement.Binding.SettlementDataProperty, 251 TradingTerminationProperty: oracleTermination.Binding.TradingTerminationProperty, 252 }), 253 } 254 ti.Future.DataSourceSpecForSettlementData = datasource.SpecFromDefinition(*settleSpec.Data.SetFilterDecimals(uint64(settlementDecimals))) 255 ti.Future.DataSourceSpecForTradingTermination = termSpec 256 ti.Future.DataSourceSpecBinding = futureUp.DataSourceSpecBinding 257 // ensure we update the existing market 258 existing.TradableInstrument.Instrument.Product = ti 259 update.Changes.Instrument = &types.UpdateInstrumentConfiguration{ 260 Product: &types.UpdateInstrumentConfigurationFuture{ 261 Future: futureUp, 262 }, 263 } 264 case *types.InstrumentPerps: 265 perp, err := config.OracleConfigs.GetFullPerp(oracle) 266 if err != nil { 267 panic(err) 268 } 269 pfp := types.PerpsFromProto(perp) 270 if pfp.DataSourceSpecForSettlementData == nil || pfp.DataSourceSpecForSettlementData.Data == nil { 271 panic("Oracle does not have a data source for settlement data") 272 } 273 if pfp.DataSourceSpecForSettlementSchedule == nil || pfp.DataSourceSpecForSettlementSchedule.Data == nil { 274 panic("Oracle does not have a data source for settlement schedule") 275 } 276 update.Changes.Instrument = &types.UpdateInstrumentConfiguration{ 277 Product: &types.UpdateInstrumentConfigurationPerps{ 278 Perps: &types.UpdatePerpsProduct{ 279 QuoteName: pfp.QuoteName, 280 MarginFundingFactor: pfp.MarginFundingFactor, 281 ClampLowerBound: pfp.ClampLowerBound, 282 ClampUpperBound: pfp.ClampUpperBound, 283 FundingRateScalingFactor: pfp.FundingRateScalingFactor, 284 FundingRateLowerBound: pfp.FundingRateLowerBound, 285 FundingRateUpperBound: pfp.FundingRateUpperBound, 286 DataSourceSpecForSettlementData: *pfp.DataSourceSpecForSettlementData.Data, 287 DataSourceSpecForSettlementSchedule: *pfp.DataSourceSpecForSettlementSchedule.Data, 288 DataSourceSpecBinding: pfp.DataSourceSpecBinding, 289 }, 290 }, 291 } 292 // apply update 293 ti.Perps.ClampLowerBound = pfp.ClampLowerBound 294 ti.Perps.ClampUpperBound = pfp.ClampUpperBound 295 ti.Perps.FundingRateScalingFactor = pfp.FundingRateScalingFactor 296 ti.Perps.FundingRateUpperBound = pfp.FundingRateUpperBound 297 ti.Perps.FundingRateLowerBound = pfp.FundingRateLowerBound 298 ti.Perps.MarginFundingFactor = pfp.MarginFundingFactor 299 ti.Perps.DataSourceSpecBinding = pfp.DataSourceSpecBinding 300 ti.Perps.DataSourceSpecForSettlementData = pfp.DataSourceSpecForSettlementData 301 ti.Perps.DataSourceSpecForSettlementSchedule = pfp.DataSourceSpecForSettlementSchedule 302 existing.TradableInstrument.Instrument.Product = ti 303 default: 304 panic("unsuported product") 305 } 306 update.Changes.Instrument.Code = existing.TradableInstrument.Instrument.Code 307 } 308 // price monitoring 309 if pm, ok := row.priceMonitoring(); ok { 310 priceMonitoring, err := config.PriceMonitoring.Get(pm) 311 if err != nil { 312 panic(err) 313 } 314 pmt := types.PriceMonitoringSettingsFromProto(priceMonitoring) 315 // update existing 316 existing.PriceMonitoringSettings.Parameters = pmt.Parameters 317 update.Changes.PriceMonitoringParameters = pmt.Parameters 318 } 319 // liquidity monitoring 320 if lm, ok := row.liquidityMonitoring(); ok { 321 liqMon, err := config.LiquidityMonitoring.GetType(lm) 322 if err != nil { 323 panic(err) 324 } 325 existing.LiquidityMonitoringParameters = liqMon 326 update.Changes.LiquidityMonitoringParameters = liqMon 327 } 328 // risk model 329 if rm, ok := row.riskModel(); ok { 330 tip := existing.TradableInstrument.IntoProto() 331 if err := config.RiskModels.LoadModel(rm, tip); err != nil { 332 panic(err) 333 } 334 current := types.TradableInstrumentFromProto(tip) 335 // find the correct params: 336 switch { 337 case current.GetSimpleRiskModel() != nil: 338 update.Changes.RiskParameters = types.UpdateMarketConfigurationSimple{ 339 Simple: current.GetSimpleRiskModel().Params, 340 } 341 case current.GetLogNormalRiskModel() != nil: 342 update.Changes.RiskParameters = types.UpdateMarketConfigurationLogNormal{ 343 LogNormal: current.GetLogNormalRiskModel(), 344 } 345 default: 346 panic("Unsupported risk model parameters") 347 } 348 // update existing 349 existing.TradableInstrument = current 350 } 351 // linear linSlippage factor 352 linSlippage, ok := row.tryLinearSlippageFactor() 353 if ok { 354 slippageD := num.DecimalFromFloat(linSlippage) 355 update.Changes.LinearSlippageFactor = slippageD 356 existing.LinearSlippageFactor = slippageD 357 } else { 358 update.Changes.LinearSlippageFactor = existing.LinearSlippageFactor 359 } 360 361 // quadratic slippage factor 362 quadSlippage, ok := row.tryQuadraticSlippageFactor() 363 if ok { 364 slippageD := num.DecimalFromFloat(quadSlippage) 365 update.Changes.QuadraticSlippageFactor = slippageD 366 existing.QuadraticSlippageFactor = slippageD 367 } else { 368 update.Changes.QuadraticSlippageFactor = existing.QuadraticSlippageFactor 369 } 370 371 if liquiditySla, ok := row.tryLiquiditySLA(); ok { 372 sla, err := config.LiquiditySLAParams.Get(liquiditySla) 373 if err != nil { 374 return update, err 375 } 376 slaParams := types.LiquiditySLAParamsFromProto(sla) 377 // update existing 378 existing.LiquiditySLAParams = slaParams 379 update.Changes.LiquiditySLAParameters = slaParams 380 } 381 382 update.Changes.LiquidityFeeSettings = existing.Fees.LiquidityFeeSettings 383 if liquidityFeeSettings, ok := row.tryLiquidityFeeSettings(); ok { 384 settings, err := config.FeesConfig.Get(liquidityFeeSettings) 385 if err != nil { 386 return update, err 387 } 388 s := types.LiquidityFeeSettingsFromProto(settings.LiquidityFeeSettings) 389 existing.Fees.LiquidityFeeSettings = s 390 update.Changes.LiquidityFeeSettings = s 391 } 392 393 if existing.MarkPriceConfiguration != nil { 394 markPriceConfig := existing.MarkPriceConfiguration.DeepClone() 395 markPriceConfig.CompositePriceType = row.markPriceType() 396 397 if row.row.HasColumn("decay power") { 398 markPriceConfig.DecayPower = row.decayPower() 399 } 400 if row.row.HasColumn("decay weight") { 401 markPriceConfig.DecayWeight = row.decayWeight() 402 } 403 if row.row.HasColumn("cash amount") { 404 markPriceConfig.CashAmount = row.cashAmount() 405 } 406 if row.row.HasColumn("source weights") { 407 markPriceConfig.SourceWeights = row.priceSourceWeights() 408 } 409 if row.row.HasColumn("source staleness tolerance") { 410 markPriceConfig.SourceStalenessTolerance = row.priceSourceStalnessTolerance() 411 } 412 if row.row.HasColumn("oracle1") { 413 markPriceConfig.DataSources, markPriceConfig.SpecBindingForCompositePrice = row.oracles(config) 414 } 415 update.Changes.MarkPriceConfiguration = markPriceConfig 416 existing.MarkPriceConfiguration = markPriceConfig 417 } 418 update.Changes.TickSize = row.tickSize() 419 return update, nil 420 } 421 422 func newPerpMarket(config *market.Config, row marketRow) types.Market { 423 fees, err := config.FeesConfig.Get(row.fees()) 424 if err != nil { 425 panic(err) 426 } 427 428 perp, err := config.OracleConfigs.GetFullPerp(row.oracleConfig()) 429 if err != nil { 430 panic(err) 431 } 432 pfp := types.PerpsFromProto(perp) 433 asset, quote := row.asset(), row.quoteName() 434 // long term, this should become redundant, but for the perps flag this is useful to have 435 if asset != pfp.SettlementAsset { 436 pfp.SettlementAsset = asset 437 } 438 if quote != pfp.QuoteName { 439 pfp.QuoteName = row.quoteName() 440 } 441 442 priceMonitoring, err := config.PriceMonitoring.Get(row.priceMonitoring()) 443 if err != nil { 444 panic(err) 445 } 446 447 marginCalculator, err := config.MarginCalculators.Get(row.marginCalculator()) 448 if err != nil { 449 panic(err) 450 } 451 452 liqMon, err := config.LiquidityMonitoring.GetType(row.liquidityMonitoring()) 453 if err != nil { 454 panic(err) 455 } 456 lqs, err := config.LiquidationStrat.Get(row.liquidationStrat()) 457 if err != nil { 458 panic(err) 459 } 460 liqStrat, err := types.LiquidationStrategyFromProto(lqs) 461 if err != nil { 462 panic(err) 463 } 464 465 linearSlippageFactor := row.linearSlippageFactor() 466 quadraticSlippageFactor := row.quadraticSlippageFactor() 467 468 slaParams, err := config.LiquiditySLAParams.Get(row.liquiditySLA()) 469 if err != nil { 470 panic(err) 471 } 472 473 pCap := row.getCapped() 474 specs, binding := row.oracles(config) 475 markPriceConfig := &types.CompositePriceConfiguration{ 476 CompositePriceType: row.markPriceType(), 477 DecayWeight: row.decayWeight(), 478 DecayPower: row.decayPower(), 479 CashAmount: row.cashAmount(), 480 SourceWeights: row.priceSourceWeights(), 481 SourceStalenessTolerance: row.priceSourceStalnessTolerance(), 482 DataSources: specs, 483 SpecBindingForCompositePrice: binding, 484 } 485 486 m := types.Market{ 487 TradingMode: types.MarketTradingModeContinuous, 488 State: types.MarketStateActive, 489 ID: row.id(), 490 DecimalPlaces: row.decimalPlaces(), 491 PositionDecimalPlaces: row.positionDecimalPlaces(), 492 Fees: types.FeesFromProto(fees), 493 LiquidationStrategy: liqStrat, 494 TradableInstrument: &types.TradableInstrument{ 495 Instrument: &types.Instrument{ 496 ID: fmt.Sprintf("Crypto/%s/Perpetual", row.id()), 497 Code: fmt.Sprintf("CRYPTO/%v", row.id()), 498 Name: fmt.Sprintf("%s perpetual", row.id()), 499 Metadata: &types.InstrumentMetadata{ 500 Tags: []string{ 501 "asset_class:fx/crypto", 502 "product:perpetual", 503 }, 504 }, 505 Product: &types.InstrumentPerps{ 506 Perps: pfp, 507 }, 508 }, 509 MarginCalculator: types.MarginCalculatorFromProto(marginCalculator), 510 }, 511 OpeningAuction: openingAuction(row), 512 PriceMonitoringSettings: types.PriceMonitoringSettingsFromProto(priceMonitoring), 513 LiquidityMonitoringParameters: liqMon, 514 LinearSlippageFactor: num.DecimalFromFloat(linearSlippageFactor), 515 QuadraticSlippageFactor: num.DecimalFromFloat(quadraticSlippageFactor), 516 LiquiditySLAParams: types.LiquiditySLAParamsFromProto(slaParams), 517 MarkPriceConfiguration: markPriceConfig, 518 TickSize: row.tickSize(), 519 } 520 521 if row.isSuccessor() { 522 m.ParentMarketID = row.parentID() 523 m.InsurancePoolFraction = row.insuranceFraction() 524 // increase opening auction duration by a given amount 525 m.OpeningAuction.Duration += row.successorAuction() 526 } 527 528 tip := m.TradableInstrument.IntoProto() 529 err = config.RiskModels.LoadModel(row.riskModel(), tip) 530 if row.IsCapped() { 531 tip.MarginCalculator.FullyCollateralised = ptr.From(pCap.FullyCollateralised) 532 } 533 m.TradableInstrument = types.TradableInstrumentFromProto(tip) 534 if err != nil { 535 panic(err) 536 } 537 538 return m 539 } 540 541 func newMarket(config *market.Config, row marketRow) types.Market { 542 fees, err := config.FeesConfig.Get(row.fees()) 543 if err != nil { 544 panic(err) 545 } 546 547 oracleConfigForSettlement, err := config.OracleConfigs.GetFuture(row.oracleConfig(), "settlement data") 548 if err != nil { 549 panic(err) 550 } 551 552 oracleConfigForTradingTermination, err := config.OracleConfigs.GetFuture(row.oracleConfig(), "trading termination") 553 if err != nil { 554 panic(err) 555 } 556 557 settlementDataDecimals := config.OracleConfigs.GetSettlementDataDP(row.oracleConfig()) 558 settlSpec := datasource.FromOracleSpecProto(oracleConfigForSettlement.Spec) 559 var binding proto.DataSourceSpecToFutureBinding 560 binding.SettlementDataProperty = oracleConfigForSettlement.Binding.SettlementDataProperty 561 binding.TradingTerminationProperty = oracleConfigForTradingTermination.Binding.TradingTerminationProperty 562 563 priceMonitoring, err := config.PriceMonitoring.Get(row.priceMonitoring()) 564 if err != nil { 565 panic(err) 566 } 567 568 marginCalculator, err := config.MarginCalculators.Get(row.marginCalculator()) 569 if err != nil { 570 panic(err) 571 } 572 573 liqMon, err := config.LiquidityMonitoring.GetType(row.liquidityMonitoring()) 574 if err != nil { 575 panic(err) 576 } 577 578 lqs, err := config.LiquidationStrat.Get(row.liquidationStrat()) 579 if err != nil { 580 panic(err) 581 } 582 liqStrat, err := types.LiquidationStrategyFromProto(lqs) 583 if err != nil { 584 panic(err) 585 } 586 587 linearSlippageFactor := row.linearSlippageFactor() 588 quadraticSlippageFactor := row.quadraticSlippageFactor() 589 590 slaParams, err := config.LiquiditySLAParams.Get(row.liquiditySLA()) 591 if err != nil { 592 panic(err) 593 } 594 595 sources, bindings := row.oracles(config) 596 markPriceConfig := &types.CompositePriceConfiguration{ 597 CompositePriceType: row.markPriceType(), 598 DecayWeight: row.decayWeight(), 599 DecayPower: row.decayPower(), 600 CashAmount: row.cashAmount(), 601 SourceWeights: row.priceSourceWeights(), 602 SourceStalenessTolerance: row.priceSourceStalnessTolerance(), 603 DataSources: sources, 604 SpecBindingForCompositePrice: bindings, 605 } 606 607 pCap := row.getCapped() 608 m := types.Market{ 609 TradingMode: types.MarketTradingModeContinuous, 610 State: types.MarketStateActive, 611 ID: row.id(), 612 DecimalPlaces: row.decimalPlaces(), 613 PositionDecimalPlaces: row.positionDecimalPlaces(), 614 Fees: types.FeesFromProto(fees), 615 LiquidationStrategy: liqStrat, 616 TradableInstrument: &types.TradableInstrument{ 617 Instrument: &types.Instrument{ 618 ID: fmt.Sprintf("Crypto/%s/Futures", row.id()), 619 Code: fmt.Sprintf("CRYPTO/%v", row.id()), 620 Name: fmt.Sprintf("%s future", row.id()), 621 Metadata: &types.InstrumentMetadata{ 622 Tags: []string{ 623 "asset_class:fx/crypto", 624 "product:futures", 625 }, 626 }, 627 Product: &types.InstrumentFuture{ 628 Future: &types.Future{ 629 SettlementAsset: row.asset(), 630 QuoteName: row.quoteName(), 631 DataSourceSpecForSettlementData: datasource.SpecFromDefinition(*settlSpec.Data.SetFilterDecimals(uint64(settlementDataDecimals))), 632 DataSourceSpecForTradingTermination: datasource.SpecFromProto(oracleConfigForTradingTermination.Spec.ExternalDataSourceSpec.Spec), 633 DataSourceSpecBinding: datasource.SpecBindingForFutureFromProto(&binding), 634 Cap: pCap, 635 }, 636 }, 637 }, 638 MarginCalculator: types.MarginCalculatorFromProto(marginCalculator), 639 }, 640 OpeningAuction: openingAuction(row), 641 PriceMonitoringSettings: types.PriceMonitoringSettingsFromProto(priceMonitoring), 642 LiquidityMonitoringParameters: liqMon, 643 LinearSlippageFactor: num.DecimalFromFloat(linearSlippageFactor), 644 QuadraticSlippageFactor: num.DecimalFromFloat(quadraticSlippageFactor), 645 LiquiditySLAParams: types.LiquiditySLAParamsFromProto(slaParams), 646 MarkPriceConfiguration: markPriceConfig, 647 TickSize: row.tickSize(), 648 AllowedEmptyAmmLevels: row.allowedEmptyAMMLevels(), 649 } 650 651 if row.isSuccessor() { 652 m.ParentMarketID = row.parentID() 653 m.InsurancePoolFraction = row.insuranceFraction() 654 // increase opening auction duration by a given amount 655 m.OpeningAuction.Duration += row.successorAuction() 656 } 657 658 tip := m.TradableInstrument.IntoProto() 659 if row.IsCapped() { 660 tip.MarginCalculator.FullyCollateralised = ptr.From(pCap.FullyCollateralised) 661 } 662 err = config.RiskModels.LoadModel(row.riskModel(), tip) 663 m.TradableInstrument = types.TradableInstrumentFromProto(tip) 664 if err != nil { 665 panic(err) 666 } 667 668 return m 669 } 670 671 func openingAuction(row marketRow) *types.AuctionDuration { 672 auction := &types.AuctionDuration{ 673 Duration: row.auctionDuration(), 674 } 675 676 if auction.Duration <= 0 { 677 auction = nil 678 } 679 return auction 680 } 681 682 func parseMarketsTable(table *godog.Table) []RowWrapper { 683 return StrictParseTable(table, []string{ 684 "id", 685 "quote name", 686 "asset", 687 "risk model", 688 "fees", 689 "data source config", 690 "price monitoring", 691 "margin calculator", 692 "auction duration", 693 "linear slippage factor", 694 "quadratic slippage factor", 695 "sla params", 696 }, []string{ 697 "decimal places", 698 "position decimal places", 699 "liquidity monitoring", 700 "parent market id", 701 "insurance pool fraction", 702 "successor auction", 703 "is passed", 704 "market type", 705 "liquidation strategy", 706 "price type", 707 "decay weight", 708 "decay power", 709 "cash amount", 710 "source weights", 711 "source staleness tolerance", 712 "oracle1", 713 "oracle2", 714 "oracle3", 715 "oracle4", 716 "oracle5", 717 "tick size", 718 "max price cap", 719 "binary", 720 "fully collateralised", 721 "allowed empty amm levels", 722 }) 723 } 724 725 func parseMarketsUpdateTable(table *godog.Table) []RowWrapper { 726 return StrictParseTable(table, []string{ 727 "id", 728 }, []string{ 729 "linear slippage factor", 730 "quadratic slippage factor", 731 "data source config", // product update 732 "price monitoring", // price monitoring update 733 "risk model", // risk model update 734 "liquidity monitoring", // liquidity monitoring update 735 "sla params", 736 "liquidity fee settings", 737 "liquidation strategy", 738 "price type", 739 "decay weight", 740 "decay power", 741 "cash amount", 742 "source weights", 743 "source staleness tolerance", 744 "oracle1", 745 "oracle2", 746 "oracle3", 747 "oracle4", 748 "oracle5", 749 "tick size", 750 }) 751 } 752 753 type marketRow struct { 754 row RowWrapper 755 } 756 757 type marketUpdateRow struct { 758 row RowWrapper 759 } 760 761 func (r marketUpdateRow) id() string { 762 return r.row.MustStr("id") 763 } 764 765 func (r marketUpdateRow) tickSize() *num.Uint { 766 if r.row.HasColumn("tick size") { 767 return num.MustUintFromString(r.row.MustStr("tick size"), 10) 768 } 769 return num.UintOne() 770 } 771 772 func (r marketUpdateRow) oracleConfig() (string, bool) { 773 if r.row.HasColumn("data source config") { 774 oc := r.row.MustStr("data source config") 775 return oc, true 776 } 777 return "", false 778 } 779 780 func (r marketUpdateRow) priceMonitoring() (string, bool) { 781 if r.row.HasColumn("price monitoring") { 782 pm := r.row.MustStr("price monitoring") 783 return pm, true 784 } 785 return "", false 786 } 787 788 func (r marketUpdateRow) riskModel() (string, bool) { 789 if r.row.HasColumn("risk model") { 790 rm := r.row.MustStr("risk model") 791 return rm, true 792 } 793 return "", false 794 } 795 796 func (r marketUpdateRow) liquidityMonitoring() (string, bool) { 797 if r.row.HasColumn("liquidity monitoring") { 798 lm := r.row.MustStr("liquidity monitoring") 799 return lm, true 800 } 801 return "", false 802 } 803 804 func (r marketUpdateRow) liquidationStrat() (string, bool) { 805 if r.row.HasColumn("liquidation strategy") { 806 ls := r.row.MustStr("liquidation strategy") 807 return ls, true 808 } 809 return "", false 810 } 811 812 func (r marketUpdateRow) priceSourceWeights() []num.Decimal { 813 if !r.row.HasColumn("source weights") { 814 return []num.Decimal{num.DecimalZero(), num.DecimalZero(), num.DecimalZero(), num.DecimalZero()} 815 } 816 weights := strings.Split(r.row.mustColumn("source weights"), ",") 817 d := make([]num.Decimal, 0, len(weights)) 818 for _, v := range weights { 819 d = append(d, num.MustDecimalFromString(v)) 820 } 821 return d 822 } 823 824 func (r marketUpdateRow) compositePriceOracleFromName(config *market.Config, name string) (*datasource.Spec, *datasource.SpecBindingForCompositePrice) { 825 if !r.row.HasColumn(name) { 826 return nil, nil 827 } 828 829 rawSpec, binding, err := config.OracleConfigs.GetOracleDefinitionForCompositePrice(r.row.Str(name)) 830 if err != nil { 831 return nil, nil 832 } 833 spec := datasource.FromOracleSpecProto(rawSpec) 834 filters := spec.Data.GetFilters() 835 ds := datasource.NewDefinition(datasource.ContentTypeOracle).SetOracleConfig( 836 &signedoracle.SpecConfiguration{ 837 Signers: spec.Data.GetSigners(), 838 Filters: filters, 839 }, 840 ) 841 return datasource.SpecFromDefinition(*ds), &datasource.SpecBindingForCompositePrice{PriceSourceProperty: binding.PriceSourceProperty} 842 } 843 844 func (r marketUpdateRow) oracles(config *market.Config) ([]*datasource.Spec, []*datasource.SpecBindingForCompositePrice) { 845 specs := []*datasource.Spec{} 846 bindings := []*datasource.SpecBindingForCompositePrice{} 847 names := []string{"oracle1", "oracle2", "oracle3", "oracle4", "oracle5"} 848 for _, v := range names { 849 spec, binding := r.compositePriceOracleFromName(config, v) 850 if spec == nil { 851 continue 852 } 853 specs = append(specs, spec) 854 bindings = append(bindings, binding) 855 } 856 if len(specs) > 0 { 857 return specs, bindings 858 } 859 860 return nil, nil 861 } 862 863 func (r marketUpdateRow) priceSourceStalnessTolerance() []time.Duration { 864 if !r.row.HasColumn("source staleness tolerance") { 865 return []time.Duration{1000, 1000, 1000, 1000} 866 } 867 durations := strings.Split(r.row.mustColumn("source staleness tolerance"), ",") 868 d := make([]time.Duration, 0, len(durations)) 869 for _, v := range durations { 870 dur, err := time.ParseDuration(v) 871 if err != nil { 872 panic(err) 873 } 874 d = append(d, dur) 875 } 876 return d 877 } 878 879 func (r marketUpdateRow) cashAmount() *num.Uint { 880 if !r.row.HasColumn("cash amount") { 881 return num.UintZero() 882 } 883 return num.MustUintFromString(r.row.mustColumn("cash amount"), 10) 884 } 885 886 func (r marketUpdateRow) decayPower() num.Decimal { 887 if !r.row.HasColumn("decay power") { 888 return num.DecimalZero() 889 } 890 return num.MustDecimalFromString(r.row.mustColumn("decay power")) 891 } 892 893 func (r marketUpdateRow) decayWeight() num.Decimal { 894 if !r.row.HasColumn("decay weight") { 895 return num.DecimalZero() 896 } 897 return num.MustDecimalFromString(r.row.mustColumn("decay weight")) 898 } 899 900 func (r marketUpdateRow) markPriceType() types.CompositePriceType { 901 if !r.row.HasColumn("price type") { 902 return types.CompositePriceTypeByLastTrade 903 } 904 if r.row.mustColumn("price type") == "last trade" { 905 return types.CompositePriceTypeByLastTrade 906 } else if r.row.mustColumn("price type") == "median" { 907 return types.CompositePriceTypeByMedian 908 } else if r.row.mustColumn("price type") == "weight" { 909 return types.CompositePriceTypeByWeight 910 } else { 911 panic("invalid price type") 912 } 913 } 914 915 func (r marketRow) tickSize() *num.Uint { 916 if r.row.HasColumn("tick size") { 917 return num.MustUintFromString(r.row.MustStr("tick size"), 10) 918 } 919 return num.UintOne() 920 } 921 922 func (r marketRow) allowedEmptyAMMLevels() uint64 { 923 if r.row.HasColumn("allowed empty AMM levels") { 924 return r.row.MustU64("allowed empty AMM levels") 925 } 926 return 100 927 } 928 929 func (r marketRow) id() string { 930 return r.row.MustStr("id") 931 } 932 933 func (r marketRow) liquidationStrat() string { 934 if r.row.HasColumn("liquidation strategy") { 935 ls := r.row.MustStr("liquidation strategy") 936 return ls 937 } 938 return "" 939 } 940 941 func (r marketRow) decimalPlaces() uint64 { 942 if !r.row.HasColumn("decimal places") { 943 return 0 944 } 945 return r.row.MustU64("decimal places") 946 } 947 948 func (r marketRow) positionDecimalPlaces() int64 { 949 if !r.row.HasColumn("position decimal places") { 950 return 0 951 } 952 return r.row.MustI64("position decimal places") 953 } 954 955 func (r marketRow) quoteName() string { 956 return r.row.MustStr("quote name") 957 } 958 959 func (r marketRow) asset() string { 960 return r.row.MustStr("asset") 961 } 962 963 func (r marketRow) riskModel() string { 964 return r.row.MustStr("risk model") 965 } 966 967 func (r marketRow) fees() string { 968 return r.row.MustStr("fees") 969 } 970 971 func (r marketRow) oracleConfig() string { 972 return r.row.MustStr("data source config") 973 } 974 975 func (r marketRow) priceMonitoring() string { 976 return r.row.MustStr("price monitoring") 977 } 978 979 func (r marketRow) marginCalculator() string { 980 return r.row.MustStr("margin calculator") 981 } 982 983 func (r marketRow) auctionDuration() int64 { 984 return r.row.MustI64("auction duration") 985 } 986 987 func (r marketRow) liquidityMonitoring() string { 988 if !r.row.HasColumn("liquidity monitoring") { 989 return "default-parameters" 990 } 991 return r.row.MustStr("liquidity monitoring") 992 } 993 994 func (r marketRow) liquiditySLA() string { 995 return r.row.MustStr("sla params") 996 } 997 998 func (r marketUpdateRow) tryLiquiditySLA() (string, bool) { 999 if r.row.HasColumn("sla params") { 1000 sla := r.row.MustStr("sla params") 1001 return sla, true 1002 } 1003 return "", false 1004 } 1005 1006 func (r marketUpdateRow) tryLiquidityFeeSettings() (string, bool) { 1007 if r.row.HasColumn("liquidity fee settings") { 1008 s := r.row.MustStr("liquidity fee settings") 1009 return s, true 1010 } 1011 return "", false 1012 } 1013 1014 func (r marketRow) linearSlippageFactor() float64 { 1015 if !r.row.HasColumn("linear slippage factor") { 1016 // set to 0.1 by default 1017 return 0.001 1018 } 1019 return r.row.MustF64("linear slippage factor") 1020 } 1021 1022 func (r marketRow) priceSourceWeights() []num.Decimal { 1023 if !r.row.HasColumn("source weights") { 1024 return []num.Decimal{num.DecimalZero(), num.DecimalZero(), num.DecimalZero(), num.DecimalZero()} 1025 } 1026 weights := strings.Split(r.row.mustColumn("source weights"), ",") 1027 d := make([]num.Decimal, 0, len(weights)) 1028 for _, v := range weights { 1029 d = append(d, num.MustDecimalFromString(v)) 1030 } 1031 return d 1032 } 1033 1034 func (r marketRow) priceSourceStalnessTolerance() []time.Duration { 1035 if !r.row.HasColumn("source staleness tolerance") { 1036 return []time.Duration{1000, 1000, 1000, 1000} 1037 } 1038 durations := strings.Split(r.row.mustColumn("source staleness tolerance"), ",") 1039 d := make([]time.Duration, 0, len(durations)) 1040 for _, v := range durations { 1041 dur, err := time.ParseDuration(v) 1042 if err != nil { 1043 panic(err) 1044 } 1045 d = append(d, dur) 1046 } 1047 return d 1048 } 1049 1050 func (r marketRow) cashAmount() *num.Uint { 1051 if !r.row.HasColumn("cash amount") { 1052 return num.UintZero() 1053 } 1054 return num.MustUintFromString(r.row.mustColumn("cash amount"), 10) 1055 } 1056 1057 func (r marketRow) decayPower() num.Decimal { 1058 if !r.row.HasColumn("decay power") { 1059 return num.DecimalZero() 1060 } 1061 return num.MustDecimalFromString(r.row.mustColumn("decay power")) 1062 } 1063 1064 func (r marketRow) decayWeight() num.Decimal { 1065 if !r.row.HasColumn("decay weight") { 1066 return num.DecimalZero() 1067 } 1068 return num.MustDecimalFromString(r.row.mustColumn("decay weight")) 1069 } 1070 1071 func (r marketRow) markPriceType() types.CompositePriceType { 1072 if !r.row.HasColumn("price type") { 1073 return types.CompositePriceTypeByLastTrade 1074 } 1075 if r.row.mustColumn("price type") == "last trade" { 1076 return types.CompositePriceTypeByLastTrade 1077 } else if r.row.mustColumn("price type") == "median" { 1078 return types.CompositePriceTypeByMedian 1079 } else if r.row.mustColumn("price type") == "weight" { 1080 return types.CompositePriceTypeByWeight 1081 } else { 1082 panic("invalid price type") 1083 } 1084 } 1085 1086 func (r marketRow) compositePriceOracleFromName(config *market.Config, name string) (*datasource.Spec, *datasource.SpecBindingForCompositePrice) { 1087 if !r.row.HasColumn(name) { 1088 return nil, nil 1089 } 1090 rawSpec, binding, err := config.OracleConfigs.GetOracleDefinitionForCompositePrice(r.row.Str(name)) 1091 if err != nil { 1092 return nil, nil 1093 } 1094 spec := datasource.FromOracleSpecProto(rawSpec) 1095 filters := spec.Data.GetFilters() 1096 ds := datasource.NewDefinition(datasource.ContentTypeOracle).SetOracleConfig( 1097 &signedoracle.SpecConfiguration{ 1098 Signers: spec.Data.GetSigners(), 1099 Filters: filters, 1100 }, 1101 ) 1102 return datasource.SpecFromDefinition(*ds), &datasource.SpecBindingForCompositePrice{PriceSourceProperty: binding.PriceSourceProperty} 1103 } 1104 1105 func (r marketRow) oracles(config *market.Config) ([]*datasource.Spec, []*datasource.SpecBindingForCompositePrice) { 1106 specs := []*datasource.Spec{} 1107 bindings := []*datasource.SpecBindingForCompositePrice{} 1108 names := []string{"oracle1", "oracle2", "oracle3", "oracle4", "oracle5"} 1109 for _, v := range names { 1110 spec, binding := r.compositePriceOracleFromName(config, v) 1111 if spec == nil { 1112 continue 1113 } 1114 specs = append(specs, spec) 1115 bindings = append(bindings, binding) 1116 } 1117 if len(specs) > 0 { 1118 return specs, bindings 1119 } 1120 1121 return nil, nil 1122 } 1123 1124 func (r marketRow) quadraticSlippageFactor() float64 { 1125 if !r.row.HasColumn("quadratic slippage factor") { 1126 // set to 0.1 by default 1127 return 0.0 1128 } 1129 return r.row.MustF64("quadratic slippage factor") 1130 } 1131 1132 func (r marketRow) isSuccessor() bool { 1133 if pid, ok := r.row.StrB("parent market id"); !ok || len(pid) == 0 { 1134 return false 1135 } 1136 return true 1137 } 1138 1139 func (r marketRow) IsCapped() bool { 1140 return r.row.HasColumn("max price cap") 1141 } 1142 1143 func (r marketRow) getCapped() *types.FutureCap { 1144 if !r.IsCapped() { 1145 return nil 1146 } 1147 return &types.FutureCap{ 1148 MaxPrice: r.row.MustUint("max price cap"), 1149 Binary: r.row.Bool("binary"), 1150 FullyCollateralised: r.row.Bool("fully collateralised"), 1151 } 1152 } 1153 1154 func (r marketRow) isPerp() bool { 1155 if mt, ok := r.row.StrB("market type"); !ok || mt != "perp" { 1156 return false 1157 } 1158 return true 1159 } 1160 1161 func (r marketRow) parentID() string { 1162 return r.row.MustStr("parent market id") 1163 } 1164 1165 func (r marketRow) insuranceFraction() num.Decimal { 1166 if !r.row.HasColumn("insurance pool fraction") { 1167 return num.DecimalZero() 1168 } 1169 return r.row.Decimal("insurance pool fraction") 1170 } 1171 1172 func (r marketRow) successorAuction() int64 { 1173 if !r.row.HasColumn("successor auction") { 1174 return 5 * r.auctionDuration() // five times auction duration 1175 } 1176 return r.row.MustI64("successor auction") 1177 } 1178 1179 func (r marketUpdateRow) tryLinearSlippageFactor() (float64, bool) { 1180 if r.row.HasColumn("linear slippage factor") { 1181 return r.row.MustF64("linear slippage factor"), true 1182 } 1183 return -1, false 1184 } 1185 1186 func (r marketUpdateRow) tryQuadraticSlippageFactor() (float64, bool) { 1187 if r.row.HasColumn("quadratic slippage factor") { 1188 return r.row.MustF64("quadratic slippage factor"), true 1189 } 1190 return -1, false 1191 }