code.vegaprotocol.io/vega@v0.79.0/core/execution/stoporders/trailing_stop_orders_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 stoporders_test
    17  
    18  import (
    19  	"testing"
    20  
    21  	"code.vegaprotocol.io/vega/core/execution/stoporders"
    22  	"code.vegaprotocol.io/vega/core/types"
    23  	"code.vegaprotocol.io/vega/libs/num"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  )
    27  
    28  func TestTrailingRemoveOrderRisesAbove(t *testing.T) {
    29  	trailing := stoporders.NewTrailingStopOrders()
    30  
    31  	// initial price
    32  	trailing.PriceUpdated(num.NewUint(50))
    33  	trailing.Insert("a", num.DecimalFromFloat(0.50), types.StopOrderTriggerDirectionRisesAbove)
    34  
    35  	trailing.PriceUpdated(num.NewUint(60))
    36  	trailing.Insert("b", num.DecimalFromFloat(0.10), types.StopOrderTriggerDirectionRisesAbove)
    37  
    38  	atPrice, offset, ok := trailing.Exists("a")
    39  	assert.True(t, ok)
    40  	assert.Equal(t, atPrice, num.NewUint(50))
    41  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
    42  
    43  	atPrice, offset, ok = trailing.Exists("b")
    44  	assert.True(t, ok)
    45  	assert.Equal(t, atPrice, num.NewUint(60))
    46  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
    47  
    48  	affectedOrders := trailing.PriceUpdated(num.NewUint(55))
    49  	assert.Len(t, affectedOrders, 0)
    50  
    51  	atPrice, offset, ok = trailing.Exists("a")
    52  	assert.True(t, ok)
    53  	assert.Equal(t, atPrice, num.NewUint(50))
    54  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
    55  
    56  	atPrice, offset, ok = trailing.Exists("b")
    57  	assert.True(t, ok)
    58  	assert.Equal(t, atPrice, num.NewUint(55))
    59  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
    60  
    61  	// should have 2 in the 55 bucket
    62  	trailing.Insert("c", num.DecimalFromFloat(0.15), types.StopOrderTriggerDirectionRisesAbove)
    63  
    64  	// now remove stuff
    65  	// 2 levels
    66  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionRisesAbove), 2)
    67  
    68  	err := trailing.Remove("b")
    69  	assert.NoError(t, err)
    70  	_, _, ok = trailing.Exists("b")
    71  	assert.False(t, ok)
    72  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionRisesAbove), 2)
    73  	err = trailing.Remove("b")
    74  	assert.EqualError(t, err, "order not found")
    75  
    76  	err = trailing.Remove("a")
    77  	assert.NoError(t, err)
    78  	_, _, ok = trailing.Exists("a")
    79  	assert.False(t, ok)
    80  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionRisesAbove), 1)
    81  	err = trailing.Remove("a")
    82  	assert.EqualError(t, err, "order not found")
    83  
    84  	err = trailing.Remove("c")
    85  	assert.NoError(t, err)
    86  	_, _, ok = trailing.Exists("c")
    87  	assert.False(t, ok)
    88  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionRisesAbove), 0)
    89  	err = trailing.Remove("c")
    90  	assert.EqualError(t, err, "order not found")
    91  }
    92  
    93  func TestTrailingRemoveOrderFallsBelow(t *testing.T) {
    94  	trailing := stoporders.NewTrailingStopOrders()
    95  
    96  	// initial price
    97  	trailing.PriceUpdated(num.NewUint(50))
    98  	trailing.Insert("a", num.DecimalFromFloat(0.50), types.StopOrderTriggerDirectionFallsBelow)
    99  
   100  	trailing.PriceUpdated(num.NewUint(40))
   101  	trailing.Insert("b", num.DecimalFromFloat(0.10), types.StopOrderTriggerDirectionFallsBelow)
   102  
   103  	atPrice, offset, ok := trailing.Exists("a")
   104  	assert.True(t, ok)
   105  	assert.Equal(t, atPrice, num.NewUint(50))
   106  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   107  
   108  	atPrice, offset, ok = trailing.Exists("b")
   109  	assert.True(t, ok)
   110  	assert.Equal(t, atPrice, num.NewUint(40))
   111  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
   112  
   113  	affectedOrders := trailing.PriceUpdated(num.NewUint(45))
   114  	assert.Len(t, affectedOrders, 0)
   115  
   116  	atPrice, offset, ok = trailing.Exists("a")
   117  	assert.True(t, ok)
   118  	assert.Equal(t, atPrice, num.NewUint(50))
   119  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   120  
   121  	atPrice, offset, ok = trailing.Exists("b")
   122  	assert.True(t, ok)
   123  	assert.Equal(t, atPrice, num.NewUint(45))
   124  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
   125  
   126  	// should have 2 in the 55 bucket
   127  	trailing.Insert("c", num.DecimalFromFloat(0.15), types.StopOrderTriggerDirectionFallsBelow)
   128  
   129  	// now remove stuff
   130  	// 2 levels
   131  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionFallsBelow), 2)
   132  
   133  	err := trailing.Remove("b")
   134  	assert.NoError(t, err)
   135  	_, _, ok = trailing.Exists("b")
   136  	assert.False(t, ok)
   137  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionFallsBelow), 2)
   138  	err = trailing.Remove("b")
   139  	assert.EqualError(t, err, "order not found")
   140  
   141  	err = trailing.Remove("a")
   142  	assert.NoError(t, err)
   143  	_, _, ok = trailing.Exists("a")
   144  	assert.False(t, ok)
   145  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionFallsBelow), 1)
   146  	err = trailing.Remove("a")
   147  	assert.EqualError(t, err, "order not found")
   148  
   149  	err = trailing.Remove("c")
   150  	assert.NoError(t, err)
   151  	_, _, ok = trailing.Exists("c")
   152  	assert.False(t, ok)
   153  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionFallsBelow), 0)
   154  	err = trailing.Remove("c")
   155  	assert.EqualError(t, err, "order not found")
   156  }
   157  
   158  // - A trailing stop order for a 5% drop placed when the price is `50`, followed by a
   159  // price rise to `60` will:
   160  //   - Be triggered by a fall to `57`. (<a name="0014-ORDT-027"
   161  //
   162  // href="#0014-ORDT-027">0014-ORDT-027</a>)
   163  //   - Not be triggered by a fall to `58`. (<a name="0014-ORDT-036"
   164  //
   165  // href="#0014-ORDT-036">0014-ORDT-036</a>).
   166  func TestTrailingAC_0014_ORDT_027_036(t *testing.T) {
   167  	trailing := stoporders.NewTrailingStopOrders()
   168  
   169  	// initial price
   170  	trailing.PriceUpdated(num.NewUint(50))
   171  	trailing.Insert("a", num.DecimalFromFloat(0.05), types.StopOrderTriggerDirectionFallsBelow)
   172  
   173  	// price move
   174  	trailing.PriceUpdated(num.NewUint(60))
   175  
   176  	atPrice, offset, ok := trailing.Exists("a")
   177  	assert.True(t, ok)
   178  	assert.Equal(t, atPrice, num.NewUint(60))
   179  	assert.Equal(t, offset, num.DecimalFromFloat(0.05))
   180  
   181  	// move price to 58, nothing happen
   182  	affectedOrders := trailing.PriceUpdated(num.NewUint(58))
   183  	assert.Len(t, affectedOrders, 0)
   184  
   185  	affectedOrders = trailing.PriceUpdated(num.NewUint(57))
   186  	assert.Len(t, affectedOrders, 1)
   187  
   188  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionFallsBelow), 0)
   189  }
   190  
   191  // A trailing stop order for a 5% rise placed when the price is `50`, followed by a drop
   192  // to `40` will:
   193  //   - Be triggered by a rise to `42`. (<a name="0014-ORDT-028"
   194  //
   195  // href="#0014-ORDT-028">0014-ORDT-028</a>)
   196  //   - Not be triggered by a rise to `41`. (<a name="0014-ORDT-029"
   197  //
   198  // href="#0014-ORDT-029">0014-ORDT-029</a>).
   199  func TestTrailingAC_0014_ORDT_827_029(t *testing.T) {
   200  	trailing := stoporders.NewTrailingStopOrders()
   201  
   202  	// initial price
   203  	trailing.PriceUpdated(num.NewUint(50))
   204  	trailing.Insert("a", num.DecimalFromFloat(0.05), types.StopOrderTriggerDirectionRisesAbove)
   205  
   206  	// price move
   207  	trailing.PriceUpdated(num.NewUint(40))
   208  
   209  	atPrice, offset, ok := trailing.Exists("a")
   210  	assert.True(t, ok)
   211  	assert.Equal(t, atPrice, num.NewUint(40))
   212  	assert.Equal(t, offset, num.DecimalFromFloat(0.05))
   213  
   214  	// move price to 41, nothing happen
   215  	affectedOrders := trailing.PriceUpdated(num.NewUint(41))
   216  	assert.Len(t, affectedOrders, 0)
   217  
   218  	affectedOrders = trailing.PriceUpdated(num.NewUint(42))
   219  	assert.Len(t, affectedOrders, 1)
   220  
   221  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionRisesAbove), 0)
   222  }
   223  
   224  // A trailing stop order for a 25% drop placed when the price is `50`, followed by a
   225  // price rise to `60`, then to `50`, then another rise to `57` will:
   226  //   - Be triggered by a fall to `45`. (<a name="0014-ORDT-030"
   227  //
   228  // href="#0014-ORDT-030">0014-ORDT-030</a>)
   229  //   - Not be triggered by a fall to `46`. (<a name="0014-ORDT-031"
   230  //
   231  // href="#0014-ORDT-031">0014-ORDT-031</a>).
   232  func TestTrailingAC_0014_ORDT_030_031(t *testing.T) {
   233  	trailing := stoporders.NewTrailingStopOrders()
   234  
   235  	// initial price
   236  	trailing.PriceUpdated(num.NewUint(50))
   237  	trailing.Insert("a", num.DecimalFromFloat(0.25), types.StopOrderTriggerDirectionFallsBelow)
   238  
   239  	// price move
   240  	affectedOrders := trailing.PriceUpdated(num.NewUint(60))
   241  	assert.Len(t, affectedOrders, 0)
   242  
   243  	affectedOrders = trailing.PriceUpdated(num.NewUint(50))
   244  	assert.Len(t, affectedOrders, 0)
   245  
   246  	affectedOrders = trailing.PriceUpdated(num.NewUint(57))
   247  	assert.Len(t, affectedOrders, 0)
   248  
   249  	atPrice, offset, ok := trailing.Exists("a")
   250  	assert.True(t, ok)
   251  	assert.Equal(t, atPrice, num.NewUint(60))
   252  	assert.Equal(t, offset, num.DecimalFromFloat(0.25))
   253  
   254  	// move price to 46, nothing happen
   255  	affectedOrders = trailing.PriceUpdated(num.NewUint(46))
   256  	assert.Len(t, affectedOrders, 0)
   257  
   258  	affectedOrders = trailing.PriceUpdated(num.NewUint(45))
   259  	assert.Len(t, affectedOrders, 1)
   260  
   261  	assert.Equal(t, trailing.Len(types.StopOrderTriggerDirectionFallsBelow), 0)
   262  }
   263  
   264  func TestTrailingStopOrdersMultipleOffsetPerPriceFallsBelow(t *testing.T) {
   265  	trailing := stoporders.NewTrailingStopOrders()
   266  
   267  	// initial price
   268  	trailing.PriceUpdated(num.NewUint(50))
   269  	// won't trigger unless it goes bellow 50%
   270  	trailing.Insert("a", num.DecimalFromFloat(0.50), types.StopOrderTriggerDirectionFallsBelow)
   271  
   272  	trailing.PriceUpdated(num.NewUint(40))
   273  	// won't trigger unless it goes bellow 50%
   274  	trailing.Insert("b", num.DecimalFromFloat(0.10), types.StopOrderTriggerDirectionFallsBelow)
   275  
   276  	// as of no they should be in 2 differen buckets
   277  
   278  	atPrice, offset, ok := trailing.Exists("a")
   279  	assert.True(t, ok)
   280  	assert.Equal(t, atPrice, num.NewUint(50))
   281  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   282  
   283  	atPrice, offset, ok = trailing.Exists("b")
   284  	assert.True(t, ok)
   285  	assert.Equal(t, atPrice, num.NewUint(40))
   286  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
   287  
   288  	affectedOrders := trailing.PriceUpdated(num.NewUint(45))
   289  	assert.Len(t, affectedOrders, 0)
   290  
   291  	// ensure a is still in the same bucked
   292  	// b moved to 45
   293  
   294  	atPrice, offset, ok = trailing.Exists("a")
   295  	assert.True(t, ok)
   296  	assert.Equal(t, atPrice, num.NewUint(50))
   297  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   298  
   299  	atPrice, offset, ok = trailing.Exists("b")
   300  	assert.True(t, ok)
   301  	assert.Equal(t, atPrice, num.NewUint(45))
   302  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
   303  
   304  	affectedOrders = trailing.PriceUpdated(num.NewUint(60))
   305  	assert.Len(t, affectedOrders, 0)
   306  
   307  	// ensure they are in the same buckets
   308  
   309  	atPrice, offset, ok = trailing.Exists("a")
   310  	assert.True(t, ok)
   311  	assert.Equal(t, atPrice, num.NewUint(60))
   312  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   313  
   314  	atPrice, offset, ok = trailing.Exists("b")
   315  	assert.True(t, ok)
   316  	assert.Equal(t, atPrice, num.NewUint(60))
   317  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
   318  
   319  	// now move prices so b triggers
   320  	affectedOrders = trailing.PriceUpdated(num.NewUint(54))
   321  	assert.Len(t, affectedOrders, 1)
   322  	assert.Equal(t, affectedOrders[0], "b")
   323  
   324  	atPrice, offset, ok = trailing.Exists("a")
   325  	assert.True(t, ok)
   326  	assert.Equal(t, atPrice, num.NewUint(60))
   327  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   328  
   329  	_, _, ok = trailing.Exists("b")
   330  	assert.False(t, ok)
   331  }
   332  
   333  func TestTrailingStopOrdersMultipleOffsetPerPriceRisesAbove(t *testing.T) {
   334  	trailing := stoporders.NewTrailingStopOrders()
   335  
   336  	// initial price
   337  	trailing.PriceUpdated(num.NewUint(50))
   338  	// won't trigger unless it goes bellow 50%
   339  	trailing.Insert("a", num.DecimalFromFloat(0.50), types.StopOrderTriggerDirectionRisesAbove)
   340  
   341  	trailing.PriceUpdated(num.NewUint(60))
   342  	// won't trigger unless it goes bellow 50%
   343  	trailing.Insert("b", num.DecimalFromFloat(0.10), types.StopOrderTriggerDirectionRisesAbove)
   344  
   345  	// as of no they should be in 2 differen buckets
   346  
   347  	atPrice, offset, ok := trailing.Exists("a")
   348  	assert.True(t, ok)
   349  	assert.Equal(t, atPrice, num.NewUint(50))
   350  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   351  
   352  	atPrice, offset, ok = trailing.Exists("b")
   353  	assert.True(t, ok)
   354  	assert.Equal(t, atPrice, num.NewUint(60))
   355  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
   356  
   357  	affectedOrders := trailing.PriceUpdated(num.NewUint(55))
   358  	assert.Len(t, affectedOrders, 0)
   359  
   360  	// ensure a is still in the same bucked
   361  	// b moved to 45
   362  
   363  	atPrice, offset, ok = trailing.Exists("a")
   364  	assert.True(t, ok)
   365  	assert.Equal(t, atPrice, num.NewUint(50))
   366  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   367  
   368  	atPrice, offset, ok = trailing.Exists("b")
   369  	assert.True(t, ok)
   370  	assert.Equal(t, atPrice, num.NewUint(55))
   371  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
   372  
   373  	affectedOrders = trailing.PriceUpdated(num.NewUint(40))
   374  	assert.Len(t, affectedOrders, 0)
   375  
   376  	// ensure they are in the same buckets
   377  
   378  	atPrice, offset, ok = trailing.Exists("a")
   379  	assert.True(t, ok)
   380  	assert.Equal(t, atPrice, num.NewUint(40))
   381  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   382  
   383  	atPrice, offset, ok = trailing.Exists("b")
   384  	assert.True(t, ok)
   385  	assert.Equal(t, atPrice, num.NewUint(40))
   386  	assert.Equal(t, offset, num.DecimalFromFloat(0.10))
   387  
   388  	// now move prices so b triggers
   389  	affectedOrders = trailing.PriceUpdated(num.NewUint(44))
   390  	assert.Len(t, affectedOrders, 1)
   391  	assert.Equal(t, affectedOrders[0], "b")
   392  
   393  	atPrice, offset, ok = trailing.Exists("a")
   394  	assert.True(t, ok)
   395  	assert.Equal(t, atPrice, num.NewUint(40))
   396  	assert.Equal(t, offset, num.DecimalFromFloat(0.50))
   397  
   398  	_, _, ok = trailing.Exists("b")
   399  	assert.False(t, ok)
   400  }