From 8b305ef305ca1a64466eefec8515762c98ca3ebc Mon Sep 17 00:00:00 2001 From: Witold Date: Wed, 20 Mar 2024 15:18:19 +0100 Subject: [PATCH 1/2] refactor: bring margin estimate in line with actual behaviour for isolated mode --- core/risk/isolated_margin.go | 8 +- core/risk/isolated_margin_ex_test.go | 6 +- core/risk/isolated_margin_test.go | 10 +- core/risk/margins_calculation.go | 28 ++++- datanode/api/trading_data_v2.go | 6 +- datanode/api/trading_data_v2_test.go | 176 ++++++++++++++++++++++----- 6 files changed, 182 insertions(+), 52 deletions(-) diff --git a/core/risk/isolated_margin.go b/core/risk/isolated_margin.go index 67a98bfb082..f48077b6fb9 100644 --- a/core/risk/isolated_margin.go +++ b/core/risk/isolated_margin.go @@ -34,7 +34,7 @@ func (e *Engine) calculateIsolatedMargins(m events.Margin, marketObservable *num auction := e.as.InAuction() && !e.as.CanLeave() // NB:we don't include orders when calculating margin for isolated margin as they are margined separately! margins := e.calculateMargins(m, marketObservable, *e.factors, false, auction, inc) - margins.OrderMargin = calcOrderMargins(m.Size(), orders, e.positionFactor, marginFactor, auctionPrice) + margins.OrderMargin = CalcOrderMargins(m.Size(), orders, e.positionFactor, marginFactor, auctionPrice) margins.CollateralReleaseLevel = num.UintZero() margins.SearchLevel = num.UintZero() margins.MarginMode = types.MarginModeIsolatedMargin @@ -497,11 +497,11 @@ func getIsolatedMarginTransfersOnPositionChange(party, asset string, trades []*t } func (e *Engine) CalcOrderMarginsForClosedOutParty(orders []*types.Order, marginFactor num.Decimal) *num.Uint { - return calcOrderMargins(0, orders, e.positionFactor, marginFactor, nil) + return CalcOrderMargins(0, orders, e.positionFactor, marginFactor, nil) } -// calcOrderMargins calculates the the order margin required for the party given their current orders and margin factor. -func calcOrderMargins(positionSize int64, orders []*types.Order, positionFactor, marginFactor num.Decimal, auctionPrice *num.Uint) *num.Uint { +// CalcOrderMargins calculates the the order margin required for the party given their current orders and margin factor. +func CalcOrderMargins(positionSize int64, orders []*types.Order, positionFactor, marginFactor num.Decimal, auctionPrice *num.Uint) *num.Uint { if len(orders) == 0 { return num.UintZero() } diff --git a/core/risk/isolated_margin_ex_test.go b/core/risk/isolated_margin_ex_test.go index 9cb73f119e6..0dff0d4e60c 100644 --- a/core/risk/isolated_margin_ex_test.go +++ b/core/risk/isolated_margin_ex_test.go @@ -91,7 +91,7 @@ func TestSwithToIsolatedMarginContinuous(t *testing.T) { require.Equal(t, num.NewUint(0), riskEvent[0].MarginLevels().OrderMargin) buyOrderInfo, sellOrderInfo := extractOrderInfo(orders) - requiredPositionMarginStatic, requiredOrderMarginStatic := risk.CalculateRequiredMarginInIsolatedMode(evt.size, evt.AverageEntryPrice().ToDecimal(), evt.Price().ToDecimal(), buyOrderInfo, sellOrderInfo, positionFactor, marginFactor) + requiredPositionMarginStatic, requiredOrderMarginStatic := risk.CalculateRequiredMarginInIsolatedMode(evt.size, evt.AverageEntryPrice().ToDecimal(), evt.Price().ToDecimal(), buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, nil) require.True(t, !requiredPositionMarginStatic.IsZero()) require.True(t, requiredOrderMarginStatic.IsZero()) transferRecalc := requiredPositionMarginStatic.Sub(evt.MarginBalance().ToDecimal()) @@ -119,7 +119,7 @@ func TestSwithToIsolatedMarginContinuous(t *testing.T) { require.Equal(t, num.NewUint(300), riskEvent[0].MarginLevels().OrderMargin) buyOrderInfo, sellOrderInfo = extractOrderInfo(orders) - requiredPositionMarginStatic, requiredOrderMarginStatic = risk.CalculateRequiredMarginInIsolatedMode(evt.size, evt.AverageEntryPrice().ToDecimal(), evt.Price().ToDecimal(), buyOrderInfo, sellOrderInfo, positionFactor, marginFactor) + requiredPositionMarginStatic, requiredOrderMarginStatic = risk.CalculateRequiredMarginInIsolatedMode(evt.size, evt.AverageEntryPrice().ToDecimal(), evt.Price().ToDecimal(), buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, nil) require.True(t, !requiredPositionMarginStatic.IsZero()) require.True(t, !requiredOrderMarginStatic.IsZero()) transferRecalc = requiredPositionMarginStatic.Sub(evt.MarginBalance().ToDecimal()) @@ -147,7 +147,7 @@ func TestSwithToIsolatedMarginContinuous(t *testing.T) { require.Equal(t, num.NewUint(300), riskEvent[0].MarginLevels().OrderMargin) buyOrderInfo, sellOrderInfo = extractOrderInfo(orders) - requiredPositionMarginStatic, requiredOrderMarginStatic = risk.CalculateRequiredMarginInIsolatedMode(evt.size, evt.AverageEntryPrice().ToDecimal(), evt.Price().ToDecimal(), buyOrderInfo, sellOrderInfo, positionFactor, marginFactor) + requiredPositionMarginStatic, requiredOrderMarginStatic = risk.CalculateRequiredMarginInIsolatedMode(evt.size, evt.AverageEntryPrice().ToDecimal(), evt.Price().ToDecimal(), buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, nil) require.True(t, !requiredPositionMarginStatic.IsZero()) require.True(t, !requiredOrderMarginStatic.IsZero()) transferRecalc = evt.MarginBalance().ToDecimal().Sub(requiredPositionMarginStatic) diff --git a/core/risk/isolated_margin_test.go b/core/risk/isolated_margin_test.go index 7f07bb63559..1992dd82328 100644 --- a/core/risk/isolated_margin_test.go +++ b/core/risk/isolated_margin_test.go @@ -262,7 +262,7 @@ func TestCalcOrderMarginContinous(t *testing.T) { // buy orderMargin = 0.5*(10 * 50 + 20 * 40 + 30 * 20)/10 = 95 // sell orderMargin = 0.5*(10 * 20 + 20 * 40 + 30 * 50)/10 = 125 // order margin = max(95,125) = 125 - orderSideMargin := calcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) + orderSideMargin := CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) require.Equal(t, num.NewUint(125), orderSideMargin) staticResult := CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero()) require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String()) @@ -271,7 +271,7 @@ func TestCalcOrderMarginContinous(t *testing.T) { // buy orderMargin = 0.5*(10 * 50 + 20 * 40 + 30 * 20)/10 = 95 // sell orderMargin = 0.5*(6 * 20 + 20 * 40 + 30 * 50)/10 = 121 currentPos = 4 - orderSideMargin = calcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) + orderSideMargin = CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) require.Equal(t, num.NewUint(121), orderSideMargin) staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero()) require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String()) @@ -280,7 +280,7 @@ func TestCalcOrderMarginContinous(t *testing.T) { // buy orderMargin = 0.5*(10 * 50 + 20 * 40 + 30 * 20)/10 = 95 // sell orderMargin = 0.5*(0 * 20 + 5 * 40 + 30 * 50)/10 = 85 currentPos = 25 - orderSideMargin = calcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) + orderSideMargin = CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) require.Equal(t, num.NewUint(95), orderSideMargin) staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero()) require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String()) @@ -289,7 +289,7 @@ func TestCalcOrderMarginContinous(t *testing.T) { // buy orderMargin = 0.5*(6 * 50 + 20 * 40 + 30 * 20)/10 = 85 // sell orderMargin = 0.5*(10 * 20 + 20 * 40 + 30 * 50)/10 = 125 currentPos = -4 - orderSideMargin = calcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) + orderSideMargin = CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) require.Equal(t, num.NewUint(125), orderSideMargin) staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero()) require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String()) @@ -298,7 +298,7 @@ func TestCalcOrderMarginContinous(t *testing.T) { // buy orderMargin = 0.5*(0 * 50 + 10 * 40 + 30 * 20)/10 = 50 // sell orderMargin = 0.5*(10 * 20 + 20 * 40 + 30 * 50)/10 = 125 currentPos = -20 - orderSideMargin = calcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) + orderSideMargin = CalcOrderMargins(currentPos, orders, positionFactor, marginFactor, nil) require.Equal(t, num.NewUint(125), orderSideMargin) staticResult = CalcOrderMarginIsolatedMode(currentPos, buyOrderInfo, sellOrderInfo, positionFactor, marginFactor, num.DecimalZero()) require.Equal(t, staticResult.Round(0).String(), orderSideMargin.String()) diff --git a/core/risk/margins_calculation.go b/core/risk/margins_calculation.go index b9f23cabc6d..73cbd78570a 100644 --- a/core/risk/margins_calculation.go +++ b/core/risk/margins_calculation.go @@ -339,28 +339,44 @@ func calcOrderSideMarginIsolatedMode(currentPosition int64, orders []*OrderInfo, return margin.Mul(marginFactor).Div(positionFactor) } -func CalculateRequiredMarginInIsolatedMode(sizePosition int64, averageEntryPrice, marketObservable num.Decimal, buyOrders, sellOrders []*OrderInfo, positionFactor, marginFactor num.Decimal) (num.Decimal, num.Decimal) { - totalOrderNotional := num.DecimalZero() +func CalculateRequiredMarginInIsolatedMode(sizePosition int64, averageEntryPrice, marketObservable num.Decimal, buyOrders, sellOrders []*OrderInfo, positionFactor, marginFactor num.Decimal, auctionPrice *num.Uint) (num.Decimal, num.Decimal) { marketOrderAdjustedPositionNotional := averageEntryPrice.Copy().Mul(num.DecimalFromInt64(sizePosition)) + var orders []*types.Order = make([]*types.Order, 0, len(buyOrders)+len(sellOrders)) // assume market orders fill immediately at marketObservable price for _, o := range buyOrders { if o.IsMarketOrder { + sizePosition += int64(o.TrueRemaining) marketOrderAdjustedPositionNotional = marketOrderAdjustedPositionNotional.Add(marketObservable.Mul(num.DecimalFromInt64(int64(o.TrueRemaining)))) } else { - totalOrderNotional = totalOrderNotional.Add(o.Price.Mul(num.DecimalFromInt64(int64(o.TrueRemaining)))) + price, _ := num.UintFromDecimal(o.Price) + ord := &types.Order{ + Status: types.OrderStatusActive, + Remaining: o.TrueRemaining, + Price: price, + Side: types.SideBuy, + } + orders = append(orders, ord) } } for _, o := range sellOrders { if o.IsMarketOrder { + sizePosition -= int64(o.TrueRemaining) marketOrderAdjustedPositionNotional = marketOrderAdjustedPositionNotional.Sub(marketObservable.Mul(num.DecimalFromInt64(int64(o.TrueRemaining)))) } else { - totalOrderNotional = totalOrderNotional.Add(o.Price.Mul(num.DecimalFromInt64(int64(o.TrueRemaining)))) + price, _ := num.UintFromDecimal(o.Price) + ord := &types.Order{ + Status: types.OrderStatusActive, + Remaining: o.TrueRemaining, + Price: price, + Side: types.SideSell, + } + orders = append(orders, ord) } } requiredPositionMargin := marketOrderAdjustedPositionNotional.Abs().Mul(marginFactor).Div(positionFactor) - requiredOrderMargin := totalOrderNotional.Mul(marginFactor).Div(positionFactor) + requiredOrderMargin := CalcOrderMargins(sizePosition, orders, positionFactor, marginFactor, auctionPrice) - return requiredPositionMargin, requiredOrderMargin + return requiredPositionMargin, requiredOrderMargin.ToDecimal() } diff --git a/datanode/api/trading_data_v2.go b/datanode/api/trading_data_v2.go index ef5203c0ed1..a5ffc309c83 100644 --- a/datanode/api/trading_data_v2.go +++ b/datanode/api/trading_data_v2.go @@ -3417,7 +3417,11 @@ func (t *TradingDataServiceV2) EstimatePosition(ctx context.Context, req *v2.Est var wMarginDelta, bMarginDelta, posMarginDelta num.Decimal combinedMargin := marginAccountBalance.Add(orderAccountBalance) if isolatedMarginMode { - requiredPositionMargin, requiredOrderMargin := risk.CalculateRequiredMarginInIsolatedMode(req.OpenVolume, avgEntryPrice, marketObservable, buyOrders, sellOrders, positionFactor, dMarginFactor) + var ap *num.Uint = nil + if !auctionPrice.IsZero() { + ap, _ = num.UintFromDecimal(auctionPrice) + } + requiredPositionMargin, requiredOrderMargin := risk.CalculateRequiredMarginInIsolatedMode(req.OpenVolume, avgEntryPrice, marketObservable, buyOrders, sellOrders, positionFactor, dMarginFactor, ap) posMarginDelta = requiredPositionMargin.Sub(marginAccountBalance) wMarginDelta = requiredPositionMargin.Add(requiredOrderMargin).Sub(combinedMargin) bMarginDelta = wMarginDelta diff --git a/datanode/api/trading_data_v2_test.go b/datanode/api/trading_data_v2_test.go index f3f70a8c064..50c5f20b174 100644 --- a/datanode/api/trading_data_v2_test.go +++ b/datanode/api/trading_data_v2_test.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "math" + "sort" "strconv" "strings" "testing" @@ -133,7 +134,6 @@ func TestEstimatePosition(t *testing.T) { rfLong := num.DecimalFromFloat(0.1) rfShort := num.DecimalFromFloat(0.2) - markPrice := 123.456 * math.Pow10(marketDecimals) auctionEnd := int64(0) fundingPayment := 1234.56789 @@ -197,7 +197,7 @@ func TestEstimatePosition(t *testing.T) { expectedLiquidationBestVolumeOnly string }{ { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: 0, avgEntryPrice: 0, orders: []*v2.OrderInfo{ @@ -214,7 +214,7 @@ func TestEstimatePosition(t *testing.T) { marginMode: vega.MarginMode_MARGIN_MODE_CROSS_MARGIN, }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: 0, avgEntryPrice: 0, orders: []*v2.OrderInfo{ @@ -232,7 +232,7 @@ func TestEstimatePosition(t *testing.T) { marginFactor: 0.1, }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: int64(10 * math.Pow10(positionDecimalPlaces)), avgEntryPrice: 111.1 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{ @@ -249,7 +249,7 @@ func TestEstimatePosition(t *testing.T) { marginMode: vega.MarginMode_MARGIN_MODE_CROSS_MARGIN, }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: int64(-10 * math.Pow10(positionDecimalPlaces)), avgEntryPrice: 111.1 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{ @@ -267,7 +267,7 @@ func TestEstimatePosition(t *testing.T) { marginFactor: 0.5, }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: int64(-10 * math.Pow10(positionDecimalPlaces)), avgEntryPrice: 111.1 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{ @@ -290,7 +290,7 @@ func TestEstimatePosition(t *testing.T) { marginMode: vega.MarginMode_MARGIN_MODE_CROSS_MARGIN, }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: int64(-10 * math.Pow10(positionDecimalPlaces)), avgEntryPrice: 111.1 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{ @@ -314,7 +314,7 @@ func TestEstimatePosition(t *testing.T) { marginFactor: 0.3, }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: int64(10 * math.Pow10(positionDecimalPlaces)), avgEntryPrice: 111.1 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{ @@ -361,7 +361,7 @@ func TestEstimatePosition(t *testing.T) { marginMode: vega.MarginMode_MARGIN_MODE_CROSS_MARGIN, }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: -int64(10 * math.Pow10(positionDecimalPlaces)), avgEntryPrice: 111.1 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{ @@ -409,13 +409,13 @@ func TestEstimatePosition(t *testing.T) { marginFactor: 0.1, }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: 0, avgEntryPrice: 0, orders: []*v2.OrderInfo{ { Side: entities.SideBuy, - Price: fmt.Sprintf("%f", markPrice), + Price: floatToStringWithDp(123.456, marketDecimals), Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), IsMarketOrder: false, }, @@ -428,7 +428,7 @@ func TestEstimatePosition(t *testing.T) { expectedCollIncBest: "3703680000", }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: 0, avgEntryPrice: 0, orders: []*v2.OrderInfo{ @@ -447,9 +447,9 @@ func TestEstimatePosition(t *testing.T) { expectedCollIncBest: "3703680000", }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: int64(1 * math.Pow10(positionDecimalPlaces)), - avgEntryPrice: markPrice, + avgEntryPrice: 123.456 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{}, marginAccountBalance: 0, generalAccountBalance: 0, @@ -459,13 +459,13 @@ func TestEstimatePosition(t *testing.T) { expectedCollIncBest: "3703680000", }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: 0, avgEntryPrice: 0, orders: []*v2.OrderInfo{ { Side: entities.SideSell, - Price: fmt.Sprintf("%f", markPrice), + Price: floatToStringWithDp(123.456, marketDecimals), Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), IsMarketOrder: false, }, @@ -478,7 +478,7 @@ func TestEstimatePosition(t *testing.T) { expectedCollIncBest: "3703680000", }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: 0, avgEntryPrice: 0, orders: []*v2.OrderInfo{ @@ -497,9 +497,9 @@ func TestEstimatePosition(t *testing.T) { expectedCollIncBest: "3703680000", }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: -int64(1 * math.Pow10(positionDecimalPlaces)), - avgEntryPrice: markPrice, + avgEntryPrice: 123.456 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{}, marginAccountBalance: 0, generalAccountBalance: 0, @@ -509,9 +509,9 @@ func TestEstimatePosition(t *testing.T) { expectedCollIncBest: "3703680000", }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: -int64(1 * math.Pow10(positionDecimalPlaces)), - avgEntryPrice: markPrice, + avgEntryPrice: 123.456 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{ { Side: entities.SideBuy, @@ -528,9 +528,9 @@ func TestEstimatePosition(t *testing.T) { expectedCollIncBest: "0", }, { - markPrice: markPrice, + markPrice: 123.456 * math.Pow10(marketDecimals), openVolume: int64(1 * math.Pow10(positionDecimalPlaces)), - avgEntryPrice: markPrice, + avgEntryPrice: 123.456 * math.Pow10(marketDecimals), orders: []*v2.OrderInfo{ { Side: entities.SideSell, @@ -558,6 +558,50 @@ func TestEstimatePosition(t *testing.T) { marginFactor: 0.01277, expectedLiquidationBestVolumeOnly: "6781300000", }, + { + markPrice: 3225 * math.Pow10(marketDecimals), + openVolume: 0, + avgEntryPrice: 0, + orders: []*v2.OrderInfo{ + { + Side: entities.SideSell, + Price: floatToStringWithDp(5000, marketDecimals), + Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), + IsMarketOrder: false, + }, + }, + marginAccountBalance: 0, + generalAccountBalance: 0, + orderMarginAccountBalance: 0, + marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, + marginFactor: 0.1, + expectedCollIncBest: "50000000000", + }, + { + markPrice: 3225 * math.Pow10(marketDecimals), + openVolume: 0, + avgEntryPrice: 0, + orders: []*v2.OrderInfo{ + { + Side: entities.SideSell, + Price: floatToStringWithDp(5000, marketDecimals), + Remaining: uint64(1 * math.Pow10(positionDecimalPlaces)), + IsMarketOrder: false, + }, + { + Side: entities.SideBuy, + Price: floatToStringWithDp(2500, marketDecimals), + Remaining: uint64(2 * math.Pow10(positionDecimalPlaces)), + IsMarketOrder: false, + }, + }, + marginAccountBalance: 0, + generalAccountBalance: 0, + orderMarginAccountBalance: 50000000000, + marginMode: vega.MarginMode_MARGIN_MODE_ISOLATED_MARGIN, + marginFactor: 0.1, + expectedCollIncBest: "0", + }, } for i, tc := range testCases { mktData := entities.MarketData{ @@ -673,11 +717,11 @@ func TestEstimatePosition(t *testing.T) { if isolatedMargin { priceFactor := math.Pow10(assetDecimals - marketDecimals) marketOrderNotional := getMarketOrderNotional(tc.markPrice, tc.orders, priceFactor, positionDecimalPlaces) - adjNotional := (tc.avgEntryPrice*priceFactor*float64(tc.openVolume)/math.Pow10(positionDecimalPlaces) + marketOrderNotional) + adjNotional := tc.avgEntryPrice*priceFactor*float64(tc.openVolume)/math.Pow10(positionDecimalPlaces) + marketOrderNotional requiredPositionMargin := math.Abs(adjNotional) * tc.marginFactor - requiredOrderMargin := getLimitOrderNotional(t, tc.orders, priceFactor, positionDecimalPlaces) * tc.marginFactor - expectedCollIncBest = requiredPositionMargin + requiredOrderMargin - tc.marginAccountBalance - tc.orderMarginAccountBalance + requiredBuyOrderMargin, requireSellOrderMargin := getLimitOrderNotionalScaledByMarginFactorAndNetOfPosition(t, tc.openVolume, tc.orders, priceFactor, positionDecimalPlaces, tc.marginFactor) + expectedCollIncBest = requiredPositionMargin + max(requiredBuyOrderMargin, requireSellOrderMargin) - tc.marginAccountBalance - tc.orderMarginAccountBalance expectedCollIncWorst = expectedCollIncBest expectedPosMarginIncrease = max(0, requiredPositionMargin-tc.marginAccountBalance) @@ -804,18 +848,84 @@ func (s *mockStream) Context() context.Context { return context.Backgroun func (s *mockStream) SendMsg(m interface{}) error { return nil } func (s *mockStream) RecvMsg(m interface{}) error { return nil } -func getLimitOrderNotional(t *testing.T, orders []*v2.OrderInfo, priceFactor float64, positionDecimals int) float64 { +func getLimitOrderNotionalScaledByMarginFactorAndNetOfPosition(t *testing.T, positionSize int64, orders []*v2.OrderInfo, priceFactor float64, positionDecimals int, marginFactor float64) (float64, float64) { t.Helper() - notional := 0.0 + buyNotional, sellNotional := 0.0, 0.0 + buyOrders, sellOrders := make([]*v2.OrderInfo, 0), make([]*v2.OrderInfo, 0) for _, o := range orders { - if o.IsMarketOrder { - continue + if o.Side == entities.SideBuy { + if o.IsMarketOrder { + positionSize += int64(o.Remaining) + continue + } + buyOrders = append(buyOrders, o) } - price, err := strconv.ParseFloat(o.Price, 64) + if o.Side == entities.SideSell { + if o.IsMarketOrder { + positionSize -= int64(o.Remaining) + continue + } + sellOrders = append(sellOrders, o) + } + } + + // sort orders from best to worst + sort.Slice(buyOrders, func(i, j int) bool { + price_i, err := strconv.ParseFloat(buyOrders[i].Price, 64) + require.NoError(t, err) + price_j, err := strconv.ParseFloat(buyOrders[j].Price, 64) + require.NoError(t, err) + + return price_i > price_j + }) + sort.Slice(sellOrders, func(i, j int) bool { + price_i, err := strconv.ParseFloat(sellOrders[i].Price, 64) require.NoError(t, err) - notional += price * priceFactor * float64(o.Remaining) / math.Pow10(positionDecimals) + price_j, err := strconv.ParseFloat(sellOrders[j].Price, 64) + require.NoError(t, err) + + return price_i < price_j + }) + + remainingCovered := uint64(math.Abs(float64(positionSize))) + for _, o := range buyOrders { + size := o.Remaining + if remainingCovered != 0 && (positionSize < 0) { + if size >= remainingCovered { // part of the order doesn't require margin + size = size - remainingCovered + remainingCovered = 0 + } else { // the entire order doesn't require margin + remainingCovered -= size + size = 0 + } + } + if size > 0 { + price, err := strconv.ParseFloat(o.Price, 64) + require.NoError(t, err) + buyNotional += price * priceFactor * float64(size) / math.Pow10(positionDecimals) + } } - return notional + + remainingCovered = uint64(math.Abs(float64(positionSize))) + for _, o := range sellOrders { + size := o.Remaining + if remainingCovered != 0 && (positionSize > 0) { + if size >= remainingCovered { // part of the order doesn't require margin + size = size - remainingCovered + remainingCovered = 0 + } else { // the entire order doesn't require margin + remainingCovered -= size + size = 0 + } + } + if size > 0 { + price, err := strconv.ParseFloat(o.Price, 64) + require.NoError(t, err) + sellNotional += price * priceFactor * float64(size) / math.Pow10(positionDecimals) + } + } + + return buyNotional * marginFactor, sellNotional * marginFactor } func getMarketOrderNotional(marketObservable float64, orders []*v2.OrderInfo, priceFactor float64, positionDecimals int) float64 { From 8def52952ff553d59f477a9d46c2bd959785ced6 Mon Sep 17 00:00:00 2001 From: Witold Date: Wed, 20 Mar 2024 15:20:55 +0100 Subject: [PATCH 2/2] chore: update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e46c860f8..c7a5e039fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - [10946](https://github.com/vegaprotocol/vega/issues/10945) - Save dispatch strategy for recurring governance transfer in the database. - [10943](https://github.com/vegaprotocol/vega/issues/10943) - Fix error message format when node vote is sent again. +- [10928](https://github.com/vegaprotocol/vega/issues/10928) - Fix `collateralIncreaseEstimate` for limit orders in isolated margin mode ## 0.75.0