code.vegaprotocol.io/vega@v0.79.0/core/monitor/price/pricemonitoring_test.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 price_test
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/monitor/price"
    24  	"code.vegaprotocol.io/vega/core/monitor/price/mocks"
    25  	"code.vegaprotocol.io/vega/core/types"
    26  	"code.vegaprotocol.io/vega/libs/num"
    27  	"code.vegaprotocol.io/vega/logging"
    28  	proto "code.vegaprotocol.io/vega/protos/vega"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func TestEmptyParametersList(t *testing.T) {
    35  	ctrl := gomock.NewController(t)
    36  	defer ctrl.Finish()
    37  	riskModel := mocks.NewMockRangeProvider(ctrl)
    38  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
    39  	currentPrice := num.NewUint(123)
    40  
    41  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
    42  
    43  	settings := &types.PriceMonitoringSettings{
    44  		Parameters: &types.PriceMonitoringParameters{
    45  			Triggers: []*types.PriceMonitoringTrigger{},
    46  		},
    47  	}
    48  
    49  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(4)
    50  	auctionStateMock.EXPECT().InAuction().Return(false).Times(4)
    51  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
    52  	statevar := mocks.NewMockStateVarEngine(ctrl)
    53  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
    54  
    55  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
    56  	require.NoError(t, err)
    57  	require.NotNil(t, pm)
    58  
    59  	pm.OnTimeUpdate(now)
    60  	b := pm.CheckPrice(context.TODO(), auctionStateMock, currentPrice, true, true)
    61  	require.False(t, b)
    62  
    63  	pm.OnTimeUpdate(now.Add(time.Second))
    64  	b = pm.CheckPrice(context.TODO(), auctionStateMock, currentPrice, true, true)
    65  	require.False(t, b)
    66  
    67  	pm.OnTimeUpdate(now.Add(time.Minute))
    68  	b = pm.CheckPrice(context.TODO(), auctionStateMock, currentPrice, true, true)
    69  	require.False(t, b)
    70  
    71  	pm.OnTimeUpdate(now.Add(time.Hour))
    72  	b = pm.CheckPrice(context.TODO(), auctionStateMock, currentPrice, true, true)
    73  	require.False(t, b)
    74  }
    75  
    76  func TestErrorWithNilRiskModel(t *testing.T) {
    77  	ctrl := gomock.NewController(t)
    78  	defer ctrl.Finish()
    79  	t1 := proto.PriceMonitoringTrigger{Horizon: 7200, Probability: "0.95", AuctionExtension: 300}
    80  	t2 := proto.PriceMonitoringTrigger{Horizon: 3600, Probability: "0.99", AuctionExtension: 60}
    81  
    82  	pSet := &proto.PriceMonitoringSettings{
    83  		Parameters: &proto.PriceMonitoringParameters{
    84  			Triggers: []*proto.PriceMonitoringTrigger{&t1, &t2},
    85  		},
    86  	}
    87  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
    88  	settings := types.PriceMonitoringSettingsFromProto(pSet)
    89  	statevar := mocks.NewMockStateVarEngine(ctrl)
    90  	// statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
    91  	// auctionStateMock.EXPECT().IsPriceAuction().Times(1).Return(false)
    92  	pm, err := price.NewMonitor("asset", "market", nil, auctionStateMock, settings, statevar, logging.NewTestLogger())
    93  	require.Error(t, err)
    94  	require.Nil(t, pm)
    95  }
    96  
    97  func TestGetHorizonYearFractions(t *testing.T) {
    98  	ctrl := gomock.NewController(t)
    99  	riskModel := mocks.NewMockRangeProvider(ctrl)
   100  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   101  	t1 := proto.PriceMonitoringTrigger{Horizon: 7200, Probability: "0.95", AuctionExtension: 300}
   102  	t2 := proto.PriceMonitoringTrigger{Horizon: 3600, Probability: "0.99", AuctionExtension: 60}
   103  
   104  	pSet := &proto.PriceMonitoringSettings{
   105  		Parameters: &proto.PriceMonitoringParameters{
   106  			Triggers: []*proto.PriceMonitoringTrigger{&t1, &t2},
   107  		},
   108  	}
   109  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   110  	statevar := mocks.NewMockStateVarEngine(ctrl)
   111  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   112  	auctionStateMock.EXPECT().IsPriceAuction().Times(1).Return(false)
   113  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   114  	require.NoError(t, err)
   115  	require.NotNil(t, pm)
   116  
   117  	yearFractions := pm.GetHorizonYearFractions()
   118  	require.Equal(t, 2, len(yearFractions))
   119  	require.Equal(t, horizonToYearFraction(t2.Horizon), yearFractions[0])
   120  	require.Equal(t, horizonToYearFraction(t1.Horizon), yearFractions[1])
   121  }
   122  
   123  func TestRecordPriceChange(t *testing.T) {
   124  	ctrl := gomock.NewController(t)
   125  	defer ctrl.Finish()
   126  	riskModel := mocks.NewMockRangeProvider(ctrl)
   127  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   128  	cp := num.NewUint(123)
   129  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   130  	t1 := proto.PriceMonitoringTrigger{Horizon: 7200, Probability: "0.95", AuctionExtension: 300}
   131  	t2 := proto.PriceMonitoringTrigger{Horizon: 3600, Probability: "0.99", AuctionExtension: 60}
   132  
   133  	pSet := &proto.PriceMonitoringSettings{
   134  		Parameters: &proto.PriceMonitoringParameters{
   135  			Triggers: []*proto.PriceMonitoringTrigger{&t1, &t2},
   136  		},
   137  	}
   138  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   139  
   140  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(4)
   141  	auctionStateMock.EXPECT().InAuction().Return(false).Times(4)
   142  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
   143  	statevar := mocks.NewMockStateVarEngine(ctrl)
   144  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   145  
   146  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   147  	require.NoError(t, err)
   148  	require.NotNil(t, pm)
   149  
   150  	pm.OnTimeUpdate(now)
   151  	b := pm.CheckPrice(context.TODO(), auctionStateMock, cp, true, true)
   152  	require.False(t, b)
   153  	one := num.NewUint(1)
   154  	cp1 := num.Sum(cp, one)      // plus 1
   155  	cp2 := num.Sum(cp, one, one) // plus 2
   156  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cp2, true, true)
   157  	require.False(t, b)
   158  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cp1, true, true)
   159  	require.False(t, b)
   160  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cp, true, true)
   161  	require.False(t, b)
   162  }
   163  
   164  func TestCheckBoundViolationsWithinCurrentTimeWith2HorizonProbabilityPairs(t *testing.T) {
   165  	ctrl := gomock.NewController(t)
   166  	defer ctrl.Finish()
   167  	riskModel := mocks.NewMockRangeProvider(ctrl)
   168  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   169  	cp := num.NewUint(123)
   170  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   171  	t1Time, t2Time := int64(60), int64(300)
   172  	t1 := proto.PriceMonitoringTrigger{Horizon: 3600, Probability: "0.99", AuctionExtension: t1Time}
   173  	t2 := proto.PriceMonitoringTrigger{Horizon: 7200, Probability: "0.95", AuctionExtension: t2Time}
   174  	pSet := &proto.PriceMonitoringSettings{
   175  		Parameters: &proto.PriceMonitoringParameters{
   176  			Triggers: []*proto.PriceMonitoringTrigger{&t1, &t2},
   177  		},
   178  	}
   179  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   180  
   181  	maxDown1, maxUp1, maxDown2, maxUp2 := num.NewUint(1), num.NewUint(2), num.NewUint(3), num.NewUint(4)
   182  
   183  	cpDec := num.DecimalFromUint(cp)
   184  	// get the price bounds
   185  	pMin1 := cpDec.Sub(num.DecimalFromUint(maxDown1))
   186  	pMin2 := cpDec.Sub(num.DecimalFromUint(maxDown2))
   187  	pMax1 := cpDec.Add(num.DecimalFromUint(maxUp1))
   188  	pMax2 := cpDec.Add(num.DecimalFromUint(maxUp2))
   189  	one := num.NewUint(1) // 1, just to tweak prices when calling CheckPrice
   190  	require.True(t, maxDown2.GT(maxDown1))
   191  	require.True(t, maxUp2.GT(maxUp1))
   192  
   193  	downFactors := []num.Decimal{pMin1.Div(cpDec), pMin2.Div(cpDec)}
   194  	upFactors := []num.Decimal{pMax1.Div(cpDec), pMax2.Div(cpDec)}
   195  
   196  	auctionStateMock.EXPECT().IsFBA().Return(false).AnyTimes()
   197  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).AnyTimes()
   198  	auctionStateMock.EXPECT().InAuction().Return(false).Times(14)
   199  	statevar := mocks.NewMockStateVarEngine(ctrl)
   200  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   201  
   202  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   203  	require.NoError(t, err)
   204  	require.NotNil(t, pm)
   205  	pm.UpdateTestFactors(downFactors, upFactors)
   206  
   207  	pm.OnTimeUpdate(now)
   208  	b := pm.CheckPrice(context.TODO(), auctionStateMock, cp, true, true)
   209  	require.False(t, b)
   210  
   211  	cPrice := num.Sum(cp, maxUp1)
   212  	cPrice = cPrice.Sub(cPrice, one)
   213  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   214  	require.False(t, b)
   215  
   216  	cPrice = num.Sum(cp, one)
   217  	cPrice = cPrice.Sub(cPrice, maxDown1)
   218  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   219  	require.False(t, b)
   220  
   221  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   222  	require.False(t, b)
   223  
   224  	cPrice = num.Sum(one, cPrice.Sub(cp, maxDown1)) // add one bc price bounds are now using Ceil for min price
   225  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   226  	require.False(t, b)
   227  
   228  	// set the min duration to equal auction extension 1
   229  	pm.SetMinDuration(time.Duration(t1.AuctionExtension) * time.Second)
   230  	end := types.AuctionDuration{Duration: t1.AuctionExtension}
   231  	auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   232  
   233  	delta := num.Sum(maxUp1, maxUp2)
   234  	cPrice = num.Sum(cp, delta.Div(delta, num.Sum(one, one)))
   235  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   236  	require.False(t, b)
   237  
   238  	// Reinstantiate price monitoring after auction to reset internal state
   239  	pm, err = price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   240  	require.NoError(t, err)
   241  	require.NotNil(t, pm)
   242  	pm.UpdateTestFactors(downFactors, upFactors)
   243  
   244  	pm.OnTimeUpdate(now)
   245  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cp, true, true)
   246  	require.False(t, b)
   247  
   248  	auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   249  	delta = num.Sum(maxDown1, maxDown2)
   250  	cPrice = cPrice.Sub(cp, delta.Div(delta, num.Sum(one, one)))
   251  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   252  	require.False(t, b)
   253  
   254  	// Reinstantiate price monitoring after auction to reset internal state
   255  	pm, err = price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   256  	require.NoError(t, err)
   257  	require.NotNil(t, pm)
   258  	pm.UpdateTestFactors(downFactors, upFactors)
   259  
   260  	pm.OnTimeUpdate(now)
   261  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cp, true, true)
   262  	require.False(t, b)
   263  
   264  	auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   265  	cPrice = num.Sum(cp, num.UintZero().Sub(maxUp2, one)) // max price bound is now floored, so sub 1 to stay below second price bound
   266  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   267  	require.False(t, b)
   268  
   269  	// Reinstantiate price monitoring after auction to reset internal state
   270  	pm, err = price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   271  	require.NoError(t, err)
   272  	require.NotNil(t, pm)
   273  	pm.UpdateTestFactors(downFactors, upFactors)
   274  
   275  	pm.OnTimeUpdate(now)
   276  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cp, true, true)
   277  	require.False(t, b)
   278  
   279  	auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   280  	cPrice = num.Sum(cPrice.Sub(cp, maxDown2), one) // add 1 back, avoid breaching both down limits
   281  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   282  	require.False(t, b)
   283  
   284  	// Reinstantiate price monitoring after auction to reset internal state
   285  	pm, err = price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   286  	require.NoError(t, err)
   287  	require.NotNil(t, pm)
   288  	pm.UpdateTestFactors(downFactors, upFactors)
   289  	pm.OnTimeUpdate(now)
   290  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cp, true, true)
   291  	require.False(t, b)
   292  
   293  	end = types.AuctionDuration{Duration: t1.AuctionExtension}
   294  	auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   295  	cPrice = num.Sum(cp, maxUp2, maxUp2)
   296  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   297  	require.False(t, b)
   298  	// recheck with same price, 2nd trigger should get breached now
   299  	end2 := types.AuctionDuration{Duration: t2.AuctionExtension}
   300  	auctionStateMock.EXPECT().InAuction().Return(true).Times(1)
   301  	auctionStateMock.EXPECT().ExtendAuctionPrice(end2).Times(1)
   302  
   303  	auctionEnd := now.Add(time.Duration(end.Duration) * time.Second)
   304  	now = auctionEnd.Add(time.Second)
   305  	pm.OnTimeUpdate(now)
   306  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   307  	require.False(t, b)
   308  
   309  	// recheck with same price, auction should end now
   310  	auctionStateMock.EXPECT().InAuction().Return(true).Times(1)
   311  	auctionStateMock.EXPECT().SetReadyToLeave().Times(1)
   312  	auctionEnd = auctionEnd.Add(time.Duration(end2.Duration) * time.Second)
   313  	auctionStateMock.EXPECT().ExpiresAt().Return(&auctionEnd)
   314  	now = auctionEnd.Add(time.Second)
   315  	pm.OnTimeUpdate(now)
   316  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   317  	require.False(t, b)
   318  
   319  	// Check with same price again after exiting, should not start auction now
   320  	auctionStateMock.EXPECT().InAuction().Return(false).Times(3)
   321  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   322  	require.False(t, b)
   323  
   324  	// Update factors and check again, should still be fine
   325  	pm.UpdateTestFactors(downFactors, upFactors)
   326  	pm.OnTimeUpdate(now)
   327  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   328  	require.False(t, b)
   329  
   330  	auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   331  	delta = num.Sum(maxDown2, maxDown2)
   332  	cPrice = cPrice.Sub(cp, delta)
   333  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true)
   334  	require.False(t, b)
   335  }
   336  
   337  func TestAuctionStartedAndEndendBy1Trigger(t *testing.T) {
   338  	ctrl := gomock.NewController(t)
   339  	defer ctrl.Finish()
   340  	riskModel := mocks.NewMockRangeProvider(ctrl)
   341  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   342  	price1 := num.NewUint(123)
   343  	ctx := context.Background()
   344  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   345  	t1 := proto.PriceMonitoringTrigger{Horizon: 600, Probability: "0.95", AuctionExtension: 60}
   346  	t2 := proto.PriceMonitoringTrigger{Horizon: 600, Probability: "0.99", AuctionExtension: 120}
   347  	pSet := &proto.PriceMonitoringSettings{
   348  		Parameters: &proto.PriceMonitoringParameters{
   349  			Triggers: []*proto.PriceMonitoringTrigger{&t1, &t2},
   350  		},
   351  	}
   352  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   353  
   354  	maxDown1, maxUp1 := num.NewUint(1), num.NewUint(2)
   355  	maxDown2 := num.Sum(maxUp1, maxUp1)   // yes, maxUp -> maxUp == maxDown*2, down2 == down1*4
   356  	maxUp2 := num.Sum(maxDown2, maxDown2) // double
   357  	decPrice := price1.ToDecimal()
   358  	p1Min1 := decPrice.Sub(num.DecimalFromUint(maxDown1))
   359  	p1Min2 := decPrice.Sub(num.DecimalFromUint(maxDown2))
   360  	p1Max1 := decPrice.Add(num.DecimalFromUint(maxUp1))
   361  	p1Max2 := decPrice.Add(num.DecimalFromUint(maxUp2))
   362  	downFactorsP1 := []num.Decimal{p1Min1.Div(decPrice), p1Min2.Div(decPrice)}
   363  	upFactorsP1 := []num.Decimal{p1Max1.Div(decPrice), p1Max2.Div(decPrice)}
   364  
   365  	auctionStateMock.EXPECT().IsFBA().Return(false).AnyTimes()
   366  	auctionStateMock.EXPECT().InAuction().Return(false).Times(2)
   367  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
   368  	statevar := mocks.NewMockStateVarEngine(ctrl)
   369  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   370  
   371  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   372  	require.NoError(t, err)
   373  	require.NotNil(t, pm)
   374  	pm.UpdateTestFactors(downFactorsP1, upFactorsP1)
   375  	pm.OnTimeUpdate(now)
   376  	b := pm.CheckPrice(ctx, auctionStateMock, price1, true, true)
   377  	require.False(t, b)
   378  
   379  	end := types.AuctionDuration{Duration: t1.AuctionExtension}
   380  	pm.SetMinDuration(time.Duration(t1.AuctionExtension) * time.Second)
   381  	auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   382  
   383  	delta := num.Sum().Sub(maxUp2, maxUp1)
   384  	cPrice := num.Sum(price1, delta)
   385  	pm.OnTimeUpdate(now)
   386  	b = pm.CheckPrice(context.TODO(), auctionStateMock, cPrice, true, true) // t1 violated only
   387  	require.False(t, b)
   388  
   389  	initialAuctionEnd := now.Add(time.Duration(t1.AuctionExtension) * time.Second)
   390  
   391  	auctionStateMock.EXPECT().InAuction().Return(true).Times(1)
   392  	auctionStateMock.EXPECT().ExpiresAt().Return(&initialAuctionEnd).Times(1)
   393  	auctionStateMock.EXPECT().SetReadyToLeave().Times(1)
   394  
   395  	afterInitialAuction := initialAuctionEnd.Add(time.Nanosecond)
   396  	pm.OnTimeUpdate(afterInitialAuction)
   397  	b = pm.CheckPrice(ctx, auctionStateMock, cPrice, true, true) // price should be accepted now
   398  	require.False(t, b)
   399  }
   400  
   401  func TestAuctionStartedAndEndendBy2Triggers(t *testing.T) {
   402  	ctrl := gomock.NewController(t)
   403  	riskModel := mocks.NewMockRangeProvider(ctrl)
   404  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   405  	price1 := num.NewUint(123)
   406  	ctx := context.Background()
   407  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   408  	t1 := proto.PriceMonitoringTrigger{Horizon: 600, Probability: "0.95", AuctionExtension: 60}
   409  	t2 := proto.PriceMonitoringTrigger{Horizon: 600, Probability: "0.99", AuctionExtension: 120}
   410  	pSet := &proto.PriceMonitoringSettings{
   411  		Parameters: &proto.PriceMonitoringParameters{
   412  			Triggers: []*proto.PriceMonitoringTrigger{&t1, &t2},
   413  		},
   414  	}
   415  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   416  
   417  	_, _, _, _, maxUp1 := getPriceBounds(price1, 1, 2)
   418  	_, _, _, _, maxUp2 := getPriceBounds(price1, 1*4, 2*4)
   419  
   420  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(2)
   421  	auctionStateMock.EXPECT().InAuction().Return(false).Times(2)
   422  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
   423  	statevar := mocks.NewMockStateVarEngine(ctrl)
   424  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   425  
   426  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   427  	require.NoError(t, err)
   428  	require.NotNil(t, pm)
   429  
   430  	pm.OnTimeUpdate(now)
   431  	b := pm.CheckPrice(ctx, auctionStateMock, price1, true, true)
   432  	require.False(t, b)
   433  
   434  	end := types.AuctionDuration{Duration: t1.AuctionExtension + t2.AuctionExtension}
   435  	pm.SetMinDuration(time.Duration(end.Duration) * time.Second)
   436  	// auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   437  
   438  	cPrice := num.Sum(price1, maxUp2, maxUp1)
   439  	b = pm.CheckPrice(ctx, auctionStateMock, cPrice, true, true) // t1 violated only
   440  	require.False(t, b)
   441  
   442  	initialAuctionEnd := now.Add(time.Duration(t1.AuctionExtension+t2.AuctionExtension) * time.Second)
   443  
   444  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(1)
   445  	auctionStateMock.EXPECT().InAuction().Return(true).Times(1)
   446  	auctionStateMock.EXPECT().ExpiresAt().Return(&initialAuctionEnd).Times(1)
   447  	auctionStateMock.EXPECT().SetReadyToLeave().Times(1)
   448  
   449  	afterInitialAuction := initialAuctionEnd.Add(time.Nanosecond)
   450  	pm.OnTimeUpdate(afterInitialAuction)
   451  	b = pm.CheckPrice(ctx, auctionStateMock, cPrice, true, true) // price should be accepted now
   452  	require.False(t, b)
   453  }
   454  
   455  func TestAuctionStartedAndEndendBy1TriggerAndExtendedBy2nd(t *testing.T) {
   456  	// Also verifies that GetCurrentBounds() works as expected
   457  	ctrl := gomock.NewController(t)
   458  	defer ctrl.Finish()
   459  	riskModel := mocks.NewMockRangeProvider(ctrl)
   460  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   461  	price1 := num.NewUint(123)
   462  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   463  	t1 := proto.PriceMonitoringTrigger{Horizon: 600, Probability: "0.95", AuctionExtension: 60}
   464  	t2 := proto.PriceMonitoringTrigger{Horizon: 600, Probability: "0.99", AuctionExtension: 120}
   465  	pSet := &proto.PriceMonitoringSettings{
   466  		Parameters: &proto.PriceMonitoringParameters{
   467  			Triggers: []*proto.PriceMonitoringTrigger{&t1, &t2},
   468  		},
   469  	}
   470  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   471  	ctx := context.Background()
   472  	decPrice, pMin1, pMax1, _, maxUp1 := getPriceBounds(price1, 1, 2)
   473  	_, pMin2, pMax2, _, maxUp2 := getPriceBounds(price1, 1*4, 2*4)
   474  
   475  	one := num.NewUint(1)
   476  	t1lb1, _ := num.UintFromDecimal(pMin1)
   477  	t1lb1.AddSum(one) // account for value being ceil'ed
   478  	t1ub1, _ := num.UintFromDecimal(pMax1)
   479  	t1ub1.Sub(t1ub1, one) // floor
   480  	t2lb1, _ := num.UintFromDecimal(pMin2)
   481  	t2lb1.AddSum(one) // again: ceil
   482  	t2ub1, _ := num.UintFromDecimal(pMax2)
   483  
   484  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(2)
   485  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
   486  	auctionStateMock.EXPECT().InAuction().Return(false).Times(2)
   487  	statevar := mocks.NewMockStateVarEngine(ctrl)
   488  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   489  
   490  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   491  	downFactors := []num.Decimal{pMin1.Div(decPrice), pMin2.Div(decPrice)}
   492  	upFactors := []num.Decimal{pMax1.Div(decPrice), pMax2.Div(decPrice)}
   493  	pm.UpdateTestFactors(downFactors, upFactors)
   494  
   495  	require.NoError(t, err)
   496  	require.NotNil(t, pm)
   497  
   498  	pm.OnTimeUpdate(now)
   499  	b := pm.CheckPrice(ctx, auctionStateMock, price1, true, true)
   500  	require.False(t, b)
   501  
   502  	bounds := pm.GetCurrentBounds()
   503  	require.Len(t, bounds, 2)
   504  	require.Equal(t, bounds[0].Trigger.IntoProto(), &t1)
   505  	require.True(t, bounds[0].MinValidPrice.EQ(t1lb1))
   506  	require.True(t, bounds[0].MaxValidPrice.EQ(t1ub1))
   507  	require.Equal(t, bounds[0].ReferencePrice, decPrice)
   508  	require.Equal(t, bounds[1].Trigger.IntoProto(), &t2)
   509  	require.True(t, bounds[1].MinValidPrice.EQ(t2lb1))
   510  	require.True(t, bounds[1].MaxValidPrice.EQ(t2ub1))
   511  	require.Equal(t, bounds[1].ReferencePrice, decPrice)
   512  
   513  	end := types.AuctionDuration{Duration: t1.AuctionExtension}
   514  	pm.SetMinDuration(time.Duration(end.Duration) * time.Second)
   515  	auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   516  
   517  	cPrice := num.Sum(price1, maxUp2)
   518  	cPrice.Sub(cPrice, maxUp1)
   519  	cp2 := cPrice
   520  	b = pm.CheckPrice(ctx, auctionStateMock, cp2, true, true) // t1 violated only
   521  	require.False(t, b)
   522  
   523  	initialAuctionEnd := now.Add(time.Duration(t1.AuctionExtension) * time.Second)
   524  
   525  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(1)
   526  	auctionStateMock.EXPECT().InAuction().Return(true).Times(1)
   527  	auctionStateMock.EXPECT().ExpiresAt().Return(&initialAuctionEnd).Times(1)
   528  
   529  	bounds = pm.GetCurrentBounds()
   530  	require.Len(t, bounds, 1)
   531  	require.Equal(t, bounds[0].Trigger.IntoProto(), &t2)
   532  	require.True(t, bounds[0].MinValidPrice.EQ(t2lb1))
   533  	require.True(t, bounds[0].MaxValidPrice.EQ(t2ub1))
   534  	require.Equal(t, bounds[0].ReferencePrice, decPrice)
   535  
   536  	afterInitialAuction := initialAuctionEnd.Add(time.Nanosecond)
   537  	now = afterInitialAuction
   538  
   539  	cPrice = num.Sum(price1, maxUp2, maxUp1)
   540  	end2 := types.AuctionDuration{Duration: t2.AuctionExtension}
   541  	auctionStateMock.EXPECT().ExtendAuctionPrice(end2).Times(1)
   542  	pm.OnTimeUpdate(afterInitialAuction)
   543  	cp3 := cPrice
   544  	b = pm.CheckPrice(ctx, auctionStateMock, cp3, true, true) // price should violated 2nd trigger and result in auction extension
   545  	require.False(t, b)
   546  
   547  	bounds = pm.GetCurrentBounds()
   548  	require.Len(t, bounds, 0)
   549  
   550  	extendedAuctionEnd := now.Add(time.Duration(t1.AuctionExtension+t2.AuctionExtension) * time.Second)
   551  
   552  	// get new bounds
   553  	_, pMin1, pMax1, _, _ = getPriceBounds(cPrice, 1, 2)
   554  	_, pMin2, pMax2, _, _ = getPriceBounds(cPrice, 1*4, 2*4)
   555  
   556  	t1lb1, _ = num.UintFromDecimal(pMin1)
   557  	t1lb1.AddSum(one) // again ceil
   558  	t1ub1, _ = num.UintFromDecimal(pMax1)
   559  	t1ub1.Sub(t1ub1, one) // floor...
   560  	t2lb1, _ = num.UintFromDecimal(pMin2)
   561  	t2lb1.AddSum(one) // ceil...
   562  	t2ub1, _ = num.UintFromDecimal(pMax2)
   563  	t2ub1.Sub(t2ub1, one) // floor...
   564  
   565  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(1)
   566  	auctionStateMock.EXPECT().InAuction().Return(true).Times(1)
   567  	auctionStateMock.EXPECT().SetReadyToLeave().Times(1)
   568  
   569  	afterExtendedAuction := extendedAuctionEnd.Add(time.Nanosecond)
   570  	pm.OnTimeUpdate(afterExtendedAuction)
   571  	b = pm.CheckPrice(ctx, auctionStateMock, cp3, true, true) // price should be accepted now
   572  	require.False(t, b)
   573  }
   574  
   575  func TestAuctionStartedBy1TriggerAndNotExtendedBy2ndStaleTrigger(t *testing.T) {
   576  	// Also verifies that GetCurrentBounds() works as expected
   577  	ctrl := gomock.NewController(t)
   578  	defer ctrl.Finish()
   579  	riskModel := mocks.NewMockRangeProvider(ctrl)
   580  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   581  	price1 := num.NewUint(123)
   582  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   583  	t1 := proto.PriceMonitoringTrigger{Horizon: 6, Probability: "0.95", AuctionExtension: 60}
   584  	t2 := proto.PriceMonitoringTrigger{Horizon: 6, Probability: "0.99", AuctionExtension: 120}
   585  	pSet := &proto.PriceMonitoringSettings{
   586  		Parameters: &proto.PriceMonitoringParameters{
   587  			Triggers: []*proto.PriceMonitoringTrigger{&t1, &t2},
   588  		},
   589  	}
   590  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   591  	ctx := context.Background()
   592  	decPrice, pMin1, pMax1, _, maxUp1 := getPriceBounds(price1, 1, 2)
   593  	_, pMin2, pMax2, _, maxUp2 := getPriceBounds(price1, 1*4, 2*4)
   594  
   595  	one := num.NewUint(1)
   596  	t1lb1, _ := num.UintFromDecimal(pMin1)
   597  	t1lb1.AddSum(one) // account for value being ceil'ed
   598  	t1ub1, _ := num.UintFromDecimal(pMax1)
   599  	t1ub1.Sub(t1ub1, one) // floor
   600  	t2lb1, _ := num.UintFromDecimal(pMin2)
   601  	t2lb1.AddSum(one) // again: ceil
   602  	t2ub1, _ := num.UintFromDecimal(pMax2)
   603  
   604  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(2)
   605  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
   606  	auctionStateMock.EXPECT().InAuction().Return(false).Times(2)
   607  	statevar := mocks.NewMockStateVarEngine(ctrl)
   608  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   609  
   610  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   611  	downFactors := []num.Decimal{pMin1.Div(decPrice), pMin2.Div(decPrice)}
   612  	upFactors := []num.Decimal{pMax1.Div(decPrice), pMax2.Div(decPrice)}
   613  	pm.UpdateTestFactors(downFactors, upFactors)
   614  
   615  	require.NoError(t, err)
   616  	require.NotNil(t, pm)
   617  
   618  	pm.OnTimeUpdate(now)
   619  	b := pm.CheckPrice(ctx, auctionStateMock, price1, true, true)
   620  	require.False(t, b)
   621  
   622  	bounds := pm.GetCurrentBounds()
   623  	require.Len(t, bounds, 2)
   624  	require.Equal(t, bounds[0].Trigger.IntoProto(), &t1)
   625  	require.True(t, bounds[0].MinValidPrice.EQ(t1lb1))
   626  	require.True(t, bounds[0].MaxValidPrice.EQ(t1ub1))
   627  	require.Equal(t, bounds[0].ReferencePrice, decPrice)
   628  	require.Equal(t, bounds[1].Trigger.IntoProto(), &t2)
   629  	require.True(t, bounds[1].MinValidPrice.EQ(t2lb1))
   630  	require.True(t, bounds[1].MaxValidPrice.EQ(t2ub1))
   631  	require.Equal(t, bounds[1].ReferencePrice, decPrice)
   632  
   633  	end := types.AuctionDuration{Duration: t1.AuctionExtension}
   634  	pm.SetMinDuration(time.Duration(end.Duration) * time.Second)
   635  	auctionStateMock.EXPECT().StartPriceAuction(now, &end).Times(1)
   636  
   637  	cPrice := num.Sum(price1, maxUp2)
   638  	cPrice.Sub(cPrice, maxUp1)
   639  	cp2 := cPrice
   640  	b = pm.CheckPrice(ctx, auctionStateMock, cp2, true, true) // t1 violated only
   641  	require.False(t, b)
   642  
   643  	initialAuctionEnd := now.Add(time.Duration(t1.AuctionExtension) * time.Second)
   644  
   645  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(1)
   646  	auctionStateMock.EXPECT().InAuction().Return(true).Times(1)
   647  
   648  	bounds = pm.GetCurrentBounds()
   649  	require.Len(t, bounds, 1)
   650  	require.Equal(t, bounds[0].Trigger.IntoProto(), &t2)
   651  	require.True(t, bounds[0].MinValidPrice.EQ(t2lb1))
   652  	require.True(t, bounds[0].MaxValidPrice.EQ(t2ub1))
   653  	require.Equal(t, bounds[0].ReferencePrice, decPrice)
   654  
   655  	afterInitialAuction := initialAuctionEnd.Add(time.Nanosecond)
   656  	now = afterInitialAuction
   657  
   658  	auctionStateMock.EXPECT().ExtendAuctionPrice(gomock.Any()).Times(1)
   659  
   660  	cPrice = num.Sum(price1, maxUp2, maxUp1)
   661  	pm.OnTimeUpdate(afterInitialAuction)
   662  	cp3 := cPrice
   663  	b = pm.CheckPrice(ctx, auctionStateMock, cp3, true, true) // price should violated 2nd trigger and result in auction extension
   664  	require.False(t, b)
   665  }
   666  
   667  func TestMarketInOpeningAuction(t *testing.T) {
   668  	ctrl := gomock.NewController(t)
   669  	defer ctrl.Finish()
   670  	riskModel := mocks.NewMockRangeProvider(ctrl)
   671  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   672  	currentPrice := num.NewUint(123)
   673  	t1 := proto.PriceMonitoringTrigger{Horizon: 7200, Probability: "0.95", AuctionExtension: 300}
   674  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   675  	pSet := &proto.PriceMonitoringSettings{
   676  		Parameters: &proto.PriceMonitoringParameters{
   677  			Triggers: []*proto.PriceMonitoringTrigger{&t1},
   678  		},
   679  	}
   680  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   681  
   682  	ctx := context.Background()
   683  
   684  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(1)
   685  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
   686  	auctionStateMock.EXPECT().InAuction().Return(true).Times(1)
   687  	end := now.Add(time.Second)
   688  	auctionStateMock.EXPECT().ExpiresAt().Return(&end).Times(1)
   689  	statevar := mocks.NewMockStateVarEngine(ctrl)
   690  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   691  
   692  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   693  	require.NoError(t, err)
   694  	require.NotNil(t, pm)
   695  
   696  	pm.OnTimeUpdate(now)
   697  	b := pm.CheckPrice(ctx, auctionStateMock, currentPrice, true, true)
   698  	require.False(t, b)
   699  }
   700  
   701  func TestMarketInGenericAuction(t *testing.T) {
   702  	ctrl := gomock.NewController(t)
   703  	defer ctrl.Finish()
   704  	riskModel := mocks.NewMockRangeProvider(ctrl)
   705  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   706  	currentPrice := num.NewUint(123)
   707  	t1 := proto.PriceMonitoringTrigger{Horizon: 7200, Probability: "0.95", AuctionExtension: 300}
   708  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   709  
   710  	pSet := &proto.PriceMonitoringSettings{
   711  		Parameters: &proto.PriceMonitoringParameters{
   712  			Triggers: []*proto.PriceMonitoringTrigger{&t1},
   713  		},
   714  	}
   715  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   716  
   717  	_, _, _, maxDown, maxUp := getPriceBounds(currentPrice, 5, 10)
   718  	one := num.NewUint(1)
   719  	ctx := context.Background()
   720  
   721  	// price monitoring starts with auction, not initialised, so there's no fixed price level it'll check
   722  	auctionStateMock.EXPECT().IsFBA().Return(false).AnyTimes()
   723  	auctionStateMock.EXPECT().InAuction().Return(true).Times(4)
   724  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
   725  	auctionStateMock.EXPECT().CanLeave().Return(false).AnyTimes()
   726  	statevar := mocks.NewMockStateVarEngine(ctrl)
   727  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   728  	end := now.Add(time.Second)
   729  	auctionStateMock.EXPECT().ExpiresAt().Return(&end).AnyTimes()
   730  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   731  	require.NoError(t, err)
   732  	require.NotNil(t, pm)
   733  
   734  	pm.OnTimeUpdate(now)
   735  	pm.ResetPriceHistory(currentPrice)
   736  
   737  	cPrice := num.Sum(currentPrice, maxUp)
   738  	cPrice.Sub(cPrice, one)
   739  	b := pm.CheckPrice(ctx, auctionStateMock, cPrice, true, true)
   740  	require.False(t, b)
   741  
   742  	cPrice.Sub(num.Sum(currentPrice, one), maxDown)
   743  	cp3 := cPrice
   744  	b = pm.CheckPrice(ctx, auctionStateMock, cp3, true, true)
   745  	require.False(t, b)
   746  
   747  	extension := types.AuctionDuration{Duration: t1.AuctionExtension}
   748  	auctionStateMock.EXPECT().ExtendAuctionPrice(extension).Times(1)
   749  	cPrice = num.Sum(currentPrice, maxUp, maxUp)
   750  	cp4 := cPrice
   751  	b = pm.CheckPrice(ctx, auctionStateMock, cp4, true, true)
   752  	require.False(t, b)
   753  
   754  	cPrice = num.Sum(maxDown, maxDown)
   755  	cPrice.Sub(currentPrice, cPrice)
   756  	cp5 := cPrice
   757  	b = pm.CheckPrice(ctx, auctionStateMock, cp5, true, true)
   758  	require.False(t, b)
   759  }
   760  
   761  func TestGetValidPriceRange_NoTriggers(t *testing.T) {
   762  	ctrl := gomock.NewController(t)
   763  	defer ctrl.Finish()
   764  	riskModel := mocks.NewMockRangeProvider(ctrl)
   765  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   766  	currentPrice := num.NewUint(123)
   767  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   768  	ctx := context.Background()
   769  
   770  	settings := &types.PriceMonitoringSettings{
   771  		Parameters: &types.PriceMonitoringParameters{
   772  			Triggers: []*types.PriceMonitoringTrigger{},
   773  		},
   774  	}
   775  
   776  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(1)
   777  	auctionStateMock.EXPECT().InAuction().Return(false).Times(1)
   778  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
   779  	statevar := mocks.NewMockStateVarEngine(ctrl)
   780  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   781  
   782  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   783  	require.NoError(t, err)
   784  	require.NotNil(t, pm)
   785  
   786  	expMax := num.MaxUint()
   787  	min, max := pm.GetValidPriceRange()
   788  	require.True(t, min.Representation().IsZero())
   789  	require.Equal(t, expMax.String(), max.Representation().String())
   790  
   791  	pm.OnTimeUpdate(now)
   792  	b := pm.CheckPrice(ctx, auctionStateMock, currentPrice, true, true)
   793  	require.False(t, b)
   794  
   795  	min, max = pm.GetValidPriceRange()
   796  	require.True(t, min.Representation().IsZero())
   797  	require.Equal(t, expMax.String(), max.Representation().String())
   798  }
   799  
   800  func TestGetValidPriceRange_2triggers(t *testing.T) {
   801  	ctrl := gomock.NewController(t)
   802  	defer ctrl.Finish()
   803  	riskModel := mocks.NewMockRangeProvider(ctrl)
   804  	auctionStateMock := mocks.NewMockAuctionState(ctrl)
   805  	currentPrice := num.NewUint(123)
   806  	now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)
   807  	var t1Time int64 = 60
   808  	var t2Time int64 = 300
   809  	t1 := proto.PriceMonitoringTrigger{Horizon: 3600, Probability: "0.99", AuctionExtension: t1Time}
   810  	t2 := proto.PriceMonitoringTrigger{Horizon: 7200, Probability: "0.95", AuctionExtension: t2Time}
   811  	pSet := &proto.PriceMonitoringSettings{
   812  		Parameters: &proto.PriceMonitoringParameters{
   813  			Triggers: []*proto.PriceMonitoringTrigger{&t1, &t2},
   814  		},
   815  	}
   816  	settings := types.PriceMonitoringSettingsFromProto(pSet)
   817  
   818  	ctx := context.Background()
   819  	_, pMin1, pMax1, maxDown1, maxUp1 := getPriceBounds(currentPrice, 1, 2)
   820  	_, pMin2, pMax2, _, _ := getPriceBounds(currentPrice, 3, 4)
   821  	one := num.NewUint(1)
   822  	currentPriceD := currentPrice.ToDecimal()
   823  	auctionStateMock.EXPECT().IsFBA().Return(false).Times(12)
   824  	auctionStateMock.EXPECT().InAuction().Return(false).Times(12)
   825  	auctionStateMock.EXPECT().IsPriceAuction().Return(false).Times(1)
   826  	statevar := mocks.NewMockStateVarEngine(ctrl)
   827  	statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
   828  
   829  	pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
   830  	require.NoError(t, err)
   831  	require.NotNil(t, pm)
   832  	downFactors := []num.Decimal{pMin1.Div(currentPriceD), pMin2.Div(currentPriceD)}
   833  	upFactors := []num.Decimal{pMax1.Div(currentPriceD), pMax2.Div(currentPriceD)}
   834  
   835  	pm.UpdateTestFactors(downFactors, upFactors)
   836  
   837  	pm.OnTimeUpdate(now)
   838  	b := pm.CheckPrice(ctx, auctionStateMock, currentPrice, true, true)
   839  	require.False(t, b)
   840  
   841  	_, _ = pm.GetValidPriceRange()
   842  	now = now.Add(time.Second)
   843  	cPrice := num.Sum(currentPrice, maxUp1)
   844  	cPrice.Sub(cPrice, one)
   845  	pm.OnTimeUpdate(now)
   846  	b = pm.CheckPrice(ctx, auctionStateMock, cPrice, true, true)
   847  	require.False(t, b)
   848  
   849  	_, _ = pm.GetValidPriceRange()
   850  	now = now.Add(time.Minute)
   851  	cPrice = num.Sum(currentPrice, one)
   852  	cPrice.Sub(cPrice, maxDown1)
   853  	pm.OnTimeUpdate(now)
   854  	b = pm.CheckPrice(ctx, auctionStateMock, cPrice, true, true)
   855  	require.False(t, b)
   856  
   857  	_, _ = pm.GetValidPriceRange()
   858  	now = now.Add(time.Hour)
   859  	cPrice = num.Sum(currentPrice, maxUp1)
   860  	cPrice.Sub(cPrice, one)
   861  	pm.OnTimeUpdate(now)
   862  	b = pm.CheckPrice(ctx, auctionStateMock, cPrice, true, true)
   863  	require.False(t, b)
   864  
   865  	_, _ = pm.GetValidPriceRange()
   866  	now = now.Add(time.Minute)
   867  	cPrice.Sub(currentPrice, maxDown1)
   868  	cPrice.AddSum(one)
   869  	pm.OnTimeUpdate(now)
   870  	b = pm.CheckPrice(ctx, auctionStateMock, cPrice, true, true)
   871  	require.False(t, b)
   872  
   873  	min, max := pm.GetValidPriceRange()
   874  	b = pm.CheckPrice(ctx, auctionStateMock, min.Representation(), true, true)
   875  	require.False(t, b)
   876  
   877  	b = pm.CheckPrice(ctx, auctionStateMock, max.Representation(), true, true)
   878  	require.False(t, b)
   879  
   880  	// Should trigger an auction
   881  	auctionStateMock.EXPECT().StartPriceAuction(now, gomock.Any()).Times(1)
   882  
   883  	cPrice.Sub(min.Representation(), one)
   884  	b = pm.CheckPrice(ctx, auctionStateMock, cPrice, true, true)
   885  	require.False(t, b)
   886  
   887  	now = now.Add(time.Second)
   888  	pm.OnTimeUpdate(now)
   889  	b = pm.CheckPrice(ctx, auctionStateMock, currentPrice, true, true)
   890  	require.False(t, b)
   891  
   892  	min, max = pm.GetValidPriceRange()
   893  
   894  	b = pm.CheckPrice(ctx, auctionStateMock, min.Representation(), true, true)
   895  	require.False(t, b)
   896  
   897  	b = pm.CheckPrice(ctx, auctionStateMock, max.Representation(), true, true)
   898  	require.False(t, b)
   899  
   900  	// Should trigger an auction
   901  	auctionStateMock.EXPECT().StartPriceAuction(now, gomock.Any()).Times(1)
   902  	cPrice.Add(max.Representation(), one)
   903  	cp11 := cPrice
   904  	b = pm.CheckPrice(ctx, auctionStateMock, cp11, true, true)
   905  	require.False(t, b)
   906  }
   907  
   908  var secondsPerYear = num.DecimalFromFloat(365.25 * 24 * 60 * 60)
   909  
   910  func getPriceBounds(price *num.Uint, min, max uint64) (decPr, minPr, maxPr num.Decimal, mn, mx *num.Uint) {
   911  	decPr = price.ToDecimal()
   912  	mn = num.NewUint(min)
   913  	mx = num.NewUint(max)
   914  	minPr = decPr.Sub(mn.ToDecimal())
   915  	maxPr = decPr.Add(mx.ToDecimal())
   916  	return
   917  }
   918  
   919  func horizonToYearFraction(horizon int64) num.Decimal {
   920  	hdec := num.DecimalFromFloat(float64(horizon))
   921  	return hdec.Div(secondsPerYear)
   922  }