Skip to content

Commit

Permalink
Merge pull request #11373 from vegaprotocol/feat-11028
Browse files Browse the repository at this point in the history
Feat 11028
  • Loading branch information
EVODelavega committed Jun 17, 2024
2 parents a00a388 + eff3eb2 commit e328f7a
Show file tree
Hide file tree
Showing 14 changed files with 1,774 additions and 390 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- [11266](https://github.com/vegaprotocol/vega/issues/11266) - Include derived parties rewards API
- [11357](https://github.com/vegaprotocol/vega/issues/11357) - Support historical game scores
- [11023](https://github.com/vegaprotocol/vega/issues/11023) - Add proposed fees to `vAMM` data.
- [11028](https://github.com/vegaprotocol/vega/issues/11028) - Add API to estimate order book depth based on `vAMM`.

### 🐛 Fixes

Expand Down
4 changes: 4 additions & 0 deletions core/execution/amm/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ type Sqrter struct {
cache map[string]num.Decimal
}

func NewSqrter() *Sqrter {
return &Sqrter{cache: map[string]num.Decimal{}}
}

// sqrt calculates the square root of the uint and caches it.
func (s *Sqrter) sqrt(u *num.Uint) num.Decimal {
if r, ok := s.cache[u.String()]; ok {
Expand Down
129 changes: 129 additions & 0 deletions core/execution/amm/estimator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (C) 2023 Gobalsky Labs Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package amm

import (
"code.vegaprotocol.io/vega/libs/num"
)

type EstimatedBounds struct {
PositionSizeAtUpper num.Decimal
PositionSizeAtLower num.Decimal
LossOnCommitmentAtUpper num.Decimal
LossOnCommitmentAtLower num.Decimal
LiquidationPriceAtUpper num.Decimal
LiquidationPriceAtLower num.Decimal
}

func EstimateBounds(
sqrter *Sqrter,
lowerPrice, basePrice, upperPrice *num.Uint,
leverageLower, leverageUpper num.Decimal,
balance *num.Uint,
linearSlippageFactor, initialMargin,
riskFactorShort, riskFactorLong num.Decimal,
) EstimatedBounds {
// test liquidity unit
unitLower := LiquidityUnit(sqrter, basePrice, lowerPrice)
unitUpper := LiquidityUnit(sqrter, upperPrice, basePrice)

// test average entry price
avgEntryLower := AverageEntryPrice(sqrter, unitLower, basePrice)
avgEntryUpper := AverageEntryPrice(sqrter, unitUpper, upperPrice)

// test risk factor
riskFactorLower := RiskFactor(leverageLower, riskFactorLong, linearSlippageFactor, initialMargin)
riskFactorUpper := RiskFactor(leverageUpper, riskFactorShort, linearSlippageFactor, initialMargin)

lowerPriceD := lowerPrice.ToDecimal()
upperPriceD := upperPrice.ToDecimal()
balanceD := balance.ToDecimal()

// test position at bounds
boundPosLower := PositionAtLowerBound(riskFactorLower, balanceD, lowerPriceD, avgEntryLower)
boundPosUpper := PositionAtUpperBound(riskFactorUpper, balanceD, upperPriceD, avgEntryUpper)

// test loss on commitment
lossLower := LossOnCommitment(avgEntryLower, lowerPriceD, boundPosLower)
lossUpper := LossOnCommitment(avgEntryUpper, upperPriceD, boundPosUpper)

// test liquidation price
liquidationPriceAtLower := LiquidationPrice(balanceD, lossLower, boundPosLower, lowerPriceD, linearSlippageFactor, riskFactorLong)
liquidationPriceAtUpper := LiquidationPrice(balanceD, lossUpper, boundPosUpper, upperPriceD, linearSlippageFactor, riskFactorShort)

return EstimatedBounds{
PositionSizeAtUpper: boundPosUpper,
PositionSizeAtLower: boundPosLower,
LossOnCommitmentAtUpper: lossUpper,
LossOnCommitmentAtLower: lossLower,
LiquidationPriceAtUpper: liquidationPriceAtUpper,
LiquidationPriceAtLower: liquidationPriceAtLower,
}
}

// Lu = (sqrt(pu) * sqrt(pl)) / (sqrt(pu) - sqrt(pl)).
func LiquidityUnit(sqrter *Sqrter, pu, pl *num.Uint) num.Decimal {
sqrtPu := sqrter.sqrt(pu)
sqrtPl := sqrter.sqrt(pl)

return sqrtPu.Mul(sqrtPl).Div(sqrtPu.Sub(sqrtPl))
}

// Rf = min(Lb, 1 / (Fs + Fl) * Fi).
func RiskFactor(lb, fs, fl, fi num.Decimal) num.Decimal {
b := num.DecimalOne().Div(fs.Add(fl).Mul(fi))
return num.MinD(lb, b)
}

// Pa = Lu * sqrt(pu) * (1 - (Lu / (Lu + sqrt(pu)))).
func AverageEntryPrice(sqrter *Sqrter, lu num.Decimal, pu *num.Uint) num.Decimal {
sqrtPu := sqrter.sqrt(pu)
// (1 - Lu / (Lu + sqrt(pu)))
oneSubLuDivLuWithUpSquared := num.DecimalOne().Sub(lu.Div(lu.Add(sqrtPu)))
return lu.Mul(sqrtPu).Mul(oneSubLuDivLuWithUpSquared)
}

// Pvl = rf * b / (pl * (1 - rf) + rf * pa).
func PositionAtLowerBound(rf, b, pl, pa num.Decimal) num.Decimal {
oneSubRf := num.DecimalOne().Sub(rf)
rfMulPa := rf.Mul(pa)

return rf.Mul(b).Div(
pl.Mul(oneSubRf).Add(rfMulPa),
)
}

// Pvl = -rf * b / (pl * (1 + rf) - rf * pa).
func PositionAtUpperBound(rf, b, pl, pa num.Decimal) num.Decimal {
onePlusRf := num.DecimalOne().Add(rf)
rfMulPa := rf.Mul(pa)

return rf.Neg().Mul(b).Div(
pl.Mul(onePlusRf).Sub(rfMulPa),
)
}

// lc = |pa - pb * pB|.
func LossOnCommitment(pa, pb, pB num.Decimal) num.Decimal {
return pa.Sub(pb).Mul(pB).Abs()
}

// Pliq = (b - lc - Pb * pb) / (|Pb| * (fl + mr) - Pb).
func LiquidationPrice(b, lc, pB, pb, fl, mr num.Decimal) num.Decimal {
return b.Sub(lc).Sub(pB.Mul(pb)).Div(
pB.Abs().Mul(fl.Add(mr)).Sub(pB),
)
}
167 changes: 167 additions & 0 deletions core/execution/amm/estimator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright (C) 2023 Gobalsky Labs Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package amm

import (
"testing"

"code.vegaprotocol.io/vega/libs/num"

"github.com/stretchr/testify/assert"
)

func TestEstimateSeparateFunctions(t *testing.T) {
balance := num.NewUint(100)
lowerPrice := num.NewUint(900)
basePrice := num.NewUint(1000)
upperPrice := num.NewUint(1300)
leverageAtUpper := num.NewDecimalFromFloat(1.00)
leverageAtLower := num.NewDecimalFromFloat(5.00)
sqrter := NewSqrter()

shortRiskFactor := num.NewDecimalFromFloat(0.01)
longRiskFactor := num.NewDecimalFromFloat(0.01)
linearSlippage := num.NewDecimalFromFloat(0.05)
initialMargin := num.DecimalOne()

// test liquidity unit
unitLower := LiquidityUnit(sqrter, basePrice, lowerPrice)
unitUpper := LiquidityUnit(sqrter, upperPrice, basePrice)

assert.Equal(t, num.DecimalFromFloat(584.6049894).String(), unitLower.Round(7).String())
assert.Equal(t, num.DecimalFromFloat(257.2170745).String(), unitUpper.Round(7).String())

// test average entry price
avgEntryLower := AverageEntryPrice(sqrter, unitLower, basePrice)
avgEntryUpper := AverageEntryPrice(sqrter, unitUpper, upperPrice)
assert.Equal(t, num.DecimalFromFloat(948.683).String(), avgEntryLower.Round(3).String())
assert.Equal(t, num.DecimalFromFloat(1140.175).String(), avgEntryUpper.Round(3).String())

// test risk factor
riskFactorLower := RiskFactor(leverageAtLower, longRiskFactor, linearSlippage, initialMargin)
riskFactorUpper := RiskFactor(leverageAtUpper, shortRiskFactor, linearSlippage, initialMargin)
assert.Equal(t, leverageAtLower.String(), riskFactorLower.String())
assert.Equal(t, leverageAtUpper.String(), riskFactorUpper.String())

lowerPriceD := lowerPrice.ToDecimal()
upperPriceD := upperPrice.ToDecimal()

// test position at bounds
lowerBoundPos := PositionAtLowerBound(riskFactorLower, balance.ToDecimal(), lowerPriceD, avgEntryLower)
upperBoundPos := PositionAtUpperBound(riskFactorUpper, balance.ToDecimal(), upperPriceD, avgEntryUpper)
assert.Equal(t, num.DecimalFromFloat(0.437).String(), lowerBoundPos.Round(3).String())
assert.Equal(t, num.DecimalFromFloat(-0.069).String(), upperBoundPos.Round(3).String())

// test loss on commitment
lossAtLower := LossOnCommitment(avgEntryLower, lowerPriceD, lowerBoundPos)
lossAtUpper := LossOnCommitment(avgEntryUpper, upperPriceD, upperBoundPos)
assert.Equal(t, num.DecimalFromFloat(21.28852368).String(), lossAtLower.Round(8).String())
assert.Equal(t, num.DecimalFromFloat(10.94820416).String(), lossAtUpper.Round(8).String())

linearSlippageFactor := num.DecimalZero()

// test liquidation price
liquidationPriceAtLower := LiquidationPrice(balance.ToDecimal(), lossAtLower, lowerBoundPos, lowerPriceD, linearSlippageFactor, longRiskFactor)
liquidationPriceAtUpper := LiquidationPrice(balance.ToDecimal(), lossAtUpper, upperBoundPos, upperPriceD, linearSlippageFactor, shortRiskFactor)
assert.Equal(t, num.DecimalFromFloat(727.2727273).String(), liquidationPriceAtLower.Round(7).String())
assert.Equal(t, num.DecimalFromFloat(2574.257426).String(), liquidationPriceAtUpper.Round(6).String())
}

func TestEstimate(t *testing.T) {
initialMargin := num.DecimalFromFloat(1)
riskFactorShort := num.DecimalFromFloat(0.01)
riskFactorLong := num.DecimalFromFloat(0.01)
linearSlippageFactor := num.DecimalFromFloat(0)
sqrter := NewSqrter()

t.Run("test 0014-NP-VAMM-001", func(t *testing.T) {
lowerPrice := num.NewUint(900)
basePrice := num.NewUint(1000)
upperPrice := num.NewUint(1100)
leverageUpper := num.DecimalFromFloat(2.00)
leverageLower := num.DecimalFromFloat(2.00)
balance := num.NewUint(100)

expectedMetrics := EstimatedBounds{
PositionSizeAtUpper: num.DecimalFromFloat(-0.166),
PositionSizeAtLower: num.DecimalFromFloat(0.201),
LossOnCommitmentAtUpper: num.DecimalFromFloat(8.515),
LossOnCommitmentAtLower: num.DecimalFromFloat(9.762),
LiquidationPriceAtUpper: num.DecimalFromFloat(1633.663),
LiquidationPriceAtLower: num.DecimalFromFloat(454.545),
}

metrics := EstimateBounds(
sqrter,
lowerPrice,
basePrice,
upperPrice,
leverageLower,
leverageUpper,
balance,
linearSlippageFactor,
initialMargin,
riskFactorShort,
riskFactorLong,
)

assert.Equal(t, expectedMetrics.PositionSizeAtUpper.String(), metrics.PositionSizeAtUpper.Round(3).String())
assert.Equal(t, expectedMetrics.PositionSizeAtLower.String(), metrics.PositionSizeAtLower.Round(3).String())
assert.Equal(t, expectedMetrics.LossOnCommitmentAtUpper.String(), metrics.LossOnCommitmentAtUpper.Round(3).String())
assert.Equal(t, expectedMetrics.LossOnCommitmentAtLower.String(), metrics.LossOnCommitmentAtLower.Round(3).String())
assert.Equal(t, expectedMetrics.LiquidationPriceAtUpper.String(), metrics.LiquidationPriceAtUpper.Round(3).String())
assert.Equal(t, expectedMetrics.LiquidationPriceAtLower.String(), metrics.LiquidationPriceAtLower.Round(3).String())
})

t.Run("test 0014-NP-VAMM-004", func(t *testing.T) {
lowerPrice := num.NewUint(900)
basePrice := num.NewUint(1000)
upperPrice := num.NewUint(1300)
leverageUpper := num.DecimalFromFloat(1)
leverageLower := num.DecimalFromFloat(5)
balance := num.NewUint(100)

expectedMetrics := EstimatedBounds{
PositionSizeAtUpper: num.DecimalFromFloat(-0.069),
PositionSizeAtLower: num.DecimalFromFloat(0.437),
LossOnCommitmentAtUpper: num.DecimalFromFloat(10.948),
LossOnCommitmentAtLower: num.DecimalFromFloat(21.289),
LiquidationPriceAtUpper: num.DecimalFromFloat(2574.257),
LiquidationPriceAtLower: num.DecimalFromFloat(727.273),
}

metrics := EstimateBounds(
sqrter,
lowerPrice,
basePrice,
upperPrice,
leverageLower,
leverageUpper,
balance,
linearSlippageFactor,
initialMargin,
riskFactorShort,
riskFactorLong,
)

assert.Equal(t, expectedMetrics.PositionSizeAtUpper.String(), metrics.PositionSizeAtUpper.Round(3).String())
assert.Equal(t, expectedMetrics.PositionSizeAtLower.String(), metrics.PositionSizeAtLower.Round(3).String())
assert.Equal(t, expectedMetrics.LossOnCommitmentAtUpper.String(), metrics.LossOnCommitmentAtUpper.Round(3).String())
assert.Equal(t, expectedMetrics.LossOnCommitmentAtLower.String(), metrics.LossOnCommitmentAtLower.Round(3).String())
assert.Equal(t, expectedMetrics.LiquidationPriceAtUpper.String(), metrics.LiquidationPriceAtUpper.Round(3).String())
assert.Equal(t, expectedMetrics.LiquidationPriceAtLower.String(), metrics.LiquidationPriceAtLower.Round(3).String())
})
}
9 changes: 9 additions & 0 deletions datanode/api/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,15 @@ var (
ErrDateRangeValidationFailed = newInvalidArgumentError("invalid date range")

ErrListAMMPools = errors.New("failed to list AMM pools")

// Amm bounds estimates.
ErrInvalidBasePrice = newInvalidArgumentError("invalid base price")
ErrInvalidUpperPrice = newInvalidArgumentError("invalid upper price")
ErrInvalidLowerPrice = newInvalidArgumentError("invalid lower price")
ErrInvalidLeverageAtLowerPrice = newInvalidArgumentError("invalid leverage at lower price")
ErrInvalidLeverageAtUpperPrice = newInvalidArgumentError("invalid leverage at upper price")
ErrInvalidCommitmentAmount = newInvalidArgumentError("invalid commitment amount")
ErrEstimateAMMBounds = errors.New("failed to estimate AMM bounds")
)

// errorMap contains a mapping between errors and Vega numeric error codes.
Expand Down
Loading

0 comments on commit e328f7a

Please sign in to comment.