Skip to content

Commit

Permalink
[Perpetual]: Implement on chain liquidation price (#860)
Browse files Browse the repository at this point in the history
* add liquidation price

* add to query

* liq price

* fix test

* add test
  • Loading branch information
amityadav0 authored Oct 16, 2024
1 parent aac7144 commit 3df75d3
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 122 deletions.
4 changes: 4 additions & 0 deletions proto/elys/perpetual/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ message MtpAndPrice {
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
string liquidation_price = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}

// ParamsRequest is request type for the Query/Params RPC method.
Expand Down
1 change: 1 addition & 0 deletions x/perpetual/client/cli/query_mtp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func networkWithMTPObjects(t *testing.T, n int) (*network.Network, []*types.MtpA
},
TradingAssetPrice: sdk.ZeroDec(),
Pnl: sdk.ZeroDec(),
LiquidationPrice: sdk.ZeroDec(),
}

mtps = append(mtps, &mtp)
Expand Down
62 changes: 62 additions & 0 deletions x/perpetual/keeper/mtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
gomath "math"

"cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
Expand Down Expand Up @@ -161,6 +162,7 @@ func (k Keeper) GetMTPs(ctx sdk.Context, pagination *query.PageRequest) ([]*type
}

pnl := sdk.ZeroDec()
liquidationPrice := sdk.ZeroDec()
if realTime {
mtpHealth, err := k.GetMTPHealth(ctx, mtp, ammPool, baseCurrency)
if err == nil {
Expand All @@ -173,6 +175,7 @@ func (k Keeper) GetMTPs(ctx sdk.Context, pagination *query.PageRequest) ([]*type
if err != nil {
return err
}
liquidationPrice = k.GetLiquidationPrice(ctx, mtp, ammPool, baseCurrency)
}

info, found := k.oracleKeeper.GetAssetInfo(ctx, mtp.TradingAsset)
Expand All @@ -190,6 +193,7 @@ func (k Keeper) GetMTPs(ctx sdk.Context, pagination *query.PageRequest) ([]*type
Mtp: &mtp,
TradingAssetPrice: asset_price,
Pnl: pnl,
LiquidationPrice: liquidationPrice,
})
return nil
})
Expand Down Expand Up @@ -225,6 +229,7 @@ func (k Keeper) GetMTPsForPool(ctx sdk.Context, ammPoolId uint64, pagination *qu
var mtp types.MTP
k.cdc.MustUnmarshal(value, &mtp)
pnl := sdk.ZeroDec()
liquidationPrice := sdk.ZeroDec()
if accumulate && mtp.AmmPoolId == ammPoolId {
if realTime {
// Interest
Expand All @@ -239,6 +244,7 @@ func (k Keeper) GetMTPsForPool(ctx sdk.Context, ammPoolId uint64, pagination *qu
if err != nil {
return false, err
}
liquidationPrice = k.GetLiquidationPrice(ctx, mtp, ammPool, baseCurrency)
}

info, found := k.oracleKeeper.GetAssetInfo(ctx, mtp.TradingAsset)
Expand All @@ -255,6 +261,7 @@ func (k Keeper) GetMTPsForPool(ctx sdk.Context, ammPoolId uint64, pagination *qu
Mtp: &mtp,
TradingAssetPrice: asset_price,
Pnl: pnl,
LiquidationPrice: liquidationPrice,
})
return true, nil
}
Expand Down Expand Up @@ -314,6 +321,7 @@ func (k Keeper) GetMTPsForAddressWithPagination(ctx sdk.Context, mtpAddress sdk.
}

pnl := sdk.ZeroDec()
liquidationPrice := sdk.ZeroDec()
if realTime {
mtpHealth, err := k.GetMTPHealth(ctx, mtp, ammPool, baseCurrency)
if err == nil {
Expand All @@ -326,6 +334,7 @@ func (k Keeper) GetMTPsForAddressWithPagination(ctx sdk.Context, mtpAddress sdk.
if err != nil {
return err
}
liquidationPrice = k.GetLiquidationPrice(ctx, mtp, ammPool, baseCurrency)
}

info, found := k.oracleKeeper.GetAssetInfo(ctx, mtp.TradingAsset)
Expand All @@ -343,6 +352,7 @@ func (k Keeper) GetMTPsForAddressWithPagination(ctx sdk.Context, mtpAddress sdk.
Mtp: &mtp,
TradingAssetPrice: asset_price,
Pnl: pnl,
LiquidationPrice: liquidationPrice,
})
return nil
})
Expand Down Expand Up @@ -491,6 +501,58 @@ func (k Keeper) GetPnL(ctx sdk.Context, mtp types.MTP, ammPool ammtypes.Pool, ba
return estimatedPnL, nil
}

