code.vegaprotocol.io/vega@v0.79.0/datanode/api/trading_data_v2_test.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 api_test 17 18 import ( 19 "archive/zip" 20 "bytes" 21 "context" 22 "embed" 23 "fmt" 24 "io" 25 "math" 26 "slices" 27 "sort" 28 "strconv" 29 "strings" 30 "sync" 31 "testing" 32 "time" 33 34 "code.vegaprotocol.io/vega/datanode/api" 35 "code.vegaprotocol.io/vega/datanode/api/mocks" 36 "code.vegaprotocol.io/vega/datanode/entities" 37 "code.vegaprotocol.io/vega/datanode/networkhistory/segment" 38 "code.vegaprotocol.io/vega/datanode/service" 39 smocks "code.vegaprotocol.io/vega/datanode/service/mocks" 40 "code.vegaprotocol.io/vega/libs/crypto" 41 "code.vegaprotocol.io/vega/libs/num" 42 "code.vegaprotocol.io/vega/libs/ptr" 43 "code.vegaprotocol.io/vega/logging" 44 v2 "code.vegaprotocol.io/vega/protos/data-node/api/v2" 45 "code.vegaprotocol.io/vega/protos/vega" 46 47 "github.com/golang/mock/gomock" 48 "github.com/stretchr/testify/assert" 49 "github.com/stretchr/testify/require" 50 "golang.org/x/exp/maps" 51 "google.golang.org/genproto/googleapis/api/httpbody" 52 "google.golang.org/grpc/metadata" 53 ) 54 55 //go:embed testdata/dummysegment.zip 56 var testData embed.FS 57 58 func makeFullSegment(from, to, dbVersion int64) segment.Full { 59 return segment.Full{ 60 MetaData: segment.MetaData{ 61 Base: segment.Base{ 62 HeightFrom: from, 63 HeightTo: to, 64 DatabaseVersion: dbVersion, 65 ChainID: "test-chain-id", 66 }, 67 }, 68 } 69 } 70 71 func TestExportNetworkHistory(t *testing.T) { 72 req := &v2.ExportNetworkHistoryRequest{ 73 FromBlock: 1, 74 ToBlock: 3000, 75 Table: v2.Table_TABLE_ORDERS, 76 } 77 78 ctrl := gomock.NewController(t) 79 historyService := mocks.NewMockNetworkHistoryService(ctrl) 80 81 testSegments := []segment.Full{ 82 makeFullSegment(1, 1000, 1), 83 makeFullSegment(1001, 2000, 1), 84 makeFullSegment(2001, 3000, 2), 85 } 86 87 historyService.EXPECT().ListAllHistorySegments().Times(1).Return(testSegments, nil) 88 historyService.EXPECT().GetHistorySegmentReader(gomock.Any(), gomock.Any()).Times(3).DoAndReturn( 89 func(ctx context.Context, id string) (io.ReadSeekCloser, int64, error) { 90 reader, err := testData.Open("testdata/dummysegment.zip") 91 require.NoError(t, err) 92 info, _ := reader.Stat() 93 return reader.(io.ReadSeekCloser), info.Size(), nil 94 }, 95 ) 96 97 stream := &mockStream{} 98 apiService := api.TradingDataServiceV2{ 99 NetworkHistoryService: historyService, 100 } 101 102 err := apiService.ExportNetworkHistory(req, stream) 103 require.NoError(t, err) 104 105 // Now check that we got a zip file with two CSV files in it; as we crossed a schema migration boundary 106 require.Greater(t, len(stream.sent), 0) 107 assert.Equal(t, stream.sent[0].ContentType, "application/zip") 108 109 zipBytes := stream.sent[0].Data 110 zipBuffer := bytes.NewReader(zipBytes) 111 zipReader, err := zip.NewReader(zipBuffer, int64(len(zipBytes))) 112 require.NoError(t, err) 113 114 filenames := []string{} 115 for _, file := range zipReader.File { 116 filenames = append(filenames, file.Name) 117 fileReader, err := file.Open() 118 require.NoError(t, err) 119 fileContents, err := io.ReadAll(fileReader) 120 require.NoError(t, err) 121 assert.True(t, strings.HasPrefix(string(fileContents), "header row\nmock data, more mock data,")) 122 } 123 124 require.Equal(t, filenames, []string{ 125 "test-chain-id-orders-001-000001-002000.csv", 126 "test-chain-id-orders-002-002001-003000.csv", 127 }) 128 } 129 130 type dummyReferralService struct{} 131 132 func (*dummyReferralService) GetReferralSetStats(ctx context.Context, setID *entities.ReferralSetID, atEpoch *uint64, referee *entities.PartyID, pagination entities.CursorPagination) ([]entities.FlattenReferralSetStats, entities.PageInfo, error) { 133 return []entities.FlattenReferralSetStats{ 134 { 135 DiscountFactors: &vega.DiscountFactors{ 136 MakerDiscountFactor: "0.001", 137 InfrastructureDiscountFactor: "0.002", 138 LiquidityDiscountFactor: "0.003", 139 }, 140 }, 141 }, entities.PageInfo{}, nil 142 } 143 144 func (*dummyReferralService) ListReferralSets(ctx context.Context, referralSetID *entities.ReferralSetID, referrer, referee *entities.PartyID, pagination entities.CursorPagination) ([]entities.ReferralSet, entities.PageInfo, error) { 145 return nil, entities.PageInfo{}, nil 146 } 147 148 func (*dummyReferralService) ListReferralSetReferees(ctx context.Context, referralSetID *entities.ReferralSetID, referrer, referee *entities.PartyID, pagination entities.CursorPagination, aggregationEpochs uint32) ([]entities.ReferralSetRefereeStats, entities.PageInfo, error) { 149 return nil, entities.PageInfo{}, nil 150 } 151 152 func TestEstimateFees(t *testing.T) { 153 ctrl := gomock.NewController(t) 154 ctx := context.TODO() 155 assetDecimals := 8 156 marketDecimals := 3 157 positionDecimalPlaces := 2 158 marginFundingFactor := 0.95 159 initialMarginScalingFactor := 1.5 160 linearSlippageFactor := num.DecimalFromFloat(0.005) 161 quadraticSlippageFactor := num.DecimalZero() 162 rfLong := num.DecimalFromFloat(0.1) 163 rfShort := num.DecimalFromFloat(0.2) 164 165 asset := entities.Asset{ 166 Decimals: assetDecimals, 167 } 168 169 tickSize := num.DecimalOne() 170 171 mkt := entities.Market{ 172 DecimalPlaces: marketDecimals, 173 PositionDecimalPlaces: positionDecimalPlaces, 174 LinearSlippageFactor: &linearSlippageFactor, 175 QuadraticSlippageFactor: &quadraticSlippageFactor, 176 TradableInstrument: entities.TradableInstrument{ 177 TradableInstrument: &vega.TradableInstrument{ 178 Instrument: &vega.Instrument{ 179 Product: &vega.Instrument_Perpetual{ 180 Perpetual: &vega.Perpetual{ 181 SettlementAsset: crypto.RandomHash(), 182 MarginFundingFactor: fmt.Sprintf("%f", marginFundingFactor), 183 }, 184 }, 185 }, 186 MarginCalculator: &vega.MarginCalculator{ 187 ScalingFactors: &vega.ScalingFactors{ 188 SearchLevel: initialMarginScalingFactor * 0.9, 189 InitialMargin: initialMarginScalingFactor, 190 CollateralRelease: initialMarginScalingFactor * 1.1, 191 }, 192 }, 193 }, 194 }, 195 TickSize: &tickSize, 196 Fees: entities.Fees{ 197 Factors: &entities.FeeFactors{ 198 MakerFee: "0.1", 199 InfrastructureFee: "0.02", 200 LiquidityFee: "0.03", 201 BuyBackFee: "0.04", 202 TreasuryFee: "0.05", 203 }, 204 }, 205 } 206 207 rf := entities.RiskFactor{ 208 Long: rfLong, 209 Short: rfShort, 210 } 211 212 assetService := mocks.NewMockAssetService(ctrl) 213 marketService := mocks.NewMockMarketsService(ctrl) 214 riskFactorService := mocks.NewMockRiskFactorService(ctrl) 215 vdService := mocks.NewMockVolumeDiscountService(ctrl) 216 epochService := mocks.NewMockEpochService(ctrl) 217 218 assetService.EXPECT().GetByID(ctx, gomock.Any()).Return(asset, nil).AnyTimes() 219 marketService.EXPECT().GetByID(ctx, gomock.Any()).Return(mkt, nil).AnyTimes() 220 riskFactorService.EXPECT().GetMarketRiskFactors(ctx, gomock.Any()).Return(rf, nil).AnyTimes() 221 epochService.EXPECT().GetCurrent(gomock.Any()).Return(entities.Epoch{ID: 1}, nil).AnyTimes() 222 vdService.EXPECT().Stats(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]entities.FlattenVolumeDiscountStats{ 223 { 224 DiscountFactors: &vega.DiscountFactors{ 225 MakerDiscountFactor: "0.0001", 226 InfrastructureDiscountFactor: "0.0002", 227 LiquidityDiscountFactor: "0.0003", 228 }, 229 }, 230 }, entities.PageInfo{}, nil) 231 232 apiService := api.TradingDataServiceV2{ 233 AssetService: assetService, 234 MarketsService: marketService, 235 RiskFactorService: riskFactorService, 236 VolumeDiscountStatsService: vdService, 237 ReferralSetsService: &dummyReferralService{}, 238 EpochService: epochService, 239 } 240 241 estimate, err := apiService.EstimateFee(ctx, &v2.EstimateFeeRequest{ 242 MarketId: crypto.RandomHash(), 243 Price: "100", 244 Size: 10, 245 Party: nil, 246 }) 247 require.NoError(t, err) 248 // no party was passed so the calculation is returned without discounts 249 require.Equal(t, "100000", estimate.Fee.MakerFee) 250 require.Equal(t, "20000", estimate.Fee.InfrastructureFee) 251 require.Equal(t, "30000", estimate.Fee.LiquidityFee) 252 require.Equal(t, "40000", estimate.Fee.BuyBackFee) 253 require.Equal(t, "50000", estimate.Fee.TreasuryFee) 254 require.Equal(t, "", estimate.Fee.MakerFeeReferrerDiscount) 255 require.Equal(t, "", estimate.Fee.MakerFeeVolumeDiscount) 256 require.Equal(t, "", estimate.Fee.InfrastructureFeeReferrerDiscount) 257 require.Equal(t, "", estimate.Fee.InfrastructureFeeVolumeDiscount) 258 require.Equal(t, "", estimate.Fee.LiquidityFeeReferrerDiscount) 259 require.Equal(t, "", estimate.Fee.LiquidityFeeVolumeDiscount) 260 261 party := "party" 262 estimate, err = apiService.EstimateFee(ctx, &v2.EstimateFeeRequest{ 263 MarketId: crypto.RandomHash(), 264 Price: "100", 265 Size: 10, 266 Party: &party, 267 }) 268 require.NoError(t, err) 269 // before discount makerFee = 100000 270 // ref discount = 0.001 * 100000 = 100 271 // vol discount = 0.0001 * (100000 - 100) = 9.99 => 9 272 // 100000 - 100 - 9 = 99,891 273 require.Equal(t, "99891", estimate.Fee.MakerFee) 274 require.Equal(t, "100", estimate.Fee.MakerFeeReferrerDiscount) 275 require.Equal(t, "9", estimate.Fee.MakerFeeVolumeDiscount) 276 277 // before discount infraFee = 20000 278 // ref discount = 0.002 * 20000 = 40 279 // vol discount = 0.0002 * (20000 - 40) = 3.992 => 3 280 // 20000 - 40 - 3 = 19,957 281 require.Equal(t, "19957", estimate.Fee.InfrastructureFee) 282 require.Equal(t, "40", estimate.Fee.InfrastructureFeeReferrerDiscount) 283 require.Equal(t, "3", estimate.Fee.InfrastructureFeeVolumeDiscount) 284 285 // before discount liqFee = 30000 286 // ref discount = 0.003 * 30000 = 90 287 // vol discount = 0.0003 * (30000 - 90) = 8.973 => 8 288 // 30000 - 90 - 8 = 29,902 289 require.Equal(t, "29902", estimate.Fee.LiquidityFee) 290 require.Equal(t, "90", estimate.Fee.LiquidityFeeReferrerDiscount) 291 require.Equal(t, "8", estimate.Fee.LiquidityFeeVolumeDiscount) 292 293 // no discount on buy back and treasury 294 require.Equal(t, "40000", estimate.Fee.BuyBackFee) 295 require.Equal(t, "50000", estimate.Fee.TreasuryFee) 296 } 297 298 func TestEstimatePositionCappedFuture(t *testing.T) { 299 ctrl := gomock.NewController(t) 300 ctx := context.Background() 301 assetId := "assetID" 302 marketId := "marketID" 303 304 assetDecimals := 8 305 marketDecimals := 3 306 positionDecimalPlaces := 2 307 initialMarginScalingFactor := 1.5 308 linearSlippageFactor := num.DecimalFromFloat(0.005) 309 quadraticSlippageFactor := num.DecimalZero() 310 rfLong := num.DecimalFromFloat(0.1) 311 rfShort := num.DecimalFromFloat(0.2) 312 313 auctionEnd := int64(0) 314 fundingPayment := 1234.56789 315 316 asset := entities.Asset{ 317 Decimals: assetDecimals, 318 } 319 320 tickSize := num.DecimalOne() 321 322 mkt := entities.Market{ 323 DecimalPlaces: marketDecimals, 324 PositionDecimalPlaces: positionDecimalPlaces, 325 LinearSlippageFactor: &linearSlippageFactor, 326 QuadraticSlippageFactor: &quadraticSlippageFactor, 327 TradableInstrument: entities.TradableInstrument{ 328 TradableInstrument: &vega.TradableInstrument{ 329 Instrument: &vega.Instrument{ 330 Product: &vega.Instrument_Future{ 331 Future: &vega.Future{ 332 SettlementAsset: assetId, 333 Cap: &vega.FutureCap{ 334 MaxPrice: floatToStringWithDp(100, marketDecimals), 335 FullyCollateralised: ptr.From(true), 336 }, 337 }, 338 }, 339 }, 340 MarginCalculator: &vega.MarginCalculator{ 341 ScalingFactors: &vega.ScalingFactors{ 342 SearchLevel: initialMarginScalingFactor * 0.9, 343 InitialMargin: initialMarginScalingFactor, 344 CollateralRelease: initialMarginScalingFactor * 1.1, 345 }, 346 }, 347 }, 348 }, 349 TickSize: &tickSize, 350 } 351 352 rf := entities.RiskFactor{ 353 Long: rfLong, 354 Short: rfShort, 355 } 356 357 assetService := mocks.NewMockAssetService(ctrl) 358 marketService := mocks.NewMockMarketsService(ctrl) 359 riskFactorService := mocks.NewMockRiskFactorService(ctrl) 360 361 assetService.EXPECT().GetByID(ctx, assetId).Return(asset, nil).AnyTimes() 362 marketService.EXPECT().GetByID(ctx, marketId).Return(mkt, nil).AnyTimes() 363 riskFactorService.EXPECT().GetMarketRiskFactors(ctx, marketId).Return(rf, nil).AnyTimes() 364 365 mktData := entities.MarketData{ 366 MarkPrice: num.DecimalFromFloat(123.456 * math.Pow10(marketDecimals)), 367 AuctionEnd: auctionEnd, 368 ProductData: &entities.ProductData{ 369 ProductData: &vega.ProductData{ 370 Data: &vega.ProductData_PerpetualData{ 371 PerpetualData: &vega.PerpetualData{ 372 FundingPayment: fmt.Sprintf("%f", fundingPayment), 373 FundingRate: "0.05", 374 }, 375 }, 376 }, 377 }, 378 } 379 marketDataService := mocks.NewMockMarketDataService(ctrl) 380 marketDataService.EXPECT().GetMarketDataByID(ctx, marketId).Return(mktData, nil).AnyTimes() 381 382 apiService := api.TradingDataServiceV2{ 383 AssetService: assetService, 384 MarketsService: marketService, 385 MarketDataService: marketDataService, 386 RiskFactorService: riskFactorService, 387 } 388 389 req := &v2.EstimatePositionRequest{ 390 MarketId: marketId, 391 OpenVolume: 0, 392 AverageEntryPrice: "0", 393 Orders: []*v2.OrderInfo{ 394 { 395 Side: entities.SideBuy, 396 Price: floatToStringWithDp(100, marketDecimals), 397 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 398 IsMarketOrder: false, 399 }, 400 }, 401 MarginAccountBalance: fmt.Sprintf("%f", 100*math.Pow10(assetDecimals)), 402 GeneralAccountBalance: fmt.Sprintf("%f", 1000*math.Pow10(assetDecimals)), 403 OrderMarginAccountBalance: "0", 404 MarginMode: vega.MarginMode_MARGIN_MODE_CROSS_MARGIN, 405 MarginFactor: ptr.From("0"), 406 } 407 408 // error because hypothetical order is outide of max range 409 _, err := apiService.EstimatePosition(ctx, req) 410 require.Error(t, err) 411 412 req.Orders = []*v2.OrderInfo{ 413 { 414 Side: entities.SideBuy, 415 Price: floatToStringWithDp(50, marketDecimals), 416 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 417 IsMarketOrder: false, 418 }, 419 } 420 421 // error because hypothetical order is outide of max range 422 resp, err := apiService.EstimatePosition(ctx, req) 423 require.NoError(t, err) 424 425 assert.Equal(t, "500000000000", resp.Margin.BestCase.MaintenanceMargin) 426 assert.Equal(t, "500000000000", resp.Margin.WorstCase.MaintenanceMargin) 427 } 428 429 func TestEstimatePosition(t *testing.T) { 430 ctrl := gomock.NewController(t) 431 ctx := context.TODO() 432 assetId := "assetID" 433 marketId := "marketID" 434 435 assetDecimals := 8 436 marketDecimals := 3 437 positionDecimalPlaces := 2 438 marginFundingFactor := 0.95 439 initialMarginScalingFactor := 1.5 440 linearSlippageFactor := num.DecimalFromFloat(0.005) 441 quadraticSlippageFactor := num.DecimalZero() 442 rfLong := num.DecimalFromFloat(0.1) 443 rfShort := num.DecimalFromFloat(0.2) 444 445 auctionEnd := int64(0) 446 fundingPayment := 1234.56789 447 448 asset := entities.Asset{ 449 Decimals: assetDecimals, 450 } 451 452 tickSize := num.DecimalOne() 453 454 mkt := entities.Market{ 455 DecimalPlaces: marketDecimals, 456 PositionDecimalPlaces: positionDecimalPlaces, 457 LinearSlippageFactor: &linearSlippageFactor, 458 QuadraticSlippageFactor: &quadraticSlippageFactor, 459 TradableInstrument: entities.TradableInstrument{ 460 TradableInstrument: &vega.TradableInstrument{ 461 Instrument: &vega.Instrument{ 462 Product: &vega.Instrument_Perpetual{ 463 Perpetual: &vega.Perpetual{ 464 SettlementAsset: assetId, 465 MarginFundingFactor: fmt.Sprintf("%f", marginFundingFactor), 466 }, 467 }, 468 }, 469 MarginCalculator: &vega.MarginCalculator{ 470 ScalingFactors: &vega.ScalingFactors{ 471 SearchLevel: initialMarginScalingFactor * 0.9, 472 InitialMargin: initialMarginScalingFactor, 473 CollateralRelease: initialMarginScalingFactor * 1.1, 474 }, 475 }, 476 }, 477 }, 478 TickSize: &tickSize, 479 } 480 481 rf := entities.RiskFactor{ 482 Long: rfLong, 483 Short: rfShort, 484 } 485 486 assetService := mocks.NewMockAssetService(ctrl) 487 marketService := mocks.NewMockMarketsService(ctrl) 488 riskFactorService := mocks.NewMockRiskFactorService(ctrl) 489 490 assetService.EXPECT().GetByID(ctx, assetId).Return(asset, nil).AnyTimes() 491 marketService.EXPECT().GetByID(ctx, marketId).Return(mkt, nil).AnyTimes() 492 riskFactorService.EXPECT().GetMarketRiskFactors(ctx, marketId).Return(rf, nil).AnyTimes() 493 494 testCases := []struct { 495 markPrice float64 496 openVolume int64 497 avgEntryPrice float64 498 orders []*v2.OrderInfo 499 marginAccountBalance float64 500 generalAccountBalance float64 501 orderMarginAccountBalance float64 502 marginMode vega.MarginMode 503 marginFactor float64 504 expectedCollIncBest string 505 expectedLiquidationBestVolumeOnly string 506 }{ 507 { 508 markPrice: 123.456 * math.Pow10(marketDecimals), 509 openVolume: 0, 510 avgEntryPrice: 0, 511 orders: []*v2.OrderInfo{ 512 { 513 Side: entities.SideBuy, 514 Price: floatToStringWithDp(100, marketDecimals), 515 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 516 IsMarketOrder: false, 517 }, 518 }, 519 marginAccountBalance: 100 * math.Pow10(assetDecimals), 520 generalAccountBalance: 1000 * math.Pow10(assetDecimals), 521 orderMarginAccountBalance: 0, 522 marginMode: vega.MarginMode_MARGIN_MODE_CROSS_MARGIN, 523 }, 524 { 525 markPrice: 123.456 * math.Pow10(marketDecimals), 526 openVolume: 0, 527 avgEntryPrice: 0, 528 orders: []*v2.OrderInfo{ 529 { 530 Side: entities.SideSell, 531 Price: floatToStringWithDp(100, marketDecimals), 532 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 533 IsMarketOrder: false, 534 }, 535 }, 536 marginAccountBalance: 100 * math.Pow10(assetDecimals), 537 generalAccountBalance: 1000 * math.Pow10(assetDecimals), 538 orderMarginAccountBalance: 0, 539 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 540 marginFactor: 0.1, 541 }, 542 { 543 markPrice: 123.456 * math.Pow10(marketDecimals), 544 openVolume: int64(10 * math.Pow10(positionDecimalPlaces)), 545 avgEntryPrice: 111.1 * math.Pow10(marketDecimals), 546 orders: []*v2.OrderInfo{ 547 { 548 Side: entities.SideSell, 549 Price: floatToStringWithDp(100, marketDecimals), 550 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 551 IsMarketOrder: false, 552 }, 553 }, 554 marginAccountBalance: 0, 555 generalAccountBalance: 1000 * math.Pow10(assetDecimals), 556 orderMarginAccountBalance: 0, 557 marginMode: vega.MarginMode_MARGIN_MODE_CROSS_MARGIN, 558 }, 559 { 560 markPrice: 123.456 * math.Pow10(marketDecimals), 561 openVolume: int64(-10 * math.Pow10(positionDecimalPlaces)), 562 avgEntryPrice: 111.1 * math.Pow10(marketDecimals), 563 orders: []*v2.OrderInfo{ 564 { 565 Side: entities.SideBuy, 566 Price: floatToStringWithDp(100, marketDecimals), 567 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 568 IsMarketOrder: false, 569 }, 570 }, 571 marginAccountBalance: 0, 572 generalAccountBalance: 1000 * math.Pow10(assetDecimals), 573 orderMarginAccountBalance: 10 * math.Pow10(assetDecimals), 574 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 575 marginFactor: 0.5, 576 }, 577 { 578 markPrice: 123.456 * math.Pow10(marketDecimals), 579 openVolume: int64(-10 * math.Pow10(positionDecimalPlaces)), 580 avgEntryPrice: 111.1 * math.Pow10(marketDecimals), 581 orders: []*v2.OrderInfo{ 582 { 583 Side: entities.SideSell, 584 Price: floatToStringWithDp(100, marketDecimals), 585 Remaining: uint64(11 * math.Pow10(positionDecimalPlaces)), 586 IsMarketOrder: false, 587 }, 588 { 589 Side: entities.SideBuy, 590 Price: floatToStringWithDp(100, marketDecimals), 591 Remaining: uint64(11 * math.Pow10(positionDecimalPlaces)), 592 IsMarketOrder: true, 593 }, 594 }, 595 marginAccountBalance: 100 * math.Pow10(assetDecimals), 596 generalAccountBalance: 0, 597 orderMarginAccountBalance: 0, 598 marginMode: vega.MarginMode_MARGIN_MODE_CROSS_MARGIN, 599 }, 600 { 601 markPrice: 123.456 * math.Pow10(marketDecimals), 602 openVolume: int64(-10 * math.Pow10(positionDecimalPlaces)), 603 avgEntryPrice: 111.1 * math.Pow10(marketDecimals), 604 orders: []*v2.OrderInfo{ 605 { 606 Side: entities.SideBuy, 607 Price: floatToStringWithDp(100, marketDecimals), 608 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 609 IsMarketOrder: false, 610 }, 611 { 612 Side: entities.SideSell, 613 Price: floatToStringWithDp(100, marketDecimals), 614 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 615 IsMarketOrder: false, 616 }, 617 }, 618 marginAccountBalance: 100 * math.Pow10(assetDecimals), 619 generalAccountBalance: 1000 * math.Pow10(assetDecimals), 620 orderMarginAccountBalance: 10 * math.Pow10(assetDecimals), 621 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 622 marginFactor: 0.3, 623 }, 624 { 625 markPrice: 123.456 * math.Pow10(marketDecimals), 626 openVolume: int64(10 * math.Pow10(positionDecimalPlaces)), 627 avgEntryPrice: 111.1 * math.Pow10(marketDecimals), 628 orders: []*v2.OrderInfo{ 629 { 630 Side: entities.SideSell, 631 Price: floatToStringWithDp(100, marketDecimals), 632 Remaining: uint64(3 * math.Pow10(positionDecimalPlaces)), 633 IsMarketOrder: false, 634 }, 635 { 636 Side: entities.SideSell, 637 Price: floatToStringWithDp(101, marketDecimals), 638 Remaining: uint64(4 * math.Pow10(positionDecimalPlaces)), 639 IsMarketOrder: false, 640 }, 641 { 642 Side: entities.SideSell, 643 Price: floatToStringWithDp(105, marketDecimals), 644 Remaining: uint64(5 * math.Pow10(positionDecimalPlaces)), 645 IsMarketOrder: false, 646 }, 647 { 648 Side: entities.SideBuy, 649 Price: floatToStringWithDp(95, marketDecimals), 650 Remaining: uint64(2 * math.Pow10(positionDecimalPlaces)), 651 IsMarketOrder: true, 652 }, 653 { 654 Side: entities.SideBuy, 655 Price: floatToStringWithDp(94, marketDecimals), 656 Remaining: uint64(3 * math.Pow10(positionDecimalPlaces)), 657 IsMarketOrder: true, 658 }, 659 { 660 Side: entities.SideBuy, 661 Price: floatToStringWithDp(90, marketDecimals), 662 Remaining: uint64(10 * math.Pow10(positionDecimalPlaces)), 663 IsMarketOrder: true, 664 }, 665 }, 666 marginAccountBalance: 100 * math.Pow10(assetDecimals), 667 generalAccountBalance: 1000 * math.Pow10(assetDecimals), 668 orderMarginAccountBalance: 0, 669 marginMode: vega.MarginMode_MARGIN_MODE_CROSS_MARGIN, 670 }, 671 { 672 markPrice: 123.456 * math.Pow10(marketDecimals), 673 openVolume: -int64(10 * math.Pow10(positionDecimalPlaces)), 674 avgEntryPrice: 111.1 * math.Pow10(marketDecimals), 675 orders: []*v2.OrderInfo{ 676 { 677 Side: entities.SideSell, 678 Price: floatToStringWithDp(100, marketDecimals), 679 Remaining: uint64(3 * math.Pow10(positionDecimalPlaces)), 680 IsMarketOrder: false, 681 }, 682 { 683 Side: entities.SideSell, 684 Price: floatToStringWithDp(101, marketDecimals), 685 Remaining: uint64(4 * math.Pow10(positionDecimalPlaces)), 686 IsMarketOrder: false, 687 }, 688 { 689 Side: entities.SideSell, 690 Price: floatToStringWithDp(105, marketDecimals), 691 Remaining: uint64(5 * math.Pow10(positionDecimalPlaces)), 692 IsMarketOrder: false, 693 }, 694 { 695 Side: entities.SideBuy, 696 Price: floatToStringWithDp(95, marketDecimals), 697 Remaining: uint64(2 * math.Pow10(positionDecimalPlaces)), 698 IsMarketOrder: true, 699 }, 700 { 701 Side: entities.SideBuy, 702 Price: floatToStringWithDp(94, marketDecimals), 703 Remaining: uint64(3 * math.Pow10(positionDecimalPlaces)), 704 IsMarketOrder: true, 705 }, 706 { 707 Side: entities.SideBuy, 708 Price: floatToStringWithDp(90, marketDecimals), 709 Remaining: uint64(10 * math.Pow10(positionDecimalPlaces)), 710 IsMarketOrder: true, 711 }, 712 }, 713 marginAccountBalance: 100 * math.Pow10(assetDecimals), 714 generalAccountBalance: 1000 * math.Pow10(assetDecimals), 715 orderMarginAccountBalance: 10 * math.Pow10(assetDecimals), 716 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 717 marginFactor: 0.1, 718 }, 719 { 720 markPrice: 123.456 * math.Pow10(marketDecimals), 721 openVolume: 0, 722 avgEntryPrice: 0, 723 orders: []*v2.OrderInfo{ 724 { 725 Side: entities.SideBuy, 726 Price: floatToStringWithDp(123.456, marketDecimals), 727 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 728 IsMarketOrder: false, 729 }, 730 }, 731 marginAccountBalance: 0, 732 generalAccountBalance: 0, 733 orderMarginAccountBalance: 0, 734 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 735 marginFactor: 0.3, 736 expectedCollIncBest: "3703680000", 737 }, 738 { 739 markPrice: 123.456 * math.Pow10(marketDecimals), 740 openVolume: 0, 741 avgEntryPrice: 0, 742 orders: []*v2.OrderInfo{ 743 { 744 Side: entities.SideBuy, 745 Price: "0", 746 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 747 IsMarketOrder: true, 748 }, 749 }, 750 marginAccountBalance: 0, 751 generalAccountBalance: 0, 752 orderMarginAccountBalance: 0, 753 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 754 marginFactor: 0.3, 755 expectedCollIncBest: "3703680000", 756 }, 757 { 758 markPrice: 123.456 * math.Pow10(marketDecimals), 759 openVolume: int64(1 * math.Pow10(positionDecimalPlaces)), 760 avgEntryPrice: 123.456 * math.Pow10(marketDecimals), 761 orders: []*v2.OrderInfo{}, 762 marginAccountBalance: 0, 763 generalAccountBalance: 0, 764 orderMarginAccountBalance: 0, 765 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 766 marginFactor: 0.3, 767 expectedCollIncBest: "3703680000", 768 }, 769 { 770 markPrice: 123.456 * math.Pow10(marketDecimals), 771 openVolume: 0, 772 avgEntryPrice: 0, 773 orders: []*v2.OrderInfo{ 774 { 775 Side: entities.SideSell, 776 Price: floatToStringWithDp(123.456, marketDecimals), 777 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 778 IsMarketOrder: false, 779 }, 780 }, 781 marginAccountBalance: 0, 782 generalAccountBalance: 0, 783 orderMarginAccountBalance: 0, 784 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 785 marginFactor: 0.3, 786 expectedCollIncBest: "3703680000", 787 }, 788 { 789 markPrice: 123.456 * math.Pow10(marketDecimals), 790 openVolume: 0, 791 avgEntryPrice: 0, 792 orders: []*v2.OrderInfo{ 793 { 794 Side: entities.SideSell, 795 Price: "0", 796 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 797 IsMarketOrder: true, 798 }, 799 }, 800 marginAccountBalance: 0, 801 generalAccountBalance: 0, 802 orderMarginAccountBalance: 0, 803 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 804 marginFactor: 0.3, 805 expectedCollIncBest: "3703680000", 806 }, 807 { 808 markPrice: 123.456 * math.Pow10(marketDecimals), 809 openVolume: -int64(1 * math.Pow10(positionDecimalPlaces)), 810 avgEntryPrice: 123.456 * math.Pow10(marketDecimals), 811 orders: []*v2.OrderInfo{}, 812 marginAccountBalance: 0, 813 generalAccountBalance: 0, 814 orderMarginAccountBalance: 0, 815 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 816 marginFactor: 0.3, 817 expectedCollIncBest: "3703680000", 818 }, 819 { 820 markPrice: 123.456 * math.Pow10(marketDecimals), 821 openVolume: -int64(1 * math.Pow10(positionDecimalPlaces)), 822 avgEntryPrice: 123.456 * math.Pow10(marketDecimals), 823 orders: []*v2.OrderInfo{ 824 { 825 Side: entities.SideBuy, 826 Price: "0", 827 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 828 IsMarketOrder: true, 829 }, 830 }, 831 marginAccountBalance: 0, 832 generalAccountBalance: 0, 833 orderMarginAccountBalance: 0, 834 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 835 marginFactor: 0.1, 836 expectedCollIncBest: "0", 837 }, 838 { 839 markPrice: 123.456 * math.Pow10(marketDecimals), 840 openVolume: int64(1 * math.Pow10(positionDecimalPlaces)), 841 avgEntryPrice: 123.456 * math.Pow10(marketDecimals), 842 orders: []*v2.OrderInfo{ 843 { 844 Side: entities.SideSell, 845 Price: "0", 846 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 847 IsMarketOrder: true, 848 }, 849 }, 850 marginAccountBalance: 0, 851 generalAccountBalance: 0, 852 orderMarginAccountBalance: 0, 853 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 854 marginFactor: 0.1, 855 expectedCollIncBest: "0", 856 }, 857 { 858 markPrice: 67813, 859 openVolume: 10000, 860 avgEntryPrice: 68113, 861 orders: []*v2.OrderInfo{}, 862 marginAccountBalance: 68389, 863 generalAccountBalance: 0, 864 orderMarginAccountBalance: 0, 865 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 866 marginFactor: 0.01277, 867 expectedLiquidationBestVolumeOnly: "6781300000", 868 }, 869 { 870 markPrice: 3225 * math.Pow10(marketDecimals), 871 openVolume: 0, 872 avgEntryPrice: 0, 873 orders: []*v2.OrderInfo{ 874 { 875 Side: entities.SideSell, 876 Price: floatToStringWithDp(5000, marketDecimals), 877 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 878 IsMarketOrder: false, 879 }, 880 }, 881 marginAccountBalance: 0, 882 generalAccountBalance: 0, 883 orderMarginAccountBalance: 0, 884 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 885 marginFactor: 0.1, 886 expectedCollIncBest: "50000000000", 887 }, 888 { 889 markPrice: 3225 * math.Pow10(marketDecimals), 890 openVolume: 0, 891 avgEntryPrice: 0, 892 orders: []*v2.OrderInfo{ 893 { 894 Side: entities.SideSell, 895 Price: floatToStringWithDp(5000, marketDecimals), 896 Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), 897 IsMarketOrder: false, 898 }, 899 { 900 Side: entities.SideBuy, 901 Price: floatToStringWithDp(2500, marketDecimals), 902 Remaining: uint64(2 * math.Pow10(positionDecimalPlaces)), 903 IsMarketOrder: false, 904 }, 905 }, 906 marginAccountBalance: 0, 907 generalAccountBalance: 0, 908 orderMarginAccountBalance: 50000000000, 909 marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, 910 marginFactor: 0.1, 911 expectedCollIncBest: "0", 912 }, 913 } 914 for i, tc := range testCases { 915 mktData := entities.MarketData{ 916 MarkPrice: num.DecimalFromFloat(tc.markPrice), 917 AuctionEnd: auctionEnd, 918 ProductData: &entities.ProductData{ 919 ProductData: &vega.ProductData{ 920 Data: &vega.ProductData_PerpetualData{ 921 PerpetualData: &vega.PerpetualData{ 922 FundingPayment: fmt.Sprintf("%f", fundingPayment), 923 FundingRate: "0.05", 924 }, 925 }, 926 }, 927 }, 928 } 929 marketDataService := mocks.NewMockMarketDataService(ctrl) 930 marketDataService.EXPECT().GetMarketDataByID(ctx, marketId).Return(mktData, nil).AnyTimes() 931 932 apiService := api.TradingDataServiceV2{ 933 AssetService: assetService, 934 MarketsService: marketService, 935 MarketDataService: marketDataService, 936 RiskFactorService: riskFactorService, 937 } 938 939 marginFactor := fmt.Sprintf("%f", tc.marginFactor) 940 exclude := false 941 dontScale := false 942 req := &v2.EstimatePositionRequest{ 943 MarketId: marketId, 944 OpenVolume: tc.openVolume, 945 AverageEntryPrice: fmt.Sprintf("%f", tc.avgEntryPrice), 946 Orders: tc.orders, 947 MarginAccountBalance: fmt.Sprintf("%f", tc.marginAccountBalance), 948 GeneralAccountBalance: fmt.Sprintf("%f", tc.generalAccountBalance), 949 OrderMarginAccountBalance: fmt.Sprintf("%f", tc.orderMarginAccountBalance), 950 MarginMode: tc.marginMode, 951 MarginFactor: &marginFactor, 952 IncludeRequiredPositionMarginInAvailableCollateral: &exclude, 953 ScaleLiquidationPriceToMarketDecimals: &dontScale, 954 } 955 include := true 956 req2 := &v2.EstimatePositionRequest{ 957 MarketId: marketId, 958 OpenVolume: tc.openVolume, 959 AverageEntryPrice: fmt.Sprintf("%f", tc.avgEntryPrice), 960 Orders: tc.orders, 961 MarginAccountBalance: fmt.Sprintf("%f", tc.marginAccountBalance), 962 GeneralAccountBalance: fmt.Sprintf("%f", tc.generalAccountBalance), 963 OrderMarginAccountBalance: fmt.Sprintf("%f", tc.orderMarginAccountBalance), 964 MarginMode: tc.marginMode, 965 MarginFactor: &marginFactor, 966 IncludeRequiredPositionMarginInAvailableCollateral: &include, 967 ScaleLiquidationPriceToMarketDecimals: &dontScale, 968 } 969 970 isolatedMargin := tc.marginMode == vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN 971 972 res, err := apiService.EstimatePosition(ctx, req) 973 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 974 require.NotNil(t, res, fmt.Sprintf("test case #%v", i+1)) 975 976 if res.Margin.WorstCase.MaintenanceMargin != "0" { 977 require.NotEqual(t, "0", res.Margin.BestCase.InitialMargin, fmt.Sprintf("test case #%v", i+1)) 978 require.NotEqual(t, "0", res.Margin.WorstCase.InitialMargin, fmt.Sprintf("test case #%v", i+1)) 979 if isolatedMargin { 980 require.Equal(t, "0", res.Margin.BestCase.SearchLevel, fmt.Sprintf("test case #%v", i+1)) 981 require.Equal(t, "0", res.Margin.BestCase.CollateralReleaseLevel, fmt.Sprintf("test case #%v", i+1)) 982 require.Equal(t, "0", res.Margin.WorstCase.SearchLevel, fmt.Sprintf("test case #%v", i+1)) 983 require.Equal(t, "0", res.Margin.WorstCase.CollateralReleaseLevel, fmt.Sprintf("test case #%v", i+1)) 984 } else { 985 require.NotEqual(t, "0", res.Margin.BestCase.SearchLevel, fmt.Sprintf("test case #%v", i+1)) 986 require.NotEqual(t, "0", res.Margin.BestCase.CollateralReleaseLevel, fmt.Sprintf("test case #%v", i+1)) 987 require.NotEqual(t, "0", res.Margin.WorstCase.SearchLevel, fmt.Sprintf("test case #%v", i+1)) 988 require.NotEqual(t, "0", res.Margin.WorstCase.CollateralReleaseLevel, fmt.Sprintf("test case #%v", i+1)) 989 } 990 } 991 992 colIncBest, err := strconv.ParseFloat(res.CollateralIncreaseEstimate.BestCase, 64) 993 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 994 colIncWorst, err := strconv.ParseFloat(res.CollateralIncreaseEstimate.WorstCase, 64) 995 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 996 if tc.expectedCollIncBest != "" { 997 require.Equal(t, tc.expectedCollIncBest, res.CollateralIncreaseEstimate.BestCase, fmt.Sprintf("test case #%v", i+1)) 998 } 999 if tc.expectedLiquidationBestVolumeOnly != "" { 1000 require.Equal(t, tc.expectedLiquidationBestVolumeOnly, res.Liquidation.BestCase.OpenVolumeOnly, fmt.Sprintf("test case #%v", i+1)) 1001 } 1002 1003 if tc.openVolume == 0 { 1004 require.Equal(t, colIncBest, colIncWorst, fmt.Sprintf("test case #%v", i+1)) 1005 } else { 1006 if isolatedMargin { 1007 require.Equal(t, colIncWorst, colIncBest, fmt.Sprintf("test case #%v", i+1)) 1008 } else { 1009 require.GreaterOrEqual(t, colIncWorst, colIncBest, fmt.Sprintf("test case #%v", i+1)) 1010 } 1011 } 1012 initialMarginBest, err := strconv.ParseFloat(res.Margin.BestCase.InitialMargin, 64) 1013 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 1014 initialMarginWorst, err := strconv.ParseFloat(res.Margin.WorstCase.InitialMargin, 64) 1015 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 1016 1017 releaseMarginBest, err := strconv.ParseFloat(res.Margin.BestCase.CollateralReleaseLevel, 64) 1018 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 1019 releaseMarginWorst, err := strconv.ParseFloat(res.Margin.WorstCase.CollateralReleaseLevel, 64) 1020 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 1021 1022 expectedCollIncBest := 0.0 1023 expectedCollIncWorst := 0.0 1024 expectedPosMarginIncrease := 0.0 1025 if isolatedMargin { 1026 priceFactor := math.Pow10(assetDecimals - marketDecimals) 1027 marketOrderNotional := getMarketOrderNotional(tc.markPrice, tc.orders, priceFactor, positionDecimalPlaces) 1028 adjNotional := tc.avgEntryPrice*priceFactor*float64(tc.openVolume)/math.Pow10(positionDecimalPlaces) + marketOrderNotional 1029 1030 requiredPositionMargin := math.Abs(adjNotional) * tc.marginFactor 1031 requiredBuyOrderMargin, requireSellOrderMargin := getLimitOrderNotionalScaledByMarginFactorAndNetOfPosition(t, tc.openVolume, tc.orders, priceFactor, positionDecimalPlaces, tc.marginFactor) 1032 expectedCollIncBest = requiredPositionMargin + max(requiredBuyOrderMargin, requireSellOrderMargin) - tc.marginAccountBalance - tc.orderMarginAccountBalance 1033 expectedCollIncWorst = expectedCollIncBest 1034 1035 expectedPosMarginIncrease = max(0, requiredPositionMargin-tc.marginAccountBalance) 1036 } else { 1037 collat := tc.marginAccountBalance + tc.orderMarginAccountBalance 1038 bDelta := initialMarginBest - collat 1039 wDelta := initialMarginWorst - collat 1040 if bDelta > 0 || collat > releaseMarginBest { 1041 expectedCollIncBest = bDelta 1042 } 1043 if wDelta > 0 || collat > releaseMarginWorst { 1044 expectedCollIncWorst = wDelta 1045 } 1046 } 1047 1048 actualCollIncBest, err := strconv.ParseFloat(res.CollateralIncreaseEstimate.BestCase, 64) 1049 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 1050 actualCollIncWorst, err := strconv.ParseFloat(res.CollateralIncreaseEstimate.WorstCase, 64) 1051 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 1052 1053 require.Equal(t, expectedCollIncBest, actualCollIncBest, fmt.Sprintf("test case #%v", i+1)) 1054 require.Equal(t, expectedCollIncWorst, actualCollIncWorst, fmt.Sprintf("test case #%v", i+1)) 1055 1056 res2, err := apiService.EstimatePosition(ctx, req2) 1057 require.NoError(t, err, fmt.Sprintf("test case #%v", i+1)) 1058 require.NotNil(t, res2, fmt.Sprintf("test case #%v", i+1)) 1059 1060 if isolatedMargin { 1061 if expectedPosMarginIncrease > 0 { 1062 if countOrders(tc.orders, entities.SideBuy) > 0 && res.Liquidation.WorstCase.IncludingBuyOrders != "0" { 1063 require.NotEqual(t, res.Liquidation.WorstCase.IncludingBuyOrders, res2.Liquidation.WorstCase.IncludingBuyOrders, fmt.Sprintf("test case #%v", i+1)) 1064 } 1065 if countOrders(tc.orders, entities.SideSell) > 0 && res.Liquidation.WorstCase.IncludingSellOrders != "0" { 1066 require.NotEqual(t, res.Liquidation.WorstCase.IncludingSellOrders, res2.Liquidation.WorstCase.IncludingSellOrders, fmt.Sprintf("test case #%v", i+1)) 1067 } 1068 if countOrders(tc.orders, entities.SideBuy) > 0 && res.Liquidation.BestCase.IncludingBuyOrders != "0" { 1069 require.NotEqual(t, res.Liquidation.BestCase.IncludingBuyOrders, res2.Liquidation.BestCase.IncludingBuyOrders, fmt.Sprintf("test case #%v", i+1)) 1070 } 1071 if countOrders(tc.orders, entities.SideSell) > 0 && res.Liquidation.BestCase.IncludingSellOrders != "0" { 1072 require.NotEqual(t, res.Liquidation.BestCase.IncludingSellOrders, res2.Liquidation.BestCase.IncludingSellOrders, fmt.Sprintf("test case #%v", i+1)) 1073 } 1074 } 1075 } 1076 1077 scale := true 1078 req2.ScaleLiquidationPriceToMarketDecimals = &scale 1079 1080 res3, err := apiService.EstimatePosition(ctx, req2) 1081 require.NoError(t, err) 1082 require.NotNil(t, res3) 1083 1084 dp := int64(assetDecimals - marketDecimals) 1085 compareDps(t, res2.Liquidation.BestCase.OpenVolumeOnly, res3.Liquidation.BestCase.OpenVolumeOnly, dp) 1086 compareDps(t, res2.Liquidation.BestCase.IncludingBuyOrders, res3.Liquidation.BestCase.IncludingBuyOrders, dp) 1087 compareDps(t, res2.Liquidation.BestCase.IncludingSellOrders, res3.Liquidation.BestCase.IncludingSellOrders, dp) 1088 compareDps(t, res2.Liquidation.WorstCase.OpenVolumeOnly, res3.Liquidation.WorstCase.OpenVolumeOnly, dp) 1089 compareDps(t, res2.Liquidation.WorstCase.IncludingBuyOrders, res3.Liquidation.WorstCase.IncludingBuyOrders, dp) 1090 compareDps(t, res2.Liquidation.WorstCase.IncludingSellOrders, res3.Liquidation.WorstCase.IncludingSellOrders, dp) 1091 1092 liqFp, err := strconv.ParseFloat(res3.Liquidation.WorstCase.OpenVolumeOnly, 64) 1093 require.NoError(t, err) 1094 effectiveOpenVolume := tc.openVolume + sumMarketOrderVolume(tc.orders) 1095 if tc.openVolume != 0 && effectiveOpenVolume > 0 { 1096 require.LessOrEqual(t, liqFp, tc.markPrice, fmt.Sprintf("test case #%v", i+1)) 1097 } 1098 if tc.openVolume != 0 && effectiveOpenVolume < 0 { 1099 require.GreaterOrEqual(t, liqFp, tc.markPrice, fmt.Sprintf("test case #%v", i+1)) 1100 } 1101 } 1102 } 1103 1104 func TestListAccounts(t *testing.T) { 1105 ctrl := gomock.NewController(t) 1106 accountStore := smocks.NewMockAccountStore(ctrl) 1107 balanceStore := smocks.NewMockBalanceStore(ctrl) 1108 ammSvc := mocks.NewMockAMMService(ctrl) 1109 1110 apiService := api.TradingDataServiceV2{ 1111 AccountService: service.NewAccount(accountStore, balanceStore, logging.NewTestLogger()), 1112 AMMPoolService: ammSvc, 1113 } 1114 1115 ctx := context.Background() 1116 1117 req := &v2.ListAccountsRequest{ 1118 Filter: &v2.AccountFilter{ 1119 AssetId: "asset1", 1120 PartyIds: []string{"90421f905ab72919671caca4ffb891ba8b253a4d506e1c0223745268edf4416d", "03f49799559c8fd87859edba4b95d40a22e93dedee64f9d7bdc586fa6bbb90e9"}, 1121 MarketIds: []string{"a7878862705cf303cae4ecc9e6cc60781672a9eb5b29eb62bb88b880821340ea", "af56a491ee1dc0576d8bf28e11d936eb744e9976ae0046c2ec824e2beea98ea0"}, 1122 }, 1123 } 1124 1125 // without derived keys 1126 { 1127 expect := []entities.AccountBalance{ 1128 { 1129 Account: &entities.Account{ 1130 PartyID: "90421f905ab72919671caca4ffb891ba8b253a4d506e1c0223745268edf4416d", 1131 AssetID: "asset1", 1132 }, 1133 }, 1134 { 1135 Account: &entities.Account{ 1136 PartyID: "03f49799559c8fd87859edba4b95d40a22e93dedee64f9d7bdc586fa6bbb90e9", 1137 AssetID: "asset1", 1138 }, 1139 }, 1140 } 1141 1142 accountFilter := entities.AccountFilter{ 1143 AssetID: entities.AssetID(req.Filter.AssetId), 1144 PartyIDs: entities.NewPartyIDSlice(req.Filter.PartyIds...), 1145 MarketIDs: entities.NewMarketIDSlice(req.Filter.MarketIds...), 1146 } 1147 1148 // ammSvc.EXPECT().GetSubKeysForParties(gomock.Any(), gomock.Any(), gomock.Any()).MaxTimes(1).Return(nil, nil) 1149 accountStore.EXPECT().QueryBalances(gomock.Any(), accountFilter, gomock.Any()).Times(1).Return(expect, entities.PageInfo{}, nil) 1150 1151 resp, err := apiService.ListAccounts(ctx, req) 1152 require.NoError(t, err) 1153 require.Len(t, resp.Accounts.Edges, 2) 1154 require.Equal(t, expect[0].ToProto(), resp.Accounts.Edges[0].Node) 1155 require.Equal(t, expect[1].ToProto(), resp.Accounts.Edges[1].Node) 1156 } 1157 1158 // now test with derived keys 1159 { 1160 req.IncludeDerivedParties = ptr.From(true) 1161 1162 expect := []entities.AccountBalance{ 1163 { 1164 Account: &entities.Account{ 1165 PartyID: "90421f905ab72919671caca4ffb891ba8b253a4d506e1c0223745268edf4416d", 1166 AssetID: "asset1", 1167 }, 1168 }, 1169 { 1170 Account: &entities.Account{ 1171 PartyID: "03f49799559c8fd87859edba4b95d40a22e93dedee64f9d7bdc586fa6bbb90e9", 1172 AssetID: "asset1", 1173 }, 1174 }, 1175 } 1176 1177 partyPerDerivedKey := map[string]string{ 1178 "653f9a9850852ca541f20464893536e7986be91c4c364788f6d273fb452778ba": "90421f905ab72919671caca4ffb891ba8b253a4d506e1c0223745268edf4416d", 1179 "79b3aaa5ff0933408cf8f1bcb0b1006cd7bc259d76d400721744e8edc12f2929": "03f49799559c8fd87859edba4b95d40a22e93dedee64f9d7bdc586fa6bbb90e9", 1180 "35c2dc44b391a5f27ace705b554cd78ba42412c3d2597ceba39642f49ebf5d2b": "90421f905ab72919671caca4ffb891ba8b253a4d506e1c0223745268edf4416d", 1181 "161c1c424215cff4f32154871c225dc9760bcac1d4d6783deeaacf7f8b6861ab": "03f49799559c8fd87859edba4b95d40a22e93dedee64f9d7bdc586fa6bbb90e9", 1182 } 1183 1184 ammSvc.EXPECT().GetSubKeysForParties(gomock.Any(), gomock.Any(), gomock.Any()).Times(len(expect)).DoAndReturn(func(_ context.Context, partyIDs []string, _ []string) ([]string, error) { 1185 if len(partyIDs) == 0 { 1186 return nil, nil 1187 } 1188 ret := make([]string, 0, 2) 1189 for dk, pid := range partyPerDerivedKey { 1190 if pid == partyIDs[0] { 1191 ret = append(ret, dk) 1192 } 1193 } 1194 return ret, nil 1195 }) 1196 for derivedKey := range partyPerDerivedKey { 1197 expect = append(expect, entities.AccountBalance{ 1198 Account: &entities.Account{ 1199 PartyID: entities.PartyID(derivedKey), 1200 AssetID: "asset1", 1201 }, 1202 }) 1203 } 1204 1205 accountStore.EXPECT().QueryBalances(gomock.Any(), gomock.Any(), gomock.Any()). 1206 Do(func(ctx context.Context, filter entities.AccountFilter, pageInfo entities.CursorPagination) { 1207 var expectPartyIDs []string 1208 for _, e := range expect { 1209 expectPartyIDs = append(expectPartyIDs, e.PartyID.String()) 1210 } 1211 1212 var gotPartyIDs []string 1213 for _, p := range filter.PartyIDs { 1214 gotPartyIDs = append(gotPartyIDs, p.String()) 1215 } 1216 1217 slices.Sort(expectPartyIDs) 1218 slices.Sort(gotPartyIDs) 1219 require.Zero(t, slices.Compare(expectPartyIDs, gotPartyIDs)) 1220 }).Times(1).Return(expect, entities.PageInfo{}, nil) 1221 1222 resp, err := apiService.ListAccounts(ctx, req) 1223 require.NoError(t, err) 1224 require.Len(t, resp.Accounts.Edges, 6) 1225 1226 for i := range expect { 1227 require.Equal(t, expect[i].ToProto().Owner, resp.Accounts.Edges[i].Node.Owner) 1228 1229 if party, ok := partyPerDerivedKey[expect[i].PartyID.String()]; ok { 1230 require.NotNil(t, resp.Accounts.Edges[i].Node.ParentPartyId) 1231 require.Equal(t, party, *resp.Accounts.Edges[i].Node.ParentPartyId) 1232 } 1233 } 1234 } 1235 } 1236 1237 func TestObserveAccountBalances(t *testing.T) { 1238 ctrl := gomock.NewController(t) 1239 accountStore := smocks.NewMockAccountStore(ctrl) 1240 balanceStore := smocks.NewMockBalanceStore(ctrl) 1241 ammSvc := mocks.NewMockAMMService(ctrl) 1242 1243 apiService := api.TradingDataServiceV2{ 1244 AccountService: service.NewAccount(accountStore, balanceStore, logging.NewTestLogger()), 1245 AMMPoolService: ammSvc, 1246 } 1247 1248 apiService.SetLogger(logging.NewTestLogger()) 1249 1250 ctx := context.Background() 1251 1252 req := &v2.ObserveAccountsRequest{ 1253 MarketId: "a7878862705cf303cae4ecc9e6cc60781672a9eb5b29eb62bb88b880821340ea", 1254 PartyId: "90421f905ab72919671caca4ffb891ba8b253a4d506e1c0223745268edf4416d", 1255 Asset: "asset1", 1256 } 1257 1258 // without derived keys 1259 { 1260 expect := []entities.AccountBalance{ 1261 { 1262 Account: &entities.Account{ 1263 PartyID: entities.PartyID(req.PartyId), 1264 AssetID: entities.AssetID(req.Asset), 1265 MarketID: entities.MarketID(req.MarketId), 1266 Type: vega.AccountType_ACCOUNT_TYPE_GENERAL, 1267 }, 1268 }, 1269 { 1270 Account: &entities.Account{ 1271 PartyID: entities.PartyID(req.PartyId), 1272 AssetID: entities.AssetID(req.Asset), 1273 MarketID: entities.MarketID(req.MarketId), 1274 Type: vega.AccountType_ACCOUNT_TYPE_MARGIN, 1275 }, 1276 }, 1277 { 1278 Account: &entities.Account{ 1279 PartyID: entities.PartyID(req.PartyId), 1280 AssetID: entities.AssetID(req.Asset), 1281 MarketID: entities.MarketID(req.MarketId), 1282 Type: vega.AccountType_ACCOUNT_TYPE_BOND, 1283 }, 1284 }, 1285 } 1286 1287 balanceStore.EXPECT().Flush(gomock.Any()).Return(expect[1:], nil).Times(1) 1288 1289 accountFilter := entities.AccountFilter{ 1290 AssetID: "asset1", 1291 PartyIDs: entities.NewPartyIDSlice(req.PartyId), 1292 MarketIDs: entities.NewMarketIDSlice(req.MarketId), 1293 } 1294 1295 accountStore.EXPECT().QueryBalances(gomock.Any(), accountFilter, gomock.Any()).Times(1).Return(expect[:1], entities.PageInfo{}, nil) 1296 1297 srvCtx, cancel := context.WithCancel(ctx) 1298 res := mockObserveAccountServer{ 1299 mockServerStream: mockServerStream{ctx: srvCtx}, 1300 send: func(oar *v2.ObserveAccountsResponse) error { 1301 switch res := oar.Response.(type) { 1302 case *v2.ObserveAccountsResponse_Snapshot: 1303 require.Len(t, res.Snapshot.Accounts, 1) 1304 require.Equal(t, expect[0].ToProto(), res.Snapshot.Accounts[0]) 1305 case *v2.ObserveAccountsResponse_Updates: 1306 require.Equal(t, len(expect[1:]), len(res.Updates.Accounts)) 1307 require.Equal(t, expect[1].ToProto().Owner, res.Updates.Accounts[0].Owner) 1308 require.Equal(t, expect[1].ToProto().Type, res.Updates.Accounts[0].Type) 1309 require.Equal(t, expect[2].ToProto().Owner, res.Updates.Accounts[1].Owner) 1310 require.Equal(t, expect[2].ToProto().Type, res.Updates.Accounts[1].Type) 1311 cancel() 1312 default: 1313 t.Fatalf("unexpected response type: %T", oar.Response) 1314 } 1315 return nil 1316 }, 1317 } 1318 1319 wg := sync.WaitGroup{} 1320 wg.Add(1) 1321 go func() { 1322 defer wg.Done() 1323 apiService.ObserveAccounts(req, res) 1324 }() 1325 1326 time.Sleep(1 * time.Second) 1327 err := apiService.AccountService.Flush(ctx) 1328 require.NoError(t, err) 1329 wg.Wait() 1330 } 1331 1332 // now test with derived keys 1333 { 1334 req.IncludeDerivedParties = ptr.From(true) 1335 1336 expect := []entities.AccountBalance{ 1337 { 1338 Account: &entities.Account{ 1339 PartyID: entities.PartyID(req.PartyId), 1340 AssetID: entities.AssetID(req.Asset), 1341 MarketID: entities.MarketID(req.MarketId), 1342 Type: vega.AccountType_ACCOUNT_TYPE_GENERAL, 1343 }, 1344 }, 1345 { 1346 Account: &entities.Account{ 1347 PartyID: entities.PartyID(req.PartyId), 1348 AssetID: entities.AssetID(req.Asset), 1349 MarketID: entities.MarketID(req.MarketId), 1350 Type: vega.AccountType_ACCOUNT_TYPE_MARGIN, 1351 }, 1352 }, 1353 { 1354 Account: &entities.Account{ 1355 PartyID: entities.PartyID(req.PartyId), 1356 AssetID: entities.AssetID(req.Asset), 1357 MarketID: entities.MarketID(req.MarketId), 1358 Type: vega.AccountType_ACCOUNT_TYPE_BOND, 1359 }, 1360 }, 1361 } 1362 1363 partyPerDerivedKey := map[string]string{ 1364 "653f9a9850852ca541f20464893536e7986be91c4c364788f6d273fb452778ba": req.PartyId, 1365 } 1366 ammSvc.EXPECT().GetSubKeysForParties(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(maps.Keys(partyPerDerivedKey), nil) 1367 1368 for derivedKey := range partyPerDerivedKey { 1369 expect = append(expect, entities.AccountBalance{ 1370 Account: &entities.Account{ 1371 PartyID: entities.PartyID(derivedKey), 1372 AssetID: entities.AssetID(req.Asset), 1373 MarketID: entities.MarketID(req.MarketId), 1374 Type: vega.AccountType_ACCOUNT_TYPE_GENERAL, 1375 }, 1376 }) 1377 } 1378 balanceStore.EXPECT().Flush(gomock.Any()).Return(expect[3:], nil).Times(1) 1379 1380 accountFilter := entities.AccountFilter{ 1381 AssetID: "asset1", 1382 PartyIDs: entities.NewPartyIDSlice(append(maps.Keys(partyPerDerivedKey), req.PartyId)...), 1383 MarketIDs: entities.NewMarketIDSlice(req.MarketId), 1384 } 1385 1386 accountStore.EXPECT().QueryBalances(gomock.Any(), accountFilter, gomock.Any()).Times(1).Return(expect[:3], entities.PageInfo{}, nil) 1387 1388 srvCtx, cancel := context.WithCancel(ctx) 1389 res := mockObserveAccountServer{ 1390 mockServerStream: mockServerStream{ctx: srvCtx}, 1391 send: func(oar *v2.ObserveAccountsResponse) error { 1392 switch res := oar.Response.(type) { 1393 case *v2.ObserveAccountsResponse_Snapshot: 1394 require.Len(t, res.Snapshot.Accounts, 3) 1395 require.Equal(t, expect[0].ToProto(), res.Snapshot.Accounts[0]) 1396 require.Equal(t, expect[1].ToProto(), res.Snapshot.Accounts[1]) 1397 require.Equal(t, expect[2].ToProto(), res.Snapshot.Accounts[2]) 1398 case *v2.ObserveAccountsResponse_Updates: 1399 require.Equal(t, len(expect[3:]), len(res.Updates.Accounts)) 1400 require.Equal(t, expect[3].ToProto().Owner, res.Updates.Accounts[0].Owner) 1401 require.Equal(t, expect[3].ToProto().Type, res.Updates.Accounts[0].Type) 1402 cancel() 1403 default: 1404 t.Fatalf("unexpected response type: %T", oar.Response) 1405 } 1406 return nil 1407 }, 1408 } 1409 1410 wg := sync.WaitGroup{} 1411 wg.Add(1) 1412 go func() { 1413 defer wg.Done() 1414 apiService.ObserveAccounts(req, res) 1415 }() 1416 1417 time.Sleep(1 * time.Second) 1418 err := apiService.AccountService.Flush(ctx) 1419 require.NoError(t, err) 1420 wg.Wait() 1421 } 1422 } 1423 1424 func TestListRewards(t *testing.T) { 1425 ctrl := gomock.NewController(t) 1426 marketStore := smocks.NewMockMarketStore(ctrl) 1427 rewardStore := smocks.NewMockRewardStore(ctrl) 1428 ammSvc := mocks.NewMockAMMService(ctrl) 1429 1430 apiService := api.TradingDataServiceV2{ 1431 MarketsService: service.NewMarkets(marketStore), 1432 RewardService: service.NewReward(rewardStore, logging.NewTestLogger()), 1433 AMMPoolService: ammSvc, 1434 } 1435 1436 ctx := context.Background() 1437 1438 req := &v2.ListRewardsRequest{ 1439 PartyId: "90421f905ab72919671caca4ffb891ba8b253a4d506e1c0223745268edf4416d", 1440 } 1441 1442 // without derived keys 1443 { 1444 expect := []entities.Reward{ 1445 { 1446 PartyID: entities.PartyID(req.PartyId), 1447 }, 1448 } 1449 1450 pagination := entities.DefaultCursorPagination(true) 1451 1452 rewardStore.EXPECT().GetByCursor(ctx, 1453 []string{req.PartyId}, req.AssetId, req.FromEpoch, req.ToEpoch, pagination, req.TeamId, req.GameId, req.MarketId). 1454 Times(1).Return(expect, entities.PageInfo{}, nil) 1455 1456 resp, err := apiService.ListRewards(ctx, req) 1457 require.NoError(t, err) 1458 require.Len(t, resp.Rewards.Edges, 1) 1459 require.Equal(t, expect[0].ToProto().PartyId, resp.Rewards.Edges[0].Node.PartyId) 1460 } 1461 1462 // now test with derived keys 1463 { 1464 req.IncludeDerivedParties = ptr.From(true) 1465 1466 expect := []entities.Reward{ 1467 { 1468 PartyID: entities.PartyID(req.PartyId), 1469 }, 1470 { 1471 PartyID: entities.PartyID("653f9a9850852ca541f20464893536e7986be91c4c364788f6d273fb452778ba"), 1472 }, 1473 { 1474 PartyID: entities.PartyID("35c2dc44b391a5f27ace705b554cd78ba42412c3d2597ceba39642f49ebf5d2b"), 1475 }, 1476 } 1477 1478 pagination := entities.DefaultCursorPagination(true) 1479 1480 ammSvc.EXPECT().GetSubKeysForParties(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]string{ 1481 "653f9a9850852ca541f20464893536e7986be91c4c364788f6d273fb452778ba", 1482 "35c2dc44b391a5f27ace705b554cd78ba42412c3d2597ceba39642f49ebf5d2b", 1483 }, nil) 1484 1485 rewardStore.EXPECT().GetByCursor(ctx, gomock.Any(), req.AssetId, req.FromEpoch, req.ToEpoch, 1486 pagination, req.TeamId, req.GameId, req.MarketId). 1487 Do(func(_ context.Context, gotPartyIDs []string, _ *string, _, _ *uint64, _ entities.CursorPagination, _, _, _ *string) { 1488 expectPartyIDs := []string{expect[0].PartyID.String(), expect[1].PartyID.String(), expect[2].PartyID.String()} 1489 1490 slices.Sort(expectPartyIDs) 1491 slices.Sort(gotPartyIDs) 1492 require.Zero(t, slices.Compare(expectPartyIDs, gotPartyIDs)) 1493 }). 1494 Times(1).Return(expect, entities.PageInfo{}, nil) 1495 1496 resp, err := apiService.ListRewards(ctx, req) 1497 require.NoError(t, err) 1498 require.Len(t, resp.Rewards.Edges, 3) 1499 require.Equal(t, expect[0].ToProto().PartyId, resp.Rewards.Edges[0].Node.PartyId) 1500 require.Equal(t, expect[1].ToProto().PartyId, resp.Rewards.Edges[1].Node.PartyId) 1501 require.Equal(t, expect[2].ToProto().PartyId, resp.Rewards.Edges[2].Node.PartyId) 1502 } 1503 } 1504 1505 func TestListRewardSummaries(t *testing.T) { 1506 ctrl := gomock.NewController(t) 1507 marketStore := smocks.NewMockMarketStore(ctrl) 1508 rewardStore := smocks.NewMockRewardStore(ctrl) 1509 ammSvc := mocks.NewMockAMMService(ctrl) 1510 1511 apiService := api.TradingDataServiceV2{ 1512 MarketsService: service.NewMarkets(marketStore), 1513 RewardService: service.NewReward(rewardStore, logging.NewTestLogger()), 1514 AMMPoolService: ammSvc, 1515 } 1516 1517 ctx := context.Background() 1518 1519 t.Run("without party id", func(t *testing.T) { 1520 req := &v2.ListRewardSummariesRequest{ 1521 AssetId: ptr.From("asset1"), 1522 } 1523 1524 expect := []entities.RewardSummary{ 1525 { 1526 PartyID: entities.PartyID("random-party"), 1527 AssetID: entities.AssetID(*req.AssetId), 1528 Amount: num.NewDecimalFromFloat(200), 1529 }, 1530 } 1531 1532 rewardStore.EXPECT().GetSummaries(ctx, []string{}, req.AssetId). 1533 Times(1).Return(expect, nil) 1534 1535 resp, err := apiService.ListRewardSummaries(ctx, req) 1536 require.NoError(t, err) 1537 require.Len(t, resp.Summaries, 1) 1538 require.Equal(t, expect[0].ToProto(), resp.Summaries[0]) 1539 }) 1540 1541 t.Run("with derived keys without party", func(t *testing.T) { 1542 req := &v2.ListRewardSummariesRequest{ 1543 AssetId: ptr.From("asset1"), 1544 IncludeDerivedParties: ptr.From(true), 1545 } 1546 1547 expect := []entities.RewardSummary{ 1548 { 1549 PartyID: entities.PartyID("random-party"), 1550 AssetID: entities.AssetID(*req.AssetId), 1551 Amount: num.NewDecimalFromFloat(200), 1552 }, 1553 } 1554 1555 ammSvc.EXPECT().GetSubKeysForParties(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil, nil) 1556 rewardStore.EXPECT().GetSummaries(ctx, []string{}, req.AssetId). 1557 Times(1).Return(expect, nil) 1558 1559 resp, err := apiService.ListRewardSummaries(ctx, req) 1560 require.NoError(t, err) 1561 require.Len(t, resp.Summaries, 1) 1562 require.Equal(t, expect[0].ToProto(), resp.Summaries[0]) 1563 }) 1564 1565 t.Run("without derived keys with party", func(t *testing.T) { 1566 req := &v2.ListRewardSummariesRequest{ 1567 PartyId: ptr.From("90421f905ab72919671caca4ffb891ba8b253a4d506e1c0223745268edf4416d"), 1568 AssetId: ptr.From("asset1"), 1569 } 1570 1571 expect := []entities.RewardSummary{ 1572 { 1573 PartyID: entities.PartyID(*req.PartyId), 1574 AssetID: entities.AssetID(*req.AssetId), 1575 Amount: num.NewDecimalFromFloat(200), 1576 }, 1577 } 1578 1579 rewardStore.EXPECT().GetSummaries(ctx, []string{*req.PartyId}, req.AssetId). 1580 Times(1).Return(expect, nil) 1581 1582 resp, err := apiService.ListRewardSummaries(ctx, req) 1583 require.NoError(t, err) 1584 require.Len(t, resp.Summaries, 1) 1585 require.Equal(t, expect[0].ToProto(), resp.Summaries[0]) 1586 }) 1587 1588 t.Run("with derived keys and party", func(t *testing.T) { 1589 req := &v2.ListRewardSummariesRequest{ 1590 PartyId: ptr.From("90421f905ab72919671caca4ffb891ba8b253a4d506e1c0223745268edf4416d"), 1591 AssetId: ptr.From("asset1"), 1592 IncludeDerivedParties: ptr.From(true), 1593 } 1594 1595 expect := []entities.RewardSummary{ 1596 { 1597 PartyID: entities.PartyID(*req.PartyId), 1598 AssetID: entities.AssetID(*req.AssetId), 1599 Amount: num.NewDecimalFromFloat(200), 1600 }, 1601 { 1602 PartyID: entities.PartyID("653f9a9850852ca541f20464893536e7986be91c4c364788f6d273fb452778ba"), 1603 AssetID: entities.AssetID(*req.AssetId), 1604 Amount: num.NewDecimalFromFloat(150), 1605 }, 1606 { 1607 PartyID: entities.PartyID("35c2dc44b391a5f27ace705b554cd78ba42412c3d2597ceba39642f49ebf5d2b"), 1608 AssetID: entities.AssetID(*req.AssetId), 1609 Amount: num.NewDecimalFromFloat(130), 1610 }, 1611 } 1612 1613 ammSvc.EXPECT().GetSubKeysForParties(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return([]string{ 1614 "653f9a9850852ca541f20464893536e7986be91c4c364788f6d273fb452778ba", 1615 "35c2dc44b391a5f27ace705b554cd78ba42412c3d2597ceba39642f49ebf5d2b", 1616 }, nil) 1617 1618 rewardStore.EXPECT().GetSummaries(ctx, gomock.Any(), req.AssetId). 1619 Do(func(_ context.Context, gotPartyIDs []string, _ *string) { 1620 var expectPartyIDs []string 1621 for _, e := range expect { 1622 expectPartyIDs = append(expectPartyIDs, e.PartyID.String()) 1623 } 1624 1625 slices.Sort(expectPartyIDs) 1626 slices.Sort(gotPartyIDs) 1627 require.Zero(t, slices.Compare(expectPartyIDs, gotPartyIDs)) 1628 }).Times(1).Return(expect, nil) 1629 1630 resp, err := apiService.ListRewardSummaries(ctx, req) 1631 require.NoError(t, err) 1632 require.Len(t, resp.Summaries, 3) 1633 require.Equal(t, expect[0].ToProto(), resp.Summaries[0]) 1634 require.Equal(t, expect[1].ToProto(), resp.Summaries[1]) 1635 require.Equal(t, expect[2].ToProto(), resp.Summaries[2]) 1636 }) 1637 } 1638 1639 //nolint:unparam 1640 func floatToStringWithDp(value float64, dp int) string { 1641 return fmt.Sprintf("%f", value*math.Pow10(dp)) 1642 } 1643 1644 func compareDps(t *testing.T, bigger, smaller string, dp int64) { 1645 t.Helper() 1646 b, err := strconv.ParseFloat(bigger, 64) 1647 require.NoError(t, err) 1648 s, err := strconv.ParseFloat(smaller, 64) 1649 require.NoError(t, err) 1650 if s != 0 { 1651 l := int64(math.Round(math.Log10(b / s))) 1652 require.Equal(t, dp, l) 1653 } 1654 } 1655 1656 func countOrders(orders []*v2.OrderInfo, side vega.Side) int { 1657 c := 0 1658 for _, o := range orders { 1659 if o.Side == side { 1660 c += 1 1661 } 1662 } 1663 return c 1664 } 1665 1666 func sumMarketOrderVolume(orders []*v2.OrderInfo) int64 { 1667 v := int64(0) 1668 for _, o := range orders { 1669 if !o.IsMarketOrder { 1670 continue 1671 } 1672 if o.Side == entities.SideBuy { 1673 v += int64(o.Remaining) 1674 } 1675 if o.Side == entities.SideSell { 1676 v -= int64(o.Remaining) 1677 } 1678 } 1679 return v 1680 } 1681 1682 type mockStream struct { 1683 sent []*httpbody.HttpBody 1684 } 1685 1686 func (s *mockStream) Send(b *httpbody.HttpBody) error { s.sent = append(s.sent, b); return nil } 1687 func (s *mockStream) SetHeader(metadata.MD) error { return nil } 1688 func (s *mockStream) SendHeader(metadata.MD) error { return nil } 1689 func (s *mockStream) SetTrailer(metadata.MD) {} 1690 func (s *mockStream) Context() context.Context { return context.Background() } 1691 func (s *mockStream) SendMsg(m interface{}) error { return nil } 1692 func (s *mockStream) RecvMsg(m interface{}) error { return nil } 1693 1694 func getLimitOrderNotionalScaledByMarginFactorAndNetOfPosition(t *testing.T, positionSize int64, orders []*v2.OrderInfo, priceFactor float64, positionDecimals int, marginFactor float64) (float64, float64) { 1695 t.Helper() 1696 buyNotional, sellNotional := 0.0, 0.0 1697 buyOrders, sellOrders := make([]*v2.OrderInfo, 0), make([]*v2.OrderInfo, 0) 1698 for _, o := range orders { 1699 if o.Side == entities.SideBuy { 1700 if o.IsMarketOrder { 1701 positionSize += int64(o.Remaining) 1702 continue 1703 } 1704 buyOrders = append(buyOrders, o) 1705 } 1706 if o.Side == entities.SideSell { 1707 if o.IsMarketOrder { 1708 positionSize -= int64(o.Remaining) 1709 continue 1710 } 1711 sellOrders = append(sellOrders, o) 1712 } 1713 } 1714 1715 // sort orders from best to worst 1716 sort.Slice(buyOrders, func(i, j int) bool { 1717 price_i, err := strconv.ParseFloat(buyOrders[i].Price, 64) 1718 require.NoError(t, err) 1719 price_j, err := strconv.ParseFloat(buyOrders[j].Price, 64) 1720 require.NoError(t, err) 1721 1722 return price_i > price_j 1723 }) 1724 sort.Slice(sellOrders, func(i, j int) bool { 1725 price_i, err := strconv.ParseFloat(sellOrders[i].Price, 64) 1726 require.NoError(t, err) 1727 price_j, err := strconv.ParseFloat(sellOrders[j].Price, 64) 1728 require.NoError(t, err) 1729 1730 return price_i < price_j 1731 }) 1732 1733 remainingCovered := uint64(math.Abs(float64(positionSize))) 1734 for _, o := range buyOrders { 1735 size := o.Remaining 1736 if remainingCovered != 0 && (positionSize < 0) { 1737 if size >= remainingCovered { // part of the order doesn't require margin 1738 size = size - remainingCovered 1739 remainingCovered = 0 1740 } else { // the entire order doesn't require margin 1741 remainingCovered -= size 1742 size = 0 1743 } 1744 } 1745 if size > 0 { 1746 price, err := strconv.ParseFloat(o.Price, 64) 1747 require.NoError(t, err) 1748 buyNotional += price * priceFactor * float64(size) / math.Pow10(positionDecimals) 1749 } 1750 } 1751 1752 remainingCovered = uint64(math.Abs(float64(positionSize))) 1753 for _, o := range sellOrders { 1754 size := o.Remaining 1755 if remainingCovered != 0 && (positionSize > 0) { 1756 if size >= remainingCovered { // part of the order doesn't require margin 1757 size = size - remainingCovered 1758 remainingCovered = 0 1759 } else { // the entire order doesn't require margin 1760 remainingCovered -= size 1761 size = 0 1762 } 1763 } 1764 if size > 0 { 1765 price, err := strconv.ParseFloat(o.Price, 64) 1766 require.NoError(t, err) 1767 sellNotional += price * priceFactor * float64(size) / math.Pow10(positionDecimals) 1768 } 1769 } 1770 1771 return buyNotional * marginFactor, sellNotional * marginFactor 1772 } 1773 1774 func getMarketOrderNotional(marketObservable float64, orders []*v2.OrderInfo, priceFactor float64, positionDecimals int) float64 { 1775 notional := 0.0 1776 for _, o := range orders { 1777 if !o.IsMarketOrder { 1778 continue 1779 } 1780 size := float64(o.Remaining) / math.Pow10(positionDecimals) 1781 if o.Side == vega.Side_SIDE_SELL { 1782 size = -size 1783 } 1784 notional += marketObservable * priceFactor * size 1785 } 1786 return notional 1787 } 1788 1789 type mockObserveAccountServer struct { 1790 mockServerStream 1791 send func(*v2.ObserveAccountsResponse) error 1792 } 1793 1794 func (m mockObserveAccountServer) Send(resp *v2.ObserveAccountsResponse) error { 1795 if m.send != nil { 1796 return m.send(resp) 1797 } 1798 return nil 1799 } 1800 1801 type mockServerStream struct { 1802 ctx context.Context 1803 recvMsg func(m interface{}) error 1804 sendMsg func(m interface{}) error 1805 setHeader func(md metadata.MD) error 1806 sendHeader func(md metadata.MD) error 1807 setTrailer func(md metadata.MD) 1808 } 1809 1810 func (m mockServerStream) Context() context.Context { 1811 return m.ctx 1812 } 1813 1814 func (m mockServerStream) SendMsg(msg interface{}) error { 1815 if m.sendMsg != nil { 1816 return m.sendMsg(msg) 1817 } 1818 return nil 1819 } 1820 1821 func (m mockServerStream) RecvMsg(msg interface{}) error { 1822 if m.recvMsg != nil { 1823 return m.recvMsg(msg) 1824 } 1825 return nil 1826 } 1827 1828 func (m mockServerStream) SetHeader(md metadata.MD) error { 1829 if m.setHeader != nil { 1830 return m.setHeader(md) 1831 } 1832 return nil 1833 } 1834 1835 func (m mockServerStream) SendHeader(md metadata.MD) error { 1836 if m.sendHeader != nil { 1837 return m.sendHeader(md) 1838 } 1839 return nil 1840 } 1841 1842 func (m mockServerStream) SetTrailer(md metadata.MD) { 1843 if m.setTrailer != nil { 1844 m.setTrailer(md) 1845 } 1846 }