Skip to content

Commit

Permalink
Merge pull request #11305 from vegaprotocol/11304
Browse files Browse the repository at this point in the history
fix: Correctly verify pegged order offset with respect to tick size i…
  • Loading branch information
jeremyletang committed May 22, 2024
1 parent 230591d commit 438132a
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

- [11293](https://github.com/vegaprotocol/vega/issues/11293) - Panic in data node with position estimate endpoint.
- [11297](https://github.com/vegaprotocol/vega/issues/11297) - Handle properly asset decimals < market decimals when uncrossing the order book upon leaving auction.
- [11304](https://github.com/vegaprotocol/vega/issues/11304) - Correctly verify pegged order offset with respect to tick size in the right units.


## 0.76.3
Expand Down
22 changes: 14 additions & 8 deletions core/execution/future/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -1374,26 +1374,30 @@ func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) {
return num.UintZero(), common.ErrUnableToReprice
}

offset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor))
// we're converting both offset and tick size to asset decimals so we can adjust the price (in asset) directly
priceInMarket, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor))
if order.Side == types.SideSell {
price = price.AddSum(offset)
priceInMarket.AddSum(order.PeggedOrder.Offset)
// this can only happen when pegged to mid, in which case we want to round to the nearest *better* tick size
// but this can never cross the mid by construction as the the minimum offset is 1 tick size and all prices must be
// whole multiples of tick size.
if mod := num.UintZero().Mod(price, m.mkt.TickSize); !mod.IsZero() {
price.Sub(price, mod)
if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
priceInMarket.Sub(priceInMarket, mod)
}
price, _ := num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))

return price, nil
}

if price.LTE(offset) {
if priceInMarket.LTE(order.PeggedOrder.Offset) {
return num.UintZero(), common.ErrUnableToReprice
}

price.Sub(price, offset)
if mod := num.UintZero().Mod(price, m.mkt.TickSize); !mod.IsZero() {
price = num.UintZero().Sub(price.AddSum(m.mkt.TickSize), mod)
priceInMarket.Sub(priceInMarket, order.PeggedOrder.Offset)
if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
priceInMarket = num.UintZero().Sub(priceInMarket.AddSum(m.mkt.TickSize), mod)
}
price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))

return price, nil
}
Expand Down Expand Up @@ -1792,6 +1796,8 @@ func (m *Market) validateOrder(ctx context.Context, order *types.Order) (err err
return nil
}

// validateOrder checks that the order parameters are valid for the market.
// NB: price in market, tickSize in market decimals.
func (m *Market) validateTickSize(price *num.Uint) error {
d := num.UintZero().Mod(price, m.mkt.TickSize)
if !d.IsZero() {
Expand Down
20 changes: 12 additions & 8 deletions core/execution/spot/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -819,26 +819,29 @@ func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) {
return num.UintZero(), common.ErrUnableToReprice
}

offset, _ := num.UintFromDecimal(order.PeggedOrder.Offset.ToDecimal().Mul(m.priceFactor))
// we're converting both offset and tick size to asset decimals so we can adjust the price (in asset) directly
priceInMarket, _ := num.UintFromDecimal(price.ToDecimal().Div(m.priceFactor))
if order.Side == types.SideSell {
price = price.AddSum(offset)
priceInMarket.AddSum(order.PeggedOrder.Offset)
// this can only happen when pegged to mid, in which case we want to round to the nearest *better* tick size
// but this can never cross the mid by construction as the the minimum offset is 1 tick size and all prices must be
// whole multiples of tick size.
if mod := num.UintZero().Mod(price, m.mkt.TickSize); !mod.IsZero() {
price.Sub(price, mod)
if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
priceInMarket.Sub(priceInMarket, mod)
}
price, _ := num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))
return price, nil
}

if price.LTE(offset) {
if priceInMarket.LTE(order.PeggedOrder.Offset) {
return num.UintZero(), common.ErrUnableToReprice
}

price.Sub(price, offset)
if mod := num.UintZero().Mod(price, m.mkt.TickSize); !mod.IsZero() {
price = num.UintZero().Sub(price.AddSum(m.mkt.TickSize), mod)
priceInMarket.Sub(priceInMarket, order.PeggedOrder.Offset)
if mod := num.UintZero().Mod(priceInMarket, m.mkt.TickSize); !mod.IsZero() {
priceInMarket = num.UintZero().Sub(priceInMarket.AddSum(m.mkt.TickSize), mod)
}
price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor))

