Skip to content

Commit

Permalink
test: add fee support and docs
Browse files Browse the repository at this point in the history
Signed-off-by: Elias Van Ootegem <elias@vega.xyz>
  • Loading branch information
EVODelavega committed Oct 1, 2024
1 parent daa4aa2 commit 5abcd2f
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 100 deletions.
38 changes: 38 additions & 0 deletions core/integration/docs/positions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## Verifying positions and PnL

At its heart, we are testing a trading platform. Therefore, we need to have the ability to verify the positions held by traders, and their realised or unrealised profit and loss (PnL). The data-node component implements a positions API which collates data events sent out when parties trade, positions are marked to market or settled, when perpetual markets process a funding payment, etc...
The integration test framework implements a number of steps to verify whether or not a trade took place, verifying [individual transfer data](transfers.md). In addition to this, the integration test framework implements a step that allows us to verify the position data analogous to the data-node API.

```cucumber
Then the parties should have the following profit and loss:
| market id | party | volume | unrealised pnl | realised pnl | status | taker fees | taker fees since | maker fees | maker fees since | other fees | other fees since | funding payments | funding payments since |
| ETH/DEC19 | trader2 | 0 | 0 | 0 | POSITION_STATUS_ORDERS_CLOSED | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| ETH/DEC19 | trader3 | 0 | 0 | -162 | POSITION_STATUS_CLOSED_OUT | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| ETH/DEC19 | auxiliary1 | -10 | -900 | 0 | | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| ETH/DEC19 | auxiliary2 | 5 | 475 | 586 | | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
```

Where the fields are defined as follows:

```
| name | type | required |
|------------------------|----------------|----------|
| market id | string | no |
| party | string | yes |
| volume | int64 | yes |
| unrealised pnl | Int | yes |
| realised pnl | Int | yes |
| status | PositionStatus | no |
| taker fees | Uint | no |
| maker fees | Uint | no |
| other fees | Uint | no |
| taker fees since | Uint | no |
| maker fees since | Uint | no |
| other fees since | Uint | no |
| funding payments | Int | no |
| funding payments since | Int | no |
| is amm | bool | no |
```

