Skip to content

Commit ef8e45b

Browse files
Johnny A. dos Santosvslee
authored andcommitted
BL3P: Get full order book (#485)
* Add orderbook option * Add call to fetch full orderbook from BL3P * Remove configureawait from On* overrides
1 parent d91d17d commit ef8e45b

File tree

11 files changed

+198
-50
lines changed

11 files changed

+198
-50
lines changed

ExchangeSharp/API/Exchanges/BL3P/ExchangeBL3PAPI.cs

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,13 @@ public ExchangeBL3PAPI(ref string publicApiKey, ref string privateApiKey)
6262

6363
protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync()
6464
{
65-
return (await OnGetMarketSymbolsMetadataAsync().ConfigureAwait(false))
65+
return (await OnGetMarketSymbolsMetadataAsync())
6666
.Select(em => em.MarketSymbol);
6767
}
6868

6969
protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymbol)
7070
{
71-
var result = await MakeJsonRequestAsync<JObject>($"/{marketSymbol}/ticker")
72-
.ConfigureAwait(false);
71+
var result = await MakeJsonRequestAsync<JObject>($"/{marketSymbol}/ticker");
7372

7473
return await this.ParseTickerAsync(
7574
result,
@@ -80,7 +79,7 @@ protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymb
8079
baseVolumeKey: "volume.24h",
8180
timestampKey: "timestamp",
8281
timestampType: TimestampType.UnixSeconds
83-
).ConfigureAwait(false);
82+
);
8483
}
8584

8685
protected internal override Task<IEnumerable<ExchangeMarket>> OnGetMarketSymbolsMetadataAsync()
@@ -131,27 +130,7 @@ Task MessageCallback(IWebSocket _, byte[] msg)
131130
{
132131
var bl3POrderBook = JsonConvert.DeserializeObject<BL3POrderBook>(msg.ToStringFromUTF8());
133132

134-
var exchangeOrderBook = new ExchangeOrderBook
135-
{
136-
MarketSymbol = bl3POrderBook.MarketSymbol,
137-
LastUpdatedUtc = CryptoUtility.UtcNow
138-
};
139-
140-
var asks = bl3POrderBook.Asks
141-
.OrderBy(b => b.Price, exchangeOrderBook.Asks.Comparer)
142-
.Take(maxCount);
143-
foreach (var ask in asks)
144-
{
145-
exchangeOrderBook.Asks.Add(ask.Price, ask.ToExchangeOrder());
146-
}
147-
148-
var bids = bl3POrderBook.Bids
149-
.OrderBy(b => b.Price, exchangeOrderBook.Bids.Comparer)
150-
.Take(maxCount);
151-
foreach (var bid in bids)
152-
{
153-
exchangeOrderBook.Bids.Add(bid.Price, bid.ToExchangeOrder());
154-
}
133+
var exchangeOrderBook = ConvertToExchangeOrderBook(maxCount, bl3POrderBook);
155134

156135
callback(exchangeOrderBook);
157136

@@ -161,7 +140,7 @@ Task MessageCallback(IWebSocket _, byte[] msg)
161140
return new MultiWebsocketWrapper(
162141
await Task.WhenAll(
163142
marketSymbols.Select(ms => ConnectWebSocketAsync($"{ms}/orderbook", MessageCallback))
164-
).ConfigureAwait(false)
143+
)
165144
);
166145
}
167146

