Skip to content

Commit

Permalink
Merge pull request #11751 from vegaprotocol/11747-amm-spread
Browse files Browse the repository at this point in the history
feat: AMMs can specify a spread around their fair-price
  • Loading branch information
jeremyletang authored Nov 6, 2024
2 parents e2230d9 + 9441602 commit 2ffdb8e
Show file tree
Hide file tree
Showing 48 changed files with 4,725 additions and 4,262 deletions.
12 changes: 10 additions & 2 deletions commands/amend_amm.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,17 @@ func checkAmendAMM(cmd *commandspb.AmendAMM) Errors {

if cmd.MinimumPriceChangeTrigger != nil {
if minPriceChange, err := num.DecimalFromString(*cmd.MinimumPriceChangeTrigger); err != nil {
errs.AddForProperty("submit_amm.mimimum_price_change_trigger", ErrIsNotValid)
errs.AddForProperty("amend_amm.mimimum_price_change_trigger", ErrIsNotValid)
} else if minPriceChange.LessThan(num.DecimalZero()) {
errs.AddForProperty("submit_amm.proposed_fee", ErrMustBePositiveOrZero)
errs.AddForProperty("amend_amm.mimimum_price_change_trigger", ErrMustBePositiveOrZero)
}
}

if cmd.Spread != nil {
if spread, err := num.DecimalFromString(*cmd.Spread); err != nil {
errs.AddForProperty("amend_amm.spread", ErrIsNotValid)
} else if spread.LessThan(num.DecimalZero()) {
errs.AddForProperty("amend_amm.spread", ErrMustBePositiveOrZero)
}
}

Expand Down
8 changes: 8 additions & 0 deletions commands/submit_amm.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ func checkSubmitAMM(cmd *commandspb.SubmitAMM) Errors {
}
}

if cmd.Spread != nil {
if spread, err := num.DecimalFromString(*cmd.Spread); err != nil {
errs.AddForProperty("submit_amm.spread", ErrIsNotValid)
} else if spread.LessThan(num.DecimalZero()) {
errs.AddForProperty("submit_amm.spread", ErrMustBePositiveOrZero)
}
}

