Skip to content

Commit

Permalink
feat: add tick size
Browse files Browse the repository at this point in the history
  • Loading branch information
ze97286 committed Feb 27, 2024
1 parent cfb23a5 commit c90f75e
Show file tree
Hide file tree
Showing 44 changed files with 2,188 additions and 1,273 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### 🚨 Breaking changes

- [](https://github.com/vegaprotocol/vega/issues/xxx)
- [10635](https://github.com/vegaprotocol/vega/issues/10635) - Add support for tick size

### 🗑️ Deprecation

Expand Down
23 changes: 23 additions & 0 deletions commands/proposal_submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ func checkNewSpotMarketConfiguration(changes *vegapb.NewSpotMarketConfiguration)
errs.Merge(checkNewInstrument(changes.Instrument, "new_spot_market.changes.instrument"))
errs.Merge(checkNewSpotRiskParameters(changes))
errs.Merge(checkSLAParams(changes.SlaParams, "new_spot_market.changes.sla_params"))
errs.Merge(checkTickSize(changes.TickSize, "new_spot_market.changes"))

return errs
}
Expand Down Expand Up @@ -918,6 +919,26 @@ func checkNewMarketChangesConfiguration(changes *vegapb.NewMarketConfiguration)
errs.Merge(checkSLAParams(changes.LiquiditySlaParameters, "new_market.changes.sla_params"))
errs.Merge(checkLiquidityFeeSettings(changes.LiquidityFeeSettings, "new_market.changes.liquidity_fee_settings"))
errs.Merge(checkCompositePriceConfiguration(changes.MarkPriceConfiguration, "new_market.changes.mark_price_configuration"))
errs.Merge(checkTickSize(changes.TickSize, "new_market.changes"))

return errs
}

func checkTickSize(tickSize string, parent string) Errors {
errs := NewErrors()

if len(tickSize) == 0 {
errs.AddForProperty(fmt.Sprintf("%s.tick_size", parent), ErrIsRequired)
return errs
}

tickSizeU, overflow := num.UintFromString(tickSize, 10)
if overflow {
errs.AddForProperty(fmt.Sprintf("%s.tick_size", parent), ErrNotAValidInteger)
} else if tickSizeU.IsZero() || tickSizeU.IsNegative() {
errs.AddForProperty(fmt.Sprintf("%s.tick_size", parent), ErrMustBePositive)
}

return errs
}

Expand Down Expand Up @@ -965,6 +986,7 @@ func checkUpdateMarket(updateMarket *vegapb.UpdateMarket) Errors {
errs.Merge(checkSLAParams(changes.LiquiditySlaParameters, "update_market.changes.sla_params"))
errs.Merge(checkLiquidityFeeSettings(changes.LiquidityFeeSettings, "update_market.changes.liquidity_fee_settings"))
errs.Merge(checkCompositePriceConfiguration(changes.MarkPriceConfiguration, "update_market.changes.mark_price_configuration"))
errs.Merge(checkTickSize(changes.TickSize, "update_market.changes"))
return errs
}

Expand Down Expand Up @@ -995,6 +1017,7 @@ func checkUpdateSpotMarket(updateSpotMarket *vegapb.UpdateSpotMarket) Errors {
errs.Merge(checkTargetStakeParams(changes.TargetStakeParameters, "update_spot_market.changes"))
errs.Merge(checkUpdateSpotRiskParameters(changes))
errs.Merge(checkSLAParams(changes.SlaParams, "update_spot_market.changes.sla_params"))
errs.Merge(checkTickSize(changes.TickSize, "update_spot_market.changes"))
return errs
}

Expand Down
39 changes: 39 additions & 0 deletions commands/proposal_submission_new_market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,45 @@ func TestCheckProposalSubmissionForNewMarket(t *testing.T) {
t.Run("Submitting a new market with valid hysteresis epochs succeeds", testNewMarketChangeSubmissionWithValidPerformanceHysteresisEpochsSucceeds)
t.Run("Submitting a new market with invalid liquidity fee settings", testLiquidityFeeSettings)
t.Run("Submitting a new market with invalid mark price configuration ", testCompositePriceConfiguration)
t.Run("Submitting a new market with invalid tick size fails and with valid tick size succeeds", testNewMarketTickSize)
}

type tickSizeCase struct {
tickSize string
err error
}