Details for the [`PositionStatus` type](types.md#Position-status)

9 changes: 9 additions & 0 deletions core/integration/docs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

Below is a list of types used in the docs and the possible values

## Position status

Possible values for `PositionStatus` are:

* POSITION_STATUS_UNSPECIFIED
* POSITION_STATUS_ORDERS_CLOSED
* POSITION_STATUS_CLOSED_OUT
* POSITION_STATUS_DISTRESSED

## Auction trigger

Possible values for `AuctionTrigger` are:
Expand Down
10 changes: 5 additions & 5 deletions core/integration/features/zero-position.feature
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ Feature: Closeout scenarios
| trader3 | USD | ETH/DEC19 | 0 | 0 |

Then the parties should have the following profit and loss:
| party | volume | unrealised pnl | realised pnl | status |
| trader2 | 0 | 0 | 0 | POSITION_STATUS_ORDERS_CLOSED |
| trader3 | 0 | 0 | -162 | POSITION_STATUS_CLOSED_OUT |
| auxiliary1 | -10 | -900 | 0 | |
| auxiliary2 | 5 | 475 | 586 | |
| party | volume | unrealised pnl | realised pnl | status | taker fees | taker fees since | maker fees | maker fees since | other fees | other fees since | funding payments | funding payments since |
| trader2 | 0 | 0 | 0 | POSITION_STATUS_ORDERS_CLOSED | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| trader3 | 0 | 0 | -162 | POSITION_STATUS_CLOSED_OUT | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| auxiliary1 | -10 | -900 | 0 | | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| auxiliary2 | 5 | 475 | 586 | | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
And the insurance pool balance should be "0" for the market "ETH/DEC19"
When the parties place the following orders:
| party | market id | side | price | volume | resulting trades | type | tif | reference |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ func positionAPIProduceTheFollowingRow(exec Execution, positionService *plugins.
var pos []*types.Position
// check position status if needed
ps, checkPS := row.positionState()
checkFees := row.checkFees()
checkFunding := row.checkFunding()
party := row.party()
readableParty := party
if row.isAMM() {
Expand Down Expand Up @@ -79,12 +81,22 @@ func positionAPIProduceTheFollowingRow(exec Execution, positionService *plugins.
}

if areSamePosition(pos, row) {
if !checkPS {
if !checkFees && !checkFunding && !checkPS {
return nil
}
// check state if required
states, _ := positionService.GetPositionStatesByParty(party)
if len(states) == 1 && states[0] == ps {
match := true
if checkFees {
match = feesMatch(pos, row)
}
if checkFunding && match {
match = fundingMatches(pos, row)
}
if checkPS && match {
// check state if required
states, _ := positionService.GetPositionStatesByParty(party)
match = len(states) == 1 && states[0] == ps
}
if match {
return nil
}
}
Expand All @@ -109,15 +121,19 @@ func errProfitAndLossValuesForParty(pos []*types.Position, row pnlRow) error {
}
return formatDiff(
fmt.Sprintf("invalid positions values for party(%v)", row.party()),
row.diffMap(),
map[string]string{
"volume": i64ToS(row.volume()),
"unrealised PNL": row.unrealisedPNL().String(),
"realised PNL": row.realisedPNL().String(),
},
map[string]string{
"volume": i64ToS(pos[0].OpenVolume),
"unrealised PNL": pos[0].UnrealisedPnl.String(),
"realised PNL": pos[0].RealisedPnl.String(),
"volume": i64ToS(pos[0].OpenVolume),
"unrealised PNL": pos[0].UnrealisedPnl.String(),
"realised PNL": pos[0].RealisedPnl.String(),
"taker fees": pos[0].TakerFeesPaid.String(),
"taker fees since": pos[0].TakerFeesPaidSince.String(),
"maker fees": pos[0].MakerFeesReceived.String(),
"maker fees since": pos[0].MakerFeesReceivedSince.String(),
"other fees": pos[0].FeesPaid.String(),
"other fees since": pos[0].FeesPaidSince.String(),
"funding payments": pos[0].FundingPaymentAmount.String(),
"funding payments since": pos[0].FundingPaymentAmountSince.String(),
},
)
}
Expand All @@ -133,6 +149,52 @@ func areSamePosition(pos []*types.Position, row pnlRow) bool {
pos[0].UnrealisedPnl.Equals(row.unrealisedPNL())
}

func feesMatch(pos []*types.Position, row pnlRow) bool {
if len(pos) == 0 {
return false
}
taker, ok := row.takerFees()
if ok && !taker.EQ(pos[0].TakerFeesPaid) {
return false
}
maker, ok := row.makerFees()
if ok && !maker.EQ(pos[0].MakerFeesReceived) {
return false
}
other, ok := row.otherFees()
if ok && !other.EQ(pos[0].FeesPaid) {
return false
}
taker, ok = row.takerFeesSince()
if ok && !taker.EQ(pos[0].TakerFeesPaidSince) {
return false
}
maker, ok = row.makerFeesSince()
if ok && !maker.EQ(pos[0].MakerFeesReceivedSince) {
return false
}
other, ok = row.otherFeesSince()
if ok && !other.EQ(pos[0].FeesPaidSince) {
return false
}
return true
}

func fundingMatches(pos []*types.Position, row pnlRow) bool {
if len(pos) == 0 {
return false
}
fp, ok := row.fundingPayment()
if ok && !fp.EQ(pos[0].FundingPaymentAmount) {
return false
}
fp, ok = row.fundingPaymentSince()
if ok && !fp.EQ(pos[0].FundingPaymentAmountSince) {
return false
}
return true
}

func errCannotGetPositionForParty(party string, err error) error {
return fmt.Errorf("error getting party position, party(%v), err(%v)", party, err)
}
Expand All @@ -147,6 +209,14 @@ func parseProfitAndLossTable(table *godog.Table) []RowWrapper {
"status",
"market id",
"is amm",
"taker fees",
"taker fees since",
"maker fees",
"maker fees since",
"other fees",
"other fees since",
"funding payments",
"funding payments since",
})
}

Expand Down Expand Up @@ -191,3 +261,130 @@ func (r pnlRow) isAMM() bool {
}
return r.row.MustBool("is amm")
}

func (r pnlRow) takerFees() (*num.Uint, bool) {
if !r.row.HasColumn("taker fees") {
return nil, false
}
return r.row.MustUint("taker fees"), true
}

func (r pnlRow) takerFeesSince() (*num.Uint, bool) {
if !r.row.HasColumn("taker fees since") {
return nil, false
}
return r.row.MustUint("taker fees since"), true
}

func (r pnlRow) makerFees() (*num.Uint, bool) {
if !r.row.HasColumn("maker fees") {
return nil, false
}
return r.row.MustUint("maker fees"), true
}

func (r pnlRow) makerFeesSince() (*num.Uint, bool) {
if !r.row.HasColumn("maker fees since") {
return nil, false
}
return r.row.MustUint("maker fees since"), true
}

func (r pnlRow) otherFees() (*num.Uint, bool) {
if !r.row.HasColumn("other fees") {
return nil, false
}
return r.row.MustUint("other fees"), true
}

func (r pnlRow) otherFeesSince() (*num.Uint, bool) {
if !r.row.HasColumn("other fees since") {
return nil, false
}
return r.row.MustUint("other fees since"), true
}

func (r pnlRow) fundingPayment() (*num.Int, bool) {
if !r.row.HasColumn("funding payments") {
return nil, false
}
return r.row.MustInt("funding payments"), true
}

func (r pnlRow) fundingPaymentSince() (*num.Int, bool) {
if !r.row.HasColumn("funding payments since") {
return nil, false
}
return r.row.MustInt("funding payments since"), true
}

func (r pnlRow) checkFees() bool {
if _, taker := r.takerFees(); taker {
return true
}
if _, maker := r.makerFees(); maker {
return true
}
if _, other := r.otherFees(); other {
return true
}
if _, ok := r.takerFeesSince(); ok {
return true
}
if _, ok := r.makerFeesSince(); ok {
return true
}
if _, ok := r.otherFeesSince(); ok {
return true
}
return false
}

func (r pnlRow) checkFunding() bool {
if _, ok := r.fundingPayment(); ok {
return true
}
_, ok := r.fundingPaymentSince()
return ok
}

func (r pnlRow) diffMap() map[string]string {
m := map[string]string{
"volume": i64ToS(r.volume()),
"unrealised PNL": r.unrealisedPNL().String(),
"realised PNL": r.realisedPNL().String(),
"taker fees": "",
"taker fees since": "",
"maker fees": "",
"maker fees since": "",
"other fees": "",
"other fees since": "",
"funding payments": "",
"funding payments since": "",
}
if v, ok := r.takerFees(); ok {
m["taker fees"] = v.String()
}
if v, ok := r.makerFees(); ok {
m["maker fees"] = v.String()
}
if v, ok := r.otherFees(); ok {
m["other fees"] = v.String()
}
if v, ok := r.takerFeesSince(); ok {
m["taker fees since"] = v.String()
}
if v, ok := r.makerFeesSince(); ok {
m["maker fees since"] = v.String()
}
if v, ok := r.otherFeesSince(); ok {
m["other fees since"] = v.String()
}
if v, ok := r.fundingPayment(); ok {
m["funding payments"] = v.String()
}
if v, ok := r.fundingPaymentSince(); ok {
m["funding payments since"] = v.String()
}
return m
}
Loading

0 comments on commit 5abcd2f

Please sign in to comment.