code.vegaprotocol.io/vega@v0.79.0/core/execution/common/liquidity_provision.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package common
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"sort"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/core/events"
    26  	"code.vegaprotocol.io/vega/core/fee"
    27  	"code.vegaprotocol.io/vega/core/idgeneration"
    28  	"code.vegaprotocol.io/vega/core/liquidity/v2"
    29  	"code.vegaprotocol.io/vega/core/types"
    30  	"code.vegaprotocol.io/vega/libs/num"
    31  	"code.vegaprotocol.io/vega/logging"
    32  
    33  	"golang.org/x/exp/maps"
    34  )
    35  
    36  var ErrCommitmentAmountTooLow = errors.New("commitment amount is too low")
    37  
    38  type marketType int
    39  
    40  const (
    41  	FutureMarketType marketType = iota
    42  	SpotMarketType
    43  )
    44  
    45  type MarketLiquidity struct {
    46  	log   *logging.Logger
    47  	idGen IDGenerator
    48  
    49  	liquidityEngine       LiquidityEngine
    50  	collateral            Collateral
    51  	broker                Broker
    52  	orderBook             liquidity.OrderBook
    53  	equityShares          EquityLikeShares
    54  	amm                   AMM
    55  	marketActivityTracker *MarketActivityTracker
    56  	fee                   *fee.Engine
    57  
    58  	marketType marketType
    59  	marketID   string
    60  	asset      string
    61  
    62  	priceFactor num.Decimal
    63  
    64  	priceRange                num.Decimal
    65  	earlyExitPenalty          num.Decimal
    66  	minLPStakeQuantumMultiple num.Decimal
    67  
    68  	bondPenaltyFactor num.Decimal
    69  	elsFeeFactor      num.Decimal
    70  	stakeToCcyVolume  num.Decimal
    71  	ammStats          map[string]*AMMState
    72  	tick              int64
    73  }
    74  
    75  func NewMarketLiquidity(
    76  	log *logging.Logger,
    77  	liquidityEngine LiquidityEngine,
    78  	collateral Collateral,
    79  	broker Broker,
    80  	orderBook liquidity.OrderBook,
    81  	equityShares EquityLikeShares,
    82  	marketActivityTracker *MarketActivityTracker,
    83  	fee *fee.Engine,
    84  	marketType marketType,
    85  	marketID string,
    86  	asset string,
    87  	priceFactor num.Decimal,
    88  	priceRange num.Decimal,
    89  	amm AMM,
    90  ) *MarketLiquidity {
    91  	ml := &MarketLiquidity{
    92  		log:                   log,
    93  		liquidityEngine:       liquidityEngine,
    94  		collateral:            collateral,
    95  		broker:                broker,
    96  		orderBook:             orderBook,
    97  		equityShares:          equityShares,
    98  		marketActivityTracker: marketActivityTracker,
    99  		fee:                   fee,
   100  		marketType:            marketType,
   101  		marketID:              marketID,
   102  		asset:                 asset,
   103  		priceFactor:           priceFactor,
   104  		priceRange:            priceRange,
   105  		amm:                   amm,
   106  		ammStats:              map[string]*AMMState{},
   107  	}
   108  
   109  	return ml
   110  }
   111  
   112  func (m *MarketLiquidity) SetAMM(a AMM) {
   113  	m.amm = a
   114  }
   115  
   116  func (m *MarketLiquidity) bondUpdate(ctx context.Context, transfer *types.Transfer) (*types.LedgerMovement, error) {
   117  	switch m.marketType {
   118  	case SpotMarketType:
   119  		return m.collateral.BondSpotUpdate(ctx, m.marketID, transfer)
   120  	default:
   121  		return m.collateral.BondUpdate(ctx, m.marketID, transfer)
   122  	}
   123  }
   124  
   125  func (m *MarketLiquidity) transferFees(ctx context.Context, ft events.FeesTransfer) ([]*types.LedgerMovement, error) {
   126  	switch m.marketType {
   127  	case SpotMarketType:
   128  		return m.collateral.TransferSpotFees(ctx, m.marketID, m.asset, ft, types.AccountTypeGeneral)
   129  	default:
   130  		return m.collateral.TransferFees(ctx, m.marketID, m.asset, ft)
   131  	}
   132  }
   133  
   134  func (m *MarketLiquidity) applyAMMStats() {
   135  	ids := maps.Keys(m.ammStats)
   136  	sort.Strings(ids)
   137  
   138  	for _, party := range ids {
   139  		amm := m.ammStats[party]
   140  
   141  		// if the amm has been cancelled for a whole epoch then just kill their ELS
   142  		if !m.amm.IsAMMPartyID(party) && amm.stake.IsZero() {
   143  			m.equityShares.SetPartyStake(party, nil)
   144  			delete(m.ammStats, party)
   145  			continue
   146  		}
   147  
   148  		// otherwise update it and start our stats again
   149  		stake, _ := num.UintFromDecimal(amm.stake)
   150  		m.equityShares.SetPartyStake(party, stake)
   151  		amm.StartEpoch()
   152  	}
   153  }
   154  
   155  func (m *MarketLiquidity) applyPendingProvisions(
   156  	ctx context.Context,
   157  	now time.Time,
   158  	targetStake *num.Uint,
   159  ) liquidity.Provisions {
   160  	provisions := m.liquidityEngine.ProvisionsPerParty()
   161  	pendingProvisions := m.liquidityEngine.PendingProvision()
   162  
   163  	zero := num.DecimalZero()
   164  
   165  	// totalStake - targetStake
   166  	totalTargetStakeDifference := m.liquidityEngine.CalculateSuppliedStakeWithoutPending().ToDecimal().Sub(targetStake.ToDecimal())
   167  	maxPenaltyFreeReductionAmount := num.MaxD(zero, totalTargetStakeDifference)
   168  
   169  	sumOfCommitmentVariations := num.DecimalZero()
   170  	commitmentVariationPerParty := map[string]num.Decimal{}
   171  
   172  	for partyID, provision := range provisions {
   173  		acc, err := m.collateral.GetPartyBondAccount(m.marketID, partyID, m.asset)
   174  		if err != nil {
   175  			// the bond account should be definitely there at this point
   176  			m.log.Panic("can not get LP party bond account", logging.Error(err))
   177  		}
   178  
   179  		amendment, foundIdx := pendingProvisions.Get(partyID)
   180  		if foundIdx < 0 {
   181  			continue
   182  		}
   183  
   184  		// amendedCommitment - originalCommitment
   185  		proposedCommitmentVariation := amendment.CommitmentAmount.ToDecimal().Sub(provision.CommitmentAmount.ToDecimal())
   186  
   187  		// if commitment is increased or not changed, there is not penalty applied
   188  		if !proposedCommitmentVariation.IsNegative() {
   189  			continue
   190  		}
   191  
   192  		// min(-proposedCommitmentVariation, bondAccountBalance)
   193  		commitmentVariation := num.MinD(proposedCommitmentVariation.Neg(), acc.Balance.ToDecimal())
   194  		if commitmentVariation.IsZero() {
   195  			continue
   196  		}
   197  
   198  		commitmentVariationPerParty[partyID] = commitmentVariation
   199  		sumOfCommitmentVariations = sumOfCommitmentVariations.Add(commitmentVariation)
   200  	}
   201  
   202  	ledgerMovements := make([]*types.LedgerMovement, 0, len(commitmentVariationPerParty))
   203  
   204  	one := num.DecimalOne()
   205  
   206  	keys := sortedKeys(commitmentVariationPerParty)
   207  	for _, partyID := range keys {
   208  		commitmentVariation := commitmentVariationPerParty[partyID]
   209  		// (commitmentVariation/sumOfCommitmentVariations) * maxPenaltyFreeReductionAmount
   210  		partyMaxPenaltyFreeReductionAmount := commitmentVariation.Div(sumOfCommitmentVariations).
   211  			Mul(maxPenaltyFreeReductionAmount)
   212  
   213  		// transfer entire decreased commitment to their general account, no penalty will be applied
   214  		if commitmentVariation.LessThanOrEqual(partyMaxPenaltyFreeReductionAmount) {
   215  			commitmentVariationU, _ := num.UintFromDecimal(commitmentVariation)
   216  			if commitmentVariationU.IsZero() {
   217  				continue
   218  			}
   219  
   220  			transfer := m.NewTransfer(partyID, types.TransferTypeBondHigh, commitmentVariationU)
   221  			bondLedgerMovement, err := m.bondUpdate(ctx, transfer)
   222  			if err != nil {
   223  				m.log.Panic("failed to apply SLA penalties to bond account", logging.Error(err))
   224  			}
   225  
   226  			ledgerMovements = append(ledgerMovements, bondLedgerMovement)
   227  			continue
   228  		}
   229  
   230  		partyMaxPenaltyFreeReductionAmountU, _ := num.UintFromDecimal(partyMaxPenaltyFreeReductionAmount)
   231  
   232  		if !partyMaxPenaltyFreeReductionAmountU.IsZero() {
   233  			transfer := m.NewTransfer(partyID, types.TransferTypeBondHigh, partyMaxPenaltyFreeReductionAmountU)
   234  			bondLedgerMovement, err := m.bondUpdate(ctx, transfer)
   235  			if err != nil {
   236  				m.log.Panic("failed to apply SLA penalties to bond account", logging.Error(err))
   237  			}
   238  
   239  			ledgerMovements = append(ledgerMovements, bondLedgerMovement)
   240  		}
   241  
   242  		penaltyIncurringReductionAmount := commitmentVariation.Sub(partyMaxPenaltyFreeReductionAmount)
   243  
   244  		// transfer to general account
   245  		freeAmount := one.Sub(m.earlyExitPenalty).Mul(penaltyIncurringReductionAmount)
   246  		freeAmountU, _ := num.UintFromDecimal(freeAmount)
   247  
   248  		if !freeAmountU.IsZero() {
   249  			transfer := m.NewTransfer(partyID, types.TransferTypeBondHigh, freeAmountU)
   250  			bondLedgerMovement, err := m.bondUpdate(ctx, transfer)
   251  			if err != nil {
   252  				m.log.Panic("failed to apply SLA penalties to bond account", logging.Error(err))
   253  			}
   254  
   255  			ledgerMovements = append(ledgerMovements, bondLedgerMovement)
   256  		}
   257  
   258  		slashingAmount := m.earlyExitPenalty.Mul(penaltyIncurringReductionAmount)
   259  		slashingAmountU, _ := num.UintFromDecimal(slashingAmount)
   260  
   261  		if !slashingAmountU.IsZero() {
   262  			transfer := m.NewTransfer(partyID, types.TransferTypeBondSlashing, slashingAmountU)
   263  			bondLedgerMovement, err := m.bondUpdate(ctx, transfer)
   264  			if err != nil {
   265  				m.log.Panic("failed to apply SLA penalties to bond account", logging.Error(err))
   266  			}
   267  
   268  			ledgerMovements = append(ledgerMovements, bondLedgerMovement)
   269  		}
   270  	}
   271  
   272  	if len(ledgerMovements) > 0 {
   273  		m.broker.Send(events.NewLedgerMovements(ctx, ledgerMovements))
   274  	}
   275  
   276  	return m.liquidityEngine.ApplyPendingProvisions(ctx, now)
   277  }
   278  
   279  func (m *MarketLiquidity) syncPartyCommitmentWithBondAccount(
   280  	ctx context.Context,
   281  	appliedLiquidityProvisions liquidity.Provisions,
   282  ) {
   283  	if len(appliedLiquidityProvisions) == 0 {
   284  		appliedLiquidityProvisions = liquidity.Provisions{}
   285  	}
   286  
   287  	for partyID, provision := range m.liquidityEngine.ProvisionsPerParty() {
   288  		acc, err := m.collateral.GetPartyBondAccount(m.marketID, partyID, m.asset)
   289  		if err != nil {
   290  			// the bond account should be definitely there at this point
   291  			m.log.Panic("can not get LP party bond account",
   292  				logging.Error(err),
   293  				logging.PartyID(partyID),
   294  			)
   295  		}
   296  
   297  		// lp provision and bond account are in sync, no need to change
   298  		if provision.CommitmentAmount.EQ(acc.Balance) {
   299  			continue
   300  		}
   301  
   302  		if acc.Balance.IsZero() {
   303  			if err := m.liquidityEngine.CancelLiquidityProvision(ctx, partyID); err != nil {
   304  				// the commitment should exists
   305  				m.log.Panic("can not cancel liquidity provision commitment",
   306  					logging.Error(err),
   307  					logging.PartyID(partyID),
   308  				)
   309  			}
   310  
   311  			provision.CommitmentAmount = acc.Balance.Clone()
   312  			appliedLiquidityProvisions.Set(provision)
   313  			continue
   314  		}
   315  
   316  		updatedProvision, err := m.liquidityEngine.UpdatePartyCommitment(partyID, acc.Balance)
   317  		if err != nil {
   318  			m.log.Panic("failed to update party commitment", logging.Error(err))
   319  		}
   320  		appliedLiquidityProvisions.Set(updatedProvision)
   321  	}
   322  
   323  	for _, provision := range appliedLiquidityProvisions {
   324  		// now we can setup our party stake to calculate equities
   325  		m.equityShares.SetPartyStake(provision.Party, provision.CommitmentAmount.Clone())
   326  		// force update of shares so they are updated for all, used to be in the loop, but should be
   327  		// fine to just do this once
   328  		_ = m.equityShares.AllShares()
   329  	}
   330  }
   331  
   332  func (m *MarketLiquidity) OnEpochStart(
   333  	ctx context.Context, now time.Time,
   334  	markPrice, midPrice, targetStake *num.Uint,
   335  	positionFactor num.Decimal,
   336  ) {
   337  	m.liquidityEngine.ResetSLAEpoch(now, markPrice, midPrice, positionFactor)
   338  
   339  	m.applyAMMStats()
   340  	m.tick = 0 // start of a new epoch, we are at tick 0
   341  	appliedProvisions := m.applyPendingProvisions(ctx, now, targetStake)
   342  	m.syncPartyCommitmentWithBondAccount(ctx, appliedProvisions)
   343  }
   344  
   345  func (m *MarketLiquidity) OnEpochEnd(ctx context.Context, t time.Time, epoch types.Epoch) {
   346  	m.calculateAndDistribute(ctx, t)
   347  
   348  	// report liquidity fees allocation stats
   349  	feeStats := m.liquidityEngine.PaidLiquidityFeesStats()
   350  	if !feeStats.TotalFeesPaid.IsZero() {
   351  		m.broker.Send(events.NewPaidLiquidityFeesStatsEvent(ctx, feeStats.ToProto(m.marketID, m.asset, epoch.Seq)))
   352  	}
   353  }
   354  
   355  func (m *MarketLiquidity) OnMarketClosed(ctx context.Context, t time.Time) {
   356  	m.calculateAndDistribute(ctx, t)
   357  }
   358  
   359  func (m *MarketLiquidity) calculateAndDistribute(ctx context.Context, t time.Time) {
   360  	penalties := m.liquidityEngine.CalculateSLAPenalties(t)
   361  
   362  	if m.amm != nil {
   363  		for _, subAccountID := range maps.Keys(m.amm.GetAMMPoolsBySubAccount()) {
   364  			// set penalty to zero for pool sub accounts as they always meet their obligations for SLA
   365  			penalties.PenaltiesPerParty[subAccountID] = &liquidity.SlaPenalty{
   366  				Fee:  num.DecimalZero(),
   367  				Bond: num.DecimalZero(),
   368  			}
   369  			penalties.AllPartiesHaveFullFeePenalty = false
   370  		}
   371  	}
   372  
   373  	m.distributeFeesBonusesAndApplyPenalties(ctx, penalties)
   374  }
   375  
   376  func (m *MarketLiquidity) OnTick(ctx context.Context, t time.Time) {
   377  	// distribute liquidity fees each feeDistributionTimeStep
   378  	if m.liquidityEngine.ReadyForFeesAllocation(t) {
   379  		if err := m.AllocateFees(ctx); err != nil {
   380  			m.log.Panic("liquidity fee distribution error", logging.Error(err))
   381  		}
   382  
   383  		// reset next distribution period
   384  		m.liquidityEngine.ResetFeeAllocationPeriod(t)
   385  		return
   386  	}
   387  
   388  	m.updateLiquidityScores()
   389  	// first tick since the start of the epoch will be 0
   390  	m.updateAMMCommitment(m.tick)
   391  	// increment tick
   392  	m.tick++
   393  }
   394  
   395  func (m *MarketLiquidity) EndBlock(markPrice, midPrice *num.Uint, positionFactor num.Decimal) {
   396  	m.liquidityEngine.EndBlock(markPrice, midPrice, positionFactor)
   397  }
   398  
   399  func (m *MarketLiquidity) updateLiquidityScores() {
   400  	minLpPrice, maxLpPrice, err := m.ValidOrdersPriceRange()
   401  	if err != nil {
   402  		m.log.Debug("liquidity score update error", logging.Error(err))
   403  		return
   404  	}
   405  	bestBid, bestAsk, err := m.getBestStaticPricesDecimal()
   406  	if err != nil {
   407  		m.log.Debug("liquidity score update error", logging.Error(err))
   408  		return
   409  	}
   410  
   411  	m.liquidityEngine.UpdateAverageLiquidityScores(bestBid, bestAsk, minLpPrice, maxLpPrice)
   412  }
   413  
   414  func (m *MarketLiquidity) getBestStaticPricesDecimal() (bid, ask num.Decimal, err error) {
   415  	bid = num.DecimalZero()
   416  	ask = num.DecimalZero()
   417  
   418  	binUint, err := m.orderBook.GetBestStaticBidPrice()
   419  	if err != nil {
   420  		return
   421  	}
   422  	bid = binUint.ToDecimal()
   423  
   424  	askUint, err := m.orderBook.GetBestStaticAskPrice()
   425  	if err != nil {
   426  		return
   427  	}
   428  	ask = askUint.ToDecimal()
   429  
   430  	return bid, ask, nil
   431  }
   432  
   433  // updateSharesWithLiquidityScores multiplies each LP i share with their score and divides all LP i share with total shares amount.
   434  func (m *MarketLiquidity) updateSharesWithLiquidityScores(shares, scores map[string]num.Decimal) map[string]num.Decimal {
   435  	total := num.DecimalZero()
   436  
   437  	for partyID, share := range shares {
   438  		score, ok := scores[partyID]
   439  		if !ok {
   440  			continue
   441  		}
   442  
   443  		shares[partyID] = share.Mul(score)
   444  		total = total.Add(shares[partyID])
   445  	}
   446  
   447  	// normalize - share i / total
   448  	if !total.IsZero() {
   449  		for k, v := range shares {
   450  			shares[k] = v.Div(total)
   451  		}
   452  	}
   453  
   454  	return shares
   455  }
   456  
   457  func (m *MarketLiquidity) canSubmitCommitment(marketState types.MarketState) bool {
   458  	switch marketState {
   459  	case types.MarketStateActive, types.MarketStatePending, types.MarketStateSuspended, types.MarketStateProposed, types.MarketStateSuspendedViaGovernance:
   460  		return true
   461  	}
   462  
   463  	return false
   464  }
   465  
   466  // SubmitLiquidityProvision forwards a LiquidityProvisionSubmission to the Liquidity Engine.
   467  func (m *MarketLiquidity) SubmitLiquidityProvision(
   468  	ctx context.Context,
   469  	sub *types.LiquidityProvisionSubmission,
   470  	party string,
   471  	deterministicID string,
   472  	marketState types.MarketState,
   473  ) (err error) {
   474  	m.idGen = idgeneration.New(deterministicID)
   475  	defer func() { m.idGen = nil }()
   476  
   477  	if !m.canSubmitCommitment(marketState) {
   478  		return ErrCommitmentSubmissionNotAllowed
   479  	}
   480  
   481  	if err := m.ensureMinCommitmentAmount(sub.CommitmentAmount); err != nil {
   482  		return err
   483  	}
   484  
   485  	submittedImmediately, err := m.liquidityEngine.SubmitLiquidityProvision(ctx, sub, party, m.idGen)
   486  	if err != nil {
   487  		return err
   488  	}
   489  
   490  	if err := m.makePerPartyAccountsAndTransfers(ctx, party, sub.CommitmentAmount); err != nil {
   491  		if newErr := m.liquidityEngine.RejectLiquidityProvision(ctx, party); newErr != nil {
   492  			m.log.Debug("unable to submit cancel liquidity provision submission",
   493  				logging.String("party", party),
   494  				logging.String("id", deterministicID),
   495  				logging.Error(newErr))
   496  
   497  			err = fmt.Errorf("%v, %w", err, newErr)
   498  		}
   499  
   500  		return err
   501  	}
   502  
   503  	if submittedImmediately {
   504  		// now we can setup our party stake to calculate equities
   505  		m.equityShares.SetPartyStake(party, sub.CommitmentAmount.Clone())
   506  		// force update of shares so they are updated for all
   507  		_ = m.equityShares.AllShares()
   508  	}
   509  
   510  	return nil
   511  }
   512  
   513  // makePerPartyAccountsAndTransfers create a party specific per market accounts for bond, margin and fee.
   514  // It also transfers required commitment amount to per market bond account.
   515  func (m *MarketLiquidity) makePerPartyAccountsAndTransfers(ctx context.Context, party string, commitmentAmount *num.Uint) error {
   516  	bondAcc, err := m.collateral.GetOrCreatePartyBondAccount(ctx, party, m.marketID, m.asset)
   517  	if err != nil {
   518  		return err
   519  	}
   520  
   521  	_, err = m.collateral.GetOrCreatePartyLiquidityFeeAccount(ctx, party, m.marketID, m.asset)
   522  	if err != nil {
   523  		return err
   524  	}
   525  
   526  	// calculates the amount that needs to be moved into the bond account
   527  	amount, neg := num.UintZero().Delta(commitmentAmount, bondAcc.Balance)
   528  	ty := types.TransferTypeBondLow
   529  	if neg {
   530  		ty = types.TransferTypeBondHigh
   531  	}
   532  	transfer := &types.Transfer{
   533  		Owner: party,
   534  		Amount: &types.FinancialAmount{
   535  			Amount: amount.Clone(),
   536  			Asset:  m.asset,
   537  		},
   538  		Type:      ty,
   539  		MinAmount: amount.Clone(),
   540  	}
   541  
   542  	tresp, err := m.bondUpdate(ctx, transfer)
   543  	if err != nil {
   544  		m.log.Debug("bond update error", logging.Error(err))
   545  		return err
   546  	}
   547  	m.broker.Send(events.NewLedgerMovements(ctx, []*types.LedgerMovement{tresp}))
   548  
   549  	return nil
   550  }
   551  
   552  // AmendLiquidityProvision forwards a LiquidityProvisionAmendment to the Liquidity Engine.
   553  func (m *MarketLiquidity) AmendLiquidityProvision(
   554  	ctx context.Context,
   555  	lpa *types.LiquidityProvisionAmendment,
   556  	party string,
   557  	deterministicID string,
   558  	marketState types.MarketState,
   559  ) error {
   560  	m.idGen = idgeneration.New(deterministicID)
   561  	defer func() { m.idGen = nil }()
   562  
   563  	if !m.canSubmitCommitment(marketState) {
   564  		return ErrCommitmentSubmissionNotAllowed
   565  	}
   566  
   567  	if err := m.liquidityEngine.ValidateLiquidityProvisionAmendment(lpa); err != nil {
   568  		return err
   569  	}
   570  
   571  	if lpa.CommitmentAmount != nil {
   572  		if err := m.ensureMinCommitmentAmount(lpa.CommitmentAmount); err != nil {
   573  			return err
   574  		}
   575  	}
   576  
   577  	if !m.liquidityEngine.IsLiquidityProvider(party) {
   578  		return ErrPartyNotLiquidityProvider
   579  	}
   580  
   581  	pendingAmendment := m.liquidityEngine.PendingProvisionByPartyID(party)
   582  	currentProvision := m.liquidityEngine.LiquidityProvisionByPartyID(party)
   583  
   584  	provisionToCopy := currentProvision
   585  	if currentProvision == nil {
   586  		if pendingAmendment == nil {
   587  			m.log.Panic(
   588  				"cannot edit liquidity provision from a non liquidity provider party",
   589  				logging.PartyID(party),
   590  			)
   591  		}
   592  
   593  		provisionToCopy = pendingAmendment
   594  	}
   595  
   596  	// If commitment amount is not provided we keep the same
   597  	if lpa.CommitmentAmount == nil || lpa.CommitmentAmount.IsZero() {
   598  		lpa.CommitmentAmount = provisionToCopy.CommitmentAmount
   599  	}
   600  
   601  	// If commitment amount is not provided we keep the same
   602  	if lpa.Fee.IsZero() {
   603  		lpa.Fee = provisionToCopy.Fee
   604  	}
   605  
   606  	// If commitment amount is not provided we keep the same
   607  	if lpa.Reference == "" {
   608  		lpa.Reference = provisionToCopy.Reference
   609  	}
   610  
   611  	var proposedCommitmentVariation num.Decimal
   612  
   613  	// if pending commitment is being decreased then release the bond collateral
   614  	if pendingAmendment != nil && !lpa.CommitmentAmount.IsZero() && lpa.CommitmentAmount.LT(pendingAmendment.CommitmentAmount) {
   615  		amountToRelease := num.UintZero().Sub(pendingAmendment.CommitmentAmount, lpa.CommitmentAmount)
   616  		if err := m.releasePendingBondCollateral(ctx, amountToRelease, party); err != nil {
   617  			m.log.Debug("could not submit update bond for lp amendment",
   618  				logging.PartyID(party),
   619  				logging.MarketID(m.marketID),
   620  				logging.Error(err))
   621  
   622  			return err
   623  		}
   624  
   625  		proposedCommitmentVariation = pendingAmendment.CommitmentAmount.ToDecimal().Sub(lpa.CommitmentAmount.ToDecimal())
   626  	} else {
   627  		if currentProvision != nil {
   628  			proposedCommitmentVariation = currentProvision.CommitmentAmount.ToDecimal().Sub(lpa.CommitmentAmount.ToDecimal())
   629  		} else {
   630  			proposedCommitmentVariation = pendingAmendment.CommitmentAmount.ToDecimal().Sub(lpa.CommitmentAmount.ToDecimal())
   631  		}
   632  	}
   633  
   634  	// if increase commitment transfer funds to bond account
   635  	if proposedCommitmentVariation.IsNegative() {
   636  		_, err := m.ensureAndTransferCollateral(ctx, lpa.CommitmentAmount, party)
   637  		if err != nil {
   638  			m.log.Debug("could not submit update bond for lp amendment",
   639  				logging.PartyID(party),
   640  				logging.MarketID(m.marketID),
   641  				logging.Error(err))
   642  
   643  			return err
   644  		}
   645  	}
   646  
   647  	applied, err := m.liquidityEngine.AmendLiquidityProvision(ctx, lpa, party, false)
   648  	if err != nil {
   649  		m.log.Panic("error while amending liquidity provision, this should not happen at this point, the LP was validated earlier",
   650  			logging.Error(err))
   651  	}
   652  
   653  	if currentProvision != nil && applied && proposedCommitmentVariation.IsPositive() && !lpa.CommitmentAmount.IsZero() {
   654  		amountToRelease := num.UintZero().Sub(currentProvision.CommitmentAmount, lpa.CommitmentAmount)
   655  		if err := m.releasePendingBondCollateral(ctx, amountToRelease, party); err != nil {
   656  			m.log.Debug("could not submit update bond for lp amendment",
   657  				logging.PartyID(party),
   658  				logging.MarketID(m.marketID),
   659  				logging.Error(err))
   660  
   661  			// rollback the amendment - TODO karel
   662  			lpa.CommitmentAmount = currentProvision.CommitmentAmount
   663  			m.liquidityEngine.AmendLiquidityProvision(ctx, lpa, party, false)
   664  
   665  			return err
   666  		}
   667  	}
   668  
   669  	return nil
   670  }
   671  
   672  // CancelLiquidityProvision amends liquidity provision to 0.
   673  func (m *MarketLiquidity) CancelLiquidityProvision(ctx context.Context, party string) error {
   674  	currentProvision := m.liquidityEngine.LiquidityProvisionByPartyID(party)
   675  	pendingAmendment := m.liquidityEngine.PendingProvisionByPartyID(party)
   676  
   677  	if currentProvision == nil && pendingAmendment == nil {
   678  		return ErrPartyHasNoExistingLiquidityProvision
   679  	}
   680  
   681  	if pendingAmendment != nil && !pendingAmendment.CommitmentAmount.IsZero() {
   682  		if err := m.releasePendingBondCollateral(ctx, pendingAmendment.CommitmentAmount.Clone(), party); err != nil {
   683  			m.log.Debug("could release bond collateral for pending amendment",
   684  				logging.PartyID(party),
   685  				logging.MarketID(m.marketID),
   686  				logging.Error(err))
   687  
   688  			return err
   689  		}
   690  	}
   691  
   692  	amendment := &types.LiquidityProvisionAmendment{
   693  		MarketID:         m.marketID,
   694  		CommitmentAmount: num.UintZero(),
   695  		Fee:              num.DecimalZero(),
   696  	}
   697  
   698  	applied, err := m.liquidityEngine.AmendLiquidityProvision(ctx, amendment, party, true)
   699  	if err != nil {
   700  		m.log.Panic("error while amending liquidity provision, this should not happen at this point, the LP was validated earlier",
   701  			logging.Error(err))
   702  	}
   703  
   704  	if applied && currentProvision != nil && !currentProvision.CommitmentAmount.IsZero() {
   705  		if err := m.releasePendingBondCollateral(ctx, currentProvision.CommitmentAmount.Clone(), party); err != nil {
   706  			m.log.Debug("could not submit update bond for lp amendment",
   707  				logging.PartyID(party),
   708  				logging.MarketID(m.marketID),
   709  				logging.Error(err))
   710  
   711  			// rollback amendment
   712  			amendment.CommitmentAmount = currentProvision.CommitmentAmount
   713  			amendment.Fee = currentProvision.Fee
   714  			m.liquidityEngine.AmendLiquidityProvision(ctx, amendment, party, false)
   715  
   716  			return err
   717  		}
   718  	}
   719  	// remove ELS for the cancelled LP if cancellation was applied immediately (e.g. during opening auction)
   720  	if applied {
   721  		m.equityShares.SetPartyStake(party, amendment.CommitmentAmount)
   722  		// force update for all shares
   723  		_ = m.equityShares.AllShares()
   724  	}
   725  
   726  	return nil
   727  }
   728  
   729  func (m *MarketLiquidity) StopAllLiquidityProvision(ctx context.Context) {
   730  	for _, p := range m.liquidityEngine.ProvisionsPerParty().Slice() {
   731  		m.liquidityEngine.StopLiquidityProvision(ctx, p.Party)
   732  	}
   733  }
   734  
   735  // checks that party has enough collateral to support the commitment increase.
   736  func (m *MarketLiquidity) ensureAndTransferCollateral(
   737  	ctx context.Context, commitmentAmount *num.Uint, party string,
   738  ) (*types.Transfer, error) {
   739  	bondAcc, err := m.collateral.GetOrCreatePartyBondAccount(
   740  		ctx, party, m.marketID, m.asset)
   741  	if err != nil {
   742  		return nil, err
   743  	}
   744  
   745  	// first check if there's enough funds in the gen + bond
   746  	// account to cover the new commitment
   747  	if !m.collateral.CanCoverBond(m.marketID, party, m.asset, commitmentAmount.Clone()) {
   748  		return nil, ErrNotEnoughStake
   749  	}
   750  
   751  	// build our transfer to be sent to collateral
   752  	amount, neg := num.UintZero().Delta(commitmentAmount, bondAcc.Balance)
   753  	ty := types.TransferTypeBondLow
   754  	if neg {
   755  		ty = types.TransferTypeBondHigh
   756  	}
   757  	transfer := &types.Transfer{
   758  		Owner: party,
   759  		Amount: &types.FinancialAmount{
   760  			Amount: amount,
   761  			Asset:  m.asset,
   762  		},
   763  		Type:      ty,
   764  		MinAmount: amount.Clone(),
   765  	}
   766  
   767  	// move our bond
   768  	tresp, err := m.bondUpdate(ctx, transfer)
   769  	if err != nil {
   770  		return nil, err
   771  	}
   772  	m.broker.Send(events.NewLedgerMovements(
   773  		ctx, []*types.LedgerMovement{tresp}))
   774  
   775  	// now we will use the actual transfer as a rollback later on eventually
   776  	// so let's just change from HIGH to LOW and inverse
   777  	if transfer.Type == types.TransferTypeBondHigh {
   778  		transfer.Type = types.TransferTypeBondLow
   779  	} else {
   780  		transfer.Type = types.TransferTypeBondHigh
   781  	}
   782  
   783  	return transfer, nil
   784  }
   785  
   786  // releasePendingCollateral releases pending amount collateral from bond to general account.
   787  func (m *MarketLiquidity) releasePendingBondCollateral(
   788  	ctx context.Context, releaseAmount *num.Uint, party string,
   789  ) error {
   790  	transfer := &types.Transfer{
   791  		Owner: party,
   792  		Amount: &types.FinancialAmount{
   793  			Amount: releaseAmount,
   794  			Asset:  m.asset,
   795  		},
   796  		Type:      types.TransferTypeBondHigh,
   797  		MinAmount: releaseAmount.Clone(),
   798  	}
   799  
   800  	ledgerMovement, err := m.bondUpdate(ctx, transfer)
   801  	if err != nil {
   802  		return err
   803  	}
   804  	m.broker.Send(events.NewLedgerMovements(
   805  		ctx, []*types.LedgerMovement{ledgerMovement}))
   806  
   807  	return nil
   808  }
   809  
   810  func (m *MarketLiquidity) ensureMinCommitmentAmount(amount *num.Uint) error {
   811  	quantum, err := m.collateral.GetAssetQuantum(m.asset)
   812  	if err != nil {
   813  		m.log.Panic("could not get quantum for asset, this should never happen",
   814  			logging.AssetID(m.asset),
   815  			logging.Error(err),
   816  		)
   817  	}
   818  	minStake := quantum.Mul(m.minLPStakeQuantumMultiple)
   819  	if amount.ToDecimal().LessThan(minStake) {
   820  		return ErrCommitmentAmountTooLow
   821  	}
   822  
   823  	return nil
   824  }
   825  
   826  // ValidOrdersPriceRange returns min and max valid prices range for LP orders.
   827  func (m *MarketLiquidity) ValidOrdersPriceRange() (*num.Uint, *num.Uint, error) {
   828  	bestBid, err := m.orderBook.GetBestStaticBidPrice()
   829  	if err != nil {
   830  		return num.UintOne(), num.MaxUint(), err
   831  	}
   832  
   833  	bestAsk, err := m.orderBook.GetBestStaticAskPrice()
   834  	if err != nil {
   835  		return num.UintOne(), num.MaxUint(), err
   836  	}
   837  
   838  	// (bestBid + bestAsk) / 2
   839  	midPrice := bestBid.ToDecimal().Add(bestAsk.ToDecimal()).Div(num.DecimalFromFloat(2))
   840  	// (1 - priceRange) * midPrice
   841  	lowerBoundPriceD := num.DecimalOne().Sub(m.priceRange).Mul(midPrice)
   842  	// (1 + priceRange) * midPrice
   843  	upperBoundPriceD := num.DecimalOne().Add(m.priceRange).Mul(midPrice)
   844  
   845  	// ceil lower bound
   846  	ceiledLowerBound, rL := lowerBoundPriceD.QuoRem(m.priceFactor, int32(0))
   847  	if !rL.IsZero() {
   848  		ceiledLowerBound = ceiledLowerBound.Add(num.DecimalOne())
   849  	}
   850  	lowerBoundPriceD = ceiledLowerBound.Mul(m.priceFactor)
   851  
   852  	// floor upper bound
   853  	flooredUpperBound, _ := upperBoundPriceD.QuoRem(m.priceFactor, int32(0))
   854  	upperBoundPriceD = flooredUpperBound.Mul(m.priceFactor)
   855  
   856  	lowerBound, _ := num.UintFromDecimal(lowerBoundPriceD)
   857  	upperBound, _ := num.UintFromDecimal(upperBoundPriceD)
   858  
   859  	// floor at 1 to avoid non-positive value
   860  	uintPriceFactor, _ := num.UintFromDecimal(m.priceFactor)
   861  
   862  	if lowerBound.IsNegative() || lowerBound.IsZero() {
   863  		lowerBound = uintPriceFactor
   864  	}
   865  
   866  	if lowerBound.GTE(upperBound) {
   867  		// if we ended up with overlapping upper and lower bound we set the upper bound to lower bound plus one tick.
   868  		upperBound = upperBound.Add(lowerBound, uintPriceFactor)
   869  	}
   870  
   871  	// we can't have lower bound >= best static ask as then a buy order with that price would trade on entry
   872  	// so place it one tick to the left
   873  	if lowerBound.GTE(bestAsk) {
   874  		lowerBound = num.UintZero().Sub(bestAsk, uintPriceFactor)
   875  	}
   876  
   877  	// we can't have upper bound <= best static bid as then a sell order with that price would trade on entry
   878  	// so place it one tick to the right
   879  	if upperBound.LTE(bestAsk) {
   880  		upperBound = num.UintZero().Add(bestAsk, uintPriceFactor)
   881  	}
   882  
   883  	return lowerBound, upperBound, nil
   884  }
   885  
   886  func (m *MarketLiquidity) UpdateMarketConfig(risk liquidity.RiskModel, monitor liquidity.PriceMonitor) {
   887  	m.liquidityEngine.UpdateMarketConfig(risk, monitor)
   888  }
   889  
   890  func (m *MarketLiquidity) UpdateSLAParameters(slaParams *types.LiquiditySLAParams) {
   891  	m.priceRange = slaParams.PriceRange
   892  	m.liquidityEngine.UpdateSLAParameters(slaParams)
   893  }
   894  
   895  func (m *MarketLiquidity) OnMinLPStakeQuantumMultiple(minLPStakeQuantumMultiple num.Decimal) {
   896  	m.minLPStakeQuantumMultiple = minLPStakeQuantumMultiple
   897  }
   898  
   899  func (m *MarketLiquidity) OnMinProbabilityOfTradingLPOrdersUpdate(v num.Decimal) {
   900  	m.liquidityEngine.OnMinProbabilityOfTradingLPOrdersUpdate(v)
   901  }
   902  
   903  func (m *MarketLiquidity) OnProbabilityOfTradingTauScalingUpdate(v num.Decimal) {
   904  	m.liquidityEngine.OnProbabilityOfTradingTauScalingUpdate(v)
   905  }
   906  
   907  func (m *MarketLiquidity) OnBondPenaltyFactorUpdate(bondPenaltyFactor num.Decimal) {
   908  	m.bondPenaltyFactor = bondPenaltyFactor
   909  }
   910  
   911  func (m *MarketLiquidity) OnNonPerformanceBondPenaltySlopeUpdate(nonPerformanceBondPenaltySlope num.Decimal) {
   912  	m.liquidityEngine.OnNonPerformanceBondPenaltySlopeUpdate(nonPerformanceBondPenaltySlope)
   913  }
   914  
   915  func (m *MarketLiquidity) OnNonPerformanceBondPenaltyMaxUpdate(nonPerformanceBondPenaltyMax num.Decimal) {
   916  	m.liquidityEngine.OnNonPerformanceBondPenaltyMaxUpdate(nonPerformanceBondPenaltyMax)
   917  }
   918  
   919  func (m *MarketLiquidity) OnMaximumLiquidityFeeFactorLevelUpdate(liquidityFeeFactorLevelUpdate num.Decimal) {
   920  	m.liquidityEngine.OnMaximumLiquidityFeeFactorLevelUpdate(liquidityFeeFactorLevelUpdate)
   921  }
   922  
   923  func (m *MarketLiquidity) OnEarlyExitPenalty(earlyExitPenalty num.Decimal) {
   924  	m.earlyExitPenalty = earlyExitPenalty
   925  }
   926  
   927  func (m *MarketLiquidity) OnStakeToCcyVolumeUpdate(stakeToCcyVolume num.Decimal) {
   928  	m.liquidityEngine.OnStakeToCcyVolumeUpdate(stakeToCcyVolume)
   929  	m.stakeToCcyVolume = stakeToCcyVolume
   930  }
   931  
   932  func (m *MarketLiquidity) OnProvidersFeeCalculationTimeStep(d time.Duration) {
   933  	m.liquidityEngine.OnProvidersFeeCalculationTimeStep(d)
   934  }
   935  
   936  func (m *MarketLiquidity) IsProbabilityOfTradingInitialised() bool {
   937  	return m.liquidityEngine.IsProbabilityOfTradingInitialised()
   938  }
   939  
   940  func (m *MarketLiquidity) GetAverageLiquidityScores() map[string]num.Decimal {
   941  	return m.liquidityEngine.GetAverageLiquidityScores()
   942  }
   943  
   944  func (m *MarketLiquidity) ProvisionsPerParty() liquidity.ProvisionsPerParty {
   945  	return m.liquidityEngine.ProvisionsPerParty()
   946  }
   947  
   948  func (m *MarketLiquidity) CalculateSuppliedStake() *num.Uint {
   949  	return m.liquidityEngine.CalculateSuppliedStake()
   950  }