func getTickSizeCases() []tickSizeCase {
return []tickSizeCase{
{tickSize: "", err: commands.ErrIsRequired},
{tickSize: "banana", err: commands.ErrNotAValidInteger},
{tickSize: "-1", err: commands.ErrMustBePositive},
{tickSize: "0", err: commands.ErrMustBePositive},
{tickSize: "1", err: nil},
{tickSize: "123", err: nil},
}
}

func testNewMarketTickSize(t *testing.T) {
cases := getTickSizeCases()
for _, tsc := range cases {
err := checkProposalSubmission(&commandspb.ProposalSubmission{
Terms: &vegapb.ProposalTerms{
Change: &vegapb.ProposalTerms_NewMarket{
NewMarket: &vegapb.NewMarket{
Changes: &vegapb.NewMarketConfiguration{
TickSize: tsc.tickSize,
},
},
},
},
})
if tsc.err != nil {
assert.Contains(t, err.Get("proposal_submission.terms.change.new_market.changes.tick_size"), tsc.err)
} else {
assert.Empty(t, err.Get("proposal_submission.terms.change.new_market.changes.tick_size"))
}
}
}

func testNewMarketChangeSubmissionWithoutNewMarketFails(t *testing.T) {
Expand Down
27 changes: 27 additions & 0 deletions commands/proposal_submission_new_spot_market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,33 @@ func TestCheckProposalSubmissionForNewSpotMarket(t *testing.T) {
t.Run("Submitting a new spot market with valid competition factor succeeds", testNewSpotMarketChangeSubmissionWithValidCompetitionFactorSucceeds)
t.Run("Submitting a new spot market with invalid hysteresis epochs fails", testNewSpotMarketChangeSubmissionWithInvalidPerformanceHysteresisEpochsFails)
t.Run("Submitting a new spot market with valid hysteresis epochs succeeds", testNewSpotMarketChangeSubmissionWithValidPerformanceHysteresisEpochsSucceeds)

t.Run("Submitting a new spot market with invalid tick size fails and valid tick size succeeds", testNewSpotMarketTickSize)
}

func testNewSpotMarketTickSize(t *testing.T) {
cases := getTickSizeCases()
for _, tsc := range cases {
err := checkProposalSubmission(&commandspb.ProposalSubmission{
Terms: &vegapb.ProposalTerms{
Change: &vegapb.ProposalTerms_NewSpotMarket{
NewSpotMarket: &vegapb.NewSpotMarket{
Changes: &vegapb.NewSpotMarketConfiguration{
Instrument: &protoTypes.InstrumentConfiguration{
Product: &protoTypes.InstrumentConfiguration_Spot{},
},
TickSize: tsc.tickSize,
},
},
},
},
})
if tsc.err != nil {
assert.Contains(t, err.Get("proposal_submission.terms.change.new_spot_market.changes.tick_size"), tsc.err)
} else {
assert.Empty(t, err.Get("proposal_submission.terms.change.new_spot_market.changes.tick_size"))
}
}
}

func testNewSpotMarketChangeSubmissionWithoutNewSpotMarketFails(t *testing.T) {
Expand Down
25 changes: 24 additions & 1 deletion commands/proposal_submission_update_market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,30 @@ func TestCheckProposalSubmissionForUpdateMarket(t *testing.T) {
t.Run("Submitting a perps market product parameters", testUpdatePerpsMarketChangeSubmissionProductParameters)
t.Run("Submitting a perps market with funding rate modifiers", testUpdatePerpetualMarketWithFundingRateModifiers)
t.Run("Submitting a market update with invalid mark price configuration ", testUpdateMarketCompositePriceConfiguration)
t.Run("Submitting a market update with invalid intenal composite price configuration ", testUpdatePerpsMarketChangeSubmissionWithInternalCompositePriceConfig)
t.Run("Submitting a market update with invalid intenal composite price configuration", testUpdatePerpsMarketChangeSubmissionWithInternalCompositePriceConfig)
t.Run("Submitting a market update with invalid tick size fails and valid tick size succeeds", testUpdateMarketTickSize)
}

func testUpdateMarketTickSize(t *testing.T) {
cases := getTickSizeCases()
for _, tsc := range cases {
err := checkProposalSubmission(&commandspb.ProposalSubmission{
Terms: &vegapb.ProposalTerms{
Change: &vegapb.ProposalTerms_UpdateMarket{
UpdateMarket: &vegapb.UpdateMarket{
Changes: &vegapb.UpdateMarketConfiguration{
TickSize: tsc.tickSize,
},
},
},
},
})
if tsc.err != nil {
assert.Contains(t, err.Get("proposal_submission.terms.change.update_market.changes.tick_size"), tsc.err)
} else {
assert.Empty(t, err.Get("proposal_submission.terms.change.update_market.changes.tick_size"))
}
}
}

func testUpdatePerpsMarketChangeSubmissionWithInternalCompositePriceConfig(t *testing.T) {
Expand Down
23 changes: 23 additions & 0 deletions commands/proposal_submission_update_spot_market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,29 @@ func TestCheckProposalSubmissionForUpdateSpotMarket(t *testing.T) {
t.Run("Submitting a spot market update with valid competition factor succeeds", testUpdateSpotMarketChangeSubmissionWithValidCompetitionFactorSucceeds)
t.Run("Submitting a spot market update with invalid hysteresis epochs fails", testUpdateSpotMarketChangeSubmissionWithInvalidPerformanceHysteresisEpochsFails)
t.Run("Submitting a spot market update with valid hysteresis epochs succeeds", testUpdateSpotMarketChangeSubmissionWithValidPerformanceHysteresisEpochsSucceeds)
t.Run("Submitting a spot market update with invalid tick size fails and valid tick size succeeds", testUpdateSpotMarketTickSize)
}

func testUpdateSpotMarketTickSize(t *testing.T) {
cases := getTickSizeCases()
for _, tsc := range cases {
err := checkProposalSubmission(&commandspb.ProposalSubmission{
Terms: &protoTypes.ProposalTerms{
Change: &protoTypes.ProposalTerms_UpdateSpotMarket{
UpdateSpotMarket: &protoTypes.UpdateSpotMarket{
Changes: &protoTypes.UpdateSpotMarketConfiguration{
TickSize: tsc.tickSize,
},
},
},
},
})
if tsc.err != nil {
assert.Contains(t, err.Get("proposal_submission.terms.change.update_spot_market.changes.tick_size"), tsc.err)
} else {
assert.Empty(t, err.Get("proposal_submission.terms.change.update_spot_market.changes.tick_size"))
}
}
}

func testUpdateSpotMarketChangeSubmissionWithoutUpdateMarketFails(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions core/execution/engine_snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ func getSpotMarketConfig() *types.Market {
SourceStalenessTolerance: []time.Duration{0, 0, 0, 0},
CompositePriceType: types.CompositePriceTypeByLastTrade,
},
TickSize: num.UintOne(),
}
}

Expand Down Expand Up @@ -387,6 +388,7 @@ func getMarketConfig() *types.Market {
SourceStalenessTolerance: []time.Duration{0, 0, 0, 0},
CompositePriceType: types.CompositePriceTypeByLastTrade,
},
TickSize: num.UintOne(),
}
}

