code.vegaprotocol.io/vega@v0.79.0/commands/stop_orders_submission.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 commands
    17  
    18  import (
    19  	"fmt"
    20  	"math/big"
    21  
    22  	"code.vegaprotocol.io/vega/libs/num"
    23  	types "code.vegaprotocol.io/vega/protos/vega"
    24  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    25  )
    26  
    27  func CheckStopOrdersSubmission(cmd *commandspb.StopOrdersSubmission) error {
    28  	return checkStopOrdersSubmission(cmd).ErrorOrNil()
    29  }
    30  
    31  func checkStopOrdersSubmission(cmd *commandspb.StopOrdersSubmission) Errors {
    32  	errs := NewErrors()
    33  
    34  	if cmd == nil {
    35  		return errs.FinalAddForProperty("stop_orders_submission", ErrIsRequired)
    36  	}
    37  
    38  	var market1, market2 string
    39  	if cmd.FallsBelow != nil {
    40  		checkStopOrderSetup(
    41  			"stop_orders_submission.falls_below", errs, cmd.FallsBelow, cmd.RisesAbove != nil)
    42  		if cmd.FallsBelow.OrderSubmission != nil {
    43  			market1 = cmd.FallsBelow.OrderSubmission.MarketId
    44  			if cmd.FallsBelow.SizeOverrideSetting != nil {
    45  				if *cmd.FallsBelow.SizeOverrideSetting == types.StopOrder_SIZE_OVERRIDE_SETTING_POSITION {
    46  					if cmd.FallsBelow.SizeOverrideValue != nil {
    47  						value := cmd.FallsBelow.SizeOverrideValue.GetPercentage()
    48  						// Check that the string represents a number between >0&&>=1
    49  						percentage, err := num.DecimalFromString(value)
    50  						if err != nil {
    51  							return errs.FinalAddForProperty("stop_orders_submission.falls_below.size_override_value", ErrIsNotValidNumber)
    52  						}
    53  						if percentage.LessThanOrEqual(num.DecimalFromFloat(0.0)) {
    54  							return errs.FinalAddForProperty("stop_orders_submission.falls_below.size_override_value", ErrMustBeBetween01)
    55  						}
    56  						if percentage.GreaterThan(num.DecimalFromFloat(1.0)) {
    57  							return errs.FinalAddForProperty("stop_orders_submission.falls_below.size_override_value", ErrMustBeBetween01)
    58  						}
    59  					}
    60  				}
    61  			}
    62  		}
    63  	}
    64  
    65  	if cmd.RisesAbove != nil {
    66  		checkStopOrderSetup(
    67  			"stop_orders_submission.rises_below", errs, cmd.RisesAbove, cmd.FallsBelow != nil)
    68  		if cmd.RisesAbove.OrderSubmission != nil {
    69  			market2 = cmd.RisesAbove.OrderSubmission.MarketId
    70  			if cmd.RisesAbove.SizeOverrideSetting != nil {
    71  				if *cmd.RisesAbove.SizeOverrideSetting == types.StopOrder_SIZE_OVERRIDE_SETTING_POSITION {
    72  					if cmd.RisesAbove.SizeOverrideValue != nil {
    73  						value := cmd.RisesAbove.SizeOverrideValue.GetPercentage()
    74  						// Check that the string represents a number between >0&&>=1
    75  						percentage, err := num.DecimalFromString(value)
    76  						if err != nil {
    77  							return errs.FinalAddForProperty("stop_orders_submission.rises_above.size_override_value", ErrIsNotValidNumber)
    78  						}
    79  						if percentage.LessThanOrEqual(num.DecimalFromFloat(0.0)) {
    80  							return errs.FinalAddForProperty("stop_orders_submission.rises_above.size_override_value", ErrMustBeBetween01)
    81  						}
    82  						if percentage.GreaterThan(num.DecimalFromFloat(1.0)) {
    83  							return errs.FinalAddForProperty("stop_orders_submission.rises_above.size_override_value", ErrMustBeBetween01)
    84  						}
    85  					}
    86  				}
    87  			}
    88  		}
    89  	}
    90  
    91  	if cmd.FallsBelow == nil && cmd.RisesAbove == nil {
    92  		return errs.FinalAdd(ErrMustHaveAtLeastOneOfRisesAboveOrFallsBelow)
    93  	}
    94  
    95  	if cmd.FallsBelow != nil && cmd.RisesAbove != nil && market1 != market2 {
    96  		return errs.FinalAdd(ErrFallsBelowAndRiseAboveMarketIDMustBeTheSame)
    97  	}
    98  
    99  	return errs
   100  }
   101  
   102  func checkStopOrderSetup(
   103  	fieldName string,
   104  	errs Errors,
   105  	setup *commandspb.StopOrderSetup,
   106  	isOCO bool,
   107  ) {
   108  	if err := checkOrderSubmission(setup.OrderSubmission); !err.Empty() {
   109  		errs.Merge(err)
   110  	}
   111  
   112  	if setup.OrderSubmission != nil && setup.OrderSubmission.TimeInForce == types.Order_TIME_IN_FORCE_GFA {
   113  		errs.AddForProperty(fmt.Sprintf("%s.order_submission.time_in_force", fieldName), ErrIsNotValid)
   114  	}
   115  
   116  	if setup.ExpiresAt != nil {
   117  		if *setup.ExpiresAt < 0 {
   118  			errs.AddForProperty(fmt.Sprintf("%s.expires_at", fieldName), ErrMustBePositive)
   119  		}
   120  		if setup.ExpiryStrategy == nil {
   121  			errs.AddForProperty(fmt.Sprintf("%s.expiry_strategy", fieldName), ErrExpiryStrategyRequiredWhenExpiresAtSet)
   122  		} else {
   123  			if *setup.ExpiryStrategy == types.StopOrder_EXPIRY_STRATEGY_UNSPECIFIED {
   124  				errs.AddForProperty(fmt.Sprintf("%s.expiry_strategy", fieldName), ErrIsRequired)
   125  			} else if _, ok := types.StopOrder_ExpiryStrategy_name[int32(*setup.ExpiryStrategy)]; !ok {
   126  				errs.AddForProperty(fmt.Sprintf("%s.expiry_strategy", fieldName), ErrIsNotValid)
   127  			} else if isOCO && *setup.ExpiryStrategy == types.StopOrder_EXPIRY_STRATEGY_SUBMIT {
   128  				errs.AddForProperty(fmt.Sprintf("%s.expiry_strategy", fieldName), ErrIsNotValidWithOCO)
   129  			}
   130  		}
   131  	}
   132  
   133  	if setup.Trigger != nil {
   134  		switch t := setup.Trigger.(type) {
   135  		case *commandspb.StopOrderSetup_Price:
   136  			if len(t.Price) <= 0 {
   137  				errs.AddForProperty(fmt.Sprintf("%s.trigger.price", fieldName), ErrIsRequired)
   138  			} else {
   139  				if price, ok := big.NewInt(0).SetString(t.Price, 10); !ok {
   140  					errs.AddForProperty(fmt.Sprintf("%s.trigger.price", fieldName), ErrNotAValidInteger)
   141  				} else if price.Cmp(big.NewInt(0)) != 1 {
   142  					errs.AddForProperty(fmt.Sprintf("%s.trigger.price", fieldName), ErrMustBePositive)
   143  				}
   144  			}
   145  		case *commandspb.StopOrderSetup_TrailingPercentOffset:
   146  			if len(t.TrailingPercentOffset) <= 0 {
   147  				errs.AddForProperty(fmt.Sprintf("%s.trigger.trailing_percent_offset", fieldName), ErrIsRequired)
   148  			} else {
   149  				if poffset, err := num.DecimalFromString(t.TrailingPercentOffset); err != nil {
   150  					errs.AddForProperty(fmt.Sprintf("%s.trigger.trailing_percent_offset", fieldName), ErrNotAValidFloat)
   151  				} else if poffset.LessThanOrEqual(num.DecimalZero()) || poffset.GreaterThanOrEqual(num.DecimalOne()) {
   152  					errs.AddForProperty(fmt.Sprintf("%s.trigger.trailing_percent_offset", fieldName), ErrMustBeWithinRange01)
   153  				} else if !poffset.Mod(num.MustDecimalFromString("0.001")).Equal(num.DecimalZero()) {
   154  					errs.AddForProperty(fmt.Sprintf("%s.trigger.trailing_percent_offset", fieldName), ErrTrailingPercentOffsetMinimalIncrementNotReached)
   155  				}
   156  			}
   157  		}
   158  	} else {
   159  		errs.AddForProperty(fmt.Sprintf("%s.trigger", fieldName), ErrMustHaveAStopOrderTrigger)
   160  	}
   161  }