if cmd.ConcentratedLiquidityParameters == nil {
errs.FinalAddForProperty("submit_amm.concentrated_liquidity_parameters", ErrIsRequired)
} else {
Expand Down
2 changes: 2 additions & 0 deletions core/events/amm_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func NewAMMPoolEvent(
lowerCurve *AMMCurve,
upperCurve *AMMCurve,
minimumPriceChangeTrigger num.Decimal,
spread num.Decimal,
) *AMMPool {
return &AMMPool{
Base: newBase(ctx, AMMPoolEvent),
Expand All @@ -71,6 +72,7 @@ func NewAMMPoolEvent(
LowerCurve: lowerCurve.ToProtoEvent(),
UpperCurve: upperCurve.ToProtoEvent(),
MinimumPriceChangeTrigger: minimumPriceChangeTrigger.String(),
Spread: spread.String(),
},
}
}
Expand Down
50 changes: 38 additions & 12 deletions core/execution/amm/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ type Engine struct {
minCommitmentQuantum *num.Uint
maxCalculationLevels *num.Uint
allowedEmptyAMMLevels uint64

inAuction bool
}

func New(
Expand Down Expand Up @@ -211,13 +213,16 @@ func NewFromProto(
e.add(p)
}

e.inAuction = state.Auction

return e, nil
}

func (e *Engine) IntoProto() *v1.AmmState {
state := &v1.AmmState{
AmmPartyIds: make([]*v1.StringMapEntry, 0, len(e.ammParties)),
Pools: make([]*v1.PoolMapEntry, 0, len(e.pools)),
Auction: e.inAuction,
}

for k, v := range e.ammParties {
Expand All @@ -237,6 +242,20 @@ func (e *Engine) IntoProto() *v1.AmmState {
return state
}

func (e *Engine) EnterAuction() {
e.inAuction = true
for _, p := range e.poolsCpy {
p.inAuction = true
}
}

func (e *Engine) LeaveAuction() {
e.inAuction = false
for _, p := range e.poolsCpy {
p.inAuction = false
}
}

func (e *Engine) OnMinCommitmentQuantumUpdate(ctx context.Context, c *num.Uint) {
e.minCommitmentQuantum = c.Clone()
}
Expand Down Expand Up @@ -349,7 +368,7 @@ func (e *Engine) GetVolumeAtPrice(price *num.Uint, side types.Side) uint64 {
vol := uint64(0)
for _, pool := range e.poolsCpy {
// get the pool's current price
best, ok := pool.BestPrice(types.OtherSide(side))
best, ok, _ := pool.BestPrice(types.OtherSide(side))
if !ok {
continue
}
Expand Down Expand Up @@ -382,8 +401,8 @@ func (e *Engine) submit(active []*Pool, agg *types.Order, inner, outer *num.Uint
for _, p := range active {
p.setEphemeralPosition()

price, ok := p.BestPrice(types.OtherSide(agg.Side))
if !ok {
price, volume := p.BestPriceAndVolume(types.OtherSide(agg.Side))
if volume == 0 {
continue
}

Expand Down Expand Up @@ -572,14 +591,20 @@ func (e *Engine) partition(agg *types.Order, inner, outer *num.Uint) ([]*Pool, [
// we hit a discontinuity where an AMM's two curves meet if we try to trade over its base-price
// so we partition the inner/outer price range at the base price so that we instead trade across it
// in two steps.
var addBase bool
boundary := p.upper.low
if inner != nil && outer != nil {
if boundary.LT(outer) && boundary.GT(inner) {
bounds[boundary.String()] = boundary.Clone()
}
} else if outer == nil && boundary.GT(inner) {
bounds[boundary.String()] = boundary.Clone()
} else if inner == nil && boundary.LT(outer) {
switch {
case inner != nil && outer != nil:
addBase = boundary.LT(outer) && boundary.GT(inner)
case inner == nil && outer == nil:
addBase = true
case outer == nil && boundary.GT(inner):
addBase = true
case inner == nil && boundary.LT(outer):
addBase = true
}

if addBase {
bounds[boundary.String()] = boundary.Clone()
}

Expand Down Expand Up @@ -696,13 +721,13 @@ func (e *Engine) Create(
e.positionFactor,
e.maxCalculationLevels,
e.allowedEmptyAMMLevels,
submit.SlippageTolerance,
submit.MinimumPriceChangeTrigger,
)
if err != nil {
return nil, err
}

pool.inAuction = e.inAuction

// sanity check, a *new* AMM should not already have a position. If it does it means that the party
// previously had an AMM but it was stopped/cancelled while still holding a position which should not happen.
// It should have either handed its position over to the liquidation engine, or be in reduce-only mode
Expand Down Expand Up @@ -874,6 +899,7 @@ func (e *Engine) sendUpdate(ctx context.Context, pool *Pool) {
TheoreticalPosition: pool.upper.pv,
},
pool.MinimumPriceChangeTrigger,
pool.Spread,
),
)
}
Expand Down
24 changes: 12 additions & 12 deletions core/execution/amm/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func testSubmitOrderAtBestPrice(t *testing.T) {
ensurePosition(t, tst.pos, 0, num.NewUint(0))
orders := tst.engine.SubmitOrder(agg, num.NewUint(2000), num.NewUint(2001))
require.Len(t, orders, 1)
assert.Equal(t, "2000", orders[0].Price.String())
assert.Equal(t, "2001", orders[0].Price.String())
assert.Equal(t, 11927, int(orders[0].Size))

bb, _, ba, _ := tst.engine.BestPricesAndVolumes()
Expand Down Expand Up @@ -360,7 +360,7 @@ func testSubmitOrderProRata(t *testing.T) {
orders := tst.engine.SubmitOrder(agg, num.NewUint(2010), num.NewUint(2020))
require.Len(t, orders, 3)
for _, o := range orders {
assert.Equal(t, "2000", o.Price.String())
assert.Equal(t, "2001", o.Price.String())
assert.Equal(t, uint64(222), o.Size)
}
}
Expand Down Expand Up @@ -402,9 +402,9 @@ func testSubmitOrderAcrossAMMBoundary(t *testing.T) {
require.Len(t, orders, 6)

// first round, three orders moving all pool's to the upper boundary of the shortest
assert.Equal(t, "2049", orders[0].Price.String())
assert.Equal(t, "2049", orders[1].Price.String())
assert.Equal(t, "2049", orders[2].Price.String())
assert.Equal(t, "2048", orders[0].Price.String())
assert.Equal(t, "2048", orders[1].Price.String())
assert.Equal(t, "2048", orders[2].Price.String())

// second round, 2 orders moving all pool's to the upper boundary of the second shortest
assert.Equal(t, "2124", orders[3].Price.String())
Expand Down Expand Up @@ -452,9 +452,9 @@ func testSubmitOrderAcrossAMMBoundarySell(t *testing.T) {
require.Len(t, orders, 6)

// first round, three orders moving all pool's to the upper boundary of the shortest
assert.Equal(t, "1949", orders[0].Price.String())
assert.Equal(t, "1949", orders[1].Price.String())
assert.Equal(t, "1949", orders[2].Price.String())
assert.Equal(t, "1948", orders[0].Price.String())
assert.Equal(t, "1948", orders[1].Price.String())
assert.Equal(t, "1948", orders[2].Price.String())

// second round, 2 orders moving all pool's to the upper boundary of the second shortest
assert.Equal(t, "1874", orders[3].Price.String())
Expand Down Expand Up @@ -503,7 +503,7 @@ func TestBestPricesAndVolumeNearBound(t *testing.T) {
expectSubaccountCreation(t, tst, party, subAccount)
whenAMMIsSubmitted(t, tst, submit)

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

Expand All @@ -514,7 +514,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(10).Return(
tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(8).Return(
[]events.MarketPosition{&marketPosition{size: -222000, averageEntry: num.NewUint(0)}},
)

Expand All @@ -525,7 +525,7 @@ func TestBestPricesAndVolumeNearBound(t *testing.T) {
assert.Equal(t, 104, 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(10).Return(
tst.pos.EXPECT().GetPositionsByParty(gomock.Any()).Times(8).Return(
[]events.MarketPosition{&marketPosition{size: 270400, averageEntry: num.NewUint(0)}},
)

Expand Down Expand Up @@ -746,7 +746,7 @@ func testAMMSnapshot(t *testing.T) {
orders := tst.engine.SubmitOrder(agg, num.NewUint(2010), num.NewUint(2020))
require.Len(t, orders, 3)
for _, o := range orders {
assert.Equal(t, "2000", o.Price.String())
assert.Equal(t, "2001", o.Price.String())
assert.Equal(t, uint64(222), o.Size)
}

Expand Down
Loading

0 comments on commit 2ffdb8e

Please sign in to comment.