From 890d2eb6038a0f93fc321f68adcbe44ebbeffe8b Mon Sep 17 00:00:00 2001 From: wwestgarth Date: Tue, 27 Aug 2024 15:36:19 +0100 Subject: [PATCH] fix: calculate AMM tradable volume purely in position to avoid precision loss --- CHANGELOG.md | 1 + core/execution/amm/engine_test.go | 8 +- core/execution/amm/pool.go | 57 ++++--- core/execution/amm/pool_test.go | 146 ++++++++++++++++-- .../features/amm/0090-VAMM-006-014.feature | 8 +- .../features/amm/0090-VAMM-020.feature | 52 +++---- .../features/amm/11504-rounding.feature | 10 +- .../amm/reduce-only-single-sided.feature | 26 ++-- 8 files changed, 219 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e67f55473c4..8b4779bb364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ - [11568](https://github.com/vegaprotocol/vega/issues/11568) - order book shape on closing `AMM` no longer panics. - [11540](https://github.com/vegaprotocol/vega/issues/11540) - Fix spam check for spots to use not double count quantum. - [11542](https://github.com/vegaprotocol/vega/issues/11542) - Fix non determinism in lottery ranking. +- [11616](https://github.com/vegaprotocol/vega/issues/11616) - `AMM` tradable volume now calculated purely in positions to prevent loss of precision. - [11544](https://github.com/vegaprotocol/vega/issues/11544) - Fix empty candles stream. - [11579](https://github.com/vegaprotocol/vega/issues/11579) - Spot calculate fee on amend, use order price if no amended price is provided. - [11585](https://github.com/vegaprotocol/vega/issues/11585) - Initialise rebate stats service in API. diff --git a/core/execution/amm/engine_test.go b/core/execution/amm/engine_test.go index 7eb4c617d9d..f387988a8f4 100644 --- a/core/execution/amm/engine_test.go +++ b/core/execution/amm/engine_test.go @@ -462,7 +462,7 @@ func TestBestPricesAndVolumeNearBound(t *testing.T) { expectSubaccountCreation(t, tst, party, subAccount) whenAMMIsSubmitted(t, tst, submit) - tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(5).Return( + tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(3).Return( []events.MarketPosition{&marketPosition{size: 0, averageEntry: num.NewUint(0)}}, ) @@ -473,7 +473,7 @@ func TestBestPricesAndVolumeNearBound(t *testing.T) { assert.Equal(t, 1192, int(avolume)) // lets move its position so that the fair price is within one tick of the AMMs upper boundary - tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(5).Return( + tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(3).Return( []events.MarketPosition{&marketPosition{size: -222000, averageEntry: num.NewUint(0)}}, ) @@ -484,7 +484,7 @@ func TestBestPricesAndVolumeNearBound(t *testing.T) { assert.Equal(t, 103, int(avolume)) // lets move its position so that the fair price is within one tick of the AMMs upper boundary - tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(5).Return( + tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(3).Return( []events.MarketPosition{&marketPosition{size: 270400, averageEntry: num.NewUint(0)}}, ) @@ -492,7 +492,7 @@ func TestBestPricesAndVolumeNearBound(t *testing.T) { assert.Equal(t, "180000", bid.String()) // make sure we are capped to the boundary and not 179904 assert.Equal(t, "180104", ask.String()) assert.Equal(t, 58, int(bvolume)) - assert.Equal(t, 1463, int(avolume)) + assert.Equal(t, 1460, int(avolume)) } func testClosingReduceOnlyPool(t *testing.T) { diff --git a/core/execution/amm/pool.go b/core/execution/amm/pool.go index df456ec17ef..e4c89e673e4 100644 --- a/core/execution/amm/pool.go +++ b/core/execution/amm/pool.go @@ -607,39 +607,52 @@ func (p *Pool) TradableVolumeInRange(side types.Side, price1 *num.Uint, price2 * st, nd = nd, st } - fp := p.fairPrice() + // map the given st/nd prices into positions, then the difference is the volume + asPosition := func(price *num.Uint) int64 { + switch { + case price.GT(p.lower.high): + // in upper curve + if !p.upper.empty { + return p.upper.positionAtPrice(p.sqrt, num.Min(p.upper.high, price)) + } + case price.LT(p.lower.high): + // in lower curve + if !p.lower.empty { + return p.lower.positionAtPrice(p.sqrt, num.Max(p.lower.low, price)) + } + } + return 0 + } + + stP := asPosition(st) + ndP := asPosition(nd) + if side == types.SideSell { - // want all buy volume so everything below fair price - nd = num.Min(fp, nd) + // want all buy volume so everything below fair price, where the AMM is long + ndP = num.MaxV(pos, ndP) } if side == types.SideBuy { - // want all sell volume so everything above fair price - st = num.Max(fp, st) + // want all sell volume so everything above fair price, where the AMM is short + stP = num.MinV(pos, stP) } - var other *curve - var volume uint64 - // get the curve based on the pool's current position, if the position is zero we take the curve the trade will put us in - // e.g trading with an incoming buy order will make the pool short, so we take the upper curve. - if pos < 0 || (pos == 0 && side == types.SideBuy) { - volume = p.upper.volumeBetweenPrices(p.sqrt, st, nd) - other = p.lower - } else { - volume = p.lower.volumeBetweenPrices(p.sqrt, st, nd) - other = p.upper + if !p.closing() { + return uint64(stP - ndP) } - if p.closing() { - return num.MinV(volume, uint64(num.AbsV(pos))) + if pos > 0 { + // if closing and long, we have no volume at short prices, so cap range to > 0 + stP = num.MaxV(0, stP) + ndP = num.MaxV(0, ndP) } - // if the position is non-zero, the incoming order could push us across to the other curve - // so we need to check for volume there too - if pos != 0 { - volume += other.volumeBetweenPrices(p.sqrt, st, nd) + if pos < 0 { + // if closing and short, we have no volume at long prices, so cap range to < 0 + stP = num.MinV(0, stP) + ndP = num.MinV(0, ndP) } - return volume + return num.MinV(uint64(stP-ndP), uint64(num.AbsV(pos))) } // getBalance returns the total balance of the pool i.e it's general account + it's margin account. diff --git a/core/execution/amm/pool_test.go b/core/execution/amm/pool_test.go index 9dcc80a62cd..0444f469e83 100644 --- a/core/execution/amm/pool_test.go +++ b/core/execution/amm/pool_test.go @@ -38,6 +38,7 @@ func TestAMMPool(t *testing.T) { t.Run("test pool logic with position factor", testPoolPositionFactor) t.Run("test one sided pool", testOneSidedPool) t.Run("test near zero volume curve triggers and error", testNearZeroCurveErrors) + t.Run("test volume between prices when closing", testTradeableVolumeInRangeClosing) } func testTradeableVolumeInRange(t *testing.T) { @@ -85,7 +86,7 @@ func testTradeableVolumeInRange(t *testing.T) { price1: num.NewUint(500), price2: num.NewUint(3500), side: types.SideBuy, - expectedVolume: 1337, + expectedVolume: 1335, position: 700, // position at full lower boundary, incoming is by so whole volume of both curves is available }, { @@ -101,7 +102,7 @@ func testTradeableVolumeInRange(t *testing.T) { price1: num.NewUint(500), price2: num.NewUint(3500), side: types.SideBuy, - expectedVolume: 986, + expectedVolume: 985, position: 350, }, { @@ -109,14 +110,129 @@ func testTradeableVolumeInRange(t *testing.T) { price1: num.NewUint(500), price2: num.NewUint(3500), side: types.SideSell, - expectedVolume: 1053, + expectedVolume: 1052, position: -350, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ensurePositionN(t, p.pos, tt.position, num.UintZero(), 2) + ensurePositionN(t, p.pos, tt.position, num.UintZero(), 1) + volume := p.pool.TradableVolumeInRange(tt.side, tt.price1, tt.price2) + assert.Equal(t, int(tt.expectedVolume), int(volume)) + }) + } +} + +func testTradeableVolumeInRangeClosing(t *testing.T) { + p := newTestPool(t) + defer p.ctrl.Finish() + + // pool is reducing its + p.pool.status = types.AMMPoolStatusReduceOnly + + tests := []struct { + name string + price1 *num.Uint + price2 *num.Uint + position int64 + side types.Side + expectedVolume uint64 + nposcalls int + }{ + { + name: "0 position, 0 buy volume", + price1: num.NewUint(1800), + price2: num.NewUint(2200), + side: types.SideBuy, + expectedVolume: 0, + nposcalls: 1, + }, + { + name: "0 position, 0 sell volume", + price1: num.NewUint(1800), + price2: num.NewUint(2200), + side: types.SideSell, + expectedVolume: 0, + nposcalls: 1, + }, + { + name: "long position, 0 volume for incoming SELL", + price1: num.NewUint(1800), + price2: num.NewUint(2200), + side: types.SideSell, + position: 10, + expectedVolume: 0, + nposcalls: 1, + }, + { + name: "long position, 10 volume for incoming BUY", + price1: num.NewUint(1800), + price2: num.NewUint(2200), + side: types.SideBuy, + position: 10, + expectedVolume: 10, + nposcalls: 2, + }, + { + name: "short position, 0 volume for incoming BUY", + price1: num.NewUint(1800), + price2: num.NewUint(2200), + side: types.SideBuy, + position: -10, + expectedVolume: 0, + nposcalls: 1, + }, + { + name: "short position, 10 volume for incoming SELL", + price1: num.NewUint(1800), + price2: num.NewUint(2200), + side: types.SideSell, + position: -10, + expectedVolume: 10, + nposcalls: 2, + }, + { + name: "asking for SELL volume but for prices outside of price ranges", + price1: num.NewUint(2000), + price2: num.NewUint(2200), + side: types.SideBuy, + position: 10, + expectedVolume: 0, + nposcalls: 2, + }, + { + name: "asking for BUY volume but for prices outside of price ranges", + price1: num.NewUint(1800), + price2: num.NewUint(1850), + side: types.SideSell, + position: -10, + expectedVolume: 0, + nposcalls: 2, + }, + { + name: "asking for partial closing volume when long", + price1: num.NewUint(1800), + price2: num.NewUint(1850), + side: types.SideBuy, + position: 702, + expectedVolume: 186, + nposcalls: 2, + }, + { + name: "asking for partial closing volume when short", + price1: num.NewUint(2100), + price2: num.NewUint(2150), + side: types.SideSell, + position: -635, + expectedVolume: 155, + nposcalls: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ensurePositionN(t, p.pos, tt.position, num.UintZero(), tt.nposcalls) volume := p.pool.TradableVolumeInRange(tt.side, tt.price1, tt.price2) assert.Equal(t, int(tt.expectedVolume), int(volume)) }) @@ -149,7 +265,7 @@ func TestTradeableVolumeWhenAtBoundary(t *testing.T) { defer p.ctrl.Finish() // when position is zero fair-price should be the base - ensurePositionN(t, p.pos, 0, num.UintZero(), 3) + ensurePositionN(t, p.pos, 0, num.UintZero(), 2) fp := p.pool.BestPrice(nil) assert.Equal(t, "6765400000000000000000", fp.String()) @@ -160,7 +276,7 @@ func TestTradeableVolumeWhenAtBoundary(t *testing.T) { assert.Equal(t, fullLong, int(volume)) // now lets pretend the AMM has fully traded out in that direction, best price will be near but not quite the lower bound - ensurePositionN(t, p.pos, int64(fullLong), num.UintZero(), 3) + ensurePositionN(t, p.pos, int64(fullLong), num.UintZero(), 2) fp = p.pool.BestPrice(nil) assert.Equal(t, "6712721893865935337785", fp.String()) assert.True(t, fp.GTE(num.MustUintFromString("6712720000000000000000", 10))) @@ -174,12 +290,12 @@ func testPoolPositionFactor(t *testing.T) { p := newTestPoolWithPositionFactor(t, num.DecimalFromInt64(1000)) defer p.ctrl.Finish() - ensurePositionN(t, p.pos, 0, num.UintZero(), 2) + ensurePositionN(t, p.pos, 0, num.UintZero(), 1) volume := p.pool.TradableVolumeInRange(types.SideBuy, num.NewUint(2000), num.NewUint(2200)) // with position factot of 1 the volume is 635 assert.Equal(t, int(635395), int(volume)) - ensurePositionN(t, p.pos, 0, num.UintZero(), 2) + ensurePositionN(t, p.pos, 0, num.UintZero(), 1) volume = p.pool.TradableVolumeInRange(types.SideSell, num.NewUint(1800), num.NewUint(2000)) // with position factot of 1 the volume is 702 assert.Equal(t, int(702411), int(volume)) @@ -256,18 +372,18 @@ func testOneSidedPool(t *testing.T) { defer p.ctrl.Finish() // side with liquidity returns volume - ensurePositionN(t, p.pos, 0, num.UintZero(), 2) + ensurePositionN(t, p.pos, 0, num.UintZero(), 1) volume := p.pool.TradableVolumeInRange(types.SideBuy, num.NewUint(2000), num.NewUint(2200)) assert.Equal(t, int(635), int(volume)) // empty side returns no volume - ensurePositionN(t, p.pos, 0, num.UintZero(), 2) + ensurePositionN(t, p.pos, 0, num.UintZero(), 1) volume = p.pool.TradableVolumeInRange(types.SideSell, num.NewUint(1800), num.NewUint(2000)) assert.Equal(t, int(0), int(volume)) // pool with short position and incoming sell only reports volume up to base // empty side returns no volume - ensurePositionN(t, p.pos, -10, num.UintZero(), 2) + ensurePositionN(t, p.pos, -10, num.UintZero(), 1) volume = p.pool.TradableVolumeInRange(types.SideSell, num.NewUint(1800), num.NewUint(2200)) assert.Equal(t, int(10), int(volume)) @@ -440,22 +556,22 @@ func TestNotebook(t *testing.T) { pos := int64(0) - ensurePositionN(t, p.pos, pos, num.UintZero(), 2) + ensurePositionN(t, p.pos, pos, num.UintZero(), 1) volume := p.pool.TradableVolumeInRange(types.SideSell, base, low) assert.Equal(t, int(702), int(volume)) - ensurePositionN(t, p.pos, pos, num.UintZero(), 2) + ensurePositionN(t, p.pos, pos, num.UintZero(), 1) volume = p.pool.TradableVolumeInRange(types.SideBuy, up, base) assert.Equal(t, int(635), int(volume)) lowmid := num.NewUint(1900) upmid := num.NewUint(2100) - ensurePositionN(t, p.pos, pos, num.UintZero(), 2) + ensurePositionN(t, p.pos, pos, num.UintZero(), 1) volume = p.pool.TradableVolumeInRange(types.SideSell, low, lowmid) assert.Equal(t, int(365), int(volume)) - ensurePositionN(t, p.pos, pos, num.UintZero(), 2) + ensurePositionN(t, p.pos, pos, num.UintZero(), 1) volume = p.pool.TradableVolumeInRange(types.SideBuy, upmid, up) assert.Equal(t, int(306), int(volume)) diff --git a/core/integration/features/amm/0090-VAMM-006-014.feature b/core/integration/features/amm/0090-VAMM-006-014.feature index e7b5b7987f3..c96d7d99ee3 100644 --- a/core/integration/features/amm/0090-VAMM-006-014.feature +++ b/core/integration/features/amm/0090-VAMM-006-014.feature @@ -478,10 +478,10 @@ Feature: Ensure the vAMM positions follow the market correctly | party5 | ETH/MAR22 | buy | 65 | 120 | 1 | TYPE_LIMIT | TIF_GTC | Then the market data for the market "ETH/MAR22" should be: | mark price | trading mode | ref price | mid price | static mid price | best offer price | best bid price | - | 95 | TRADING_MODE_CONTINUOUS | 100 | 120 | 120 | 121 | 120 | + | 95 | TRADING_MODE_CONTINUOUS | 100 | 120 | 120 | 121 | 119 | And the following trades should be executed: | buyer | price | size | seller | is amm | - | party5 | 114 | 64 | vamm1-id | true | + | party5 | 114 | 65 | vamm1-id | true | # Check the resulting position, vAMM further increased their position When the network moves ahead "1" blocks Then the parties should have the following profit and loss: @@ -490,5 +490,5 @@ Feature: Ensure the vAMM positions follow the market correctly | party2 | -1 | -14 | 0 | | | party3 | -350 | -6650 | 0 | | | party4 | 420 | 7980 | 0 | | - | party5 | 64 | 0 | 0 | | - | vamm1-id | -134 | -1330 | 0 | true | + | party5 | 65 | 0 | 0 | | + | vamm1-id | -135 | -1330 | 0 | true | diff --git a/core/integration/features/amm/0090-VAMM-020.feature b/core/integration/features/amm/0090-VAMM-020.feature index 5d27589bde0..78dcf289cfa 100644 --- a/core/integration/features/amm/0090-VAMM-020.feature +++ b/core/integration/features/amm/0090-VAMM-020.feature @@ -151,18 +151,18 @@ Feature: Test vAMM cancellation by reduce-only from long. | buyer | price | size | seller | is amm | | party5 | 89 | 10 | party4 | | | party5 | 90 | 10 | party4 | | - | party5 | 90 | 39 | vamm1-id | true | + | party5 | 90 | 19 | vamm1-id | true | | party5 | 91 | 10 | party4 | | - | party5 | 90 | 39 | vamm1-id | true | - | party5 | 94 | 211 | vamm1-id | true | + | party5 | 90 | 19 | vamm1-id | true | + | party5 | 94 | 231 | vamm1-id | true | # check the state of the market, trigger MTM settlement and check balances before closing out the last 100 for the vAMM When the network moves ahead "1" blocks Then the parties should have the following profit and loss: | party | volume | unrealised pnl | realised pnl | is amm | | party4 | -380 | 230 | 0 | | - | party5 | 280 | 276 | 0 | | - | vamm1-id | 100 | -100 | -406 | true | + | party5 | 280 | 196 | 0 | | + | vamm1-id | 100 | -100 | -326 | true | # vAMM is still quoting bid price, though it is in reduce-only mode, and therefore doesn't place those orders. # The best bid should be 40 here? And the market data for the market "ETH/MAR22" should be: @@ -171,14 +171,14 @@ Feature: Test vAMM cancellation by reduce-only from long. # vAMM receives some fees, but pays MTM loss, excess margin is released And the following transfers should happen: | from | from account | to | to account | market id | amount | asset | is amm | type | - | | ACCOUNT_TYPE_FEES_MAKER | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 80 | USD | true | TRANSFER_TYPE_MAKER_FEE_RECEIVE | - | vamm1-id | ACCOUNT_TYPE_MARGIN | | ACCOUNT_TYPE_SETTLEMENT | ETH/MAR22 | 506 | USD | true | TRANSFER_TYPE_MTM_LOSS | - | vamm1-id | ACCOUNT_TYPE_MARGIN | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 45731 | USD | true | TRANSFER_TYPE_MARGIN_HIGH | + | | ACCOUNT_TYPE_FEES_MAKER | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 87 | USD | true | TRANSFER_TYPE_MAKER_FEE_RECEIVE | + | vamm1-id | ACCOUNT_TYPE_MARGIN | | ACCOUNT_TYPE_SETTLEMENT | ETH/MAR22 | 426 | USD | true | TRANSFER_TYPE_MTM_LOSS | + | vamm1-id | ACCOUNT_TYPE_MARGIN | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 45811 | USD | true | TRANSFER_TYPE_MARGIN_HIGH | # After receiving fees, and excess margin is correctly released, the balances of the vAMM sub-accounts match the position: And the parties should have the following account balances: | party | asset | market id | general | margin | is amm | | vamm1 | USD | | 900000 | | | - | vamm1-id | USD | ETH/MAR22 | 81497 | 18225 | true | + | vamm1-id | USD | ETH/MAR22 | 81576 | 18225 | true | # Now make sure the vAMM, though clearly having sufficient balance to increase its position, still doesn't place any buy orders (reduce only check 2) # Like before, place orders at mid, offer, and bid prices @@ -211,8 +211,8 @@ Feature: Test vAMM cancellation by reduce-only from long. Then the parties should have the following profit and loss: | party | volume | unrealised pnl | realised pnl | is amm | | party4 | -380 | -1290 | 0 | | - | party5 | 380 | 1396 | 0 | | - | vamm1-id | 0 | 0 | -106 | true | + | party5 | 380 | 1316 | 0 | | + | vamm1-id | 0 | 0 | -26 | true | And the AMM pool status should be: | party | market id | amount | status | base | lower bound | upper bound | lower leverage | upper leverage | | vamm1 | ETH/MAR22 | 100000 | STATUS_CANCELLED | 100 | 85 | 150 | 4 | 4 | @@ -224,10 +224,10 @@ Feature: Test vAMM cancellation by reduce-only from long. | | ACCOUNT_TYPE_FEES_MAKER | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 40 | USD | true | TRANSFER_TYPE_MAKER_FEE_RECEIVE | | | ACCOUNT_TYPE_SETTLEMENT | vamm1-id | ACCOUNT_TYPE_MARGIN | ETH/MAR22 | 400 | USD | true | TRANSFER_TYPE_MTM_WIN | | vamm1-id | ACCOUNT_TYPE_MARGIN | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 18625 | USD | true | TRANSFER_TYPE_MARGIN_HIGH | - | vamm1-id | ACCOUNT_TYPE_GENERAL | vamm1 | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 100162 | USD | true | TRANSFER_TYPE_AMM_RELEASE | + | vamm1-id | ACCOUNT_TYPE_GENERAL | vamm1 | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 100241 | USD | true | TRANSFER_TYPE_AMM_RELEASE | And the parties should have the following account balances: | party | asset | market id | general | margin | is amm | - | vamm1 | USD | | 1000162 | | | + | vamm1 | USD | | 1000241 | | | | vamm1-id | USD | ETH/MAR22 | 0 | 0 | true | @VAMM @@ -284,18 +284,18 @@ Feature: Test vAMM cancellation by reduce-only from long. | buyer | price | size | seller | is amm | | party5 | 89 | 10 | party4 | | | party5 | 90 | 10 | party4 | | - | party5 | 90 | 39 | vamm1-id | true | + | party5 | 90 | 19 | vamm1-id | true | | party5 | 91 | 10 | party4 | | - | party5 | 90 | 39 | vamm1-id | true | - | party5 | 94 | 211 | vamm1-id | true | + | party5 | 90 | 19 | vamm1-id | true | + | party5 | 94 | 231 | vamm1-id | true | # check the state of the market, trigger MTM settlement and check balances before closing out the last 100 for the vAMM When the network moves ahead "1" blocks Then the parties should have the following profit and loss: | party | volume | unrealised pnl | realised pnl | is amm | | party4 | -380 | 230 | 0 | | - | party5 | 280 | 276 | 0 | | - | vamm1-id | 100 | -100 | -406 | true | + | party5 | 280 | 196 | 0 | | + | vamm1-id | 100 | -100 | -326 | true | # vAMM is still quoting bid price, though it is in reduce-only mode, and therefore doesn't place those orders. # The best bid should be 40 here? And the market data for the market "ETH/MAR22" should be: @@ -304,14 +304,14 @@ Feature: Test vAMM cancellation by reduce-only from long. # vAMM receives some fees, but pays MTM loss, excess margin is released And the following transfers should happen: | from | from account | to | to account | market id | amount | asset | is amm | type | - | | ACCOUNT_TYPE_FEES_MAKER | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 80 | USD | true | TRANSFER_TYPE_MAKER_FEE_RECEIVE | - | vamm1-id | ACCOUNT_TYPE_MARGIN | | ACCOUNT_TYPE_SETTLEMENT | ETH/MAR22 | 506 | USD | true | TRANSFER_TYPE_MTM_LOSS | - | vamm1-id | ACCOUNT_TYPE_MARGIN | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 45731 | USD | true | TRANSFER_TYPE_MARGIN_HIGH | + | | ACCOUNT_TYPE_FEES_MAKER | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 87 | USD | true | TRANSFER_TYPE_MAKER_FEE_RECEIVE | + | vamm1-id | ACCOUNT_TYPE_MARGIN | | ACCOUNT_TYPE_SETTLEMENT | ETH/MAR22 | 426 | USD | true | TRANSFER_TYPE_MTM_LOSS | + | vamm1-id | ACCOUNT_TYPE_MARGIN | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 45811 | USD | true | TRANSFER_TYPE_MARGIN_HIGH | # After receiving fees, and excess margin is correctly released, the balances of the vAMM sub-accounts match the position: And the parties should have the following account balances: | party | asset | market id | general | margin | is amm | | vamm1 | USD | | 900000 | | | - | vamm1-id | USD | ETH/MAR22 | 81497 | 18225 | true | + | vamm1-id | USD | ETH/MAR22 | 81576 | 18225 | true | # Now make sure the vAMM, though clearly having sufficient balance to increase its position, still doesn't place any buy orders (reduce only check 2) # Like before, place orders at mid, offer, and bid prices @@ -343,8 +343,8 @@ Feature: Test vAMM cancellation by reduce-only from long. Then the parties should have the following profit and loss: | party | volume | unrealised pnl | realised pnl | is amm | | party4 | -380 | -1290 | 0 | | - | party5 | 380 | 1396 | 0 | | - | vamm1-id | 0 | 0 | -106 | true | + | party5 | 380 | 1316 | 0 | | + | vamm1-id | 0 | 0 | -26 | true | And the AMM pool status should be: | party | market id | amount | status | base | lower bound | upper bound | lower leverage | upper leverage | | vamm1 | ETH/MAR22 | 100000 | STATUS_CANCELLED | 100 | 85 | 150 | 4 | 4 | @@ -356,8 +356,8 @@ Feature: Test vAMM cancellation by reduce-only from long. | | ACCOUNT_TYPE_FEES_MAKER | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 40 | USD | true | TRANSFER_TYPE_MAKER_FEE_RECEIVE | | | ACCOUNT_TYPE_SETTLEMENT | vamm1-id | ACCOUNT_TYPE_MARGIN | ETH/MAR22 | 400 | USD | true | TRANSFER_TYPE_MTM_WIN | | vamm1-id | ACCOUNT_TYPE_MARGIN | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 18625 | USD | true | TRANSFER_TYPE_MARGIN_HIGH | - | vamm1-id | ACCOUNT_TYPE_GENERAL | vamm1 | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 100162 | USD | true | TRANSFER_TYPE_AMM_RELEASE | + | vamm1-id | ACCOUNT_TYPE_GENERAL | vamm1 | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 100241 | USD | true | TRANSFER_TYPE_AMM_RELEASE | And the parties should have the following account balances: | party | asset | market id | general | margin | is amm | - | vamm1 | USD | | 1000162 | | | + | vamm1 | USD | | 1000241 | | | | vamm1-id | USD | ETH/MAR22 | 0 | 0 | true | diff --git a/core/integration/features/amm/11504-rounding.feature b/core/integration/features/amm/11504-rounding.feature index f52f06376e9..1237845c54c 100644 --- a/core/integration/features/amm/11504-rounding.feature +++ b/core/integration/features/amm/11504-rounding.feature @@ -134,14 +134,14 @@ Feature: Ensure rounding errors do not cause empty curve panic. | party4 | ETH/MAR22 | buy | 31 | 110 | 2 | TYPE_LIMIT | TIF_GTC | Then the following trades should be executed: | buyer | price | size | seller | is amm | - | party4 | 104 | 15 | vamm1-id | true | - | party4 | 104 | 16 | vamm2-id | true | + | party4 | 105 | 16 | vamm1-id | true | + | party4 | 104 | 15 | vamm2-id | true | When the network moves ahead "1" blocks Then the market data for the market "ETH/MAR22" should be: | mark price | trading mode | mid price | static mid price | | 104 | TRADING_MODE_CONTINUOUS | 108 | 108 | And the parties should have the following profit and loss: | party | volume | unrealised pnl | realised pnl | is amm | - | party4 | 36 | 20 | 0 | | - | vamm1-id | -18 | -12 | 0 | true | - | vamm2-id | -18 | -8 | 0 | true | + | party4 | 36 | 4 | 0 | | + | vamm1-id | -19 | 4 | 0 | true | + | vamm2-id | -17 | -8 | 0 | true | diff --git a/core/integration/features/amm/reduce-only-single-sided.feature b/core/integration/features/amm/reduce-only-single-sided.feature index dd69132e86f..6f7fafcd5cc 100644 --- a/core/integration/features/amm/reduce-only-single-sided.feature +++ b/core/integration/features/amm/reduce-only-single-sided.feature @@ -152,18 +152,18 @@ Feature: Cover more complex scenarios. | buyer | price | size | seller | is amm | | party5 | 89 | 10 | party4 | | | party5 | 90 | 10 | party4 | | - | party5 | 90 | 39 | vamm1-id | true | + | party5 | 90 | 19 | vamm1-id | true | | party5 | 91 | 10 | party4 | | - | party5 | 90 | 39 | vamm1-id | true | - | party5 | 94 | 211 | vamm1-id | true | + | party5 | 90 | 19 | vamm1-id | true | + | party5 | 94 | 231 | vamm1-id | true | # check the state of the market, trigger MTM settlement and check balances before closing out the last 100 for the vAMM When the network moves ahead "2" blocks Then the parties should have the following profit and loss: | party | volume | unrealised pnl | realised pnl | is amm | | party4 | -380 | 230 | 0 | | - | party5 | 280 | 276 | 0 | | - | vamm1-id | 100 | -100 | -406 | true | + | party5 | 280 | 196 | 0 | | + | vamm1-id | 100 | -100 | -326 | true | # vAMM is still quoting bid price, though it is in reduce-only mode, and therefore doesn't place those orders. # The best bid should be 40 here? And the market data for the market "ETH/MAR22" should be: @@ -172,14 +172,14 @@ Feature: Cover more complex scenarios. # vAMM receives some fees, but pays MTM loss, excess margin is released And the following transfers should happen: | from | from account | to | to account | market id | amount | asset | is amm | type | - | | ACCOUNT_TYPE_FEES_MAKER | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 80 | USD | true | TRANSFER_TYPE_MAKER_FEE_RECEIVE | - | vamm1-id | ACCOUNT_TYPE_MARGIN | | ACCOUNT_TYPE_SETTLEMENT | ETH/MAR22 | 506 | USD | true | TRANSFER_TYPE_MTM_LOSS | - | vamm1-id | ACCOUNT_TYPE_MARGIN | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 45731 | USD | true | TRANSFER_TYPE_MARGIN_HIGH | + | | ACCOUNT_TYPE_FEES_MAKER | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 87 | USD | true | TRANSFER_TYPE_MAKER_FEE_RECEIVE | + | vamm1-id | ACCOUNT_TYPE_MARGIN | | ACCOUNT_TYPE_SETTLEMENT | ETH/MAR22 | 426 | USD | true | TRANSFER_TYPE_MTM_LOSS | + | vamm1-id | ACCOUNT_TYPE_MARGIN | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 45811 | USD | true | TRANSFER_TYPE_MARGIN_HIGH | # After receiving fees, and excess margin is correctly released, the balances of the vAMM sub-accounts match the position: And the parties should have the following account balances: | party | asset | market id | general | margin | is amm | | vamm1 | USD | | 900000 | | | - | vamm1-id | USD | ETH/MAR22 | 81497 | 18225 | true | + | vamm1-id | USD | ETH/MAR22 | 81576 | 18225 | true | # Now make sure the vAMM, though clearly having sufficient balance to increase its position, still doesn't place any buy orders (reduce only check 2) # Like before, place orders at mid, offer, and bid prices @@ -236,8 +236,8 @@ Feature: Cover more complex scenarios. Then the parties should have the following profit and loss: | party | volume | unrealised pnl | realised pnl | is amm | | party4 | -380 | -1290 | 0 | | - | party5 | 380 | 1396 | 0 | | - | vamm1-id | 0 | 0 | -106 | true | + | party5 | 380 | 1316 | 0 | | + | vamm1-id | 0 | 0 | -26 | true | And the AMM pool status should be: | party | market id | amount | status | base | lower bound | upper bound | lower leverage | upper leverage | | vamm1 | ETH/MAR22 | 100000 | STATUS_CANCELLED | 100 | 85 | 150 | 4 | 4 | @@ -249,10 +249,10 @@ Feature: Cover more complex scenarios. | | ACCOUNT_TYPE_FEES_MAKER | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 40 | USD | true | TRANSFER_TYPE_MAKER_FEE_RECEIVE | | | ACCOUNT_TYPE_SETTLEMENT | vamm1-id | ACCOUNT_TYPE_MARGIN | ETH/MAR22 | 400 | USD | true | TRANSFER_TYPE_MTM_WIN | | vamm1-id | ACCOUNT_TYPE_MARGIN | vamm1-id | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 18625 | USD | true | TRANSFER_TYPE_MARGIN_HIGH | - | vamm1-id | ACCOUNT_TYPE_GENERAL | vamm1 | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 100162 | USD | true | TRANSFER_TYPE_AMM_RELEASE | + | vamm1-id | ACCOUNT_TYPE_GENERAL | vamm1 | ACCOUNT_TYPE_GENERAL | ETH/MAR22 | 100241 | USD | true | TRANSFER_TYPE_AMM_RELEASE | And the parties should have the following account balances: | party | asset | market id | general | margin | is amm | - | vamm1 | USD | | 1000162 | | | + | vamm1 | USD | | 1000241 | | | | vamm1-id | USD | ETH/MAR22 | 0 | 0 | true |