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

feat(perpetual): close/open estimation queries return both custody and position size, later in terms of trading asset #861

Merged
merged 5 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
46 changes: 24 additions & 22 deletions proto/elys/perpetual/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -214,22 +214,23 @@ message QueryOpenEstimationResponse {
string trading_asset = 3;
cosmos.base.v1beta1.Coin collateral = 4 [(gogoproto.nullable) = false] ;
string interest_amount = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin position_size = 6 [(gogoproto.nullable) = false] ;
string swap_fee = 7 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string discount = 8 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string open_price = 9 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string take_profit_price = 10 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string liquidation_price = 11 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string estimated_pnl = 12 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
string estimated_pnl_denom = 13;
cosmos.base.v1beta1.Coin available_liquidity = 14 [(gogoproto.nullable) = false] ;
string slippage = 15 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string weight_balance_ratio = 16 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string borrow_interest_rate = 17 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string funding_rate = 18 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string price_impact = 19 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin borrow_fee = 20 [(gogoproto.nullable) = false ] ;
cosmos.base.v1beta1.Coin funding_fee = 21 [(gogoproto.nullable) = false ] ;
cosmos.base.v1beta1.Coin custody = 6 [(gogoproto.nullable) = false] ;
cosmos.base.v1beta1.Coin position_size = 7 [(gogoproto.nullable) = false] ;
string swap_fee = 8 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string discount = 9 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string open_price = 10 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string take_profit_price = 11 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string liquidation_price = 12 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string estimated_pnl = 13 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
string estimated_pnl_denom = 14;
cosmos.base.v1beta1.Coin available_liquidity = 15 [(gogoproto.nullable) = false] ;
string slippage = 16 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string weight_balance_ratio = 17 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string borrow_interest_rate = 18 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string funding_rate = 19 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string price_impact = 20 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin borrow_fee = 21 [(gogoproto.nullable) = false ] ;
cosmos.base.v1beta1.Coin funding_fee = 22 [(gogoproto.nullable) = false ] ;
}

