Skip to content

Commit

Permalink
Merge pull request #10006 from vegaprotocol/liquidation-strategy
Browse files Browse the repository at this point in the history
Liquidation strategy
  • Loading branch information
EVODelavega authored Nov 7, 2023
2 parents e0d7b88 + b4040e7 commit 5877f4e
Show file tree
Hide file tree
Showing 28 changed files with 2,224 additions and 821 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/xxxx) -
- [9945](https://github.com/vegaprotocol/vega/issues/9945) - Add liquidation strategy.

### 🗑️ Deprecation

Expand Down
1 change: 1 addition & 0 deletions commands/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ var (
ErrMustBeLessThen366 = errors.New("must be less then 366")
ErrMustBeAtMost500 = errors.New("must be at most 500")
ErrMustBeSetTo0IfSizeSet = errors.New("must be set to 0 if the property \"order_amendment.size\" is set")
ErrMustBeAtMost3600 = errors.New("must be at most 3600")
ErrMustBeWithinRangeGT0LT20 = errors.New("price range must be strictly greater than 0 and less than or equal to 20")
)

Expand Down
25 changes: 25 additions & 0 deletions commands/proposal_submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,7 @@ func checkNewMarketChanges(change *protoTypes.ProposalTerms_NewMarket) Errors {
}
}

errs.Merge(checkLiquidationStrategy(changes.LiquidationStrategy, "proposal_submission.terms.change.new_market.changes"))
errs.Merge(checkPriceMonitoring(changes.PriceMonitoringParameters, "proposal_submission.terms.change.new_market.changes"))
errs.Merge(checkLiquidityMonitoring(changes.LiquidityMonitoringParameters, "proposal_submission.terms.change.new_market.changes"))
errs.Merge(checkNewInstrument(changes.Instrument, "proposal_submission.terms.change.new_market.changes.instrument"))
Expand Down Expand Up @@ -904,6 +905,7 @@ func checkUpdateMarketChanges(change *protoTypes.ProposalTerms_UpdateMarket) Err
}
}

errs.Merge(checkLiquidationStrategy(changes.LiquidationStrategy, "proposal_submission.terms.change.update_market.changes"))
errs.Merge(checkPriceMonitoring(changes.PriceMonitoringParameters, "proposal_submission.terms.change.update_market.changes"))
errs.Merge(checkLiquidityMonitoring(changes.LiquidityMonitoringParameters, "proposal_submission.terms.change.update_market.changes"))
errs.Merge(checkUpdateInstrument(changes.Instrument))
Expand Down Expand Up @@ -975,6 +977,29 @@ func checkPriceMonitoring(parameters *protoTypes.PriceMonitoringParameters, pare
return errs
}

func checkLiquidationStrategy(params *protoTypes.LiquidationStrategy, parent string) Errors {
errs := NewErrors()
if params == nil {
// @TODO these will be required, in that case the check for nil should be removed
// or return an error.
return errs
}
dispFrac, err := num.DecimalFromString(params.DisposalFraction)
if err != nil || dispFrac.IsNegative() || dispFrac.IsZero() || dispFrac.GreaterThan(num.DecimalOne()) {
errs.AddForProperty(fmt.Sprintf("%s.liquidation_strategy.disposal_fraction", parent), ErrMustBeBetween01)
}
maxFrac, err := num.DecimalFromString(params.MaxFractionConsumed)
if err != nil || maxFrac.IsNegative() || maxFrac.IsZero() || maxFrac.GreaterThan(num.DecimalOne()) {
errs.AddForProperty(fmt.Sprintf("%s.liquidation_strategy.max_fraction_consumed", parent), ErrMustBeBetween01)
}
if params.DisposalTimeStep < 1 {
errs.AddForProperty(fmt.Sprintf("%s.liquidation_strategy.disposal_time_step", parent), ErrMustBePositive)
} else if params.DisposalTimeStep > 3600 {
errs.AddForProperty(fmt.Sprintf("%s.liquidation_strategy.disposal_time_step", parent), ErrMustBeAtMost3600)
}
return errs
}

