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 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 0229c5a26..784641d88 100644 --- a/v2/convert.go +++ b/v2/convert.go @@ -67,6 +67,13 @@ func f64ValOrZero(i interface{}) float64 { return 0.0 } +func siMapOrNil(i interface{}) map[string]interface{} { + if m, ok := i.(map[string]interface{}); ok { + return m + } + return nil +} + 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..6b6f68379 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, @@ -207,6 +214,7 @@ func (o *OrderNewRequest) ToJSON() ([]byte, error) { PriceAuxLimit: o.PriceAuxLimit, PriceOcoStop: o.PriceOcoStop, TimeInForce: o.TimeInForce, + Meta: o.Meta, } if o.Hidden { @@ -224,21 +232,28 @@ func (o *OrderNewRequest) ToJSON() ([]byte, error) { if o.Close { aux.Flags = aux.Flags + OrderFlagClose } + + if o.AffiliateCode != "" { + aux.Meta = make(map[string]interface{}) + aux.Meta["aff_code"] = o.AffiliateCode + } + 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 +268,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, @@ -275,6 +291,7 @@ func (o *OrderUpdateRequest) ToJSON() ([]byte, error) { PriceAuxLimit: o.PriceAuxLimit, Delta: o.Delta, TimeInForce: o.TimeInForce, + Meta: o.Meta, } if o.Hidden { @@ -377,6 +394,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 @@ -398,7 +416,6 @@ func NewOrderFromRaw(raw []interface{}) (o *Order, err error) { } else if len(raw) < 26 { 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])), @@ -421,9 +438,11 @@ func NewOrderFromRaw(raw []interface{}) (o *Order, err error) { Hidden: bValOrFalse(raw[24]), PlacedID: i64ValOrZero(raw[25]), } + if len(raw) >= 31 { + o.Meta = siMapOrNil(raw[31]) + } } - - return + return o, nil } // OrderSnapshotFromRaw takes a raw list of values as returned from the websocket