Skip to content

Commit

Permalink
feat: allow choice of method to calculate liqudity fees
Browse files Browse the repository at this point in the history
  • Loading branch information
wwestgarth committed Nov 3, 2023
1 parent f50e3c5 commit a8c4fec
Show file tree
Hide file tree
Showing 42 changed files with 2,430 additions and 1,097 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

### 🛠 Improvements

- [](https://github.com/vegaprotocol/vega/issues/xxxx) -
- [9930](https://github.com/vegaprotocol/vega/issues/9930) - `LiquidityFeeSettings` can now be used in market proposals to choose how liquidity fees are calculated.

### 🐛 Fixes

Expand Down
38 changes: 37 additions & 1 deletion commands/proposal_submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ func checkNewMarketChanges(change *protoTypes.ProposalTerms_NewMarket) Errors {
errs.Merge(checkNewInstrument(changes.Instrument, "proposal_submission.terms.change.new_market.changes.instrument"))
errs.Merge(checkNewRiskParameters(changes))
errs.Merge(checkSLAParams(changes.LiquiditySlaParameters, "proposal_submission.terms.change.new_market.changes.sla_params"))

errs.Merge(checkLiquidityFeeSettings(changes.LiquidityFeeSettings, "proposal_submission.terms.change.new_market.changes.liquidity_fee_settings"))
return errs
}

Expand Down Expand Up @@ -930,6 +930,7 @@ func checkUpdateMarketChanges(change *protoTypes.ProposalTerms_UpdateMarket) Err
errs.Merge(checkUpdateInstrument(changes.Instrument))
errs.Merge(checkUpdateRiskParameters(changes))
errs.Merge(checkSLAParams(changes.LiquiditySlaParameters, "proposal_submission.terms.change.update_market.changes.sla_params"))
errs.Merge(checkLiquidityFeeSettings(changes.LiquidityFeeSettings, "proposal_submission.terms.change.update_market.changes.liquidity_fee_settings"))

return errs
}
Expand Down Expand Up @@ -1646,6 +1647,41 @@ func checkSLAParams(config *protoTypes.LiquiditySLAParameters, parent string) Er
return errs
}

func checkLiquidityFeeSettings(config *protoTypes.LiquidityFeeSettings, parent string) Errors {
errs := NewErrors()
if config == nil {
return nil // no error, we'll default to margin-cost method
}

// check for valid enum range
if config.Method == protoTypes.LiquidityFeeSettings_METHOD_UNSPECIFIED {
errs.AddForProperty(fmt.Sprintf("%s.method", parent), ErrIsRequired)
}
if _, ok := protoTypes.LiquidityFeeSettings_Method_name[int32(config.Method)]; !ok {
errs.AddForProperty(fmt.Sprintf("%s.method", parent), ErrIsNotValid)
}

if config.FeeConstant == nil && config.Method == protoTypes.LiquidityFeeSettings_METHOD_CONSTANT {
errs.AddForProperty(fmt.Sprintf("%s.fee_constant", parent), ErrIsRequired)
}

if config.FeeConstant != nil {
if config.Method != protoTypes.LiquidityFeeSettings_METHOD_CONSTANT {
errs.AddForProperty(fmt.Sprintf("%s.method", parent), ErrIsNotValid)
}

fee, err := num.DecimalFromString(*config.FeeConstant)
switch {
case err != nil:
errs.AddForProperty(fmt.Sprintf("%s.fee_constant", parent), ErrIsNotValidNumber)
case fee.IsNegative():
errs.AddForProperty(fmt.Sprintf("%s.fee_constant", parent), ErrMustBePositiveOrZero)
}
}

return errs
}