func checkLiquidityMonitoring(parameters *protoTypes.LiquidityMonitoringParameters, parentProperty string) Errors {
errs := NewErrors()

Expand Down
257 changes: 257 additions & 0 deletions commands/proposal_submission_new_market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ func TestCheckProposalSubmissionForNewMarket(t *testing.T) {
t.Run("Submitting a future market with external data sources for settlement and termination with no signers fail", testFutureMarketSubmissionWithExternalSettlementDataAndTerminationNoSignerFails)
t.Run("Submitting a future market with internal time trigger termination data fails", testFutureMarketSubmissionWithInternalTimeTriggerTerminationDataFails)
t.Run("Submitting a future market with internal time trigger settlement data fails", testFutureMarketSubmissionWithInternalTimeTriggerSettlementDataFails)
t.Run("Submitting a future market with valid liquidation strategy succeeds", testFutureMarketSubmissionWithValidLiquidationStrategySucceeds)
t.Run("Submitting a future market with invalid liquidation strategy fails", testFutureMarketSubmissionWithInvalidLiquidationStrategyFails)

t.Run("Submitting a perps market change without perps fails", testNewPerpsMarketChangeSubmissionWithoutPerpsFails)
t.Run("Submitting a perps market change with perps succeeds", testNewPerpsMarketChangeSubmissionWithPerpsSucceeds)
Expand Down Expand Up @@ -5916,3 +5918,258 @@ func testLiquidityFeeSettings(t *testing.T) {
assert.Contains(t, err.Get("proposal_submission.terms.change.new_market.changes.liquidity_fee_settings."+c.field), c.err)
}
}

func testFutureMarketSubmissionWithValidLiquidationStrategySucceeds(t *testing.T) {
pubKey := []*dstypes.Signer{
dstypes.CreateSignerFromString("bd069246503a57271375f1995c46e03db88c4e1a564077b33a9872f905650dc4", dstypes.SignerTypePubKey),
}

err := checkProposalSubmission(&commandspb.ProposalSubmission{
Terms: &vegapb.ProposalTerms{
Change: &vegapb.ProposalTerms_NewMarket{
NewMarket: &vegapb.NewMarket{
Changes: &vegapb.NewMarketConfiguration{
Instrument: &vegapb.InstrumentConfiguration{
Product: &vegapb.InstrumentConfiguration_Future{
Future: &vegapb.FutureProduct{
DataSourceSpecForSettlementData: vegapb.NewDataSourceDefinition(
vegapb.DataSourceContentTypeOracle,
).SetOracleConfig(
&vegapb.DataSourceDefinitionExternal_Oracle{
Oracle: &vegapb.DataSourceSpecConfiguration{
Signers: dstypes.SignersIntoProto(pubKey),
Filters: []*datapb.Filter{
{
Key: &datapb.PropertyKey{
Name: "prices.ETH.value",
Type: datapb.PropertyKey_TYPE_INTEGER,
},
Conditions: []*datapb.Condition{
{
Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
},
},
},
},
},
},
),
DataSourceSpecForTradingTermination: vegapb.NewDataSourceDefinition(
vegapb.DataSourceContentTypeOracle,
).SetOracleConfig(
&vegapb.DataSourceDefinitionExternal_Oracle{
Oracle: &vegapb.DataSourceSpecConfiguration{
Signers: dstypes.SignersIntoProto(pubKey),
Filters: []*datapb.Filter{
{
Key: &datapb.PropertyKey{
Name: "vegaprotocol.builtin.timestamp",
Type: datapb.PropertyKey_TYPE_TIMESTAMP,
},
Conditions: []*datapb.Condition{
{
Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
Value: fmt.Sprintf("%d", time.Now().Add(time.Hour*24*365).UnixNano()),
},
},
},
},
},
},
),
},
},
},
LiquidationStrategy: &vegapb.LiquidationStrategy{
DisposalTimeStep: 20,
DisposalFraction: "0.05",
FullDisposalSize: 20,
MaxFractionConsumed: "0.01",
},
},
},
},
},
})

assert.Empty(t, err.Get("proposal_submission.terms.change.new_market.changes.liquidation_strategy.disposal_fraction"))
assert.Empty(t, err.Get("proposal_submission.terms.change.new_market.changes.liquidation_strategy.max_fraction_consumed"))
assert.Empty(t, err.Get("proposal_submission.terms.change.new_market.changes.liquidation_strategy.disposal_time_step"))
}

func testFutureMarketSubmissionWithInvalidLiquidationStrategyFails(t *testing.T) {
pubKey := []*dstypes.Signer{
dstypes.CreateSignerFromString("bd069246503a57271375f1995c46e03db88c4e1a564077b33a9872f905650dc4", dstypes.SignerTypePubKey),
}

submission := &commandspb.ProposalSubmission{
Terms: &vegapb.ProposalTerms{
Change: &vegapb.ProposalTerms_NewMarket{
NewMarket: &vegapb.NewMarket{
Changes: &vegapb.NewMarketConfiguration{
Instrument: &vegapb.InstrumentConfiguration{
Product: &vegapb.InstrumentConfiguration_Future{
Future: &vegapb.FutureProduct{
DataSourceSpecForSettlementData: vegapb.NewDataSourceDefinition(
vegapb.DataSourceContentTypeOracle,
).SetOracleConfig(
&vegapb.DataSourceDefinitionExternal_Oracle{
Oracle: &vegapb.DataSourceSpecConfiguration{
Signers: dstypes.SignersIntoProto(pubKey),
Filters: []*datapb.Filter{
{
Key: &datapb.PropertyKey{
Name: "prices.ETH.value",
Type: datapb.PropertyKey_TYPE_INTEGER,
},
Conditions: []*datapb.Condition{
{
Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
},
},
},
},
},
},
),
DataSourceSpecForTradingTermination: vegapb.NewDataSourceDefinition(
vegapb.DataSourceContentTypeOracle,
).SetOracleConfig(
&vegapb.DataSourceDefinitionExternal_Oracle{
Oracle: &vegapb.DataSourceSpecConfiguration{
Signers: dstypes.SignersIntoProto(pubKey),
Filters: []*datapb.Filter{
{
Key: &datapb.PropertyKey{
Name: "vegaprotocol.builtin.timestamp",
Type: datapb.PropertyKey_TYPE_TIMESTAMP,
},
Conditions: []*datapb.Condition{
{
Operator: datapb.Condition_OPERATOR_GREATER_THAN_OR_EQUAL,
Value: fmt.Sprintf("%d", time.Now().Add(time.Hour*24*365).UnixNano()),
},
},
},
},
},
},
),
},
},
},
LiquidationStrategy: &vegapb.LiquidationStrategy{
DisposalTimeStep: 20,
DisposalFraction: "0.05",
FullDisposalSize: 20,
MaxFractionConsumed: "0.01",
},
},
},
},
},
}

data := map[string]struct {
ls *vegapb.LiquidationStrategy
err error
}{
"proposal_submission.terms.change.new_market.changes.liquidation_strategy.disposal_fraction": {
ls: &vegapb.LiquidationStrategy{
DisposalTimeStep: 20,
DisposalFraction: "123",
FullDisposalSize: 20,
MaxFractionConsumed: "0.01",
},
err: commands.ErrMustBeBetween01,
},
"proposal_submission.terms.change.new_market.changes.liquidation_strategy.max_fraction_consumed": {
ls: &vegapb.LiquidationStrategy{
DisposalTimeStep: 20,
DisposalFraction: "0.1",
FullDisposalSize: 20,
MaxFractionConsumed: "-0.1",
},
err: commands.ErrMustBeBetween01,
},
"proposal_submission.terms.change.new_market.changes.liquidation_strategy.disposal_time_step": {
ls: &vegapb.LiquidationStrategy{
DisposalTimeStep: 0,
DisposalFraction: "0.1",
FullDisposalSize: 20,
MaxFractionConsumed: "0.1",
},
err: commands.ErrMustBePositive,
},
}
checks := []string{
"proposal_submission.terms.change.new_market.changes.liquidation_strategy.disposal_fraction",
"proposal_submission.terms.change.new_market.changes.liquidation_strategy.max_fraction_consumed",
"proposal_submission.terms.change.new_market.changes.liquidation_strategy.disposal_time_step",
}
for ec, exp := range data {
nm := submission.Terms.GetNewMarket()
nm.Changes.LiquidationStrategy = exp.ls
submission.Terms.Change = &vegapb.ProposalTerms_NewMarket{
NewMarket: nm,
}
err := checkProposalSubmission(submission)
for _, k := range checks {
if k != ec {
assert.Empty(t, err.Get(k))
} else {
assert.Contains(t, err.Get(k), exp.err)
}
}
}
// pretty much the same as above, only this time set the disposal fraction to a negative value
// and max fraction consumed to a large positive
// finally set the disposal time step to a large int value, this changes the error
data = map[string]struct {
ls *vegapb.LiquidationStrategy
err error
}{
"proposal_submission.terms.change.new_market.changes.liquidation_strategy.disposal_fraction": {
ls: &vegapb.LiquidationStrategy{
DisposalTimeStep: 20,
DisposalFraction: "-2",
FullDisposalSize: 20,
MaxFractionConsumed: "0.01",
},
err: commands.ErrMustBeBetween01,
},
"proposal_submission.terms.change.new_market.changes.liquidation_strategy.max_fraction_consumed": {
ls: &vegapb.LiquidationStrategy{
DisposalTimeStep: 20,
DisposalFraction: "0.1",
FullDisposalSize: 20,
MaxFractionConsumed: "2",
},
err: commands.ErrMustBeBetween01,
},
"proposal_submission.terms.change.new_market.changes.liquidation_strategy.disposal_time_step": {
ls: &vegapb.LiquidationStrategy{
DisposalTimeStep: 3601,
DisposalFraction: "0.1",
FullDisposalSize: 20,
MaxFractionConsumed: "0.1",
},
err: commands.ErrMustBeAtMost3600,
},
}
for ec, exp := range data {
nm := submission.Terms.GetNewMarket()
nm.Changes.LiquidationStrategy = exp.ls
submission.Terms.Change = &vegapb.ProposalTerms_NewMarket{
NewMarket: nm,
}
err := checkProposalSubmission(submission)
for _, k := range checks {
if k != ec {
assert.Empty(t, err.Get(k))
} else {
assert.Contains(t, err.Get(k), exp.err)
}
}
}
}
Loading

0 comments on commit 5877f4e

Please sign in to comment.