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 }