Skip to content

Commit

Permalink
Merge pull request #11661 from vegaprotocol/11519-positions-include-fees
Browse files Browse the repository at this point in the history
11519 positions include fees
  • Loading branch information
EVODelavega authored Sep 6, 2024
2 parents 83ee334 + ba936ee commit ac73a65
Show file tree
Hide file tree
Showing 11 changed files with 2,469 additions and 1,568 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

### 🛠 Improvements

- [](https://github.com/vegaprotocol/vega/issues/xxx)
- [11644](https://github.com/vegaprotocol/vega/issues/11644) - `liveOnly` flag has been added to the `AMM` API to show only active `AMMs`.
- [11519](https://github.com/vegaprotocol/vega/issues/11519) - Add fees to position API types.

### 🐛 Fixes

Expand Down
95 changes: 81 additions & 14 deletions datanode/entities/position.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ type Position struct {
PendingAverageEntryMarketPrice decimal.Decimal
LossSocialisationAmount decimal.Decimal
DistressedStatus PositionStatus
TakerFeesPaid num.Decimal
MakerFeesReceived num.Decimal
FeesPaid num.Decimal
TakerFeesPaidSince num.Decimal
MakerFeesReceivedSince num.Decimal
FeesPaidSince num.Decimal
FundingPaymentAmount num.Decimal
FundingPaymentAmountSince num.Decimal
}

func NewEmptyPosition(marketID MarketID, partyID PartyID) Position {
Expand All @@ -93,6 +101,14 @@ func NewEmptyPosition(marketID MarketID, partyID PartyID) Position {
PendingAverageEntryMarketPrice: num.DecimalZero(),
LossSocialisationAmount: num.DecimalZero(),
DistressedStatus: PositionStatusUnspecified,
TakerFeesPaid: num.DecimalZero(),
MakerFeesReceived: num.DecimalZero(),
FeesPaid: num.DecimalZero(),
TakerFeesPaidSince: num.DecimalZero(),
MakerFeesReceivedSince: num.DecimalZero(),
FeesPaidSince: num.DecimalZero(),
FundingPaymentAmount: num.DecimalZero(),
FundingPaymentAmountSince: num.DecimalZero(),
}
}

Expand Down Expand Up @@ -123,6 +139,14 @@ func (p *Position) UpdateWithTrade(trade vega.Trade, seller bool, pf num.Decimal
if seller {
size *= -1
}
// add fees paid/received
fees := getFeeAmountsForSide(&trade, seller)
maker, taker, other := num.DecimalFromUint(fees.maker), num.DecimalFromUint(fees.taker), num.DecimalFromUint(fees.other)
p.MakerFeesReceived = p.MakerFeesReceived.Add(maker)
p.TakerFeesPaid = p.TakerFeesPaid.Add(taker)
p.FeesPaid = p.FeesPaid.Add(other)
// check if we should reset the "since" fields for fees
since := p.PendingOpenVolume == 0
// close out trade doesn't require the MTM calculation to be performed
// the distressed trader will be handled through a settle distressed event, the network
// open volume should just be updated, the average entry price is unchanged.
Expand All @@ -133,6 +157,8 @@ func (p *Position) UpdateWithTrade(trade vega.Trade, seller bool, pf num.Decimal
opened, closed := CalculateOpenClosedVolume(p.PendingOpenVolume, size)
realisedPnlDelta := assetPrice.Sub(p.PendingAverageEntryPrice).Mul(num.DecimalFromInt64(closed)).Div(pf)
p.PendingRealisedPnl = p.PendingRealisedPnl.Add(realisedPnlDelta)
// did we start with a positive/negative position?
pos := p.PendingOpenVolume > 0
p.PendingOpenVolume -= closed

marketPriceUint, _ := num.UintFromDecimal(marketPrice)
Expand All @@ -141,6 +167,18 @@ func (p *Position) UpdateWithTrade(trade vega.Trade, seller bool, pf num.Decimal
p.PendingAverageEntryPrice = updateVWAP(p.PendingAverageEntryPrice, p.PendingOpenVolume, opened, assetPriceUint)
p.PendingAverageEntryMarketPrice = updateVWAP(p.PendingAverageEntryMarketPrice, p.PendingOpenVolume, opened, marketPriceUint)
p.PendingOpenVolume += opened
// either the position is no longer 0, or the position has flipped sides (and is non-zero)
if since || (pos != (p.PendingOpenVolume > 0) && p.PendingOpenVolume != 0) {
p.MakerFeesReceivedSince = num.DecimalZero()
p.TakerFeesPaidSince = num.DecimalZero()
p.FeesPaidSince = num.DecimalZero()
}
if p.PendingOpenVolume != 0 {
// running total of fees paid since get incremented
p.MakerFeesReceivedSince = p.MakerFeesReceivedSince.Add(maker)
p.TakerFeesPaidSince = p.TakerFeesPaidSince.Add(taker)
p.FeesPaidSince = p.FeesPaidSince.Add(other)
}
p.pendingMTM(assetPrice, pf)
if trade.Type == types.TradeTypeNetworkCloseOutBad {
p.updateWithBadTrade(trade, seller, pf)
Expand All @@ -152,9 +190,12 @@ func (p *Position) UpdateWithTrade(trade vega.Trade, seller bool, pf num.Decimal
}

func (p *Position) ApplyFundingPayment(amount *num.Int) {
da := num.DecimalFromInt(amount)
p.PendingRealisedPnl = p.PendingRealisedPnl.Add(da)
p.RealisedPnl = p.RealisedPnl.Add(da)
amt := num.DecimalFromInt(amount)
p.FundingPaymentAmount = p.FundingPaymentAmount.Add(amt)
p.FundingPaymentAmountSince = p.FundingPaymentAmountSince.Add(amt)
// da := num.DecimalFromInt(amount)
// p.PendingRealisedPnl = p.PendingRealisedPnl.Add(da)
// p.RealisedPnl = p.RealisedPnl.Add(da)
}

func (p *Position) UpdateOrdersClosed() {
Expand All @@ -173,17 +214,29 @@ func (p *Position) ToggleDistressedStatus() {

func (p *Position) UpdateWithPositionSettlement(e positionSettlement) {
pf := e.PositionFactor()
resetFP := false
for _, t := range e.Trades() {
if p.OpenVolume == 0 {
resetFP = true
}
openedVolume, closedVolume := CalculateOpenClosedVolume(p.OpenVolume, t.Size())
// Deal with any volume we have closed
realisedPnlDelta := num.DecimalFromUint(t.Price()).Sub(p.AverageEntryPrice).Mul(num.DecimalFromInt64(closedVolume)).Div(pf)
p.RealisedPnl = p.RealisedPnl.Add(realisedPnlDelta)
pos := p.OpenVolume > 0
p.OpenVolume -= closedVolume

// Then with any we have opened
p.AverageEntryPrice = updateVWAP(p.AverageEntryPrice, p.OpenVolume, openedVolume, t.Price())
p.AverageEntryMarketPrice = updateVWAP(p.AverageEntryMarketPrice, p.OpenVolume, openedVolume, t.MarketPrice())
p.OpenVolume += openedVolume
// check if position flipped
if !resetFP && (pos != (p.OpenVolume > 0) && p.OpenVolume != 0) {
resetFP = true
}
}
if resetFP {
p.FundingPaymentAmountSince = num.DecimalZero()
}
p.mtm(e.Price(), pf)
p.TxHash = TxHash(e.TxHash())
Expand Down Expand Up @@ -225,6 +278,10 @@ func (p *Position) UpdateWithSettleDistressed(e settleDistressed) {
p.OpenVolume = 0
p.TxHash = TxHash(e.TxHash())
p.DistressedStatus = PositionStatusClosedOut
p.FundingPaymentAmountSince = num.DecimalZero()
p.FeesPaidSince = num.DecimalZero()
p.MakerFeesReceivedSince = num.DecimalZero()
p.TakerFeesPaidSince = num.DecimalZero()
p.syncPending()
}

Expand All @@ -248,15 +305,23 @@ func (p Position) ToProto() *vega.Position {
// we use the pending values when converting to protos
// so trades are reflected as accurately as possible
return &vega.Position{
MarketId: p.MarketID.String(),
PartyId: p.PartyID.String(),
OpenVolume: p.PendingOpenVolume,
RealisedPnl: p.PendingRealisedPnl.Round(0).String(),
UnrealisedPnl: p.PendingUnrealisedPnl.Round(0).String(),
AverageEntryPrice: p.PendingAverageEntryMarketPrice.Round(0).String(),
UpdatedAt: timestamp,
LossSocialisationAmount: p.LossSocialisationAmount.Round(0).String(),
PositionStatus: vega.PositionStatus(p.DistressedStatus),
MarketId: p.MarketID.String(),
PartyId: p.PartyID.String(),
OpenVolume: p.PendingOpenVolume,
RealisedPnl: p.PendingRealisedPnl.Round(0).String(),
UnrealisedPnl: p.PendingUnrealisedPnl.Round(0).String(),
AverageEntryPrice: p.PendingAverageEntryMarketPrice.Round(0).String(),
UpdatedAt: timestamp,
LossSocialisationAmount: p.LossSocialisationAmount.Round(0).String(),
PositionStatus: vega.PositionStatus(p.DistressedStatus),
TakerFeesPaid: p.TakerFeesPaid.String(),
MakerFeesReceived: p.MakerFeesReceived.String(),
FeesPaid: p.FeesPaid.String(),
TakerFeesPaidSince: p.TakerFeesPaidSince.String(),
MakerFeesReceivedSince: p.MakerFeesReceivedSince.String(),
FeesPaidSince: p.FeesPaidSince.String(),
FundingPaymentAmount: p.FundingPaymentAmount.String(),
FundingPaymentAmountSince: p.FundingPaymentAmountSince.String(),
}
}

Expand Down Expand Up @@ -352,15 +417,17 @@ var PositionColumns = []string{
"market_id", "party_id", "open_volume", "realised_pnl", "unrealised_pnl",
"average_entry_price", "average_entry_market_price", "loss", "adjustment", "tx_hash", "vega_time", "pending_open_volume",
"pending_realised_pnl", "pending_unrealised_pnl", "pending_average_entry_price", "pending_average_entry_market_price",
"loss_socialisation_amount", "distressed_status",
"loss_socialisation_amount", "distressed_status", "taker_fees_paid", "maker_fees_received", "fees_paid",
"taker_fees_paid_since", "maker_fees_received_since", "fees_paid_since", "funding_payment_amount", "funding_payment_amount_since",
}

func (p Position) ToRow() []interface{} {
return []interface{}{
p.MarketID, p.PartyID, p.OpenVolume, p.RealisedPnl, p.UnrealisedPnl,
p.AverageEntryPrice, p.AverageEntryMarketPrice, p.Loss, p.Adjustment, p.TxHash, p.VegaTime, p.PendingOpenVolume,
p.PendingRealisedPnl, p.PendingUnrealisedPnl, p.PendingAverageEntryPrice, p.PendingAverageEntryMarketPrice,
p.LossSocialisationAmount, p.DistressedStatus,
p.LossSocialisationAmount, p.DistressedStatus, p.TakerFeesPaid, p.MakerFeesReceived, p.FeesPaid,
p.TakerFeesPaidSince, p.MakerFeesReceivedSince, p.FeesPaidSince, p.FundingPaymentAmount, p.FundingPaymentAmountSince,
}
}

Expand Down
93 changes: 93 additions & 0 deletions datanode/entities/position_fees.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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 entities

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

type feeAmounts struct {
side types.Side
maker *num.Uint
taker *num.Uint
other *num.Uint
}

func newFeeAmounts(side types.Side) *feeAmounts {
return &feeAmounts{
side: side,
maker: num.UintZero(),
taker: num.UintZero(),
other: num.UintZero(),
}
}

func getFeeAmountsForSide(trade *vega.Trade, seller bool) *feeAmounts {
b, s := getFeeAmounts(trade)
if seller {
return s
}
return b
}

func getFeeAmounts(trade *vega.Trade) (*feeAmounts, *feeAmounts) {
buyer, seller := newFeeAmounts(types.SideBuy), newFeeAmounts(types.SideSell)
buyer.setAmounts(trade)
seller.setAmounts(trade)
// auction end trades don't really have an aggressor, maker and taker fees are split.
if trade.Aggressor == types.SideSell {
buyer.maker.AddSum(seller.taker)
} else if trade.Aggressor == types.SideBuy {
seller.maker.AddSum(buyer.taker)
} else {
buyer.maker.AddSum(seller.taker)
seller.maker.AddSum(buyer.taker)
}
return buyer, seller
}

func (f *feeAmounts) setAmounts(trade *vega.Trade) {
fee := trade.BuyerFee
if f.side == types.SideSell {
fee = trade.SellerFee
}
if fee == nil {
return
}
maker, infra, lFee, tFee, bbFee, hvFee := num.UintZero(), num.UintZero(), num.UintZero(), num.UintZero(), num.UintZero(), num.UintZero()
if len(fee.MakerFee) > 0 {
maker, _ = num.UintFromString(fee.MakerFee, 10)
}
if len(fee.InfrastructureFee) > 0 {
infra, _ = num.UintFromString(fee.InfrastructureFee, 10)
}
if len(fee.LiquidityFee) > 0 {
lFee, _ = num.UintFromString(fee.LiquidityFee, 10)
}
if len(fee.TreasuryFee) > 0 {
tFee, _ = num.UintFromString(fee.TreasuryFee, 10)
}
if len(fee.BuyBackFee) > 0 {
bbFee, _ = num.UintFromString(fee.BuyBackFee, 10)
}
if len(fee.HighVolumeMakerFee) > 0 {
hvFee, _ = num.UintFromString(fee.HighVolumeMakerFee, 10)
}
f.other.AddSum(infra, lFee, tFee, bbFee, hvFee)
f.taker.AddSum(maker)
}
74 changes: 70 additions & 4 deletions datanode/entities/position_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,72 @@ func TestPnLWithPositionDecimals(t *testing.T) {
assert.EqualValues(t, 0, pp.OpenVolume)
}

func TestTradeFees(t *testing.T) {
// ctx := context.Background()
market := "market-id"
party := "party1"
sParty := "seller"
position := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party))
sPos := entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(sParty))
dp := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(3))

// first update with trades
trade := vega.Trade{
Id: "t1",
MarketId: market,
Price: "1000",
Size: 2,
Buyer: party,
Seller: sParty,
AssetPrice: "1000",
Aggressor: types.SideSell,
BuyerFee: &vega.Fee{
MakerFee: "10",
InfrastructureFee: "1",
LiquidityFee: "2",
TreasuryFee: "1",
BuyBackFee: "0",
HighVolumeMakerFee: "0",
},
SellerFee: &vega.Fee{
MakerFee: "20",
InfrastructureFee: "2",
LiquidityFee: "3",
TreasuryFee: "2",
BuyBackFee: "1",
HighVolumeMakerFee: "1",
},
}
bFeesPaid := num.NewDecimalFromFloat(4)
sFeesPaid := num.NewDecimalFromFloat(9)
bMaker := num.NewDecimalFromFloat(10)
sMaker := num.NewDecimalFromFloat(20)
position.UpdateWithTrade(trade, false, dp)
sPos.UpdateWithTrade(trade, true, dp)
require.True(t, position.FeesPaid.Equal(bFeesPaid))
require.True(t, sPos.FeesPaid.Equal(sFeesPaid))
// maker fees swap
require.True(t, sPos.TakerFeesPaid.Equal(sMaker))
require.True(t, position.MakerFeesReceived.Equal(sMaker))
// we have an aggressor, so only one side received maker fees
require.True(t, sPos.MakerFeesReceived.Equal(num.DecimalZero()))
require.True(t, position.TakerFeesPaid.Equal(bMaker))
// now the same trade but with no aggressor
trade.Aggressor = types.SideUnspecified
position = entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(party))
sPos = entities.NewEmptyPosition(entities.MarketID(market), entities.PartyID(sParty))
position.UpdateWithTrade(trade, false, dp)
sPos.UpdateWithTrade(trade, true, dp)
require.True(t, position.FeesPaid.Equal(bFeesPaid))
require.True(t, sPos.FeesPaid.Equal(sFeesPaid))
// maker fees swap
require.True(t, sPos.TakerFeesPaid.Equal(sMaker))
require.True(t, position.MakerFeesReceived.Equal(sMaker))
// we have an aggressor, so only one side received maker fees
require.True(t, sPos.MakerFeesReceived.Equal(bMaker))
require.True(t, position.TakerFeesPaid.Equal(bMaker))
}

func TestPnLWithTradeDecimals(t *testing.T) {
ctx := context.Background()
market := "market-id"
Expand Down Expand Up @@ -362,20 +428,20 @@ func TestUpdateWithTradesAndFundingPayment(t *testing.T) {
assert.Equal(t, "0", pp.UnrealisedPnl)
position.ApplyFundingPayment(num.NewInt(100))
pp = position.ToProto()
assert.Equal(t, "100", pp.RealisedPnl)
assert.Equal(t, "100", position.FundingPaymentAmount.String())
assert.Equal(t, "0", pp.UnrealisedPnl, pp.AverageEntryPrice)
position.UpdateWithTrade(trades[1].ToVega(dp), false, dp)
pp = position.ToProto()
assert.Equal(t, "100", pp.RealisedPnl)
assert.Equal(t, "100", position.FundingPaymentAmount.String())
assert.Equal(t, "-133", pp.UnrealisedPnl, pp.AverageEntryPrice)
ps := events.NewSettlePositionEvent(ctx, party, market, num.NewUint(1000), []events.TradeSettlement{trades[0], trades[1]}, 1, dp)
position.UpdateWithPositionSettlement(ps)
psp := position.ToProto()
assert.Equal(t, "100", psp.RealisedPnl)
assert.Equal(t, "100", position.FundingPaymentAmount.String())
assert.Equal(t, "-133", psp.UnrealisedPnl)
position.ApplyFundingPayment(num.NewInt(-50))
pp = position.ToProto()
assert.Equal(t, "50", pp.RealisedPnl)
assert.Equal(t, "50", position.FundingPaymentAmount.String())
assert.Equal(t, "-133", pp.UnrealisedPnl, pp.AverageEntryPrice)
}

Expand Down
Loading

0 comments on commit ac73a65

Please sign in to comment.