Skip to content

Commit 73f7032

Browse files
authored
BittrexWS update (#590)
+ RestApi minor bug fixed + WS updated for reversed MarketName
1 parent 96fc005 commit 73f7032

File tree

2 files changed

+150
-137
lines changed

2 files changed

+150
-137
lines changed

src/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,25 @@ namespace ExchangeSharp
1414
{
1515
using Newtonsoft.Json;
1616
using Newtonsoft.Json.Linq;
17-
using System;
18-
using System.Collections.Generic;
19-
using System.Linq;
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Linq;
20+
using System.Net;
2021
using System.Security.Cryptography;
2122
using System.Text;
2223
using System.Threading.Tasks;
24+
using System.Web;
2325

24-
public sealed partial class ExchangeBittrexAPI : ExchangeAPI
26+
public sealed partial class ExchangeBittrexAPI : ExchangeAPI
2527
{
2628
public override string BaseUrl { get; set; } = "https://api.bittrex.com/v3";
27-
public ExchangeBittrexAPI()
29+
private ExchangeBittrexAPI()
2830
{
2931
RateLimit = new RateGate(60, TimeSpan.FromSeconds(60));
3032
RequestContentType = "application/json";
31-
MarketSymbolIsReversed = true;
32-
WebSocketOrderBookType = WebSocketOrderBookType.DeltasOnly;
33+
MarketSymbolIsReversed = false;
34+
WebSocketOrderBookType = WebSocketOrderBookType.FullBookAlways;
35+
3336
}
3437

3538
#region Utilities
@@ -148,7 +151,7 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti
148151
if (request.Method == "POST")
149152
await CryptoUtility.WriteToRequestAsync(request, JsonConvert.SerializeObject(payload));
150153
}
151-
Console.WriteLine(request.RequestUri);
154+
//Console.WriteLine(request.RequestUri);
152155
//return base.ProcessRequestAsync(request, payload);
153156
}
154157
#endregion
@@ -227,7 +230,7 @@ protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymb
227230
{
228231
JToken ticker = await MakeJsonRequestAsync<JToken>("/markets/" + marketSymbol + "/ticker");
229232
//NOTE: Bittrex uses the term "BaseVolume" when referring to the QuoteCurrencyVolume
230-
return await this.ParseTickerAsync(ticker[0], marketSymbol, "askRate", "bidRate", "lastTradeRate", "volume", "quoteVolume", "updatedAt", TimestampType.Iso8601);
233+
return await this.ParseTickerAsync(ticker, marketSymbol, "askRate", "bidRate", "lastTradeRate", "volume", "quoteVolume", "updatedAt", TimestampType.Iso8601);
231234
}
232235

233236
protected override async Task<IEnumerable<KeyValuePair<string, ExchangeTicker>>> OnGetTickersAsync()
@@ -365,7 +368,10 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
365368
orderParams.Add("type", order.OrderType == ExchangeSharp.OrderType.Market ? "MARKET" : "LIMIT");
366369
orderParams.Add("quantity", orderAmount);
367370
if (order.OrderType == ExchangeSharp.OrderType.Limit)
371+
{
368372
orderParams.Add("limit", orderPrice);
373+
//orderParams.Add("timeInForce", "GOOD_TIL_CANCELLED");
374+
}
369375

370376
foreach (KeyValuePair<string, object> kv in order.ExtraParameters)
371377
{

src/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs

Lines changed: 135 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
MIT LICENSE
33
44
Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com
@@ -30,67 +30,73 @@ The above copyright notice and this permission notice shall be included in all c
3030

3131
namespace ExchangeSharp
3232
{
33-
public partial class ExchangeBittrexAPI
34-
{
33+
public partial class ExchangeBittrexAPI
34+
{
3535

3636
#if HAS_SIGNALR
3737

38-
/// <summary>
39-
/// Ideally this would be one instance per exchange instance, but Bittrex simply does not work if you request multiple end points over a single hub connection, sigh...
40-
/// </summary>
41-
public sealed class BittrexWebSocketManager : SignalrManager
42-
{
43-
public BittrexWebSocketManager() : base("https://socket.bittrex.com/signalr", "c2")
44-
{
45-
FunctionNamesToFullNames["uS"] = "SubscribeToSummaryDeltas";
46-
FunctionNamesToFullNames["uE"] = "SubscribeToExchangeDeltas";
47-
}
38+
/// <summary>
39+
/// Ideally this would be one instance per exchange instance, but Bittrex simply does not work if you request multiple end points over a single hub connection, sigh...
40+
/// </summary>
41+
public sealed class BittrexWebSocketManager : SignalrManager
42+
{
43+
public BittrexWebSocketManager() : base("https://socket.bittrex.com/signalr", "c2")
44+
{
45+
FunctionNamesToFullNames["uS"] = "SubscribeToSummaryDeltas";
46+
FunctionNamesToFullNames["uE"] = "SubscribeToExchangeDeltas";
47+
}
4848

49-
/// <summary>
50-
/// Subscribe to all market summaries
51-
/// </summary>
52-
/// <param name="callback">Callback</param>
53-
/// <param name="marketSymbols">Symbols</param>
54-
/// <returns>IDisposable to close the socket</returns>
55-
public async Task<IWebSocket> SubscribeToSummaryDeltasAsync(Func<string, Task> callback, params string[] marketSymbols)
56-
{
57-
SignalrManager.SignalrSocketConnection conn = new SignalrManager.SignalrSocketConnection(this);
58-
await conn.OpenAsync("uS", callback);
59-
return conn;
60-
}
49+
/// <summary>
50+
/// Subscribe to all market summaries
51+
/// </summary>
52+
/// <param name="callback">Callback</param>
53+
/// <param name="marketSymbols">Symbols</param>
54+
/// <returns>IDisposable to close the socket</returns>
55+
public async Task<IWebSocket> SubscribeToSummaryDeltasAsync(Func<string, Task> callback, params string[] marketSymbols)
56+
{
57+
SignalrManager.SignalrSocketConnection conn = new SignalrManager.SignalrSocketConnection(this);
58+
await conn.OpenAsync("uS", callback);
59+
return conn;
60+
}
6161

62-
/// <summary>
63-
/// Subscribe to order book updates
64-
/// </summary>
65-
/// <param name="callback">Callback</param>
66-
/// <param name="marketSymbols">The market symbols to subscribe to</param>
67-
/// <returns>IDisposable to close the socket</returns>
68-
public async Task<IWebSocket> SubscribeToExchangeDeltasAsync(Func<string, Task> callback, params string[] marketSymbols)
69-
{
70-
SignalrManager.SignalrSocketConnection conn = new SignalrManager.SignalrSocketConnection(this);
71-
List<object[]> paramList = new List<object[]>();
72-
foreach (string marketSymbol in marketSymbols)
73-
{
74-
paramList.Add(new object[] { marketSymbol });
75-
}
76-
await conn.OpenAsync("uE", callback, 0, paramList.ToArray());
77-
return conn;
78-
}
79-
}
62+
/// <summary>
63+
/// Subscribe to order book updates
64+
/// </summary>
65+
/// <param name="callback">Callback</param>
66+
/// <param name="marketSymbols">The market symbols to subscribe to</param>
67+
/// <returns>IDisposable to close the socket</returns>
68+
public async Task<IWebSocket> SubscribeToExchangeDeltasAsync(Func<string, Task> callback, params string[] marketSymbols)
69+
{
70+
SignalrManager.SignalrSocketConnection conn = new SignalrManager.SignalrSocketConnection(this);
71+
List<object[]> paramList = new List<object[]>();
72+
foreach (string marketSymbol in marketSymbols)
73+
{
74+
paramList.Add(new object[] { ReverseMarketNameForWS((marketSymbol).ToStringInvariant()) });
75+
}
76+
await conn.OpenAsync("uE", callback, 0, paramList.ToArray());
77+
return conn;
78+
}
79+
}
8080

8181
private BittrexWebSocketManager webSocket;
8282

83-
protected override async Task<IWebSocket> OnGetTickersWebSocketAsync(Action<IReadOnlyCollection<KeyValuePair<string, ExchangeTicker>>> callback, params string[] marketSymbols)
84-
{
85-
HashSet<string> filter = new HashSet<string>();
86-
foreach (string marketSymbol in marketSymbols)
87-
{
88-
filter.Add(marketSymbol);
89-
}
90-
async Task innerCallback(string json)
91-
{
92-
#region sample json
93-
/*
83+
public static string ReverseMarketNameForWS(string WebSocketFeedMarketName)
84+
{
85+
var pair = WebSocketFeedMarketName.Split('-');
86+
return (pair[1] + '-' + pair[0]).ToUpperInvariant();
87+
}
88+
89+
protected override async Task<IWebSocket> OnGetTickersWebSocketAsync(Action<IReadOnlyCollection<KeyValuePair<string, ExchangeTicker>>> callback, params string[] marketSymbols)
90+
{
91+
HashSet<string> filter = new HashSet<string>();
92+
foreach (string marketSymbol in marketSymbols)
93+
{
94+
filter.Add(marketSymbol);
95+
}
96+
async Task innerCallback(string json)
97+
{
98+
#region sample json
99+
/*
94100
{
95101
Nonce : int,
96102
Deltas :
@@ -115,60 +121,62 @@ async Task innerCallback(string json)
115121
*/
116122
#endregion
117123

118-
var freshTickers = new Dictionary<string, ExchangeTicker>(StringComparer.OrdinalIgnoreCase);
119-
JToken token = JToken.Parse(json);
120-
token = token["D"];
121-
foreach (JToken ticker in token)
122-
{
123-
string marketName = ticker["M"].ToStringInvariant();
124-
if (filter.Count != 0 && !filter.Contains(marketName))
125-
{
126-
continue;
127-
}
128-
var (baseCurrency, quoteCurrency) = await ExchangeMarketSymbolToCurrenciesAsync(marketName);
129-
decimal last = ticker["l"].ConvertInvariant<decimal>();
130-
decimal ask = ticker["A"].ConvertInvariant<decimal>();
131-
decimal bid = ticker["B"].ConvertInvariant<decimal>();
132-
decimal baseCurrencyVolume = ticker["V"].ConvertInvariant<decimal>();
133-
decimal quoteCurrencyVolume = ticker["m"].ConvertInvariant<decimal>();//NOTE: Bittrex uses the term BaseVolume when referring to QuoteCurrencyVolume
134-
DateTime timestamp = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(ticker["T"].ConvertInvariant<long>());
135-
var t = new ExchangeTicker
136-
{
137-
MarketSymbol = marketName,
138-
Ask = ask,
139-
Bid = bid,
140-
Last = last,
141-
Volume = new ExchangeVolume
142-
{
143-
BaseCurrencyVolume = baseCurrencyVolume,
144-
BaseCurrency = baseCurrency,
145-
QuoteCurrencyVolume = quoteCurrencyVolume,
146-
QuoteCurrency = quoteCurrency,
147-
Timestamp = timestamp
148-
}
149-
};
150-
freshTickers[marketName] = t;
151-
}
152-
callback(freshTickers);
153-
}
154-
return await new BittrexWebSocketManager().SubscribeToSummaryDeltasAsync(innerCallback, marketSymbols);
155-
}
124+
var freshTickers = new Dictionary<string, ExchangeTicker>(StringComparer.OrdinalIgnoreCase);
125+
JToken token = JToken.Parse(json);
126+
token = token["D"];
127+
foreach (JToken ticker in token)
128+
{
156129

157-
protected override async Task<IWebSocket> OnGetDeltaOrderBookWebSocketAsync
158-
(
159-
Action<ExchangeOrderBook> callback,
160-
int maxCount = 20,
161-
params string[] marketSymbols
162-
)
163-
{
164-
if (marketSymbols == null || marketSymbols.Length == 0)
165-
{
166-
marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
167-
}
168-
Task innerCallback(string json)
169-
{
170-
#region sample json
171-
/*
130+
131+
string marketName = ReverseMarketNameForWS(ticker["M"].ToStringInvariant());
132+
if (filter.Count != 0 && !filter.Contains(marketName))
133+
{
134+
continue;
135+
}
136+
var (baseCurrency, quoteCurrency) = await ExchangeMarketSymbolToCurrenciesAsync(marketName);
137+
decimal last = ticker["l"].ConvertInvariant<decimal>();
138+
decimal ask = ticker["A"].ConvertInvariant<decimal>();
139+
decimal bid = ticker["B"].ConvertInvariant<decimal>();
140+
decimal baseCurrencyVolume = ticker["V"].ConvertInvariant<decimal>();
141+
decimal quoteCurrencyVolume = ticker["m"].ConvertInvariant<decimal>();//NOTE: Bittrex uses the term BaseVolume when referring to QuoteCurrencyVolume
142+
DateTime timestamp = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(ticker["T"].ConvertInvariant<long>());
143+
var t = new ExchangeTicker
144+
{
145+
MarketSymbol = marketName,
146+
Ask = ask,
147+
Bid = bid,
148+
Last = last,
149+
Volume = new ExchangeVolume
150+
{
151+
BaseCurrencyVolume = baseCurrencyVolume,
152+
BaseCurrency = baseCurrency,
153+
QuoteCurrencyVolume = quoteCurrencyVolume,
154+
QuoteCurrency = quoteCurrency,
155+
Timestamp = timestamp
156+
}
157+
};
158+
freshTickers[marketName] = t;
159+
}
160+
callback(freshTickers);
161+
}
162+
return await new BittrexWebSocketManager().SubscribeToSummaryDeltasAsync(innerCallback, marketSymbols);
163+
}
164+
165+
protected override async Task<IWebSocket> OnGetDeltaOrderBookWebSocketAsync
166+
(
167+
Action<ExchangeOrderBook> callback,
168+
int maxCount = 20,
169+
params string[] marketSymbols
170+
)
171+
{
172+
if (marketSymbols == null || marketSymbols.Length == 0)
173+
{
174+
marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
175+
}
176+
Task innerCallback(string json)
177+
{
178+
#region sample json
179+
/*
172180
{
173181
MarketName : string,
174182
Nonce : int,
@@ -200,27 +208,26 @@ Task innerCallback(string json)
200208
]
201209
}
202210
*/
203-
#endregion
204-
205-
var ordersUpdates = JsonConvert.DeserializeObject<BittrexStreamUpdateExchangeState>(json);
206-
var book = new ExchangeOrderBook();
207-
foreach (BittrexStreamOrderBookUpdateEntry ask in ordersUpdates.Sells)
208-
{
209-
var depth = new ExchangeOrderPrice { Price = ask.Rate, Amount = ask.Quantity };
210-
book.Asks[depth.Price] = depth;
211-
}
212-
213-
foreach (BittrexStreamOrderBookUpdateEntry bid in ordersUpdates.Buys)
214-
{
215-
var depth = new ExchangeOrderPrice { Price = bid.Rate, Amount = bid.Quantity };
216-
book.Bids[depth.Price] = depth;
217-
}
211+
#endregion
218212

219-
book.MarketSymbol = ordersUpdates.MarketName;
220-
book.SequenceId = ordersUpdates.Nonce;
221-
callback(book);
222-
return Task.CompletedTask;
223-
}
213+
var ordersUpdates = JsonConvert.DeserializeObject<BittrexStreamUpdateExchangeState>(json);
214+
var book = new ExchangeOrderBook();
215+
foreach (BittrexStreamOrderBookUpdateEntry ask in ordersUpdates.Sells)
216+
{
217+
var depth = new ExchangeOrderPrice { Price = ask.Rate, Amount = ask.Quantity };
218+
book.Asks[depth.Price] = depth;
219+
}
220+
foreach (BittrexStreamOrderBookUpdateEntry bid in ordersUpdates.Buys)
221+
{
222+
var depth = new ExchangeOrderPrice { Price = bid.Rate, Amount = bid.Quantity };
223+
book.Bids[depth.Price] = depth;
224+
}
225+
book.MarketSymbol = ReverseMarketNameForWS(ordersUpdates.MarketName).ToUpperInvariant();
226+
book.SequenceId = ordersUpdates.Nonce;
227+
book.LastUpdatedUtc = DateTime.UtcNow;
228+
callback(book);
229+
return Task.CompletedTask;
230+
}
224231

225232
return await new BittrexWebSocketManager().SubscribeToExchangeDeltasAsync(innerCallback, marketSymbols);
226233
}
@@ -236,7 +243,7 @@ async Task innerCallback(string json)
236243
var ordersUpdates = JsonConvert.DeserializeObject<BittrexStreamUpdateExchangeState>(json);
237244
foreach (var fill in ordersUpdates.Fills)
238245
{
239-
await callback(new KeyValuePair<string, ExchangeTrade>(ordersUpdates.MarketName, new ExchangeTrade()
246+
await callback(new KeyValuePair<string, ExchangeTrade>(ReverseMarketNameForWS(ordersUpdates.MarketName), new ExchangeTrade()
240247
{
241248
Amount = fill.Quantity,
242249
// Bittrex doesn't currently send out FillId on socket.bittrex.com, only beta.bittrex.com, but this will be ready when they start

0 commit comments

Comments
 (0)