-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10573 from vegaprotocol/10568-pnl-fix
fix: clone amount to prevent subtracting twice, causing underflow
- Loading branch information
Showing
9 changed files
with
332 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
core/integration/features/settlement/10568-pnl-underflow.feature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
Feature: Test loss socialization case 1 | ||
|
||
Background: | ||
Given the markets: | ||
| id | quote name | asset | risk model | margin calculator | auction duration | fees | price monitoring | data source config | linear slippage factor | quadratic slippage factor | sla params | | ||
| ETH/DEC19 | BTC | BTC | default-simple-risk-model-2 | default-margin-calculator | 1 | default-none | default-none | default-eth-for-future | 1e6 | 1e6 | default-futures | | ||
And the following network parameters are set: | ||
| name | value | | ||
| market.auction.minimumDuration | 1 | | ||
| network.markPriceUpdateMaximumFrequency | 0s | | ||
|
||
@LossSocEvts | ||
Scenario: Case 1: trader1 has insufficient MTM & only trader2 socialises the losses (0002-STTL-009) | ||
Description : Case 1 from https://docs.google.com/spreadsheets/d/1CIPH0aQmIKj6YeFW9ApP_l-jwB4OcsNQ/edit#gid=1555964910 | ||
|
||
# setup accounts | ||
Given the parties deposit on asset's general account the following amount: | ||
| party | asset | amount | | ||
| sellSideProvider | BTC | 100000000 | | ||
| buySideProvider | BTC | 100000000 | | ||
| party1 | BTC | 5000 | | ||
| party2 | BTC | 50000 | | ||
| party3 | BTC | 50000 | | ||
| aux1 | BTC | 100000000 | | ||
| aux2 | BTC | 100000000 | | ||
# setup order book | ||
When the parties place the following orders: | ||
| party | market id | side | volume | price | resulting trades | type | tif | reference | | ||
| sellSideProvider | ETH/DEC19 | sell | 1000 | 120 | 0 | TYPE_LIMIT | TIF_GTC | sell-provider-1 | | ||
| buySideProvider | ETH/DEC19 | buy | 1000 | 80 | 0 | TYPE_LIMIT | TIF_GTC | buy-provider-1 | | ||
| aux1 | ETH/DEC19 | sell | 1 | 120 | 0 | TYPE_LIMIT | TIF_GTC | aux-s-1 | | ||
| aux2 | ETH/DEC19 | buy | 1 | 80 | 0 | TYPE_LIMIT | TIF_GTC | aux-b-1 | | ||
| aux1 | ETH/DEC19 | sell | 1 | 100 | 0 | TYPE_LIMIT | TIF_GTC | aux-s-2 | | ||
| aux2 | ETH/DEC19 | buy | 1 | 100 | 0 | TYPE_LIMIT | TIF_GTC | aux-b-2 | | ||
Then the opening auction period ends for market "ETH/DEC19" | ||
And the mark price should be "100" for the market "ETH/DEC19" | ||
And the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC19" | ||
# party 1 place an order + we check margins | ||
When the parties place the following orders with ticks: | ||
| party | market id | side | volume | price | resulting trades | type | tif | reference | | ||
| party1 | ETH/DEC19 | sell | 100 | 100 | 0 | TYPE_LIMIT | TIF_GTC | ref-1 | | ||
Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC19" | ||
# then party2 place an order, and we calculate the margins again | ||
When the parties place the following orders with ticks: | ||
| party | market id | side | volume | price | resulting trades | type | tif | reference | | ||
| party2 | ETH/DEC19 | buy | 100 | 100 | 1 | TYPE_LIMIT | TIF_GTC | ref-1 | | ||
Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC19" | ||
# then we change the volume in the book | ||
Then the parties cancel the following orders: | ||
| party | reference | | ||
| sellSideProvider | sell-provider-1 | | ||
| buySideProvider | buy-provider-1 | | ||
Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC19" | ||
When the parties place the following orders with ticks: | ||
| party | market id | side | volume | price | resulting trades | type | tif | reference | | ||
| sellSideProvider | ETH/DEC19 | sell | 1000 | 200 | 0 | TYPE_LIMIT | TIF_GTC | sell-provider-2 | | ||
| buySideProvider | ETH/DEC19 | buy | 1000 | 80 | 0 | TYPE_LIMIT | TIF_GTC | buy-provider-2 | | ||
Then the parties cancel the following orders: | ||
| party | reference | | ||
| aux1 | aux-s-1 | | ||
| aux2 | aux-b-1 | | ||
Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC19" | ||
When the parties place the following orders with ticks: | ||
| party | market id | side | volume | price | resulting trades | type | tif | reference | | ||
| party2 | ETH/DEC19 | buy | 100 | 180 | 0 | TYPE_LIMIT | TIF_GTC | ref-1 | | ||
| party3 | ETH/DEC19 | sell | 100 | 180 | 1 | TYPE_LIMIT | TIF_GTC | ref-2 | | ||
Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC19" | ||
Then the parties should have the following profit and loss: | ||
| party | volume | unrealised pnl | realised pnl | | ||
| party1 | 0 | 0 | -5000 | | ||
| party2 | 200 | 8000 | -2970 | | ||
| party3 | -100 | 0 | 0 | | ||
| aux2 | 1 | 80 | -30 | | ||
And the insurance pool balance should be "0" for the market "ETH/DEC19" | ||
And debug loss socialisation events | ||
And the cumulated balance for all accounts should be worth "400105000" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
core/integration/steps/the_loss_socialisation_amount_is.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// 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 steps | ||
|
||
import ( | ||
"fmt" | ||
|
||
"code.vegaprotocol.io/vega/core/events" | ||
"code.vegaprotocol.io/vega/core/integration/stubs" | ||
"code.vegaprotocol.io/vega/libs/num" | ||
"code.vegaprotocol.io/vega/logging" | ||
|
||
"github.com/cucumber/godog" | ||
) | ||
|
||
func TheLossSocialisationAmountsAre(broker *stubs.BrokerStub, table *godog.Table) error { | ||
lsMkt := getLossSocPerMarket(broker) | ||
for _, r := range parseLossSocTable(table) { | ||
lsr := lossSocRow{r: r} | ||
mevts, ok := lsMkt[lsr.Market()] | ||
if !ok { | ||
return fmt.Errorf("no loss socialisation events found for market %s", lsr.Market()) | ||
} | ||
parties := map[string]struct{}{} | ||
for _, e := range mevts { | ||
if lsr.Amount().EQ(e.Amount()) { | ||
parties[e.PartyID()] = struct{}{} | ||
} | ||
} | ||
if c := lsr.Count(); c != -1 { | ||
if len(parties) != c { | ||
return fmt.Errorf("expected %d loss socialisation events for market %s and amount %s, instead found %d", c, lsr.Market(), lsr.Amount().String(), len(parties)) | ||
} | ||
} | ||
for _, p := range lsr.Party() { | ||
if _, ok := parties[p]; !ok { | ||
return fmt.Errorf("no loss socialisation found for party %s on market %s for amount %s", p, lsr.Market(), lsr.Amount().String()) | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func DebugLossSocialisationEvents(broker *stubs.BrokerStub, log *logging.Logger) error { | ||
lsEvts := getLossSocPerMarket(broker) | ||
for mkt, evts := range lsEvts { | ||
log.Infof("\nLoss socialisation events for market %s:", mkt) | ||
for _, e := range evts { | ||
log.Infof( | ||
"Party: %s - Amount: %s", | ||
e.PartyID(), | ||
e.Amount().String(), | ||
) | ||
} | ||
log.Info("----------------------------------------------------------------------------") | ||
} | ||
return nil | ||
} | ||
|
||
func getLossSocPerMarket(broker *stubs.BrokerStub) map[string][]*events.LossSoc { | ||
evts := broker.GetLossSoc() | ||
ret := map[string][]*events.LossSoc{} | ||
for _, e := range evts { | ||
mkt := e.MarketID() | ||
mevts, ok := ret[mkt] | ||
if !ok { | ||
mevts = []*events.LossSoc{} | ||
} | ||
ret[mkt] = append(mevts, e) | ||
} | ||
return ret | ||
} | ||
|
||
func parseLossSocTable(table *godog.Table) []RowWrapper { | ||
return StrictParseTable(table, []string{ | ||
"market", | ||
"amount", | ||
}, []string{ | ||
"party", | ||
"count", | ||
}) | ||
} | ||
|
||
type lossSocRow struct { | ||
r RowWrapper | ||
} | ||
|
||
func (l lossSocRow) Market() string { | ||
return l.r.MustStr("market") | ||
} | ||
|
||
func (l lossSocRow) Amount() *num.Int { | ||
return l.r.MustInt("amount") | ||
} | ||
|
||
func (l lossSocRow) Party() []string { | ||
if l.r.HasColumn("party") { | ||
return l.r.MustStrSlice("party", ",") | ||
} | ||
return nil | ||
} | ||
|
||
func (l lossSocRow) Count() int { | ||
if l.r.HasColumn("count") { | ||
return int(l.r.MustI64("count")) | ||
} | ||
return -1 | ||
} |
Oops, something went wrong.