diff --git a/core/execution/common/errors.go b/core/execution/common/errors.go index e202782614e..4c6aaeb0ccc 100644 --- a/core/execution/common/errors.go +++ b/core/execution/common/errors.go @@ -88,4 +88,6 @@ var ( ErrInvalidOrderPrice = errors.New("invalid order price") // ErrIsolatedMarginFullyCollateralised is returned when a party tries to switch margin modes on a fully collateralised market. ErrIsolatedMarginFullyCollateralised = errors.New("isolated margin not permitted on fully collateralised markets") + // ErrSettlementDataOutOfRange is returned when a capped future receives settlement data that is outside of the acceptable range (either > max price, or neither 0 nor max for binary settlements). + ErrSettlementDataOutOfRange = errors.New("settlement data is outside of the price cap") ) diff --git a/core/execution/future/market.go b/core/execution/future/market.go index c4e19a28b23..ecb9f7af26e 100644 --- a/core/execution/future/market.go +++ b/core/execution/future/market.go @@ -1158,8 +1158,9 @@ func (m *Market) BlockEnd(ctx context.Context) { m.nextMTM = t.Add(m.mtmDelta) + // mark price mustn't be zero, except for capped futures, where a zero price may well be possible if !m.as.InAuction() && (prevMarkPrice == nil || !m.markPriceCalculator.GetPrice().EQ(prevMarkPrice) || m.settlement.HasTraded()) && - !m.getCurrentMarkPrice().IsZero() { + (!m.getCurrentMarkPrice().IsZero() || m.capMax != nil) { if m.confirmMTM(ctx, false) { closedPositions := m.position.GetClosedPositions() if len(closedPositions) > 0 { @@ -1428,7 +1429,11 @@ func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) { price, _ := num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor)) if m.capMax != nil { - price = num.Min(price, m.capMax.Clone()) + upper := num.UintZero().Sub(m.capMax, num.UintOne()) + price = num.Min(price, upper) + } + if price.IsZero() { + price = num.UintOne() } return price, nil } @@ -1444,7 +1449,11 @@ func (m *Market) getNewPeggedPrice(order *types.Order) (*num.Uint, error) { price, _ = num.UintFromDecimal(priceInMarket.ToDecimal().Mul(m.priceFactor)) if m.capMax != nil { - price = num.Min(price, m.capMax.Clone()) + upper := num.UintZero().Sub(m.capMax, num.UintOne()) + price = num.Min(price, upper) + } + if price.IsZero() { + price = num.UintOne() } return price, nil } @@ -1492,9 +1501,14 @@ func (m *Market) UpdateMarketState(ctx context.Context, changes *types.MarketSta if m.mkt.State == types.MarketStatePending || m.mkt.State == types.MarketStateProposed { final = types.MarketStateCancelled } - m.uncrossOrderAtAuctionEnd(ctx) // terminate and settle data (either last traded price for perp, or settlement data provided via governance settlement, _ := num.UintFromDecimal(changes.SettlementPrice.ToDecimal().Mul(m.priceFactor)) + if !m.validateSettlementData(settlement) { + // final settlement is not valid/impossible + return common.ErrSettlementDataOutOfRange + } + // in case we're in auction, uncross + m.uncrossOrderAtAuctionEnd(ctx) m.tradingTerminatedWithFinalState(ctx, final, settlement) } else if changes.UpdateType == types.MarketStateUpdateTypeSuspend { m.mkt.State = types.MarketStateSuspendedViaGovernance @@ -2290,14 +2304,21 @@ func (m *Market) SubmitOrderWithIDGeneratorAndOrderID( m.triggerStopOrders(ctx, idgen) }() order := orderSubmission.IntoOrder(party) + order.CreatedAt = m.timeService.GetTimeNow().UnixNano() + order.ID = orderID if order.Price != nil { order.OriginalPrice = order.Price.Clone() order.Price, _ = num.UintFromDecimal(order.Price.ToDecimal().Mul(m.priceFactor)) + if order.Type == types.OrderTypeLimit && order.PeggedOrder == nil && order.Price.IsZero() { + // limit orders need to be priced > 0 + order.Status = types.OrderStatusRejected + order.Reason = types.OrderErrorPriceNotInTickSize // @TODO add new error + m.broker.Send(events.NewOrderEvent(ctx, order)) + return nil, common.ErrInvalidOrderPrice + } } - order.CreatedAt = m.timeService.GetTimeNow().UnixNano() - order.ID = orderID // check max price in case of capped market - if m.capMax != nil && order.Price != nil && order.Price.GT(m.capMax) { + if m.capMax != nil && order.Price != nil && order.Price.GTE(m.capMax) { order.Status = types.OrderStatusRejected order.Reason = types.OrderErrorPriceLTEMaxPrice m.broker.Send(events.NewOrderEvent(ctx, order)) @@ -4651,16 +4672,21 @@ func (m *Market) terminateMarket(ctx context.Context, finalState types.MarketSta m.broker.Send(events.NewMarketUpdatedEvent(ctx, *m.mkt)) var err error - if settlementDataInAsset != nil { + if settlementDataInAsset != nil && m.validateSettlementData(settlementDataInAsset) { m.settlementDataWithLock(ctx, finalState, settlementDataInAsset) } else if m.settlementDataInMarket != nil { // because we need to be able to perform the MTM settlement, only update market state now settlementDataInAsset, err = m.tradableInstrument.Instrument.Product.ScaleSettlementDataToDecimalPlaces(m.settlementDataInMarket, m.assetDP) if err != nil { m.log.Error(err.Error()) - } else { - m.settlementDataWithLock(ctx, finalState, settlementDataInAsset) + return + } + if !m.validateSettlementData(settlementDataInAsset) { + m.log.Warn("invalid settlement data", logging.MarketID(m.GetID())) + m.settlementDataInMarket = nil + return } + m.settlementDataWithLock(ctx, finalState, settlementDataInAsset) } else { m.log.Debug("no settlement data", logging.MarketID(m.GetID())) } @@ -4713,6 +4739,13 @@ func (m *Market) settlementData(ctx context.Context, settlementData *num.Numeric return } + // validate the settlement data + if !m.validateSettlementData(settlementDataInAsset) { + m.log.Warn("settlement data for capped market is invalid", logging.MarketID(m.GetID())) + // reset settlement data, it's not valid + m.settlementDataInMarket = nil + return + } m.settlementDataWithLock(ctx, types.MarketStateSettled, settlementDataInAsset) } @@ -4798,13 +4831,31 @@ func (m *Market) settlementDataPerp(ctx context.Context, settlementData *num.Num m.checkForReferenceMoves(ctx, orderUpdates, false) } +func (m *Market) validateSettlementData(data *num.Uint) bool { + if m.closed { + return false + } + // not capped, accept the data + if m.fCap == nil { + return true + } + // data > max + if m.capMax.LT(data) { + return false + } + // binary capped market: reject if data is not zero and not == max price. + if m.fCap.Binary && !data.IsZero() && !data.EQ(m.capMax) { + return false + } + return true +} + // NB this must be called with the lock already acquired. func (m *Market) settlementDataWithLock(ctx context.Context, finalState types.MarketState, settlementDataInAsset *num.Uint) { if m.closed { return } if m.capMax != nil && m.capMax.LT(settlementDataInAsset) { - // we cannot perform the final settlement because the settlement price is out of the [0, max_price] range return } if m.fCap != nil && m.fCap.Binary { diff --git a/core/integration/features/capped-futures/0016-PFUT-015.feature b/core/integration/features/capped-futures/0016-PFUT-015.feature index e0188b3012c..07090bd400e 100644 --- a/core/integration/features/capped-futures/0016-PFUT-015.feature +++ b/core/integration/features/capped-futures/0016-PFUT-015.feature @@ -41,7 +41,7 @@ Feature: Pegged orders are capped to max price. | party2 | DAI/DEC22 | buy | 1 | 800000000 | 0 | TYPE_LIMIT | TIF_GTC | party2-1 | | party2 | DAI/DEC22 | buy | 1 | 3500000000 | 0 | TYPE_LIMIT | TIF_GTC | party2-2 | | party3 | DAI/DEC22 | sell | 1 | 3500000000 | 0 | TYPE_LIMIT | TIF_GTC | party3-1 | - | party3 | DAI/DEC22 | sell | 1 | 4500000000 | 0 | TYPE_LIMIT | TIF_GTC | party3-2 | + | party3 | DAI/DEC22 | sell | 1 | 4499999999 | 0 | TYPE_LIMIT | TIF_GTC | party3-2 | And the opening auction period ends for market "DAI/DEC22" Then the following trades should be executed: @@ -49,11 +49,11 @@ Feature: Pegged orders are capped to max price. | party2 | 3500000000 | 1 | party3 | And the market data for the market "DAI/DEC22" should be: | mark price | best static bid price | static mid price | best static offer price | - | 3500000000 | 800000000 | 2650000000 | 4500000000 | + | 3500000000 | 800000000 | 2649999999 | 4499999999 | And the order book should have the following volumes for market "DAI/DEC22": | side | price | volume | - | sell | 4500000000 | 1 | - | sell | 2650000010 | 5 | + | sell | 4499999999 | 1 | + | sell | 2650000009 | 5 | | buy | 2649999990 | 5 | | buy | 800000000 | 1 | # Ensure the price cap is enforced on all orders @@ -67,13 +67,13 @@ Feature: Pegged orders are capped to max price. | party2 | party2-1 | And the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | reference | - | party2 | DAI/DEC22 | buy | 1 | 4499999999 | 0 | TYPE_LIMIT | TIF_GTC | party2-2 | + | party2 | DAI/DEC22 | buy | 1 | 4499999998 | 0 | TYPE_LIMIT | TIF_GTC | party2-2 | Then the market data for the market "DAI/DEC22" should be: | mark price | best static bid price | static mid price | best static offer price | - | 3500000000 | 4499999999 | 4499999999 | 4500000000 | + | 3500000000 | 4499999998 | 4499999998 | 4499999999 | # Now the sell order should be capped to max price, buy order is offset by 10 And the order book should have the following volumes for market "DAI/DEC22": | side | price | volume | - | sell | 4500000000 | 6 | - | buy | 4499999999 | 1 | - | buy | 4499999990 | 5 | + | sell | 4499999999 | 6 | + | buy | 4499999998 | 1 | + | buy | 4499999989 | 5 | diff --git a/core/integration/features/capped-futures/0016-PFUT-016.feature b/core/integration/features/capped-futures/0016-PFUT-016.feature index eecc71d7826..9b9c29a0983 100644 --- a/core/integration/features/capped-futures/0016-PFUT-016.feature +++ b/core/integration/features/capped-futures/0016-PFUT-016.feature @@ -49,7 +49,7 @@ Feature: Oracle price data within range is used to determine the mid price | party2 | DAI/DEC22 | buy | 1 | 2500000 | 0 | TYPE_LIMIT | TIF_GTC | party2-1 | | | party2 | DAI/DEC22 | buy | 1 | 3500000 | 0 | TYPE_LIMIT | TIF_GTC | party2-2 | | | party3 | DAI/DEC22 | sell | 1 | 3500000 | 0 | TYPE_LIMIT | TIF_GTC | party3-1 | | - | party3 | DAI/DEC22 | sell | 1 | 4500000 | 0 | TYPE_LIMIT | TIF_GTC | party3-2 | | + | party3 | DAI/DEC22 | sell | 1 | 4499999 | 0 | TYPE_LIMIT | TIF_GTC | party3-2 | | | party3 | DAI/DEC22 | sell | 1 | 8000000 | 0 | TYPE_LIMIT | TIF_GTC | party3-2 | invalid order price | And the opening auction period ends for market "DAI/DEC22" @@ -59,11 +59,12 @@ Feature: Oracle price data within range is used to determine the mid price And the market data for the market "DAI/DEC22" should be: | mark price | best static bid price | static mid price | best static offer price | - | 3500000 | 2500000 | 3500000 | 4500000 | + | 3500000 | 2500000 | 3499999 | 4499999 | + And debug detailed orderbook volumes for market "DAI/DEC22" And the order book should have the following volumes for market "DAI/DEC22": | side | price | volume | - | sell | 3600000 | 5 | - | sell | 4500000 | 1 | + | sell | 3599999 | 5 | + | sell | 4499999 | 1 | | buy | 3400000 | 5 | | buy | 2500000 | 1 | diff --git a/core/integration/features/capped-futures/0016-PFUT-021.feature b/core/integration/features/capped-futures/0016-PFUT-021.feature index 3ea7b290942..f70dcd9d9b2 100644 --- a/core/integration/features/capped-futures/0016-PFUT-021.feature +++ b/core/integration/features/capped-futures/0016-PFUT-021.feature @@ -56,7 +56,7 @@ Feature: When `max_price` is specified and the market is ran in a fully-collater When the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | reference | error | | aux1 | ETH/DEC21 | buy | 2 | 999 | 0 | TYPE_LIMIT | TIF_GTC | ref-1 | | - | aux2 | ETH/DEC21 | sell | 2 | 1500 | 0 | TYPE_LIMIT | TIF_GTC | ref-2 | | + | aux2 | ETH/DEC21 | sell | 2 | 1499 | 0 | TYPE_LIMIT | TIF_GTC | ref-2 | | | party1 | ETH/DEC21 | buy | 5 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | ref-3 | | | party2 | ETH/DEC21 | sell | 5 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | ref-4 | | And the network moves ahead "2" blocks @@ -78,7 +78,7 @@ Feature: When `max_price` is specified and the market is ran in a fully-collater | party | market id | maintenance | search | initial | release | margin mode | | party1 | ETH/DEC21 | 5000 | 5000 | 5000 | 5000 | cross margin | | party2 | ETH/DEC21 | 2500 | 2500 | 2500 | 2500 | cross margin | - | aux2 | ETH/DEC21 | 0 | 0 | 0 | 0 | cross margin | + | aux2 | ETH/DEC21 | 2 | 2 | 2 | 2 | cross margin | | aux1 | ETH/DEC21 | 1998 | 1998 | 1998 | 1998 | cross margin | #update mark price @@ -100,7 +100,7 @@ Feature: When `max_price` is specified and the market is ran in a fully-collater | party1 | USD | ETH/DEC21 | 5000 | 5500 | | party2 | USD | ETH/DEC21 | 2500 | 7000 | | aux1 | USD | ETH/DEC21 | 3098 | 96908 | - | aux2 | USD | ETH/DEC21 | 400 | 99572 | + | aux2 | USD | ETH/DEC21 | 402 | 99570 | # The market is fully collateralised, switching to isolated margin is not supported When the parties submit update margin mode: | party | market | margin_mode | margin_factor | error | @@ -110,15 +110,15 @@ Feature: When `max_price` is specified and the market is ran in a fully-collater #update mark price to max_price When the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | reference | error | - | aux3 | ETH/DEC21 | buy | 2 | 1500 | 1 | TYPE_LIMIT | TIF_GTC | aux3-1 | | + | aux3 | ETH/DEC21 | buy | 2 | 1499 | 1 | TYPE_LIMIT | TIF_GTC | aux3-1 | | And the following trades should be executed: | buyer | price | size | seller | - | aux3 | 1500 | 2 | aux2 | + | aux3 | 1499 | 2 | aux2 | And the network moves ahead "2" blocks Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC21" - Then the mark price should be "1500" for the market "ETH/DEC21" + Then the mark price should be "1499" for the market "ETH/DEC21" # MTM settlement 5 long makes a profit of 2000, 5 short loses 2000 # Now for aux1 and 2, the calculations from above still hold but more margin is required due to the open positions: @@ -126,11 +126,11 @@ Feature: When `max_price` is specified and the market is ran in a fully-collater # aux2: short position of size 2, traded price at 1500, then margin: postion size * (max price - average entry price) = 3*(1100+1500*2)/3 And the parties should have the following account balances: | party | asset | market id | margin | general | - | party1 | USD | ETH/DEC21 | 5000 | 7500 | - | party2 | USD | ETH/DEC21 | 2500 | 5000 | - | aux1 | USD | ETH/DEC21 | 3098 | 97308 | - | aux2 | USD | ETH/DEC21 | 402 | 99185 | - | aux3 | USD | ETH/DEC21 | 3000 | 96925 | + | party1 | USD | ETH/DEC21 | 5000 | 7495 | + | party2 | USD | ETH/DEC21 | 2500 | 5005 | + | aux1 | USD | ETH/DEC21 | 3098 | 97307 | + | aux2 | USD | ETH/DEC21 | 402 | 99186 | + | aux3 | USD | ETH/DEC21 | 2998 | 96927 | And the parties should have the following margin levels: | party | market id | maintenance | search | initial | release | margin mode | @@ -138,19 +138,22 @@ Feature: When `max_price` is specified and the market is ran in a fully-collater | party2 | ETH/DEC21 | 2500 | 2500 | 2500 | 2500 | cross margin | | aux2 | ETH/DEC21 | 402 | 402 | 402 | 402 | cross margin | | aux1 | ETH/DEC21 | 3098 | 3098 | 3098 | 3098 | cross margin | - #trade at max_price + + #0016-PFUT-024: trade at max_price, no closeout for parties with short position When the parties place the following orders: | party | market id | side | volume | price | resulting trades | type | tif | reference | - | aux4 | ETH/DEC21 | buy | 2 | 1500 | 0 | TYPE_LIMIT | TIF_GTC | aux4-1 | - | aux5 | ETH/DEC21 | sell | 2 | 1500 | 1 | TYPE_LIMIT | TIF_GTC | aux5-1 | + | aux4 | ETH/DEC21 | buy | 2 | 1499 | 0 | TYPE_LIMIT | TIF_GTC | aux4-1 | + | aux5 | ETH/DEC21 | sell | 2 | 1499 | 1 | TYPE_LIMIT | TIF_GTC | aux5-1 | And the network moves ahead "2" blocks # aux5: short position of size 2, traded price at 1500, then margin: postion size * (max price - average entry price) = 0 And the parties should have the following account balances: | party | asset | market id | margin | general | - | aux4 | USD | ETH/DEC21 | 3000 | 97015 | - | aux5 | USD | ETH/DEC21 | 0 | 99925 | + | aux1 | USD | ETH/DEC21 | 3098 | 97307 | + | aux2 | USD | ETH/DEC21 | 402 | 99186 | + | aux4 | USD | ETH/DEC21 | 2998 | 97017 | + | aux5 | USD | ETH/DEC21 | 2 | 99923 | And the following transfers should happen: | from | to | from account | to account | market id | amount | asset | @@ -159,7 +162,3 @@ Feature: When `max_price` is specified and the market is ran in a fully-collater | aux5 | | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_FEES_LIQUIDITY | ETH/DEC21 | 0 | USD | | market | aux4 | ACCOUNT_TYPE_FEES_MAKER | ACCOUNT_TYPE_GENERAL | ETH/DEC21 | 15 | USD | - - - - diff --git a/core/integration/features/capped-futures/0016-PFUT-022.feature b/core/integration/features/capped-futures/0016-PFUT-022.feature new file mode 100644 index 00000000000..5ce5b0c1c41 --- /dev/null +++ b/core/integration/features/capped-futures/0016-PFUT-022.feature @@ -0,0 +1,195 @@ +Feature: When max_price is specified and the market is ran in a fully-collateralised mode and it has parties with open positions settling it at a price of 0 works correctly + + Background: + Given time is updated to "2019-11-30T00:00:00Z" + And the average block duration is "1" + + And the oracle spec for settlement data filtering data from "0xCAFECAFE1" named "ethDec21Oracle": + | property | type | binding | + | prices.ETH.value | TYPE_INTEGER | settlement data | + + And the oracle spec for trading termination filtering data from "0xCAFECAFE1" named "ethDec21Oracle": + | property | type | binding | + | trading.terminated | TYPE_BOOLEAN | trading termination | + + And the settlement data decimals for the oracle named "ethDec21Oracle" is given in "0" decimal places + + And the following network parameters are set: + | name | value | + | market.auction.minimumDuration | 1 | + | network.markPriceUpdateMaximumFrequency | 1s | + | market.liquidity.successorLaunchWindowLength | 1s | + | limits.markets.maxPeggedOrders | 4 | + + And the fees configuration named "fees-config-1": + | maker fee | infrastructure fee | + | 0.005 | 0.02 | + And the price monitoring named "price-monitoring-1": + | horizon | probability | auction extension | + | 3600000 | 0.99 | 300 | + And the log normal risk model named "lognormal-risk-model-1": + | risk aversion | tau | mu | r | sigma | + | 0.0002 | 0.01 | 0 | 0.0 | 1.2 | + + And the markets: + | id | quote name | asset | risk model | margin calculator | auction duration | fees | price monitoring | data source config | linear slippage factor | quadratic slippage factor | sla params | max price cap | fully collateralised | binary | + | ETH/DEC21 | ETH | USD | lognormal-risk-model-1 | default-capped-margin-calculator | 1 | fees-config-1 | price-monitoring-1 | ethDec21Oracle | 0.25 | 0 | default-futures | 1500 | true | false | + + @SLABug @NoPerp @Capped @CSettle + Scenario: 0016-PFUT-022: The sum of all final settlement cashflows equals 0 (loss socialisation does not happen). Assuming general account balances of all parties were 0 after opening the positions and all of their funds were in the margin accounts: short parties end up with balances equal to abs(position size) * max_price and long parties end up with 0 balances. + Given the initial insurance pool balance is "10000" for all the markets + And the parties deposit on asset's general account the following amount: + | party | asset | amount | + | party1 | USD | 10000 | + | party2 | USD | 10000 | + | aux1 | USD | 100000 | + | aux2 | USD | 100000 | + | aux3 | USD | 100000 | + | aux4 | USD | 100000 | + | aux5 | USD | 100000 | + | party-lp | USD | 100000000 | + + And the parties submit the following liquidity provision: + | id | party | market id | commitment amount | fee | lp type | + | lp2 | party-lp | ETH/DEC21 | 30000 | 0 | submission | + + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | error | + | aux1 | ETH/DEC21 | buy | 2 | 999 | 0 | TYPE_LIMIT | TIF_GTC | ref-1 | | + | aux2 | ETH/DEC21 | sell | 2 | 1499 | 0 | TYPE_LIMIT | TIF_GTC | ref-2 | | + | party1 | ETH/DEC21 | buy | 5 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | ref-3 | | + | party2 | ETH/DEC21 | sell | 5 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | ref-4 | | + And the network moves ahead "2" blocks + + Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC21" + And the market state should be "STATE_ACTIVE" for the market "ETH/DEC21" + And the mark price should be "1000" for the market "ETH/DEC21" + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 5000 | 5000 | + | party2 | USD | ETH/DEC21 | 2500 | 7500 | + + #order margin for aux1: limit price * size = 999*2=1998 + #order margin for aux2: (max price - limit price) * size = (1500-1301)*2=398 + # party1 maintenance margin level: position size * average entry price = 5*1000=5000 + # party2 maintenance margin level: position size * (max price - average entry price)=5*(1500-1000)=2500 + # Aux1: potential position * average price on book = 2 * 999 = 1998, but due to the MTM settlement the margin level + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 5000 | 5000 | 5000 | 5000 | cross margin | + | party2 | ETH/DEC21 | 2500 | 2500 | 2500 | 2500 | cross margin | + | aux2 | ETH/DEC21 | 2 | 2 | 2 | 2 | cross margin | + | aux1 | ETH/DEC21 | 1998 | 1998 | 1998 | 1998 | cross margin | + + #update mark price + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux1 | ETH/DEC21 | buy | 1 | 1100 | 0 | TYPE_LIMIT | TIF_GTC | aux1-2 | + | aux2 | ETH/DEC21 | sell | 1 | 1100 | 1 | TYPE_LIMIT | TIF_GTC | aux2-2 | + + And the network moves ahead "2" blocks + Then the mark price should be "1100" for the market "ETH/DEC21" + + # MTM settlement 5 long makes a profit of 500, 5 short loses 500 + # Now for aux1 and 2, the calculations from above still hold but more margin is required due to the open positions: + # aux1: position * 1100 + 999*2 = 1100 + 1998 = 3098 + # aux2: then placing the order (max price - average order price) * 3 = (1500 - (1301 + 1301 + 1100)/3) * 3 = (1500 - 1234) * 3 = 266 * 3 = 798 + # aux2's short position and potential margins are calculated separately as 2 * (1500-1301) + 1 * (1500 - 1100) = 398 + 400 = 798 + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 5000 | 5500 | + | party2 | USD | ETH/DEC21 | 2500 | 7000 | + | aux1 | USD | ETH/DEC21 | 3098 | 96908 | + | aux2 | USD | ETH/DEC21 | 402 | 99570 | + # The market is fully collateralised, switching to isolated margin is not supported + When the parties submit update margin mode: + | party | market | margin_mode | margin_factor | error | + | party1 | ETH/DEC21 | isolated margin | 0.5 | margin factor (0.5) must be greater than max(riskFactorLong (0.3696680542085883), riskFactorShort (0.5650462045113667)) + linearSlippageFactor (0.25) | + | party1 | ETH/DEC21 | isolated margin | 0.9 | isolated margin not permitted on fully collateralised markets | + + #update mark price to max_price + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | error | + | aux3 | ETH/DEC21 | buy | 2 | 1499 | 1 | TYPE_LIMIT | TIF_GTC | aux3-1 | | + + And the following trades should be executed: + | buyer | price | size | seller | + | aux3 | 1499 | 2 | aux2 | + + And the network moves ahead "2" blocks + Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC21" + Then the mark price should be "1499" for the market "ETH/DEC21" + + # MTM settlement 5 long makes a profit of 2000, 5 short loses 2000 + # Now for aux1 and 2, the calculations from above still hold but more margin is required due to the open positions: + # aux1: position * 1100 + 999*2 = 1100 + 1998 = 3098 + # aux2: short position of size 2, traded price at 1500, then margin: postion size * (max price - average entry price) = 3*(1100+1500*2)/3 + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 5000 | 7495 | + | party2 | USD | ETH/DEC21 | 2500 | 5005 | + | aux1 | USD | ETH/DEC21 | 3098 | 97307 | + | aux2 | USD | ETH/DEC21 | 402 | 99186 | + | aux3 | USD | ETH/DEC21 | 2998 | 96927 | + + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 5000 | 5000 | 5000 | 5000 | cross margin | + | party2 | ETH/DEC21 | 2500 | 2500 | 2500 | 2500 | cross margin | + | aux2 | ETH/DEC21 | 402 | 402 | 402 | 402 | cross margin | + | aux1 | ETH/DEC21 | 3098 | 3098 | 3098 | 3098 | cross margin | + #trade at max_price + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux4 | ETH/DEC21 | buy | 2 | 1499 | 0 | TYPE_LIMIT | TIF_GTC | aux4-1 | + | aux5 | ETH/DEC21 | sell | 2 | 1499 | 1 | TYPE_LIMIT | TIF_GTC | aux5-1 | + + And the network moves ahead "2" blocks + Then the parties should have the following account balances: + | party | asset | market id | margin | general | + | aux4 | USD | ETH/DEC21 | 2998 | 97017 | + | aux5 | USD | ETH/DEC21 | 2 | 99923 | + And the following transfers should happen: + | from | to | from account | to account | market id | amount | asset | + | aux5 | | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_FEES_MAKER | ETH/DEC21 | 15 | USD | + | aux5 | | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_FEES_INFRASTRUCTURE | ETH/DEC21 | 60 | USD | + | aux5 | | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_FEES_LIQUIDITY | ETH/DEC21 | 0 | USD | + | market | aux4 | ACCOUNT_TYPE_FEES_MAKER | ACCOUNT_TYPE_GENERAL | ETH/DEC21 | 15 | USD | + + # Terminate trading + When the oracles broadcast data signed with "0xCAFECAFE1": + | name | value | + | trading.terminated | true | + And the network moves ahead "2" blocks + Then the market state should be "STATE_TRADING_TERMINATED" for the market "ETH/DEC21" + + # Trigger settlement at price 0. + When the oracles broadcast data signed with "0xCAFECAFE1": + | name | value | + | prices.ETH.value | 0 | + And the network moves ahead "2" blocks + Then the last market state should be "STATE_SETTLED" for the market "ETH/DEC21" + And the following transfers should happen: + | from | to | from account | to account | market id | amount | asset | type | + | aux1 | | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC21 | 1499 | USD | TRANSFER_TYPE_LOSS | + | aux3 | | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC21 | 2998 | USD | TRANSFER_TYPE_LOSS | + | aux4 | | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC21 | 2998 | USD | TRANSFER_TYPE_LOSS | + | party1 | | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC21 | 5000 | USD | TRANSFER_TYPE_LOSS | + | party1 | | ACCOUNT_TYPE_GENERAL | ACCOUNT_TYPE_SETTLEMENT | ETH/DEC21 | 2495 | USD | TRANSFER_TYPE_LOSS | + | market | aux2 | ACCOUNT_TYPE_SETTLEMENT | ACCOUNT_TYPE_MARGIN | ETH/DEC21 | 4497 | USD | TRANSFER_TYPE_WIN | + | market | aux5 | ACCOUNT_TYPE_SETTLEMENT | ACCOUNT_TYPE_MARGIN | ETH/DEC21 | 2998 | USD | TRANSFER_TYPE_WIN | + | market | party2 | ACCOUNT_TYPE_SETTLEMENT | ACCOUNT_TYPE_MARGIN | ETH/DEC21 | 7495 | USD | TRANSFER_TYPE_WIN | + | aux1 | aux1 | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_GENERAL | ETH/DEC21 | 1599 | USD | TRANSFER_TYPE_CLEAR_ACCOUNT | + | aux2 | aux2 | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_GENERAL | ETH/DEC21 | 4899 | USD | TRANSFER_TYPE_CLEAR_ACCOUNT | + | aux5 | aux5 | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_GENERAL | ETH/DEC21 | 3000 | USD | TRANSFER_TYPE_CLEAR_ACCOUNT | + | party2 | party2 | ACCOUNT_TYPE_MARGIN | ACCOUNT_TYPE_GENERAL | ETH/DEC21 | 9995 | USD | TRANSFER_TYPE_CLEAR_ACCOUNT | + | party-lp | party-lp | ACCOUNT_TYPE_BOND | ACCOUNT_TYPE_GENERAL | ETH/DEC21 | 30000 | USD | TRANSFER_TYPE_CLEAR_ACCOUNT | + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 0 | 5000 | + | party2 | USD | ETH/DEC21 | 0 | 15000 | + | aux1 | USD | ETH/DEC21 | 0 | 98906 | + | aux2 | USD | ETH/DEC21 | 0 | 104085 | + | aux3 | USD | ETH/DEC21 | 0 | 96927 | + | aux4 | USD | ETH/DEC21 | 0 | 97017 | + | aux5 | USD | ETH/DEC21 | 0 | 102923 | diff --git a/core/integration/features/capped-futures/0016-PFUT-023.feature b/core/integration/features/capped-futures/0016-PFUT-023.feature new file mode 100644 index 00000000000..2052ce5bac0 --- /dev/null +++ b/core/integration/features/capped-futures/0016-PFUT-023.feature @@ -0,0 +1,142 @@ +Feature: When max_price is specified and the market is ran in a fully-collateralised mode and it has parties with open positions settling it at a price of 0 works correctly + + Background: + Given time is updated to "2019-11-30T00:00:00Z" + And the average block duration is "1" + + And the oracle spec for settlement data filtering data from "0xCAFECAFE1" named "ethDec21Oracle": + | property | type | binding | + | prices.ETH.value | TYPE_INTEGER | settlement data | + + And the oracle spec for trading termination filtering data from "0xCAFECAFE1" named "ethDec21Oracle": + | property | type | binding | + | trading.terminated | TYPE_BOOLEAN | trading termination | + + And the settlement data decimals for the oracle named "ethDec21Oracle" is given in "0" decimal places + + And the following network parameters are set: + | name | value | + | market.auction.minimumDuration | 1 | + | network.markPriceUpdateMaximumFrequency | 1s | + | market.liquidity.successorLaunchWindowLength | 1s | + | limits.markets.maxPeggedOrders | 4 | + + And the fees configuration named "fees-config-1": + | maker fee | infrastructure fee | + | 0.005 | 0.02 | + And the price monitoring named "price-monitoring-1": + | horizon | probability | auction extension | + | 3600000 | 0.99 | 300 | + And the log normal risk model named "lognormal-risk-model-1": + | risk aversion | tau | mu | r | sigma | + | 0.0002 | 0.01 | 0 | 0.0 | 1.2 | + + And the markets: + | id | quote name | asset | risk model | margin calculator | auction duration | fees | price monitoring | data source config | linear slippage factor | quadratic slippage factor | sla params | max price cap | fully collateralised | binary | + | ETH/DEC21 | ETH | USD | lognormal-risk-model-1 | default-capped-margin-calculator | 1 | fees-config-1 | default-none | ethDec21Oracle | 0.25 | 0 | default-futures | 1500 | true | false | + + @SLABug @NoPerp @Capped @CZero + Scenario: 0016-PFUT-023: no closeout happens when mark to market settlement is carried out at a price of `0` + Given the initial insurance pool balance is "10000" for all the markets + And the parties deposit on asset's general account the following amount: + | party | asset | amount | + | party1 | USD | 10000 | + | party2 | USD | 10000 | + | aux1 | USD | 100000 | + | aux2 | USD | 100000 | + | aux3 | USD | 100000 | + | aux4 | USD | 100000 | + | aux5 | USD | 100000 | + | party-lp | USD | 100000000 | + + And the parties submit the following liquidity provision: + | id | party | market id | commitment amount | fee | lp type | + | lp2 | party-lp | ETH/DEC21 | 30000 | 0 | submission | + + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | error | + | aux1 | ETH/DEC21 | buy | 2 | 1 | 0 | TYPE_LIMIT | TIF_GTC | ref-1 | | + | aux2 | ETH/DEC21 | sell | 2 | 1499 | 0 | TYPE_LIMIT | TIF_GTC | ref-2 | | + | party1 | ETH/DEC21 | buy | 5 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | ref-3 | | + | party2 | ETH/DEC21 | sell | 5 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | ref-4 | | + And the network moves ahead "2" blocks + + Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC21" + And the market state should be "STATE_ACTIVE" for the market "ETH/DEC21" + And the mark price should be "1000" for the market "ETH/DEC21" + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 5000 | 5000 | + | party2 | USD | ETH/DEC21 | 2500 | 7500 | + + #order margin for aux1: limit price * size = 999*2=1998 + #order margin for aux2: (max price - limit price) * size = (1500-1301)*2=398 + #party1 maintenance margin level: position size * average entry price = 5*1000=5000 + #party2 maintenance margin level: position size * (max price - average entry price)=5*(1500-1000)=2500 + #aux1: potential position * average price on book = 2 * 0 = 0 + #aux2: potential position * (max price - limit price) = 2 * 0 = 0 + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 5000 | 5000 | 5000 | 5000 | cross margin | + | party2 | ETH/DEC21 | 2500 | 2500 | 2500 | 2500 | cross margin | + | aux2 | ETH/DEC21 | 2 | 2 | 2 | 2 | cross margin | + | aux1 | ETH/DEC21 | 2 | 2 | 2 | 2 | cross margin | + + #update mark price + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux1 | ETH/DEC21 | buy | 1 | 1 | 0 | TYPE_LIMIT | TIF_GTC | aux1-2 | + | aux2 | ETH/DEC21 | sell | 1 | 1 | 1 | TYPE_LIMIT | TIF_GTC | aux2-2 | + + And debug trades + And debug detailed orderbook volumes for market "ETH/DEC21" + And the network moves ahead "2" blocks + Then the mark price should be "1" for the market "ETH/DEC21" + + And the following trades should be executed: + | buyer | price | size | seller | + | aux1 | 1 | 1 | aux2 | + + # MTM settlement 5 long makes a loss of 5000, 5 short makes a profit of 5000 + # Now for aux1 and 2, the calculations from above still hold but more margin is required due to the open positions: + # aux1: position * 1100 + 999*2 = 1100 + 1998 = 3098 + # aux2: then placing the order (max price - average order price) * 3 = (1500 - (1301 + 1301 + 1100)/3) * 3 = (1500 - 1234) * 3 = 266 * 3 = 798 + # aux2's short position and potential margins are calculated separately as 2 * (1500-1301) + 1 * (1500 - 1100) = 398 + 400 = 798 + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 5000 | 5 | + | party2 | USD | ETH/DEC21 | 2500 | 12495 | + | aux1 | USD | ETH/DEC21 | 3 | 99998 | + | aux2 | USD | ETH/DEC21 | 1501 | 98497 | + + #update mark price to max_price + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | error | + | aux3 | ETH/DEC21 | buy | 2 | 1499 | 1 | TYPE_LIMIT | TIF_GTC | aux3-1 | | + + And the following trades should be executed: + | buyer | price | size | seller | + | aux3 | 1499 | 2 | aux2 | + + And the network moves ahead "2" blocks + Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC21" + Then the mark price should be "1499" for the market "ETH/DEC21" + + # MTM settlement 5 long makes a profit of 2000, 5 short loses 2000 + # Now for aux1 and 2, the calculations from above still hold but more margin is required due to the open positions: + # aux1: position * 1100 + 999*2 = 1100 + 1998 = 3098 + # aux2: short position of size 2, traded price at 1500, then margin: postion size * (max price - average entry price) = 3*(1100+1500*2)/3 + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 5000 | 7495 | + | party2 | USD | ETH/DEC21 | 2500 | 5005 | + | aux1 | USD | ETH/DEC21 | 3 | 101496 | + | aux2 | USD | ETH/DEC21 | 1503 | 97012 | + | aux3 | USD | ETH/DEC21 | 2998 | 96927 | + + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 5000 | 5000 | 5000 | 5000 | cross margin | + | party2 | ETH/DEC21 | 2500 | 2500 | 2500 | 2500 | cross margin | + | aux2 | ETH/DEC21 | 1503 | 1503 | 1503 | 1503 | cross margin | + | aux1 | ETH/DEC21 | 3 | 3 | 3 | 3 | cross margin | diff --git a/core/integration/features/capped-futures/0016-PFUT-026.feature b/core/integration/features/capped-futures/0016-PFUT-026.feature new file mode 100644 index 00000000000..da94dc0aaa2 --- /dev/null +++ b/core/integration/features/capped-futures/0016-PFUT-026.feature @@ -0,0 +1,161 @@ +Feature: Futures market can be created with a with [hardcoded risk factors](./0018-RSKM-quant_risk_models.ipynb). + + Background: + Given the average block duration is "1" + + And the oracle spec for settlement data filtering data from "0xCAFECAFE1" named "ethDec21Oracle": + | property | type | binding | + | prices.ETH.value | TYPE_INTEGER | settlement data | + + And the oracle spec for trading termination filtering data from "0xCAFECAFE1" named "ethDec21Oracle": + | property | type | binding | + | trading.terminated | TYPE_BOOLEAN | trading termination | + + And the settlement data decimals for the oracle named "ethDec21Oracle" is given in "0" decimal places + + And the following network parameters are set: + | name | value | + | market.auction.minimumDuration | 1 | + | network.markPriceUpdateMaximumFrequency | 1s | + | market.liquidity.successorLaunchWindowLength | 1s | + | limits.markets.maxPeggedOrders | 4 | + + And the fees configuration named "fees-config-1": + | maker fee | infrastructure fee | + | 0.005 | 0.02 | + And the price monitoring named "price-monitoring-1": + | horizon | probability | auction extension | + | 3600000 | 0.99 | 300 | + And the simple risk model named "simple-risk-model": + | long | short | max move up | min move down | probability of trading | + | 0.1 | 0.2 | 100 | -100 | 0.2 | + And the log normal risk model named "lognormal-risk-model-1": + | risk aversion | tau | mu | r | sigma | + | 0.0002 | 0.01 | 0 | 0.0 | 1.2 | + And the markets: + | id | quote name | asset | risk model | margin calculator | auction duration | fees | price monitoring | data source config | linear slippage factor | quadratic slippage factor | sla params | max price cap | fully collateralised | binary | + | ETH/DEC21 | ETH | USD | simple-risk-model | default-margin-calculator | 1 | fees-config-1 | default-none | ethDec21Oracle | 0.25 | 0 | default-futures | 1500 | false | false | + + @SLABug @NoPerp @Capped @MarginCap + Scenario: 0016-PFUT-026, 0016-PFUT-028: Capped futures market can be created with a with [hardcoded risk factors](./0018-RSKM-quant_risk_models.ipynb). + Given the initial insurance pool balance is "10000" for all the markets + And the parties deposit on asset's general account the following amount: + | party | asset | amount | + | party1 | USD | 10000 | + | party2 | USD | 10000 | + | aux1 | USD | 100000 | + | aux2 | USD | 100000 | + | aux3 | USD | 100000 | + | aux4 | USD | 100000 | + | aux5 | USD | 100000 | + | party-lp | USD | 100000000 | + + And the parties submit the following liquidity provision: + | id | party | market id | commitment amount | fee | lp type | + | lp2 | party-lp | ETH/DEC21 | 30000 | 0 | submission | + + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | error | + | aux1 | ETH/DEC21 | buy | 2 | 1 | 0 | TYPE_LIMIT | TIF_GTC | ref-1 | | + | aux2 | ETH/DEC21 | sell | 2 | 1499 | 0 | TYPE_LIMIT | TIF_GTC | ref-2 | | + | party1 | ETH/DEC21 | buy | 5 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | ref-3 | | + | party2 | ETH/DEC21 | sell | 5 | 1000 | 0 | TYPE_LIMIT | TIF_GTC | ref-4 | | + And the network moves ahead "2" blocks + + Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC21" + And the market state should be "STATE_ACTIVE" for the market "ETH/DEC21" + And the mark price should be "1000" for the market "ETH/DEC21" + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 2100 | 7900 | + | party2 | USD | ETH/DEC21 | 2700 | 7300 | + + #order margin for aux1: limit price * size = 999*2=1998 + #order margin for aux2: (max price - limit price) * size = (1500-1301)*2=398 + #party1 maintenance margin level: position size * average entry price = 5*1000=5000 + #party2 maintenance margin level: position size * (max price - average entry price)=5*(1500-1000)=2500 + #aux1: potential position * average price on book = 2 * 0 = 0 + #aux2: potential position * (max price - limit price) = 2 * 0 = 0 + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 1750 | 1925 | 2100 | 2450 | cross margin | + | party2 | ETH/DEC21 | 2250 | 2475 | 2700 | 3150 | cross margin | + | aux2 | ETH/DEC21 | 400 | 440 | 480 | 560 | cross margin | + | aux1 | ETH/DEC21 | 200 | 220 | 240 | 280 | cross margin | + + #update mark price + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux1 | ETH/DEC21 | buy | 1 | 1 | 0 | TYPE_LIMIT | TIF_GTC | aux1-2 | + | aux2 | ETH/DEC21 | sell | 1 | 1 | 1 | TYPE_LIMIT | TIF_GTC | aux2-2 | + + And the network moves ahead "2" blocks + Then the mark price should be "1" for the market "ETH/DEC21" + + And the following trades should be executed: + | buyer | price | size | seller | + | aux1 | 1 | 1 | aux2 | + + # MTM settlement 5 long makes a loss of 5000, 5 short makes a profit of 5000 + # Now for aux1 and 2, the calculations from above still hold but more margin is required due to the open positions: + # aux1: position * 1100 + 999*2 = 1100 + 1998 = 3098 + # aux2: then placing the order (max price - average order price) * 3 = (1500 - (1301 + 1301 + 1100)/3) * 3 = (1500 - 1234) * 3 = 266 * 3 = 798 + # aux2's short position and potential margins are calculated separately as 2 * (1500-1301) + 1 * (1500 - 1100) = 398 + 400 = 798 + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 2 | 5003 | + | party2 | USD | ETH/DEC21 | 3 | 14992 | + | aux1 | USD | ETH/DEC21 | 1 | 100000 | + | aux2 | USD | ETH/DEC21 | 1 | 99997 | + + #update mark price to max_price + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | error | + | aux3 | ETH/DEC21 | buy | 2 | 1499 | 1 | TYPE_LIMIT | TIF_GTC | aux3-1 | | + + And the following trades should be executed: + | buyer | price | size | seller | + | aux3 | 1499 | 2 | aux2 | + + And the network moves ahead "2" blocks + Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC21" + Then the mark price should be "1499" for the market "ETH/DEC21" + + # MTM settlement 5 long makes a profit of 2000, 5 short loses 2000 + # Now for aux1 and 2, the calculations from above still hold but more margin is required due to the open positions: + # aux1: position * 1100 + 999*2 = 1100 + 1998 = 3098 + # aux2: short position of size 2, traded price at 1500, then margin: postion size * (max price - average entry price) = 3*(1100+1500*2)/3 + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 3150 | 9345 | + | party2 | USD | ETH/DEC21 | 4050 | 3455 | + | aux1 | USD | ETH/DEC21 | 990 | 100509 | + | aux2 | USD | ETH/DEC21 | 2430 | 96085 | + | aux3 | USD | ETH/DEC21 | 1260 | 98665 | + + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 2624 | 2886 | 3148 | 3673 | cross margin | + | party2 | ETH/DEC21 | 3373 | 3710 | 4047 | 4722 | cross margin | + | aux2 | ETH/DEC21 | 2024 | 2226 | 2428 | 2833 | cross margin | + | aux1 | ETH/DEC21 | 825 | 907 | 990 | 1155 | cross margin | + + When the markets are updated: + | id | risk model | + | ETH/DEC21 | lognormal-risk-model-1 | + # Place a trade, but don't move mark price. This will trigger the margin to be recalculated + And the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux2 | ETH/DEC21 | sell | 1 | 1499 | 0 | TYPE_LIMIT | TIF_GTC | aux2-3 | + | aux1 | ETH/DEC21 | buy | 1 | 1499 | 1 | TYPE_LIMIT | TIF_GTC | aux1-3 | + When the network moves ahead "2" blocks + Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC21" + # Ensure the mark price is still 1499 + And the mark price should be "1499" for the market "ETH/DEC21" + # Now check the margin levels + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 4645 | 5109 | 5574 | 6503 | cross margin | + | party2 | ETH/DEC21 | 6109 | 6719 | 7330 | 8552 | cross margin | + | aux2 | ETH/DEC21 | 4888 | 5376 | 5865 | 6843 | cross margin | + | aux1 | ETH/DEC21 | 2967 | 3263 | 3560 | 4153 | cross margin | diff --git a/core/integration/features/capped-futures/0019-MCAL-154.feature b/core/integration/features/capped-futures/0019-MCAL-154.feature new file mode 100644 index 00000000000..2a085322112 --- /dev/null +++ b/core/integration/features/capped-futures/0019-MCAL-154.feature @@ -0,0 +1,170 @@ +Feature: Margin calculation on a fully collateralised capped future + + Background: + Given time is updated to "2019-11-30T00:00:00Z" + And the average block duration is "1" + + And the oracle spec for settlement data filtering data from "0xCAFECAFE1" named "ethDec21Oracle": + | property | type | binding | + | prices.ETH.value | TYPE_INTEGER | settlement data | + + And the oracle spec for trading termination filtering data from "0xCAFECAFE1" named "ethDec21Oracle": + | property | type | binding | + | trading.terminated | TYPE_BOOLEAN | trading termination | + + And the settlement data decimals for the oracle named "ethDec21Oracle" is given in "0" decimal places + + And the following network parameters are set: + | name | value | + | market.auction.minimumDuration | 1 | + | network.markPriceUpdateMaximumFrequency | 0s | + | market.liquidity.successorLaunchWindowLength | 1s | + | limits.markets.maxPeggedOrders | 4 | + + And the fees configuration named "fees-config-1": + | maker fee | infrastructure fee | + | 0.005 | 0.02 | + And the price monitoring named "price-monitoring-1": + | horizon | probability | auction extension | + | 3600000 | 0.99 | 300 | + And the log normal risk model named "lognormal-risk-model-1": + | risk aversion | tau | mu | r | sigma | + | 0.0002 | 0.01 | 0 | 0.0 | 1.2 | + + And the markets: + | id | quote name | asset | risk model | margin calculator | auction duration | fees | price monitoring | data source config | linear slippage factor | quadratic slippage factor | sla params | max price cap | fully collateralised | binary | + | ETH/DEC21 | ETH | USD | lognormal-risk-model-1 | default-capped-margin-calculator | 1 | fees-config-1 | price-monitoring-1 | ethDec21Oracle | 0.25 | 0 | default-futures | 100 | true | false | + + @SLABug @NoPerp @Capped @CFC + Scenario: 0019-MCAL-154: Party A posts an order to buy 10 contracts at a price of 30, there's no other volume in that price range so the order lands on the book and the maintenance and initial margin levels for the party and order margin account balance are all equal to 300. + Given the initial insurance pool balance is "10000" for all the markets + And the parties deposit on asset's general account the following amount: + | party | asset | amount | + | party1 | USD | 10000 | + | party2 | USD | 10000 | + | aux1 | USD | 100000 | + | aux2 | USD | 100000 | + | aux3 | USD | 100000 | + | aux4 | USD | 100000 | + | aux5 | USD | 100000 | + | party-lp | USD | 100000000 | + + And the parties submit the following liquidity provision: + | id | party | market id | commitment amount | fee | lp type | + | lp2 | party-lp | ETH/DEC21 | 30000 | 0 | submission | + + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | error | + | aux1 | ETH/DEC21 | buy | 2 | 9 | 0 | TYPE_LIMIT | TIF_GTC | ref-1 | | + | aux2 | ETH/DEC21 | sell | 2 | 99 | 0 | TYPE_LIMIT | TIF_GTC | ref-2 | | + | party1 | ETH/DEC21 | buy | 5 | 50 | 0 | TYPE_LIMIT | TIF_GTC | ref-3 | | + | party2 | ETH/DEC21 | sell | 5 | 50 | 0 | TYPE_LIMIT | TIF_GTC | ref-4 | | + And the network moves ahead "2" blocks + + Then the trading mode should be "TRADING_MODE_CONTINUOUS" for the market "ETH/DEC21" + And the market state should be "STATE_ACTIVE" for the market "ETH/DEC21" + And the mark price should be "50" for the market "ETH/DEC21" + And the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 250 | 9750 | + | party2 | USD | ETH/DEC21 | 250 | 9750 | + | aux1 | USD | ETH/DEC21 | 18 | 99982 | + + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 250 | 250 | 250 | 250 | cross margin | + | party2 | ETH/DEC21 | 250 | 250 | 250 | 250 | cross margin | + | aux1 | ETH/DEC21 | 18 | 18 | 18 | 18 | cross margin | + | aux2 | ETH/DEC21 | 2 | 2 | 2 | 2 | cross margin | + + # The case the AC is actually about: buy 10@30 + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux3 | ETH/DEC21 | buy | 10 | 30 | 0 | TYPE_LIMIT | TIF_GTC | aux3-1 | + And the network moves ahead "2" blocks + Then the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 250 | 9750 | + | party2 | USD | ETH/DEC21 | 250 | 9750 | + | aux1 | USD | ETH/DEC21 | 18 | 99982 | + | aux3 | USD | ETH/DEC21 | 300 | 99700 | + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 250 | 250 | 250 | 250 | cross margin | + | party2 | ETH/DEC21 | 250 | 250 | 250 | 250 | cross margin | + | aux1 | ETH/DEC21 | 18 | 18 | 18 | 18 | cross margin | + | aux2 | ETH/DEC21 | 2 | 2 | 2 | 2 | cross margin | + | aux3 | ETH/DEC21 | 300 | 300 | 300 | 300 | cross margin | + + #0019-MCAL-155 + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux4 | ETH/DEC21 | sell | 15 | 20 | 1 | TYPE_LIMIT | TIF_GTC | aux3-1 | + And the network moves ahead "2" blocks + + And the following trades should be executed: + | buyer | price | size | seller | + | aux3 | 30 | 10 | aux4 | + + Then the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 250 | 9650 | + | party2 | USD | ETH/DEC21 | 250 | 9850 | + | aux1 | USD | ETH/DEC21 | 18 | 99982 | + | aux3 | USD | ETH/DEC21 | 300 | 99702 | + | aux4 | USD | ETH/DEC21 | 1100 | 98892 | + And the parties should have the following margin levels: + | party | market id | maintenance | search | initial | release | margin mode | + | party1 | ETH/DEC21 | 250 | 250 | 250 | 250 | cross margin | + | party2 | ETH/DEC21 | 250 | 250 | 250 | 250 | cross margin | + | aux1 | ETH/DEC21 | 18 | 18 | 18 | 18 | cross margin | + | aux2 | ETH/DEC21 | 2 | 2 | 2 | 2 | cross margin | + | aux3 | ETH/DEC21 | 300 | 300 | 300 | 300 | cross margin | + | aux4 | ETH/DEC21 | 1100 | 1100 | 1100 | 1100 | cross margin | + + #0019-MCAL-156 + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux4 | ETH/DEC21 | buy | 15 | 18 | 0 | TYPE_LIMIT | TIF_GTC | aux3-1 | + And the network moves ahead "2" blocks + + Then the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 250 | 9650 | + | party2 | USD | ETH/DEC21 | 250 | 9850 | + | aux1 | USD | ETH/DEC21 | 18 | 99982 | + | aux3 | USD | ETH/DEC21 | 300 | 99702 | + | aux4 | USD | ETH/DEC21 | 1100 | 98892 | + + #0019-MCAL-157 + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux4 | ETH/DEC21 | buy | 30 | 16 | 0 | TYPE_LIMIT | TIF_GTC | aux3-1 | + And the network moves ahead "2" blocks + + Then the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 250 | 9650 | + | party2 | USD | ETH/DEC21 | 250 | 9850 | + | aux1 | USD | ETH/DEC21 | 18 | 99982 | + | aux3 | USD | ETH/DEC21 | 300 | 99702 | + | aux4 | USD | ETH/DEC21 | 1420 | 98572 | + + #0019-MCAL-158 + When the parties place the following orders: + | party | market id | side | volume | price | resulting trades | type | tif | reference | + | aux3 | ETH/DEC21 | sell | 20 | 17 | 1 | TYPE_LIMIT | TIF_GTC | aux3-1 | + And the network moves ahead "2" blocks + + And the following trades should be executed: + | buyer | price | size | seller | + | aux4 | 18 | 15 | aux3 | + + Then the parties should have the following account balances: + | party | asset | market id | margin | general | + | party1 | USD | ETH/DEC21 | 250 | 9590 | + | party2 | USD | ETH/DEC21 | 250 | 9910 | + | aux1 | USD | ETH/DEC21 | 18 | 99982 | + | aux3 | USD | ETH/DEC21 | 785 | 99089 | + | aux4 | USD | ETH/DEC21 | 610 | 99504 | + diff --git a/core/integration/steps/the_markets.go b/core/integration/steps/the_markets.go index 1373353be09..2596c445007 100644 --- a/core/integration/steps/the_markets.go +++ b/core/integration/steps/the_markets.go @@ -653,12 +653,6 @@ func newMarket(config *market.Config, row marketRow) types.Market { tip := m.TradableInstrument.IntoProto() if row.IsCapped() { tip.MarginCalculator.FullyCollateralised = ptr.From(pCap.FullyCollateralised) - // scaling factors should be irrelevant - tip.MarginCalculator.ScalingFactors = &proto.ScalingFactors{ - SearchLevel: 1.0, - InitialMargin: 1.0, - CollateralRelease: 1.0, - } } err = config.RiskModels.LoadModel(row.riskModel(), tip) m.TradableInstrument = types.TradableInstrumentFromProto(tip)