message QueryGetAllToPayRequest {}
Expand Down Expand Up @@ -271,10 +272,11 @@ message QueryCloseEstimationRequest {

message QueryCloseEstimationResponse {
Position position = 1;
cosmos.base.v1beta1.Coin position_size = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin liabilities = 3 [(gogoproto.nullable) = false];
string price_impact = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string swap_fee = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin return_amount = 6 [(gogoproto.nullable) = false];
string liquidation_price = 7 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin custody = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin position_size = 3 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin liabilities = 4 [(gogoproto.nullable) = false];
string price_impact = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string swap_fee = 6 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin return_amount = 7 [(gogoproto.nullable) = false];
string liquidation_price = 8 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}
8 changes: 7 additions & 1 deletion x/perpetual/keeper/query_close_estimation.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,15 @@ func (k Keeper) HandleCloseEstimation(ctx sdk.Context, req *types.QueryCloseEsti
sdk.NewDecFromBigInt(collateralAmountInBaseCurrency.BigInt()).Quo(sdk.NewDecFromBigInt(mtp.Custody.BigInt())),
)

positionSizeInTradingAsset := mtp.Custody
if mtp.Position == types.Position_SHORT {
positionSizeInTradingAsset = mtp.Liabilities
}

return &types.QueryCloseEstimationResponse{
Position: mtp.Position,
PositionSize: sdk.NewCoin(mtp.CustodyAsset, mtp.Custody),
PositionSize: sdk.NewCoin(mtp.TradingAsset, positionSizeInTradingAsset),
Custody: sdk.NewCoin(mtp.CustodyAsset, mtp.Custody),
Liabilities: sdk.NewCoin(mtp.LiabilitiesAsset, mtp.Liabilities),
// TODO: price impact calculation
PriceImpact: sdk.ZeroDec(),
Expand Down
86 changes: 76 additions & 10 deletions x/perpetual/keeper/query_close_estimation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func TestCloseEstimation_MTPNotFound(t *testing.T) {
mockChecker.AssertExpectations(t)
}

func TestCloseEstimation_ExistingMTP(t *testing.T) {
func TestCloseEstimation_ExistingLongMTP(t *testing.T) {
// Setup the perpetual keeper
k, ctx, assetProfileKeeper := keepertest.PerpetualKeeper(t)

Expand All @@ -105,35 +105,34 @@ func TestCloseEstimation_ExistingMTP(t *testing.T) {
}
mtp = types.MTP{
AmmPoolId: 2,
CollateralAsset: ptypes.BaseCurrency,
CollateralAsset: ptypes.ATOM,
Collateral: sdk.NewInt(100),
CustodyAsset: "uatom",
CustodyAsset: ptypes.ATOM,
Custody: sdk.NewInt(50),
LiabilitiesAsset: ptypes.BaseCurrency,
Liabilities: sdk.NewInt(400),
TradingAsset: "uatom",
TradingAsset: ptypes.ATOM,
Position: types.Position_LONG,
BorrowInterestUnpaidCollateral: sdk.NewInt(10),
OpenPrice: sdk.MustNewDecFromStr("1.5"),
}
pool = types.Pool{
BorrowInterestRate: math.LegacyNewDec(2),
}
ammPool = ammtypes.Pool{}
baseCurrency = "usdc"
ammPool = ammtypes.Pool{}
)

// Mock behavior
mockChecker.On("GetMTP", ctx, sdk.MustAccAddressFromBech32(query.Address), query.PositionId).Return(mtp, nil).Once()
mockChecker.On("GetPool", ctx, mtp.AmmPoolId).Return(pool, true).Once()
mockChecker.On("GetAmmPool", ctx, mtp.AmmPoolId, mtp.CustodyAsset).Return(ammPool, nil).Once()
mockChecker.On("EstimateSwap", ctx, sdk.NewCoin(mtp.CustodyAsset, mtp.Custody), mtp.CollateralAsset, ammPool).Return(math.NewInt(10000), nil).Once()
mockChecker.On("EstimateSwapGivenOut", ctx, sdk.NewCoin(mtp.CollateralAsset, mtp.BorrowInterestUnpaidCollateral), baseCurrency, ammPool).Return(math.NewInt(200), nil).Once()
mockChecker.On("EstimateSwapGivenOut", ctx, sdk.NewCoin(baseCurrency, sdk.NewInt(9400)), mtp.CollateralAsset, ammPool).Return(math.NewInt(9400), nil).Once()
mockChecker.On("EstimateSwapGivenOut", ctx, sdk.NewCoin(mtp.CollateralAsset, mtp.Collateral), baseCurrency, ammPool).Return(math.NewInt(1111), nil).Once()
mockChecker.On("EstimateSwapGivenOut", ctx, sdk.NewCoin(mtp.CollateralAsset, mtp.BorrowInterestUnpaidCollateral), ptypes.BaseCurrency, ammPool).Return(math.NewInt(200), nil).Once()
mockChecker.On("EstimateSwapGivenOut", ctx, sdk.NewCoin(ptypes.BaseCurrency, sdk.NewInt(9400)), mtp.CollateralAsset, ammPool).Return(math.NewInt(9400), nil).Once()
mockChecker.On("EstimateSwapGivenOut", ctx, sdk.NewCoin(mtp.CollateralAsset, mtp.Collateral), ptypes.BaseCurrency, ammPool).Return(math.NewInt(1111), nil).Once()

assetProfileKeeper.On("GetEntry", ctx, ptypes.BaseCurrency).Return(atypes.Entry{
Denom: baseCurrency,
Denom: ptypes.BaseCurrency,
}, true).Once()

res, err := k.CloseEstimation(ctx, query)
Expand All @@ -144,6 +143,73 @@ func TestCloseEstimation_ExistingMTP(t *testing.T) {

assert.Equal(t, mtp.Position, res.Position)
assert.Equal(t, mtp.Custody, res.PositionSize.Amount)
assert.Equal(t, mtp.Custody, res.Custody.Amount)
assert.Equal(t, mtp.Liabilities, res.Liabilities.Amount)
assert.Equal(t, sdk.ZeroDec(), res.PriceImpact)
assert.Equal(t, swapFee, res.SwapFee)
assert.Equal(t, sdk.NewCoin(mtp.CollateralAsset, sdk.NewInt(9400)), res.ReturnAmount)
}

func TestCloseEstimation_ExistingShortMTP(t *testing.T) {
// Setup the perpetual keeper
k, ctx, assetProfileKeeper := keepertest.PerpetualKeeper(t)

// Setup the mock checker
mockChecker := new(mocks.CloseEstimationChecker)

// assign the mock checker to the keeper
k.CloseEstimationChecker = mockChecker

address := sdk.AccAddress([]byte("address"))

// get swap fee param
swapFee := k.GetSwapFee(ctx)

var (
query = &types.QueryCloseEstimationRequest{
Address: address.String(),
PositionId: 1,
}
mtp = types.MTP{
AmmPoolId: 2,
CollateralAsset: ptypes.BaseCurrency,
Collateral: sdk.NewInt(100),
CustodyAsset: ptypes.BaseCurrency,
Custody: sdk.NewInt(50),
LiabilitiesAsset: ptypes.ATOM,
Liabilities: sdk.NewInt(400),
TradingAsset: ptypes.ATOM,
Position: types.Position_SHORT,
BorrowInterestUnpaidCollateral: sdk.NewInt(10),
OpenPrice: sdk.MustNewDecFromStr("1.5"),
}
pool = types.Pool{
BorrowInterestRate: math.LegacyNewDec(2),
}
ammPool = ammtypes.Pool{}
)

// Mock behavior
mockChecker.On("GetMTP", ctx, sdk.MustAccAddressFromBech32(query.Address), query.PositionId).Return(mtp, nil).Once()
mockChecker.On("GetPool", ctx, mtp.AmmPoolId).Return(pool, true).Once()
mockChecker.On("GetAmmPool", ctx, mtp.AmmPoolId, mtp.CustodyAsset).Return(ammPool, nil).Once()
mockChecker.On("EstimateSwap", ctx, sdk.NewCoin(mtp.CustodyAsset, mtp.Custody), mtp.TradingAsset, ammPool).Return(math.NewInt(10000), nil).Once()
mockChecker.On("EstimateSwapGivenOut", ctx, sdk.NewCoin(mtp.CollateralAsset, mtp.BorrowInterestUnpaidCollateral), mtp.TradingAsset, ammPool).Return(math.NewInt(200), nil).Once()
mockChecker.On("EstimateSwapGivenOut", ctx, sdk.NewCoin(mtp.TradingAsset, sdk.NewInt(9400)), mtp.CollateralAsset, ammPool).Return(math.NewInt(9400), nil).Once()

assetProfileKeeper.On("GetEntry", ctx, ptypes.BaseCurrency).Return(atypes.Entry{
Denom: ptypes.BaseCurrency,
}, true).Once()

res, err := k.CloseEstimation(ctx, query)
assert.NoError(t, err)

mockChecker.AssertExpectations(t)
assetProfileKeeper.AssertExpectations(t)

assert.Equal(t, mtp.Position, res.Position)
assert.Equal(t, mtp.Liabilities, res.PositionSize.Amount)
assert.Equal(t, mtp.Custody, res.Custody.Amount)
assert.Equal(t, mtp.Liabilities, res.Liabilities.Amount)
assert.Equal(t, sdk.ZeroDec(), res.PriceImpact)
assert.Equal(t, swapFee, res.SwapFee)
Expand Down
24 changes: 15 additions & 9 deletions x/perpetual/keeper/query_open_estimation.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ func (k Keeper) HandleOpenEstimation(ctx sdk.Context, req *types.QueryOpenEstima
leveragedAmount := sdk.NewDecFromBigInt(collateralAmountInBaseCurrency.Amount.BigInt()).Mul(req.Leverage).TruncateInt()
leveragedCoin := sdk.NewCoin(baseCurrency, leveragedAmount)

_, _, positionSize, openPrice, swapFee, discount, availableLiquidity, slippage, weightBonus, priceImpact, err := k.amm.CalcSwapEstimationByDenom(ctx, leveragedCoin, baseCurrency, req.TradingAsset, baseCurrency, req.Discount, swapFee, decimals)
_, _, custody, openPrice, swapFee, discount, availableLiquidity, slippage, weightBonus, priceImpact, err := k.amm.CalcSwapEstimationByDenom(ctx, leveragedCoin, baseCurrency, req.TradingAsset, baseCurrency, req.Discount, swapFee, decimals)
if err != nil {
return nil, err
}

if req.Position == types.Position_SHORT {
positionSize = sdk.NewCoin(req.Collateral.Denom, leveragedAmount)
custody = sdk.NewCoin(req.Collateral.Denom, leveragedAmount)
}

// invert openPrice if collateral is not in base currency
Expand All @@ -93,31 +93,36 @@ func (k Keeper) HandleOpenEstimation(ctx sdk.Context, req *types.QueryOpenEstima
// if position is short then:
if req.Position == types.Position_SHORT {
// estimated_pnl = custody_amount - liabilities_amount * take_profit_price - collateral_amount
estimatedPnL = sdk.NewDecFromBigInt(positionSize.Amount.BigInt()).Sub(liabilitiesAmountDec.Mul(req.TakeProfitPrice)).Sub(sdk.NewDecFromBigInt(req.Collateral.Amount.BigInt()))
estimatedPnL = sdk.NewDecFromBigInt(custody.Amount.BigInt()).Sub(liabilitiesAmountDec.Mul(req.TakeProfitPrice)).Sub(sdk.NewDecFromBigInt(req.Collateral.Amount.BigInt()))
} else {
// if position is long then:
// if collateral is not in base currency
if req.Collateral.Denom != baseCurrency {
// estimated_pnl = (custody_amount - collateral_amount) * take_profit_price - liabilities_amount
estimatedPnL = sdk.NewDecFromBigInt(positionSize.Amount.BigInt()).Sub(sdk.NewDecFromBigInt(req.Collateral.Amount.BigInt())).Mul(req.TakeProfitPrice).Sub(liabilitiesAmountDec)
estimatedPnL = sdk.NewDecFromBigInt(custody.Amount.BigInt()).Sub(sdk.NewDecFromBigInt(req.Collateral.Amount.BigInt())).Mul(req.TakeProfitPrice).Sub(liabilitiesAmountDec)
} else {
// estimated_pnl = custody_amount * take_profit_price - liabilities_amount - collateral_amount
estimatedPnL = sdk.NewDecFromBigInt(positionSize.Amount.BigInt()).Mul(req.TakeProfitPrice).Sub(liabilitiesAmountDec).Sub(sdk.NewDecFromBigInt(req.Collateral.Amount.BigInt()))
estimatedPnL = sdk.NewDecFromBigInt(custody.Amount.BigInt()).Mul(req.TakeProfitPrice).Sub(liabilitiesAmountDec).Sub(sdk.NewDecFromBigInt(req.Collateral.Amount.BigInt()))
}
}

// calculate liquidation price
// liquidation_price = open_price_value - collateral_amount / custody_amount
liquidationPrice := openPrice.Sub(
sdk.NewDecFromBigInt(collateralAmountInBaseCurrency.Amount.BigInt()).Quo(sdk.NewDecFromBigInt(positionSize.Amount.BigInt())),
sdk.NewDecFromBigInt(collateralAmountInBaseCurrency.Amount.BigInt()).Quo(sdk.NewDecFromBigInt(custody.Amount.BigInt())),
)

positionSizeInTradingAsset := custody

// if position is short then liquidation price is open price + collateral amount / (custody amount / open price)
if req.Position == types.Position_SHORT {
positionSizeInTradingAsset := sdk.NewDecFromBigInt(positionSize.Amount.BigInt()).Quo(openPrice)
// for short position size = liabilities
custodyAmountInTradingAssetDec := sdk.NewDecFromBigInt(custody.Amount.BigInt()).Quo(openPrice)
liquidationPrice = openPrice.Add(
sdk.NewDecFromBigInt(collateralAmountInBaseCurrency.Amount.BigInt()).Quo(positionSizeInTradingAsset),
sdk.NewDecFromBigInt(collateralAmountInBaseCurrency.Amount.BigInt()).Quo(custodyAmountInTradingAssetDec),
)
positionSizeInTradingAssetDec := sdk.NewDecFromBigInt((custody.Amount.Sub(req.Collateral.Amount)).BigInt()).Quo(openPrice)
positionSizeInTradingAsset = sdk.NewCoin(req.TradingAsset, positionSizeInTradingAssetDec.TruncateInt())
}

// get pool rates
Expand Down Expand Up @@ -171,7 +176,8 @@ func (k Keeper) HandleOpenEstimation(ctx sdk.Context, req *types.QueryOpenEstima
TradingAsset: req.TradingAsset,
Collateral: req.Collateral,
InterestAmount: interestAmount,
PositionSize: positionSize,
Custody: custody,
PositionSize: positionSizeInTradingAsset,
SwapFee: swapFee,
Discount: discount,
OpenPrice: openPrice,
Expand Down
Loading
Loading