Skip to content

Commit

Permalink
fix: fix settlement pnl calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiasmatt committed Oct 11, 2023
1 parent 4ed63f1 commit 6ee1d1f
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 7 deletions.
53 changes: 47 additions & 6 deletions x/perp/v2/integration/action/settlement.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package action

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/NibiruChain/nibiru/app"
"github.com/NibiruChain/nibiru/x/common/asset"
"github.com/NibiruChain/nibiru/x/common/testutil/action"
"github.com/NibiruChain/nibiru/x/perp/v2/types"
)

// closeMarket
type closeMarket struct {
pair asset.Pair
}
Expand All @@ -25,25 +29,62 @@ func CloseMarket(pair asset.Pair) action.Action {
return closeMarket{pair: pair}
}

// settlePosition
type settlePosition struct {
pair asset.Pair
version uint64
trader sdk.AccAddress
pair asset.Pair
version uint64
trader sdk.AccAddress
responseCheckers []SettlePositionChecker
}

func (c settlePosition) Do(app *app.NibiruApp, ctx sdk.Context) (sdk.Context, error, bool) {
_, err := app.PerpKeeperV2.SettlePosition(ctx, c.pair, c.version, c.trader)
resp, err := app.PerpKeeperV2.SettlePosition(ctx, c.pair, c.version, c.trader)
if err != nil {
return ctx, err, false
}

for _, checker := range c.responseCheckers {
if err := checker(*resp); err != nil {
return ctx, err, false
}
}

return ctx, nil, true
}

func SettlePosition(pair asset.Pair, version uint64, trader sdk.AccAddress) action.Action {
return settlePosition{pair: pair, version: version, trader: trader}
func SettlePosition(pair asset.Pair, version uint64, trader sdk.AccAddress, responseCheckers ...SettlePositionChecker) action.Action {
return settlePosition{pair: pair, version: version, trader: trader, responseCheckers: responseCheckers}
}

type SettlePositionChecker func(resp types.PositionResp) error

func SettlePositionChecker_PositionEquals(expected types.Position) SettlePositionChecker {
return func(resp types.PositionResp) error {
return types.PositionsAreEqual(&expected, &resp.Position)
}
}

func SettlePositionChecker_MarginToVault(expectedMarginToVault sdk.Dec) SettlePositionChecker {
return func(resp types.PositionResp) error {
if expectedMarginToVault.Equal(resp.MarginToVault) {
return nil
} else {
return fmt.Errorf("expected margin to vault %s, got %s", expectedMarginToVault, resp.MarginToVault)
}
}
}

func SettlePositionChecker_BadDebt(expectedBadDebt sdk.Dec) SettlePositionChecker {
return func(resp types.PositionResp) error {
if expectedBadDebt.Equal(resp.BadDebt) {
return nil
} else {
return fmt.Errorf("expected bad debt %s, got %s", expectedBadDebt, resp.BadDebt)
}
}
}

// settlePositionShouldFail
type settlePositionShouldFail struct {
pair asset.Pair
version uint64
Expand Down
2 changes: 1 addition & 1 deletion x/perp/v2/keeper/settlement.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (k Keeper) SettlePosition(ctx sdk.Context, pair asset.Pair, version uint64,
// Settles a position and realizes PnL and funding payments.
// Returns the updated AMM and the realized PnL and funding payments.
func (k Keeper) settlePosition(ctx sdk.Context, market types.Market, amm types.AMM, position types.Position) (updatedAMM *types.AMM, resp *types.PositionResp, err error) {
positionNotional := position.Size_.Mul(amm.SettlementPrice)
positionNotional := position.Size_.Abs().Mul(amm.SettlementPrice)

resp = &types.PositionResp{
ExchangedPositionSize: position.Size_.Neg(),
Expand Down
70 changes: 70 additions & 0 deletions x/perp/v2/keeper/settlement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/NibiruChain/nibiru/x/common/denoms"
"github.com/NibiruChain/nibiru/x/common/testutil"
. "github.com/NibiruChain/nibiru/x/common/testutil/action"
. "github.com/NibiruChain/nibiru/x/common/testutil/assertion"
. "github.com/NibiruChain/nibiru/x/perp/v2/integration/action"
. "github.com/NibiruChain/nibiru/x/perp/v2/integration/assertion"
"github.com/NibiruChain/nibiru/x/perp/v2/types"
Expand Down Expand Up @@ -120,6 +121,7 @@ func TestSettlePosition(t *testing.T) {
startTime := time.Now()

alice := testutil.AccAddress()
bob := testutil.AccAddress()

tc := TestCases{
TC("Happy path").When(
Expand All @@ -146,6 +148,74 @@ func TestSettlePosition(t *testing.T) {
PositionShouldNotExist(alice, pairBtcUsdc, 1),
),

TC("Happy path, but with bad debt").When(
CreateCustomMarket(
pairBtcUsdc,
WithPricePeg(sdk.OneDec()),
WithSqrtDepth(sdk.NewDec(100_000)),
),
SetBlockNumber(1),
SetBlockTime(startTime),
FundAccount(alice, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(104)))), // need 4 because we need to pay for the close position fee
FundAccount(bob, sdk.NewCoins(sdk.NewCoin(denoms.NUSD, sdk.NewInt(1_020)))),
MarketOrder(
alice,
pairBtcUsdc,
types.Direction_SHORT,
sdk.NewInt(100),
sdk.NewDec(10),
sdk.ZeroDec(),
),
MarketOrder(
bob,
pairBtcUsdc,
types.Direction_LONG,
sdk.NewInt(1_000),
sdk.NewDec(10),
sdk.ZeroDec(),
),
QueryPosition(pairBtcUsdc, alice, QueryPosition_MarginRatioEquals(sdk.MustNewDecFromStr("-0.093502230451982156"))),
).When(
// Alice opened a short position (leverage x10) while bob a bigger long position
// Price jumped by 10%, with a settlement price of 1.09
// That creates a bad debt for alice
// Her Realized Pnl is -101.01010101 and her margin is 100, so -1.01010101 is bad debt
// Bob's Realized Pnl is 1010, so he has 1010 more than his margin

CloseMarket(pairBtcUsdc),
SettlePosition(
pairBtcUsdc,
1,
alice,
SettlePositionChecker_PositionEquals(
types.Position{
TraderAddress: alice.String(),
Pair: "ubtc:unusd",
Size_: sdk.MustNewDecFromStr("0"),
Margin: sdk.MustNewDecFromStr("0"),
OpenNotional: sdk.MustNewDecFromStr("0"),
LatestCumulativePremiumFraction: sdk.MustNewDecFromStr("0"),
LastUpdatedBlockNumber: 1,
},
),
SettlePositionChecker_MarginToVault(sdk.ZeroDec()),
SettlePositionChecker_BadDebt(sdk.MustNewDecFromStr("1.010101010101010101")),
),
SettlePosition(
pairBtcUsdc,
1,
bob,
SettlePositionChecker_MarginToVault(sdk.MustNewDecFromStr("-1101.010101010101010100")),
SettlePositionChecker_BadDebt(sdk.ZeroDec()),
),
).Then(
PositionShouldNotExist(alice, pairBtcUsdc, 1),
PositionShouldNotExist(bob, pairBtcUsdc, 1),
SetBlockNumber(2),
BalanceEqual(alice, "unusd", sdk.NewInt(0)),
BalanceEqual(bob, "unusd", sdk.NewInt(1101-20)),
),

TC("Error: can't settle on enabled market").When(
CreateCustomMarket(
pairBtcUsdc,
Expand Down

0 comments on commit 6ee1d1f

Please sign in to comment.