code.vegaprotocol.io/vega@v0.79.0/core/execution/common/mat_intermediate_scores_internal_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 common
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  	"time"
    22  
    23  	bmocks "code.vegaprotocol.io/vega/core/broker/mocks"
    24  	"code.vegaprotocol.io/vega/core/events"
    25  	"code.vegaprotocol.io/vega/core/execution/common/mocks"
    26  	"code.vegaprotocol.io/vega/core/types"
    27  	"code.vegaprotocol.io/vega/libs/num"
    28  	"code.vegaprotocol.io/vega/logging"
    29  	vgproto "code.vegaprotocol.io/vega/protos/vega"
    30  
    31  	"github.com/golang/mock/gomock"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func TestPublishGameMetricAverageNotional(t *testing.T) {
    36  	ctx := context.Background()
    37  	epochService := &DummyEpochEngine{}
    38  	ctrl := gomock.NewController(t)
    39  	teams := mocks.NewMockTeams(ctrl)
    40  	balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl)
    41  	broker := bmocks.NewMockBroker(ctrl)
    42  	gameScoreEvents := []events.Event{}
    43  
    44  	broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
    45  	broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
    46  		if evt.StreamMessage().GetGameScores() != nil {
    47  			gameScoreEvents = append(gameScoreEvents, evt)
    48  		}
    49  	}).AnyTimes()
    50  	tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{})
    51  	epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore)
    52  	tracker.SetEligibilityChecker(&DummyEligibilityChecker{})
    53  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}})
    54  
    55  	// add some markets for 2 different assets
    56  	tracker.MarketProposed("a1", "m1", "z1")
    57  	tracker.MarketProposed("a1", "m2", "z2")
    58  	tracker.MarketProposed("a1", "m3", "z3")
    59  
    60  	// add some markets for 2 different assets
    61  	tracker.MarketProposed("a1", "m1", "z1")
    62  	tracker.MarketProposed("a1", "m2", "z2")
    63  	tracker.MarketProposed("a1", "m3", "z3")
    64  
    65  	// record some values for all metrics
    66  	tracker.RecordPosition("a1", "p1", "m1", 10, num.NewUint(1), num.DecimalOne(), time.Unix(5, 0))
    67  	tracker.RecordPosition("a1", "p1", "m2", 20, num.NewUint(2), num.DecimalOne(), time.Unix(20, 0))
    68  	tracker.RecordPosition("a1", "p1", "m3", 30, num.NewUint(3), num.DecimalOne(), time.Unix(30, 0))
    69  	tracker.RecordPosition("a1", "p2", "m1", 100, num.NewUint(10), num.DecimalOne(), time.Unix(15, 0))
    70  	tracker.RecordPosition("a1", "p2", "m2", 200, num.NewUint(20), num.DecimalOne(), time.Unix(25, 0))
    71  	tracker.RecordPosition("a1", "p2", "m3", 300, num.NewUint(30), num.DecimalOne(), time.Unix(45, 0))
    72  
    73  	// end epoch1
    74  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: time.Unix(0, 0), EndTime: time.Unix(60, 0)})
    75  
    76  	// get metrics for market m1 with window size=1
    77  	balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.NewUint(0), nil).AnyTimes()
    78  
    79  	ds1 := &vgproto.DispatchStrategy{
    80  		AssetForMetric:       "a1",
    81  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
    82  		Markets:              []string{"m1"},
    83  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
    84  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
    85  		WindowLength:         1,
    86  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
    87  	}
    88  	ds2 := &vgproto.DispatchStrategy{
    89  		AssetForMetric:       "a1",
    90  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
    91  		Markets:              []string{"m2"},
    92  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
    93  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
    94  		WindowLength:         1,
    95  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
    96  	}
    97  	ds3 := &vgproto.DispatchStrategy{
    98  		AssetForMetric:       "a1",
    99  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
   100  		Markets:              []string{"m3"},
   101  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   102  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   103  		WindowLength:         1,
   104  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   105  	}
   106  	ds4 := &vgproto.DispatchStrategy{
   107  		AssetForMetric:       "a1",
   108  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
   109  		Markets:              []string{"m1", "m2"},
   110  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   111  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   112  		WindowLength:         1,
   113  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   114  	}
   115  	ds5 := &vgproto.DispatchStrategy{
   116  		AssetForMetric:       "a1",
   117  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_AVERAGE_NOTIONAL,
   118  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   119  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   120  		WindowLength:         1,
   121  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   122  	}
   123  
   124  	// calculate intermediate scores
   125  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(60, 0))
   126  	require.Equal(t, 5, len(gameScoreEvents))
   127  	ps1 := gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   128  	require.Equal(t, "p1", ps1[0].Party)
   129  	require.Equal(t, "p2", ps1[1].Party)
   130  	require.Equal(t, "0.0000009", ps1[0].Score)
   131  	require.Equal(t, "0.000075", ps1[1].Score)
   132  
   133  	ps2 := gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   134  	require.Equal(t, "p1", ps2[0].Party)
   135  	require.Equal(t, "p2", ps2[1].Party)
   136  	require.Equal(t, "0.0000026", ps2[0].Score)
   137  	require.Equal(t, "0.0002333", ps2[1].Score)
   138  
   139  	ps3 := gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   140  	require.Equal(t, "p1", ps3[0].Party)
   141  	require.Equal(t, "p2", ps3[1].Party)
   142  	require.Equal(t, "0.0000045", ps3[0].Score)
   143  	require.Equal(t, "0.000225", ps3[1].Score)
   144  
   145  	ps4 := gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   146  	require.Equal(t, "p1", ps4[0].Party)
   147  	require.Equal(t, "p2", ps4[1].Party)
   148  	require.Equal(t, "0.0000035", ps4[0].Score)
   149  	require.Equal(t, "0.0003083", ps4[1].Score)
   150  
   151  	ps5 := gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   152  	require.Equal(t, "p1", ps5[0].Party)
   153  	require.Equal(t, "p2", ps5[1].Party)
   154  	require.Equal(t, "0.000008", ps5[0].Score)
   155  	require.Equal(t, "0.0005333", ps5[1].Score)
   156  
   157  	// now we end the epoch and make sure that we get the exact same results
   158  	gameScoreEvents = []events.Event{}
   159  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: time.Unix(0, 0), EndTime: time.Unix(60, 0)})
   160  
   161  	// we expect that if we take a snapshot of scores now, it looks identical because we didn't change the time
   162  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(60, 0))
   163  	require.Equal(t, 5, len(gameScoreEvents))
   164  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   165  	require.Equal(t, "p1", ps1[0].Party)
   166  	require.Equal(t, "p2", ps1[1].Party)
   167  	require.Equal(t, "0.0000009", ps1[0].Score)
   168  	require.Equal(t, "0.000075", ps1[1].Score)
   169  
   170  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   171  	require.Equal(t, "p1", ps2[0].Party)
   172  	require.Equal(t, "p2", ps2[1].Party)
   173  	require.Equal(t, "0.0000026", ps2[0].Score)
   174  	require.Equal(t, "0.0002333", ps2[1].Score)
   175  
   176  	ps3 = gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   177  	require.Equal(t, "p1", ps3[0].Party)
   178  	require.Equal(t, "p2", ps3[1].Party)
   179  	require.Equal(t, "0.0000045", ps3[0].Score)
   180  	require.Equal(t, "0.000225", ps3[1].Score)
   181  
   182  	ps4 = gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   183  	require.Equal(t, "p1", ps4[0].Party)
   184  	require.Equal(t, "p2", ps4[1].Party)
   185  	require.Equal(t, "0.0000035", ps4[0].Score)
   186  	require.Equal(t, "0.0003083", ps4[1].Score)
   187  
   188  	ps5 = gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   189  	require.Equal(t, "p1", ps5[0].Party)
   190  	require.Equal(t, "p2", ps5[1].Party)
   191  	require.Equal(t, "0.000008", ps5[0].Score)
   192  	require.Equal(t, "0.0005333", ps5[1].Score)
   193  
   194  	// start epoch 2 and record some activity
   195  	epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(60, 0)})
   196  	tracker.RecordPosition("a1", "p1", "m1", 20, num.NewUint(5), num.DecimalOne(), time.Unix(90, 0))
   197  	tracker.RecordPosition("a1", "p2", "m2", 10, num.NewUint(10), num.DecimalOne(), time.Unix(75, 0))
   198  	tracker.RecordPosition("a2", "p1", "m3", 20, num.NewUint(5), num.DecimalOne(), time.Unix(90, 0))
   199  	tracker.RecordPosition("a2", "p2", "m2", 10, num.NewUint(10), num.DecimalOne(), time.Unix(75, 0))
   200  
   201  	gameScoreEvents = []events.Event{}
   202  
   203  	// lets look at the events when the window size is 1
   204  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(120, 0))
   205  	require.Equal(t, 5, len(gameScoreEvents))
   206  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   207  	require.Equal(t, "p1", ps1[0].Party)
   208  	require.Equal(t, "p2", ps1[1].Party)
   209  	require.Equal(t, "0.0000055", ps1[0].Score)
   210  	require.Equal(t, "0.0001", ps1[1].Score)
   211  
   212  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   213  	require.Equal(t, "p1", ps2[0].Party)
   214  	require.Equal(t, "p2", ps2[1].Party)
   215  	require.Equal(t, "0.000004", ps2[0].Score)
   216  	require.Equal(t, "0.0001075", ps2[1].Score)
   217  
   218  	ps3 = gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   219  	require.Equal(t, "p1", ps3[0].Party)
   220  	require.Equal(t, "p2", ps3[1].Party)
   221  	require.Equal(t, "0.000009", ps3[0].Score)
   222  	require.Equal(t, "0.0009", ps3[1].Score)
   223  
   224  	ps4 = gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   225  	require.Equal(t, "p1", ps4[0].Party)
   226  	require.Equal(t, "p2", ps4[1].Party)
   227  	require.Equal(t, "0.0000095", ps4[0].Score)
   228  	require.Equal(t, "0.0002075", ps4[1].Score)
   229  
   230  	ps5 = gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   231  	require.Equal(t, "p1", ps5[0].Party)
   232  	require.Equal(t, "p2", ps5[1].Party)
   233  	require.Equal(t, "0.0000185", ps5[0].Score)
   234  	require.Equal(t, "0.0011075", ps5[1].Score)
   235  
   236  	// now lets change the window to 2:
   237  	ds1.WindowLength = 2
   238  	ds2.WindowLength = 2
   239  	ds3.WindowLength = 2
   240  	ds4.WindowLength = 2
   241  	ds5.WindowLength = 2
   242  
   243  	gameScoreEvents = []events.Event{}
   244  
   245  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(120, 0))
   246  	require.Equal(t, 5, len(gameScoreEvents))
   247  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   248  	require.Equal(t, "p1", ps1[0].Party)
   249  	require.Equal(t, "p2", ps1[1].Party)
   250  	require.Equal(t, "0.0000032", ps1[0].Score)
   251  	require.Equal(t, "0.0000875", ps1[1].Score)
   252  
   253  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   254  	require.Equal(t, "p1", ps2[0].Party)
   255  	require.Equal(t, "p2", ps2[1].Party)
   256  	require.Equal(t, "0.0000033", ps2[0].Score)
   257  	require.Equal(t, "0.0001704", ps2[1].Score)
   258  
   259  	ps3 = gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   260  	require.Equal(t, "p1", ps3[0].Party)
   261  	require.Equal(t, "p2", ps3[1].Party)
   262  	require.Equal(t, "0.00000675", ps3[0].Score)
   263  	require.Equal(t, "0.0005625", ps3[1].Score)
   264  
   265  	ps4 = gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   266  	require.Equal(t, "p1", ps4[0].Party)
   267  	require.Equal(t, "p2", ps4[1].Party)
   268  	require.Equal(t, "0.0000065", ps4[0].Score)
   269  	require.Equal(t, "0.0002579", ps4[1].Score)
   270  
   271  	ps5 = gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   272  	require.Equal(t, "p1", ps5[0].Party)
   273  	require.Equal(t, "p2", ps5[1].Party)
   274  	require.Equal(t, "0.00001325", ps5[0].Score)
   275  	require.Equal(t, "0.0008204", ps5[1].Score)
   276  }
   277  
   278  func TestPublishGameMetricReturnVolatility(t *testing.T) {
   279  	ctx := context.Background()
   280  	epochService := &DummyEpochEngine{}
   281  	ctrl := gomock.NewController(t)
   282  	teams := mocks.NewMockTeams(ctrl)
   283  	balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl)
   284  	broker := bmocks.NewMockBroker(ctrl)
   285  	gameScoreEvents := []events.Event{}
   286  	broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   287  	broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   288  		if evt.StreamMessage().GetGameScores() != nil {
   289  			gameScoreEvents = append(gameScoreEvents, evt)
   290  		}
   291  	}).AnyTimes()
   292  	tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{})
   293  	epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore)
   294  	tracker.SetEligibilityChecker(&DummyEligibilityChecker{})
   295  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Time{}})
   296  
   297  	// add some markets for 2 different assets
   298  	tracker.MarketProposed("a1", "m1", "z1")
   299  	tracker.MarketProposed("a1", "m2", "z2")
   300  	tracker.MarketProposed("a1", "m3", "z3")
   301  
   302  	// add some markets for 2 different assets
   303  	tracker.MarketProposed("a1", "m1", "z1")
   304  	tracker.MarketProposed("a1", "m2", "z2")
   305  	tracker.MarketProposed("a1", "m3", "z3")
   306  
   307  	// record some values for all metrics
   308  	tracker.RecordPosition("a1", "p1", "m1", 10, num.NewUint(1), num.DecimalOne(), time.Unix(5, 0))
   309  	tracker.RecordPosition("a1", "p1", "m2", 20, num.NewUint(2), num.DecimalOne(), time.Unix(20, 0))
   310  	tracker.RecordPosition("a1", "p1", "m3", 30, num.NewUint(3), num.DecimalOne(), time.Unix(30, 0))
   311  	tracker.RecordPosition("a1", "p2", "m1", 100, num.NewUint(10), num.DecimalOne(), time.Unix(15, 0))
   312  	tracker.RecordPosition("a1", "p2", "m2", 200, num.NewUint(20), num.DecimalOne(), time.Unix(25, 0))
   313  	tracker.RecordPosition("a1", "p2", "m3", 300, num.NewUint(30), num.DecimalOne(), time.Unix(45, 0))
   314  	tracker.RecordPosition("a1", "p3", "m1", 10, num.NewUint(1), num.DecimalOne(), time.Unix(10, 0))
   315  	tracker.RecordPosition("a1", "p3", "m2", 20, num.NewUint(2), num.DecimalOne(), time.Unix(10, 0))
   316  	tracker.RecordPosition("a1", "p3", "m3", 30, num.NewUint(3), num.DecimalOne(), time.Unix(10, 0))
   317  
   318  	tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(80))
   319  	tracker.RecordM2M("a1", "p2", "m1", num.DecimalFromInt64(20))
   320  	tracker.RecordM2M("a1", "p3", "m1", num.DecimalFromInt64(-100))
   321  	tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(10))
   322  	tracker.RecordM2M("a1", "p2", "m1", num.DecimalFromInt64(-10))
   323  	tracker.RecordM2M("a1", "p1", "m2", num.DecimalFromInt64(50))
   324  	tracker.RecordM2M("a1", "p2", "m2", num.DecimalFromInt64(-5))
   325  	tracker.RecordM2M("a1", "p3", "m2", num.DecimalFromInt64(-45))
   326  	tracker.RecordM2M("a1", "p1", "m3", num.DecimalFromInt64(-35))
   327  	tracker.RecordM2M("a1", "p2", "m3", num.DecimalFromInt64(35))
   328  
   329  	// get metrics for market m1 with window size=1
   330  	balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.NewUint(0), nil).AnyTimes()
   331  
   332  	ds1 := &vgproto.DispatchStrategy{
   333  		AssetForMetric:       "a1",
   334  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RETURN_VOLATILITY,
   335  		Markets:              []string{"m1"},
   336  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   337  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   338  		WindowLength:         1,
   339  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   340  	}
   341  	ds2 := &vgproto.DispatchStrategy{
   342  		AssetForMetric:       "a1",
   343  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RETURN_VOLATILITY,
   344  		Markets:              []string{"m2"},
   345  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   346  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   347  		WindowLength:         1,
   348  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   349  	}
   350  	ds3 := &vgproto.DispatchStrategy{
   351  		AssetForMetric:       "a1",
   352  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RETURN_VOLATILITY,
   353  		Markets:              []string{"m3"},
   354  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   355  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   356  		WindowLength:         1,
   357  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   358  	}
   359  	ds4 := &vgproto.DispatchStrategy{
   360  		AssetForMetric:       "a1",
   361  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RETURN_VOLATILITY,
   362  		Markets:              []string{"m1", "m2"},
   363  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   364  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   365  		WindowLength:         1,
   366  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   367  	}
   368  	ds5 := &vgproto.DispatchStrategy{
   369  		AssetForMetric:       "a1",
   370  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RETURN_VOLATILITY,
   371  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   372  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   373  		WindowLength:         1,
   374  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   375  	}
   376  
   377  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(60, 0))
   378  	require.Equal(t, 5, len(gameScoreEvents))
   379  	ps1 := gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   380  	require.Equal(t, "p1", ps1[0].Party)
   381  	require.Equal(t, "p2", ps1[1].Party)
   382  	require.Equal(t, "0", ps1[0].Score)
   383  	require.Equal(t, "0", ps1[1].Score)
   384  
   385  	ps2 := gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   386  	require.Equal(t, "p1", ps2[0].Party)
   387  	require.Equal(t, "p2", ps2[1].Party)
   388  	require.Equal(t, "0", ps2[0].Score)
   389  	require.Equal(t, "0", ps2[1].Score)
   390  
   391  	ps3 := gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   392  	require.Equal(t, "p1", ps3[0].Party)
   393  	require.Equal(t, "p2", ps3[1].Party)
   394  	require.Equal(t, "0", ps3[0].Score)
   395  	require.Equal(t, "0", ps3[1].Score)
   396  
   397  	ps4 := gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   398  	require.Equal(t, "p1", ps4[0].Party)
   399  	require.Equal(t, "p2", ps4[1].Party)
   400  	require.Equal(t, "0", ps4[0].Score)
   401  	require.Equal(t, "0", ps4[1].Score)
   402  
   403  	ps5 := gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   404  	require.Equal(t, "p1", ps5[0].Party)
   405  	require.Equal(t, "p2", ps5[1].Party)
   406  	require.Equal(t, "0", ps5[0].Score)
   407  	require.Equal(t, "0", ps5[1].Score)
   408  
   409  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: time.Unix(0, 0), EndTime: time.Unix(60, 0)})
   410  	epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(60, 0)})
   411  	tracker.RecordPosition("a1", "p1", "m1", 20, num.NewUint(5), num.DecimalOne(), time.Unix(90, 0))
   412  	tracker.RecordPosition("a1", "p2", "m2", 10, num.NewUint(10), num.DecimalOne(), time.Unix(75, 0))
   413  	tracker.RecordPosition("a1", "p3", "m1", 10, num.NewUint(10), num.DecimalOne(), time.Unix(75, 0))
   414  	tracker.RecordPosition("a2", "p1", "m3", 20, num.NewUint(5), num.DecimalOne(), time.Unix(90, 0))
   415  	tracker.RecordPosition("a2", "p2", "m2", 10, num.NewUint(10), num.DecimalOne(), time.Unix(75, 0))
   416  
   417  	tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(45))
   418  	tracker.RecordM2M("a1", "p3", "m1", num.DecimalFromInt64(-45))
   419  	tracker.RecordM2M("a1", "p1", "m2", num.DecimalFromInt64(-10))
   420  	tracker.RecordM2M("a1", "p2", "m2", num.DecimalFromInt64(10))
   421  	// nothing in m3
   422  
   423  	ds1.WindowLength = 2
   424  	ds2.WindowLength = 2
   425  	ds3.WindowLength = 2
   426  	ds4.WindowLength = 2
   427  	ds5.WindowLength = 2
   428  
   429  	gameScoreEvents = []events.Event{}
   430  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(120, 0))
   431  	require.Equal(t, 5, len(gameScoreEvents))
   432  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   433  	require.Equal(t, "p1", ps1[0].Party)
   434  	require.Equal(t, "p2", ps1[1].Party)
   435  	require.Equal(t, "0.086044426422046", ps1[0].Score)
   436  	require.Equal(t, "0", ps1[1].Score)
   437  	require.Equal(t, true, ps1[0].IsEligible)
   438  	require.Equal(t, false, ps1[1].IsEligible)
   439  
   440  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   441  	require.Equal(t, "p1", ps2[0].Party)
   442  	require.Equal(t, "p2", ps2[1].Party)
   443  	require.Equal(t, "0.2214532481172412", ps2[0].Score)
   444  	require.Equal(t, "85.1257359604949139", ps2[1].Score)
   445  	require.Equal(t, true, ps2[0].IsEligible)
   446  	require.Equal(t, true, ps2[1].IsEligible)
   447  
   448  	ps3 = gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   449  	require.Equal(t, "p1", ps3[0].Party)
   450  	require.Equal(t, "p2", ps3[1].Party)
   451  	require.Equal(t, "0", ps3[0].Score)
   452  	require.Equal(t, "0", ps3[1].Score)
   453  	require.Equal(t, false, ps3[0].IsEligible)
   454  	require.Equal(t, false, ps1[1].IsEligible)
   455  
   456  	ps4 = gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   457  	require.Equal(t, "p1", ps4[0].Party)
   458  	require.Equal(t, "p2", ps4[1].Party)
   459  	require.Equal(t, "0.0326518156928779", ps4[0].Score)
   460  	require.Equal(t, "574.5715725244936759", ps4[1].Score)
   461  	require.Equal(t, true, ps4[0].IsEligible)
   462  	require.Equal(t, true, ps4[1].IsEligible)
   463  
   464  	ps5 = gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   465  	require.Equal(t, "p1", ps5[0].Party)
   466  	require.Equal(t, "p2", ps5[1].Party)
   467  	require.Equal(t, "0.0524262906455334", ps5[0].Score)
   468  	require.Equal(t, "27.2358805547724978", ps5[1].Score)
   469  	require.Equal(t, true, ps5[0].IsEligible)
   470  	require.Equal(t, true, ps5[1].IsEligible)
   471  
   472  	// now end the epoch properly
   473  	epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: time.Unix(60, 0), EndTime: time.Unix(120, 0)})
   474  	epochService.target(context.Background(), types.Epoch{Seq: 3, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(120, 0)})
   475  
   476  	// record some m2ms
   477  	tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(20))
   478  	tracker.RecordM2M("a1", "p3", "m1", num.DecimalFromInt64(-25))
   479  	tracker.RecordM2M("a1", "p1", "m2", num.DecimalFromInt64(-15))
   480  	tracker.RecordM2M("a1", "p2", "m2", num.DecimalFromInt64(15))
   481  
   482  	gameScoreEvents = []events.Event{}
   483  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(150, 0))
   484  	require.Equal(t, 5, len(gameScoreEvents))
   485  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   486  	require.Equal(t, "p1", ps1[0].Party)
   487  	require.Equal(t, "p2", ps1[1].Party)
   488  	require.Equal(t, "1", ps1[0].Score)
   489  	require.Equal(t, "0", ps1[1].Score)
   490  	require.Equal(t, true, ps1[0].IsEligible)
   491  	require.Equal(t, false, ps1[1].IsEligible)
   492  
   493  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   494  	require.Equal(t, "p1", ps2[0].Party)
   495  	require.Equal(t, "p2", ps2[1].Party)
   496  	require.Equal(t, "64", ps2[0].Score)
   497  	require.Equal(t, "2.2746573501746843", ps2[1].Score)
   498  	require.Equal(t, true, ps2[0].IsEligible)
   499  	require.Equal(t, true, ps2[1].IsEligible)
   500  
   501  	ps3 = gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   502  	require.Equal(t, "p1", ps3[0].Party)
   503  	require.Equal(t, "p2", ps3[1].Party)
   504  	require.Equal(t, "0", ps3[0].Score)
   505  	require.Equal(t, "0", ps3[1].Score)
   506  	require.Equal(t, false, ps3[0].IsEligible)
   507  	require.Equal(t, false, ps1[1].IsEligible)
   508  
   509  	ps4 = gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   510  	require.Equal(t, "p1", ps4[0].Party)
   511  	require.Equal(t, "p2", ps4[1].Party)
   512  	require.Equal(t, "0.7901234567901235", ps4[0].Score)
   513  	require.Equal(t, "2.2746573501746843", ps4[1].Score)
   514  	require.Equal(t, true, ps4[0].IsEligible)
   515  	require.Equal(t, true, ps4[1].IsEligible)
   516  
   517  	ps5 = gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   518  	require.Equal(t, "p1", ps5[0].Party)
   519  	require.Equal(t, "p2", ps5[1].Party)
   520  	require.Equal(t, "0.7901234567901235", ps5[0].Score)
   521  	require.Equal(t, "2.2746573501746843", ps5[1].Score)
   522  	require.Equal(t, true, ps5[0].IsEligible)
   523  	require.Equal(t, true, ps5[1].IsEligible)
   524  }
   525  
   526  func TestPublishGameMetricRelativeReturn(t *testing.T) {
   527  	ctx := context.Background()
   528  	epochService := &DummyEpochEngine{}
   529  	ctrl := gomock.NewController(t)
   530  	teams := mocks.NewMockTeams(ctrl)
   531  	balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl)
   532  	broker := bmocks.NewMockBroker(ctrl)
   533  	gameScoreEvents := []events.Event{}
   534  	broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   535  	broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   536  		if evt.StreamMessage().GetGameScores() != nil {
   537  			gameScoreEvents = append(gameScoreEvents, evt)
   538  		}
   539  	}).AnyTimes()
   540  	tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{})
   541  	epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore)
   542  	tracker.SetEligibilityChecker(&DummyEligibilityChecker{})
   543  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(0, 0)})
   544  
   545  	balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.NewUint(0), nil).AnyTimes()
   546  
   547  	ds1 := &vgproto.DispatchStrategy{
   548  		AssetForMetric:       "a1",
   549  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN,
   550  		Markets:              []string{"m1"},
   551  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   552  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   553  		WindowLength:         1,
   554  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   555  	}
   556  	ds2 := &vgproto.DispatchStrategy{
   557  		AssetForMetric:       "a1",
   558  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN,
   559  		Markets:              []string{"m2"},
   560  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   561  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   562  		WindowLength:         1,
   563  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   564  	}
   565  	ds3 := &vgproto.DispatchStrategy{
   566  		AssetForMetric:       "a1",
   567  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN,
   568  		Markets:              []string{"m3"},
   569  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   570  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   571  		WindowLength:         1,
   572  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   573  	}
   574  	ds4 := &vgproto.DispatchStrategy{
   575  		AssetForMetric:       "a1",
   576  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN,
   577  		Markets:              []string{"m1", "m2"},
   578  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   579  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   580  		WindowLength:         1,
   581  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   582  	}
   583  	ds5 := &vgproto.DispatchStrategy{
   584  		AssetForMetric:       "a1",
   585  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_RELATIVE_RETURN,
   586  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   587  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   588  		WindowLength:         1,
   589  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   590  	}
   591  
   592  	// add some markets for 2 different assets
   593  	tracker.MarketProposed("a1", "m1", "z1")
   594  	tracker.MarketProposed("a1", "m2", "z2")
   595  	tracker.MarketProposed("a1", "m3", "z3")
   596  
   597  	// record some values for all metrics
   598  	tracker.RecordPosition("a1", "p1", "m1", 10, num.NewUint(1), num.DecimalOne(), time.Unix(5, 0))
   599  	tracker.RecordPosition("a1", "p1", "m2", 20, num.NewUint(2), num.DecimalOne(), time.Unix(20, 0))
   600  	tracker.RecordPosition("a1", "p1", "m3", 30, num.NewUint(3), num.DecimalOne(), time.Unix(30, 0))
   601  	tracker.RecordPosition("a1", "p2", "m1", 100, num.NewUint(10), num.DecimalOne(), time.Unix(15, 0))
   602  	tracker.RecordPosition("a1", "p2", "m2", 200, num.NewUint(20), num.DecimalOne(), time.Unix(25, 0))
   603  	tracker.RecordPosition("a1", "p2", "m3", 300, num.NewUint(30), num.DecimalOne(), time.Unix(45, 0))
   604  
   605  	tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(-100))
   606  	tracker.RecordM2M("a1", "p2", "m1", num.DecimalFromInt64(100))
   607  	tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(250))
   608  	tracker.RecordM2M("a1", "p2", "m1", num.DecimalFromInt64(-250))
   609  	tracker.RecordM2M("a1", "p1", "m2", num.DecimalFromInt64(-50))
   610  	tracker.RecordM2M("a1", "p2", "m2", num.DecimalFromInt64(50))
   611  	tracker.RecordM2M("a1", "p1", "m3", num.DecimalFromInt64(100))
   612  	tracker.RecordM2M("a1", "p2", "m3", num.DecimalFromInt64(-100))
   613  
   614  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(60, 0))
   615  	require.Equal(t, 5, len(gameScoreEvents))
   616  	ps1 := gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   617  	require.Equal(t, "p1", ps1[0].Party)
   618  	require.Equal(t, "p2", ps1[1].Party)
   619  	require.Equal(t, "16.3636375537190948", ps1[0].Score)
   620  	require.Equal(t, "-2", ps1[1].Score)
   621  
   622  	ps2 := gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   623  	require.Equal(t, "p1", ps2[0].Party)
   624  	require.Equal(t, "p2", ps2[1].Party)
   625  	require.Equal(t, "-3.7500003750000375", ps2[0].Score)
   626  	require.Equal(t, "0.4285714530612259", ps2[1].Score)
   627  
   628  	ps3 := gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   629  	require.Equal(t, "p1", ps3[0].Party)
   630  	require.Equal(t, "p2", ps3[1].Party)
   631  	require.Equal(t, "6.6666666666666667", ps3[0].Score)
   632  	require.Equal(t, "-1.3333333333333333", ps3[1].Score)
   633  
   634  	ps4 := gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   635  	require.Equal(t, "p1", ps4[0].Party)
   636  	require.Equal(t, "p2", ps4[1].Party)
   637  	require.Equal(t, "12.6136371787190573", ps4[0].Score)
   638  	require.Equal(t, "-1.5714285469387741", ps4[1].Score)
   639  
   640  	ps5 := gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   641  	require.Equal(t, "p1", ps5[0].Party)
   642  	require.Equal(t, "p2", ps5[1].Party)
   643  	require.Equal(t, "19.280303845385724", ps5[0].Score)
   644  	require.Equal(t, "-2.9047618802721074", ps5[1].Score)
   645  
   646  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: time.Unix(0, 0), EndTime: time.Unix(60, 0)})
   647  	epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(60, 0)})
   648  
   649  	tracker.RecordPosition("a1", "p1", "m1", 20, num.NewUint(5), num.DecimalOne(), time.Unix(90, 0))
   650  	tracker.RecordPosition("a1", "p2", "m2", 10, num.NewUint(10), num.DecimalOne(), time.Unix(75, 0))
   651  	tracker.RecordPosition("a2", "p1", "m3", 20, num.NewUint(5), num.DecimalOne(), time.Unix(90, 0))
   652  	tracker.RecordPosition("a2", "p2", "m2", 10, num.NewUint(10), num.DecimalOne(), time.Unix(75, 0))
   653  
   654  	tracker.RecordM2M("a1", "p1", "m1", num.DecimalFromInt64(450))
   655  	tracker.RecordM2M("a1", "p2", "m1", num.DecimalFromInt64(-450))
   656  	tracker.RecordM2M("a1", "p1", "m2", num.DecimalFromInt64(-100))
   657  	tracker.RecordM2M("a1", "p2", "m2", num.DecimalFromInt64(100))
   658  	// nothing in m3
   659  
   660  	gameScoreEvents = []events.Event{}
   661  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(120, 0))
   662  	require.Equal(t, 5, len(gameScoreEvents))
   663  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   664  	require.Equal(t, "p1", ps1[0].Party)
   665  	require.Equal(t, "p2", ps1[1].Party)
   666  	require.Equal(t, "30", ps1[0].Score)
   667  	require.Equal(t, "-4.5", ps1[1].Score)
   668  
   669  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   670  	require.Equal(t, "p1", ps2[0].Party)
   671  	require.Equal(t, "p2", ps2[1].Party)
   672  	require.Equal(t, "-5", ps2[0].Score)
   673  	require.Equal(t, "1.7391304347826087", ps2[1].Score)
   674  
   675  	ps3 = gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   676  	require.Equal(t, "p1", ps3[0].Party)
   677  	require.Equal(t, "p2", ps3[1].Party)
   678  	require.Equal(t, "0", ps3[0].Score)
   679  	require.Equal(t, "0", ps3[1].Score)
   680  	require.False(t, ps3[0].IsEligible)
   681  	require.False(t, ps3[1].IsEligible)
   682  
   683  	ps4 = gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   684  	require.Equal(t, "p1", ps4[0].Party)
   685  	require.Equal(t, "p2", ps4[1].Party)
   686  	require.Equal(t, "25", ps4[0].Score)
   687  	require.Equal(t, "-2.7608695652173913", ps4[1].Score)
   688  
   689  	ps5 = gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   690  	require.Equal(t, "p1", ps5[0].Party)
   691  	require.Equal(t, "p2", ps5[1].Party)
   692  	require.Equal(t, "25", ps5[0].Score)
   693  	require.Equal(t, "-2.7608695652173913", ps5[1].Score)
   694  
   695  	// check with window length = 2
   696  	ds1.WindowLength = 2
   697  	ds2.WindowLength = 2
   698  	ds3.WindowLength = 2
   699  	ds4.WindowLength = 2
   700  	ds5.WindowLength = 2
   701  
   702  	gameScoreEvents = []events.Event{}
   703  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5}, time.Unix(120, 0))
   704  	require.Equal(t, 5, len(gameScoreEvents))
   705  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   706  	require.Equal(t, "p1", ps1[0].Party)
   707  	require.Equal(t, "p2", ps1[1].Party)
   708  	require.Equal(t, "23.1818187768595474", ps1[0].Score)
   709  	require.Equal(t, "-3.25", ps1[1].Score)
   710  	require.Equal(t, true, ps1[0].IsEligible)
   711  	require.Equal(t, true, ps1[1].IsEligible)
   712  
   713  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   714  	require.Equal(t, "p1", ps2[0].Party)
   715  	require.Equal(t, "p2", ps2[1].Party)
   716  	require.Equal(t, "-4.3750001875000188", ps2[0].Score)
   717  	require.Equal(t, "1.0838509439219173", ps2[1].Score)
   718  	require.Equal(t, true, ps2[0].IsEligible)
   719  	require.Equal(t, true, ps2[1].IsEligible)
   720  
   721  	ps3 = gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
   722  	require.Equal(t, "p1", ps3[0].Party)
   723  	require.Equal(t, "p2", ps3[1].Party)
   724  	require.Equal(t, "3.3333333333333334", ps3[0].Score)
   725  	require.Equal(t, "-0.6666666666666667", ps3[1].Score)
   726  	require.Equal(t, true, ps3[0].IsEligible)
   727  	require.Equal(t, true, ps1[1].IsEligible)
   728  
   729  	ps4 = gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
   730  	require.Equal(t, "p1", ps4[0].Party)
   731  	require.Equal(t, "p2", ps4[1].Party)
   732  	require.Equal(t, "18.8068185893595287", ps4[0].Score)
   733  	require.Equal(t, "-2.1661490560780827", ps4[1].Score)
   734  	require.Equal(t, true, ps4[0].IsEligible)
   735  	require.Equal(t, true, ps4[1].IsEligible)
   736  
   737  	ps5 = gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
   738  	require.Equal(t, "p1", ps5[0].Party)
   739  	require.Equal(t, "p2", ps5[1].Party)
   740  	require.Equal(t, "22.140151922692862", ps5[0].Score)
   741  	require.Equal(t, "-2.8328157227447494", ps5[1].Score)
   742  	require.Equal(t, true, ps5[0].IsEligible)
   743  	require.Equal(t, true, ps5[1].IsEligible)
   744  }
   745  
   746  func TestPublishGameMetricRealisedReturn(t *testing.T) {
   747  	ctx := context.Background()
   748  	epochService := &DummyEpochEngine{}
   749  	ctrl := gomock.NewController(t)
   750  	teams := mocks.NewMockTeams(ctrl)
   751  	balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl)
   752  	broker := bmocks.NewMockBroker(ctrl)
   753  	gameScoreEvents := []events.Event{}
   754  	broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   755  	broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   756  		if evt.StreamMessage().GetGameScores() != nil {
   757  			gameScoreEvents = append(gameScoreEvents, evt)
   758  		}
   759  	}).AnyTimes()
   760  	tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{})
   761  	epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore)
   762  	tracker.SetEligibilityChecker(&DummyEligibilityChecker{})
   763  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(0, 0)})
   764  
   765  	balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.NewUint(0), nil).AnyTimes()
   766  
   767  	tracker.MarketProposed("a1", "m1", "z1")
   768  
   769  	ds1 := &vgproto.DispatchStrategy{
   770  		AssetForMetric:       "a1",
   771  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN,
   772  		Markets:              []string{"m1"},
   773  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   774  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   775  		WindowLength:         1,
   776  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   777  	}
   778  	ds2 := &vgproto.DispatchStrategy{
   779  		AssetForMetric:       "a1",
   780  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_REALISED_RETURN,
   781  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   782  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   783  		WindowLength:         1,
   784  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   785  	}
   786  
   787  	tracker.RecordFundingPayment("a1", "p1", "m1", num.DecimalFromInt64(100))
   788  	tracker.RecordRealisedPosition("a1", "p1", "m1", num.DecimalFromInt64(-50))
   789  	tracker.RecordFundingPayment("a1", "p1", "m1", num.DecimalFromInt64(-200))
   790  	tracker.RecordRealisedPosition("a1", "p1", "m1", num.DecimalFromInt64(20))
   791  	tracker.RecordFundingPayment("a1", "p2", "m1", num.DecimalFromInt64(-100))
   792  	tracker.RecordRealisedPosition("a1", "p2", "m1", num.DecimalFromInt64(-10))
   793  	tracker.RecordRealisedPosition("a1", "p2", "m1", num.DecimalFromInt64(20))
   794  	tracker.RecordFundingPayment("a1", "p3", "m1", num.DecimalFromInt64(200))
   795  
   796  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2}, time.Unix(60, 0))
   797  	require.Equal(t, 2, len(gameScoreEvents))
   798  	ps1 := gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   799  	require.Equal(t, "p1", ps1[0].Party)
   800  	require.Equal(t, "p2", ps1[1].Party)
   801  	require.Equal(t, "p3", ps1[2].Party)
   802  	require.Equal(t, "-130", ps1[0].Score)
   803  	require.Equal(t, "-90", ps1[1].Score)
   804  	require.Equal(t, "200", ps1[2].Score)
   805  
   806  	ps2 := gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   807  	require.Equal(t, "p1", ps2[0].Party)
   808  	require.Equal(t, "p2", ps2[1].Party)
   809  	require.Equal(t, "p3", ps2[2].Party)
   810  	require.Equal(t, "-130", ps2[0].Score)
   811  	require.Equal(t, "-90", ps2[1].Score)
   812  	require.Equal(t, "200", ps2[2].Score)
   813  
   814  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END, StartTime: time.Unix(0, 0), EndTime: time.Unix(60, 0)})
   815  	epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(60, 0)})
   816  
   817  	tracker.RecordFundingPayment("a1", "p1", "m1", num.DecimalFromInt64(-30))
   818  	tracker.RecordRealisedPosition("a1", "p2", "m1", num.DecimalFromInt64(70))
   819  	tracker.RecordRealisedPosition("a1", "p2", "m1", num.DecimalFromInt64(80))
   820  	tracker.RecordRealisedPosition("a1", "p3", "m1", num.DecimalFromInt64(-50))
   821  
   822  	// with window size1
   823  	gameScoreEvents = []events.Event{}
   824  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2}, time.Unix(120, 0))
   825  	require.Equal(t, 2, len(gameScoreEvents))
   826  
   827  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   828  	require.Equal(t, "p1", ps1[0].Party)
   829  	require.Equal(t, "p2", ps1[1].Party)
   830  	require.Equal(t, "p3", ps1[2].Party)
   831  	require.Equal(t, "-30", ps1[0].Score)
   832  	require.Equal(t, "150", ps1[1].Score)
   833  	require.Equal(t, "-50", ps1[2].Score)
   834  
   835  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   836  	require.Equal(t, "p1", ps2[0].Party)
   837  	require.Equal(t, "p2", ps2[1].Party)
   838  	require.Equal(t, "p3", ps2[2].Party)
   839  	require.Equal(t, "-30", ps2[0].Score)
   840  	require.Equal(t, "150", ps2[1].Score)
   841  	require.Equal(t, "-50", ps2[2].Score)
   842  
   843  	// check with window length = 2
   844  	ds1.WindowLength = 2
   845  	ds2.WindowLength = 2
   846  
   847  	gameScoreEvents = []events.Event{}
   848  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2}, time.Unix(120, 0))
   849  	require.Equal(t, 2, len(gameScoreEvents))
   850  
   851  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   852  	require.Equal(t, "p1", ps1[0].Party)
   853  	require.Equal(t, "p2", ps1[1].Party)
   854  	require.Equal(t, "p3", ps1[2].Party)
   855  	require.Equal(t, "-80", ps1[0].Score)
   856  	require.Equal(t, "30", ps1[1].Score)
   857  	require.Equal(t, "75", ps1[2].Score)
   858  
   859  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   860  	require.Equal(t, "p1", ps2[0].Party)
   861  	require.Equal(t, "p2", ps2[1].Party)
   862  	require.Equal(t, "p3", ps2[2].Party)
   863  	require.Equal(t, "-80", ps2[0].Score)
   864  	require.Equal(t, "30", ps2[1].Score)
   865  	require.Equal(t, "75", ps2[2].Score)
   866  }
   867  
   868  func TestPublishGameMetricFees(t *testing.T) {
   869  	ctx := context.Background()
   870  	epochService := &DummyEpochEngine{}
   871  	ctrl := gomock.NewController(t)
   872  	teams := mocks.NewMockTeams(ctrl)
   873  	balanceChecker := mocks.NewMockAccountBalanceChecker(ctrl)
   874  	broker := bmocks.NewMockBroker(ctrl)
   875  	gameScoreEvents := []events.Event{}
   876  	broker.EXPECT().SendBatch(gomock.Any()).AnyTimes()
   877  	broker.EXPECT().Send(gomock.Any()).Do(func(evt events.Event) {
   878  		if evt.StreamMessage().GetGameScores() != nil {
   879  			gameScoreEvents = append(gameScoreEvents, evt)
   880  		}
   881  	}).AnyTimes()
   882  	tracker := NewMarketActivityTracker(logging.NewTestLogger(), teams, balanceChecker, broker, DummyCollateralEngine{})
   883  	epochService.NotifyOnEpoch(tracker.OnEpochEvent, tracker.OnEpochRestore)
   884  	tracker.SetEligibilityChecker(&DummyEligibilityChecker{})
   885  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START, StartTime: time.Unix(0, 0)})
   886  
   887  	balanceChecker.EXPECT().GetAvailableBalance(gomock.Any()).Return(num.NewUint(0), nil).AnyTimes()
   888  
   889  	tracker.MarketProposed("a1", "m1", "me")
   890  	tracker.MarketProposed("a1", "m2", "me2")
   891  
   892  	ds1 := &vgproto.DispatchStrategy{
   893  		AssetForMetric:       "a1",
   894  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID,
   895  		Markets:              []string{"m1"},
   896  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   897  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   898  		WindowLength:         1,
   899  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   900  	}
   901  	ds2 := &vgproto.DispatchStrategy{
   902  		AssetForMetric:       "a1",
   903  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID,
   904  		Markets:              []string{"m2"},
   905  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   906  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   907  		WindowLength:         1,
   908  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   909  	}
   910  	ds3 := &vgproto.DispatchStrategy{
   911  		AssetForMetric:       "a1",
   912  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID,
   913  		Markets:              []string{"m1", "m2"},
   914  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   915  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   916  		WindowLength:         1,
   917  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   918  	}
   919  	ds4 := &vgproto.DispatchStrategy{
   920  		AssetForMetric:       "a1",
   921  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_PAID,
   922  		Markets:              []string{},
   923  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   924  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   925  		WindowLength:         1,
   926  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   927  	}
   928  	ds5 := &vgproto.DispatchStrategy{
   929  		AssetForMetric:       "a1",
   930  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED,
   931  		Markets:              []string{"m1"},
   932  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   933  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   934  		WindowLength:         1,
   935  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   936  	}
   937  	ds6 := &vgproto.DispatchStrategy{
   938  		AssetForMetric:       "a1",
   939  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED,
   940  		Markets:              []string{"m2"},
   941  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   942  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   943  		WindowLength:         1,
   944  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   945  	}
   946  	ds7 := &vgproto.DispatchStrategy{
   947  		AssetForMetric:       "a1",
   948  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED,
   949  		Markets:              []string{"m1", "m2"},
   950  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   951  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   952  		WindowLength:         1,
   953  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   954  	}
   955  	ds8 := &vgproto.DispatchStrategy{
   956  		AssetForMetric:       "a1",
   957  		Metric:               vgproto.DispatchMetric_DISPATCH_METRIC_MAKER_FEES_RECEIVED,
   958  		Markets:              []string{},
   959  		EntityScope:          vgproto.EntityScope_ENTITY_SCOPE_INDIVIDUALS,
   960  		IndividualScope:      vgproto.IndividualScope_INDIVIDUAL_SCOPE_ALL,
   961  		WindowLength:         1,
   962  		DistributionStrategy: vgproto.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA,
   963  	}
   964  
   965  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_START})
   966  
   967  	tracker.MarketProposed("a1", "market1", "me")
   968  	tracker.MarketProposed("a1", "market2", "me2")
   969  
   970  	// update with a few transfers
   971  	transfersM1 := []*types.Transfer{
   972  		{Owner: "p1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(100)}},
   973  		{Owner: "p1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(200)}},
   974  		{Owner: "p1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(400)}},
   975  		{Owner: "p1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(300)}},
   976  		{Owner: "p2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(900)}},
   977  		{Owner: "p2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(800)}},
   978  		{Owner: "p2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(600)}},
   979  		{Owner: "p2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(200)}},
   980  	}
   981  	tracker.UpdateFeesFromTransfers("a1", "m1", transfersM1)
   982  
   983  	transfersM2 := []*types.Transfer{
   984  		{Owner: "p1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "a2", Amount: num.NewUint(150)}},
   985  		{Owner: "p2", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "a2", Amount: num.NewUint(150)}},
   986  	}
   987  	tracker.UpdateFeesFromTransfers("a1", "m2", transfersM2)
   988  
   989  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5, ds6, ds7, ds8}, time.Unix(10, 0))
   990  	require.Equal(t, 8, len(gameScoreEvents))
   991  
   992  	ps1 := gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
   993  	require.Equal(t, "p1", ps1[0].Party)
   994  	require.Equal(t, "0.3333333333333333", ps1[0].Score)
   995  	require.Equal(t, "p2", ps1[1].Party)
   996  	require.Equal(t, "0.6666666666666667", ps1[1].Score)
   997  
   998  	ps2 := gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
   999  	require.Equal(t, "p1", ps2[0].Party)
  1000  	require.Equal(t, "0", ps2[0].Score)
  1001  	require.False(t, ps2[0].IsEligible)
  1002  	require.Equal(t, "p2", ps2[1].Party)
  1003  	require.Equal(t, "1", ps2[1].Score)
  1004  
  1005  	ps3 := gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
  1006  	require.Equal(t, "p1", ps3[0].Party)
  1007  	require.Equal(t, "0.303030303030303", ps3[0].Score)
  1008  	require.Equal(t, "p2", ps3[1].Party)
  1009  	require.Equal(t, "0.696969696969697", ps3[1].Score)
  1010  
  1011  	ps4 := gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
  1012  	require.Equal(t, "p1", ps4[0].Party)
  1013  	require.Equal(t, "0.303030303030303", ps4[0].Score)
  1014  	require.Equal(t, "p2", ps4[1].Party)
  1015  	require.Equal(t, "0.696969696969697", ps4[1].Score)
  1016  
  1017  	ps5 := gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
  1018  	require.Equal(t, "p1", ps5[0].Party)
  1019  	require.Equal(t, "0.25", ps5[0].Score)
  1020  	require.Equal(t, "p2", ps5[1].Party)
  1021  	require.Equal(t, "0.75", ps5[1].Score)
  1022  
  1023  	ps6 := gameScoreEvents[5].StreamMessage().GetGameScores().PartyScores
  1024  	require.Equal(t, "p1", ps6[0].Party)
  1025  	require.Equal(t, "1", ps6[0].Score)
  1026  	require.Equal(t, "p2", ps6[1].Party)
  1027  	require.Equal(t, "0", ps6[1].Score)
  1028  	require.False(t, ps6[1].IsEligible)
  1029  
  1030  	ps7 := gameScoreEvents[6].StreamMessage().GetGameScores().PartyScores
  1031  	require.Equal(t, "p1", ps7[0].Party)
  1032  	require.Equal(t, "0.3023255813953488", ps7[0].Score)
  1033  	require.Equal(t, "p2", ps7[1].Party)
  1034  	require.Equal(t, "0.6976744186046512", ps7[1].Score)
  1035  
  1036  	ps8 := gameScoreEvents[7].StreamMessage().GetGameScores().PartyScores
  1037  	require.Equal(t, "p1", ps8[0].Party)
  1038  	require.Equal(t, "0.3023255813953488", ps8[0].Score)
  1039  	require.Equal(t, "p2", ps8[1].Party)
  1040  	require.Equal(t, "0.6976744186046512", ps8[1].Score)
  1041  
  1042  	epochService.target(context.Background(), types.Epoch{Seq: 1, Action: vgproto.EpochAction_EPOCH_ACTION_END})
  1043  	epochService.target(context.Background(), types.Epoch{Seq: 2, Action: vgproto.EpochAction_EPOCH_ACTION_START})
  1044  
  1045  	ds1.WindowLength = 2
  1046  	ds2.WindowLength = 2
  1047  	ds3.WindowLength = 2
  1048  	ds4.WindowLength = 2
  1049  	ds5.WindowLength = 2
  1050  	ds6.WindowLength = 2
  1051  	ds7.WindowLength = 2
  1052  	ds8.WindowLength = 2
  1053  
  1054  	// pay/receive some fees in me for the new epoch
  1055  	transfersM1 = []*types.Transfer{
  1056  		{Owner: "p1", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(300)}},
  1057  		{Owner: "p1", Type: types.TransferTypeMakerFeePay, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(100)}},
  1058  		{Owner: "p2", Type: types.TransferTypeMakerFeeReceive, Amount: &types.FinancialAmount{Asset: "a1", Amount: num.NewUint(900)}},
  1059  	}
  1060  	tracker.UpdateFeesFromTransfers("a1", "m1", transfersM1)
  1061  
  1062  	gameScoreEvents = []events.Event{}
  1063  	tracker.PublishGameMetric(ctx, []*vgproto.DispatchStrategy{ds1, ds2, ds3, ds4, ds5, ds6, ds7, ds8}, time.Unix(20, 0))
  1064  	require.Equal(t, 8, len(gameScoreEvents))
  1065  
  1066  	ps1 = gameScoreEvents[0].StreamMessage().GetGameScores().PartyScores
  1067  	require.Equal(t, "p1", ps1[0].Party)
  1068  	require.Equal(t, "0.375", ps1[0].Score)
  1069  	require.Equal(t, "p2", ps1[1].Party)
  1070  	require.Equal(t, "0.625", ps1[1].Score)
  1071  
  1072  	ps2 = gameScoreEvents[1].StreamMessage().GetGameScores().PartyScores
  1073  	require.Equal(t, "p1", ps2[0].Party)
  1074  	require.Equal(t, "0", ps2[0].Score)
  1075  	require.False(t, ps2[0].IsEligible)
  1076  	require.Equal(t, "p2", ps2[1].Party)
  1077  	require.Equal(t, "1", ps2[1].Score)
  1078  
  1079  	ps3 = gameScoreEvents[2].StreamMessage().GetGameScores().PartyScores
  1080  	require.Equal(t, "p1", ps3[0].Party)
  1081  	require.Equal(t, "0.3428571428571429", ps3[0].Score)
  1082  	require.Equal(t, "p2", ps3[1].Party)
  1083  	require.Equal(t, "0.6571428571428571", ps3[1].Score)
  1084  
  1085  	ps4 = gameScoreEvents[3].StreamMessage().GetGameScores().PartyScores
  1086  	require.Equal(t, "p1", ps4[0].Party)
  1087  	require.Equal(t, "0.3428571428571429", ps4[0].Score)
  1088  	require.Equal(t, "p2", ps4[1].Party)
  1089  	require.Equal(t, "0.6571428571428571", ps4[1].Score)
  1090  
  1091  	ps5 = gameScoreEvents[4].StreamMessage().GetGameScores().PartyScores
  1092  	require.Equal(t, "p1", ps5[0].Party)
  1093  	require.Equal(t, "0.25", ps5[0].Score)
  1094  	require.Equal(t, "p2", ps5[1].Party)
  1095  	require.Equal(t, "0.75", ps5[1].Score)
  1096  
  1097  	ps6 = gameScoreEvents[5].StreamMessage().GetGameScores().PartyScores
  1098  	require.Equal(t, "p1", ps6[0].Party)
  1099  	require.Equal(t, "1", ps6[0].Score)
  1100  	require.Equal(t, "p2", ps6[1].Party)
  1101  	require.Equal(t, "0", ps6[1].Score)
  1102  	require.False(t, ps6[1].IsEligible)
  1103  
  1104  	ps7 = gameScoreEvents[6].StreamMessage().GetGameScores().PartyScores
  1105  	require.Equal(t, "p1", ps7[0].Party)
  1106  	require.Equal(t, "0.2835820895522388", ps7[0].Score)
  1107  	require.Equal(t, "p2", ps7[1].Party)
  1108  	require.Equal(t, "0.7164179104477612", ps7[1].Score)
  1109  
  1110  	ps8 = gameScoreEvents[7].StreamMessage().GetGameScores().PartyScores
  1111  	require.Equal(t, "p1", ps8[0].Party)
  1112  	require.Equal(t, "0.2835820895522388", ps8[0].Score)
  1113  	require.Equal(t, "p2", ps8[1].Party)
  1114  	require.Equal(t, "0.7164179104477612", ps8[1].Score)
  1115  }