From ff1e9c4cc11b1e39b3d561b2961d27aaac7388f4 Mon Sep 17 00:00:00 2001 From: JacobPlaster Date: Wed, 4 Dec 2019 13:02:37 +0000 Subject: [PATCH 1/4] types: Add ability to specify meta and affiliate code in order --- v2/convert.go | 7 +++ v2/types.go | 133 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 86 insertions(+), 54 deletions(-) diff --git a/v2/convert.go b/v2/convert.go index 0229c5a26..94bd6d32e 100644 --- a/v2/convert.go +++ b/v2/convert.go @@ -67,6 +67,13 @@ func f64ValOrZero(i interface{}) float64 { return 0.0 } +func siMapOrEmpty(i interface{}) map[string]interface{} { + if m, ok := i.(map[string]interface{}); ok { + return m + } + return make(map[string]interface{}) +} + func bValOrFalse(i interface{}) bool { if r, ok := i.(bool); ok { return r diff --git a/v2/types.go b/v2/types.go index fbd900809..6f309e9ed 100644 --- a/v2/types.go +++ b/v2/types.go @@ -154,21 +154,27 @@ const ( // OrderNewRequest represents an order to be posted to the bitfinex websocket // service. type OrderNewRequest struct { - GID int64 `json:"gid"` - CID int64 `json:"cid"` - Type string `json:"type"` - Symbol string `json:"symbol"` - Amount float64 `json:"amount,string"` - Price float64 `json:"price,string"` - Leverage int64 `json:"lev,omitempty"` - PriceTrailing float64 `json:"price_trailing,string,omitempty"` - PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` - PriceOcoStop float64 `json:"price_oco_stop,string,omitempty"` - Hidden bool `json:"hidden,omitempty"` - PostOnly bool `json:"postonly,omitempty"` - Close bool `json:"close,omitempty"` - OcoOrder bool `json:"oco_order,omitempty"` - TimeInForce string `json:"tif,omitempty"` + GID int64 `json:"gid"` + CID int64 `json:"cid"` + Type string `json:"type"` + Symbol string `json:"symbol"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + Leverage int64 `json:"lev,omitempty"` + PriceTrailing float64 `json:"price_trailing,string,omitempty"` + PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` + PriceOcoStop float64 `json:"price_oco_stop,string,omitempty"` + Hidden bool `json:"hidden,omitempty"` + PostOnly bool `json:"postonly,omitempty"` + Close bool `json:"close,omitempty"` + OcoOrder bool `json:"oco_order,omitempty"` + TimeInForce string `json:"tif,omitempty"` + AffiliateCode string `json:"-"` + Meta map[string]interface{} `json:"meta,omitempty"` +} + +type OrderMeta struct { + AffiliateCode string `json:"aff_code,string,omitempty"` } // MarshalJSON converts the order object into the format required by the bitfinex @@ -183,18 +189,19 @@ func (o *OrderNewRequest) MarshalJSON() ([]byte, error) { func (o *OrderNewRequest) ToJSON() ([]byte, error) { aux := struct { - GID int64 `json:"gid"` - CID int64 `json:"cid"` - Type string `json:"type"` - Symbol string `json:"symbol"` - Amount float64 `json:"amount,string"` - Price float64 `json:"price,string"` - Leverage int64 `json:"lev,omitempty"` - PriceTrailing float64 `json:"price_trailing,string,omitempty"` - PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` - PriceOcoStop float64 `json:"price_oco_stop,string,omitempty"` - TimeInForce string `json:"tif,omitempty"` - Flags int `json:"flags,omitempty"` + GID int64 `json:"gid"` + CID int64 `json:"cid"` + Type string `json:"type"` + Symbol string `json:"symbol"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + Leverage int64 `json:"lev,omitempty"` + PriceTrailing float64 `json:"price_trailing,string,omitempty"` + PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` + PriceOcoStop float64 `json:"price_oco_stop,string,omitempty"` + TimeInForce string `json:"tif,omitempty"` + Flags int `json:"flags,omitempty"` + Meta map[string]interface{} `json:"meta,omitempty"` }{ GID: o.GID, CID: o.CID, @@ -224,21 +231,34 @@ func (o *OrderNewRequest) ToJSON() ([]byte, error) { if o.Close { aux.Flags = aux.Flags + OrderFlagClose } + + if o.Meta == nil { + aux.Meta = make(map[string]interface{}) + } + + if o.AffiliateCode != "" { + aux.Meta["aff_code"] = o.AffiliateCode + } + + l, _ := json.Marshal(aux) + fmt.Println(string(l)) + return json.Marshal(aux) } type OrderUpdateRequest struct { - ID int64 `json:"id"` - GID int64 `json:"gid,omitempty"` - Price float64 `json:"price,string,omitempty"` - Amount float64 `json:"amount,string,omitempty"` - Leverage int64 `json:"lev,omitempty"` - Delta float64 `json:"delta,string,omitempty"` - PriceTrailing float64 `json:"price_trailing,string,omitempty"` - PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` - Hidden bool `json:"hidden,omitempty"` - PostOnly bool `json:"postonly,omitempty"` - TimeInForce string `json:"tif,omitempty"` + ID int64 `json:"id"` + GID int64 `json:"gid,omitempty"` + Price float64 `json:"price,string,omitempty"` + Amount float64 `json:"amount,string,omitempty"` + Leverage int64 `json:"lev,omitempty"` + Delta float64 `json:"delta,string,omitempty"` + PriceTrailing float64 `json:"price_trailing,string,omitempty"` + PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` + Hidden bool `json:"hidden,omitempty"` + PostOnly bool `json:"postonly,omitempty"` + TimeInForce string `json:"tif,omitempty"` + Meta map[string]interface{} `json:"meta,omitempty"` } // MarshalJSON converts the order object into the format required by the bitfinex @@ -253,18 +273,19 @@ func (o *OrderUpdateRequest) MarshalJSON() ([]byte, error) { func (o *OrderUpdateRequest) ToJSON() ([]byte, error) { aux := struct { - ID int64 `json:"id"` - GID int64 `json:"gid,omitempty"` - Price float64 `json:"price,string,omitempty"` - Amount float64 `json:"amount,string,omitempty"` - Leverage int64 `json:"lev,omitempty"` - Delta float64 `json:"delta,string,omitempty"` - PriceTrailing float64 `json:"price_trailing,string,omitempty"` - PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` - Hidden bool `json:"hidden,omitempty"` - PostOnly bool `json:"postonly,omitempty"` - TimeInForce string `json:"tif,omitempty"` - Flags int `json:"flags,omitempty"` + ID int64 `json:"id"` + GID int64 `json:"gid,omitempty"` + Price float64 `json:"price,string,omitempty"` + Amount float64 `json:"amount,string,omitempty"` + Leverage int64 `json:"lev,omitempty"` + Delta float64 `json:"delta,string,omitempty"` + PriceTrailing float64 `json:"price_trailing,string,omitempty"` + PriceAuxLimit float64 `json:"price_aux_limit,string,omitempty"` + Hidden bool `json:"hidden,omitempty"` + PostOnly bool `json:"postonly,omitempty"` + TimeInForce string `json:"tif,omitempty"` + Flags int `json:"flags,omitempty"` + Meta map[string]interface{} `json:"meta,omitempty"` }{ ID: o.ID, GID: o.GID, @@ -277,6 +298,10 @@ func (o *OrderUpdateRequest) ToJSON() ([]byte, error) { TimeInForce: o.TimeInForce, } + if o.Meta == nil { + aux.Meta = make(map[string]interface{}) + } + if o.Hidden { aux.Flags = aux.Flags + OrderFlagHidden } @@ -377,6 +402,7 @@ type Order struct { Notify bool Hidden bool PlacedID int64 + Meta map[string]interface{} } // NewOrderFromRaw takes the raw list of values as returned from the websocket @@ -395,10 +421,9 @@ func NewOrderFromRaw(raw []interface{}) (o *Order, err error) { MTSUpdated: i64ValOrZero(raw[8]), // 3 trailing zeroes, what do they map to? } - } else if len(raw) < 26 { + } else if len(raw) < 32 { return o, fmt.Errorf("data slice too short for order: %#v", raw) } else { - // TODO: API docs say ID, GID, CID, MTS_CREATE, MTS_UPDATE are int but API returns float o = &Order{ ID: int64(f64ValOrZero(raw[0])), GID: int64(f64ValOrZero(raw[1])), @@ -420,10 +445,10 @@ func NewOrderFromRaw(raw []interface{}) (o *Order, err error) { Notify: bValOrFalse(raw[23]), Hidden: bValOrFalse(raw[24]), PlacedID: i64ValOrZero(raw[25]), + Meta: siMapOrEmpty(raw[31]), } } - - return + return o, nil } // OrderSnapshotFromRaw takes a raw list of values as returned from the websocket From 70bf227e8302d578cc07d901393ae898be2d0529 Mon Sep 17 00:00:00 2001 From: JacobPlaster Date: Wed, 4 Dec 2019 13:02:48 +0000 Subject: [PATCH 2/4] bump to 2.2.6 --- CHANGELOG | 5 +++++ VERSION | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 860c3e604..396a1fe8b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +2.2.6 + +- Adds AffiliateCode field to order submission +- Extracts and exposes Meta field from order object + 2.2.5 - hotfix: parse notify info even if type not recognised diff --git a/VERSION b/VERSION index 21bb5e156..bda8fbec1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.5 +2.2.6 From 71a360d46c55b9fa82b1f1281e0019e76464b69d Mon Sep 17 00:00:00 2001 From: JacobPlaster Date: Wed, 4 Dec 2019 13:24:39 +0000 Subject: [PATCH 3/4] types.Order: pass through meta --- v2/types.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/v2/types.go b/v2/types.go index 6f309e9ed..41801ba16 100644 --- a/v2/types.go +++ b/v2/types.go @@ -234,15 +234,14 @@ func (o *OrderNewRequest) ToJSON() ([]byte, error) { if o.Meta == nil { aux.Meta = make(map[string]interface{}) + } else { + aux.Meta = o.Meta } if o.AffiliateCode != "" { aux.Meta["aff_code"] = o.AffiliateCode } - l, _ := json.Marshal(aux) - fmt.Println(string(l)) - return json.Marshal(aux) } @@ -300,6 +299,8 @@ func (o *OrderUpdateRequest) ToJSON() ([]byte, error) { if o.Meta == nil { aux.Meta = make(map[string]interface{}) + } else { + aux.Meta = o.Meta } if o.Hidden { From d3f2ae31094701a7d04310b6aed076e9472818bc Mon Sep 17 00:00:00 2001 From: JacobPlaster Date: Wed, 4 Dec 2019 15:47:18 +0000 Subject: [PATCH 4/4] fix unit tests --- tests/integration/v2/mock_ws_private_test.go | 21 ++++++++++---------- v2/convert.go | 4 ++-- v2/types.go | 21 +++++++------------- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/tests/integration/v2/mock_ws_private_test.go b/tests/integration/v2/mock_ws_private_test.go index 0578973ce..be18d4d76 100644 --- a/tests/integration/v2/mock_ws_private_test.go +++ b/tests/integration/v2/mock_ws_private_test.go @@ -3,6 +3,7 @@ package tests import ( "context" "fmt" + "reflect" "testing" "github.com/bitfinexcom/bitfinex-api-go/v2" @@ -178,7 +179,7 @@ func TestNewOrder(t *testing.T) { if len(async.Sent) <= 1 { t.Fatalf("expected >1 sent messages, got %d", len(async.Sent)) } - assert(t, &bitfinex.OrderNewRequest{Symbol: "tBTCUSD", CID: 123, Amount: -0.456}, async.Sent[1].(*bitfinex.OrderNewRequest)) + assert(t, reflect.DeepEqual(&bitfinex.OrderNewRequest{Symbol: "tBTCUSD", CID: 123, Amount: -0.456}, async.Sent[1].(*bitfinex.OrderNewRequest)), true) // order ack async.Publish(`[0,"n",[null,"on-req",null,null,[1234567,null,123,"tBTCUSD",null,null,1,1,"MARKET",null,null,null,null,null,null,null,915.5,null,null,null,null,null,null,0,null,null],null,"SUCCESS","Submitting market buy order for 1.0 BTC."]]`) @@ -188,7 +189,7 @@ func TestNewOrder(t *testing.T) { if err != nil { t.Fatal(err) } - assert(t, &bitfinex.Notification{Type: "on-req", NotifyInfo: &bitfinex.OrderNew{ID: 1234567, CID: 123, Symbol: "tBTCUSD", Amount: 1, AmountOrig: 1, Type: "MARKET", Price: 915.5}}, not) + assert(t, reflect.DeepEqual(&bitfinex.Notification{Type: "on-req", NotifyInfo: &bitfinex.OrderNew{ID: 1234567, CID: 123, Symbol: "tBTCUSD", Amount: 1, AmountOrig: 1, Type: "MARKET", Price: 915.5}}, not), false) } func TestFills(t *testing.T) { @@ -292,7 +293,7 @@ func TestFills(t *testing.T) { if err != nil { t.Fatal(err) } - assert(t, &bitfinex.OrderCancel{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1514909325236, MTSUpdated: 1514909325631, Amount: 0, AmountOrig: 1, Type: "MARKET", Status: "EXECUTED @ 916.2(0.78): was PARTIALLY FILLED @ 915.9(0.22)", Price: 915.5, PriceAvg: 916.13496085}, oc) + assert(t, reflect.DeepEqual(&bitfinex.OrderCancel{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1514909325236, MTSUpdated: 1514909325631, Amount: 0, AmountOrig: 1, Type: "MARKET", Status: "EXECUTED @ 916.2(0.78): was PARTIALLY FILLED @ 915.9(0.22)", Price: 915.5, PriceAvg: 916.13496085}, oc), true) // fills--trade executions async.Publish(`[0,"te",[1,"tBTCUSD",1514909325593,1234567,0.21679716,915.9,null,null,-1]]`) @@ -423,7 +424,7 @@ func TestCancel(t *testing.T) { if len(async.Sent) <= 1 { t.Fatalf("expected >1 sent messages, got %d", len(async.Sent)) } - assert(t, &bitfinex.OrderNewRequest{Symbol: "tBTCUSD", CID: 123, Amount: -0.456, Type: "LIMIT", Price: 900.0}, async.Sent[1].(*bitfinex.OrderNewRequest)) + assert(t, reflect.DeepEqual(&bitfinex.OrderNewRequest{Symbol: "tBTCUSD", CID: 123, Amount: -0.456, Type: "LIMIT", Price: 900.0}, async.Sent[1].(*bitfinex.OrderNewRequest)), true) // order pending new async.Publish(`[0,"n",[null,"on-req",null,null,[1234567,null,123,"tBTCUSD",null,null,1,1,"LIMIT",null,null,null,null,null,null,null,900,null,null,null,null,null,null,0,null,null],null,"SUCCESS","Submitting limit buy order for 1.0 BTC."]]`) @@ -442,7 +443,7 @@ func TestCancel(t *testing.T) { } // assert order new update - assert(t, &bitfinex.OrderNew{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1515179518260, MTSUpdated: 1515179518315, Type: "LIMIT", Amount: 1, AmountOrig: 1, Status: "ACTIVE", Price: 900.0}, on) + assert(t, reflect.DeepEqual(&bitfinex.OrderNew{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1515179518260, MTSUpdated: 1515179518315, Type: "LIMIT", Amount: 1, AmountOrig: 1, Status: "ACTIVE", Price: 900.0}, on), true) // publish cancel request req := &bitfinex.OrderCancelRequest{ID: on.ID} @@ -468,7 +469,7 @@ func TestCancel(t *testing.T) { if err != nil { t.Fatal(err) } - assert(t, &bitfinex.OrderCancel{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1515179518260, MTSUpdated: 1515179520203, Type: "LIMIT", Status: "CANCELED", Price: 900.0, Amount: 1, AmountOrig: 1}, oc) + assert(t, reflect.DeepEqual(&bitfinex.OrderCancel{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1515179518260, MTSUpdated: 1515179520203, Type: "LIMIT", Status: "CANCELED", Price: 900.0, Amount: 1, AmountOrig: 1}, oc), true) } func TestUpdateOrder(t *testing.T) { @@ -526,7 +527,7 @@ func TestUpdateOrder(t *testing.T) { if len(async.Sent) <= 1 { t.Fatalf("expected >1 sent messages, got %d", len(async.Sent)) } - assert(t, &bitfinex.OrderNewRequest{Symbol: "tBTCUSD", CID: 123, Amount: -0.456, Type: "LIMIT", Price: 900.0}, async.Sent[1].(*bitfinex.OrderNewRequest)) + assert(t, reflect.DeepEqual(&bitfinex.OrderNewRequest{Symbol: "tBTCUSD", CID: 123, Amount: -0.456, Type: "LIMIT", Price: 900.0}, async.Sent[1].(*bitfinex.OrderNewRequest)), true) // order pending new async.Publish(`[0,"n",[null,"on-req",null,null,[1234567,null,123,"tBTCUSD",null,null,1,1,"LIMIT",null,null,null,null,null,null,null,900,null,null,null,null,null,null,0,null,null],null,"SUCCESS","Submitting limit buy order for 1.0 BTC."]]`) @@ -545,7 +546,7 @@ func TestUpdateOrder(t *testing.T) { } // assert order new update - assert(t, &bitfinex.OrderNew{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1515179518260, MTSUpdated: 1515179518315, Type: "LIMIT", Amount: 1, AmountOrig: 1, Status: "ACTIVE", Price: 900.0}, on) + assert(t, reflect.DeepEqual(&bitfinex.OrderNew{ID: 1234567, CID: 123, Symbol: "tBTCUSD", MTSCreated: 1515179518260, MTSUpdated: 1515179518315, Type: "LIMIT", Amount: 1, AmountOrig: 1, Status: "ACTIVE", Price: 900.0}, on), true) // publish update request req := &bitfinex.OrderUpdateRequest{ @@ -562,7 +563,7 @@ func TestUpdateOrder(t *testing.T) { t.Fatal(err.Error()) } // assert sent message - assert(t, req, async.Sent[pre].(*bitfinex.OrderUpdateRequest)) + assert(t, reflect.DeepEqual(req, async.Sent[pre].(*bitfinex.OrderUpdateRequest)), true) // cancel ack notify async.Publish(`[0,"n",[1547469854094,"ou-req",null,null,[1234567,0,123,"tBTCUSD",1547469854025,1547469854042,0.04,0.04,"LIMIT",null,null,null,0,"ACTIVE",null,null,1200,0,0,0,null,null,null,0,0,null,null,null,"API>BFX",null,null,null],null,"SUCCESS","Submitting update to exchange limit buy order for 0.04 BTC."]]`) @@ -574,7 +575,7 @@ func TestUpdateOrder(t *testing.T) { if err != nil { t.Fatal(err) } - assert(t, &bitfinex.OrderUpdate{ID:1234567, GID:0, CID:123, Symbol:"tBTCUSD", MTSCreated:1547469854025, MTSUpdated:1547469854121, Amount:0.04, AmountOrig:0.04, Type:"LIMIT", TypePrev:"", Flags:0, Status:"ACTIVE", Price:1200, PriceAvg:0, PriceTrailing:0, PriceAuxLimit:0, Notify:false, Hidden:false, PlacedID:0}, ou) + assert(t, reflect.DeepEqual(&bitfinex.OrderUpdate{ID:1234567, GID:0, CID:123, Symbol:"tBTCUSD", MTSCreated:1547469854025, MTSUpdated:1547469854121, Amount:0.04, AmountOrig:0.04, Type:"LIMIT", TypePrev:"", Flags:0, Status:"ACTIVE", Price:1200, PriceAvg:0, PriceTrailing:0, PriceAuxLimit:0, Notify:false, Hidden:false, PlacedID:0}, ou), true) } func TestUsesAuthenticatedSocket(t *testing.T) { diff --git a/v2/convert.go b/v2/convert.go index 94bd6d32e..784641d88 100644 --- a/v2/convert.go +++ b/v2/convert.go @@ -67,11 +67,11 @@ func f64ValOrZero(i interface{}) float64 { return 0.0 } -func siMapOrEmpty(i interface{}) map[string]interface{} { +func siMapOrNil(i interface{}) map[string]interface{} { if m, ok := i.(map[string]interface{}); ok { return m } - return make(map[string]interface{}) + return nil } func bValOrFalse(i interface{}) bool { diff --git a/v2/types.go b/v2/types.go index 41801ba16..6b6f68379 100644 --- a/v2/types.go +++ b/v2/types.go @@ -214,6 +214,7 @@ func (o *OrderNewRequest) ToJSON() ([]byte, error) { PriceAuxLimit: o.PriceAuxLimit, PriceOcoStop: o.PriceOcoStop, TimeInForce: o.TimeInForce, + Meta: o.Meta, } if o.Hidden { @@ -232,13 +233,8 @@ func (o *OrderNewRequest) ToJSON() ([]byte, error) { aux.Flags = aux.Flags + OrderFlagClose } - if o.Meta == nil { - aux.Meta = make(map[string]interface{}) - } else { - aux.Meta = o.Meta - } - if o.AffiliateCode != "" { + aux.Meta = make(map[string]interface{}) aux.Meta["aff_code"] = o.AffiliateCode } @@ -295,12 +291,7 @@ func (o *OrderUpdateRequest) ToJSON() ([]byte, error) { PriceAuxLimit: o.PriceAuxLimit, Delta: o.Delta, TimeInForce: o.TimeInForce, - } - - if o.Meta == nil { - aux.Meta = make(map[string]interface{}) - } else { - aux.Meta = o.Meta + Meta: o.Meta, } if o.Hidden { @@ -422,7 +413,7 @@ func NewOrderFromRaw(raw []interface{}) (o *Order, err error) { MTSUpdated: i64ValOrZero(raw[8]), // 3 trailing zeroes, what do they map to? } - } else if len(raw) < 32 { + } else if len(raw) < 26 { return o, fmt.Errorf("data slice too short for order: %#v", raw) } else { o = &Order{ @@ -446,7 +437,9 @@ func NewOrderFromRaw(raw []interface{}) (o *Order, err error) { Notify: bValOrFalse(raw[23]), Hidden: bValOrFalse(raw[24]), PlacedID: i64ValOrZero(raw[25]), - Meta: siMapOrEmpty(raw[31]), + } + if len(raw) >= 31 { + o.Meta = siMapOrNil(raw[31]) } } return o, nil