orders: Add derive modify struct method from order.Detail (#948)

* orders: Add derive modify struct method to order.Detail and then subsequent method to derive and standardize response details

* exchanges: call modify method in wrappers

* linter: fixes

* engine/wsroutineman: remove print summary

* glorious: nits, removed modifyOrder functionality for Bithumb. There are not docs to support this.

* Update exchanges/order/orders.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
Ryan O'Hara-Reid
2022-05-30 14:38:21 +10:00
committed by GitHub
parent 293d6104ed
commit a63aa6b616
42 changed files with 298 additions and 427 deletions

View File

@@ -843,7 +843,6 @@ func BenchmarkStringToOrderStatus(b *testing.B) {
}
func TestUpdateOrderFromModify(t *testing.T) {
var leet = "1337"
od := Detail{ID: "1"}
updated := time.Now()
@@ -852,94 +851,50 @@ func TestUpdateOrderFromModify(t *testing.T) {
t.Fatal(err)
}
om := Modify{
om := ModifyResponse{
ImmediateOrCancel: true,
HiddenOrder: true,
FillOrKill: true,
PostOnly: true,
Leverage: 1.0,
Price: 1,
Amount: 1,
LimitPriceUpper: 1,
LimitPriceLower: 1,
TriggerPrice: 1,
QuoteAmount: 1,
ExecutedAmount: 1,
RemainingAmount: 1,
Fee: 1,
Exchange: "1",
InternalOrderID: "1",
ID: "1",
AccountID: "1",
ClientID: "1",
WalletAddress: "1",
Type: 1,
Side: 1,
Status: 1,
AssetType: 1,
LastUpdated: updated,
Pair: pair,
Trades: []TradeHistory{},
}
od.UpdateOrderFromModify(&om)
od.UpdateOrderFromModifyResponse(&om)
if od.InternalOrderID == "1" {
t.Error("Should not be able to update the internal order ID")
}
if !od.ImmediateOrCancel {
t.Error("Failed to update")
}
if !od.HiddenOrder {
t.Error("Failed to update")
}
if !od.FillOrKill {
t.Error("Failed to update")
}
if !od.PostOnly {
t.Error("Failed to update")
}
if od.Leverage != 1 {
t.Error("Failed to update")
}
if od.Price != 1 {
t.Error("Failed to update")
}
if od.Amount != 1 {
t.Error("Failed to update")
}
if od.LimitPriceLower != 1 {
t.Error("Failed to update")
}
if od.LimitPriceUpper != 1 {
t.Error("Failed to update")
}
if od.TriggerPrice != 1 {
t.Error("Failed to update")
}
if od.QuoteAmount != 1 {
t.Error("Failed to update")
}
if od.ExecutedAmount != 1 {
t.Error("Failed to update")
}
if od.RemainingAmount != 1 {
t.Error("Failed to update")
}
if od.Fee != 1 {
t.Error("Failed to update")
}
if od.Exchange != "" {
t.Error("Should not be able to update exchange via modify")
}
if od.ID != "1" {
t.Error("Failed to update")
}
if od.ClientID != "1" {
t.Error("Failed to update")
}
if od.WalletAddress != "1" {
t.Error("Failed to update")
}
if od.Type != 1 {
t.Error("Failed to update")
}
@@ -961,49 +916,6 @@ func TestUpdateOrderFromModify(t *testing.T) {
if od.Trades != nil {
t.Error("Failed to update")
}
om.Trades = append(om.Trades, TradeHistory{TID: "1"}, TradeHistory{TID: "2"})
od.UpdateOrderFromModify(&om)
if len(od.Trades) != 2 {
t.Error("Failed to add trades")
}
om.Trades[0].Exchange = leet
om.Trades[0].Price = 1337
om.Trades[0].Fee = 1337
om.Trades[0].IsMaker = true
om.Trades[0].Timestamp = updated
om.Trades[0].Description = leet
om.Trades[0].Side = UnknownSide
om.Trades[0].Type = UnknownType
om.Trades[0].Amount = 1337
od.UpdateOrderFromModify(&om)
if od.Trades[0].Exchange == leet {
t.Error("Should not be able to update exchange from update")
}
if od.Trades[0].Price != 1337 {
t.Error("Failed to update trades")
}
if od.Trades[0].Fee != 1337 {
t.Error("Failed to update trades")
}
if !od.Trades[0].IsMaker {
t.Error("Failed to update trades")
}
if od.Trades[0].Timestamp != updated {
t.Error("Failed to update trades")
}
if od.Trades[0].Description != leet {
t.Error("Failed to update trades")
}
if od.Trades[0].Side != UnknownSide {
t.Error("Failed to update trades")
}
if od.Trades[0].Type != UnknownType {
t.Error("Failed to update trades")
}
if od.Trades[0].Amount != 1337 {
t.Error("Failed to update trades")
}
}
func TestUpdateOrderFromDetail(t *testing.T) {
@@ -1635,6 +1547,84 @@ func TestDetail_CopyPointerOrderSlice(t *testing.T) {
}
}
func TestDeriveModify(t *testing.T) {
t.Parallel()
var o *Detail
if _, err := o.DeriveModify(); !errors.Is(err, errOrderDetailIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, errOrderDetailIsNil)
}
pair := currency.NewPair(currency.BTC, currency.AUD)
o = &Detail{
Exchange: "wow",
ID: "wow2",
ClientOrderID: "wow3",
Type: Market,
Side: Long,
AssetType: asset.Futures,
Pair: pair,
}
mod, err := o.DeriveModify()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if mod == nil {
t.Fatal("should not be nil")
}
if mod.Exchange != "wow" ||
mod.ID != "wow2" ||
mod.ClientOrderID != "wow3" ||
mod.Type != Market ||
mod.Side != Long ||
mod.AssetType != asset.Futures ||
!mod.Pair.Equal(pair) {
t.Fatal("unexpected values")
}
}
func TestDeriveModifyResponse(t *testing.T) {
t.Parallel()
var mod *Modify
if _, err := mod.DeriveModifyResponse(); !errors.Is(err, errOrderDetailIsNil) {
t.Fatalf("received: '%v' but expected: '%v'", err, errOrderDetailIsNil)
}
pair := currency.NewPair(currency.BTC, currency.AUD)
mod = &Modify{
Exchange: "wow",
ID: "wow2",
ClientOrderID: "wow3",
Type: Market,
Side: Long,
AssetType: asset.Futures,
Pair: pair,
}
modresp, err := mod.DeriveModifyResponse()
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if modresp == nil {
t.Fatal("should not be nil")
}
if modresp.Exchange != "wow" ||
modresp.OrderID != "wow2" ||
modresp.ClientOrderID != "wow3" ||
modresp.Type != Market ||
modresp.Side != Long ||
modresp.AssetType != asset.Futures ||
!modresp.Pair.Equal(pair) {
t.Fatal("unexpected values")
}
}
func TestDeriveCancel(t *testing.T) {
t.Parallel()
var o *Detail
@@ -1660,7 +1650,6 @@ func TestDeriveCancel(t *testing.T) {
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if cancel.Exchange != "wow" ||
cancel.ID != "wow1" ||
cancel.AccountID != "wow2" ||
@@ -1671,6 +1660,6 @@ func TestDeriveCancel(t *testing.T) {
cancel.Side != Long ||
!cancel.Pair.Equal(pair) ||
cancel.AssetType != asset.Futures {
t.Fatal("unexpected values")
t.Fatalf("unexpected values %+v", cancel)
}
}

View File

@@ -83,40 +83,47 @@ type SubmitResponse struct {
// Each exchange has their own requirements, so not all fields
// are required to be populated
type Modify struct {
// Order Identifiers
Exchange string
ID string
ClientOrderID string
Type Type
Side Side
AssetType asset.Item
Pair currency.Pair
// Change fields
ImmediateOrCancel bool
HiddenOrder bool
FillOrKill bool
PostOnly bool
Leverage float64
Price float64
Amount float64
LimitPriceUpper float64
LimitPriceLower float64
TriggerPrice float64
QuoteAmount float64
ExecutedAmount float64
RemainingAmount float64
Fee float64
Exchange string
InternalOrderID string
ID string
ClientOrderID string
AccountID string
ClientID string
WalletAddress string
Type Type
Side Side
Status Status
AssetType asset.Item
Date time.Time
LastUpdated time.Time
Pair currency.Pair
Trades []TradeHistory
}
// ModifyResponse is an order modifying return type
type ModifyResponse struct {
OrderID string
// Order Identifiers
Exchange string
OrderID string
ClientOrderID string
Pair currency.Pair
Type Type
Side Side
Status Status
AssetType asset.Item
// Fields that will be copied over from Modify
ImmediateOrCancel bool
PostOnly bool
Price float64
Amount float64
TriggerPrice float64
// Fields that need to be handled in scope after DeriveModifyResponse()
// if applicable
RemainingAmount float64
Date time.Time
LastUpdated time.Time
}
// Detail contains all properties of an order

View File

@@ -242,26 +242,18 @@ func (d *Detail) UpdateOrderFromDetail(m *Detail) {
}
}
// UpdateOrderFromModify Will update an order detail (used in order management)
// UpdateOrderFromModifyResponse Will update an order detail (used in order management)
// by comparing passed in and existing values
func (d *Detail) UpdateOrderFromModify(m *Modify) {
func (d *Detail) UpdateOrderFromModifyResponse(m *ModifyResponse) {
var updated bool
if m.ID != "" && d.ID != m.ID {
d.ID = m.ID
if m.OrderID != "" && d.ID != m.OrderID {
d.ID = m.OrderID
updated = true
}
if d.ImmediateOrCancel != m.ImmediateOrCancel {
d.ImmediateOrCancel = m.ImmediateOrCancel
updated = true
}
if d.HiddenOrder != m.HiddenOrder {
d.HiddenOrder = m.HiddenOrder
updated = true
}
if d.FillOrKill != m.FillOrKill {
d.FillOrKill = m.FillOrKill
updated = true
}
if m.Price > 0 && m.Price != d.Price {
d.Price = m.Price
updated = true
@@ -270,34 +262,10 @@ func (d *Detail) UpdateOrderFromModify(m *Modify) {
d.Amount = m.Amount
updated = true
}
if m.LimitPriceUpper > 0 && m.LimitPriceUpper != d.LimitPriceUpper {
d.LimitPriceUpper = m.LimitPriceUpper
updated = true
}
if m.LimitPriceLower > 0 && m.LimitPriceLower != d.LimitPriceLower {
d.LimitPriceLower = m.LimitPriceLower
updated = true
}
if m.TriggerPrice > 0 && m.TriggerPrice != d.TriggerPrice {
d.TriggerPrice = m.TriggerPrice
updated = true
}
if m.QuoteAmount > 0 && m.QuoteAmount != d.QuoteAmount {
d.QuoteAmount = m.QuoteAmount
updated = true
}
if m.ExecutedAmount > 0 && m.ExecutedAmount != d.ExecutedAmount {
d.ExecutedAmount = m.ExecutedAmount
updated = true
}
if m.Fee > 0 && m.Fee != d.Fee {
d.Fee = m.Fee
updated = true
}
if m.AccountID != "" && m.AccountID != d.AccountID {
d.AccountID = m.AccountID
updated = true
}
if m.PostOnly != d.PostOnly {
d.PostOnly = m.PostOnly
updated = true
@@ -308,18 +276,6 @@ func (d *Detail) UpdateOrderFromModify(m *Modify) {
d.Pair = m.Pair
updated = true
}
if m.Leverage != 0 && m.Leverage != d.Leverage {
d.Leverage = m.Leverage
updated = true
}
if m.ClientID != "" && m.ClientID != d.ClientID {
d.ClientID = m.ClientID
updated = true
}
if m.WalletAddress != "" && m.WalletAddress != d.WalletAddress {
d.WalletAddress = m.WalletAddress
updated = true
}
if m.Type != UnknownType && m.Type != d.Type {
d.Type = m.Type
updated = true
@@ -336,52 +292,6 @@ func (d *Detail) UpdateOrderFromModify(m *Modify) {
d.AssetType = m.AssetType
updated = true
}
for x := range m.Trades {
var found bool
for y := range d.Trades {
if d.Trades[y].TID != m.Trades[x].TID {
continue
}
found = true
if d.Trades[y].Fee != m.Trades[x].Fee {
d.Trades[y].Fee = m.Trades[x].Fee
updated = true
}
if m.Trades[x].Price != 0 && d.Trades[y].Price != m.Trades[x].Price {
d.Trades[y].Price = m.Trades[x].Price
updated = true
}
if d.Trades[y].Side != m.Trades[x].Side {
d.Trades[y].Side = m.Trades[x].Side
updated = true
}
if d.Trades[y].Type != m.Trades[x].Type {
d.Trades[y].Type = m.Trades[x].Type
updated = true
}
if d.Trades[y].Description != m.Trades[x].Description {
d.Trades[y].Description = m.Trades[x].Description
updated = true
}
if m.Trades[x].Amount != 0 && d.Trades[y].Amount != m.Trades[x].Amount {
d.Trades[y].Amount = m.Trades[x].Amount
updated = true
}
if d.Trades[y].Timestamp != m.Trades[x].Timestamp {
d.Trades[y].Timestamp = m.Trades[x].Timestamp
updated = true
}
if d.Trades[y].IsMaker != m.Trades[x].IsMaker {
d.Trades[y].IsMaker = m.Trades[x].IsMaker
updated = true
}
}
if !found {
d.Trades = append(d.Trades, m.Trades[x])
updated = true
}
m.RemainingAmount -= m.Trades[x].Amount
}
if m.RemainingAmount > 0 && m.RemainingAmount != d.RemainingAmount {
d.RemainingAmount = m.RemainingAmount
updated = true
@@ -494,6 +404,47 @@ func CopyPointerOrderSlice(old []*Detail) []*Detail {
return copySlice
}
// DeriveModify populates a modify struct by the managed order details. Note:
// Price, Amount, Trigger price and order execution bools need to be changed
// in scope. This only derives identifiers for ease.
func (d *Detail) DeriveModify() (*Modify, error) {
if d == nil {
return nil, errOrderDetailIsNil
}
return &Modify{
Exchange: d.Exchange,
ID: d.ID,
ClientOrderID: d.ClientOrderID,
Type: d.Type,
Side: d.Side,
AssetType: d.AssetType,
Pair: d.Pair,
}, nil
}
// DeriveModifyResponse populates a modify response with its identifiers for
// cross exchange standard. NOTE: New OrderID and/or ClientOrderID plus any
// changes *might* need to be populated in scope.
func (m *Modify) DeriveModifyResponse() (*ModifyResponse, error) {
if m == nil {
return nil, errOrderDetailIsNil
}
return &ModifyResponse{
Exchange: m.Exchange,
OrderID: m.ID,
ClientOrderID: m.ClientOrderID,
Type: m.Type,
Side: m.Side,
AssetType: m.AssetType,
Pair: m.Pair,
ImmediateOrCancel: m.ImmediateOrCancel,
PostOnly: m.PostOnly,
Price: m.Price,
Amount: m.Amount,
TriggerPrice: m.TriggerPrice,
}, nil
}
// DeriveCancel populates a cancel struct by the managed order details
func (d *Detail) DeriveCancel() (*Cancel, error) {
if d == nil {