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 }