Expand Down
37 changes: 35 additions & 2 deletions core/execution/future/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -1325,14 +1325,28 @@ func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) {

offset := num.UintZero().Mul(order.PeggedOrder.Offset, m.priceFactor)
if order.Side == types.SideSell {
return price.AddSum(offset), nil
price = price.AddSum(offset)
if !num.UintZero().Mod(price, m.mkt.TickSize).IsZero() {
price = price.Div(price.AddSum(m.mkt.TickSize), m.mkt.TickSize)
price = price.Mul(price, m.mkt.TickSize)
}
return price, nil
}

if price.LTE(offset) {
return num.UintZero(), common.ErrUnableToReprice
}

return num.UintZero().Sub(price, offset), nil
price.Sub(price, offset)
if !num.UintZero().Mod(price, m.mkt.TickSize).IsZero() {
price.Div(price, m.mkt.TickSize)
price.Mul(price, m.mkt.TickSize)
if price.LTE(offset) {
return num.UintZero(), common.ErrUnableToReprice
}
}

return price, nil
}

// Reprice a pegged order. This only updates the price on the order.
Expand Down Expand Up @@ -1700,8 +1714,21 @@ func (m *Market) validateOrder(ctx context.Context, order *types.Order) (err err
}
return reason
}
return m.validateTickSize(order.PeggedOrder.Offset)
}

if order.OriginalPrice != nil {
return m.validateTickSize(order.OriginalPrice)
}

return nil
}

func (m *Market) validateTickSize(price *num.Uint) error {
d := num.UintZero().Mod(price, m.mkt.TickSize)
if !d.IsZero() {
return types.ErrOrderNotInTickSize
}
return nil
}

