Skip to content

Commit

Permalink
fix: AMM tradable-volume query should respect its fair-price
Browse files Browse the repository at this point in the history
  • Loading branch information
wwestgarth committed May 15, 2024
1 parent 1262a7b commit 19bd3a4
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 26 deletions.
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 |

0 comments on commit 19bd3a4

Please sign in to comment.