diff --git a/x/derivatives/abci.go b/x/derivatives/abci.go index 46f93264a..fd583270c 100644 --- a/x/derivatives/abci.go +++ b/x/derivatives/abci.go @@ -31,15 +31,18 @@ func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { func CheckPosition(ctx sdk.Context, k keeper.Keeper) { positions := k.GetAllPositions(ctx) params := k.GetParams(ctx) + quoteTicker := k.GetPoolQuoteTicker(ctx) for _, position := range positions { currentBaseUsdRate, currentQuoteUsdRate, err := k.GetPairUsdPriceFromMarket(ctx, position.Market) + baseMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.BaseDenom, currentBaseUsdRate) + quoteMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.QuoteDenom, currentQuoteUsdRate) if err != nil { // todo: user logger fmt.Println("failed to get pair usd price from market") fmt.Println(err) continue } - if position.NeedLiquidation(params.PerpetualFutures.MarginMaintenanceRate, currentBaseUsdRate, currentQuoteUsdRate) { + if position.NeedLiquidation(params.PerpetualFutures.MarginMaintenanceRate, baseMetricsRate, quoteMetricsRate) { msg := types.MsgReportLiquidation{ Sender: position.Address, PositionId: position.Id, diff --git a/x/derivatives/keeper/grpc_query.go b/x/derivatives/keeper/grpc_query.go index 38e507a23..eadec8bc8 100644 --- a/x/derivatives/keeper/grpc_query.go +++ b/x/derivatives/keeper/grpc_query.go @@ -49,8 +49,9 @@ func (k Keeper) PerpetualFutures(c context.Context, req *types.QueryPerpetualFut } } - longUUsd := positions.EvaluateLongPositions(getPriceFunc(ctx)) - shortUUsd := positions.EvaluateShortPositions(getPriceFunc(ctx)) + quoteTicker := k.GetPoolQuoteTicker(ctx) + longUUsd := positions.EvaluateLongPositions(quoteTicker, getPriceFunc(ctx)) + shortUUsd := positions.EvaluateShortPositions(quoteTicker, getPriceFunc(ctx)) // TODO: implement the handler logic ctx.BlockHeight() metricsQuoteTicker := "USD" @@ -209,7 +210,10 @@ func (k Keeper) MakeQueriedPositions(ctx sdk.Context, positions types.Positions) return nil, status.Error(codes.Internal, err.Error()) } - profit := perpetualFuturesPosition.ProfitAndLossInMetrics(currentBaseUsdRate, currentQuoteUsdRate) + quoteTicker := k.GetPoolQuoteTicker(ctx) + baseMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.BaseDenom, currentBaseUsdRate) + quoteMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.QuoteDenom, currentQuoteUsdRate) + profit := perpetualFuturesPosition.ProfitAndLossInMetrics(baseMetricsRate, quoteMetricsRate) // fixme do not use sdk.Coin directly positiveOrNegativeProfitCoin := sdk.Coin{ Denom: "uusd", @@ -217,12 +221,12 @@ func (k Keeper) MakeQueriedPositions(ctx sdk.Context, positions types.Positions) } positiveOrNegativeEffectiveMargin := sdk.Coin{ Denom: "uusd", - Amount: types.MicroToNormalDenom(perpetualFuturesPosition.EffectiveMarginInMetrics(currentBaseUsdRate, currentQuoteUsdRate)), + Amount: types.MicroToNormalDenom(perpetualFuturesPosition.EffectiveMarginInMetrics(baseMetricsRate, quoteMetricsRate)), } queriedPosition := types.QueriedPosition{ Position: position, ValuationProfit: positiveOrNegativeProfitCoin, - MarginMaintenanceRate: perpetualFuturesPosition.MarginMaintenanceRate(currentBaseUsdRate, currentQuoteUsdRate), + MarginMaintenanceRate: perpetualFuturesPosition.MarginMaintenanceRate(baseMetricsRate, quoteMetricsRate), EffectiveMargin: positiveOrNegativeEffectiveMargin, } queriedPositions = append(queriedPositions, queriedPosition) @@ -253,13 +257,16 @@ func (k Keeper) Position(c context.Context, req *types.QueryPositionRequest) (*t if err != nil { panic(err) } + quoteTicker := k.GetPoolQuoteTicker(ctx) + baseMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.BaseDenom, currentBaseUsdRate) + quoteMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.QuoteDenom, currentQuoteUsdRate) - profit := perpetualFuturesPosition.ProfitAndLossInMetrics(currentBaseUsdRate, currentQuoteUsdRate) + profit := perpetualFuturesPosition.ProfitAndLossInMetrics(baseMetricsRate, quoteMetricsRate) return &types.QueryPositionResponse{ Position: position, ValuationProfit: sdk.NewCoin("uusd", types.MicroToNormalDenom(profit)), - MarginMaintenanceRate: perpetualFuturesPosition.MarginMaintenanceRate(currentBaseUsdRate, currentQuoteUsdRate), - EffectiveMargin: sdk.NewCoin("uusd", types.MicroToNormalDenom(perpetualFuturesPosition.EffectiveMarginInMetrics(currentBaseUsdRate, currentQuoteUsdRate))), + MarginMaintenanceRate: perpetualFuturesPosition.MarginMaintenanceRate(baseMetricsRate, quoteMetricsRate), + EffectiveMargin: sdk.NewCoin("uusd", types.MicroToNormalDenom(perpetualFuturesPosition.EffectiveMarginInMetrics(baseMetricsRate, quoteMetricsRate))), }, nil } @@ -280,10 +287,11 @@ func (k Keeper) PerpetualFuturesPositionSize(c context.Context, req *types.Query } } var result sdk.Dec + quoteTicker := k.GetPoolQuoteTicker(ctx) if req.PositionType == types.PositionType_LONG { - result = positions.EvaluateLongPositions(getPriceFunc(ctx)) + result = positions.EvaluateLongPositions(quoteTicker, getPriceFunc(ctx)) } else if req.PositionType == types.PositionType_SHORT { - result = positions.EvaluateShortPositions(getPriceFunc(ctx)) + result = positions.EvaluateShortPositions(quoteTicker, getPriceFunc(ctx)) } else { return nil, status.Error(codes.InvalidArgument, "invalid position type") } diff --git a/x/derivatives/keeper/perpetual_futures.go b/x/derivatives/keeper/perpetual_futures.go index cdec10ae0..c955691e5 100644 --- a/x/derivatives/keeper/perpetual_futures.go +++ b/x/derivatives/keeper/perpetual_futures.go @@ -74,7 +74,10 @@ func (k Keeper) OpenPerpetualFuturesPosition(ctx sdk.Context, positionId string, } params := k.GetParams(ctx) - if position.NeedLiquidation(params.PerpetualFutures.MarginMaintenanceRate, position.OpenedBaseRate, position.OpenedQuoteRate) { + quoteTicker := k.GetPoolQuoteTicker(ctx) + baseMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.BaseDenom, position.OpenedBaseRate) + quoteMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.QuoteDenom, position.OpenedQuoteRate) + if position.NeedLiquidation(params.PerpetualFutures.MarginMaintenanceRate, baseMetricsRate, quoteMetricsRate) { return nil, types.ErrorInvalidPositionParams } @@ -121,7 +124,10 @@ func (k Keeper) ClosePerpetualFuturesPosition(ctx sdk.Context, position types.Pe } } - returningAmount, lossToLP := position.CalcReturningAmountAtClose(baseUsdPrice, quoteUsdPrice) + quoteTicker := k.GetPoolQuoteTicker(ctx) + baseMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.BaseDenom, baseUsdPrice) + quoteMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.QuoteDenom, quoteUsdPrice) + returningAmount, lossToLP := position.CalcReturningAmountAtClose(baseMetricsRate, quoteMetricsRate) if !(lossToLP.IsNil()) { // TODO: emit event to tell how much loss is taken by liquidity provider. @@ -152,7 +158,10 @@ func (k Keeper) ReportLiquidationNeededPerpetualFuturesPosition(ctx sdk.Context, panic(err) } - if position.NeedLiquidation(params.PerpetualFutures.MarginMaintenanceRate, currentBaseUsdRate, currentQuoteUsdRate) { + quoteTicker := k.GetPoolQuoteTicker(ctx) + baseMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.BaseDenom, currentBaseUsdRate) + quoteMetricsRate := types.NewMetricsRateType(quoteTicker, position.Market.QuoteDenom, currentQuoteUsdRate) + if position.NeedLiquidation(params.PerpetualFutures.MarginMaintenanceRate, baseMetricsRate, quoteMetricsRate) { k.ClosePerpetualFuturesPosition(ctx, position) rewardAmount := sdk.NewDecFromInt(position.RemainingMargin.Amount).Mul(params.PoolParams.ReportLiquidationRewardRate).RoundInt() diff --git a/x/derivatives/types/positions.go b/x/derivatives/types/positions.go index 5ee552e13..38075b9bd 100644 --- a/x/derivatives/types/positions.go +++ b/x/derivatives/types/positions.go @@ -47,7 +47,7 @@ func MustUnpackPositionInstance(positionAny types.Any) PositionInstance { return position } -func (m Position) NeedLiquidation(MarginMaintenanceRate, currentBaseUsdRate, currentQuoteUsdRate sdk.Dec) bool { +func (m Position) NeedLiquidation(MarginMaintenanceRate sdk.Dec, currentBaseMetricsRate, currentQuoteMetricsRate MetricsRateType) bool { ins, err := UnpackPositionInstance(m.PositionInstance) if err != nil { return false @@ -56,7 +56,7 @@ func (m Position) NeedLiquidation(MarginMaintenanceRate, currentBaseUsdRate, cur switch positionInstance := ins.(type) { case *PerpetualFuturesPositionInstance: perpetualFuturesPosition := NewPerpetualFuturesPosition(m, *positionInstance) - return perpetualFuturesPosition.NeedLiquidation(MarginMaintenanceRate, currentBaseUsdRate, currentQuoteUsdRate) + return perpetualFuturesPosition.NeedLiquidation(MarginMaintenanceRate, currentBaseMetricsRate, currentQuoteMetricsRate) break case *PerpetualOptionsPositionInstance: panic("not implemented") @@ -108,8 +108,8 @@ func NewPerpetualFuturesPositionFromPosition(position Position) (PerpetualFuture return PerpetualFuturesPosition{}, fmt.Errorf("this Any doesn't have PerpetualFuturesPositionInstance value") } -func (m PerpetualFuturesPosition) NeedLiquidation(minMarginMaintenanceRate, currentBaseUsdRate, currentQuoteUsdRate sdk.Dec) bool { - marginMaintenanceRate := m.MarginMaintenanceRate(currentBaseUsdRate, currentQuoteUsdRate) +func (m PerpetualFuturesPosition) NeedLiquidation(minMarginMaintenanceRate sdk.Dec, currentBaseMetricsRate, currentQuoteMetricsRate MetricsRateType) bool { + marginMaintenanceRate := m.MarginMaintenanceRate(currentBaseMetricsRate, currentQuoteMetricsRate) if marginMaintenanceRate.LT(minMarginMaintenanceRate) { return true } else { @@ -122,8 +122,8 @@ func (m PerpetualFuturesPosition) OpenedPairRate() sdk.Dec { } // todo make test -func (m PerpetualFuturesPosition) EvaluatePosition(currentBaseUsdRate sdk.Dec) sdk.Dec { - return currentBaseUsdRate.Mul(m.PositionInstance.Size_) +func (m PerpetualFuturesPosition) EvaluatePosition(currentBaseMetricsRate MetricsRateType) sdk.Dec { + return currentBaseMetricsRate.Amount.Amount.Mul(m.PositionInstance.Size_) } // TODO: consider to use sdk.DecCoin @@ -135,9 +135,9 @@ func MicroToNormalDec(amount sdk.Dec) sdk.Dec { return amount.Mul(sdk.MustNewDecFromStr("1000000")) } -func (m PerpetualFuturesPosition) CalcReturningAmountAtClose(baseUSDRate, quoteUSDRate sdk.Dec) (returningAmount math.Int, lossToLP math.Int) { +func (m PerpetualFuturesPosition) CalcReturningAmountAtClose(baseMetricsRate, quoteMetricsRate MetricsRateType) (returningAmount math.Int, lossToLP math.Int) { principal := m.RemainingMargin.Amount - pnlAmount := m.ProfitAndLossInMetrics(baseUSDRate, quoteUSDRate) + pnlAmount := m.ProfitAndLossInMetrics(baseMetricsRate, quoteMetricsRate) returningAmount = principal.Add(pnlAmount.TruncateInt()) @@ -151,7 +151,7 @@ func (m PerpetualFuturesPosition) CalcReturningAmountAtClose(baseUSDRate, quoteU } // todo make test -func (m Positions) EvaluatePositions(posType PositionType, getCurrentPriceF func(denom string) (sdk.Dec, error)) sdk.Dec { +func (m Positions) EvaluatePositions(posType PositionType, quoteTicker string, getCurrentPriceF func(denom string) (sdk.Dec, error)) sdk.Dec { usdMap := map[string]sdk.Dec{} result := sdk.ZeroDec() for _, position := range m { @@ -174,7 +174,10 @@ func (m Positions) EvaluatePositions(posType PositionType, getCurrentPriceF func if perpetualFuturesPosition.PositionInstance.PositionType != posType { continue } - result = result.Add(perpetualFuturesPosition.EvaluatePosition(usdMap[position.Market.BaseDenom])) + + metricsRate := NewMetricsRateType(quoteTicker, position.Market.BaseDenom, usdMap[position.Market.BaseDenom]) + + result = result.Add(perpetualFuturesPosition.EvaluatePosition(metricsRate)) break case *PerpetualOptionsPositionInstance: panic("not implemented") @@ -185,12 +188,12 @@ func (m Positions) EvaluatePositions(posType PositionType, getCurrentPriceF func return result } -func (m Positions) EvaluateLongPositions(getCurrentPriceF func(denom string) (sdk.Dec, error)) sdk.Dec { - return m.EvaluatePositions(PositionType_LONG, getCurrentPriceF) +func (m Positions) EvaluateLongPositions(quoteTicker string, getCurrentPriceF func(denom string) (sdk.Dec, error)) sdk.Dec { + return m.EvaluatePositions(PositionType_LONG, quoteTicker, getCurrentPriceF) } -func (m Positions) EvaluateShortPositions(getCurrentPriceF func(denom string) (sdk.Dec, error)) sdk.Dec { - return m.EvaluatePositions(PositionType_SHORT, getCurrentPriceF) +func (m Positions) EvaluateShortPositions(quoteTicker string, getCurrentPriceF func(denom string) (sdk.Dec, error)) sdk.Dec { + return m.EvaluatePositions(PositionType_SHORT, quoteTicker, getCurrentPriceF) } func (m PerpetualFuturesPosition) RequiredMarginInQuote(baseQuoteRate sdk.Dec) sdk.Dec { @@ -202,22 +205,22 @@ func (m PerpetualFuturesPosition) RequiredMarginInBase() sdk.Dec { return m.PositionInstance.MarginRequirement(sdk.MustNewDecFromStr("1")) } -// func (m PerpetualFuturesPosition) RequiredMarginInMetrics(requiredMarginInQuote, quoteUSDRate sdk.Dec) sdk.Dec { -func (m PerpetualFuturesPosition) RequiredMarginInMetrics(baseUSDRate, quoteUSDRate sdk.Dec) sdk.Dec { +// func (m PerpetualFuturesPosition) RequiredMarginInMetrics(requiredMarginInQuote, quoteMetricsRate sdk.Dec) sdk.Dec { +func (m PerpetualFuturesPosition) RequiredMarginInMetrics(baseMetricsRate, quoteMetricsRate MetricsRateType) sdk.Dec { // 必要証拠金(USD単位) = 必要証拠金(quote単位) * 現在のquote/USDレート // = 必要証拠金(base単位) * 現在のbase/USDレート if m.RemainingMargin.Denom == m.Market.QuoteDenom { - baseQuoteRate := baseUSDRate.Quo(quoteUSDRate) - return m.RequiredMarginInQuote(baseQuoteRate).Mul(quoteUSDRate) + baseQuoteRate := baseMetricsRate.Amount.Amount.Quo(quoteMetricsRate.Amount.Amount) + return m.RequiredMarginInQuote(baseQuoteRate).Mul(quoteMetricsRate.Amount.Amount) } else if m.RemainingMargin.Denom == m.Market.BaseDenom { - return m.RequiredMarginInBase().Mul(baseUSDRate) + return m.RequiredMarginInBase().Mul(baseMetricsRate.Amount.Amount) } else { panic("not supported denom") } } -func (m PerpetualFuturesPosition) ProfitAndLossInQuote(baseUSDRate, quoteUSDRate sdk.Dec) sdk.Dec { +func (m PerpetualFuturesPosition) ProfitAndLossInQuote(baseMetricsRate, quoteMetricsRate MetricsRateType) sdk.Dec { // 損益(quote単位) = (longなら1,shortなら-1) * (現在のbase/quoteレート - ポジション開設時base/quoteレート) * ポジションサイズ(base単位) - baseQuoteRate := baseUSDRate.Quo(quoteUSDRate) + baseQuoteRate := baseMetricsRate.Amount.Amount.Quo(quoteMetricsRate.Amount.Amount) profitOrLoss := baseQuoteRate.Sub(m.OpenedPairRate()).Mul(m.PositionInstance.Size_) if m.PositionInstance.PositionType == PositionType_LONG { return profitOrLoss @@ -226,35 +229,47 @@ func (m PerpetualFuturesPosition) ProfitAndLossInQuote(baseUSDRate, quoteUSDRate } } -func (m PerpetualFuturesPosition) ProfitAndLossInMetrics(baseUSDRate, quoteUSDRate sdk.Dec) sdk.Dec { +func (m PerpetualFuturesPosition) ProfitAndLossInMetrics(baseMetricsRate, quoteMetricsRate MetricsRateType) sdk.Dec { // 損益(USD単位) = 損益(quote単位) * 現在のquote/USDレート - return m.ProfitAndLossInQuote(baseUSDRate, quoteUSDRate).Mul(quoteUSDRate) + return m.ProfitAndLossInQuote(baseMetricsRate, quoteMetricsRate).Mul(quoteMetricsRate.Amount.Amount) } -func (m PerpetualFuturesPosition) MarginMaintenanceRate(baseUSDRate, quoteUSDRate sdk.Dec) sdk.Dec { +func (m PerpetualFuturesPosition) MarginMaintenanceRate(baseMetricsRate, quoteMetricsRate MetricsRateType) sdk.Dec { // 証拠金維持率 = 有効証拠金(USD単位) ÷ 必要証拠金(USD単位) - return m.EffectiveMarginInMetrics(baseUSDRate, quoteUSDRate).Quo(m.RequiredMarginInMetrics(baseUSDRate, quoteUSDRate)) + return m.EffectiveMarginInMetrics(baseMetricsRate, quoteMetricsRate).Quo(m.RequiredMarginInMetrics(baseMetricsRate, quoteMetricsRate)) } -func (m PerpetualFuturesPosition) RemainingMarginInBase(baseUSDRate sdk.Dec) sdk.Dec { +func (m PerpetualFuturesPosition) RemainingMarginInBase(baseMetricsRate MetricsRateType) sdk.Dec { // 残存証拠金(USD単位) = 残存証拠金(base単位) * 現在のbase/USDレート - return sdk.NewDecFromInt(m.RemainingMargin.Amount).Mul(baseUSDRate) + return sdk.NewDecFromInt(m.RemainingMargin.Amount).Mul(baseMetricsRate.Amount.Amount) } -func (m PerpetualFuturesPosition) RemainingMarginInQuote(quoteUSDRate sdk.Dec) sdk.Dec { +func (m PerpetualFuturesPosition) RemainingMarginInQuote(quoteMetricsRate MetricsRateType) sdk.Dec { // 残存証拠金(USD単位) = 残存証拠金(quote単位) * 現在のquote/USDレート - return sdk.NewDecFromInt(m.RemainingMargin.Amount).Mul(quoteUSDRate) + return sdk.NewDecFromInt(m.RemainingMargin.Amount).Mul(quoteMetricsRate.Amount.Amount) } -func (m PerpetualFuturesPosition) RemainingMarginInMetrics(baseUSDRate, quoteUSDRate sdk.Dec) sdk.Dec { +func (m PerpetualFuturesPosition) RemainingMarginInMetrics(baseMetricsRate, quoteMetricsRate MetricsRateType) sdk.Dec { // 残存証拠金(USD単位) = 残存証拠金(base単位) * 現在のbase/USDレート // = 残存証拠金(quote単位) * 現在のquote/USDレート if m.RemainingMargin.Denom == m.Market.BaseDenom { - return m.RemainingMarginInBase(baseUSDRate) + return m.RemainingMarginInBase(baseMetricsRate) } else if m.RemainingMargin.Denom == m.Market.QuoteDenom { - return m.RemainingMarginInQuote(quoteUSDRate) + return m.RemainingMarginInQuote(quoteMetricsRate) } else { panic("not supported denom") } } -func (m PerpetualFuturesPosition) EffectiveMarginInMetrics(baseUSDRate, quoteUSDRate sdk.Dec) sdk.Dec { +func (m PerpetualFuturesPosition) EffectiveMarginInMetrics(baseMetricsRate, quoteMetricsRate MetricsRateType) sdk.Dec { // 有効証拠金(USD単位) = 残存証拠金(USD単位) + 損益(USD単位) - return m.RemainingMarginInMetrics(baseUSDRate, quoteUSDRate).Add(m.ProfitAndLossInMetrics(baseUSDRate, quoteUSDRate)) + return m.RemainingMarginInMetrics(baseMetricsRate, quoteMetricsRate).Add(m.ProfitAndLossInMetrics(baseMetricsRate, quoteMetricsRate)) +} + +func NewMetricsRateType(unit string, denom string, amount sdk.Dec) MetricsRateType { + return MetricsRateType{ + MetricsUnit: unit, + Amount: sdk.NewDecCoinFromDec(denom, amount), + } +} + +type MetricsRateType struct { + MetricsUnit string + Amount sdk.DecCoin }