Expand Down Expand Up @@ -3520,6 +3547,12 @@ func (m *Market) amendOrder(
return nil, nil, err
}

if orderAmendment.Price != nil && amendedOrder.OriginalPrice != nil {
if err = m.validateTickSize(amendedOrder.OriginalPrice); err != nil {
return nil, nil, err
}
}

if err := m.position.ValidateAmendOrder(existingOrder, amendedOrder); err != nil {
return nil, nil, err
}
Expand Down
77 changes: 77 additions & 0 deletions core/execution/future/market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,7 @@ func getMarketWithDP(pMonitorSettings *types.PriceMonitoringSettings, openingAuc
CashAmount: num.UintZero(),
CompositePriceType: types.CompositePriceTypeByLastTrade,
},
TickSize: num.UintOne(),
}

return mkt
Expand Down Expand Up @@ -1379,6 +1380,82 @@ func TestSubmittedOrderIdIsTheDeterministicId(t *testing.T) {
assert.Equal(t, event.Order().Id, deterministicID)
}

func TestSubmitOrderWithInvalidTickSize(t *testing.T) {
now := time.Unix(10, 0)
closingAt := time.Unix(20, 0)
tm := getTestMarket(t, now, nil, nil)
tm.mktCfg.TickSize = num.NewUint(1000)
defer tm.ctrl.Finish()

party1 := "party1"
order := &types.Order{
Type: types.OrderTypeLimit,
TimeInForce: types.OrderTimeInForceGTT,
Status: types.OrderStatusActive,
ID: "",
Side: types.SideBuy,
Party: party1,
MarketID: tm.market.GetID(),
Size: 100,
Price: num.NewUint(1100),
Remaining: 100,
CreatedAt: now.UnixNano(),
ExpiresAt: closingAt.UnixNano(),
Reference: "party1-buy-order",
}
addAccount(t, tm, party1)

deterministicID := vgcrypto.RandomHash()
_, err := tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), order.Party, deterministicID)
require.Error(t, types.ErrOrderNotInTickSize, err)

tm.mktCfg.TickSize = num.NewUint(100)
_, err = tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), order.Party, deterministicID)
require.NoError(t, err)
}

func TestAmendOrderWithInvalidTickSize(t *testing.T) {
now := time.Unix(10, 0)
closingAt := time.Unix(20, 0)
tm := getTestMarket(t, now, nil, nil)
tm.mktCfg.TickSize = num.NewUint(100)
defer tm.ctrl.Finish()

party1 := "party1"
order := &types.Order{
Type: types.OrderTypeLimit,
TimeInForce: types.OrderTimeInForceGTT,
Status: types.OrderStatusActive,
ID: "",
Side: types.SideBuy,
Party: party1,
MarketID: tm.market.GetID(),
Size: 100,
Price: num.NewUint(100),
Remaining: 100,
CreatedAt: now.UnixNano(),
ExpiresAt: closingAt.UnixNano(),
Reference: "party1-buy-order",
}
addAccount(t, tm, party1)

deterministicID := vgcrypto.RandomHash()
conf, err := tm.market.Market.SubmitOrder(context.Background(), order.IntoSubmission(), order.Party, deterministicID)
require.NoError(t, err)

orderAmendment := &types.OrderAmendment{
OrderID: conf.Order.ID,
MarketID: conf.Order.MarketID,
Price: num.NewUint(1150),
}
_, err = tm.market.Market.AmendOrder(context.Background(), orderAmendment, party1, deterministicID)
require.Error(t, types.ErrOrderNotInTickSize, err)

tm.mktCfg.TickSize = num.NewUint(50)
_, err = tm.market.Market.AmendOrder(context.Background(), orderAmendment, party1, deterministicID)
require.NoError(t, err)
}

func TestMarketWithTradeClosing(t *testing.T) {
party1 := "party1"
party2 := "party2"
Expand Down
1 change: 1 addition & 0 deletions core/execution/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ func newMarket(ID string, pubKey *dstypes.SignerPubKey) *types.Market {
SourceStalenessTolerance: []time.Duration{0, 0, 0, 0},
CompositePriceType: types.CompositePriceTypeByLastTrade,
},
TickSize: num.UintOne(),
}
}

Expand Down
Loading

0 comments on commit c90f75e

Please sign in to comment.