func checkNewSpotRiskParameters(config *protoTypes.NewSpotMarketConfiguration) Errors {
errs := NewErrors()

Expand Down
84 changes: 84 additions & 0 deletions commands/proposal_submission_new_market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func TestCheckProposalSubmissionForNewMarket(t *testing.T) {
t.Run("Submitting a new market with valid competition factor succeeds", testNewMarketChangeSubmissionWithValidCompetitionFactorSucceeds)
t.Run("Submitting a new market with invalid hysteresis epochs fails", testNewMarketChangeSubmissionWithInvalidPerformanceHysteresisEpochsFails)
t.Run("Submitting a new market with valid hysteresis epochs succeeds", testNewMarketChangeSubmissionWithValidPerformanceHysteresisEpochsSucceeds)
t.Run("Submitting a new market with invalid liquidity fee settings", testLiquidityFeeSettings)
}

func testNewMarketChangeSubmissionWithoutNewMarketFails(t *testing.T) {
Expand Down Expand Up @@ -5870,3 +5871,86 @@ func testNewMarketChangeSubmissionWithValidPerformanceHysteresisEpochsSucceeds(t

assert.NotContains(t, err.Get("proposal_submission.terms.change.new_market.changes.sla_params.performance_hysteresis_epochs"), commands.ErrMustBePositive)
}

func testLiquidityFeeSettings(t *testing.T) {
cases := []struct {
lfs *vega.LiquidityFeeSettings
field string
err error
}{
{
lfs: &vega.LiquidityFeeSettings{
Method: vegapb.LiquidityFeeSettings_METHOD_MARGINAL_COST,
FeeConstant: ptr.From("0.1"),
},
field: "method",
err: commands.ErrIsNotValid,
},
{
lfs: &vega.LiquidityFeeSettings{
Method: vegapb.LiquidityFeeSettings_METHOD_WEIGHTED_AVERAGE,
FeeConstant: ptr.From("0.1"),
},
field: "method",
err: commands.ErrIsNotValid,
},
{
lfs: &vega.LiquidityFeeSettings{
Method: vegapb.LiquidityFeeSettings_METHOD_CONSTANT,
FeeConstant: nil,
},
field: "fee_constant",
err: commands.ErrIsRequired,
},
{
lfs: &vega.LiquidityFeeSettings{
Method: vegapb.LiquidityFeeSettings_METHOD_CONSTANT,
FeeConstant: ptr.From("hello"),
},
field: "fee_constant",
err: commands.ErrIsNotValidNumber,
},
{
lfs: &vega.LiquidityFeeSettings{
Method: vegapb.LiquidityFeeSettings_METHOD_CONSTANT,
FeeConstant: ptr.From("-0.1"),
},
field: "fee_constant",
err: commands.ErrMustBePositiveOrZero,
},
{
lfs: &vega.LiquidityFeeSettings{
Method: vegapb.LiquidityFeeSettings_METHOD_UNSPECIFIED,
},
field: "method",
err: commands.ErrIsRequired,
},
{
lfs: &vega.LiquidityFeeSettings{
Method: vegapb.LiquidityFeeSettings_Method(int32(100)),
},
field: "method",
err: commands.ErrIsNotValid,
},
}

for _, c := range cases {
err := checkProposalSubmission(&commandspb.ProposalSubmission{
Terms: &vegapb.ProposalTerms{
Change: &vegapb.ProposalTerms_NewMarket{
NewMarket: &vegapb.NewMarket{
Changes: &vegapb.NewMarketConfiguration{
Instrument: &vegapb.InstrumentConfiguration{
Product: &vegapb.InstrumentConfiguration_Perpetual{
Perpetual: &vegapb.PerpetualProduct{},
},
},
LiquidityFeeSettings: c.lfs,
},
},
},
},
})
assert.Contains(t, err.Get("proposal_submission.terms.change.new_market.changes.liquidity_fee_settings."+c.field), c.err)
}
}
4 changes: 4 additions & 0 deletions core/events/market_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"code.vegaprotocol.io/vega/core/events"
"code.vegaprotocol.io/vega/core/types"
"code.vegaprotocol.io/vega/libs/num"
"code.vegaprotocol.io/vega/protos/vega"
vegapb "code.vegaprotocol.io/vega/protos/vega"
datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -190,6 +191,9 @@ func TestMarketDeepClone(t *testing.T) {
InfrastructureFee: "0.2",
LiquidityFee: "0.3",
},
LiquidityFeeSettings: &vega.LiquidityFeeSettings{
Method: vega.LiquidityFeeSettings_METHOD_MARGINAL_COST,
},
},
OpeningAuction: &vegapb.AuctionDuration{
Duration: 1000,
Expand Down
4 changes: 4 additions & 0 deletions core/execution/engine_snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"testing"
"time"

"code.vegaprotocol.io/vega/protos/vega"
datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"

snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
Expand Down Expand Up @@ -284,6 +285,9 @@ func getMarketConfig() *types.Market {
InfrastructureFee: num.DecimalFromFloat(0.1),
LiquidityFee: num.DecimalFromFloat(0.1),
},
LiquidityFeeSettings: &types.LiquidityFeeSettings{
Method: vega.LiquidityFeeSettings_METHOD_MARGINAL_COST,
},
},
TradableInstrument: &types.TradableInstrument{
MarginCalculator: &types.MarginCalculator{
Expand Down
27 changes: 11 additions & 16 deletions core/execution/future/liquidity_provision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,10 @@ func TestSubmit(t *testing.T) {
mktCfg := getMarket(pMonitorSettings, &types.AuctionDuration{
Duration: 10000,
})
mktCfg.Fees = &types.Fees{
Factors: &types.FeeFactors{
LiquidityFee: num.DecimalFromFloat(0.001),
InfrastructureFee: num.DecimalFromFloat(0.0005),
MakerFee: num.DecimalFromFloat(0.00025),
},
mktCfg.Fees.Factors = &types.FeeFactors{
LiquidityFee: num.DecimalFromFloat(0.001),
InfrastructureFee: num.DecimalFromFloat(0.0005),
MakerFee: num.DecimalFromFloat(0.00025),
}
mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{
LogNormalRiskModel: &types.LogNormalRiskModel{
Expand Down Expand Up @@ -248,12 +246,11 @@ func TestSubmit(t *testing.T) {
mktCfg := getMarket(pMonitorSettings, &types.AuctionDuration{
Duration: 10000,
})
mktCfg.Fees = &types.Fees{
Factors: &types.FeeFactors{
InfrastructureFee: num.DecimalFromFloat(0.0005),
MakerFee: num.DecimalFromFloat(0.00025),
},
mktCfg.Fees.Factors = &types.FeeFactors{
InfrastructureFee: num.DecimalFromFloat(0.0005),
MakerFee: num.DecimalFromFloat(0.00025),
}

mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{
LogNormalRiskModel: &types.LogNormalRiskModel{
RiskAversionParameter: num.DecimalFromFloat(0.001),
Expand Down Expand Up @@ -335,11 +332,9 @@ func TestAmend(t *testing.T) {
mktCfg := getMarket(defaultPriceMonitorSettings, &types.AuctionDuration{
Duration: 10000,
})
mktCfg.Fees = &types.Fees{
Factors: &types.FeeFactors{
InfrastructureFee: num.DecimalFromFloat(0.0005),
MakerFee: num.DecimalFromFloat(0.00025),
},
mktCfg.Fees.Factors = &types.FeeFactors{
InfrastructureFee: num.DecimalFromFloat(0.0005),
MakerFee: num.DecimalFromFloat(0.00025),
}
mktCfg.TradableInstrument.RiskModel = &types.TradableInstrumentLogNormalRiskModel{
LogNormalRiskModel: &types.LogNormalRiskModel{
Expand Down
18 changes: 16 additions & 2 deletions core/execution/future/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -2352,8 +2352,22 @@ func (m *Market) handleRiskEvts(ctx context.Context, margins []events.Risk) []*t
// updateLiquidityFee computes the current LiquidityProvision fee and updates
// the fee engine.
func (m *Market) updateLiquidityFee(ctx context.Context) {
stake := m.getTargetStake()
fee := m.liquidityEngine.ProvisionsPerParty().FeeForTarget(stake)
var fee num.Decimal
provisions := m.liquidityEngine.ProvisionsPerParty()

switch m.mkt.Fees.LiquidityFeeSettings.Method {
case types.LiquidityFeeMethodConstant:
if len(provisions) != 0 {
fee = m.mkt.Fees.LiquidityFeeSettings.FeeConstant
}
case types.LiquidityFeeMethodMarginalCost:
fee = provisions.FeeForTarget(m.getTargetStake())
case types.LiquidityFeeMethodWeightedAverage:
fee = provisions.FeeForWeightedAverage()
default:
m.log.Panic("unknown liquidity fee method")
}

if !fee.Equals(m.getLiquidityFee()) {
m.fee.SetLiquidityFee(fee)
m.setLiquidityFee(fee)
Expand Down
10 changes: 10 additions & 0 deletions core/execution/future/market_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"code.vegaprotocol.io/vega/core/risk"
"code.vegaprotocol.io/vega/core/settlement"
"code.vegaprotocol.io/vega/core/types"
vgcontext "code.vegaprotocol.io/vega/libs/context"
"code.vegaprotocol.io/vega/libs/num"
"code.vegaprotocol.io/vega/libs/ptr"
"code.vegaprotocol.io/vega/logging"
Expand Down Expand Up @@ -65,6 +66,15 @@ func NewMarketFromSnapshot(
volumeDiscountService fee.VolumeDiscountService,
) (*Market, error) {
mkt := em.Market

if vgcontext.InProgressUpgradeFrom(ctx, "v0.73.2") {
// protocol upgrade from v0.73.2, lets populate the new liquidity-fee-settings with a default marginal-cost method
log.Info("migrating liquidity fee settings for existing market", logging.String("mid", mkt.ID))
mkt.Fees.LiquidityFeeSettings = &types.LiquidityFeeSettings{
Method: types.LiquidityFeeMethodMarginalCost,
}
}

positionFactor := num.DecimalFromFloat(10).Pow(num.DecimalFromInt64(mkt.PositionDecimalPlaces))
if len(em.Market.ID) == 0 {
return nil, common.ErrEmptyMarketID
Expand Down
44 changes: 38 additions & 6 deletions core/execution/future/market_snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestRestoreSettledMarket(t *testing.T) {
oracleEngine.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(spec.SubscriptionID(1), unsubscribe, nil)
oracleEngine.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(spec.SubscriptionID(2), unsubscribe, nil)

snap, err := newMarketFromSnapshot(t, ctrl, em, oracleEngine)
snap, err := newMarketFromSnapshot(t, context.Background(), ctrl, em, oracleEngine)
require.NoError(t, err)
require.NotEmpty(t, snap)

Expand Down Expand Up @@ -88,7 +88,7 @@ func TestRestoreTerminatedMarket(t *testing.T) {
oracleEngine.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(spec.SubscriptionID(1), unsubscribe, nil)
oracleEngine.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(spec.SubscriptionID(2), unsubscribe, nil)

snap, err := newMarketFromSnapshot(t, ctrl, em, oracleEngine)
snap, err := newMarketFromSnapshot(t, context.Background(), ctrl, em, oracleEngine)
require.NoError(t, err)
require.NotEmpty(t, snap)

Expand Down Expand Up @@ -117,15 +117,47 @@ func TestRestoreNilLastTradedPrice(t *testing.T) {
oracleEngine.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(spec.SubscriptionID(1), unsubscribe, nil)
oracleEngine.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(spec.SubscriptionID(2), unsubscribe, nil)

snap, err := newMarketFromSnapshot(t, ctrl, em, oracleEngine)
snap, err := newMarketFromSnapshot(t, context.Background(), ctrl, em, oracleEngine)
require.NoError(t, err)
require.NotEmpty(t, snap)

em2 := tm.market.GetState()
em2 := snap.GetState()
assert.Nil(t, em2.LastTradedPrice)
assert.Nil(t, em2.CurrentMarkPrice)
}

func TestRestoreMarketUpgradeV0_73_2(t *testing.T) {
now := time.Unix(10, 0)
tm := getTestMarket(t, now, nil, nil)
defer tm.ctrl.Finish()

em := tm.market.GetState()
assert.Nil(t, em.LastTradedPrice)
assert.Nil(t, em.CurrentMarkPrice)

ctrl := gomock.NewController(t)
defer ctrl.Finish()
oracleEngine := mocks.NewMockOracleEngine(ctrl)

unsubscribe := func(_ context.Context, id spec.SubscriptionID) {
}
oracleEngine.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(spec.SubscriptionID(1), unsubscribe, nil)
oracleEngine.EXPECT().Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(spec.SubscriptionID(2), unsubscribe, nil)

// set the liquidity fee settings to nil, like if we can come from an older version
em.Market.Fees.LiquidityFeeSettings = nil

// and set in the context the information that says we are upgrading
ctx := vegacontext.WithSnapshotInfo(context.Background(), "v0.73.2", true)
snap, err := newMarketFromSnapshot(t, ctx, ctrl, em, oracleEngine)
require.NoError(t, err)
require.NotEmpty(t, snap)

em2 := snap.GetState()
require.NotNil(t, em2.Market.Fees.LiquidityFeeSettings)
assert.Equal(t, em2.Market.Fees.LiquidityFeeSettings.Method, types.LiquidityFeeMethodMarginalCost)
}

func getTerminatedMarket(t *testing.T) *testMarket {
t.Helper()
pubKeys := []*dstypes.Signer{
Expand Down Expand Up @@ -171,7 +203,7 @@ func getSettledMarket(t *testing.T) *testMarket {
}

// newMarketFromSnapshot is a wrapper for NewMarketFromSnapshot with a lot of defaults handled.
func newMarketFromSnapshot(t *testing.T, ctrl *gomock.Controller, em *types.ExecMarket, oracleEngine products.OracleEngine) (*future.Market, error) {
func newMarketFromSnapshot(t *testing.T, ctx context.Context, ctrl *gomock.Controller, em *types.ExecMarket, oracleEngine products.OracleEngine) (*future.Market, error) {
t.Helper()
var (
riskConfig = risk.NewDefaultConfig()
Expand Down Expand Up @@ -207,7 +239,7 @@ func newMarketFromSnapshot(t *testing.T, ctrl *gomock.Controller, em *types.Exec
volumeDiscount.EXPECT().VolumeDiscountFactorForParty(gomock.Any()).Return(num.DecimalZero()).AnyTimes()
referralDiscountReward.EXPECT().GetReferrer(gomock.Any()).Return(types.PartyID(""), errors.New("not a referrer")).AnyTimes()

return future.NewMarketFromSnapshot(context.Background(), log, em, riskConfig, positionConfig, settlementConfig, matchingConfig,
return future.NewMarketFromSnapshot(ctx, log, em, riskConfig, positionConfig, settlementConfig, matchingConfig,
feeConfig, liquidityConfig, collateralEngine, oracleEngine, timeService, broker, stubs.NewStateVar(), cfgAsset, marketActivityTracker,
peggedOrderCounterForTest, referralDiscountReward, volumeDiscount)
}
Loading

0 comments on commit a8c4fec

Please sign in to comment.