orders: Add AdjustBaseAmount (#1181)

* orders: Add AdjustBaseAmount method that alerts if non-fatal change occurs exchange side, adjust BTCM

* glorious: nits

* glorious: nits

* thrasher: nits

* Update exchanges/btcmarkets/btcmarkets_wrapper.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/btcmarkets/btcmarkets_wrapper.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* thrasher: nits and whoopsie

* orders_test: fix

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2023-05-12 14:06:04 +10:00
committed by GitHub
parent db8735ec99
commit 8309ddf80c
3 changed files with 158 additions and 5 deletions

View File

@@ -592,7 +592,29 @@ func (b *BTCMarkets) SubmitOrder(ctx context.Context, s *order.Submit) (*order.S
if err != nil {
return nil, err
}
return s.DeriveSubmitResponse(tempResp.OrderID)
submitResp, err := s.DeriveSubmitResponse(tempResp.OrderID)
if err != nil {
return nil, err
}
if tempResp.Amount != 0 {
err = submitResp.AdjustBaseAmount(tempResp.Amount)
if err != nil {
log.Errorf(log.ExchangeSys, "Exchange %s: OrderID: %s base amount conversion error: %s\n", b.Name, submitResp.OrderID, err)
}
}
if tempResp.TargetAmount != 0 {
err = submitResp.AdjustQuoteAmount(tempResp.TargetAmount)
if err != nil {
log.Errorf(log.ExchangeSys, "Exchange %s: OrderID: %s quote amount conversion error: %s\n", b.Name, submitResp.OrderID, err)
}
}
// With market orders the price is optional, so we can set it to the
// actual price that was filled.
submitResp.Price = tempResp.Price
return submitResp, nil
}
// ModifyOrder will allow of changing orderbook placement and limit to

View File

@@ -1260,11 +1260,11 @@ func TestUpdateOrderFromDetail(t *testing.T) {
func TestClassificationError_Error(t *testing.T) {
class := ClassificationError{OrderID: "1337", Exchange: "test", Err: errors.New("test error")}
if class.Error() != "test - OrderID: 1337 classification error: test error" {
if class.Error() != "Exchange test: OrderID: 1337 classification error: test error" {
t.Fatal("unexpected output")
}
class.OrderID = ""
if class.Error() != "test - classification error: test error" {
if class.Error() != "Exchange test: classification error: test error" {
t.Fatal("unexpected output")
}
}
@@ -1959,3 +1959,75 @@ func TestIsValidOrderSubmissionSide(t *testing.T) {
t.Error("expected false")
}
}
func TestAdjustBaseAmount(t *testing.T) {
t.Parallel()
var s *SubmitResponse
err := s.AdjustBaseAmount(0)
if !errors.Is(err, errOrderSubmitResponseIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitResponseIsNil)
}
s = &SubmitResponse{}
err = s.AdjustBaseAmount(0)
if !errors.Is(err, errAmountIsZero) {
t.Fatalf("received: '%v' but expected: '%v'", err, errAmountIsZero)
}
s.Amount = 1.7777777777
err = s.AdjustBaseAmount(1.7777777777)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if s.Amount != 1.7777777777 {
t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 1.7777777777)
}
s.Amount = 1.7777777777
err = s.AdjustBaseAmount(1.777)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if s.Amount != 1.777 {
t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 1.777)
}
}
func TestAdjustQuoteAmount(t *testing.T) {
t.Parallel()
var s *SubmitResponse
err := s.AdjustQuoteAmount(0)
if !errors.Is(err, errOrderSubmitResponseIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, errOrderSubmitResponseIsNil)
}
s = &SubmitResponse{}
err = s.AdjustQuoteAmount(0)
if !errors.Is(err, errAmountIsZero) {
t.Fatalf("received: '%v' but expected: '%v'", err, errAmountIsZero)
}
s.QuoteAmount = 5.222222222222
err = s.AdjustQuoteAmount(5.222222222222)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if s.QuoteAmount != 5.222222222222 {
t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 5.222222222222)
}
s.QuoteAmount = 5.222222222222
err = s.AdjustQuoteAmount(5.22222222)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if s.QuoteAmount != 5.22222222 {
t.Fatalf("received: '%v' but expected: '%v'", s.Amount, 5.22222222)
}
}

View File

@@ -38,6 +38,7 @@ var (
errOrderSubmitIsNil = errors.New("order submit is nil")
errOrderSubmitResponseIsNil = errors.New("order submit response is nil")
errOrderDetailIsNil = errors.New("order detail is nil")
errAmountIsZero = errors.New("amount is zero")
)
// IsValidOrderSubmissionSide validates that the order side is a valid submission direction
@@ -475,6 +476,64 @@ func (s *Submit) DeriveSubmitResponse(orderID string) (*SubmitResponse, error) {
}, nil
}
// AdjustBaseAmount will adjust the base amount of a submit response if the
// exchange has modified the amount. This is usually due to decimal place
// restrictions or rounding. This will return an error if the amount is zero
// or the submit response is nil.
func (s *SubmitResponse) AdjustBaseAmount(a float64) error {
if s == nil {
return errOrderSubmitResponseIsNil
}
if a <= 0 {
return errAmountIsZero
}
if s.Amount == a {
return nil
}
// Warning because amounts should conform to exchange requirements prior to
// call but this is not fatal.
log.Warnf(log.ExchangeSys, "Exchange %s: has adjusted OrderID: %s requested base amount from %v to %v",
s.Exchange,
s.OrderID,
s.Amount,
a)
s.Amount = a
return nil
}
// AdjustQuoteAmount will adjust the quote amount of a submit response if the
// exchange has modified the amount. This is usually due to decimal place
// restrictions or rounding. This will return an error if the amount is zero
// or the submit response is nil.
func (s *SubmitResponse) AdjustQuoteAmount(a float64) error {
if s == nil {
return errOrderSubmitResponseIsNil
}
if a <= 0 {
return errAmountIsZero
}
if s.QuoteAmount == a {
return nil
}
// Warning because amounts should conform to exchange requirements prior to
// call but this is not fatal.
log.Warnf(log.ExchangeSys, "Exchange %s: has adjusted OrderID: %s requested quote amount from %v to %v",
s.Exchange,
s.OrderID,
s.Amount,
a)
s.QuoteAmount = a
return nil
}
// DeriveDetail will construct an order detail when a successful submission
// has occurred. Has an optional parameter field internal uuid for internal
// management.
@@ -1069,12 +1128,12 @@ func StringToOrderStatus(status string) (Status, error) {
func (o *ClassificationError) Error() string {
if o.OrderID != "" {
return fmt.Sprintf("%s - OrderID: %s classification error: %v",
return fmt.Sprintf("Exchange %s: OrderID: %s classification error: %v",
o.Exchange,
o.OrderID,
o.Err)
}
return fmt.Sprintf("%s - classification error: %v",
return fmt.Sprintf("Exchange %s: classification error: %v",
o.Exchange,
o.Err)
}