From e97a9e2da638adf601c62b1425f96423f4d4cd9c Mon Sep 17 00:00:00 2001 From: seb Date: Tue, 2 Jan 2018 22:57:03 +0800 Subject: [PATCH 1/8] Subscribe to OrderBook and process - Part 1 --- .../Bitfinex/BestBidAskUpdatedEventArgs.cs | 67 +++++++ .../Bitfinex/BitfinexBrokerage.Messaging.cs | 43 ++++- Brokerages/Bitfinex/Messages/L2Update.cs | 61 ++++++ Brokerages/Bitfinex/Messages/OrderBook.cs | 61 ++++++ Brokerages/Bitfinex/OrderBook.cs | 173 ++++++++++++++++++ 5 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 Brokerages/Bitfinex/BestBidAskUpdatedEventArgs.cs create mode 100644 Brokerages/Bitfinex/Messages/L2Update.cs create mode 100644 Brokerages/Bitfinex/Messages/OrderBook.cs create mode 100644 Brokerages/Bitfinex/OrderBook.cs diff --git a/Brokerages/Bitfinex/BestBidAskUpdatedEventArgs.cs b/Brokerages/Bitfinex/BestBidAskUpdatedEventArgs.cs new file mode 100644 index 000000000000..753a2f976f31 --- /dev/null +++ b/Brokerages/Bitfinex/BestBidAskUpdatedEventArgs.cs @@ -0,0 +1,67 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; + +namespace QuantConnect.Brokerages.Bitfinex +{ + /// + /// Event arguments class for the event + /// + public sealed class BestBidAskUpdatedEventArgs : EventArgs + { + /// + /// Gets the new best bid price + /// + public Symbol Symbol { get; } + + /// + /// Gets the new best bid price + /// + public decimal BestBidPrice { get; } + + /// + /// Gets the new best bid size + /// + public decimal BestBidSize { get; } + + /// + /// Gets the new best ask price + /// + public decimal BestAskPrice { get; } + + /// + /// Gets the new best ask size + /// + public decimal BestAskSize { get; } + + /// + /// Initializes a new instance of the class + /// + /// The symbol + /// The newly updated best bid price + /// >The newly updated best bid size + /// The newly updated best ask price + /// The newly updated best ask size + public BestBidAskUpdatedEventArgs(Symbol symbol, decimal bestBidPrice, decimal bestBidSize, decimal bestAskPrice, decimal bestAskSize) + { + Symbol = symbol; + BestBidPrice = bestBidPrice; + BestBidSize = bestBidSize; + BestAskPrice = bestAskPrice; + BestAskSize = bestAskSize; + } + } +} diff --git a/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs b/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs index e0fd2640fbc7..13f319a1a282 100644 --- a/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs +++ b/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs @@ -14,6 +14,7 @@ */ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -30,6 +31,10 @@ public partial class BitfinexBrokerage : BaseWebsocketsBrokerage, IDataQueueHand /// /// Wss message handler /// + /// + #region Declarations + private readonly ConcurrentDictionary _orderBooks = new ConcurrentDictionary(); + #endregion /// /// protected override void OnMessageImpl(object sender, WebSocketMessage e) @@ -49,6 +54,23 @@ protected override void OnMessageImpl(object sender, WebSocketMessage e) _lastHeartbeatUtcTime = DateTime.UtcNow; return; } + else if (ChannelList.ContainsKey(id) && ChannelList[id].Name == "book") + { + if (raw[1].Type == JTokenType.Array) + { + //order book snapshot + var data = raw[1].ToObject(typeof(string[][])); + PopulateOrderBook(data, ChannelList[id].Symbol); + return; + } + else + { + //order book update + var data = raw.ToObject(typeof(string[])); + L2Update(data, ChannelList[id].Symbol); + } + + } else if (ChannelList.ContainsKey(id) && ChannelList[id].Name == "ticker") { //ticker @@ -84,7 +106,7 @@ protected override void OnMessageImpl(object sender, WebSocketMessage e) return; } } - else if ((raw.channel == "ticker" || raw.channel == "trades") && raw.@event == "subscribed") + else if ((raw.channel == "ticker" || raw.channel == "trades" || raw.channel == "book") && raw.@event == "subscribed") { var channel = (string)raw.channel; var currentChannelId = (string)raw.chanId; @@ -128,6 +150,18 @@ protected override void OnMessageImpl(object sender, WebSocketMessage e) } } + private void PopulateOrderBook(string[][] data, string symbol) + { + //to do + return; + } + + private void L2Update(string[] data, string symbol) + { + //to do + return; + } + private void PopulateTicker(string response, string symbol) { var data = JsonConvert.DeserializeObject(response, settings); @@ -302,6 +336,13 @@ public void Subscribe(Packets.LiveNodePacket job, IEnumerable symbols) pair = item.Value })); + WebSocket.Send(JsonConvert.SerializeObject(new + { + @event = "subscribe", + channel = "book", + pair = item.Value + })); + Log.Trace("BitfinexBrokerage.Subscribe(): Sent subcribe for " + item.Value); } } diff --git a/Brokerages/Bitfinex/Messages/L2Update.cs b/Brokerages/Bitfinex/Messages/L2Update.cs new file mode 100644 index 000000000000..57b999a404fe --- /dev/null +++ b/Brokerages/Bitfinex/Messages/L2Update.cs @@ -0,0 +1,61 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +namespace QuantConnect.Brokerages.Bitfinex.Messages +{ + /// + /// Ticker message object + /// + public class L2Update : BaseMessage + { + private const int _channelId = 0; + private const int _price = 1; + private const int _count = 2; + private const int _amount = 3; + + /// + /// L2Update Message constructor + /// + /// + public L2Update(string[] values) + : base(values) + { + ChannelId = GetInt(_channelId); + Price = TryGetDecimal(_price); + Count = GetInt(_count); + Amount = TryGetDecimal(_amount); + } + + /// + /// Channel Id + /// + public int ChannelId { get; set; } + + /// + /// Price + /// + public decimal Price { get; set; } + + /// + /// Count + /// + public decimal Count { get; set; } + + /// + /// Amount + /// + public decimal Amount { get; set; } + } +} \ No newline at end of file diff --git a/Brokerages/Bitfinex/Messages/OrderBook.cs b/Brokerages/Bitfinex/Messages/OrderBook.cs new file mode 100644 index 000000000000..6847939a6265 --- /dev/null +++ b/Brokerages/Bitfinex/Messages/OrderBook.cs @@ -0,0 +1,61 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +namespace QuantConnect.Brokerages.Bitfinex.Messages +{ + /// + /// Ticker message object + /// + public class OrderBook : BaseMessage + { + private const int _channelId = 0; + private const int _price = 1; + private const int _count = 2; + private const int _amount = 3; + + /// + /// Ticker Message constructor + /// + /// + public OrderBook(string[] values) + : base(values) + { + ChannelId = GetInt(_channelId); + Price = TryGetDecimal(_price); + Count = GetInt(_count); + Amount = TryGetDecimal(_amount); + } + + /// + /// Channel Id + /// + public int ChannelId { get; set; } + + /// + /// Price + /// + public decimal Price { get; set; } + + /// + /// Count + /// + public decimal Count { get; set; } + + /// + /// Amount + /// + public decimal Amount { get; set; } + } +} \ No newline at end of file diff --git a/Brokerages/Bitfinex/OrderBook.cs b/Brokerages/Bitfinex/OrderBook.cs new file mode 100644 index 000000000000..e90cea94e493 --- /dev/null +++ b/Brokerages/Bitfinex/OrderBook.cs @@ -0,0 +1,173 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace QuantConnect.Brokerages.Bitfinex +{ + /// + /// Represents a full order book for a security. + /// It contains prices and order sizes for each bid and ask level. + /// The best bid and ask prices are also kept up to date. + /// + public class OrderBook + { + private readonly object _locker = new object(); + private readonly Symbol _symbol; + private readonly SortedDictionary _bids = new SortedDictionary(); + private readonly SortedDictionary _asks = new SortedDictionary(); + + /// + /// Event fired each time or are changed + /// + public event EventHandler BestBidAskUpdated; + + /// + /// The best bid price + /// + public decimal BestBidPrice { get; private set; } + + /// + /// The best bid size + /// + public decimal BestBidSize { get; private set; } + + /// + /// The best ask price + /// + public decimal BestAskPrice { get; private set; } + + /// + /// The best ask size + /// + public decimal BestAskSize { get; private set; } + + /// + /// Initializes a new instance of the class + /// + /// The symbol for the order book + public OrderBook(Symbol symbol) + { + _symbol = symbol; + } + + /// + /// Clears all bid/ask levels and prices. + /// + public void Clear() + { + lock (_locker) + { + _bids.Clear(); + _asks.Clear(); + } + + BestBidPrice = 0; + BestBidSize = 0; + BestAskPrice = 0; + BestAskSize = 0; + } + + /// + /// Updates or inserts a bid price level in the order book + /// + /// The bid price level to be inserted or updated + /// The new size at the bid price level + public void UpdateBidRow(decimal price, decimal size) + { + lock(_locker) + { + _bids[price] = size; + } + + if (BestBidPrice == 0 || price >= BestBidPrice) + { + BestBidPrice = price; + BestBidSize = size; + + BestBidAskUpdated?.Invoke(this, new BestBidAskUpdatedEventArgs(_symbol, BestBidPrice, BestBidSize, BestAskPrice, BestAskSize)); + } + } + + /// + /// Updates or inserts an ask price level in the order book + /// + /// The ask price level to be inserted or updated + /// The new size at the ask price level + public void UpdateAskRow(decimal price, decimal size) + { + lock(_locker) + { + _asks[price] = size; + } + + if (BestAskPrice == 0 || price <= BestAskPrice) + { + BestAskPrice = price; + BestAskSize = size; + + BestBidAskUpdated?.Invoke(this, new BestBidAskUpdatedEventArgs(_symbol, BestBidPrice, BestBidSize, BestAskPrice, BestAskSize)); + } + } + + /// + /// Removes a bid price level from the order book + /// + /// The bid price level to be removed + public void RemoveBidRow(decimal price) + { + lock(_locker) + { + _bids.Remove(price); + } + + if (price == BestBidPrice) + { + lock(_locker) + { + BestBidPrice = _bids.Keys.LastOrDefault(); + BestBidSize = BestBidPrice > 0 ? _bids[BestBidPrice] : 0; + } + + BestBidAskUpdated?.Invoke(this, new BestBidAskUpdatedEventArgs(_symbol, BestBidPrice, BestBidSize, BestAskPrice, BestAskSize)); + } + } + + /// + /// Removes an ask price level from the order book + /// + /// The ask price level to be removed + public void RemoveAskRow(decimal price) + { + lock(_locker) + { + _asks.Remove(price); + } + + if (price == BestAskPrice) + { + lock(_locker) + { + BestAskPrice = _asks.Keys.FirstOrDefault(); + BestAskSize = BestAskPrice > 0 ? _asks[BestAskPrice] : 0; + } + + BestBidAskUpdated?.Invoke(this, new BestBidAskUpdatedEventArgs(_symbol, BestBidPrice, BestBidSize, BestAskPrice, BestAskSize)); + } + } + } +} From 1b61a55cb1f2452e20107d13b32b868a0ccea993 Mon Sep 17 00:00:00 2001 From: seb Date: Wed, 3 Jan 2018 19:40:45 +0800 Subject: [PATCH 2/8] Part 2 - Order Book feed operational --- .../Bitfinex/BitfinexBrokerage.Messaging.cs | 91 ++++++++++++++++++- Brokerages/Bitfinex/Messages/OrderBook.cs | 13 +-- Brokerages/QuantConnect.Brokerages.csproj | 4 + 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs b/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs index 13f319a1a282..af12c8864068 100644 --- a/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs +++ b/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -141,7 +142,7 @@ protected override void OnMessageImpl(object sender, WebSocketMessage e) return; } - Log.Trace("BitfinexBrokerage.OnMessage(): " + e.Message); + //Log.Trace("BitfinexBrokerage.OnMessage(): " + e.Message); } catch (Exception ex) { @@ -152,16 +153,93 @@ protected override void OnMessageImpl(object sender, WebSocketMessage e) private void PopulateOrderBook(string[][] data, string symbol) { - //to do - return; + OrderBook orderBook; + if (!_orderBooks.TryGetValue(symbol, out orderBook)) + { + orderBook = new OrderBook(symbol); + _orderBooks[symbol] = orderBook; + } + else + { + orderBook.BestBidAskUpdated -= OnBestBidAskUpdated; + orderBook.Clear(); + } + + foreach (var item in data) + { + var msg = new Messages.OrderBook(item); + // Positive values -> bid + if (msg.Price > 0) + { + orderBook.UpdateAskRow(msg.Price, msg.Amount); + } + // negative values -> ask. + else if (msg.Price < 0) + { + orderBook.UpdateBidRow(msg.Price, msg.Amount); + } + + orderBook.BestBidAskUpdated += OnBestBidAskUpdated; + } + return; + } + + private void OnBestBidAskUpdated(object sender, BestBidAskUpdatedEventArgs e) + { + EmitQuoteTick(e.Symbol, e.BestBidPrice, e.BestBidSize, e.BestAskPrice, e.BestAskSize); } private void L2Update(string[] data, string symbol) { - //to do + var orderBook = _orderBooks[symbol]; + + var msg = new Messages.L2Update(data); + + // Positive values -> bid + if (msg.Price > 0) + { + if (msg.Count == 0) + { + orderBook.RemoveAskRow(msg.Price); + } + else + { + orderBook.UpdateAskRow(msg.Price, msg.Amount); + } + } + // negative values -> ask. + else if (msg.Price < 0) + { + if (msg.Count == 0) + { + orderBook.RemoveBidRow(msg.Price); + } + else + { + orderBook.UpdateBidRow(msg.Price, msg.Amount); + } + } return; } + private void EmitQuoteTick(Symbol symbol, decimal bidPrice, decimal bidSize, decimal askPrice, decimal askSize) + { + lock (Ticks) + { + Ticks.Add(new Tick + { + AskPrice = askPrice, + BidPrice = bidPrice, + Value = (askPrice + bidPrice) / 2m, + Time = DateTime.UtcNow, + Symbol = symbol, + TickType = TickType.Quote, + AskSize = askSize, + BidSize = bidSize + }); + } + } + private void PopulateTicker(string response, string symbol) { var data = JsonConvert.DeserializeObject(response, settings); @@ -340,7 +418,10 @@ public void Subscribe(Packets.LiveNodePacket job, IEnumerable symbols) { @event = "subscribe", channel = "book", - pair = item.Value + pair = item.Value, + prec = "P0", // default P0 + freq = "F0", // default F0 + length = "5" // default 25 })); Log.Trace("BitfinexBrokerage.Subscribe(): Sent subcribe for " + item.Value); diff --git a/Brokerages/Bitfinex/Messages/OrderBook.cs b/Brokerages/Bitfinex/Messages/OrderBook.cs index 6847939a6265..9110a5ce5389 100644 --- a/Brokerages/Bitfinex/Messages/OrderBook.cs +++ b/Brokerages/Bitfinex/Messages/OrderBook.cs @@ -20,10 +20,9 @@ namespace QuantConnect.Brokerages.Bitfinex.Messages /// public class OrderBook : BaseMessage { - private const int _channelId = 0; - private const int _price = 1; - private const int _count = 2; - private const int _amount = 3; + private const int _price = 0; + private const int _count = 1; + private const int _amount = 2; /// /// Ticker Message constructor @@ -32,17 +31,11 @@ public class OrderBook : BaseMessage public OrderBook(string[] values) : base(values) { - ChannelId = GetInt(_channelId); Price = TryGetDecimal(_price); Count = GetInt(_count); Amount = TryGetDecimal(_amount); } - /// - /// Channel Id - /// - public int ChannelId { get; set; } - /// /// Price /// diff --git a/Brokerages/QuantConnect.Brokerages.csproj b/Brokerages/QuantConnect.Brokerages.csproj index 537e5dc47b79..d2176107010d 100644 --- a/Brokerages/QuantConnect.Brokerages.csproj +++ b/Brokerages/QuantConnect.Brokerages.csproj @@ -171,12 +171,16 @@ + + + + From a2d92d2e4e10a798edfa085de266250320070a22 Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 4 Jan 2018 00:02:00 +0800 Subject: [PATCH 3/8] test case to expect book channels --- .../Brokerages/Bitfinex/BitfinexBrokerageWebsocketsTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/Brokerages/Bitfinex/BitfinexBrokerageWebsocketsTests.cs b/Tests/Brokerages/Bitfinex/BitfinexBrokerageWebsocketsTests.cs index 45bf0e5645c3..47767cb5aef4 100644 --- a/Tests/Brokerages/Bitfinex/BitfinexBrokerageWebsocketsTests.cs +++ b/Tests/Brokerages/Bitfinex/BitfinexBrokerageWebsocketsTests.cs @@ -366,10 +366,11 @@ public void SubscribeTest() _unit.Subscribe(new[] { Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Bitfinex), Symbol.Create("UNIVERSE", SecurityType.Crypto, Market.Bitfinex), Symbol.Create("ETHBTC", SecurityType.Crypto, Market.Bitfinex)}); - Assert.AreEqual(4, actualSymbols.Count); - Assert.AreEqual(4, actualChannels.Count); + Assert.AreEqual(6, actualSymbols.Count); + Assert.AreEqual(6, actualChannels.Count); CollectionAssert.Contains(actualChannels, "ticker"); CollectionAssert.Contains(actualChannels, "trades"); + CollectionAssert.Contains(actualChannels, "book"); CollectionAssert.Contains(actualSymbols, "BTCUSD"); CollectionAssert.Contains(actualSymbols, "ETHBTC"); } From 6cf7b05647f777e90b220e58e6a72db96131fc7b Mon Sep 17 00:00:00 2001 From: seb Date: Thu, 4 Jan 2018 01:19:49 +0800 Subject: [PATCH 4/8] minor bitfinex test case fixes --- .../Brokerages/Bitfinex/BitfinexBrokerageIntegrationTests.cs | 2 +- .../Bitfinex/BitfinexBrokerageIntegrationTestsBase.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Brokerages/Bitfinex/BitfinexBrokerageIntegrationTests.cs b/Tests/Brokerages/Bitfinex/BitfinexBrokerageIntegrationTests.cs index c9b7d8fa8369..1539c8a5a348 100644 --- a/Tests/Brokerages/Bitfinex/BitfinexBrokerageIntegrationTests.cs +++ b/Tests/Brokerages/Bitfinex/BitfinexBrokerageIntegrationTests.cs @@ -23,7 +23,7 @@ public class BitfinexBrokerageIntegrationTests : BitfinexBrokerageIntegrationTes { #region Properties - protected override Symbol Symbol => Symbol.Create("BTCUSD", SecurityType.Forex, Market.Bitfinex); + protected override Symbol Symbol => Symbol.Create("BTCUSD", SecurityType.Crypto, Market.Bitfinex); /// /// Gets a high price for the specified symbol so a limit sell won't fill diff --git a/Tests/Brokerages/Bitfinex/BitfinexBrokerageIntegrationTestsBase.cs b/Tests/Brokerages/Bitfinex/BitfinexBrokerageIntegrationTestsBase.cs index ab79c00eb9cd..756cd7bc2eed 100644 --- a/Tests/Brokerages/Bitfinex/BitfinexBrokerageIntegrationTestsBase.cs +++ b/Tests/Brokerages/Bitfinex/BitfinexBrokerageIntegrationTestsBase.cs @@ -57,13 +57,13 @@ protected override decimal GetDefaultQuantity() protected override IBrokerage CreateBrokerage(IOrderProvider orderProvider, ISecurityProvider securityProvider) { - var restClient = new RestClient("https://api.gdax.com"); + var restClient = new RestClient("https://api.bitfinex.com"); var webSocketClient = new WebSocketWrapper(); var algorithm = new Mock(); algorithm.Setup(a => a.BrokerageModel).Returns(new BitfinexBrokerageModel(AccountType.Cash)); - return new BitfinexBrokerage(Config.Get("gdax-url", "wss://ws-feed.gdax.com"), webSocketClient, restClient, Config.Get("bitfinex-api-key"), Config.Get("bitfinex-api-secret"), + return new BitfinexBrokerage(Config.Get("bitfinex-wss", "wss://api2.bitfinex.com:3000/ws"), webSocketClient, restClient, Config.Get("bitfinex-api-key"), Config.Get("bitfinex-api-secret"), algorithm.Object); } From afd828bec9d724a920de8b7bb73859128b2219e2 Mon Sep 17 00:00:00 2001 From: seb Date: Sun, 7 Jan 2018 00:36:27 +0800 Subject: [PATCH 5/8] Fix failing order cancelation due to missing brokerId in ticket --- Brokerages/Bitfinex/BitfinexBrokerage.cs | 29 +++++------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/Brokerages/Bitfinex/BitfinexBrokerage.cs b/Brokerages/Bitfinex/BitfinexBrokerage.cs index 8f3810f140f6..1561f1e4a8e5 100644 --- a/Brokerages/Bitfinex/BitfinexBrokerage.cs +++ b/Brokerages/Bitfinex/BitfinexBrokerage.cs @@ -67,7 +67,7 @@ public partial class BitfinexBrokerage : BaseWebsocketsBrokerage, IDataQueueHand /// Stores fill messages /// public ConcurrentDictionary FillSplit { get; set; } - + private enum ChannelCode { pubticker = 0, @@ -163,33 +163,16 @@ private bool PlaceOrder(Order order, Order crossOrder = null) } else { - Order caching = null; - if (order.Type == OrderType.Market) - { - caching = new MarketOrder(); - } - else if (order.Type == OrderType.Limit) + if (order.Type == OrderType.Market || order.Type == OrderType.Limit || order.Type == OrderType.StopMarket) { - caching = new LimitOrder(); - } - else if (order.Type == OrderType.StopMarket) - { - caching = new StopMarketOrder(); } else { throw new Exception("BitfinexBrokerage.PlaceOrder(): Unsupported order type was encountered: " + order.Type); } - caching.Id = order.Id; - caching.BrokerId = new List { placing.OrderId.ToString() }; - caching.Price = order.Price; - caching.Quantity = totalQuantity; - caching.Status = OrderStatus.Submitted; - caching.Symbol = order.Symbol; - caching.Time = order.Time; - - CachedOrderIDs.TryAdd(order.Id, caching); + order.BrokerId = new List { placing.OrderId.ToString() }; + CachedOrderIDs.TryAdd(order.Id, order); } if (crossOrder != null && crossOrder.Status != OrderStatus.Submitted) { @@ -199,7 +182,7 @@ private bool PlaceOrder(Order order, Order crossOrder = null) } OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, 0, "Bitfinex Order Event") { Status = OrderStatus.Submitted }); - Log.Trace("BitfinexBrokerage.PlaceOrder(): Order completed successfully orderid:" + order.Id); + Log.Trace("BitfinexBrokerage.PlaceOrder(): Order completed successfully orderId:" + order.Id); } else { @@ -245,7 +228,7 @@ public override bool CancelOrder(Order order) { var cancelPost = new OrderStatusPost { - OrderId = order.Id + OrderId = Convert.ToInt64(id) }; var response = ExecutePost(OrderCancelRequestUrl, cancelPost); From 76b7589476519db89039a81682fbd205a83ae336 Mon Sep 17 00:00:00 2001 From: seb Date: Sun, 7 Jan 2018 00:39:18 +0800 Subject: [PATCH 6/8] order update in ws - part1b --- Brokerages/Bitfinex/Messages/OrderUpdate.cs | 98 +++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Brokerages/Bitfinex/Messages/OrderUpdate.cs diff --git a/Brokerages/Bitfinex/Messages/OrderUpdate.cs b/Brokerages/Bitfinex/Messages/OrderUpdate.cs new file mode 100644 index 000000000000..86915e3f6e20 --- /dev/null +++ b/Brokerages/Bitfinex/Messages/OrderUpdate.cs @@ -0,0 +1,98 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +using System; + +namespace QuantConnect.Brokerages.Bitfinex.Messages +{ + /// + /// Ticker message object + /// + public class OrderUpdate : BaseMessage + { + private const int _id = 0; + private const int _pair = 1; + private const int _amount = 2; + private const int _amount_orig = 3; + private const int _type = 4; + private const int _status = 5; + private const int _price = 6; + private const int _price_avg = 7; + private const int _created_at = 8; + private const int _notify = 9; + private const int _hidden = 10; + private const int _oco = 11; + + /// + /// L2Update Message constructor + /// + /// + public OrderUpdate(string[] values) + : base(values) + { + OrderId = GetLong(_id); + OrderPair = GetString(_pair); + OrderAmount = TryGetDecimal(_id); + //OrderAmountOrig = + OrderType = GetString(_type); + OrderStatus = GetString(_status); + OrderPrice = TryGetDecimal(_price); + OrderPriceAvg = TryGetDecimal(_price_avg); + OrderCreatedAt = GetString(_created_at); + //OrderNotify = GetString(_notify); + //OrderHidden = GetInt(_hidden); + //OrderOco = GetInt(_oco); + } + + /// + /// Order Id + /// + public long OrderId { get; set; } + + /// + /// Order Pair + /// + public string OrderPair { get; set; } + + /// + /// Order Amount + /// + public decimal OrderAmount { get; set; } + + /// + /// Order Type + /// + public string OrderType { get; set; } + + /// + /// Order Status + /// + public string OrderStatus { get; set; } + + /// + /// Order Price + /// + public decimal OrderPrice { get; set; } + + /// + /// Order Price Avg + /// + public decimal OrderPriceAvg { get; set; } + + /// + /// Order Created At + /// + public string OrderCreatedAt { get; set; } + } +} \ No newline at end of file From 1e7827d352ae8e31fb426f8768fc045325f9e797 Mon Sep 17 00:00:00 2001 From: seb Date: Sun, 7 Jan 2018 00:38:32 +0800 Subject: [PATCH 7/8] process order updates in ws - part1 --- .../Bitfinex/BitfinexBrokerage.Messaging.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs b/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs index af12c8864068..1f782910c63b 100644 --- a/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs +++ b/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs @@ -95,6 +95,39 @@ protected override void OnMessageImpl(object sender, WebSocketMessage e) PopulateTrade(term, data); return; } + else if (id == "0" && (term == "on" || term == "ou" || term == "oc")) + { + //order updates + Log.Trace("BitfinexBrokerage.OnMessage(): Order update"); + var data = raw[2].ToObject(typeof(string[])); + UpdateOrder(term, data); + return; + + } + else if (id == "0" && term == "os") + { + //order snapshot + Log.Trace("BitfinexBrokerage.OnMessage(): Order Snapshot"); + var data = raw[2].ToObject(typeof(string[][])); + PopulateOrder(data); + + } + else if (id == "0" && (term == "pn" || term == "pu" || term == "pc")) + { + //position updates + Log.Trace("BitfinexBrokerage.OnMessage(): Order update"); + var data = raw[2].ToObject(typeof(string[])); + return; + + } + else if (id == "0" && term == "ps") + { + //position snapshot + Log.Trace("BitfinexBrokerage.OnMessage(): Position Snapshot"); + //var data = raw[2].ToObject(typeof(string[][])); + return; + + } else if (term == "ws") { //wallet @@ -280,6 +313,51 @@ private void PopulateTradeTicker(string response, string symbol) } } + private void UpdateOrder(string term, string[] data) + { + var msg = new Messages.OrderUpdate(data); + OrderDirection direction = msg.OrderAmount < 0 ? OrderDirection.Sell : OrderDirection.Buy; + Symbol symbol = Symbol.Create(msg.OrderPair.ToUpper(), SecurityType.Crypto, BrokerageMarket); + + Log.Trace(msg.ToString()); + // order new + if (term == "on") + { + // var orderEvent = new OrderEvent + //( + // orderId, symbol, DateTime.UtcNow, OrderStatus.New, + // direction, msg.OrderPrice, msg.OrderAmount, 0, "Bitfinex New Order Event" + //); + // OnOrderEvent(orderEvent); + return; + } + // todo - order update + else if (term == "ou") + { + return; + } + // order cancel + else if (term == "oc") + { + // var orderEvent = new OrderEvent + //( + // orderId, symbol, DateTime.UtcNow, OrderStatus.Canceled, + // direction, msg.OrderPrice, msg.OrderAmount, 0, "Bitfinex Cancel Order Event" + //); + //OnOrderEvent(orderEvent); + return; + } + } + + private void PopulateOrder(string[][] data) + { + foreach (var item in data) + { + var msg = new Messages.OrderUpdate(item); + Log.Trace(msg.ToString()); + } + } + private void PopulateWallet(string[][] data) { foreach (var item in data) From b35a8afc6feb54b45b27e4996ff67676b9c01d0a Mon Sep 17 00:00:00 2001 From: Sebastian Lueneburg Date: Fri, 29 Dec 2017 14:59:20 +0800 Subject: [PATCH 8/8] Nonce in auth request compatible with both ws v1.1 and v2 --- Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs b/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs index 1f782910c63b..08b8f14b4dba 100644 --- a/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs +++ b/Brokerages/Bitfinex/BitfinexBrokerage.Messaging.cs @@ -425,12 +425,14 @@ private void PopulateTrade(string term, string[] data) protected void Authenticate() { var key = ApiKey; - var payload = "AUTH" + DateTime.UtcNow.Ticks; + var authNonce = DateTime.UtcNow.Ticks; + var payload = "AUTH" + authNonce; WebSocket.Send(JsonConvert.SerializeObject(new { @event = "auth", apiKey = key, authSig = GetHexHashSignature(payload, ApiSecret), + authNonce = authNonce, authPayload = payload })); }