@@ -173,9 +152,9 @@ protected override async Task<IWebSocket> OnGetTradesWebSocketAsync(Func<KeyValu
173152
}
174153
Task MessageCallback(IWebSocket _, byte[] msg)
175154
{ // {{ "date": 1573255932, "marketplace": "BTCEUR", "price_int": 802466000, "type": "buy", "amount_int": 6193344 } }
176-
JToken token = JToken.Parse(msg.ToStringFromUTF8());
155+
var token = JToken.Parse(msg.ToStringFromUTF8());
177156
var symbol = token["marketplace"].ToStringInvariant();
178-
ExchangeTrade trade = token.ParseTrade(amountKey: "amount_int", priceKey: "price_int", typeKey: "type", timestampKey: "date",
157+
var trade = token.ParseTrade(amountKey: "amount_int", priceKey: "price_int", typeKey: "type", timestampKey: "date",
179158
timestampType: TimestampType.UnixSeconds,
180159
idKey: null, // + TODO: add Id Key when BL3P starts providing this info
181160
typeKeyIsBuyValue: "buy");
@@ -186,10 +165,37 @@ Task MessageCallback(IWebSocket _, byte[] msg)
186165
return new MultiWebsocketWrapper(
187166
await Task.WhenAll(
188167
marketSymbols.Select(ms => ConnectWebSocketAsync($"{ms}/trades", MessageCallback))
189-
).ConfigureAwait(false)
168+
)
190169
);
191170
}
192171

172+
private static ExchangeOrderBook ConvertToExchangeOrderBook(int maxCount, BL3POrderBook bl3POrderBook)
173+
{
174+
var exchangeOrderBook = new ExchangeOrderBook
175+
{
176+
MarketSymbol = bl3POrderBook.MarketSymbol,
177+
LastUpdatedUtc = CryptoUtility.UtcNow
178+
};
179+
180+
var asks = bl3POrderBook.Asks
181+
.OrderBy(b => b.Price, exchangeOrderBook.Asks.Comparer)
182+
.Take(maxCount);
183+
foreach (var ask in asks)
184+
{
185+
exchangeOrderBook.Asks.Add(ask.Price, ask.ToExchangeOrder());
186+
}
187+
188+
var bids = bl3POrderBook.Bids
189+
.OrderBy(b => b.Price, exchangeOrderBook.Bids.Comparer)
190+
.Take(maxCount);
191+
foreach (var bid in bids)
192+
{
193+
exchangeOrderBook.Bids.Add(bid.Price, bid.ToExchangeOrder());
194+
}
195+
196+
return exchangeOrderBook;
197+
}
198+
193199
protected override bool CanMakeAuthenticatedRequest(IReadOnlyDictionary<string, object> payload)
194200
{
195201
return !(PublicApiKey is null) && !(PrivateApiKey is null);
@@ -258,18 +264,31 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
258264
var resultBody = await MakeRequestAsync(
259265
$"/{order.MarketSymbol}/money/order/add",
260266
payload: data
261-
)
262-
.ConfigureAwait(false);
267+
);
263268

264269
var result = JsonConvert.DeserializeObject<BL3POrderAddResponse>(resultBody)
265270
.Except();
266271

267-
var orderDetails = await GetOrderDetailsAsync(result.OrderId, order.MarketSymbol)
268-
.ConfigureAwait(false);
272+
var orderDetails = await GetOrderDetailsAsync(result.OrderId, order.MarketSymbol);
269273

270274
return orderDetails;
271275
}
272276

277+
protected override async Task<ExchangeOrderBook> OnGetOrderBookAsync(string marketSymbol, int maxCount = 100)
278+
{
279+
if (string.IsNullOrWhiteSpace(marketSymbol))
280+
throw new ArgumentException("Value cannot be null or whitespace.", nameof(marketSymbol));
281+
282+
var resultBody = await MakeRequestAsync($"/{marketSymbol}/money/depth/full");
283+
284+
var bl3pOrderBook = JsonConvert.DeserializeObject<BL3PReponseFullOrderBook>(resultBody)
285+
.Except();
286+
287+
bl3pOrderBook.MarketSymbol??= marketSymbol;
288+
289+
return ConvertToExchangeOrderBook(maxCount, bl3pOrderBook);
290+
}
291+
273292
protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null)
274293
{
275294
if (string.IsNullOrWhiteSpace(marketSymbol))
@@ -281,14 +300,13 @@ protected override async Task OnCancelOrderAsync(string orderId, string marketSy
281300
{
282301
{"order_id", orderId}
283302
}
284-
)
285-
.ConfigureAwait(false);
303+
);
286304