func (k Keeper) GetLiquidationPrice(ctx sdk.Context, mtp types.MTP, ammPool ammtypes.Pool, baseCurrency string) sdk.Dec {
collateralAmountInBaseCurrency := mtp.Collateral
if mtp.CollateralAsset != baseCurrency {
C, err := k.EstimateSwap(ctx, sdk.NewCoin(mtp.CollateralAsset, mtp.Collateral), baseCurrency, ammPool)
if err != nil {
return sdk.ZeroDec()
}
collateralAmountInBaseCurrency = C
}

liabilitiesAmountInBaseCurrency := mtp.Liabilities
if mtp.LiabilitiesAsset != baseCurrency {
L, err := k.EstimateSwap(ctx, sdk.NewCoin(mtp.LiabilitiesAsset, mtp.Liabilities), baseCurrency, ammPool)
if err != nil {
return sdk.ZeroDec()
}
liabilitiesAmountInBaseCurrency = L
}

custodyAmountInTradingAsset := mtp.Custody
if mtp.CustodyAsset != mtp.TradingAsset {
C, err := k.EstimateSwap(ctx, sdk.NewCoin(mtp.CustodyAsset, mtp.Custody), mtp.TradingAsset, ammPool)
if err != nil {
return sdk.ZeroDec()
}
custodyAmountInTradingAsset = C
}

// open price = (collateral + liabilities) / custody
mtp.OpenPrice = math.LegacyNewDecFromBigInt(
collateralAmountInBaseCurrency.Add(liabilitiesAmountInBaseCurrency).BigInt(),
).Quo(
math.LegacyNewDecFromBigInt(custodyAmountInTradingAsset.BigInt()),
)

// calculate liquidation price
// liquidation_price = open_price_value - collateral_amount / custody_amount
liquidationPrice := mtp.OpenPrice.Sub(
sdk.NewDecFromBigInt(collateralAmountInBaseCurrency.BigInt()).Quo(sdk.NewDecFromBigInt(mtp.Custody.BigInt())),
)

// if position is short then liquidation price is open price + collateral amount / (custody amount / open price)
if mtp.Position == types.Position_SHORT {
positionSizeInTradingAsset := sdk.NewDecFromBigInt(mtp.Custody.BigInt()).Quo(mtp.OpenPrice)
liquidationPrice = mtp.OpenPrice.Add(
sdk.NewDecFromBigInt(collateralAmountInBaseCurrency.BigInt()).Quo(positionSizeInTradingAsset),
)
}

return liquidationPrice
}

func (k Keeper) DeleteLegacyMTP(ctx sdk.Context, mtpaddress string, id uint64) error {
store := ctx.KVStore(k.storeKey)
key := types.GetMTPKey(sdk.MustAccAddressFromBech32(mtpaddress), id)
Expand Down
24 changes: 24 additions & 0 deletions x/perpetual/keeper/open_long_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import (
"github.com/stretchr/testify/require"
)

// isCloseTo checks if two sdk.Dec values are within a given tolerance.
func isCloseTo(a, b sdk.Dec, tolerance sdk.Dec) bool {
diff := a.Sub(b).Abs()
return diff.LTE(tolerance)
}

func TestOpenLong_PoolNotFound(t *testing.T) {
// Setup the mock checker
mockChecker := new(mocks.OpenDefineAssetsChecker)
Expand Down Expand Up @@ -635,6 +641,9 @@ func TestOpenLong_ATOM_Collateral(t *testing.T) {
OpenPrice: sdk.MustNewDecFromStr("1.017016260000000000"),
StopLossPrice: sdk.NewDec(100),
}, mtp)

liq := mk.GetLiquidationPrice(ctx, mtp, pool, ptypes.BaseCurrency)
require.Equal(t, liq, sdk.MustNewDecFromStr("0.81810862"))
}

func TestOpenLong_Long10XAtom1000Usdc(t *testing.T) {
Expand Down Expand Up @@ -744,6 +753,17 @@ func TestOpenLong_Long10XAtom1000Usdc(t *testing.T) {
require.Equal(t, balances.AmountOf(ptypes.BaseCurrency), sdk.NewInt(10_000_000_000000))
require.Equal(t, balances.AmountOf(ptypes.ATOM), sdk.NewInt(10_000_000_000000))

res, err := mk.OpenEstimation(ctx, &types.QueryOpenEstimationRequest{
Position: types.Position_LONG,
Leverage: sdk.MustNewDecFromStr("10"),
TradingAsset: ptypes.ATOM,
Collateral: sdk.NewCoin(ptypes.BaseCurrency, sdk.NewInt(1_000_000000)),
Discount: sdk.MustNewDecFromStr("0.0"),
TakeProfitPrice: sdk.MustNewDecFromStr("5.0"),
})
require.NoError(t, err)
estimatedLiqPrice := res.LiquidationPrice

// Create a perpetual position open msg
msg2 := types.NewMsgOpen(
addr[0].String(),
Expand Down Expand Up @@ -802,6 +822,10 @@ func TestOpenLong_Long10XAtom1000Usdc(t *testing.T) {
StopLossPrice: sdk.ZeroDec(),
}, mtp)

liq := mk.GetLiquidationPrice(ctx, mtp, pool, ptypes.BaseCurrency)
tolerance := sdk.MustNewDecFromStr("0.02").Mul(liq) // 2% tolerance
require.True(t, isCloseTo(liq, estimatedLiqPrice, tolerance), "liquidation price is not within 2% tolerance")

oracle.SetPrice(ctx, oracletypes.Price{
Asset: "USDC",
Price: sdk.NewDec(1),
Expand Down
Loading

0 comments on commit 3df75d3

Please sign in to comment.