|
| 1 | +/* |
| 2 | +MIT LICENSE |
| 3 | +
|
| 4 | +Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com |
| 5 | +
|
| 6 | +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
| 7 | +
|
| 8 | +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
| 9 | +
|
| 10 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 11 | +*/ |
| 12 | + |
| 13 | +using ExchangeSharp.BinanceGroup; |
| 14 | +using Newtonsoft.Json.Linq; |
| 15 | +using System; |
| 16 | +using System.Collections.Generic; |
| 17 | +using System.Linq; |
| 18 | +using System.Threading.Tasks; |
| 19 | + |
| 20 | +namespace ExchangeSharp |
| 21 | +{ |
| 22 | + public sealed class ExchangeBinanceDEXAPI : ExchangeAPI |
| 23 | + { // unfortunately, Binance DEX doesn't share the same API as Binance and Binance.US, so it shouldn't inherit from BinanceGroupCommon |
| 24 | + public override string BaseUrl { get; set; } = "https://dex.binance.org/api/v1"; |
| 25 | + public override string BaseUrlWebSocket { get; set; } = "wss://dex.binance.org/api/ws"; |
| 26 | + //public override string BaseUrlPrivate { get; set; } = "https://dex.binance.org/api/v3"; |
| 27 | + //public override string WithdrawalUrlPrivate { get; set; } = "https://dex.binance.org/wapi/v3"; |
| 28 | + |
| 29 | + protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync() => |
| 30 | + (await GetMarketSymbolsMetadataAsync()).Select(msm => msm.MarketSymbol); |
| 31 | + |
| 32 | + public override async Task<IEnumerable<ExchangeMarket>> GetMarketSymbolsMetadataAsync() |
| 33 | + { |
| 34 | + // [{"base_asset_symbol":"AERGO-46B","list_price":"0.00350000","lot_size":"1.00000000","quote_asset_symbol":"BNB","tick_size":"0.00000001"}, ... |
| 35 | + var markets = new List<ExchangeMarket>(); |
| 36 | + JToken allSymbols = await MakeJsonRequestAsync<JToken>("/markets"); |
| 37 | + foreach (JToken marketSymbolToken in allSymbols) |
| 38 | + { |
| 39 | + var QuoteCurrency = marketSymbolToken["quote_asset_symbol"].ToStringUpperInvariant(); |
| 40 | + var BaseCurrency = marketSymbolToken["base_asset_symbol"].ToStringUpperInvariant(); |
| 41 | + var market = new ExchangeMarket |
| 42 | + { |
| 43 | + MarketSymbol = BaseCurrency + '_' + QuoteCurrency, |
| 44 | + IsActive = true, |
| 45 | + BaseCurrency = BaseCurrency, |
| 46 | + QuoteCurrency = QuoteCurrency, |
| 47 | + }; |
| 48 | + market.MinTradeSize = marketSymbolToken["lot_size"].ConvertInvariant<decimal>(); |
| 49 | + market.QuantityStepSize = marketSymbolToken["lot_size"].ConvertInvariant<decimal>(); |
| 50 | + |
| 51 | + market.MinPrice = marketSymbolToken["tick_size"].ConvertInvariant<decimal>(); |
| 52 | + market.PriceStepSize = marketSymbolToken["tick_size"].ConvertInvariant<decimal>(); |
| 53 | + |
| 54 | + markets.Add(market); |
| 55 | + } |
| 56 | + |
| 57 | + return markets; |
| 58 | + } |
| 59 | + |
| 60 | + /// <summary> |
| 61 | + /// Binance DEX doesn't suppport streaming aggregate trades like Binance/US |
| 62 | + /// </summary> |
| 63 | + /// <param name="callback"></param> |
| 64 | + /// <param name="marketSymbols"></param> |
| 65 | + /// <returns></returns> |
| 66 | + protected override async Task<IWebSocket> OnGetTradesWebSocketAsync(Func<KeyValuePair<string, ExchangeTrade>, Task> callback, params string[] marketSymbols) |
| 67 | + { |
| 68 | + /* actual data, followed by example data |
| 69 | + { |
| 70 | + "stream": "trades", |
| 71 | + "data": [{ |
| 72 | + "e": "trade", |
| 73 | + "E": 47515444, |
| 74 | + "s": "CBM-4B2_BNB", |
| 75 | + "t": "47515444-0", |
| 76 | + "p": "0.00000722", |
| 77 | + "q": "24000.00000000", |
| 78 | + "b": "F36B58E668004610B5D1DB23A40B496DF27A4D91-91340", |
| 79 | + "a": "E9F71B7AFD3325590624A692B7DF5B449AA9BA19-91463", |
| 80 | + "T": 1573417196130225520, |
| 81 | + "sa": "bnb1a8m3k7haxvj4jp3y56ft0h6mgjd2nwsel6xsx8", |
| 82 | + "ba": "bnb17d443engqprppdw3mv36gz6fdhe85nv37p2xq4", |
| 83 | + "tt": 1 |
| 84 | + }, |
| 85 | + { |
| 86 | + "e": "trade", // Event type |
| 87 | + "E": 123456795, // Event time |
| 88 | + "s": "BNB_BTC", // Symbol |
| 89 | + "t": "12348", // Trade ID |
| 90 | + "p": "0.001", // Price |
| 91 | + "q": "100", // Quantity |
| 92 | + "b": "88", // Buyer order ID |
| 93 | + "a": "52", // Seller order ID |
| 94 | + "T": 123456795, // Trade time |
| 95 | + "sa": "bnb1me5u083m2spzt8pw8vunprnctc8syy64hegrcp", // SellerAddress |
| 96 | + "ba": "bnb1kdr00ydr8xj3ydcd3a8ej2xxn8lkuja7mdunr5" // BuyerAddress |
| 97 | + "tt": 1 //tiekertype 0: Unknown 1: SellTaker 2: BuyTaker 3: BuySurplus 4: SellSurplus 5: Neutral |
| 98 | + }] |
| 99 | + } |
| 100 | + */ |
| 101 | + |
| 102 | + if (marketSymbols == null || marketSymbols.Length == 0) |
| 103 | + { |
| 104 | + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); |
| 105 | + } |
| 106 | + return await ConnectWebSocketAsync(string.Empty, messageCallback: async (_socket, msg) => |
| 107 | + { |
| 108 | + JToken token = JToken.Parse(msg.ToStringFromUTF8()); |
| 109 | + if (token["stream"].ToStringLowerInvariant() == "trades") |
| 110 | + { |
| 111 | + foreach (var data in token["data"]) |
| 112 | + { |
| 113 | + string name = data["s"].ToStringInvariant(); |
| 114 | + string marketSymbol = NormalizeMarketSymbol(name); |
| 115 | + |
| 116 | + await callback(new KeyValuePair<string, ExchangeTrade>(marketSymbol, |
| 117 | + data.ParseTradeBinanceDEX(amountKey: "q", priceKey: "p", typeKey: "tt", |
| 118 | + timestampKey: "T", // use trade time (T) instead of event time (E) |
| 119 | + timestampType: TimestampType.UnixNanoseconds, idKey: "t", typeKeyIsBuyValue: "BuyTaker"))); |
| 120 | + } |
| 121 | + } |
| 122 | + else if (token["error"] != null) |
| 123 | + { // {{ "method": "subscribe", "error": { "error": "Invalid symbol(s)" }}} |
| 124 | + Logger.Info(token["error"]["error"].ToStringInvariant()); |
| 125 | + } |
| 126 | + }, connectCallback: async (_socket) => |
| 127 | + { |
| 128 | + await _socket.SendMessageAsync(new { method = "subscribe", topic = "trades", symbols = marketSymbols }); |
| 129 | + }); |
| 130 | + } |
| 131 | + } |
| 132 | + |
| 133 | + public partial class ExchangeName { public const string BinanceDEX = "BinanceDEX"; } |
| 134 | +} |
0 commit comments