287305
JsonConvert.DeserializeObject<BL3PEmptyResponse>(resultBody)
288306
.Except();
289307
}
290308

291-
public override async Task<ExchangeOrderResult> GetOrderDetailsAsync(string orderId, string marketSymbol = null)
309+
protected override async Task<ExchangeOrderResult> OnGetOrderDetailsAsync(string orderId, string marketSymbol = null)
292310
{
293311
if (string.IsNullOrWhiteSpace(marketSymbol))
294312
throw new ArgumentException("Value cannot be null or whitespace.", nameof(marketSymbol));
@@ -301,8 +319,7 @@ public override async Task<ExchangeOrderResult> GetOrderDetailsAsync(string orde
301319
var resultBody = await MakeRequestAsync(
302320
$"/{marketSymbol}/money/order/result",
303321
payload: data
304-
)
305-
.ConfigureAwait(false);
322+
);
306323

307324

308325
var result = JsonConvert.DeserializeObject<BL3POrderResultResponse>(resultBody)

ExchangeSharp/API/Exchanges/BL3P/Models/BL3POrderBook.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace ExchangeSharp.BL3P
44
{
55
// ReSharper disable once InconsistentNaming
6-
internal class BL3POrderBook
6+
internal class BL3POrderBook : BL3PResponsePayload
77
{
88
[JsonProperty("marketplace")]
99
public string MarketSymbol { get; set; }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Newtonsoft.Json;
2+
3+
namespace ExchangeSharp.BL3P
4+
{
5+
internal class BL3PReponseFullOrderBook : BL3PResponse<BL3POrderBook>
6+
{
7+
[JsonConverter(typeof(BL3PResponseConverter<BL3POrderBook>))]
8+
protected override BL3PResponsePayload Data { get; set; }
9+
}
10+
}

ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,20 @@ protected virtual async Task<IEnumerable<KeyValuePair<string, ExchangeTicker>>>
6262
return tickers;
6363
}
6464

65-
protected virtual async Task<IEnumerable<KeyValuePair<string, ExchangeOrderBook>>> OnGetOrderBooksAsync(int maxCount = 100)
66-
{
67-
List<KeyValuePair<string, ExchangeOrderBook>> books = new List<KeyValuePair<string, ExchangeOrderBook>>();
68-
var marketSymbols = await GetMarketSymbolsAsync();
69-
foreach (string marketSymbol in marketSymbols)
70-
{
71-
var book = await GetOrderBookAsync(marketSymbol);
72-
books.Add(new KeyValuePair<string, ExchangeOrderBook>(marketSymbol, book));
73-
}
74-
return books;
65+
protected virtual async Task<IEnumerable<KeyValuePair<string, ExchangeOrderBook>>> OnGetOrderBooksAsync(
66+
int maxCount = 100
67+
)
68+
{
69+
var marketSymbols = await GetMarketSymbolsAsync();
70+
var orderBooks = await Task.WhenAll(
71+
marketSymbols.Select(async ms =>
72+
{
73+
var orderBook = await GetOrderBookAsync(ms, maxCount);
74+
orderBook.MarketSymbol ??= ms;
75+
return orderBook;
76+
})
77+
);
78+
return orderBooks.ToDictionary(k => k.MarketSymbol, v => v);
7579
}
7680

7781
protected virtual async Task<IEnumerable<ExchangeTrade>> OnGetRecentTradesAsync(string marketSymbol)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeEditing/Localization/Localizable/@EntryValue">No</s:String>
3+
</wpf:ResourceDictionary>

ExchangeSharpConsole/Constants.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace ExchangeSharpConsole
2+
{
3+
public static class Constants
4+
{
5+
public const string DefaultKeyPath = "keys.bin";
6+
}
7+
}

ExchangeSharpConsole/Options/BaseOption.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,5 +214,27 @@ string[] apiSymbols
214214
);
215215
}
216216
}
217+
218+
protected void PrintOrderBook(ExchangeOrderBook orderBook)
219+
{
220+
Console.WriteLine($"{"BID",-22} | {"ASK",22}");
221+
222+
Console.WriteLine("---");
223+
224+
var length = orderBook.Asks.Count;
225+
226+
if (orderBook.Bids.Count > length)
227+
{
228+
length = orderBook.Bids.Count;
229+
}
230+
231+
for (var i = 0; i < length; i++)
232+
{
233+
var (_, ask) = orderBook.Asks.ElementAtOrDefault(i);
234+
var (_, bid) = orderBook.Bids.ElementAtOrDefault(i);
235+
Console.WriteLine($"{bid.Price,10} ({bid.Amount,9:N2}) | " +
236+
$"{ask.Price,10} ({ask.Amount,9:N})");
237+
}
238+
}
217239
}
218240
}