return price, nil
}
Expand Down Expand Up @@ -971,6 +974,7 @@ func (m *Market) leaveAuction(ctx context.Context, now time.Time) {
}

// validateOrder checks that the order parameters are valid for the market.
// NB: price in market, tickSize in market decimals.
func (m *Market) validateOrder(ctx context.Context, order *types.Order) (err error) {
defer func() {
if err != nil {
Expand Down
89 changes: 89 additions & 0 deletions core/integration/features/orders/0037-OPEG-022.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@

Feature: 0001 tick size should be using market decimal, A pegged order specifying an offset which is not an integer multiple of the markets tick size should be rejected.

Background:
Given the following network parameters are set:
| name | value |
| market.liquidity.bondPenaltyParameter | 1 |
| network.markPriceUpdateMaximumFrequency | 0s |
| limits.markets.maxPeggedOrders | 6 |
| validators.epoch.length | 5s |
| market.liquidity.earlyExitPenalty | 0.25 |
| market.liquidity.stakeToCcyVolume | 1.0 |
| market.liquidity.sla.nonPerformanceBondPenaltySlope | 0.19 |
| market.liquidity.sla.nonPerformanceBondPenaltyMax | 1 |

And the liquidity monitoring parameters:
| name | triggering ratio | time window | scaling factor |
| lqm-params | 0.1 | 24h | 1 |

And the following assets are registered:
| id | decimal places |
| ETH | 1 |

And the average block duration is "1"
And the simple risk model named "simple-risk-model-1":
| long | short | max move up | min move down | probability of trading |
| 0.1 | 0.1 | 60 | 50 | 0.2 |
And the fees configuration named "fees-config-1":
| maker fee | infrastructure fee |
| 0.004 | 0.001 |
And the price monitoring named "price-monitoring-1":
| horizon | probability | auction extension |
| 1 | 0.99 | 5 |
And the liquidity sla params named "SLA":
| price range | commitment min time fraction | performance hysteresis epochs | sla competition factor |
| 0.01 | 0.5 | 1 | 1.0 |

Scenario:
#0037-OPEG-022:Given a market with non-zero market and asset decimals where the asset decimals are strictly less than the market decimals (yielding a negative price factor). A pegged order specifying an offset which is not an integer multiple of the markets tick size should be rejected.
#0037-OPEG-025:Given a market with non-zero market and asset decimals where the asset decimals are equal to the market decimals (yielding a negative price factor). A pegged order specifying an offset which is not an integer multiple of the markets tick size should be rejected.
#0037-OPEG-026:Given a market with non-zero market and asset decimals where the asset decimals are strictly more than the market decimals (yielding a negative price factor). A pegged order specifying an offset which is not an integer multiple of the markets tick size should be rejected.
And the markets:
| id | quote name | asset | liquidity monitoring | risk model | margin calculator | auction duration | fees | price monitoring | data source config | linear slippage factor | quadratic slippage factor | sla params | decimal places | tick size |
| ETH/DEC21 | ETH | ETH | lqm-params | simple-risk-model-1 | default-margin-calculator | 1 | fees-config-1 | price-monitoring-1 | default-eth-for-future | 0.5 | 0 | SLA | 2 | 10 |
| ETH/DEC22 | ETH | ETH | lqm-params | simple-risk-model-1 | default-margin-calculator | 1 | fees-config-1 | price-monitoring-1 | default-eth-for-future | 0.5 | 0 | SLA | 2 | 5 |
| ETH/DEC23 | ETH | ETH | lqm-params | simple-risk-model-1 | default-margin-calculator | 1 | fees-config-1 | price-monitoring-1 | default-eth-for-future | 0.5 | 0 | SLA | 1 | 5 |
| ETH/DEC24 | ETH | ETH | lqm-params | simple-risk-model-1 | default-margin-calculator | 1 | fees-config-1 | price-monitoring-1 | default-eth-for-future | 0.5 | 0 | SLA | 0 | 10 |
And the parties deposit on asset's general account the following amount:
| party | asset | amount |
| party1 | ETH | 100000 |
| party3 | ETH | 1000000 |
| party4 | ETH | 1000000 |
And the average block duration is "1"

And the parties place the following orders:
| party | market id | side | volume | price | resulting trades | type | tif | reference | error |
| party3 | ETH/DEC21 | buy | 100 | 101 | 0 | TYPE_LIMIT | TIF_GTC | p3b1-1 | OrderError: price not in tick size |
| party3 | ETH/DEC21 | buy | 10 | 111 | 0 | TYPE_LIMIT | TIF_GTC | p3b2-1 | OrderError: price not in tick size |
| party4 | ETH/DEC21 | sell | 10 | 111 | 0 | TYPE_LIMIT | TIF_GTC | p4s2-1 | OrderError: price not in tick size |
| party4 | ETH/DEC21 | sell | 1000 | 191 | 0 | TYPE_LIMIT | TIF_GTC | p4s1-1 | OrderError: price not in tick size |
| party3 | ETH/DEC22 | buy | 100 | 101 | 0 | TYPE_LIMIT | TIF_GTC | p3b1-2 | OrderError: price not in tick size |
| party3 | ETH/DEC22 | buy | 10 | 111 | 0 | TYPE_LIMIT | TIF_GTC | p3b2-2 | OrderError: price not in tick size |
| party4 | ETH/DEC22 | sell | 10 | 111 | 0 | TYPE_LIMIT | TIF_GTC | p4s2-2 | OrderError: price not in tick size |
| party4 | ETH/DEC22 | sell | 1000 | 191 | 0 | TYPE_LIMIT | TIF_GTC | p4s1-2 | OrderError: price not in tick size |
| party3 | ETH/DEC23 | buy | 100 | 101 | 0 | TYPE_LIMIT | TIF_GTC | p3b1-3 | OrderError: price not in tick size |
| party3 | ETH/DEC23 | buy | 10 | 111 | 0 | TYPE_LIMIT | TIF_GTC | p3b2-3 | OrderError: price not in tick size |
| party4 | ETH/DEC23 | sell | 10 | 111 | 0 | TYPE_LIMIT | TIF_GTC | p4s2-3 | OrderError: price not in tick size |
| party4 | ETH/DEC23 | sell | 1000 | 191 | 0 | TYPE_LIMIT | TIF_GTC | p4s1-3 | OrderError: price not in tick size |
| party3 | ETH/DEC24 | buy | 100 | 101 | 0 | TYPE_LIMIT | TIF_GTC | p3b1-4 | OrderError: price not in tick size |
| party3 | ETH/DEC24 | buy | 10 | 111 | 0 | TYPE_LIMIT | TIF_GTC | p3b2-4 | OrderError: price not in tick size |
| party4 | ETH/DEC24 | sell | 10 | 111 | 0 | TYPE_LIMIT | TIF_GTC | p4s2-4 | OrderError: price not in tick size |
| party4 | ETH/DEC24 | sell | 1000 | 191 | 0 | TYPE_LIMIT | TIF_GTC | p4s1-4 | OrderError: price not in tick size |

Then the network moves ahead "2" blocks
And the trading mode should be "TRADING_MODE_OPENING_AUCTION" for the market "ETH/DEC21"
And the trading mode should be "TRADING_MODE_OPENING_AUCTION" for the market "ETH/DEC22"
And the trading mode should be "TRADING_MODE_OPENING_AUCTION" for the market "ETH/DEC23"
And the trading mode should be "TRADING_MODE_OPENING_AUCTION" for the market "ETH/DEC24"

And the parties place the following pegged iceberg orders:
| party | market id | peak size | minimum visible size | side | pegged reference | volume | offset | reference | error |
| party1 | ETH/DEC21 | 10 | 5 | buy | MID | 10 | 10 | peg-buy-1 | |
| party1 | ETH/DEC21 | 10 | 5 | sell | MID | 20 | 20 | peg-buy-2 | |
| party1 | ETH/DEC21 | 10 | 5 | buy | MID | 20 | 100 | peg-buy-3 | |
| party1 | ETH/DEC21 | 10 | 5 | sell | MID | 20 | 2 | peg-buy-4 | OrderError: price not in tick size |
| party1 | ETH/DEC21 | 10 | 5 | buy | MID | 20 | 5 | peg-buy-5 | OrderError: price not in tick size |
| party1 | ETH/DEC22 | 10 | 5 | buy | MID | 20 | 15 | peg-buy-6 | |
| party1 | ETH/DEC23 | 10 | 5 | sell | MID | 20 | 6 | peg-buy-7 | OrderError: price not in tick size |
| party1 | ETH/DEC24 | 10 | 5 | buy | MID | 20 | 17 | peg-buy-8 | OrderError: price not in tick size |
Loading

0 comments on commit 438132a

Please sign in to comment.