Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: AMM tradable-volume query should respect its fair-price #11281

Merged
merged 1 commit into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions core/execution/amm/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,10 @@ func testSubmitOrderAcrossAMMBoundary(t *testing.T) {

// second round, 2 orders moving all pool's to the upper boundary of the second shortest
assert.Equal(t, "2124", orders[3].Price.String())
assert.Equal(t, "2125", orders[4].Price.String())
assert.Equal(t, "2124", orders[4].Price.String())

// third round, 1 orders moving the last pool to its boundary
assert.Equal(t, "2175", orders[5].Price.String())
assert.Equal(t, "2174", orders[5].Price.String())
}

func testSubmitOrderAcrossAMMBoundarySell(t *testing.T) {
Expand Down Expand Up @@ -333,7 +333,7 @@ func testBestPricesAndVolume(t *testing.T) {
whenAMMIsSubmitted(t, tst, submit)
}

tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(9).Return(
tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).AnyTimes().Return(
[]events.MarketPosition{&marketPosition{size: 0, averageEntry: num.NewUint(0)}},
)

Expand All @@ -344,9 +344,6 @@ func testBestPricesAndVolume(t *testing.T) {
assert.Equal(t, 35781, int(avolume))

// test GetVolumeAtPrice returns the same volume given best bid/ask
tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(6 * 2).Return(
[]events.MarketPosition{&marketPosition{size: 0, averageEntry: num.NewUint(0)}},
)
bvAt := tst.engine.GetVolumeAtPrice(bid, types.SideSell)
assert.Equal(t, bvolume, bvAt)
avAt := tst.engine.GetVolumeAtPrice(ask, types.SideBuy)
Expand Down
11 changes: 11 additions & 0 deletions core/execution/amm/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,17 @@ func (p *Pool) TradableVolumeInRange(side types.Side, price1 *num.Uint, price2 *
st, nd = nd, st
}

fp := p.fairPrice()
if side == types.SideSell {
// want all buy volume so everything below fair price
nd = num.Min(fp, nd)
}

if side == types.SideBuy {
// want all sell volume so everything above fair price
st = num.Max(fp, st)
}

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
Expand Down
58 changes: 41 additions & 17 deletions core/execution/amm/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,42 @@ func testTradeableVolumeInRange(t *testing.T) {
expectedVolume: 702,
},
{
name: "trade causes sign to flip and volume crosses curves",
name: "buy trade causes sign to flip and full volume crosses curves",
price1: num.NewUint(500),
price2: num.NewUint(3500),
side: types.SideBuy,
expectedVolume: 1337,
position: 700, // position at full lower boundary, incoming is by so whole volume of both curves is available
},
{
name: "sell trade causes sign to flip and full volume crosses curves",
price1: num.NewUint(500),
price2: num.NewUint(3500),
side: types.SideSell,
expectedVolume: 1337,
position: -700, // position at full upper boundary, incoming is by so whole volume of both curves is available
},
{
name: "buy trade causes sign to flip and partial volume across both curves",
price1: num.NewUint(500),
price2: num.NewUint(3500),
side: types.SideBuy,
expectedVolume: 986,
position: 350,
},
{
name: "sell trade causes sign to flip and partial volume across both curves",
price1: num.NewUint(500),
price2: num.NewUint(3500),
side: types.SideSell,
expectedVolume: 1337, // 635 + 702
position: 10,
expectedVolume: 1053,
position: -350,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ensurePosition(t, p.pos, tt.position, num.UintZero())
ensurePositionN(t, p.pos, tt.position, num.UintZero(), 2)
volume := p.pool.TradableVolumeInRange(tt.side, tt.price1, tt.price2)
assert.Equal(t, int(tt.expectedVolume), int(volume))
})
Expand All @@ -100,17 +124,17 @@ func testPoolPositionFactor(t *testing.T) {
p := newTestPoolWithPositionFactor(t, num.DecimalFromInt64(1000))
defer p.ctrl.Finish()

ensurePosition(t, p.pos, 0, num.UintZero())
ensurePositionN(t, p.pos, 0, num.UintZero(), 2)
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))

ensurePosition(t, p.pos, 0, num.UintZero())
ensurePositionN(t, p.pos, 0, num.UintZero(), 2)
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))

