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  }