code.vegaprotocol.io/vega@v0.79.0/core/integration/steps/parties_place_the_following_orders.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 steps
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"strconv"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/core/integration/helpers"
    26  	"code.vegaprotocol.io/vega/core/integration/stubs"
    27  	"code.vegaprotocol.io/vega/core/types"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  	"code.vegaprotocol.io/vega/libs/ptr"
    30  
    31  	"github.com/cucumber/godog"
    32  )
    33  
    34  type Only string
    35  
    36  const (
    37  	None   Only = ""
    38  	Post   Only = "post"
    39  	Reduce Only = "reduce"
    40  )
    41  
    42  var onlyTypes = map[string]Only{
    43  	"":       None,
    44  	"post":   Post,
    45  	"reduce": Reduce,
    46  }
    47  
    48  var refToOrderId = map[string]string{}
    49  
    50  func PartiesPlaceTheFollowingOrdersWithTicks(exec Execution, time *stubs.TimeStub, epochService EpochService, table *godog.Table) error {
    51  	// ensure time is set + idgen is not nil
    52  	now := time.GetTimeNow()
    53  	time.SetTime(now)
    54  	for _, r := range parseSubmitOrderTable(table) {
    55  		row := newSubmitOrderRow(r)
    56  
    57  		orderSubmission := types.OrderSubmission{
    58  			MarketID:    row.MarketID(),
    59  			Side:        row.Side(),
    60  			Price:       row.Price(),
    61  			Size:        row.Volume(),
    62  			ExpiresAt:   row.ExpirationDate(now),
    63  			Type:        row.OrderType(),
    64  			TimeInForce: row.TimeInForce(),
    65  			Reference:   row.Reference(),
    66  		}
    67  		only := row.Only()
    68  		switch only {
    69  		case Post:
    70  			orderSubmission.PostOnly = true
    71  		case Reduce:
    72  			orderSubmission.ReduceOnly = true
    73  		}
    74  
    75  		// check for pegged orders
    76  		if row.PeggedReference() != types.PeggedReferenceUnspecified {
    77  			orderSubmission.PeggedOrder = &types.PeggedOrder{Reference: row.PeggedReference(), Offset: row.PeggedOffset()}
    78  		}
    79  
    80  		// check for stop orders
    81  		stopOrderSubmission, err := buildStopOrder(&orderSubmission, row, now)
    82  		if err != nil {
    83  			return err
    84  		}
    85  
    86  		var resp *types.OrderConfirmation
    87  		if stopOrderSubmission != nil {
    88  			resp, err = exec.SubmitStopOrder(
    89  				context.Background(),
    90  				stopOrderSubmission,
    91  				row.Party(),
    92  			)
    93  		} else {
    94  			resp, err = exec.SubmitOrder(context.Background(), &orderSubmission, row.Party())
    95  		}
    96  
    97  		if ceerr := checkExpectedError(row, err, nil); ceerr != nil {
    98  			return ceerr
    99  		}
   100  
   101  		if !row.ExpectResultingTrades() || err != nil {
   102  			continue
   103  		}
   104  
   105  		if resp != nil {
   106  			actualTradeCount := int64(len(resp.Trades))
   107  			if actualTradeCount != row.ResultingTrades() {
   108  				return formatDiff(fmt.Sprintf("the resulting trades didn't match the expectation for order \"%v\"", row.Reference()),
   109  					map[string]string{
   110  						"total": i64ToS(row.ResultingTrades()),
   111  					},
   112  					map[string]string{
   113  						"total": i64ToS(actualTradeCount),
   114  					},
   115  				)
   116  			}
   117  		}
   118  		// make it look like we start a new block
   119  		epochService.OnBlockEnd(context.Background())
   120  		// indicate block has ended, so we MTM if needed
   121  		exec.BlockEnd(context.Background())
   122  		// trigger OnTick calls, but without actually progressing time
   123  		time.SetTime(time.GetTimeNow())
   124  	}
   125  	return nil
   126  }
   127  
   128  func PartiesPlaceTheFollowingOrdersBlocksApart(exec Execution, time *stubs.TimeStub, block *helpers.Block, epochService EpochService, table *godog.Table, blockCount string) error {
   129  	// ensure time is set + idgen is not nil
   130  	now := time.GetTimeNow()
   131  	time.SetTime(now)
   132  	nr, err := strconv.ParseInt(blockCount, 10, 0)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	for _, r := range parseSubmitOrderTable(table) {
   137  		row := newSubmitOrderRow(r)
   138  
   139  		orderSubmission := types.OrderSubmission{
   140  			MarketID:    row.MarketID(),
   141  			Side:        row.Side(),
   142  			Price:       row.Price(),
   143  			Size:        row.Volume(),
   144  			ExpiresAt:   row.ExpirationDate(now),
   145  			Type:        row.OrderType(),
   146  			TimeInForce: row.TimeInForce(),
   147  			Reference:   row.Reference(),
   148  		}
   149  		only := row.Only()
   150  		switch only {
   151  		case Post:
   152  			orderSubmission.PostOnly = true
   153  		case Reduce:
   154  			orderSubmission.ReduceOnly = true
   155  		}
   156  
   157  		resp, err := exec.SubmitOrder(context.Background(), &orderSubmission, row.Party())
   158  		if ceerr := checkExpectedError(row, err, nil); ceerr != nil {
   159  			return ceerr
   160  		}
   161  
   162  		if !row.ExpectResultingTrades() || err != nil {
   163  			continue
   164  		}
   165  
   166  		actualTradeCount := int64(len(resp.Trades))
   167  		if actualTradeCount != row.ResultingTrades() {
   168  			return formatDiff(fmt.Sprintf("the resulting trades didn't match the expectation for order \"%v\"", row.Reference()),
   169  				map[string]string{
   170  					"total": i64ToS(row.ResultingTrades()),
   171  				},
   172  				map[string]string{
   173  					"total": i64ToS(actualTradeCount),
   174  				},
   175  			)
   176  		}
   177  		now := time.GetTimeNow()
   178  		for i := int64(0); i < nr; i++ {
   179  			epochService.OnBlockEnd(context.Background())
   180  			// end of block
   181  			exec.BlockEnd(context.Background())
   182  			now = now.Add(block.GetDuration())
   183  			// progress time
   184  			time.SetTime(now)
   185  		}
   186  	}
   187  	return nil
   188  }
   189  
   190  func PartiesPlaceTheFollowingHackedOrders(
   191  	exec Execution,
   192  	ts *stubs.TimeStub,
   193  	table *godog.Table,
   194  ) error {
   195  	now := ts.GetTimeNow()
   196  	for _, r := range parseSubmitHackedOrderTable(table) {
   197  		row := newHackedOrderRow(r)
   198  		orderSubmission := types.OrderSubmission{
   199  			MarketID:    row.MarketID(),
   200  			Side:        row.Side(),
   201  			Price:       row.Price(),
   202  			Size:        row.Volume(),
   203  			ExpiresAt:   row.ExpirationDate(now),
   204  			Type:        row.OrderType(),
   205  			TimeInForce: row.TimeInForce(),
   206  			Reference:   row.Reference(),
   207  		}
   208  		party := row.Party()
   209  		if row.IsAMM() {
   210  			if ammP, ok := exec.GetAMMSubAccountID(party); ok {
   211  				party = ammP
   212  			}
   213  		}
   214  		only := row.Only()
   215  		switch only {
   216  		case Post:
   217  			orderSubmission.PostOnly = true
   218  		case Reduce:
   219  			orderSubmission.ReduceOnly = true
   220  		}
   221  
   222  		// check for pegged orders
   223  		if row.PeggedReference() != types.PeggedReferenceUnspecified {
   224  			orderSubmission.PeggedOrder = &types.PeggedOrder{Reference: row.PeggedReference(), Offset: row.PeggedOffset()}
   225  		}
   226  
   227  		// check for stop orders
   228  		stopOrderSubmission, err := buildStopOrder(&orderSubmission, row.submitOrderRow, now)
   229  		if err != nil {
   230  			return err
   231  		}
   232  
   233  		var resp *types.OrderConfirmation
   234  		if stopOrderSubmission != nil {
   235  			resp, err = exec.SubmitStopOrder(
   236  				context.Background(),
   237  				stopOrderSubmission,
   238  				party,
   239  			)
   240  		} else {
   241  			resp, err = exec.SubmitOrder(context.Background(), &orderSubmission, party)
   242  		}
   243  		if ceerr := checkExpectedError(row, err, nil); ceerr != nil {
   244  			return ceerr
   245  		}
   246  
   247  		if !row.ExpectResultingTrades() || err != nil {
   248  			continue
   249  		}
   250  
   251  		if resp == nil {
   252  			continue
   253  		}
   254  
   255  		// If we have a reference, add a reference -> orderID lookup
   256  		if len(resp.Order.Reference) > 0 {
   257  			refToOrderId[resp.Order.Reference] = resp.Order.ID
   258  		}
   259  
   260  		actualTradeCount := int64(len(resp.Trades))
   261  		if actualTradeCount != row.ResultingTrades() {
   262  			return formatDiff(fmt.Sprintf("the resulting trades didn't match the expectation for order \"%v\"", row.Reference()),
   263  				map[string]string{
   264  					"total": i64ToS(row.ResultingTrades()),
   265  				},
   266  				map[string]string{
   267  					"total": i64ToS(actualTradeCount),
   268  				},
   269  			)
   270  		}
   271  	}
   272  	return nil
   273  }
   274  
   275  func PartiesPlaceTheFollowingOrders(
   276  	exec Execution,
   277  	ts *stubs.TimeStub,
   278  	table *godog.Table,
   279  ) error {
   280  	now := ts.GetTimeNow()
   281  	for _, r := range parseSubmitOrderTable(table) {
   282  		row := newSubmitOrderRow(r)
   283  
   284  		orderSubmission := types.OrderSubmission{
   285  			MarketID:    row.MarketID(),
   286  			Side:        row.Side(),
   287  			Price:       row.Price(),
   288  			Size:        row.Volume(),
   289  			ExpiresAt:   row.ExpirationDate(now),
   290  			Type:        row.OrderType(),
   291  			TimeInForce: row.TimeInForce(),
   292  			Reference:   row.Reference(),
   293  		}
   294  		only := row.Only()
   295  		switch only {
   296  		case Post:
   297  			orderSubmission.PostOnly = true
   298  		case Reduce:
   299  			orderSubmission.ReduceOnly = true
   300  		}
   301  
   302  		// check for pegged orders
   303  		if row.PeggedReference() != types.PeggedReferenceUnspecified {
   304  			orderSubmission.PeggedOrder = &types.PeggedOrder{Reference: row.PeggedReference(), Offset: row.PeggedOffset()}
   305  		}
   306  
   307  		// check for stop orders
   308  		stopOrderSubmission, err := buildStopOrder(&orderSubmission, row, now)
   309  		if err != nil {
   310  			return err
   311  		}
   312  
   313  		var resp *types.OrderConfirmation
   314  		if stopOrderSubmission != nil {
   315  			resp, err = exec.SubmitStopOrder(
   316  				context.Background(),
   317  				stopOrderSubmission,
   318  				row.Party(),
   319  			)
   320  		} else {
   321  			resp, err = exec.SubmitOrder(context.Background(), &orderSubmission, row.Party())
   322  		}
   323  		if ceerr := checkExpectedError(row, err, nil); ceerr != nil {
   324  			return ceerr
   325  		}
   326  
   327  		if !row.ExpectResultingTrades() || err != nil {
   328  			continue
   329  		}
   330  
   331  		if resp == nil {
   332  			continue
   333  		}
   334  
   335  		// If we have a reference, add a reference -> orderID lookup
   336  		if len(resp.Order.Reference) > 0 {
   337  			refToOrderId[resp.Order.Reference] = resp.Order.ID
   338  		}
   339  
   340  		actualTradeCount := int64(len(resp.Trades))
   341  		if actualTradeCount != row.ResultingTrades() {
   342  			return formatDiff(fmt.Sprintf("the resulting trades didn't match the expectation for order \"%v\"", row.Reference()),
   343  				map[string]string{
   344  					"total": i64ToS(row.ResultingTrades()),
   345  				},
   346  				map[string]string{
   347  					"total": i64ToS(actualTradeCount),
   348  				},
   349  			)
   350  		}
   351  	}
   352  	return nil
   353  }
   354  
   355  func PartyAddsTheFollowingOrdersToABatch(party string, exec Execution, time *stubs.TimeStub, table *godog.Table) error {
   356  	// ensure time is set + idgen is not nil
   357  	now := time.GetTimeNow()
   358  	time.SetTime(now)
   359  	for _, r := range parseAddOrderToBatchTable(table) {
   360  		row := newSubmitOrderRow(r)
   361  
   362  		orderSubmission := types.OrderSubmission{
   363  			MarketID:    row.MarketID(),
   364  			Side:        row.Side(),
   365  			Price:       row.Price(),
   366  			Size:        row.Volume(),
   367  			ExpiresAt:   row.ExpirationDate(now),
   368  			Type:        row.OrderType(),
   369  			TimeInForce: row.TimeInForce(),
   370  			Reference:   row.Reference(),
   371  		}
   372  		only := row.Only()
   373  		switch only {
   374  		case Post:
   375  			orderSubmission.PostOnly = true
   376  		case Reduce:
   377  			orderSubmission.ReduceOnly = true
   378  		}
   379  		if err := exec.AddSubmitOrderToBatch(&orderSubmission, party); err != nil {
   380  			return err
   381  		}
   382  	}
   383  	return nil
   384  }
   385  
   386  func PartyAddsTheFollowingCancelsToABatch(party string, exec Execution, time *stubs.TimeStub, table *godog.Table) error {
   387  	// ensure time is set + idgen is not nil
   388  	now := time.GetTimeNow()
   389  	time.SetTime(now)
   390  	for _, r := range parseAddCancelToBatchTable(table) {
   391  		row := newCancelOrderInBatchRow(r)
   392  
   393  		// Convert the reference into a orderID
   394  		orderID := refToOrderId[row.Reference()]
   395  		orderCancel := types.OrderCancellation{
   396  			MarketID: row.MarketID(),
   397  			OrderID:  orderID,
   398  		}
   399  		if err := exec.AddCancelOrderToBatch(&orderCancel, party); err != nil {
   400  			return err
   401  		}
   402  	}
   403  	return nil
   404  }
   405  
   406  func PartyAddsTheFollowingAmendsToABatch(party string, exec Execution, time *stubs.TimeStub, table *godog.Table) error {
   407  	// ensure time is set + idgen is not nil
   408  	now := time.GetTimeNow()
   409  	time.SetTime(now)
   410  	for _, r := range parseAddAmendToBatchTable(table) {
   411  		row := newAmendOrderInBatchRow(r)
   412  
   413  		// Convert the reference into a orderID
   414  		orderID := refToOrderId[row.Reference()]
   415  		orderAmend := types.OrderAmendment{
   416  			OrderID:         orderID,
   417  			Price:           row.Price(),
   418  			SizeDelta:       row.SizeDelta(),
   419  			MarketID:        row.MarketID(),
   420  			Size:            row.Size(),
   421  			ExpiresAt:       row.ExpiresAt(),
   422  			TimeInForce:     row.TimeInForce(),
   423  			PeggedOffset:    row.PeggedOffset(),
   424  			PeggedReference: row.PeggedReference(),
   425  		}
   426  		if err := exec.AddAmendOrderToBatch(&orderAmend, party); err != nil {
   427  			return err
   428  		}
   429  	}
   430  	return nil
   431  }
   432  
   433  func PartySubmitsTheirBatchInstruction(party string, exec Execution) error {
   434  	return exec.ProcessBatch(context.Background(), party)
   435  }
   436  
   437  func PartySubmitsTheirBatchInstructionWithError(party, err string, exec Execution) error {
   438  	retErr := exec.ProcessBatch(context.Background(), party)
   439  
   440  	err = fmt.Sprintf("1 (%s)", err)
   441  	re := retErr.Error()
   442  	if re != err {
   443  		return retErr
   444  	}
   445  	return nil
   446  }
   447  
   448  func PartyStartsABatchInstruction(party string, exec Execution) error {
   449  	return exec.StartBatch(party)
   450  }
   451  
   452  func buildStopOrder(
   453  	submission *types.OrderSubmission,
   454  	row submitOrderRow,
   455  	now time.Time,
   456  ) (*types.StopOrdersSubmission, error) {
   457  	var (
   458  		fbPriced, raPriced     = row.FallsBelowPriceTrigger(), row.RisesAbovePriceTrigger()
   459  		fbTrailing, raTrailing = row.FallsBelowTrailing(), row.RisesAboveTrailing()
   460  	)
   461  
   462  	if fbPriced == nil && fbTrailing.IsZero() && raPriced == nil && raTrailing.IsZero() {
   463  		return nil, nil
   464  	}
   465  
   466  	if fbPriced != nil && !fbTrailing.IsZero() {
   467  		return nil, errors.New("cannot use bot trailing and priced trigger for falls below")
   468  	}
   469  
   470  	if raPriced != nil && !raTrailing.IsZero() {
   471  		return nil, errors.New("cannot use bot trailing and priced trigger for rises above")
   472  	}
   473  
   474  	sub := &types.StopOrdersSubmission{}
   475  
   476  	var (
   477  		fbStrategy        *types.StopOrderExpiryStrategy
   478  		fbStopOrderExpiry *time.Time
   479  		raStrategy        *types.StopOrderExpiryStrategy
   480  		raStopOrderExpiry *time.Time
   481  	)
   482  	if stopOrderExp := row.StopOrderFBExpirationDate(now); stopOrderExp != 0 {
   483  		fbStrategy = ptr.From(row.ExpiryStrategyFB())
   484  		fbStopOrderExpiry = ptr.From(time.Unix(0, stopOrderExp))
   485  	}
   486  	if stopOrderExp := row.StopOrderRAExpirationDate(now); stopOrderExp != 0 {
   487  		raStrategy = ptr.From(row.ExpiryStrategyRA())
   488  		raStopOrderExpiry = ptr.From(time.Unix(0, stopOrderExp))
   489  	}
   490  
   491  	switch {
   492  	case fbPriced != nil:
   493  		sub.FallsBelow = &types.StopOrderSetup{
   494  			OrderSubmission: submission,
   495  			Expiry: &types.StopOrderExpiry{
   496  				ExpiresAt:      fbStopOrderExpiry,
   497  				ExpiryStrategy: fbStrategy,
   498  			},
   499  			Trigger: types.NewPriceStopOrderTrigger(
   500  				types.StopOrderTriggerDirectionFallsBelow,
   501  				fbPriced.Clone(),
   502  			),
   503  		}
   504  	case !fbTrailing.IsZero():
   505  		sub.FallsBelow = &types.StopOrderSetup{
   506  			OrderSubmission: submission,
   507  			Expiry: &types.StopOrderExpiry{
   508  				ExpiresAt:      fbStopOrderExpiry,
   509  				ExpiryStrategy: fbStrategy,
   510  			},
   511  			Trigger: types.NewTrailingStopOrderTrigger(
   512  				types.StopOrderTriggerDirectionFallsBelow,
   513  				fbTrailing,
   514  			),
   515  		}
   516  	}
   517  
   518  	switch {
   519  	case raPriced != nil:
   520  		sub.RisesAbove = &types.StopOrderSetup{
   521  			OrderSubmission: ptr.From(*submission),
   522  			Expiry: &types.StopOrderExpiry{
   523  				ExpiryStrategy: raStrategy,
   524  				ExpiresAt:      raStopOrderExpiry,
   525  			},
   526  			Trigger: types.NewPriceStopOrderTrigger(
   527  				types.StopOrderTriggerDirectionRisesAbove,
   528  				raPriced.Clone(),
   529  			),
   530  		}
   531  	case !raTrailing.IsZero():
   532  		sub.RisesAbove = &types.StopOrderSetup{
   533  			OrderSubmission: ptr.From(*submission),
   534  			Expiry: &types.StopOrderExpiry{
   535  				ExpiryStrategy: raStrategy,
   536  				ExpiresAt:      raStopOrderExpiry,
   537  			},
   538  			Trigger: types.NewTrailingStopOrderTrigger(
   539  				types.StopOrderTriggerDirectionRisesAbove,
   540  				raTrailing,
   541  			),
   542  		}
   543  	}
   544  
   545  	// Handle OCO references
   546  	if sub.RisesAbove != nil && sub.FallsBelow != nil {
   547  		sub.FallsBelow.OrderSubmission.Reference += "-1"
   548  		sub.RisesAbove.OrderSubmission.Reference += "-2"
   549  	}
   550  
   551  	if row.row.HasColumn("ra size override setting") {
   552  		value := row.RisesAboveSizeOverrideSetting()
   553  		sub.RisesAbove.SizeOverrideSetting = value
   554  
   555  		if row.row.HasColumn("ra size override percentage") {
   556  			percentage := row.RisesAboveSizeOverridePercentage()
   557  			percentageValue := num.MustDecimalFromString(percentage)
   558  			sub.RisesAbove.SizeOverrideValue = &types.StopOrderSizeOverrideValue{PercentageSize: percentageValue}
   559  		}
   560  	}
   561  
   562  	if row.row.HasColumn("fb size override setting") {
   563  		value := row.FallsBelowSizeOverrideSetting()
   564  		sub.FallsBelow.SizeOverrideSetting = value
   565  
   566  		if row.row.HasColumn("fb size override percentage") {
   567  			percentage := row.FallsBelowSizeOverridePercentage()
   568  			percentageValue := num.MustDecimalFromString(percentage)
   569  			sub.FallsBelow.SizeOverrideValue = &types.StopOrderSizeOverrideValue{PercentageSize: percentageValue}
   570  		}
   571  	}
   572  
   573  	return sub, nil
   574  }
   575  
   576  func parseSubmitHackedOrderTable(table *godog.Table) []RowWrapper {
   577  	return StrictParseTable(table, []string{
   578  		"party",
   579  		"market id",
   580  		"side",
   581  		"volume",
   582  		"price",
   583  		"type",
   584  		"tif",
   585  	}, []string{
   586  		"reference",
   587  		"error",
   588  		"resulting trades",
   589  		"expires in",
   590  		"only",
   591  		"fb price trigger",
   592  		"fb trailing",
   593  		"ra price trigger",
   594  		"ra trailing",
   595  		"ra expires in",
   596  		"ra expiry strategy",
   597  		"fb expires in",
   598  		"fb expiry strategy",
   599  		"pegged reference",
   600  		"pegged offset",
   601  		"ra size override setting",
   602  		"ra size override percentage",
   603  		"fb size override setting",
   604  		"fb size override percentage",
   605  		"is amm",
   606  	})
   607  }
   608  
   609  func parseSubmitOrderTable(table *godog.Table) []RowWrapper {
   610  	return StrictParseTable(table, []string{
   611  		"party",
   612  		"market id",
   613  		"side",
   614  		"volume",
   615  		"price",
   616  		"type",
   617  		"tif",
   618  	}, []string{
   619  		"reference",
   620  		"error",
   621  		"resulting trades",
   622  		"expires in",
   623  		"only",
   624  		"fb price trigger",
   625  		"fb trailing",
   626  		"ra price trigger",
   627  		"ra trailing",
   628  		"ra expires in",
   629  		"ra expiry strategy",
   630  		"fb expires in",
   631  		"fb expiry strategy",
   632  		"pegged reference",
   633  		"pegged offset",
   634  		"ra size override setting",
   635  		"ra size override percentage",
   636  		"fb size override setting",
   637  		"fb size override percentage",
   638  	})
   639  }
   640  
   641  func parseAddOrderToBatchTable(table *godog.Table) []RowWrapper {
   642  	return StrictParseTable(table, []string{
   643  		"market id",
   644  		"side",
   645  		"volume",
   646  		"price",
   647  		"type",
   648  		"tif",
   649  	}, []string{
   650  		"reference",
   651  		"error",
   652  		"expires in",
   653  		"only",
   654  	})
   655  }
   656  
   657  func parseAddCancelToBatchTable(table *godog.Table) []RowWrapper {
   658  	return StrictParseTable(table, []string{
   659  		"market id",
   660  		"reference",
   661  	}, []string{})
   662  }
   663  
   664  func parseAddAmendToBatchTable(table *godog.Table) []RowWrapper {
   665  	return StrictParseTable(table, []string{
   666  		"market id",
   667  		"reference",
   668  	}, []string{
   669  		"price",
   670  		"size",
   671  		"size delta",
   672  		"expires at",
   673  		"tif",
   674  		"pegged offset",
   675  		"pegged reference",
   676  	})
   677  }
   678  
   679  type cancelOrderInBatchRow struct {
   680  	row RowWrapper
   681  }
   682  
   683  func newCancelOrderInBatchRow(r RowWrapper) cancelOrderInBatchRow {
   684  	row := cancelOrderInBatchRow{
   685  		row: r,
   686  	}
   687  	return row
   688  }
   689  
   690  func (r cancelOrderInBatchRow) MarketID() string {
   691  	return r.row.MustStr("market id")
   692  }
   693  
   694  func (r cancelOrderInBatchRow) Reference() string {
   695  	return r.row.MustStr("reference")
   696  }
   697  
   698  type amendOrderInBatchRow struct {
   699  	row RowWrapper
   700  }
   701  
   702  func newAmendOrderInBatchRow(r RowWrapper) amendOrderInBatchRow {
   703  	row := amendOrderInBatchRow{
   704  		row: r,
   705  	}
   706  	return row
   707  }
   708  
   709  func (r amendOrderInBatchRow) MarketID() string {
   710  	return r.row.MustStr("market id")
   711  }
   712  
   713  func (r amendOrderInBatchRow) Price() *num.Uint {
   714  	return r.row.MustUint("price")
   715  }
   716  
   717  func (r amendOrderInBatchRow) PeggedOffset() *num.Uint {
   718  	if !r.row.HasColumn("pegged offset") {
   719  		return nil
   720  	}
   721  	return r.row.MustUint("pegged offset")
   722  }
   723  
   724  func (r amendOrderInBatchRow) PeggedReference() types.PeggedReference {
   725  	if !r.row.HasColumn("pegged reference") {
   726  		return types.PeggedReferenceUnspecified
   727  	}
   728  	return r.row.MustPeggedReference("pegged reference")
   729  }
   730  
   731  func (r amendOrderInBatchRow) SizeDelta() int64 {
   732  	if !r.row.HasColumn("size delta") {
   733  		return 0
   734  	}
   735  	return r.row.MustI64("size delta")
   736  }
   737  
   738  func (r amendOrderInBatchRow) Size() *uint64 {
   739  	if !r.row.HasColumn("size") {
   740  		return nil
   741  	}
   742  	size := r.row.MustU64("size")
   743  	return &size
   744  }
   745  
   746  func (r amendOrderInBatchRow) ExpiresAt() *int64 {
   747  	if !r.row.HasColumn("expires at") {
   748  		return nil
   749  	}
   750  	expires := r.row.MustI64("expires at")
   751  	return &expires
   752  }
   753  
   754  func (r amendOrderInBatchRow) TimeInForce() types.OrderTimeInForce {
   755  	return r.row.MustTIF("tif")
   756  }
   757  
   758  func (r amendOrderInBatchRow) Reference() string {
   759  	return r.row.MustStr("reference")
   760  }
   761  
   762  type submitOrderRow struct {
   763  	row RowWrapper
   764  }
   765  
   766  type submitHackedRow struct {
   767  	submitOrderRow
   768  	row RowWrapper
   769  }
   770  
   771  func newHackedOrderRow(r RowWrapper) submitHackedRow {
   772  	return submitHackedRow{
   773  		submitOrderRow: newSubmitOrderRow(r),
   774  		row:            r,
   775  	}
   776  }
   777  
   778  func (h submitHackedRow) IsAMM() bool {
   779  	if !h.row.HasColumn("is amm") {
   780  		return false
   781  	}
   782  	return h.row.MustBool("is amm")
   783  }
   784  
   785  func newSubmitOrderRow(r RowWrapper) submitOrderRow {
   786  	row := submitOrderRow{
   787  		row: r,
   788  	}
   789  
   790  	if row.ExpectError() && row.ExpectResultingTrades() && row.ResultingTrades() > 0 {
   791  		panic("you can't expect trades and an error at the same time")
   792  	}
   793  
   794  	return row
   795  }
   796  
   797  func (r submitOrderRow) Party() string {
   798  	return r.row.MustStr("party")
   799  }
   800  
   801  func (r submitOrderRow) MarketID() string {
   802  	return r.row.MustStr("market id")
   803  }
   804  
   805  func (r submitOrderRow) Side() types.Side {
   806  	return r.row.MustSide("side")
   807  }
   808  
   809  func (r submitOrderRow) Volume() uint64 {
   810  	return r.row.MustU64("volume")
   811  }
   812  
   813  func (r submitOrderRow) Price() *num.Uint {
   814  	return r.row.MustUint("price")
   815  }
   816  
   817  func (r submitOrderRow) OrderType() types.OrderType {
   818  	return r.row.MustOrderType("type")
   819  }
   820  
   821  func (r submitOrderRow) TimeInForce() types.OrderTimeInForce {
   822  	return r.row.MustTIF("tif")
   823  }
   824  
   825  func (r submitOrderRow) ExpirationDate(now time.Time) int64 {
   826  	if r.OrderType() == types.OrderTypeMarket {
   827  		return 0
   828  	}
   829  
   830  	if r.TimeInForce() == types.OrderTimeInForceGTT {
   831  		// Allow negative expires in seconds for testing purposes
   832  		return now.Add(r.row.MustDurationSec2("expires in")).Local().UnixNano()
   833  	}
   834  	// non GTT orders don't need an expiry time
   835  	return 0
   836  }
   837  
   838  func (r submitOrderRow) ExpectResultingTrades() bool {
   839  	return r.row.HasColumn("resulting trades")
   840  }
   841  
   842  func (r submitOrderRow) ResultingTrades() int64 {
   843  	return r.row.I64("resulting trades")
   844  }
   845  
   846  func (r submitOrderRow) Reference() string {
   847  	return r.row.Str("reference")
   848  }
   849  
   850  func (r submitOrderRow) Error() string {
   851  	return r.row.Str("error")
   852  }
   853  
   854  func (r submitOrderRow) ExpectError() bool {
   855  	return r.row.HasColumn("error")
   856  }
   857  
   858  func (r submitOrderRow) Only() Only {
   859  	if !r.row.HasColumn("only") {
   860  		return None
   861  	}
   862  	v := r.row.MustStr("only")
   863  	t, ok := onlyTypes[v]
   864  	if !ok {
   865  		panic(fmt.Errorf("unsupported type %v", v))
   866  	}
   867  	return t
   868  }
   869  
   870  func (r submitOrderRow) FallsBelowPriceTrigger() *num.Uint {
   871  	if !r.row.HasColumn("fb price trigger") {
   872  		return nil
   873  	}
   874  	return r.row.MustUint("fb price trigger")
   875  }
   876  
   877  func (r submitOrderRow) RisesAbovePriceTrigger() *num.Uint {
   878  	if !r.row.HasColumn("ra price trigger") {
   879  		return nil
   880  	}
   881  	return r.row.MustUint("ra price trigger")
   882  }
   883  
   884  func (r submitOrderRow) FallsBelowTrailing() num.Decimal {
   885  	if !r.row.HasColumn("fb trailing") {
   886  		return num.DecimalZero()
   887  	}
   888  	return r.row.MustDecimal("fb trailing")
   889  }
   890  
   891  func (r submitOrderRow) RisesAboveTrailing() num.Decimal {
   892  	if !r.row.HasColumn("ra trailing") {
   893  		return num.DecimalZero()
   894  	}
   895  	return r.row.MustDecimal("ra trailing")
   896  }
   897  
   898  func (r submitOrderRow) StopOrderRAExpirationDate(now time.Time) int64 {
   899  	if !r.row.HasColumn("ra expires in") {
   900  		return 0
   901  	}
   902  	return now.Add(r.row.MustDurationSec2("ra expires in")).Local().UnixNano()
   903  }
   904  
   905  func (r submitOrderRow) StopOrderFBExpirationDate(now time.Time) int64 {
   906  	if !r.row.HasColumn("fb expires in") {
   907  		return 0
   908  	}
   909  	return now.Add(r.row.MustDurationSec2("fb expires in")).Local().UnixNano()
   910  }
   911  
   912  func (r submitOrderRow) ExpiryStrategyRA() types.StopOrderExpiryStrategy {
   913  	if !r.row.HasColumn("ra expiry strategy") {
   914  		return types.StopOrderExpiryStrategyCancels
   915  	}
   916  	return r.row.MustExpiryStrategy("ra expiry strategy")
   917  }
   918  
   919  func (r submitOrderRow) ExpiryStrategyFB() types.StopOrderExpiryStrategy {
   920  	if !r.row.HasColumn("fb expiry strategy") {
   921  		return types.StopOrderExpiryStrategyCancels
   922  	}
   923  	return r.row.MustExpiryStrategy("fb expiry strategy")
   924  }
   925  
   926  func (r submitOrderRow) PeggedReference() types.PeggedReference {
   927  	if !r.row.HasColumn("pegged reference") {
   928  		return types.PeggedReferenceUnspecified
   929  	}
   930  	return r.row.MustPeggedReference("pegged reference")
   931  }
   932  
   933  func (r submitOrderRow) PeggedOffset() *num.Uint {
   934  	if !r.row.HasColumn("pegged offset") {
   935  		return nil
   936  	}
   937  	return r.row.MustUint("pegged offset")
   938  }
   939  
   940  func (r submitOrderRow) RisesAboveSizeOverrideSetting() types.StopOrderSizeOverrideSetting {
   941  	if !r.row.HasColumn("ra size override setting") {
   942  		return types.StopOrderSizeOverrideSettingUnspecified
   943  	}
   944  	return r.row.MustSizeOverrideSetting("ra size override setting")
   945  }
   946  
   947  func (r submitOrderRow) RisesAboveSizeOverridePercentage() string {
   948  	return r.row.MustStr("ra size override percentage")
   949  }
   950  
   951  func (r submitOrderRow) FallsBelowSizeOverrideSetting() types.StopOrderSizeOverrideSetting {
   952  	if !r.row.HasColumn("fb size override setting") {
   953  		return types.StopOrderSizeOverrideSettingUnspecified
   954  	}
   955  	return r.row.MustSizeOverrideSetting("fb size override setting")
   956  }
   957  
   958  func (r submitOrderRow) FallsBelowSizeOverridePercentage() string {
   959  	return r.row.MustStr("fb size override percentage")
   960  }