Skip to content

Commit 72bf163

Browse files
authored
Merge WebSocketAsync into master (#447)
* Start work refactoring example and web socket calls to async The console example and web socket methods were not using task properly. This is the start of work to fix that. * Fix Bittrex web socket not re-adding the listener * Missed a line from last checkin * Ignore publish profiles * Move rest of example to async and logger calls * Catch errors when the connection goes down Connection re-connect will be initiated, the errors should not crash out of the method and should only log info messages * Update definitions * Just in case... * Did not mean to add 3rd condition * Add additional disposed check * Ensure Async method for all task return values Ensure Async naming convention for anything return a Task * Eradicate all .Sync calls * Fix Dispose method, update readme
1 parent 324e7f9 commit 72bf163

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+787
-672
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,5 @@ __pycache__/
263263
# Trader binary and log files
264264
*.bin
265265
*.log*
266-
ExchangeSharpConsole/Properties/launchSettings.json
266+
launchSettings.json
267+
**/PublishProfiles/*

CONTRIBUTING.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ Please follow these coding guidelines...
1010
- Only implement async methods as a general rule. Synchronous calls can be done by using the Sync extension method in ```CryptoUtility```. Saves a lot of duplicate code.
1111
- Use CryptoUtility.UtcNow instead of DateTime.UtcNow. This makes it easy to mock the date and time for unit or integration tests.
1212
- Use CryptoUtility.UnixTimeStampToDateTimeSeconds and CryptoUtility.UnixTimestampFromDateTimeSeconds, etc. for date conversions, keep all date in UTC.
13-
- Follow these code style guidelines please (we're not monsters):
13+
- Please follow these code style guidelines please (we're not monsters):
1414
- Tabs for indent.
1515
- Curly braces on separate lines.
16-
- Wrap all if statements with curly braces, makes debug and set breakpoints much easier, along with adding new code to the if statement block.
16+
- Wrap if, else if, and any loops or control logic with curly braces. Avoid `if (something) doSomething();` on same line or next line without curly braces.
17+
- Append 'Async' to the end of the name of any method returning a Task - except for unit and integration test methods.
18+
- Avoid using `.Sync()` or `.RunSynchronously()` - all code should use Tasks and/or async / await.
1719

1820
When creating a new Exchange API, please do the following:
1921
- For reference comparisons, https://github.com/ccxt/ccxt is a good project to compare against when creating a new exchange. Use node.js in Visual Studio to debug through the code.

ExchangeSharp/API/Common/BaseAPI.cs

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -313,12 +313,12 @@ public async Task<object> GenerateNonceAsync()
313313
{
314314
await OnGetNonceOffset();
315315
}
316+
317+
object nonce;
316318

317-
lock (this)
319+
while (true)
318320
{
319-
object nonce;
320-
321-
while (true)
321+
lock (this)
322322
{
323323
// some API (Binance) have a problem with requests being after server time, subtract of offset can help
324324
DateTime now = CryptoUtility.UtcNow - NonceOffset;
@@ -373,28 +373,28 @@ public async Task<object> GenerateNonceAsync()
373373

374374
case NonceStyle.Int32File:
375375
case NonceStyle.Int64File:
376-
{
377-
// why an API would use a persistent incrementing counter for nonce is beyond me, ticks is so much better with a sliding window...
378-
// making it required to increment by 1 is also a pain - especially when restarting a process or rebooting.
379-
string tempFile = Path.Combine(Path.GetTempPath(), PublicApiKey.ToUnsecureString() + ".nonce");
380-
if (!File.Exists(tempFile))
381-
{
382-
File.WriteAllText(tempFile, "0");
383-
}
384-
unchecked
385376
{
386-
long longNonce = File.ReadAllText(tempFile).ConvertInvariant<long>() + 1;
387-
long maxValue = (NonceStyle == NonceStyle.Int32File ? int.MaxValue : long.MaxValue);
388-
if (longNonce < 1 || longNonce > maxValue)
377+
// why an API would use a persistent incrementing counter for nonce is beyond me, ticks is so much better with a sliding window...
378+
// making it required to increment by 1 is also a pain - especially when restarting a process or rebooting.
379+
string tempFile = Path.Combine(Path.GetTempPath(), PublicApiKey.ToUnsecureString() + ".nonce");
380+
if (!File.Exists(tempFile))
381+
{
382+
File.WriteAllText(tempFile, "0");
383+
}
384+
unchecked
389385
{
390-
throw new APIException($"Nonce {longNonce.ToStringInvariant()} is out of bounds, valid ranges are 1 to {maxValue.ToStringInvariant()}, " +
391-
$"please regenerate new API keys. Please contact {Name} API support and ask them to change to a sensible nonce algorithm.");
386+
long longNonce = File.ReadAllText(tempFile).ConvertInvariant<long>() + 1;
387+
long maxValue = (NonceStyle == NonceStyle.Int32File ? int.MaxValue : long.MaxValue);
388+
if (longNonce < 1 || longNonce > maxValue)
389+
{
390+
throw new APIException($"Nonce {longNonce.ToStringInvariant()} is out of bounds, valid ranges are 1 to {maxValue.ToStringInvariant()}, " +
391+
$"please regenerate new API keys. Please contact {Name} API support and ask them to change to a sensible nonce algorithm.");
392+
}
393+
File.WriteAllText(tempFile, longNonce.ToStringInvariant());
394+
nonce = longNonce;
392395
}
393-
File.WriteAllText(tempFile, longNonce.ToStringInvariant());
394-
nonce = longNonce;
396+
break;
395397
}
396-
break;
397-
}
398398

399399
case NonceStyle.ExpiresUnixMilliseconds:
400400
nonce = (long)now.UnixTimestampFromDateTimeMilliseconds();
@@ -415,13 +415,13 @@ public async Task<object> GenerateNonceAsync()
415415
lastNonce = convertedNonce;
416416
break;
417417
}
418-
419-
// wait 1 millisecond for a new nonce
420-
Task.Delay(1).Sync();
421418
}
422419

423-
return nonce;
420+
// wait 1 millisecond for a new nonce
421+
await Task.Delay(1);
424422
}
423+
424+
return nonce;
425425
}
426426

427427
/// <summary>
@@ -496,7 +496,7 @@ public async Task<T> MakeJsonRequestAsync<T>(string url, string baseUrl = null,
496496
/// <param name="messageCallback">Callback for messages</param>
497497
/// <param name="connectCallback">Connect callback</param>
498498
/// <returns>Web socket - dispose of the wrapper to shutdown the socket</returns>
499-
public IWebSocket ConnectWebSocket
499+
public Task<IWebSocket> ConnectWebSocketAsync
500500
(
501501
string url,
502502
Func<IWebSocket, byte[], Task> messageCallback,
@@ -524,7 +524,7 @@ public IWebSocket ConnectWebSocket
524524
wrapper.Disconnected += disconnectCallback;
525525
}
526526
wrapper.Start();
527-
return wrapper;
527+
return Task.FromResult<IWebSocket>(wrapper);
528528
}
529529

530530
/// <summary>

ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ static ExchangeBinanceAPI()
4444
};
4545
}
4646

47-
private string GetWebSocketStreamUrlForSymbols(string suffix, params string[] marketSymbols)
47+
private async Task<string> GetWebSocketStreamUrlForSymbolsAsync(string suffix, params string[] marketSymbols)
4848
{
4949
if (marketSymbols == null || marketSymbols.Length == 0)
5050
{
51-
marketSymbols = GetMarketSymbolsAsync().Sync().ToArray();
51+
marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
5252
}
5353

5454
StringBuilder streams = new StringBuilder("/stream?streams=");
@@ -74,21 +74,21 @@ public ExchangeBinanceAPI()
7474
WebSocketOrderBookType = WebSocketOrderBookType.DeltasOnly;
7575
}
7676

77-
public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol)
77+
public override Task<string> ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol)
7878
{
7979
// All pairs in Binance end with BTC, ETH, BNB or USDT
8080
if (marketSymbol.EndsWith("BTC") || marketSymbol.EndsWith("ETH") || marketSymbol.EndsWith("BNB"))
8181
{
8282
string baseSymbol = marketSymbol.Substring(marketSymbol.Length - 3);
83-
return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator);
83+
return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator);
8484
}
8585
if (marketSymbol.EndsWith("USDT"))
8686
{
8787
string baseSymbol = marketSymbol.Substring(marketSymbol.Length - 4);
88-
return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator);
88+
return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator);
8989
}
9090

91-
return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(marketSymbol.Substring(0, marketSymbol.Length - 3) + GlobalMarketSymbolSeparator + (marketSymbol.Substring(marketSymbol.Length - 3, 3)), GlobalMarketSymbolSeparator);
91+
return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync(marketSymbol.Substring(0, marketSymbol.Length - 3) + GlobalMarketSymbolSeparator + (marketSymbol.Substring(marketSymbol.Length - 3, 3)), GlobalMarketSymbolSeparator);
9292
}
9393

9494
/// <summary>
@@ -225,7 +225,7 @@ protected override async Task<IReadOnlyDictionary<string, ExchangeCurrency>> OnG
225225
protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymbol)
226226
{
227227
JToken obj = await MakeJsonRequestAsync<JToken>("/ticker/24hr?symbol=" + marketSymbol);
228-
return ParseTicker(marketSymbol, obj);
228+
return await ParseTickerAsync(marketSymbol, obj);
229229
}
230230

231231
protected override async Task<IEnumerable<KeyValuePair<string, ExchangeTicker>>> OnGetTickersAsync()
@@ -236,32 +236,31 @@ protected override async Task<IEnumerable<KeyValuePair<string, ExchangeTicker>>>
236236
foreach (JToken child in obj)
237237
{
238238
marketSymbol = child["symbol"].ToStringInvariant();
239-
tickers.Add(new KeyValuePair<string, ExchangeTicker>(marketSymbol, ParseTicker(marketSymbol, child)));
239+
tickers.Add(new KeyValuePair<string, ExchangeTicker>(marketSymbol, await ParseTickerAsync(marketSymbol, child)));
240240
}
241241
return tickers;
242242
}
243243

244-
protected override IWebSocket OnGetTickersWebSocket(Action<IReadOnlyCollection<KeyValuePair<string, ExchangeTicker>>> callback, params string[] symbols)
244+
protected override Task<IWebSocket> OnGetTickersWebSocketAsync(Action<IReadOnlyCollection<KeyValuePair<string, ExchangeTicker>>> callback, params string[] symbols)
245245
{
246-
return ConnectWebSocket("/stream?streams=!ticker@arr", (_socket, msg) =>
246+
return ConnectWebSocketAsync("/stream?streams=!ticker@arr", async (_socket, msg) =>
247247
{
248248
JToken token = JToken.Parse(msg.ToStringFromUTF8());
249249
List<KeyValuePair<string, ExchangeTicker>> tickerList = new List<KeyValuePair<string, ExchangeTicker>>();
250250
ExchangeTicker ticker;
251251
foreach (JToken childToken in token["data"])
252252
{
253-
ticker = ParseTickerWebSocket(childToken);
253+
ticker = await ParseTickerWebSocketAsync(childToken);
254254
tickerList.Add(new KeyValuePair<string, ExchangeTicker>(ticker.MarketSymbol, ticker));
255255
}
256256
if (tickerList.Count != 0)
257257
{
258258
callback(tickerList);
259259
}
260-
return Task.CompletedTask;
261260
});
262261
}
263262

264-
protected override IWebSocket OnGetTradesWebSocket(Func<KeyValuePair<string, ExchangeTrade>, Task> callback, params string[] marketSymbols)
263+
protected override async Task<IWebSocket> OnGetTradesWebSocketAsync(Func<KeyValuePair<string, ExchangeTrade>, Task> callback, params string[] marketSymbols)
265264
{
266265
/*
267266
{
@@ -281,10 +280,10 @@ protected override IWebSocket OnGetTradesWebSocket(Func<KeyValuePair<string, Exc
281280

282281
if (marketSymbols == null || marketSymbols.Length == 0)
283282
{
284-
marketSymbols = GetMarketSymbolsAsync().Sync().ToArray();
283+
marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
285284
}
286-
string url = GetWebSocketStreamUrlForSymbols("@aggTrade", marketSymbols);
287-
return ConnectWebSocket(url, async (_socket, msg) =>
285+
string url = await GetWebSocketStreamUrlForSymbolsAsync("@aggTrade", marketSymbols);
286+
return await ConnectWebSocketAsync(url, async (_socket, msg) =>
288287
{
289288
JToken token = JToken.Parse(msg.ToStringFromUTF8());
290289
string name = token["stream"].ToStringInvariant();
@@ -297,14 +296,14 @@ protected override IWebSocket OnGetTradesWebSocket(Func<KeyValuePair<string, Exc
297296
});
298297
}
299298

300-
protected override IWebSocket OnGetDeltaOrderBookWebSocket(Action<ExchangeOrderBook> callback, int maxCount = 20, params string[] marketSymbols)
299+
protected override async Task<IWebSocket> OnGetDeltaOrderBookWebSocketAsync(Action<ExchangeOrderBook> callback, int maxCount = 20, params string[] marketSymbols)
301300
{
302301
if (marketSymbols == null || marketSymbols.Length == 0)
303302
{
304-
marketSymbols = GetMarketSymbolsAsync().Sync().ToArray();
303+
marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
305304
}
306305
string combined = string.Join("/", marketSymbols.Select(s => this.NormalizeMarketSymbol(s).ToLowerInvariant() + "@depth@100ms"));
307-
return ConnectWebSocket($"/stream?streams={combined}", (_socket, msg) =>
306+
return await ConnectWebSocketAsync($"/stream?streams={combined}", (_socket, msg) =>
308307
{
309308
string json = msg.ToStringFromUTF8();
310309
var update = JsonConvert.DeserializeObject<MultiDepthStream>(json);
@@ -786,16 +785,16 @@ private bool ParseMarketStatus(string status)
786785
return isActive;
787786
}
788787

789-
private ExchangeTicker ParseTicker(string symbol, JToken token)
788+
private async Task<ExchangeTicker> ParseTickerAsync(string symbol, JToken token)
790789
{
791790
// {"priceChange":"-0.00192300","priceChangePercent":"-4.735","weightedAvgPrice":"0.03980955","prevClosePrice":"0.04056700","lastPrice":"0.03869000","lastQty":"0.69300000","bidPrice":"0.03858500","bidQty":"38.35000000","askPrice":"0.03869000","askQty":"31.90700000","openPrice":"0.04061300","highPrice":"0.04081900","lowPrice":"0.03842000","volume":"128015.84300000","quoteVolume":"5096.25362239","openTime":1512403353766,"closeTime":1512489753766,"firstId":4793094,"lastId":4921546,"count":128453}
792-
return this.ParseTicker(token, symbol, "askPrice", "bidPrice", "lastPrice", "volume", "quoteVolume", "closeTime", TimestampType.UnixMilliseconds);
791+
return await this.ParseTickerAsync(token, symbol, "askPrice", "bidPrice", "lastPrice", "volume", "quoteVolume", "closeTime", TimestampType.UnixMilliseconds);
793792
}
794793

795-
private ExchangeTicker ParseTickerWebSocket(JToken token)
794+
private async Task<ExchangeTicker> ParseTickerWebSocketAsync(JToken token)
796795
{
797796
string marketSymbol = token["s"].ToStringInvariant();
798-
return this.ParseTicker(token, marketSymbol, "a", "b", "c", "v", "q", "E", TimestampType.UnixMilliseconds);
797+
return await this.ParseTickerAsync(token, marketSymbol, "a", "b", "c", "v", "q", "E", TimestampType.UnixMilliseconds);
799798
}
800799

801800
private ExchangeOrderResult ParseOrder(JToken token)
@@ -1060,9 +1059,9 @@ protected override async Task<IEnumerable<ExchangeTransaction>> OnGetDepositHist
10601059
return transactions;
10611060
}
10621061

1063-
protected override IWebSocket OnUserDataWebSocket(Action<object> callback, string listenKey)
1062+
protected override async Task<IWebSocket> OnUserDataWebSocketAsync(Action<object> callback, string listenKey)
10641063
{
1065-
return ConnectWebSocket($"/ws/{listenKey}", (_socket, msg) =>
1064+
return await ConnectWebSocketAsync($"/ws/{listenKey}", (_socket, msg) =>
10661065
{
10671066
JToken token = JToken.Parse(msg.ToStringFromUTF8());
10681067
var eventType = token["e"].ToStringInvariant();

ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public ExchangeBitBankAPI()
4040
protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymbol)
4141
{
4242
JToken token = await MakeJsonRequestAsync<JToken>($"/{marketSymbol}/ticker");
43-
return ParseTicker(marketSymbol, token);
43+
return await ParseTickerAsync(marketSymbol, token);
4444
}
4545

4646
// Bitbank supports endpoint for getting all rates in one request, Using this endpoint is faster then ExchangeAPI's default implementation
@@ -53,7 +53,7 @@ protected override async Task<IEnumerable<KeyValuePair<string, ExchangeTicker>>>
5353
var result = new List<KeyValuePair<string, ExchangeTicker>>();
5454
foreach (var symbol in symbols)
5555
{
56-
var data = token[GlobalMarketSymbolToExchangeMarketSymbol(symbol)];
56+
var data = token[GlobalMarketSymbolToExchangeMarketSymbolAsync(symbol)];
5757
var ticker = new ExchangeTicker()
5858
{
5959
Ask = data["sell"].ConvertInvariant<decimal>(),
@@ -207,8 +207,10 @@ protected override async Task<ExchangeWithdrawalResponse> OnWithdrawAsync(Exchan
207207
payload2.Add("amount", withdrawalRequest.Amount);
208208
payload2.Add("uuid", uuid);
209209
JToken token2 = await MakeJsonRequestAsync<JToken>($"/user/request_withdrawal", baseUrl: BaseUrlPrivate, payload: payload2, requestMethod: "POST");
210-
var resp = new ExchangeWithdrawalResponse();
211-
resp.Id = token2["txid"].ToStringInvariant();
210+
var resp = new ExchangeWithdrawalResponse
211+
{
212+
Id = token2["txid"].ToStringInvariant()
213+
};
212214
var status = token2["status"].ToStringInvariant();
213215
resp.Success = status != "REJECTED" && status != "CANCELED";
214216
resp.Message = "{" + $"label:{token2["label"]}, fee:{token2["fee"]}" + "}";
@@ -305,8 +307,11 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti
305307
}
306308
return;
307309
}
308-
private ExchangeTicker ParseTicker(string symbol, JToken token)
309-
=> this.ParseTicker(token, symbol, "sell", "buy", "last", "vol", quoteVolumeKey: null, "timestamp", TimestampType.UnixMilliseconds);
310+
311+
private async Task<ExchangeTicker> ParseTickerAsync(string symbol, JToken token)
312+
{
313+
return await this.ParseTickerAsync(token, symbol, "sell", "buy", "last", "vol", quoteVolumeKey: null, "timestamp", TimestampType.UnixMilliseconds);
314+
}
310315

311316
private string FormatPeriod(int ps)
312317
{
@@ -382,13 +387,15 @@ private ExchangeOrderResult TradeHistoryToExchangeOrderResult(JToken token)
382387
// 2. GetOrder, PostOrder
383388
private ExchangeOrderResult ParseOrderCore(JToken token)
384389
{
385-
var res = new ExchangeOrderResult();
386-
res.OrderId = token["order_id"].ToStringInvariant();
387-
res.MarketSymbol = token["pair"].ToStringInvariant();
388-
res.IsBuy = token["side"].ToStringInvariant() == "buy";
390+
var res = new ExchangeOrderResult
391+
{
392+
OrderId = token["order_id"].ToStringInvariant(),
393+
MarketSymbol = token["pair"].ToStringInvariant(),
394+
IsBuy = token["side"].ToStringInvariant() == "buy"
395+
};
389396
res.Fees = token["type"].ToStringInvariant() == "limit" ? MakerFee * res.Amount : TakerFee * res.Amount;
390397
res.Price = token["price"].ConvertInvariant<decimal>();
391-
res.FillDate = token["executed_at"] == null ? default(DateTime) : token["executed_at"].ConvertInvariant<double>().UnixTimeStampToDateTimeMilliseconds();
398+
res.FillDate = token["executed_at"] == null ? default : token["executed_at"].ConvertInvariant<double>().UnixTimeStampToDateTimeMilliseconds();
392399
res.FeesCurrency = res.MarketSymbol.Substring(0, 3);
393400
return res;
394401
}

0 commit comments

Comments
 (0)