ensurePosition(t, p.pos, -1, num.NewUint(2000))
ensurePositionN(t, p.pos, -1, num.NewUint(2000), 1)
// now best price should be the same as if the factor were 1, since its a price and not a volume
fairPrice := p.pool.BestPrice(nil)
assert.Equal(t, "2001", fairPrice.String())
Expand Down Expand Up @@ -182,30 +206,30 @@ func testOneSidedPool(t *testing.T) {
defer p.ctrl.Finish()

// side with liquidity returns volume
ensurePosition(t, p.pos, 0, num.UintZero())
ensurePositionN(t, p.pos, 0, num.UintZero(), 2)
volume := p.pool.TradableVolumeInRange(types.SideBuy, num.NewUint(2000), num.NewUint(2200))
assert.Equal(t, int(635), int(volume))

// empty side returns no volume
ensurePosition(t, p.pos, 0, num.UintZero())
ensurePositionN(t, p.pos, 0, num.UintZero(), 2)
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
ensurePosition(t, p.pos, -10, num.UintZero())
ensurePositionN(t, p.pos, -10, num.UintZero(), 2)
volume = p.pool.TradableVolumeInRange(types.SideSell, num.NewUint(1800), num.NewUint(2200))
assert.Equal(t, int(635), int(volume))
assert.Equal(t, int(10), int(volume))

// fair price at 0 position is still ok
ensurePosition(t, p.pos, 0, num.UintZero())
price := p.pool.BestPrice(nil)
assert.Equal(t, price.String(), "2000")
assert.Equal(t, "2000", price.String())

// fair price at short position is still ok
ensurePosition(t, p.pos, -10, num.UintZero())
price = p.pool.BestPrice(nil)
assert.Equal(t, price.String(), "2003")
assert.Equal(t, "2003", price.String())

// fair price when long should panic since AMM should never be able to get into that state
// fair price at short position is still ok
Expand Down Expand Up @@ -354,22 +378,22 @@ func TestNotebook(t *testing.T) {

pos := int64(0)

ensurePosition(t, p.pos, pos, num.UintZero())
ensurePositionN(t, p.pos, pos, num.UintZero(), 2)
volume := p.pool.TradableVolumeInRange(types.SideSell, base, low)
assert.Equal(t, int(702), int(volume))

ensurePosition(t, p.pos, pos, num.UintZero())
ensurePositionN(t, p.pos, pos, num.UintZero(), 2)
volume = p.pool.TradableVolumeInRange(types.SideBuy, up, base)
assert.Equal(t, int(635), int(volume))

lowmid := num.NewUint(1900)
upmid := num.NewUint(2100)

ensurePosition(t, p.pos, pos, num.UintZero())
ensurePositionN(t, p.pos, pos, num.UintZero(), 2)
volume = p.pool.TradableVolumeInRange(types.SideSell, low, lowmid)
assert.Equal(t, int(365), int(volume))

ensurePosition(t, p.pos, pos, num.UintZero())
ensurePositionN(t, p.pos, pos, num.UintZero(), 2)
volume = p.pool.TradableVolumeInRange(types.SideBuy, upmid, up)
assert.Equal(t, int(306), int(volume))

Expand Down
6 changes: 3 additions & 3 deletions core/integration/features/amm/0090-VAMM-006-014.feature
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ Feature: Ensure the vAMM positions follow the market correctly
| 95 | TRADING_MODE_CONTINUOUS | 100 | 120 | 120 | 121 | 119 |
And the following trades should be executed:
| buyer | price | size | seller | is amm |
| party5 | 114 | 65 | vamm1-id | true |
| party5 | 114 | 64 | 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:
Expand All @@ -488,5 +488,5 @@ Feature: Ensure the vAMM positions follow the market correctly
| party2 | -1 | -14 | 0 | |
| party3 | -350 | -3150 | 0 | |
| party4 | 420 | 7980 | 0 | |
| party5 | 65 | 0 | 0 | |
| vamm1-id | -135 | -1330 | -3500 | true |
| party5 | 64 | 0 | 0 | |
| vamm1-id | -134 | -1330 | -3500 | true |
Loading