ExchangeSharpConsole/Options/Interfaces/IOptionWithKey.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace ExchangeSharpConsole.Options.Interfaces
44
{
55
public interface IOptionWithKey
66
{
7-
[Option('k', "key", Default = "keys.bin",
7+
[Option('k', "key", Default = Constants.DefaultKeyPath,
88
HelpText = "Path to key file (generated with the key utility).")]
99
string KeyPath { get; set; }
1010
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using CommandLine;
2+
3+
namespace ExchangeSharpConsole.Options.Interfaces
4+
{
5+
public interface IOptionWithMaximum
6+
{
7+
[Option("max", Default = 10, HelpText = "Sets the maximum.")]
8+
int Max { get; set; }
9+
}
10+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using CommandLine;
6+
using ExchangeSharp;
7+
using ExchangeSharpConsole.Options.Interfaces;
8+
9+
namespace ExchangeSharpConsole.Options
10+
{
11+
[Verb("orderbook", HelpText = "Prints the order book from an exchange.")]
12+
public class OrderBookOption : BaseOption,
13+
IOptionPerExchange, IOptionWithMultipleMarketSymbol, IOptionWithMaximum, IOptionWithKey
14+
{
15+
public override async Task RunCommand()
16+
{
17+
using var api = GetExchangeInstance(ExchangeName);
18+
19+
if (!string.IsNullOrWhiteSpace(KeyPath)
20+
&& !KeyPath.Equals(Constants.DefaultKeyPath, StringComparison.Ordinal))
21+
{
22+
api.LoadAPIKeys(KeyPath);
23+
}
24+
25+
var marketSymbols = MarketSymbols.ToArray();
26+
27+
var orderBooks = await GetOrderBooks(marketSymbols, api);
28+
29+
foreach (var (marketSymbol, orderBook) in orderBooks)
30+
{
31+
Console.WriteLine($"Order Book for market: {marketSymbol} {orderBook}");
32+
PrintOrderBook(orderBook);
33+
Console.WriteLine();
34+
}
35+
}
36+
37+
private async Task<IEnumerable<KeyValuePair<string, ExchangeOrderBook>>> GetOrderBooks(
38+
string[] marketSymbols,
39+
IExchangeAPI api
40+
)
41+
{
42+
IEnumerable<KeyValuePair<string, ExchangeOrderBook>> orderBooks;
43+
44+
if (marketSymbols.Length == 0)
45+
{
46+
orderBooks = await api.GetOrderBooksAsync(Max);
47+
}
48+
else
49+
{
50+
var orderBooksList = await Task.WhenAll(
51+
marketSymbols.Select(async ms =>
52+
{
53+
var orderBook = await api.GetOrderBookAsync(ms, Max);
54+
55+
orderBook.MarketSymbol ??= ms;
56+
57+
return orderBook;
58+
})
59+
);
60+
orderBooks = orderBooksList.ToDictionary(k => k.MarketSymbol, v => v);
61+
}
62+
63+
return orderBooks;
64+
}
65+
66+
public string ExchangeName { get; set; }
67+
68+
public IEnumerable<string> MarketSymbols { get; set; }
69+
70+
public int Max { get; set; }
71+
72+
public string KeyPath { get; set; }
73+
}
74+
}

0 commit comments

Comments
 (0)