Skip to content

Commit

Permalink
fix: account for active flag when sorting bounds
Browse files Browse the repository at this point in the history
Signed-off-by: Elias Van Ootegem <elias@vega.xyz>
  • Loading branch information
EVODelavega committed Mar 27, 2024
1 parent f64f628 commit f1e8d98
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 3 deletions.
10 changes: 7 additions & 3 deletions core/monitor/price/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ func newPriceRangeCacheFromSlice(prs []*types.PriceRangeCache) map[*bound]priceR
return priceRangesCache
}

func (e *Engine) serialisePriceRanges() []*types.PriceRangeCache {
// SerialisePriceranges expored for testing.
func (e *Engine) SerialisePriceRanges() []*types.PriceRangeCache {
prc := make([]*types.PriceRangeCache, 0, len(e.priceRangesCache))
for bound, priceRange := range e.priceRangesCache {
prc = append(prc, &types.PriceRangeCache{
Expand All @@ -174,7 +175,10 @@ func (e *Engine) serialisePriceRanges() []*types.PriceRangeCache {
})
}

sort.Slice(prc, func(i, j int) bool {
sort.SliceStable(prc, func(i, j int) bool {
if prc[i].Bound.Active != prc[j].Bound.Active {
return prc[i].Bound.Active
}
if prc[i].Bound.UpFactor.Equal(prc[j].Bound.UpFactor) {
if prc[i].Bound.DownFactor.Equal(prc[j].Bound.DownFactor) {
return prc[i].Bound.Trigger.Horizon < prc[j].Bound.Trigger.Horizon
Expand Down Expand Up @@ -239,7 +243,7 @@ func (e *Engine) GetState() *types.PriceMonitor {
Now: e.now,
Update: e.update,
Bounds: e.serialiseBounds(),
PriceRangeCache: e.serialisePriceRanges(),
PriceRangeCache: e.SerialisePriceRanges(),
PricesNow: e.serialisePricesNow(),
PricesPast: e.serialisePricesPast(),
PriceRangeCacheTime: e.priceRangeCacheTime,
Expand Down
113 changes: 113 additions & 0 deletions core/monitor/price/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,116 @@ func TestRestorePriceBoundRepresentation(t *testing.T) {
require.Equal(t, min, sMin)
require.Equal(t, max, sMax)
}

func TestSerialiseBoundsDeterministically(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
riskModel := mocks.NewMockRangeProvider(ctrl)
auctionStateMock := mocks.NewMockAuctionState(ctrl)
currentPrice := num.NewUint(123)
now := time.Date(1993, 2, 2, 6, 0, 0, 1, time.UTC)

settings := types.PriceMonitoringSettingsFromProto(&vegapb.PriceMonitoringSettings{
Parameters: &vegapb.PriceMonitoringParameters{
Triggers: []*vegapb.PriceMonitoringTrigger{
{Horizon: 3600, Probability: "0.99", AuctionExtension: 60},
{Horizon: 3600, Probability: "0.99", AuctionExtension: 60},
{Horizon: 3600, Probability: "0.99", AuctionExtension: 60},
{Horizon: 3600, Probability: "0.99", AuctionExtension: 60},
{Horizon: 3600, Probability: "0.99", AuctionExtension: 60},
{Horizon: 7200, Probability: "0.95", AuctionExtension: 300},
{Horizon: 7200, Probability: "0.95", AuctionExtension: 300},
{Horizon: 7200, Probability: "0.95", AuctionExtension: 300},
{Horizon: 7200, Probability: "0.95", AuctionExtension: 300},
{Horizon: 7200, Probability: "0.95", AuctionExtension: 300},
},
},
})

_, pMin1, pMax1, _, _ := getPriceBounds(currentPrice, 1, 2)
_, pMin2, pMax2, _, _ := getPriceBounds(currentPrice, 3, 4)
currentPriceD := currentPrice.ToDecimal()
auctionStateMock.EXPECT().IsFBA().Return(false).AnyTimes()
auctionStateMock.EXPECT().InAuction().Return(false).AnyTimes()
auctionStateMock.EXPECT().IsPriceAuction().Return(false).AnyTimes()
statevar := mocks.NewMockStateVarEngine(ctrl)
statevar.EXPECT().RegisterStateVariable(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()

pm, err := price.NewMonitor("asset", "market", riskModel, auctionStateMock, settings, statevar, logging.NewTestLogger())
require.NoError(t, err)
require.NotNil(t, pm)
downFactors := []num.Decimal{
pMin1.Div(currentPriceD),
pMin1.Div(currentPriceD),
pMin1.Div(currentPriceD),
pMin1.Div(currentPriceD),
pMin1.Div(currentPriceD),
pMin2.Div(currentPriceD),
pMin2.Div(currentPriceD),
pMin2.Div(currentPriceD),
pMin2.Div(currentPriceD),
pMin2.Div(currentPriceD),
}
upFactors := []num.Decimal{
pMax1.Div(currentPriceD),
pMax1.Div(currentPriceD),
pMax1.Div(currentPriceD),
pMax1.Div(currentPriceD),
pMax1.Div(currentPriceD),
pMax2.Div(currentPriceD),
pMax2.Div(currentPriceD),
pMax2.Div(currentPriceD),
pMax2.Div(currentPriceD),
pMax2.Div(currentPriceD),
}

pm.UpdateTestFactors(downFactors, upFactors)

pm.OnTimeUpdate(now)
b := pm.CheckPrice(context.Background(), auctionStateMock, []*types.Trade{{Price: currentPrice, Size: 1}}, true)
require.False(t, b)

bounds := pm.GetCurrentBounds()
require.NotEmpty(t, bounds)
minP := bounds[0].MinValidPrice.Clone()
minP.Sub(minP, num.UintOne())
auctionStateMock.EXPECT().StartPriceAuction(gomock.Any(), gomock.Any()).Times(1)
b = pm.CheckPrice(context.Background(), auctionStateMock, []*types.Trade{{Price: minP, Size: 1}}, true)
require.False(t, b)

pBounds := pm.SerialisePriceRanges()
// now get state
state := pm.GetState()
snap, err := price.NewMonitorFromSnapshot("market", "asset", state, settings, riskModel, auctionStateMock, statevar, logging.NewTestLogger())
require.NoError(t, err)

sBounds := snap.SerialisePriceRanges()
require.Equal(t, len(pBounds), len(sBounds))
// ensure the inactive bound is at the back of the slice
require.False(t, pBounds[len(pBounds)-1].Bound.Active)
require.False(t, sBounds[len(sBounds)-1].Bound.Active)
for i := 0; i < len(sBounds); i++ {
pBound, sBound := pBounds[i], sBounds[i]
require.EqualValues(t, pBound, sBound)
}
// Now repeat the test above, but change the state to move the inactive bound back by one each time
for i := len(state.PriceRangeCache) - 1; i < 0; i-- {
// move the inactive price bound back by one
state.PriceRangeCache[i], state.PriceRangeCache[i-1] = state.PriceRangeCache[i-1], state.PriceRangeCache[i]
// sanity-check, make sure the inactive bound is now no longer the last element, and is where we expecti it to be
require.False(t, state.PriceRangeCache[i-1].Bound.Active)
require.True(t, state.PriceRangeCache[i].Bound.Active)
// always make sure the last element is active
require.True(t, state.PriceRangeCache[len(state.PriceRangeCache)-1].Bound.Active)
snap, err := price.NewMonitorFromSnapshot("market", "asset", state, settings, riskModel, auctionStateMock, statevar, logging.NewTestLogger())
require.NoError(t, err)
sBounds := snap.SerialisePriceRanges()
require.Equal(t, len(pBounds), len(sBounds))
// the inactive bound must be the last one
require.False(t, sBounds[len(sBounds)-1].Bound.Active)
for i := 0; i < len(sBounds); i++ {
pBound, sBound := pBounds[i], sBounds[i]
require.EqualValues(t, pBound, sBound)
}
}
}

0 comments on commit f1e8d98

Please sign in to comment.