diff --git a/ExchangeSharp/API/Exchanges/Abucoins/ExchangeAbucoinsAPI.cs b/ExchangeSharp/API/Exchanges/Abucoins/ExchangeAbucoinsAPI.cs index 2fff3b37..87f72473 100644 --- a/ExchangeSharp/API/Exchanges/Abucoins/ExchangeAbucoinsAPI.cs +++ b/ExchangeSharp/API/Exchanges/Abucoins/ExchangeAbucoinsAPI.cs @@ -67,7 +67,7 @@ protected override Task> OnGetCurr throw new NotSupportedException("Abucoins does not provide data about its currencies via the API"); } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); JToken obj = await MakeJsonRequestAsync("/products"); @@ -78,34 +78,31 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { List markets = new List(); JToken obj = await MakeJsonRequestAsync("/products"); - const decimal StepSize = 0.00000001m; foreach (JToken token in obj) { markets.Add(new ExchangeMarket { - MarketName = token["id"].ToStringInvariant(), + MarketSymbol = token["id"].ToStringInvariant(), BaseCurrency = token["base_currency"].ToStringInvariant(), - MarketCurrency = token["quote_currency"].ToStringInvariant(), + QuoteCurrency = token["quote_currency"].ToStringInvariant(), MinTradeSize = token["base_min_size"].ConvertInvariant(), MaxTradeSize = token["base_max_size"].ConvertInvariant(), - QuantityStepSize = token["quote_increment"].ConvertInvariant(), - MaxPrice = StepSize, - MinPrice = StepSize, - PriceStepSize = StepSize, + PriceStepSize = token["quote_increment"].ConvertInvariant(), + MinPrice = token["quote_increment"].ConvertInvariant(), IsActive = true }); } return markets; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken token = await MakeJsonRequestAsync("/products/" + symbol + "/ticker"); - return ParseTicker(token, symbol); + JToken token = await MakeJsonRequestAsync("/products/" + marketSymbol + "/ticker"); + return ParseTicker(token, marketSymbol); } protected override async Task>> OnGetTickersAsync() @@ -116,34 +113,34 @@ protected override async Task>> { foreach (JToken token in obj) { - string symbol = token["product_id"].ToStringInvariant(); - ExchangeTicker ticker = ParseTicker(token, symbol); + string marketSymbol = token["product_id"].ToStringInvariant(); + ExchangeTicker ticker = ParseTicker(token, marketSymbol); if (ticker != null) { - tickers.Add(new KeyValuePair(symbol, ticker)); + tickers.Add(new KeyValuePair(marketSymbol, ticker)); } } } return tickers; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - JToken token = await MakeJsonRequestAsync("/products/" + symbol + "/book?level=" + (maxCount > 50 ? "0" : "2")); + JToken token = await MakeJsonRequestAsync("/products/" + marketSymbol + "/book?level=" + (maxCount > 50 ? "0" : "2")); return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token, maxCount: maxCount); } - protected override async Task> OnGetRecentTradesAsync(string symbol) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol) { List trades = new List(); // { "time": "2017-09-21T12:33:03Z", "trade_id": "553794", "price": "14167.99328000", "size": "0.00035000", "side": "buy"} - JToken obj = await MakeJsonRequestAsync("/products/" + symbol + "/trades"); + JToken obj = await MakeJsonRequestAsync("/products/" + marketSymbol + "/trades"); if (obj.HasValues) foreach (JToken token in obj) trades.Add(ParseExchangeTrade(token)); return trades; } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { List trades = new List(); long? lastTradeId = null; @@ -153,7 +150,7 @@ protected override async Task OnGetHistoricalTradesAsync(Func("/products/" + symbol + "/trades" + (lastTradeId == null ? string.Empty : "?before=" + lastTradeId)); + obj = await MakeJsonRequestAsync("/products/" + marketSymbol + "/trades" + (lastTradeId == null ? string.Empty : "?before=" + lastTradeId)); if ((running = obj.HasValues)) { lastTradeId = obj.First()["trade_id"].ConvertInvariant(); @@ -181,7 +178,7 @@ protected override async Task OnGetHistoricalTradesAsync(Func> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { //if (limit != null) throw new APIException("Limit parameter not supported"); Really? You want to throw an exception instead of ignoring the parm? List candles = new List(); @@ -190,12 +187,12 @@ protected override async Task> OnGetCandlesAsync(strin startDate = startDate ?? endDate.Value.Subtract(TimeSpan.FromDays(1.0)); //[time, low, high, open, close, volume], ["1505984400","14209.92500000","14209.92500000","14209.92500000","14209.92500000","0.001"] - JToken obj = await MakeJsonRequestAsync("/products/" + symbol + "/candles?granularity=" + periodSeconds.ToStringInvariant() + "&start=" + ((DateTime)startDate).ToString("o") + "&end=" + ((DateTime)endDate).ToString("o")); + JToken obj = await MakeJsonRequestAsync("/products/" + marketSymbol + "/candles?granularity=" + periodSeconds.ToStringInvariant() + "&start=" + ((DateTime)startDate).ToString("o") + "&end=" + ((DateTime)endDate).ToString("o")); if (obj.HasValues) { foreach (JToken token in obj) { - candles.Add(this.ParseCandle(token, symbol, periodSeconds, 3, 2, 1, 4, 0, TimestampType.UnixSeconds, 5)); + candles.Add(this.ParseCandle(token, marketSymbol, periodSeconds, 3, 2, 1, 4, 0, TimestampType.UnixSeconds, 5)); } } return candles; @@ -229,7 +226,7 @@ protected override async Task> OnGetAmountsAvailable return amounts; } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { JToken token = await MakeJsonRequestAsync("/orders?orderID", null, await GetNoncePayloadAsync()); ExchangeOrderResult eor = new ExchangeOrderResult() @@ -239,7 +236,7 @@ protected override async Task OnGetOrderDetailsAsync(string AmountFilled = token["filled_size"].ConvertInvariant(), AveragePrice = token["price"].ConvertInvariant(), IsBuy = token["side"].ToStringInvariant().Equals("buy"), - Symbol = token["product_id"].ToStringInvariant(), + MarketSymbol = token["product_id"].ToStringInvariant(), }; if (DateTime.TryParse(token["created_at"].ToStringInvariant(), out DateTime dt)) eor.OrderDate = dt; @@ -253,7 +250,7 @@ protected override async Task OnGetOrderDetailsAsync(string return eor; } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { List result = new List(); JArray array = await MakeJsonRequestAsync("/orders?status=done", null, await GetNoncePayloadAsync()); @@ -266,7 +263,7 @@ protected override async Task> OnGetCompletedOr AmountFilled = token["filled_size"].ConvertInvariant(), AveragePrice = token["price"].ConvertInvariant(), IsBuy = token["side"].ConvertInvariant().Equals("buy"), - Symbol = token["product_id"].ToStringInvariant(), + MarketSymbol = token["product_id"].ToStringInvariant(), }; if (DateTime.TryParse(token["created_at"].ToStringInvariant(), out DateTime dt)) eor.OrderDate = dt; @@ -282,7 +279,7 @@ protected override async Task> OnGetCompletedOr return result; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List result = new List(); JArray array = await MakeJsonRequestAsync("/orders?status=open", null, await GetNoncePayloadAsync()); @@ -295,7 +292,7 @@ protected override async Task> OnGetOpenOrderDe AmountFilled = token["filled_size"].ConvertInvariant(), AveragePrice = token["price"].ConvertInvariant(), IsBuy = token["side"].ToStringInvariant().Equals("buy"), - Symbol = token["product_id"].ToStringInvariant(), + MarketSymbol = token["product_id"].ToStringInvariant(), }; if (DateTime.TryParse(token["created_at"].ToStringInvariant(), out DateTime dt)) eor.OrderDate = dt; @@ -315,7 +312,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd { ExchangeOrderResult result = new ExchangeOrderResult() { Result = ExchangeAPIOrderResult.Error }; var payload = await GetNoncePayloadAsync(); - payload["priduct_id"] = order.Symbol; + payload["priduct_id"] = order.MarketSymbol; payload["side"] = order.IsBuy ? "buy" : "sell"; payload["size"] = order.Amount; if (order.OrderType == OrderType.Limit) @@ -341,7 +338,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd result.IsBuy = token["buy"].ToStringInvariant().Equals("buy"); result.OrderDate = token["created_at"].ToDateTimeInvariant(); result.Price = token["price"].ConvertInvariant(); - result.Symbol = token["product_id"].ToStringInvariant(); + result.MarketSymbol = token["product_id"].ToStringInvariant(); result.Message = token["reject_reason"].ToStringInvariant(); switch (token["status"].ToStringInvariant()) { @@ -357,12 +354,12 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd } // This should have a return value for success - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { await MakeJsonRequestAsync("/orders/" + orderId, null, await GetNoncePayloadAsync(), "DELETE"); } - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { List deposits = new List(); @@ -376,7 +373,7 @@ protected override async Task> OnGetDepositHist { ExchangeTransaction deposit = new ExchangeTransaction() { - Symbol = token["currency"].ToStringInvariant(), + Currency = token["currency"].ToStringInvariant(), Amount = token["amount"].ConvertInvariant(), Timestamp = token["date"].ToDateTimeInvariant(), PaymentId = token["deposit_id"].ToStringInvariant(), @@ -388,26 +385,26 @@ protected override async Task> OnGetDepositHist case "pending": deposit.Status = TransactionStatus.Processing; break; default: deposit.Status = TransactionStatus.AwaitingApproval; break; } - if (deposit.Symbol == symbol) deposits.Add(deposit); + if (deposit.Currency == currency) deposits.Add(deposit); } return deposits; } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { var payload = await GetNoncePayloadAsync(); JArray array = await MakeJsonRequestAsync("/payment-methods", null, await GetNoncePayloadAsync()); if (array != null) { - var rc = array.Where(t => t["currency"].ToStringInvariant() == symbol).FirstOrDefault(); + var rc = array.Where(t => t["currency"].ToStringInvariant() == currency).FirstOrDefault(); payload = await GetNoncePayloadAsync(); - payload["currency"] = symbol; + payload["currency"] = currency; payload["method"] = rc["id"].ToStringInvariant(); JToken token = await MakeJsonRequestAsync("/deposits/make", null, payload, "POST"); ExchangeDepositDetails deposit = new ExchangeDepositDetails() { - Symbol = symbol, + Currency = currency, Address = token["address"].ToStringInvariant(), AddressTag = token["tag"].ToStringInvariant() }; @@ -463,7 +460,7 @@ protected override IWebSocket OnGetCompletedOrderDetailsWebSocket(Action> { - new KeyValuePair(symbol, this.ParseTicker(token, symbol, "best_ask", "best_bid", "price", "last_size", null, "time", TimestampType.Iso8601)) + new KeyValuePair(marketSymbol, this.ParseTicker(token, marketSymbol, "best_ask", "best_bid", "price", "last_size", null, "time", TimestampType.Iso8601)) }); } return Task.CompletedTask; diff --git a/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs b/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs index 93c35cc7..14fc4dbd 100644 --- a/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs +++ b/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs @@ -44,18 +44,18 @@ static ExchangeBinanceAPI() }; } - private string GetWebSocketStreamUrlForSymbols(string suffix, params string[] symbols) + private string GetWebSocketStreamUrlForSymbols(string suffix, params string[] marketSymbols) { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = GetSymbolsAsync().Sync().ToArray(); + marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); } StringBuilder streams = new StringBuilder("/stream?streams="); - for (int i = 0; i < symbols.Length; i++) + for (int i = 0; i < marketSymbols.Length; i++) { - string symbol = NormalizeSymbol(symbols[i]).ToLowerInvariant(); - streams.Append(symbol); + string marketSymbol = NormalizeMarketSymbol(marketSymbols[i]).ToLowerInvariant(); + streams.Append(marketSymbol); streams.Append(suffix); streams.Append('/'); } @@ -70,39 +70,39 @@ public ExchangeBinanceAPI() RequestWindow = TimeSpan.FromMinutes(15.0); NonceStyle = NonceStyle.UnixMilliseconds; NonceOffset = TimeSpan.FromSeconds(10.0); - SymbolSeparator = string.Empty; + MarketSymbolSeparator = string.Empty; WebSocketOrderBookType = WebSocketOrderBookType.DeltasOnly; } - public override string ExchangeSymbolToGlobalSymbol(string symbol) + public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) { // All pairs in Binance end with BTC, ETH, BNB or USDT - if (symbol.EndsWith("BTC") || symbol.EndsWith("ETH") || symbol.EndsWith("BNB")) + if (marketSymbol.EndsWith("BTC") || marketSymbol.EndsWith("ETH") || marketSymbol.EndsWith("BNB")) { - string baseSymbol = symbol.Substring(symbol.Length - 3); - return ExchangeSymbolToGlobalSymbolWithSeparator((symbol.Replace(baseSymbol, "") + GlobalSymbolSeparator + baseSymbol), GlobalSymbolSeparator); + string baseSymbol = marketSymbol.Substring(marketSymbol.Length - 3); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator); } - if (symbol.EndsWith("USDT")) + if (marketSymbol.EndsWith("USDT")) { - string baseSymbol = symbol.Substring(symbol.Length - 4); - return ExchangeSymbolToGlobalSymbolWithSeparator((symbol.Replace(baseSymbol, "") + GlobalSymbolSeparator + baseSymbol), GlobalSymbolSeparator); + string baseSymbol = marketSymbol.Substring(marketSymbol.Length - 4); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator); } - return ExchangeSymbolToGlobalSymbolWithSeparator(symbol.Substring(0, symbol.Length - 3) + GlobalSymbolSeparator + (symbol.Substring(symbol.Length - 3, 3)), GlobalSymbolSeparator); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(marketSymbol.Substring(0, marketSymbol.Length - 3) + GlobalMarketSymbolSeparator + (marketSymbol.Substring(marketSymbol.Length - 3, 3)), GlobalMarketSymbolSeparator); } /// /// Get the details of all trades /// - /// Symbol to get trades for or null for all + /// Symbol to get trades for or null for all /// All trades for the specified symbol, or all if null symbol - public async Task> GetMyTradesAsync(string symbol = null, DateTime? afterDate = null) + public async Task> GetMyTradesAsync(string marketSymbol = null, DateTime? afterDate = null) { await new SynchronizationContextRemover(); - return await OnGetMyTradesAsync(symbol, afterDate); + return await OnGetMyTradesAsync(marketSymbol, afterDate); } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); JToken obj = await MakeJsonRequestAsync("/ticker/allPrices"); @@ -113,60 +113,62 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { /* * { - "symbol": "QTUMETH", - "status": "TRADING", - "baseAsset": "QTUM", - "baseAssetPrecision": 8, - "quoteAsset": "ETH", - "quotePrecision": 8, - "orderTypes": [ - "LIMIT", - "LIMIT_MAKER", - "MARKET", - "STOP_LOSS_LIMIT", - "TAKE_PROFIT_LIMIT" - ], - "icebergAllowed": true, - "filters": [ - { - "filterType": "PRICE_FILTER", - "minPrice": "0.00000100", - "maxPrice": "100000.00000000", - "tickSize": "0.00000100" - }, - { - "filterType": "LOT_SIZE", - "minQty": "0.01000000", - "maxQty": "90000000.00000000", - "stepSize": "0.01000000" - }, - { - "filterType": "MIN_NOTIONAL", - "minNotional": "0.01000000" - } - ] + "symbol": "ETHBTC", + "status": "TRADING", + "baseAsset": "ETH", + "baseAssetPrecision": 8, + "quoteAsset": "BTC", + "quotePrecision": 8, + "orderTypes": [ + "LIMIT", + "MARKET", + "STOP_LOSS", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT", + "TAKE_PROFIT_LIMIT", + "LIMIT_MAKER" + ], + "icebergAllowed": false, + "filters": [ + { + "filterType": "PRICE_FILTER", + "minPrice": "0.00000100", + "maxPrice": "100000.00000000", + "tickSize": "0.00000100" + }, + { + "filterType": "LOT_SIZE", + "minQty": "0.00100000", + "maxQty": "100000.00000000", + "stepSize": "0.00100000" + }, + { + "filterType": "MIN_NOTIONAL", + "minNotional": "0.00100000" + } + ] }, */ var markets = new List(); JToken obj = await MakeJsonRequestAsync("/exchangeInfo"); JToken allSymbols = obj["symbols"]; - foreach (JToken symbol in allSymbols) + foreach (JToken marketSymbolToken in allSymbols) { var market = new ExchangeMarket { - MarketName = symbol["symbol"].ToStringUpperInvariant(), - IsActive = ParseMarketStatus(symbol["status"].ToStringUpperInvariant()), - BaseCurrency = symbol["quoteAsset"].ToStringUpperInvariant(), - MarketCurrency = symbol["baseAsset"].ToStringUpperInvariant() + MarketSymbol = marketSymbolToken["symbol"].ToStringUpperInvariant(), + IsActive = ParseMarketStatus(marketSymbolToken["status"].ToStringUpperInvariant()), + QuoteCurrency = marketSymbolToken["quoteAsset"].ToStringUpperInvariant(), + BaseCurrency = marketSymbolToken["baseAsset"].ToStringUpperInvariant() }; // "LOT_SIZE" - JToken filters = symbol["filters"]; + JToken filters = marketSymbolToken["filters"]; JToken lotSizeFilter = filters?.FirstOrDefault(x => string.Equals(x["filterType"].ToStringUpperInvariant(), "LOT_SIZE")); if (lotSizeFilter != null) { @@ -183,6 +185,13 @@ protected override async Task> OnGetSymbolsMetadataA market.MinPrice = priceFilter["minPrice"].ConvertInvariant(); market.PriceStepSize = priceFilter["tickSize"].ConvertInvariant(); } + + // MIN_NOTIONAL + JToken minNotionalFilter = filters?.FirstOrDefault(x => string.Equals(x["filterType"].ToStringUpperInvariant(), "MIN_NOTIONAL")); + if (minNotionalFilter != null) + { + market.MinTradeSizeInQuoteCurrency = minNotionalFilter["minNotional"].ConvertInvariant(); + } markets.Add(market); } @@ -212,21 +221,21 @@ protected override async Task> OnG return allCoins; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken obj = await MakeJsonRequestAsync("/ticker/24hr?symbol=" + symbol); - return ParseTicker(symbol, obj); + JToken obj = await MakeJsonRequestAsync("/ticker/24hr?symbol=" + marketSymbol); + return ParseTicker(marketSymbol, obj); } protected override async Task>> OnGetTickersAsync() { List> tickers = new List>(); - string symbol; + string marketSymbol; JToken obj = await MakeJsonRequestAsync("/ticker/24hr"); foreach (JToken child in obj) { - symbol = child["symbol"].ToStringInvariant(); - tickers.Add(new KeyValuePair(symbol, ParseTicker(symbol, child))); + marketSymbol = child["symbol"].ToStringInvariant(); + tickers.Add(new KeyValuePair(marketSymbol, ParseTicker(marketSymbol, child))); } return tickers; } @@ -241,7 +250,7 @@ protected override IWebSocket OnGetTickersWebSocket(Action(ticker.Volume.BaseSymbol, ticker)); + tickerList.Add(new KeyValuePair(ticker.Volume.QuoteCurrency, ticker)); } if (tickerList.Count != 0) { @@ -251,7 +260,7 @@ protected override IWebSocket OnGetTickersWebSocket(Action> callback, params string[] symbols) + protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) { /* { @@ -269,38 +278,38 @@ protected override IWebSocket OnGetTradesWebSocket(Action { JToken token = JToken.Parse(msg.ToStringFromUTF8()); string name = token["stream"].ToStringInvariant(); token = token["data"]; - string symbol = NormalizeSymbol(name.Substring(0, name.IndexOf('@'))); + string marketSymbol = NormalizeMarketSymbol(name.Substring(0, name.IndexOf('@'))); // buy=0 -> m = true (The buyer is maker, while the seller is taker). // buy=1 -> m = false(The seller is maker, while the buyer is taker). - callback(new KeyValuePair(symbol, token.ParseTrade("q", "p", "m", "E", TimestampType.UnixMilliseconds, "t", "false"))); + callback(new KeyValuePair(marketSymbol, token.ParseTrade("q", "p", "m", "E", TimestampType.UnixMilliseconds, "t", "false"))); return Task.CompletedTask; }); } - protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols) + protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = GetSymbolsAsync().Sync().ToArray(); + marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); } - string combined = string.Join("/", symbols.Select(s => this.NormalizeSymbol(s).ToLowerInvariant() + "@depth")); + string combined = string.Join("/", marketSymbols.Select(s => this.NormalizeMarketSymbol(s).ToLowerInvariant() + "@depth")); return ConnectWebSocket($"/stream?streams={combined}", (_socket, msg) => { string json = msg.ToStringFromUTF8(); var update = JsonConvert.DeserializeObject(json); - string symbol = update.Data.Symbol; - ExchangeOrderBook book = new ExchangeOrderBook { SequenceId = update.Data.FinalUpdate, Symbol = symbol }; + string marketSymbol = update.Data.MarketSymbol; + ExchangeOrderBook book = new ExchangeOrderBook { SequenceId = update.Data.FinalUpdate, MarketSymbol = marketSymbol }; foreach (List ask in update.Data.Asks) { var depth = new ExchangeOrderPrice { Price = ask[0].ConvertInvariant(), Amount = ask[1].ConvertInvariant() }; @@ -316,13 +325,13 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action }); } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - JToken obj = await MakeJsonRequestAsync("/depth?symbol=" + symbol + "&limit=" + maxCount); + JToken obj = await MakeJsonRequestAsync("/depth?symbol=" + marketSymbol + "&limit=" + maxCount); return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(obj, sequence: "lastUpdateId", maxCount: maxCount); } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { /* [ { "a": 26129, // Aggregate tradeId @@ -341,14 +350,14 @@ protected override async Task OnGetHistoricalTradesAsync(Func token.ParseTrade("q", "p", "m", "T", TimestampType.UnixMilliseconds, "a", "false"), StartDate = startDate, - Symbol = symbol, + MarketSymbol = marketSymbol, TimestampFunction = (DateTime dt) => ((long)CryptoUtility.UnixTimestampFromDateTimeMilliseconds(dt)).ToStringInvariant(), - Url = "/aggTrades?symbol=[symbol]&startTime={0}&endTime={1}", + Url = "/aggTrades?symbol=[marketSymbol]&startTime={0}&endTime={1}", }; await state.ProcessHistoricalTrades(); } - protected override async Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { /* [ [ @@ -367,7 +376,7 @@ protected override async Task> OnGetCandlesAsync(strin ]] */ List candles = new List(); - string url = "/klines?symbol=" + symbol; + string url = "/klines?symbol=" + marketSymbol; if (startDate != null) { url += "&startTime=" + (long)startDate.Value.UnixTimestampFromDateTimeMilliseconds(); @@ -381,7 +390,7 @@ protected override async Task> OnGetCandlesAsync(strin JToken obj = await MakeJsonRequestAsync(url); foreach (JToken token in obj) { - candles.Add(this.ParseCandle(token, symbol, periodSeconds, 1, 2, 3, 4, 0, TimestampType.UnixMilliseconds, 5, 7)); + candles.Add(this.ParseCandle(token, marketSymbol, periodSeconds, 1, 2, 3, 4, 0, TimestampType.UnixMilliseconds, 5, 7)); } return candles; @@ -420,7 +429,7 @@ protected override async Task> OnGetAmountsAvailable protected override async Task OnPlaceOrderAsync(ExchangeOrderRequest order) { Dictionary payload = await GetNoncePayloadAsync(); - payload["symbol"] = order.Symbol; + payload["symbol"] = order.MarketSymbol; payload["side"] = order.IsBuy ? "BUY" : "SELL"; if (order.OrderType == OrderType.Stop) payload["type"] = "STOP_LOOSE";//if order type is stop loose/limit, then binance expect word 'STOP_LOOSE' inestead of 'STOP' @@ -428,8 +437,8 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd payload["type"] = order.OrderType.ToStringUpperInvariant(); // Binance has strict rules on which prices and quantities are allowed. They have to match the rules defined in the market definition. - decimal outputQuantity = await ClampOrderQuantity(order.Symbol, order.Amount); - decimal outputPrice = await ClampOrderPrice(order.Symbol, order.Price); + decimal outputQuantity = await ClampOrderQuantity(order.MarketSymbol, order.Amount); + decimal outputPrice = await ClampOrderPrice(order.MarketSymbol, order.Price); // Binance does not accept quantities with more than 20 decimal places. payload["quantity"] = Math.Round(outputQuantity, 20); @@ -446,21 +455,21 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return ParseOrder(token); } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { Dictionary payload = await GetNoncePayloadAsync(); - if (string.IsNullOrEmpty(symbol)) + if (string.IsNullOrEmpty(marketSymbol)) { throw new InvalidOperationException("Binance single order details request requires symbol"); } - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; payload["orderId"] = orderId; JToken token = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload); ExchangeOrderResult result = ParseOrder(token); // Add up the fees from each trade in the order Dictionary feesPayload = await GetNoncePayloadAsync(); - feesPayload["symbol"] = symbol; + feesPayload["symbol"] = marketSymbol; JToken feesToken = await MakeJsonRequestAsync("/myTrades", BaseUrlPrivate, feesPayload); ParseFees(feesToken, result); @@ -488,13 +497,13 @@ private static void ParseFees(JToken feesToken, ExchangeOrderResult result) } } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); Dictionary payload = await GetNoncePayloadAsync(); - if (symbol.Length != 0) + if (marketSymbol.Length != 0) { - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; } JToken token = await MakeJsonRequestAsync("/openOrders", BaseUrlPrivate, payload); foreach (JToken order in token) @@ -511,7 +520,7 @@ private async Task> GetCompletedOrdersForAllSym List orders = new List(); Exception ex = null; string failedSymbol = null; - Parallel.ForEach((await GetSymbolsAsync()).Where(s => s.IndexOf("BTC", StringComparison.OrdinalIgnoreCase) >= 0), async (s) => + Parallel.ForEach((await GetMarketSymbolsAsync()).Where(s => s.IndexOf("BTC", StringComparison.OrdinalIgnoreCase) >= 0), async (s) => { try { @@ -543,17 +552,17 @@ private async Task> GetCompletedOrdersForAllSym return orders; } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { List orders = new List(); - if (symbol.Length == 0) + if (marketSymbol.Length == 0) { orders.AddRange(await GetCompletedOrdersForAllSymbolsAsync(afterDate)); } else { Dictionary payload = await GetNoncePayloadAsync(); - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; if (afterDate != null) { payload["startTime"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds(); @@ -573,7 +582,7 @@ private async Task> GetMyTradesForAllSymbols(Da List trades = new List(); Exception ex = null; string failedSymbol = null; - Parallel.ForEach((await GetSymbolsAsync()).Where(s => s.IndexOf("BTC", StringComparison.OrdinalIgnoreCase) >= 0), async (s) => + Parallel.ForEach((await GetMarketSymbolsAsync()).Where(s => s.IndexOf("BTC", StringComparison.OrdinalIgnoreCase) >= 0), async (s) => { try { @@ -605,17 +614,17 @@ private async Task> GetMyTradesForAllSymbols(Da return trades; } - private async Task> OnGetMyTradesAsync(string symbol = null, DateTime? afterDate = null) + private async Task> OnGetMyTradesAsync(string marketSymbol = null, DateTime? afterDate = null) { List trades = new List(); - if (symbol.Length == 0) + if (marketSymbol.Length == 0) { trades.AddRange(await GetCompletedOrdersForAllSymbolsAsync(afterDate)); } else { Dictionary payload = await GetNoncePayloadAsync(); - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; if (afterDate != null) { payload["timestamp"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds(); @@ -623,20 +632,20 @@ private async Task> OnGetMyTradesAsync(string s JToken token = await MakeJsonRequestAsync("/myTrades", BaseUrlPrivate, payload); foreach (JToken trade in token) { - trades.Add(ParseTrade(trade, symbol)); + trades.Add(ParseTrade(trade, marketSymbol)); } } return trades; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { Dictionary payload = await GetNoncePayloadAsync(); - if (symbol.Length == 0) + if (marketSymbol.Length == 0) { throw new InvalidOperationException("Binance cancel order request requires symbol"); } - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; payload["orderId"] = orderId; JToken token = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload, "DELETE"); } @@ -688,11 +697,12 @@ private bool ParseMarketStatus(string status) switch (status) { case "TRADING": - case "PRE_TRADING": - case "POST_TRADING": isActive = true; break; - /* case "END_OF_DAY": + /* + case "PRE_TRADING": + case "POST_TRADING": + case "END_OF_DAY": case "HALT": case "AUCTION_MATCH": case "BREAK": */ @@ -710,8 +720,8 @@ private ExchangeTicker ParseTicker(string symbol, JToken token) private ExchangeTicker ParseTickerWebSocket(JToken token) { - string symbol = token["s"].ToStringInvariant(); - return this.ParseTicker(token, symbol, "a", "b", "c", "v", "q", "E", TimestampType.UnixMilliseconds); + string marketSymbol = token["s"].ToStringInvariant(); + return this.ParseTicker(token, marketSymbol, "a", "b", "c", "v", "q", "E", TimestampType.UnixMilliseconds); } private ExchangeOrderResult ParseOrder(JToken token) @@ -769,7 +779,7 @@ private ExchangeOrderResult ParseOrder(JToken token) IsBuy = token["side"].ToStringInvariant() == "BUY", OrderDate = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(token["time"].ConvertInvariant(token["transactTime"].ConvertInvariant())), OrderId = token["orderId"].ToStringInvariant(), - Symbol = token["symbol"].ToStringInvariant() + MarketSymbol = token["symbol"].ToStringInvariant() }; switch (token["status"].ToStringInvariant()) @@ -833,7 +843,7 @@ private ExchangeOrderResult ParseTrade(JToken token, string symbol) OrderId = token["orderId"].ToStringInvariant(), Fees = token["commission"].ConvertInvariant(), FeesCurrency = token["commissionAsset"].ToStringInvariant(), - Symbol = symbol + MarketSymbol = symbol }; return result; @@ -895,12 +905,12 @@ protected override Uri ProcessRequestUrl(UriBuilder url, Dictionary /// Gets the address to deposit to and applicable details. /// - /// Symbol to get address for + /// Currency to get address for /// (ignored) Binance does not provide the ability to generate new addresses /// /// Deposit address details (including tag if applicable, such as XRP) /// - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { /* * TODO: Binance does not offer a "regenerate" option in the API, but a second IOTA deposit to the same address will not be credited @@ -909,12 +919,12 @@ protected override async Task OnGetDepositAddressAsync(s */ Dictionary payload = await GetNoncePayloadAsync(); - payload["asset"] = symbol; + payload["asset"] = currency; JToken response = await MakeJsonRequestAsync("/depositAddress.html", WithdrawalUrlPrivate, payload); ExchangeDepositDetails depositDetails = new ExchangeDepositDetails { - Symbol = response["asset"].ToStringInvariant(), + Currency = response["asset"].ToStringInvariant(), Address = response["address"].ToStringInvariant(), AddressTag = response["addressTag"].ToStringInvariant() }; @@ -923,15 +933,15 @@ protected override async Task OnGetDepositAddressAsync(s } /// Gets the deposit history for a symbol - /// The symbol to check. Null for all symbols. + /// The currency to check. Null for all symbols. /// Collection of ExchangeCoinTransfers - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { // TODO: API supports searching on status, startTime, endTime Dictionary payload = await GetNoncePayloadAsync(); - if (symbol.Length != 0) + if (currency.Length != 0) { - payload["asset"] = symbol; + payload["asset"] = currency; } JToken response = await MakeJsonRequestAsync("/depositHistory.html", WithdrawalUrlPrivate, payload); @@ -942,7 +952,7 @@ protected override async Task> OnGetDepositHist { Timestamp = token["insertTime"].ConvertInvariant().UnixTimeStampToDateTimeMilliseconds(), Amount = token["amount"].ConvertInvariant(), - Symbol = token["asset"].ToStringUpperInvariant(), + Currency = token["asset"].ToStringUpperInvariant(), Address = token["address"].ToStringInvariant(), AddressTag = token["addressTag"].ToStringInvariant(), BlockchainTxId = token["txId"].ToStringInvariant() diff --git a/ExchangeSharp/API/Exchanges/Binance/Models/MarketDepthDiffUpdate.cs b/ExchangeSharp/API/Exchanges/Binance/Models/MarketDepthDiffUpdate.cs index 10aa9639..a704a594 100644 --- a/ExchangeSharp/API/Exchanges/Binance/Models/MarketDepthDiffUpdate.cs +++ b/ExchangeSharp/API/Exchanges/Binance/Models/MarketDepthDiffUpdate.cs @@ -25,7 +25,7 @@ internal class MarketDepthDiffUpdate public long EventTime { get; set; } [JsonProperty("s")] - public string Symbol { get; set; } + public string MarketSymbol { get; set; } [JsonProperty("U")] public int FirstUpdate { get; set; } diff --git a/ExchangeSharp/API/Exchanges/BitMEX/ExchangeBitMEXAPI.cs b/ExchangeSharp/API/Exchanges/BitMEX/ExchangeBitMEXAPI.cs index d3618944..795ac54b 100644 --- a/ExchangeSharp/API/Exchanges/BitMEX/ExchangeBitMEXAPI.cs +++ b/ExchangeSharp/API/Exchanges/BitMEX/ExchangeBitMEXAPI.cs @@ -42,19 +42,19 @@ public ExchangeBitMEXAPI() // this will give us an api-expires 60 seconds into the future NonceOffset = TimeSpan.FromSeconds(-60.0); - SymbolSeparator = string.Empty; + MarketSymbolSeparator = string.Empty; RequestContentType = "application/json"; WebSocketOrderBookType = WebSocketOrderBookType.FullBookFirstThenDeltas; RateLimit = new RateGate(300, TimeSpan.FromMinutes(5)); } - public override string ExchangeSymbolToGlobalSymbol(string symbol) + public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) { throw new NotImplementedException(); } - public override string GlobalSymbolToExchangeSymbol(string symbol) + public override string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol) { throw new NotImplementedException(); } @@ -78,14 +78,14 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti } } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { - var m = await GetSymbolsMetadataAsync(); - return m.Select(x => x.MarketName); + var m = await GetMarketSymbolsMetadataAsync(); + return m.Select(x => x.MarketSymbol); } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { /* {{ @@ -195,23 +195,23 @@ protected override async Task> OnGetSymbolsMetadataA List markets = new List(); JToken allSymbols = await MakeJsonRequestAsync("/instrument"); - foreach (JToken symbol in allSymbols) + foreach (JToken marketSymbolToken in allSymbols) { var market = new ExchangeMarket { - MarketName = symbol["symbol"].ToStringUpperInvariant(), - IsActive = symbol["status"].ToStringInvariant().EqualsWithOption("Open"), - BaseCurrency = symbol["quoteCurrency"].ToStringUpperInvariant(), - MarketCurrency = symbol["underlying"].ToStringUpperInvariant(), + MarketSymbol = marketSymbolToken["symbol"].ToStringUpperInvariant(), + IsActive = marketSymbolToken["status"].ToStringInvariant().EqualsWithOption("Open"), + QuoteCurrency = marketSymbolToken["quoteCurrency"].ToStringUpperInvariant(), + BaseCurrency = marketSymbolToken["underlying"].ToStringUpperInvariant(), }; try { - market.PriceStepSize = symbol["tickSize"].ConvertInvariant(); - market.MaxPrice = symbol["maxPrice"].ConvertInvariant(); + market.PriceStepSize = marketSymbolToken["tickSize"].ConvertInvariant(); + market.MaxPrice = marketSymbolToken["maxPrice"].ConvertInvariant(); //market.MinPrice = symbol["minPrice"].ConvertInvariant(); - market.MaxTradeSize = symbol["maxOrderQty"].ConvertInvariant(); + market.MaxTradeSize = marketSymbolToken["maxOrderQty"].ConvertInvariant(); //market.MinTradeSize = symbol["minQty"].ConvertInvariant(); //market.QuantityStepSize = symbol["stepSize"].ConvertInvariant(); } @@ -224,7 +224,7 @@ protected override async Task> OnGetSymbolsMetadataA return markets; } - protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] symbols) + protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) { /* {"table":"trade","action":"partial","keys":[], @@ -249,21 +249,21 @@ protected override IWebSocket OnGetTradesWebSocket(Action(symbol, t.ParseTrade("size", "price", "size", "timestamp", TimestampType.Iso8601, "trdMatchID"))); + var marketSymbol = t["symbol"].ToStringInvariant(); + callback(new KeyValuePair(marketSymbol, t.ParseTrade("size", "price", "size", "timestamp", TimestampType.Iso8601, "trdMatchID"))); } return Task.CompletedTask; }, async (_socket) => { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = (await GetSymbolsAsync()).ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - await _socket.SendMessageAsync(new { op = "subscribe", args = symbols.Select(s => "\"trade:" + this.NormalizeSymbol(s) + "\"").ToArray() }); + await _socket.SendMessageAsync(new { op = "subscribe", args = marketSymbols.Select(s => "\"trade:" + this.NormalizeMarketSymbol(s) + "\"").ToArray() }); }); } - protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols) + protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) { /* {"info":"Welcome to the BitMEX Realtime API.","version":"2018-06-29T18:05:14.000Z","timestamp":"2018-07-05T14:22:26.267Z","docs":"https://www.bitmex.com/app/wsAPI","limit":{"remaining":39}} @@ -271,9 +271,9 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action {"table":"orderBookL2","action":"update","data":[{"symbol":"XBTUSD","id":8799343000,"side":"Buy","size":350544}]} */ - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = GetSymbolsAsync().Sync().ToArray(); + marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); } return ConnectWebSocket(string.Empty, (_socket, msg) => { @@ -293,7 +293,7 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action var size = 0m; foreach (var d in data) { - var symbol = d["symbol"].ToStringInvariant(); + var marketSymbol = d["symbol"].ToStringInvariant(); var id = d["id"].ConvertInvariant(); if (d["price"] == null) { @@ -330,25 +330,25 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action { book.Asks[depth.Price] = depth; } - book.Symbol = symbol; + book.MarketSymbol = marketSymbol; } - if (!string.IsNullOrEmpty(book.Symbol)) + if (!string.IsNullOrEmpty(book.MarketSymbol)) { callback(book); } return Task.CompletedTask; }, async (_socket) => { - if (symbols.Length == 0) + if (marketSymbols.Length == 0) { - symbols = (await GetSymbolsAsync()).ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - await _socket.SendMessageAsync(new { op = "subscribe", args = symbols.Select(s => "\"orderBookL2:" + this.NormalizeSymbol(s) + "\"").ToArray() }); + await _socket.SendMessageAsync(new { op = "subscribe", args = marketSymbols.Select(s => "\"orderBookL2:" + this.NormalizeMarketSymbol(s) + "\"").ToArray() }); }); } - protected override async Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { /* [ @@ -358,7 +358,7 @@ protected override async Task> OnGetCandlesAsync(strin List candles = new List(); string periodString = PeriodSecondsToString(periodSeconds); - string url = $"/trade/bucketed?binSize={periodString}&partial=false&symbol={symbol}&reverse=true" + symbol; + string url = $"/trade/bucketed?binSize={periodString}&partial=false&symbol={marketSymbol}&reverse=true" + marketSymbol; if (startDate != null) { url += "&startTime=" + startDate.Value.ToString("yyyy-MM-dd"); @@ -375,7 +375,7 @@ protected override async Task> OnGetCandlesAsync(strin var obj = await MakeJsonRequestAsync(url); foreach (var t in obj) { - candles.Add(this.ParseCandle(t, symbol, periodSeconds, "open", "high", "low", "close", "timestamp", TimestampType.Iso8601, "volume", "turnover", "vwap")); + candles.Add(this.ParseCandle(t, marketSymbol, periodSeconds, "open", "high", "low", "close", "timestamp", TimestampType.Iso8601, "volume", "turnover", "vwap")); } candles.Reverse(); @@ -475,15 +475,15 @@ protected override async Task> OnGetAmountsAvailable return amounts; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); Dictionary payload = await GetNoncePayloadAsync(); //string query = "/order"; string query = "/order?filter={\"open\": true}"; - if (!string.IsNullOrWhiteSpace(symbol)) + if (!string.IsNullOrWhiteSpace(marketSymbol)) { - query += "&symbol=" + NormalizeSymbol(symbol); + query += "&symbol=" + NormalizeMarketSymbol(marketSymbol); } JToken token = await MakeJsonRequestAsync(query, BaseUrl, payload, "GET"); foreach (JToken order in token) @@ -494,7 +494,7 @@ protected override async Task> OnGetOpenOrderDe return orders; } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { List orders = new List(); Dictionary payload = await GetNoncePayloadAsync(); @@ -508,7 +508,7 @@ protected override async Task OnGetOrderDetailsAsync(string return orders[0]; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { Dictionary payload = await GetNoncePayloadAsync(); payload["orderID"] = orderId; @@ -545,7 +545,7 @@ protected override async Task OnPlaceOrdersAsync(params E private void AddOrderToPayload(ExchangeOrderRequest order, Dictionary payload) { - payload["symbol"] = order.Symbol; + payload["symbol"] = order.MarketSymbol; payload["ordType"] = order.OrderType.ToStringInvariant(); payload["side"] = order.IsBuy ? "Buy" : "Sell"; payload["orderQty"] = order.Amount; @@ -601,7 +601,7 @@ private ExchangeOrderResult ParseOrder(JToken token) IsBuy = token["side"].ToStringInvariant().EqualsWithOption("Buy"), OrderDate = token["transactTime"].ConvertInvariant(), OrderId = token["orderID"].ToStringInvariant(), - Symbol = token["symbol"].ToStringInvariant() + MarketSymbol = token["symbol"].ToStringInvariant() }; // http://www.onixs.biz/fix-dictionary/5.0.SP2/tagNum_39.html diff --git a/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs b/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs index f67bb4bb..b1ae9b9c 100644 --- a/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs +++ b/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs @@ -60,15 +60,15 @@ public ExchangeBitfinexAPI() ["ZEC"] = "zcash", }; - SymbolSeparator = string.Empty; + MarketSymbolSeparator = string.Empty; } - public string NormalizeSymbolV1(string symbol) + public string NormalizeMarketSymbolV1(string marketSymbol) { - return (symbol ?? string.Empty).Replace("-", string.Empty).ToLowerInvariant(); + return (marketSymbol ?? string.Empty).Replace("-", string.Empty).ToLowerInvariant(); } - public async Task> GetOrderDetailsInternalV2(string url, string symbol = null) + public async Task> GetOrderDetailsInternalV2(string url, string marketSymbol = null) { Dictionary payload = await GetNoncePayloadAsync(); payload["limit"] = 250; @@ -80,7 +80,7 @@ public async Task> GetOrderDetailsInternalV2(st { foreach (JToken token in array) { - if (symbol == null || token[1].ToStringInvariant() == "t" + symbol.ToUpperInvariant()) + if (marketSymbol == null || token[1].ToStringInvariant() == "t" + marketSymbol.ToUpperInvariant()) { string lookup = token[1].ToStringInvariant().Substring(1).ToLowerInvariant(); if (!trades.TryGetValue(lookup, out List tradeList)) @@ -99,13 +99,13 @@ public override string PeriodSecondsToString(int seconds) return base.PeriodSecondsToString(seconds).Replace("d", "D"); // WTF Bitfinex, capital D??? } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { - var m = await GetSymbolsMetadataAsync(); - return m.Select(x => x.MarketName); + var m = await GetMarketSymbolsMetadataAsync(); + return m.Select(x => x.MarketSymbol); } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { var markets = new List(); JToken allPairs = await MakeJsonRequestAsync("/symbols_details", BaseUrlV1); @@ -115,29 +115,31 @@ protected override async Task> OnGetSymbolsMetadataA var market = new ExchangeMarket { IsActive = true, - MarketName = NormalizeSymbol(pair["pair"].ToStringInvariant()), + MarketSymbol = pair["pair"].ToStringInvariant(), MinTradeSize = pair["minimum_order_size"].ConvertInvariant(), - MaxTradeSize = pair["maximum_order_size"].ConvertInvariant() + MaxTradeSize = pair["maximum_order_size"].ConvertInvariant(), + MarginEnabled = pair["margin"].ConvertInvariant(false) }; - m = Regex.Match(market.MarketName, "^(BTC|USD|ETH|GBP|JPY|EUR|EOS)"); + var pairPropertyVal = pair["pair"].ToStringUpperInvariant(); + m = Regex.Match(pairPropertyVal, "^(BTC|USD|ETH|GBP|JPY|EUR|EOS)"); if (m.Success) { - market.MarketCurrency = m.Value; - market.BaseCurrency = market.MarketName.Substring(m.Length); + market.BaseCurrency = m.Value; + market.QuoteCurrency = pairPropertyVal.Substring(m.Length); } else { - m = Regex.Match(market.MarketName, "(BTC|USD|ETH|GBP|JPY|EUR|EOS)$"); + m = Regex.Match(pairPropertyVal, "(BTC|USD|ETH|GBP|JPY|EUR|EOS)$"); if (m.Success) { - market.MarketCurrency = market.MarketName.Substring(0, m.Index); - market.BaseCurrency = m.Value; + market.BaseCurrency = pairPropertyVal.Substring(0, m.Index); + market.QuoteCurrency = m.Value; } else { // TODO: Figure out a nicer way to handle newly added pairs - market.MarketCurrency = market.MarketName.Substring(0, 3); - market.BaseCurrency = market.MarketName.Substring(3); + market.BaseCurrency = pairPropertyVal.Substring(0, 3); + market.QuoteCurrency = pairPropertyVal.Substring(3); } } int pricePrecision = pair["price_precision"].ConvertInvariant(); @@ -147,23 +149,23 @@ protected override async Task> OnGetSymbolsMetadataA return markets; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken ticker = await MakeJsonRequestAsync("/ticker/t" + symbol); - return this.ParseTicker(ticker, symbol, 2, 0, 6, 7); + JToken ticker = await MakeJsonRequestAsync("/ticker/t" + marketSymbol); + return this.ParseTicker(ticker, marketSymbol, 2, 0, 6, 7); } protected override async Task>> OnGetTickersAsync() { List> tickers = new List>(); - IReadOnlyCollection symbols = (await GetSymbolsAsync()).ToArray(); - if (symbols != null && symbols.Count != 0) + IReadOnlyDictionary marketsBySymbol = (await GetMarketSymbolsMetadataAsync()).ToDictionary(market => market.MarketSymbol, market => market); + if (marketsBySymbol != null && marketsBySymbol.Count != 0) { StringBuilder symbolString = new StringBuilder(); - foreach (string symbol in symbols) + foreach (var marketSymbol in marketsBySymbol.Keys) { symbolString.Append('t'); - symbolString.Append(symbol.ToUpperInvariant()); + symbolString.Append(marketSymbol.ToUpperInvariant()); symbolString.Append(','); } symbolString.Length--; @@ -171,17 +173,35 @@ protected override async Task>> DateTime now = CryptoUtility.UtcNow; foreach (JArray array in token) { - tickers.Add(new KeyValuePair(array[0].ToStringInvariant().Substring(1), new ExchangeTicker + #region Return Values + //[ + // SYMBOL, + // BID, float Price of last highest bid + // BID_SIZE, float Sum of the 25 highest bid sizes + // ASK, float Price of last lowest ask + // ASK_SIZE, float Sum of the 25 lowest ask sizes + // DAILY_CHANGE, float Amount that the last price has changed since yesterday + // DAILY_CHANGE_PERC, float Amount that the price has changed expressed in percentage terms + // LAST_PRICE, float Price of the last trade + // VOLUME, float Daily volume + // HIGH, float Daily high + // LOW float Daily low + //] + #endregion + var marketSymbol = array[0].ToStringInvariant().Substring(1); + var market = marketsBySymbol[marketSymbol.ToLowerInvariant()]; + tickers.Add(new KeyValuePair(marketSymbol, new ExchangeTicker { + MarketSymbol = marketSymbol, Ask = array[3].ConvertInvariant(), Bid = array[1].ConvertInvariant(), Last = array[7].ConvertInvariant(), Volume = new ExchangeVolume { - BaseVolume = array[8].ConvertInvariant(), - BaseSymbol = array[0].ToStringInvariant(), - ConvertedVolume = array[8].ConvertInvariant() * array[7].ConvertInvariant(), - ConvertedSymbol = array[0].ToStringInvariant(), + QuoteCurrencyVolume = array[8].ConvertInvariant() * array[7].ConvertInvariant(), + QuoteCurrency = market.QuoteCurrency, + BaseCurrencyVolume = array[8].ConvertInvariant(), + BaseCurrency = market.BaseCurrency, Timestamp = now } })); @@ -190,7 +210,7 @@ protected override async Task>> return tickers; } - protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] symbols) + protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] marketSymbols) { Dictionary channelIdToSymbol = new Dictionary(); return ConnectWebSocket(string.Empty, (_socket, msg) => @@ -220,15 +240,15 @@ protected override IWebSocket OnGetTickersWebSocket(Action { - symbols = symbols == null || symbols.Length == 0 ? (await GetSymbolsAsync()).ToArray() : symbols; - foreach (var symbol in symbols) + marketSymbols = marketSymbols == null || marketSymbols.Length == 0 ? (await GetMarketSymbolsAsync()).ToArray() : marketSymbols; + foreach (var marketSymbol in marketSymbols) { - await _socket.SendMessageAsync(new { @event = "subscribe", channel = "ticker", pair = symbol }); + await _socket.SendMessageAsync(new { @event = "subscribe", channel = "ticker", pair = marketSymbol }); } }); } - protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] symbols) + protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) { Dictionary channelIdToSymbol = new Dictionary(); return ConnectWebSocket("/2", (_socket, msg) => //use websocket V2 (beta, but millisecond timestamp) @@ -270,9 +290,9 @@ protected override IWebSocket OnGetTradesWebSocket(Action { - foreach (var symbol in symbols) + foreach (var marketSymbol in marketSymbols) { - await _socket.SendMessageAsync(new { @event = "subscribe", channel = "trades", symbol }); + await _socket.SendMessageAsync(new { @event = "subscribe", channel = "trades", symbol = marketSymbol }); } }); } @@ -290,10 +310,10 @@ private ExchangeTrade ParseTradeWebSocket(JToken token) }; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { ExchangeOrderBook orders = new ExchangeOrderBook(); - decimal[][] books = await MakeJsonRequestAsync("/book/t" + symbol + "/P0?len=" + maxCount); + decimal[][] books = await MakeJsonRequestAsync("/book/t" + marketSymbol + "/P0?len=" + maxCount); foreach (decimal[] book in books) { if (book[2] > 0m) @@ -308,10 +328,10 @@ protected override async Task OnGetOrderBookAsync(string symb return orders; } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { const int maxCount = 100; - string baseUrl = "/trades/t" + symbol + "/hist?sort=" + (startDate == null ? "-1" : "1") + "&limit=" + maxCount; + string baseUrl = "/trades/t" + marketSymbol + "/hist?sort=" + (startDate == null ? "-1" : "1") + "&limit=" + maxCount; string url; List trades = new List(); decimal[][] tradeChunk; @@ -349,12 +369,12 @@ protected override async Task OnGetHistoricalTradesAsync(Func> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { // https://api.bitfinex.com/v2/candles/trade:1d:btcusd/hist?start=ms_start&end=ms_end List candles = new List(); string periodString = PeriodSecondsToString(periodSeconds); - string url = "/candles/trade:" + periodString + ":t" + symbol + "/hist?sort=1"; + string url = "/candles/trade:" + periodString + ":t" + marketSymbol + "/hist?sort=1"; if (startDate != null || endDate != null) { endDate = endDate ?? CryptoUtility.UtcNow; @@ -371,7 +391,7 @@ protected override async Task> OnGetCandlesAsync(strin /* MTS, OPEN, CLOSE, HIGH, LOW, VOL */ foreach (JToken candle in token) { - candles.Add(this.ParseCandle(candle, symbol, periodSeconds, 1, 3, 4, 2, 0, TimestampType.UnixMilliseconds, 5)); + candles.Add(this.ParseCandle(candle, marketSymbol, periodSeconds, 1, 3, 4, 2, 0, TimestampType.UnixMilliseconds, 5)); } return candles; @@ -415,10 +435,10 @@ protected override async Task> OnGetAmountsAvailable protected override async Task OnPlaceOrderAsync(ExchangeOrderRequest order) { - string symbol = NormalizeSymbolV1(order.Symbol); + string marketSymbol = NormalizeMarketSymbolV1(order.MarketSymbol); Dictionary payload = await GetNoncePayloadAsync(); - payload["symbol"] = symbol; - payload["amount"] = (await ClampOrderQuantity(symbol, order.Amount)).ToStringInvariant(); + payload["symbol"] = marketSymbol; + payload["amount"] = (await ClampOrderQuantity(marketSymbol, order.Amount)).ToStringInvariant(); payload["side"] = (order.IsBuy ? "buy" : "sell"); if (order.IsMargin) @@ -432,7 +452,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd if (order.OrderType != OrderType.Market) { - payload["price"] = (await ClampOrderPrice(symbol, order.Price)).ToStringInvariant(); + payload["price"] = (await ClampOrderPrice(marketSymbol, order.Price)).ToStringInvariant(); } else { @@ -443,7 +463,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return ParseOrder(obj); } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { if (string.IsNullOrWhiteSpace(orderId)) { @@ -456,23 +476,23 @@ protected override async Task OnGetOrderDetailsAsync(string return ParseOrder(result); } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { - return await GetOrderDetailsInternalAsync("/orders", symbol); + return await GetOrderDetailsInternalAsync("/orders", marketSymbol); } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { - if (string.IsNullOrWhiteSpace(symbol)) + if (string.IsNullOrWhiteSpace(marketSymbol)) { // HACK: Bitfinex does not provide a way to get all historical order details beyond a few days in one call, so we have to // get the historical details one by one for each symbol. - var symbols = (await GetSymbolsAsync()).Where(s => s.IndexOf("usd", StringComparison.OrdinalIgnoreCase) < 0 && s.IndexOf("btc", StringComparison.OrdinalIgnoreCase) >= 0); + var symbols = (await GetMarketSymbolsAsync()).Where(s => s.IndexOf("usd", StringComparison.OrdinalIgnoreCase) < 0 && s.IndexOf("btc", StringComparison.OrdinalIgnoreCase) >= 0); return await GetOrderDetailsInternalV1(symbols, afterDate); } // retrieve orders for the one symbol - return await GetOrderDetailsInternalV1(new string[] { symbol }, afterDate); + return await GetOrderDetailsInternalV1(new string[] { marketSymbol }, afterDate); } protected override IWebSocket OnGetCompletedOrderDetailsWebSocket(Action callback) @@ -505,30 +525,30 @@ protected override IWebSocket OnGetCompletedOrderDetailsWebSocket(Action payload = await GetNoncePayloadAsync(); payload["order_id"] = orderId.ConvertInvariant(); await MakeJsonRequestAsync("/order/cancel", BaseUrlV1, payload); } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { - if (symbol.Length == 0) + if (currency.Length == 0) { - throw new ArgumentNullException(nameof(symbol)); + throw new ArgumentNullException(nameof(currency)); } // IOTA addresses should never be used more than once - if (symbol.Equals("MIOTA", StringComparison.OrdinalIgnoreCase)) + if (currency.Equals("MIOTA", StringComparison.OrdinalIgnoreCase)) { forceRegenerate = true; } // symbol needs to be translated to full name of coin: bitcoin/litecoin/ethereum - if (!DepositMethodLookup.TryGetValue(symbol, out string fullName)) + if (!DepositMethodLookup.TryGetValue(currency, out string fullName)) { - fullName = symbol.ToLowerInvariant(); + fullName = currency.ToLowerInvariant(); } Dictionary payload = await GetNoncePayloadAsync(); @@ -539,7 +559,7 @@ protected override async Task OnGetDepositAddressAsync(s JToken result = await MakeJsonRequestAsync("/deposit/new", BaseUrlV1, payload, "POST"); var details = new ExchangeDepositDetails { - Symbol = result["currency"].ToStringInvariant(), + Currency = result["currency"].ToStringInvariant(), }; if (result["address_pool"] != null) { @@ -555,17 +575,17 @@ protected override async Task OnGetDepositAddressAsync(s } /// Gets the deposit history for a symbol - /// The symbol to check. Must be specified. + /// The symbol to check. Must be specified. /// Collection of ExchangeCoinTransfers - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { - if (symbol.Length == 0) + if (currency.Length == 0) { - throw new ArgumentNullException(nameof(symbol)); + throw new ArgumentNullException(nameof(currency)); } Dictionary payload = await GetNoncePayloadAsync(); - payload["currency"] = symbol; + payload["currency"] = currency; JToken result = await MakeJsonRequestAsync("/history/movements", BaseUrlV1, payload, "POST"); var transactions = new List(); @@ -580,7 +600,7 @@ protected override async Task> OnGetDepositHist { PaymentId = token["id"].ToStringInvariant(), BlockchainTxId = token["txid"].ToStringInvariant(), - Symbol = token["currency"].ToStringUpperInvariant(), + Currency = token["currency"].ToStringUpperInvariant(), Notes = token["description"].ToStringInvariant() + ", method: " + token["method"].ToStringInvariant(), Amount = token["amount"].ConvertInvariant(), Address = token["address"].ToStringInvariant() @@ -700,16 +720,16 @@ protected override Task> OnGetCurr throw new NotSupportedException("Bitfinex does not provide data about its currencies via the API"); } - private async Task> GetOrderDetailsInternalAsync(string url, string symbol = null) + private async Task> GetOrderDetailsInternalAsync(string url, string marketSymbol = null) { List orders = new List(); - symbol = NormalizeSymbolV1(symbol); + marketSymbol = NormalizeMarketSymbolV1(marketSymbol); JToken result = await MakeJsonRequestAsync(url, BaseUrlV1, await GetNoncePayloadAsync()); if (result is JArray array) { foreach (JToken token in array) { - if (symbol == null || token["symbol"].ToStringInvariant() == symbol) + if (marketSymbol == null || token["symbol"].ToStringInvariant() == marketSymbol) { orders.Add(ParseOrder(token)); } @@ -718,12 +738,12 @@ private async Task> GetOrderDetailsInternalAsyn return orders; } - private async Task> GetOrderDetailsInternalV1(IEnumerable symbols, DateTime? afterDate) + private async Task> GetOrderDetailsInternalV1(IEnumerable marketSymbols, DateTime? afterDate) { Dictionary orders = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (string symbol in symbols) + foreach (string marketSymbol in marketSymbols) { - string normalizedSymbol = NormalizeSymbol(symbol); + string normalizedSymbol = NormalizeMarketSymbol(marketSymbol); Dictionary payload = await GetNoncePayloadAsync(); payload["symbol"] = normalizedSymbol; payload["limit_trades"] = 250; @@ -767,7 +787,7 @@ private ExchangeOrderResult ParseOrder(JToken order) OrderId = order["id"].ToStringInvariant(), Result = (amountFilled == amount ? ExchangeAPIOrderResult.Filled : (amountFilled == 0 ? ExchangeAPIOrderResult.Pending : ExchangeAPIOrderResult.FilledPartially)), OrderDate = CryptoUtility.UnixTimeStampToDateTimeSeconds(order["timestamp"].ConvertInvariant()), - Symbol = order["symbol"].ToStringInvariant(), + MarketSymbol = order["symbol"].ToStringInvariant(), IsBuy = order["side"].ToStringInvariant() == "buy" }; } @@ -802,7 +822,7 @@ private ExchangeOrderResult ParseOrderWebSocket(JToken order) OrderDate = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(order[8].ConvertInvariant()), OrderId = order[0].ToStringInvariant(), Result = ExchangeAPIOrderResult.Filled, - Symbol = order[1].ToStringInvariant() + MarketSymbol = order[1].ToStringInvariant() }; } @@ -830,7 +850,7 @@ FEE_CURRENCY string Fee currency ExchangeOrderResult order = new ExchangeOrderResult { Result = ExchangeAPIOrderResult.Filled }; foreach (JToken trade in kv.Value) { - ExchangeOrderResult append = new ExchangeOrderResult { Symbol = kv.Key, OrderId = trade[3].ToStringInvariant() }; + ExchangeOrderResult append = new ExchangeOrderResult { MarketSymbol = kv.Key, OrderId = trade[3].ToStringInvariant() }; append.Amount = append.AmountFilled = Math.Abs(trade[4].ConvertInvariant()); append.Price = trade[7].ConvertInvariant(); append.AveragePrice = trade[5].ConvertInvariant(); @@ -867,7 +887,7 @@ private ExchangeOrderResult ParseTrade(JToken trade, string symbol) OrderDate = CryptoUtility.UnixTimeStampToDateTimeSeconds(trade["timestamp"].ConvertInvariant()), OrderId = trade["order_id"].ToStringInvariant(), Result = ExchangeAPIOrderResult.Filled, - Symbol = symbol + MarketSymbol = symbol }; } diff --git a/ExchangeSharp/API/Exchanges/Bithumb/ExchangeBithumbAPI.cs b/ExchangeSharp/API/Exchanges/Bithumb/ExchangeBithumbAPI.cs index 666309d7..f1642f70 100644 --- a/ExchangeSharp/API/Exchanges/Bithumb/ExchangeBithumbAPI.cs +++ b/ExchangeSharp/API/Exchanges/Bithumb/ExchangeBithumbAPI.cs @@ -24,28 +24,28 @@ public sealed partial class ExchangeBithumbAPI : ExchangeAPI public ExchangeBithumbAPI() { - SymbolIsUppercase = true; + MarketSymbolIsUppercase = true; } - public override string NormalizeSymbol(string symbol) + public override string NormalizeMarketSymbol(string marketSymbol) { - symbol = base.NormalizeSymbol(symbol); - int pos = symbol.IndexOf(SymbolSeparator); + marketSymbol = base.NormalizeMarketSymbol(marketSymbol); + int pos = marketSymbol.IndexOf(MarketSymbolSeparator); if (pos >= 0) { - symbol = symbol.Substring(0, pos); + marketSymbol = marketSymbol.Substring(0, pos); } - return symbol; + return marketSymbol; } - public override string ExchangeSymbolToGlobalSymbol(string symbol) + public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) { - return "KRW" + GlobalSymbolSeparator + symbol; + return "KRW" + GlobalMarketSymbolSeparator + marketSymbol; } - public override string GlobalSymbolToExchangeSymbol(string symbol) + public override string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol) { - return symbol.Substring(symbol.IndexOf(GlobalSymbolSeparator) + 1); + return marketSymbol.Substring(marketSymbol.IndexOf(GlobalMarketSymbolSeparator) + 1); } private string StatusToError(string status) @@ -73,36 +73,36 @@ protected override JToken CheckJsonResponse(JToken result) return result["data"]; } - private async Task> MakeRequestBithumbAsync(string symbol, string subUrl) + private async Task> MakeRequestBithumbAsync(string marketSymbol, string subUrl) { - symbol = NormalizeSymbol(symbol); - JToken obj = await MakeJsonRequestAsync(subUrl.Replace("$SYMBOL$", symbol ?? string.Empty)); - return new Tuple(obj, symbol); + marketSymbol = NormalizeMarketSymbol(marketSymbol); + JToken obj = await MakeJsonRequestAsync(subUrl.Replace("$SYMBOL$", marketSymbol ?? string.Empty)); + return new Tuple(obj, marketSymbol); } - private ExchangeTicker ParseTicker(string symbol, JToken data) + private ExchangeTicker ParseTicker(string marketSymbol, JToken data) { - return this.ParseTicker(data, symbol, "sell_price", "buy_price", "buy_price", "average_price", "units_traded", "date", TimestampType.UnixMilliseconds); + return this.ParseTicker(data, marketSymbol, "sell_price", "buy_price", "buy_price", "average_price", "units_traded", "date", TimestampType.UnixMilliseconds); } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { - List symbols = new List(); - string symbol = "all"; - var data = await MakeRequestBithumbAsync(symbol, "/public/ticker/$SYMBOL$"); + List marketSymbols = new List(); + string marketSymbol = "all"; + var data = await MakeRequestBithumbAsync(marketSymbol, "/public/ticker/$SYMBOL$"); foreach (JProperty token in data.Item1) { if (token.Name != "date") { - symbols.Add(token.Name); + marketSymbols.Add(token.Name); } } - return symbols; + return marketSymbols; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - var data = await MakeRequestBithumbAsync(symbol, "/public/ticker/$SYMBOL$"); + var data = await MakeRequestBithumbAsync(marketSymbol, "/public/ticker/$SYMBOL$"); return ParseTicker(data.Item2, data.Item1); } @@ -124,9 +124,9 @@ protected override async Task>> return tickers; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - var data = await MakeRequestBithumbAsync(symbol, "/public/orderbook/$SYMBOL$"); + var data = await MakeRequestBithumbAsync(marketSymbol, "/public/orderbook/$SYMBOL$"); return ExchangeAPIExtensions.ParseOrderBookFromJTokenDictionaries(data.Item1, amount: "quantity", sequence: "timestamp", maxCount: maxCount); } diff --git a/ExchangeSharp/API/Exchanges/Bitstamp/ExchangeBitstampAPI.cs b/ExchangeSharp/API/Exchanges/Bitstamp/ExchangeBitstampAPI.cs index 618a5fbc..ff74a66b 100644 --- a/ExchangeSharp/API/Exchanges/Bitstamp/ExchangeBitstampAPI.cs +++ b/ExchangeSharp/API/Exchanges/Bitstamp/ExchangeBitstampAPI.cs @@ -42,8 +42,8 @@ public ExchangeBitstampAPI() { RequestContentType = "application/x-www-form-urlencoded"; NonceStyle = NonceStyle.UnixMilliseconds; - SymbolIsUppercase = false; - SymbolSeparator = string.Empty; + MarketSymbolIsUppercase = false; + MarketSymbolSeparator = string.Empty; } /// @@ -85,7 +85,7 @@ private async Task MakeBitstampRequestAsync(string subUrl) return token; } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); foreach (JToken token in (await MakeBitstampRequestAsync("/trading-pairs-info"))) @@ -95,23 +95,23 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { // {"high": "0.10948945", "last": "0.10121817", "timestamp": "1513387486", "bid": "0.10112165", "vwap": "0.09958913", "volume": "9954.37332614", "low": "0.09100000", "ask": "0.10198408", "open": "0.10250028"} - JToken token = await MakeBitstampRequestAsync("/ticker/" + symbol); - return this.ParseTicker(token, symbol, "ask", "bid", "last", "volume", null, "timestamp", TimestampType.UnixSeconds); + JToken token = await MakeBitstampRequestAsync("/ticker/" + marketSymbol); + return this.ParseTicker(token, marketSymbol, "ask", "bid", "last", "volume", null, "timestamp", TimestampType.UnixSeconds); } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - JToken token = await MakeBitstampRequestAsync("/order_book/" + symbol); + JToken token = await MakeBitstampRequestAsync("/order_book/" + marketSymbol); return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token, maxCount: maxCount); } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { // [{"date": "1513387997", "tid": "33734815", "price": "0.01724547", "type": "1", "amount": "5.56481714"}] - JToken token = await MakeBitstampRequestAsync("/transactions/" + symbol); + JToken token = await MakeBitstampRequestAsync("/transactions/" + marketSymbol); List trades = new List(); foreach (JToken trade in token) { @@ -169,7 +169,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd { string action = order.IsBuy ? "buy" : "sell"; string market = order.OrderType == OrderType.Market ? "/market" : ""; - string url = $"/{action}{market}/{order.Symbol}/"; + string url = $"/{action}{market}/{order.MarketSymbol}/"; Dictionary payload = await GetNoncePayloadAsync(); if (order.OrderType != OrderType.Market) @@ -192,11 +192,11 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd OrderDate = CryptoUtility.UtcNow, OrderId = responseObject["id"].ToStringInvariant(), IsBuy = order.IsBuy, - Symbol = order.Symbol + MarketSymbol = order.MarketSymbol }; } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { //{ // "status": "Finished", @@ -229,31 +229,31 @@ protected override async Task OnGetOrderDetailsAsync(string JObject first = transactions.First() as JObject; List excludeStrings = new List() { "tid", "price", "fee", "datetime", "type", "btc", "usd", "eur" }; - string baseCurrency; - string marketCurrency = first.Properties().FirstOrDefault(p => !excludeStrings.Contains(p.Name, StringComparer.InvariantCultureIgnoreCase))?.Name; - if (string.IsNullOrWhiteSpace(marketCurrency)) + string quoteCurrency; + string baseCurrency = first.Properties().FirstOrDefault(p => !excludeStrings.Contains(p.Name, StringComparer.InvariantCultureIgnoreCase))?.Name; + if (string.IsNullOrWhiteSpace(baseCurrency)) { // the only 2 cases are BTC-USD and BTC-EUR - marketCurrency = "btc"; + baseCurrency = "btc"; excludeStrings.RemoveAll(s => s.Equals("usd") || s.Equals("eur")); - baseCurrency = first.Properties().FirstOrDefault(p => !excludeStrings.Contains(p.Name, StringComparer.InvariantCultureIgnoreCase))?.Name; + quoteCurrency = first.Properties().FirstOrDefault(p => !excludeStrings.Contains(p.Name, StringComparer.InvariantCultureIgnoreCase))?.Name; } else { excludeStrings.RemoveAll(s => s.Equals("usd") || s.Equals("eur") || s.Equals("btc")); - excludeStrings.Add(marketCurrency); - baseCurrency = first.Properties().FirstOrDefault(p => !excludeStrings.Contains(p.Name, StringComparer.InvariantCultureIgnoreCase))?.Name; + excludeStrings.Add(baseCurrency); + quoteCurrency = first.Properties().FirstOrDefault(p => !excludeStrings.Contains(p.Name, StringComparer.InvariantCultureIgnoreCase))?.Name; } - string _symbol = $"{marketCurrency}-{baseCurrency}"; + string _symbol = $"{baseCurrency}-{quoteCurrency}"; - decimal amountFilled = 0, spentBaseCurrency = 0, price = 0; + decimal amountFilled = 0, spentQuoteCurrency = 0, price = 0; foreach (var t in transactions) { int type = t["type"].ConvertInvariant(); if (type != 2) { continue; } - spentBaseCurrency += t[baseCurrency].ConvertInvariant(); - amountFilled += t[marketCurrency].ConvertInvariant(); + spentQuoteCurrency += t[quoteCurrency].ConvertInvariant(); + amountFilled += t[baseCurrency].ConvertInvariant(); //set price only one time if (price == 0) { @@ -265,13 +265,13 @@ protected override async Task OnGetOrderDetailsAsync(string return new ExchangeOrderResult() { AmountFilled = amountFilled, - Symbol = _symbol, - AveragePrice = spentBaseCurrency / amountFilled, + MarketSymbol = _symbol, + AveragePrice = spentQuoteCurrency / amountFilled, Price = price, }; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); // TODO: Bitstamp bug: bad request if url contains symbol, so temporarily using url for all symbols @@ -282,7 +282,7 @@ protected override async Task> OnGetOpenOrderDe { //This request doesn't give info about amount filled, use GetOrderDetails(orderId) string tokenSymbol = token["currency_pair"].ToStringLowerInvariant().Replace("/", ""); - if (!string.IsNullOrWhiteSpace(tokenSymbol) && !string.IsNullOrWhiteSpace(symbol) && !tokenSymbol.Equals(symbol, StringComparison.InvariantCultureIgnoreCase)) + if (!string.IsNullOrWhiteSpace(tokenSymbol) && !string.IsNullOrWhiteSpace(marketSymbol) && !tokenSymbol.Equals(marketSymbol, StringComparison.InvariantCultureIgnoreCase)) { continue; } @@ -293,19 +293,19 @@ protected override async Task> OnGetOpenOrderDe IsBuy = token["type"].ConvertInvariant() == 0, Price = token["price"].ConvertInvariant(), Amount = token["amount"].ConvertInvariant(), - Symbol = tokenSymbol ?? symbol + MarketSymbol = tokenSymbol ?? marketSymbol }); } return orders; } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { // TODO: Bitstamp bug: bad request if url contains symbol, so temporarily using url for all symbols // string url = string.IsNullOrWhiteSpace(symbol) ? "/user_transactions/" : "/user_transactions/" + symbol; string url = "/user_transactions/"; JToken result = await MakeJsonRequestAsync(url, null, await GetNoncePayloadAsync(), "POST"); - List orders = new List(); + List transactions = new List(); foreach (var transaction in result as JArray) { int type = transaction["type"].ConvertInvariant(); @@ -315,35 +315,38 @@ protected override async Task> OnGetCompletedOr string tradingPair = ((JObject)transaction).Properties().FirstOrDefault(p => !p.Name.Equals("order_id", StringComparison.InvariantCultureIgnoreCase) && p.Name.Contains("_"))?.Name.Replace("_", "-"); - if (!string.IsNullOrWhiteSpace(tradingPair) && !string.IsNullOrWhiteSpace(symbol) && !NormalizeSymbol(tradingPair).Equals(symbol)) + if (!string.IsNullOrWhiteSpace(tradingPair) && !string.IsNullOrWhiteSpace(marketSymbol) && !NormalizeMarketSymbol(tradingPair).Equals(marketSymbol)) { continue; } - string marketCurrency, baseCurrency; - baseCurrency = tradingPair.Trim().Substring(tradingPair.Length - 3).ToLowerInvariant(); - marketCurrency = tradingPair.Trim().ToLowerInvariant().Replace(baseCurrency, "").Replace("-", "").Replace("_", ""); - decimal resultMarketCurrency = transaction[marketCurrency].ConvertInvariant(); + var quoteCurrency = tradingPair.Trim().Substring(tradingPair.Length - 3).ToLowerInvariant(); + var baseCurrency = tradingPair.Trim().ToLowerInvariant().Replace(quoteCurrency, "").Replace("-", "").Replace("_", ""); + + decimal resultBaseCurrency = transaction[baseCurrency].ConvertInvariant(); ExchangeOrderResult order = new ExchangeOrderResult() { OrderId = transaction["order_id"].ToStringInvariant(), - IsBuy = resultMarketCurrency > 0, - Symbol = NormalizeSymbol(tradingPair), + IsBuy = resultBaseCurrency > 0, + Fees = transaction["fee"].ConvertInvariant(), + FeesCurrency = quoteCurrency.ToStringUpperInvariant(), + MarketSymbol = NormalizeMarketSymbol(tradingPair), OrderDate = transaction["datetime"].ToDateTimeInvariant(), - AmountFilled = Math.Abs(resultMarketCurrency), - AveragePrice = Math.Abs(transaction[baseCurrency].ConvertInvariant() / resultMarketCurrency) + AmountFilled = Math.Abs(resultBaseCurrency), + AveragePrice = transaction[$"{baseCurrency}_{quoteCurrency}"].ConvertInvariant() }; - orders.Add(order); + transactions.Add(order); } // at this point one transaction transformed into one order, we need to consolidate parts into order // group by order id - var groupings = orders.GroupBy(o => o.OrderId); + var groupings = transactions.GroupBy(o => o.OrderId); + List orders = new List(); foreach (var group in groupings) { - decimal spentBaseCurrency = group.Sum(o => o.AveragePrice * o.AmountFilled); + decimal spentQuoteCurrency = group.Sum(o => o.AveragePrice * o.AmountFilled); ExchangeOrderResult order = group.First(); order.AmountFilled = group.Sum(o => o.AmountFilled); - order.AveragePrice = spentBaseCurrency / order.AmountFilled; + order.AveragePrice = spentQuoteCurrency / order.AmountFilled; order.Price = order.AveragePrice; orders.Add(order); } @@ -351,7 +354,7 @@ protected override async Task> OnGetCompletedOr return orders; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { if (string.IsNullOrWhiteSpace(orderId)) { diff --git a/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI.cs b/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI.cs index 8adbd1fd..a6b047b5 100644 --- a/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI.cs +++ b/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI.cs @@ -65,7 +65,7 @@ public ExchangeBittrexAPI() "WAVES_ASSET", }; - SymbolIsReversed = true; + MarketSymbolIsReversed = true; WebSocketOrderBookType = WebSocketOrderBookType.DeltasOnly; } @@ -124,7 +124,7 @@ private ExchangeOrderResult ParseOrder(JToken token) order.Result = ExchangeAPIOrderResult.FilledPartially; } order.OrderDate = token["Opened"].ToDateTimeInvariant(token["TimeStamp"].ToDateTimeInvariant()); - order.Symbol = token["Exchange"].ToStringInvariant(); + order.MarketSymbol = token["Exchange"].ToStringInvariant(); order.Fees = token["Commission"].ConvertInvariant(); // This is always in the base pair (e.g. BTC, ETH, USDT) string exchangePair = token["Exchange"].ToStringInvariant(); @@ -198,7 +198,7 @@ protected override async Task> OnG /// Get exchange symbols including available metadata such as min trade size and whether the market is active /// /// Collection of ExchangeMarkets - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { var markets = new List(); JToken array = await MakeJsonRequestAsync("/public/getmarkets"); @@ -209,10 +209,12 @@ protected override async Task> OnGetSymbolsMetadataA { var market = new ExchangeMarket { - BaseCurrency = token["BaseCurrency"].ToStringUpperInvariant(), + //NOTE: Bittrex is weird in that they call the QuoteCurrency the "BaseCurrency" and the BaseCurrency the "MarketCurrency". + QuoteCurrency = token["BaseCurrency"].ToStringUpperInvariant(), IsActive = token["IsActive"].ConvertInvariant(), - MarketCurrency = token["MarketCurrency"].ToStringUpperInvariant(), - MarketName = token["MarketName"].ToStringUpperInvariant(), + BaseCurrency = token["MarketCurrency"].ToStringUpperInvariant(), + //NOTE: They also reverse the order of the currencies in the MarketName + MarketSymbol = token["MarketName"].ToStringUpperInvariant(), MinTradeSize = token["MinTradeSize"].ConvertInvariant(), MinPrice = StepSize, PriceStepSize = StepSize, @@ -225,44 +227,46 @@ protected override async Task> OnGetSymbolsMetadataA return markets; } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { - return (await GetSymbolsMetadataAsync()).Select(x => x.MarketName); + return (await GetMarketSymbolsMetadataAsync()).Select(x => x.MarketSymbol); } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken ticker = await MakeJsonRequestAsync("/public/getmarketsummary?market=" + symbol); - return this.ParseTicker(ticker[0], symbol, "Ask", "Bid", "Last", "BaseVolume", "Volume", "Timestamp", TimestampType.Iso8601); + JToken ticker = await MakeJsonRequestAsync("/public/getmarketsummary?market=" + marketSymbol); + //NOTE: Bittrex uses the term "BaseVolume" when referring to the QuoteCurrencyVolume + return this.ParseTicker(ticker[0], marketSymbol, "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); } protected override async Task>> OnGetTickersAsync() { JToken tickers = await MakeJsonRequestAsync("public/getmarketsummaries"); - string symbol; + string marketSymbol; List> tickerList = new List>(); foreach (JToken ticker in tickers) { - symbol = ticker["MarketName"].ToStringInvariant(); - ExchangeTicker tickerObj = this.ParseTicker(ticker, symbol, "Ask", "Bid", "Last", "BaseVolume", "Volume", "Timestamp", TimestampType.Iso8601); - tickerList.Add(new KeyValuePair(symbol, tickerObj)); + marketSymbol = ticker["MarketName"].ToStringInvariant(); + //NOTE: Bittrex uses the term "BaseVolume" when referring to the QuoteCurrencyVolume + ExchangeTicker tickerObj = this.ParseTicker(ticker, marketSymbol, "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); + tickerList.Add(new KeyValuePair(marketSymbol, tickerObj)); } return tickerList; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - JToken token = await MakeJsonRequestAsync("public/getorderbook?market=" + symbol + "&type=both&limit_bids=" + maxCount + "&limit_asks=" + maxCount); + JToken token = await MakeJsonRequestAsync("public/getorderbook?market=" + marketSymbol + "&type=both&limit_bids=" + maxCount + "&limit_asks=" + maxCount); return ExchangeAPIExtensions.ParseOrderBookFromJTokenDictionaries(token, "sell", "buy", "Rate", "Quantity", maxCount: maxCount); } /// Gets the deposit history for a symbol - /// The symbol to check. May be null. + /// The symbol to check. May be null. /// Collection of ExchangeTransactions - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { var transactions = new List(); - string url = $"/account/getdeposithistory{(string.IsNullOrWhiteSpace(symbol) ? string.Empty : $"?currency={symbol}")}"; + string url = $"/account/getdeposithistory{(string.IsNullOrWhiteSpace(currency) ? string.Empty : $"?currency={currency}")}"; JToken result = await MakeJsonRequestAsync(url, null, await GetNoncePayloadAsync()); foreach (JToken token in result) { @@ -270,7 +274,7 @@ protected override async Task> OnGetDepositHist { Amount = token["Amount"].ConvertInvariant(), Address = token["CryptoAddress"].ToStringInvariant(), - Symbol = token["Currency"].ToStringInvariant(), + Currency = token["Currency"].ToStringInvariant(), PaymentId = token["Id"].ToStringInvariant(), BlockchainTxId = token["TxId"].ToStringInvariant(), Status = TransactionStatus.Complete // As soon as it shows up in this list it is complete (verified manually) @@ -285,11 +289,11 @@ protected override async Task> OnGetDepositHist return transactions; } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { // TODO: sinceDateTime is ignored // https://bittrex.com/Api/v2.0/pub/market/GetTicks?marketName=BTC-WAVES&tickInterval=oneMin&_=1499127220008 - string baseUrl = "/pub/market/GetTicks?marketName=" + symbol + "&tickInterval=oneMin"; + string baseUrl = "/pub/market/GetTicks?marketName=" + marketSymbol + "&tickInterval=oneMin"; string url; List trades = new List(); while (true) @@ -334,10 +338,10 @@ protected override async Task OnGetHistoricalTradesAsync(Func> OnGetRecentTradesAsync(string symbol) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol) { List trades = new List(); - string baseUrl = "/public/getmarkethistory?market=" + symbol; + string baseUrl = "/public/getmarkethistory?market=" + marketSymbol; JToken array = await MakeJsonRequestAsync(baseUrl); foreach (JToken token in array) { @@ -346,7 +350,7 @@ protected override async Task> OnGetRecentTradesAsync return trades; } - protected override async Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { if (limit != null) { @@ -359,12 +363,13 @@ protected override async Task> OnGetCandlesAsync(strin List candles = new List(); endDate = endDate ?? CryptoUtility.UtcNow; startDate = startDate ?? endDate.Value.Subtract(TimeSpan.FromDays(1.0)); - JToken result = await MakeJsonRequestAsync("pub/market/GetTicks?marketName=" + symbol + "&tickInterval=" + periodString, BaseUrl2); + JToken result = await MakeJsonRequestAsync("pub/market/GetTicks?marketName=" + marketSymbol + "&tickInterval=" + periodString, BaseUrl2); if (result is JArray array) { foreach (JToken jsonCandle in array) { - MarketCandle candle = this.ParseCandle(jsonCandle, symbol, periodSeconds, "O", "H", "L", "C", "T", TimestampType.Iso8601, "BV", "V"); + //NOTE: Bittrex uses the term "BaseVolume" when referring to the QuoteCurrencyVolume + MarketCandle candle = this.ParseCandle(jsonCandle, marketSymbol, periodSeconds, "O", "H", "L", "C", "T", TimestampType.Iso8601, "V", "BV"); if (candle.Timestamp >= startDate && candle.Timestamp <= endDate) { candles.Add(candle); @@ -414,9 +419,9 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd throw new NotSupportedException("Order type " + order.OrderType + " not supported"); } - decimal orderAmount = await ClampOrderQuantity(order.Symbol, order.Amount); - decimal orderPrice = await ClampOrderPrice(order.Symbol, order.Price); - string url = (order.IsBuy ? "/market/buylimit" : "/market/selllimit") + "?market=" + order.Symbol + "&quantity=" + + decimal orderAmount = await ClampOrderQuantity(order.MarketSymbol, order.Amount); + decimal orderPrice = await ClampOrderPrice(order.MarketSymbol, order.Price); + string url = (order.IsBuy ? "/market/buylimit" : "/market/selllimit") + "?market=" + order.MarketSymbol + "&quantity=" + orderAmount.ToStringInvariant() + "&rate=" + orderPrice.ToStringInvariant(); foreach (var kv in order.ExtraParameters) { @@ -431,12 +436,12 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd OrderDate = CryptoUtility.UtcNow, OrderId = orderId, Result = ExchangeAPIOrderResult.Pending, - Symbol = order.Symbol, + MarketSymbol = order.MarketSymbol, Price = order.Price }; } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { if (string.IsNullOrWhiteSpace(orderId)) { @@ -448,10 +453,10 @@ protected override async Task OnGetOrderDetailsAsync(string return ParseOrder(result); } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); - string url = "/market/getopenorders" + (string.IsNullOrWhiteSpace(symbol) ? string.Empty : "?market=" + NormalizeSymbol(symbol)); + string url = "/market/getopenorders" + (string.IsNullOrWhiteSpace(marketSymbol) ? string.Empty : "?market=" + NormalizeMarketSymbol(marketSymbol)); JToken result = await MakeJsonRequestAsync(url, null, await GetNoncePayloadAsync()); foreach (JToken token in result.Children()) { @@ -461,10 +466,10 @@ protected override async Task> OnGetOpenOrderDe return orders; } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { List orders = new List(); - string url = "/account/getorderhistory" + (string.IsNullOrWhiteSpace(symbol) ? string.Empty : "?market=" + NormalizeSymbol(symbol)); + string url = "/account/getorderhistory" + (string.IsNullOrWhiteSpace(marketSymbol) ? string.Empty : "?market=" + NormalizeMarketSymbol(marketSymbol)); JToken result = await MakeJsonRequestAsync(url, null, await GetNoncePayloadAsync()); foreach (JToken token in result.Children()) { @@ -484,7 +489,7 @@ protected override async Task OnWithdrawAsync(Exchan { // Example: https://bittrex.com/api/v1.1/account/withdraw?apikey=API_KEY¤cy=EAC&quantity=20.40&address=EAC_ADDRESS - string url = $"/account/withdraw?currency={NormalizeSymbol(withdrawalRequest.Currency)}&quantity={withdrawalRequest.Amount.ToStringInvariant()}&address={withdrawalRequest.Address}"; + string url = $"/account/withdraw?currency={NormalizeMarketSymbol(withdrawalRequest.Currency)}&quantity={withdrawalRequest.Amount.ToStringInvariant()}&address={withdrawalRequest.Address}"; if (!string.IsNullOrWhiteSpace(withdrawalRequest.AddressTag)) { url += $"&paymentid={withdrawalRequest.AddressTag}"; @@ -500,7 +505,7 @@ protected override async Task OnWithdrawAsync(Exchan return withdrawalResponse; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { await MakeJsonRequestAsync("/market/cancel?uuid=" + orderId, null, await GetNoncePayloadAsync()); } @@ -509,28 +514,28 @@ protected override async Task OnCancelOrderAsync(string orderId, string symbol = /// Gets the address to deposit to and applicable details. /// If one does not exist, the call will fail and return ADDRESS_GENERATING until one is available. /// - /// Symbol to get address for. + /// Currency to get address for. /// (ignored) Bittrex does not support regenerating deposit addresses. /// /// Deposit address details (including tag if applicable, such as with XRP) /// - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { IReadOnlyDictionary updatedCurrencies = (await GetCurrenciesAsync()); - string url = "/account/getdepositaddress?currency=" + NormalizeSymbol(symbol); + string url = "/account/getdepositaddress?currency=" + NormalizeMarketSymbol(currency); JToken result = await MakeJsonRequestAsync(url, null, await GetNoncePayloadAsync()); // NOTE API 1.1 does not include the the static wallet address for currencies with tags such as XRP & NXT (API 2.0 does!) // We are getting the static addresses via the GetCurrencies() api. ExchangeDepositDetails depositDetails = new ExchangeDepositDetails { - Symbol = result["Currency"].ToStringInvariant(), + Currency = result["Currency"].ToStringInvariant(), }; - if (!updatedCurrencies.TryGetValue(depositDetails.Symbol, out ExchangeCurrency coin)) + if (!updatedCurrencies.TryGetValue(depositDetails.Currency, out ExchangeCurrency coin)) { - Logger.Warn($"Unable to find {depositDetails.Symbol} in existing list of coins."); + Logger.Warn($"Unable to find {depositDetails.Currency} in existing list of coins."); return null; } diff --git a/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs b/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs index 91bd27da..ca8a092a 100644 --- a/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs +++ b/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs @@ -63,15 +63,15 @@ public IWebSocket SubscribeToSummaryDeltas(Action callback) /// Subscribe to order book updates /// /// Callback - /// The ticker to subscribe to + /// The market symbols to subscribe to /// IDisposable to close the socket - public IWebSocket SubscribeToExchangeDeltas(Action callback, params string[] symbols) + public IWebSocket SubscribeToExchangeDeltas(Action callback, params string[] marketSymbols) { SignalrManager.SignalrSocketConnection conn = new SignalrManager.SignalrSocketConnection(this); List paramList = new List(); - foreach (string symbol in symbols) + foreach (string marketSymbol in marketSymbols) { - paramList.Add(new object[] { symbol }); + paramList.Add(new object[] { marketSymbol }); } Task.Run(async () => await conn.OpenAsync("uE", (s) => { @@ -120,23 +120,25 @@ void innerCallback(string json) foreach (JToken ticker in token) { string marketName = ticker["M"].ToStringInvariant(); + var (baseCurrency, quoteCurrency) = ExchangeMarketSymbolToCurrencies(marketName); decimal last = ticker["l"].ConvertInvariant(); decimal ask = ticker["A"].ConvertInvariant(); decimal bid = ticker["B"].ConvertInvariant(); - decimal volume = ticker["V"].ConvertInvariant(); - decimal baseVolume = ticker["m"].ConvertInvariant(); + decimal baseCurrencyVolume = ticker["V"].ConvertInvariant(); + decimal quoteCurrencyVolume = ticker["m"].ConvertInvariant();//NOTE: Bittrex uses the term BaseVolume when referring to QuoteCurrencyVolume DateTime timestamp = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(ticker["T"].ConvertInvariant()); var t = new ExchangeTicker { + MarketSymbol = marketName, Ask = ask, Bid = bid, Last = last, Volume = new ExchangeVolume { - ConvertedVolume = volume, - ConvertedSymbol = marketName, - BaseVolume = baseVolume, - BaseSymbol = marketName, + BaseCurrencyVolume = baseCurrencyVolume, + BaseCurrency = baseCurrency, + QuoteCurrencyVolume = quoteCurrencyVolume, + QuoteCurrency = quoteCurrency, Timestamp = timestamp } }; @@ -152,12 +154,12 @@ protected override IWebSocket OnGetOrderBookWebSocket ( Action callback, int maxCount = 20, - params string[] symbols + params string[] marketSymbols ) { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = GetSymbolsAsync().Sync().ToArray(); + marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); } void innerCallback(string json) { @@ -210,19 +212,19 @@ void innerCallback(string json) book.Bids[depth.Price] = depth; } - book.Symbol = ordersUpdates.MarketName; + book.MarketSymbol = ordersUpdates.MarketName; book.SequenceId = ordersUpdates.Nonce; callback(book); } - return this.SocketManager.SubscribeToExchangeDeltas(innerCallback, symbols); + return this.SocketManager.SubscribeToExchangeDeltas(innerCallback, marketSymbols); } - protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] symbols) + protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = GetSymbolsAsync().Sync().ToArray(); + marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); } void innerCallback(string json) { @@ -243,7 +245,7 @@ void innerCallback(string json) } } - return this.SocketManager.SubscribeToExchangeDeltas(innerCallback, symbols); + return this.SocketManager.SubscribeToExchangeDeltas(innerCallback, marketSymbols); } /// diff --git a/ExchangeSharp/API/Exchanges/Bleutrade/ExchangeBleutradeAPI.cs b/ExchangeSharp/API/Exchanges/Bleutrade/ExchangeBleutradeAPI.cs index b709fe91..f72cc1d2 100644 --- a/ExchangeSharp/API/Exchanges/Bleutrade/ExchangeBleutradeAPI.cs +++ b/ExchangeSharp/API/Exchanges/Bleutrade/ExchangeBleutradeAPI.cs @@ -36,7 +36,7 @@ static ExchangeBleutradeAPI() public ExchangeBleutradeAPI() { NonceStyle = NonceStyle.UnixMillisecondsString; - SymbolSeparator = "_"; + MarketSymbolSeparator = "_"; } #region ProcessRequest @@ -89,7 +89,7 @@ protected override async Task> OnG return currencies; } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); JToken result = await MakeJsonRequestAsync("/public/getmarkets", null, null); @@ -97,7 +97,7 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { List markets = new List(); // "result" : [{"MarketCurrency" : "DOGE","BaseCurrency" : "BTC","MarketCurrencyLong" : "Dogecoin","BaseCurrencyLong" : "Bitcoin", "MinTradeSize" : 0.10000000, "MarketName" : "DOGE_BTC", "IsActive" : true, }, ... @@ -106,9 +106,10 @@ protected override async Task> OnGetSymbolsMetadataA { markets.Add(new ExchangeMarket() { - MarketName = token["MarketName"].ToStringInvariant(), - BaseCurrency = token["BaseCurrency"].ToStringInvariant(), - MarketCurrency = token["MarketCurrency"].ToStringInvariant(), + //NOTE: Bleutrade is another weird one that calls the QuoteCurrency the "BaseCurrency" and the BaseCurrency the "MarketCurrency". + QuoteCurrency = token["BaseCurrency"].ToStringInvariant(), + BaseCurrency = token["MarketCurrency"].ToStringInvariant(), + MarketSymbol = token["MarketName"].ToStringInvariant(), IsActive = token["IsActive"].ToStringInvariant().Equals("true"), MinTradeSize = token["MinTradeSize"].ConvertInvariant(), }); @@ -116,10 +117,10 @@ protected override async Task> OnGetSymbolsMetadataA return markets; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken result = await MakeJsonRequestAsync("/public/getmarketsummary?market=" + symbol); - return this.ParseTicker(result, symbol, "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); + JToken result = await MakeJsonRequestAsync("/public/getmarketsummary?market=" + marketSymbol); + return this.ParseTicker(result, marketSymbol, "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); } protected override async Task>> OnGetTickersAsync() @@ -129,13 +130,13 @@ protected override async Task>> JToken result = await MakeJsonRequestAsync("/public/getmarketsummaries"); foreach (JToken token in result) { - var ticker = this.ParseTicker(result, token["MarketName"].ToStringInvariant(), "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); + var ticker = this.ParseTicker(token, token["MarketName"].ToStringInvariant(), "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); tickers.Add(new KeyValuePair(token["MarketName"].ToStringInvariant(), ticker)); } return tickers; } - protected override async Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { List candles = new List(); string periodString = PeriodSecondsToString(periodSeconds); @@ -145,10 +146,11 @@ protected override async Task> OnGetCandlesAsync(strin //market period(15m, 20m, 30m, 1h, 2h, 3h, 4h, 6h, 8h, 12h, 1d) count(default: 1000, max: 999999) lasthours(default: 24, max: 2160) //"result":[{"TimeStamp":"2014-07-31 10:15:00","Open":"0.00000048","High":"0.00000050","Low":"0.00000048","Close":"0.00000049","Volume":"594804.73036048","BaseVolume":"0.11510368" }, ... - JToken result = await MakeJsonRequestAsync("/public/getcandles?market=" + symbol + "&period=" + periodString + (limit == null ? string.Empty : "&lasthours=" + limit)); + JToken result = await MakeJsonRequestAsync("/public/getcandles?market=" + marketSymbol + "&period=" + periodString + (limit == null ? string.Empty : "&lasthours=" + limit)); foreach (JToken jsonCandle in result) { - MarketCandle candle = this.ParseCandle(jsonCandle, symbol, periodSeconds, "Open", "High", "Low", "Close", "Timestamp", TimestampType.Iso8601, "BaseVolume", "Volume"); + //NOTE: Bleutrade uses the term "BaseVolume" when referring to the QuoteCurrencyVolume + MarketCandle candle = this.ParseCandle(jsonCandle, marketSymbol, periodSeconds, "Open", "High", "Low", "Close", "Timestamp", TimestampType.Iso8601, "Volume", "BaseVolume"); if (candle.Timestamp >= startDate && candle.Timestamp <= endDate) { candles.Add(candle); @@ -158,20 +160,20 @@ protected override async Task> OnGetCandlesAsync(strin } - protected override async Task> OnGetRecentTradesAsync(string symbol) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol) { List trades = new List(); //"result" : [{ "TimeStamp" : "2014-07-29 18:08:00","Quantity" : 654971.69417461,"Price" : 0.00000055,"Total" : 0.360234432,"OrderType" : "BUY"}, ... ] - JToken result = await MakeJsonRequestAsync("/public/getmarkethistory?market=" + symbol); + JToken result = await MakeJsonRequestAsync("/public/getmarkethistory?market=" + marketSymbol); foreach (JToken token in result) trades.Add(ParseTrade(token)); return trades; } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { List trades = new List(); // TODO: Not directly supported so the best we can do is get their Max 200 and check the timestamp if necessary - JToken result = await MakeJsonRequestAsync("/public/getmarkethistory?market=" + symbol + "&count=200"); + JToken result = await MakeJsonRequestAsync("/public/getmarkethistory?market=" + marketSymbol + "&count=200"); foreach (JToken token in result) { ExchangeTrade trade = ParseTrade(token); @@ -186,10 +188,10 @@ protected override async Task OnGetHistoricalTradesAsync(Func OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { //"result" : { "buy" : [{"Quantity" : 4.99400000,"Rate" : 3.00650900}, {"Quantity" : 50.00000000, "Rate" : 3.50000000 } ] ... - JToken token = await MakeJsonRequestAsync("/public/getorderbook?market=" + symbol + "&type=ALL&depth=" + maxCount); + JToken token = await MakeJsonRequestAsync("/public/getorderbook?market=" + marketSymbol + "&type=ALL&depth=" + maxCount); return ExchangeAPIExtensions.ParseOrderBookFromJTokenDictionaries(token, "sell", "buy", "Rate", "Quantity", maxCount: maxCount); } @@ -223,17 +225,17 @@ protected override async Task> OnGetAmountsAvailable return amounts; } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { // "result" : { "OrderId" : "65489","Exchange" : "LTC_BTC", "Type" : "BUY", "Quantity" : 20.00000000, "QuantityRemaining" : 5.00000000, "QuantityBaseTraded" : "0.16549400", "Price" : 0.01268311, "Status" : "OPEN", "Created" : "2014-08-03 13:55:20", "Comments" : "My optional comment, eg function id #123" } JToken result = await MakeJsonRequestAsync("/account/getorder?orderid=" + orderId, null, await GetNoncePayloadAsync()); return ParseOrder(result); } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { List orders = new List(); - JToken result = await MakeJsonRequestAsync("/account/getorders?market=" + (string.IsNullOrEmpty(symbol) ? "ALL" : symbol) + "&orderstatus=OK&ordertype=ALL", null, await GetNoncePayloadAsync()); + JToken result = await MakeJsonRequestAsync("/account/getorders?market=" + (string.IsNullOrEmpty(marketSymbol) ? "ALL" : marketSymbol) + "&orderstatus=OK&ordertype=ALL", null, await GetNoncePayloadAsync()); foreach (JToken token in result) { ExchangeOrderResult order = ParseOrder(token); @@ -243,7 +245,7 @@ protected override async Task> OnGetCompletedOr return orders; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); JToken result = await MakeJsonRequestAsync("/market/getopenorders", null, await GetNoncePayloadAsync()); @@ -258,7 +260,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd order.ExtraParameters.CopyTo(payload); // Only limit order is supported - no indication on how it is filled - JToken token = await MakeJsonRequestAsync((order.IsBuy ? "/market/buylimit?" : "market/selllimit?") + "market=" + order.Symbol + + JToken token = await MakeJsonRequestAsync((order.IsBuy ? "/market/buylimit?" : "market/selllimit?") + "market=" + order.MarketSymbol + "&rate=" + order.Price.ToStringInvariant() + "&quantity=" + order.RoundAmount().ToStringInvariant(), null, payload); if (token.HasValues) { @@ -269,27 +271,27 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return result; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { await MakeJsonRequestAsync("/market/cancel?orderid=" + orderId, null, await GetNoncePayloadAsync()); } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { - JToken token = await MakeJsonRequestAsync("/account/getdepositaddress?" + "currency=" + NormalizeSymbol(symbol), BaseUrl, await GetNoncePayloadAsync()); - if (token["Currency"].ToStringInvariant().Equals(symbol) && token["Address"] != null) + JToken token = await MakeJsonRequestAsync("/account/getdepositaddress?" + "currency=" + NormalizeMarketSymbol(currency), BaseUrl, await GetNoncePayloadAsync()); + if (token["Currency"].ToStringInvariant().Equals(currency) && token["Address"] != null) { // At this time, according to Bleutrade support, they don't support any currency requiring an Address Tag, but they will add this feature in the future return new ExchangeDepositDetails() { - Symbol = token["Currency"].ToStringInvariant(), + Currency = token["Currency"].ToStringInvariant(), Address = token["Address"].ToStringInvariant() }; } return null; } - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { List transactions = new List(); @@ -302,7 +304,7 @@ protected override async Task> OnGetDepositHist PaymentId = token["Id"].ToStringInvariant(), BlockchainTxId = token["TransactionId"].ToStringInvariant(), Timestamp = token["TimeStamp"].ToDateTimeInvariant(), - Symbol = token["Coin"].ToStringInvariant(), + Currency = token["Coin"].ToStringInvariant(), Amount = token["Amount"].ConvertInvariant(), Notes = token["Label"].ToStringInvariant(), TxFee = token["fee"].ConvertInvariant(), @@ -342,7 +344,7 @@ private ExchangeOrderResult ParseOrder(JToken token) { OrderId = token["OrderId"].ToStringInvariant(), IsBuy = token["Type"].ToStringInvariant().Equals("BUY"), - Symbol = token["Exchange"].ToStringInvariant(), + MarketSymbol = token["Exchange"].ToStringInvariant(), Amount = token["Quantity"].ConvertInvariant(), OrderDate = token["Created"].ToDateTimeInvariant(), AveragePrice = token["Price"].ConvertInvariant(), diff --git a/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs b/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs index d3f91f72..82b6948e 100644 --- a/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs +++ b/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs @@ -46,7 +46,7 @@ private ExchangeOrderResult ParseOrder(JToken result) decimal stop_price = result["stop_price"].ConvertInvariant(); decimal averagePrice = (amountFilled <= 0m ? 0m : executedValue / amountFilled); decimal fees = result["fill_fees"].ConvertInvariant(); - string symbol = result["id"].ToStringInvariant(result["product_id"].ToStringInvariant()); + string marketSymbol = result["id"].ToStringInvariant(result["product_id"].ToStringInvariant()); ExchangeOrderResult order = new ExchangeOrderResult { @@ -54,12 +54,12 @@ private ExchangeOrderResult ParseOrder(JToken result) AmountFilled = amountFilled, Price = price <= 0m ? stop_price : price, Fees = fees, - FeesCurrency = symbol.Substring(0, symbol.IndexOf('-')), + FeesCurrency = marketSymbol.Substring(0, marketSymbol.IndexOf('-')), AveragePrice = averagePrice, IsBuy = (result["side"].ToStringInvariant() == "buy"), OrderDate = result["created_at"].ToDateTimeInvariant(), FillDate = result["done_at"].ToDateTimeInvariant(), - Symbol = symbol, + MarketSymbol = marketSymbol, OrderId = result["id"].ToStringInvariant() }; switch (result["status"].ToStringInvariant()) @@ -165,7 +165,7 @@ public ExchangeCoinbaseAPI() WebSocketOrderBookType = WebSocketOrderBookType.FullBookFirstThenDeltas; } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { var markets = new List(); JToken products = await MakeJsonRequestAsync("/products"); @@ -173,11 +173,12 @@ protected override async Task> OnGetSymbolsMetadataA { var market = new ExchangeMarket { - MarketName = product["id"].ToStringUpperInvariant(), - BaseCurrency = product["quote_currency"].ToStringUpperInvariant(), - MarketCurrency = product["base_currency"].ToStringUpperInvariant(), + MarketSymbol = product["id"].ToStringUpperInvariant(), + QuoteCurrency = product["quote_currency"].ToStringUpperInvariant(), + BaseCurrency = product["base_currency"].ToStringUpperInvariant(), IsActive = string.Equals(product["status"].ToStringInvariant(), "online", StringComparison.OrdinalIgnoreCase), MinTradeSize = product["base_min_size"].ConvertInvariant(), + MaxTradeSize = product["base_max_size"].ConvertInvariant(), PriceStepSize = product["quote_increment"].ConvertInvariant() }; markets.Add(market); @@ -186,9 +187,9 @@ protected override async Task> OnGetSymbolsMetadataA return markets; } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { - return (await GetSymbolsMetadataAsync()).Select(market => market.MarketName); + return (await GetMarketSymbolsMetadataAsync()).Select(market => market.MarketSymbol); } protected override async Task> OnGetCurrenciesAsync() @@ -211,17 +212,17 @@ protected override async Task> OnG return currencies; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken ticker = await MakeJsonRequestAsync("/products/" + symbol + "/ticker"); - return this.ParseTicker(ticker, symbol, "ask", "bid", "price", "volume", null, "time", TimestampType.Iso8601); + JToken ticker = await MakeJsonRequestAsync("/products/" + marketSymbol + "/ticker"); + return this.ParseTicker(ticker, marketSymbol, "ask", "bid", "price", "volume", null, "time", TimestampType.Iso8601); } protected override async Task>> OnGetTickersAsync() { List> tickers = new List>(); System.Threading.ManualResetEvent evt = new System.Threading.ManualResetEvent(false); - List symbols = (await GetSymbolsAsync()).ToList(); + List symbols = (await GetMarketSymbolsAsync()).ToList(); // stupid Coinbase does not have a one shot API call for tickers outside of web sockets using (var socket = GetTickersWebSocket((t) => @@ -252,7 +253,7 @@ protected override async Task>> } } - protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols) + protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) { return ConnectWebSocket(string.Empty, (_socket, msg) => { @@ -265,7 +266,7 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action { // parse delta update var delta = JsonConvert.DeserializeObject(message); - book.Symbol = delta.ProductId; + book.MarketSymbol = delta.ProductId; book.SequenceId = delta.Time.Ticks; foreach (string[] change in delta.Changes) { @@ -285,7 +286,7 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action { // parse snapshot var snapshot = JsonConvert.DeserializeObject(message); - book.Symbol = snapshot.ProductId; + book.MarketSymbol = snapshot.ProductId; foreach (decimal[] ask in snapshot.Asks) { decimal price = ask[0]; @@ -311,17 +312,17 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action }, async (_socket) => { // subscribe to order book channel for each symbol - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = (await GetSymbolsAsync()).ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - var chan = new Channel { Name = ChannelType.Level2, ProductIds = symbols.ToList() }; + var chan = new Channel { Name = ChannelType.Level2, ProductIds = marketSymbols.ToList() }; var channelAction = new ChannelAction { Type = ActionType.Subscribe, Channels = new List { chan } }; await _socket.SendMessageAsync(channelAction); }); } - protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] symbols) + protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] marketSymbols) { return ConnectWebSocket("/", (_socket, msg) => { @@ -334,17 +335,17 @@ protected override IWebSocket OnGetTickersWebSocket(Action { - symbols = symbols == null || symbols.Length == 0 ? (await GetSymbolsAsync()).ToArray() : symbols; + marketSymbols = marketSymbols == null || marketSymbols.Length == 0 ? (await GetMarketSymbolsAsync()).ToArray() : marketSymbols; var subscribeRequest = new { type = "subscribe", - product_ids = symbols, + product_ids = marketSymbols, channels = new object[] { new { name = "ticker", - product_ids = symbols.ToArray() + product_ids = marketSymbols.ToArray() } } }; @@ -352,7 +353,7 @@ protected override IWebSocket OnGetTickersWebSocket(Action> callback, params string[] symbols) + protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) { return ConnectWebSocket("/", (_socket, msg) => { @@ -360,21 +361,21 @@ protected override IWebSocket OnGetTradesWebSocket(Action(symbol, trade)); + string marketSymbol = token["product_id"].ToStringInvariant(); + callback(new KeyValuePair(marketSymbol, trade)); return Task.CompletedTask; }, async (_socket) => { var subscribeRequest = new { type = "subscribe", - product_ids = symbols, + product_ids = marketSymbols, channels = new object[] { new { name = "ticker", - product_ids = symbols + product_ids = marketSymbols } } }; @@ -387,7 +388,7 @@ private ExchangeTrade ParseTradeWebSocket(JToken token) return token.ParseTrade("last_size", "price", "side", "time", TimestampType.Iso8601, "sequence"); } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { /* [{ @@ -411,8 +412,8 @@ protected override async Task OnGetHistoricalTradesAsync(Func token.ParseTrade("size", "price", "side", "time", TimestampType.Iso8601, "trade_id"), StartDate = startDate, - Symbol = symbol, - Url = "/products/[symbol]/trades", + MarketSymbol = marketSymbol, + Url = "/products/[marketSymbol]/trades", UrlFunction = (ExchangeHistoricalTradeHelper _state) => { return _state.Url + (string.IsNullOrWhiteSpace(cursorBefore) ? string.Empty : "?before=" + cursorBefore.ToStringInvariant()); @@ -421,9 +422,9 @@ protected override async Task OnGetHistoricalTradesAsync(Func> OnGetRecentTradesAsync(string symbol) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol) { - string baseUrl = "/products/" + symbol.ToUpperInvariant() + "/trades"; + string baseUrl = "/products/" + marketSymbol.ToUpperInvariant() + "/trades"; JToken trades = await MakeJsonRequestAsync(baseUrl); List tradeList = new List(); foreach (JToken trade in trades) @@ -433,14 +434,14 @@ protected override async Task> OnGetRecentTradesAsync return tradeList; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 50) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 50) { - string url = "/products/" + symbol.ToUpperInvariant() + "/book?level=2"; + string url = "/products/" + marketSymbol.ToUpperInvariant() + "/book?level=2"; JToken token = await MakeJsonRequestAsync(url); return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token, maxCount: maxCount); } - protected override async Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { if (limit != null) { @@ -450,7 +451,7 @@ protected override async Task> OnGetCandlesAsync(strin // /products//candles // https://api.pro.coinbase.com/products/LTC-BTC/candles?granularity=86400&start=2017-12-04T18:15:33&end=2017-12-11T18:15:33 List candles = new List(); - string url = "/products/" + symbol + "/candles?granularity=" + periodSeconds; + string url = "/products/" + marketSymbol + "/candles?granularity=" + periodSeconds; if (startDate == null) { startDate = CryptoUtility.UtcNow.Subtract(TimeSpan.FromDays(1.0)); @@ -466,7 +467,7 @@ protected override async Task> OnGetCandlesAsync(strin JToken token = await MakeJsonRequestAsync(url); foreach (JToken candle in token) { - candles.Add(this.ParseCandle(candle, symbol, periodSeconds, 3, 2, 1, 4, 0, TimestampType.UnixSeconds, 5)); + candles.Add(this.ParseCandle(candle, marketSymbol, periodSeconds, 3, 2, 1, 4, 0, TimestampType.UnixSeconds, 5)); } // re-sort in ascending order candles.Sort((c1, c2) => c1.Timestamp.CompareTo(c2.Timestamp)); @@ -511,7 +512,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd { "nonce", nonce }, { "type", order.OrderType.ToStringLowerInvariant() }, { "side", (order.IsBuy ? "buy" : "sell") }, - { "product_id", order.Symbol }, + { "product_id", order.MarketSymbol }, { "size", order.RoundAmount().ToStringInvariant() } }; payload["time_in_force"] = "GTC"; // good til cancel @@ -538,16 +539,16 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return ParseOrder(result); } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { JToken obj = await MakeJsonRequestAsync("/orders/" + orderId, null, await GetNoncePayloadAsync(), "GET"); return ParseOrder(obj); } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); - JArray array = await MakeJsonRequestAsync("orders?status=all" + (string.IsNullOrWhiteSpace(symbol) ? string.Empty : "&product_id=" + symbol), null, await GetNoncePayloadAsync(), "GET"); + JArray array = await MakeJsonRequestAsync("orders?status=all" + (string.IsNullOrWhiteSpace(marketSymbol) ? string.Empty : "&product_id=" + marketSymbol), null, await GetNoncePayloadAsync(), "GET"); foreach (JToken token in array) { orders.Add(ParseOrder(token)); @@ -556,10 +557,10 @@ protected override async Task> OnGetOpenOrderDe return orders; } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { List orders = new List(); - JArray array = await MakeJsonRequestAsync("orders?status=done" + (string.IsNullOrWhiteSpace(symbol) ? string.Empty : "&product_id=" + symbol), null, await GetNoncePayloadAsync(), "GET"); + JArray array = await MakeJsonRequestAsync("orders?status=done" + (string.IsNullOrWhiteSpace(marketSymbol) ? string.Empty : "&product_id=" + marketSymbol), null, await GetNoncePayloadAsync(), "GET"); foreach (JToken token in array) { ExchangeOrderResult result = ParseOrder(token); @@ -572,7 +573,7 @@ protected override async Task> OnGetCompletedOr return orders; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { await MakeJsonRequestAsync("orders/" + orderId, null, await GetNoncePayloadAsync(), "DELETE"); } diff --git a/ExchangeSharp/API/Exchanges/Cryptopia/ExchangeCryptopiaAPI.cs b/ExchangeSharp/API/Exchanges/Cryptopia/ExchangeCryptopiaAPI.cs index 54734be6..bc552237 100644 --- a/ExchangeSharp/API/Exchanges/Cryptopia/ExchangeCryptopiaAPI.cs +++ b/ExchangeSharp/API/Exchanges/Cryptopia/ExchangeCryptopiaAPI.cs @@ -31,14 +31,14 @@ public ExchangeCryptopiaAPI() { RequestContentType = "application/json"; NonceStyle = NonceStyle.UnixMillisecondsString; - SymbolSeparator = "/"; + MarketSymbolSeparator = "/"; } #region ProcessRequest public string NormalizeSymbolForUrl(string symbol) { - return NormalizeSymbol(symbol).Replace(SymbolSeparator, "_"); + return NormalizeMarketSymbol(symbol).Replace(MarketSymbolSeparator, "_"); } protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) @@ -98,7 +98,7 @@ protected override async Task> OnG return currencies; } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); JToken result = await MakeJsonRequestAsync("/GetTradePairs"); @@ -109,7 +109,7 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { List markets = new List(); //[{ "Id":104, "Label":"LTC/BTC", "Currency":"Litecoin", "Symbol":"LTC", "BaseCurrency":"Bitcoin", "BaseSymbol":"BTC", "Status":"OK", "StatusMessage":"", "TradeFee":"0.20000000", "MinimumTrade":"0.00000001, "MaximumTrade":"1000000000.00000000", "MinimumBaseTrade":"0.00000500", "MaximumBaseTrade":"1000000000.00000000", "MinimumPrice":"0.00000001", "MaximumPrice":"1000000000.00000000" }, ... ] @@ -118,12 +118,16 @@ protected override async Task> OnGetSymbolsMetadataA { markets.Add(new ExchangeMarket() { - MarketName = token["Label"].ToStringInvariant(), - BaseCurrency = token["BaseSymbol"].ToStringInvariant(), - MarketCurrency = token["Symbol"].ToStringInvariant(), + MarketId = token["Id"].ToStringInvariant(), + MarketSymbol = token["Label"].ToStringInvariant(), + //NOTE: Cryptopia is calls the QuoteCurrency the "BaseSymbol" and the BaseCurrency the "Symbol".. not confusing at all! + QuoteCurrency = token["BaseSymbol"].ToStringInvariant(), + BaseCurrency = token["Symbol"].ToStringInvariant(), MaxTradeSize = token["MaximumTrade"].ConvertInvariant(), + MaxTradeSizeInQuoteCurrency = token["MaximumBaseTrade"].ConvertInvariant(), MaxPrice = token["MaximumPrice"].ConvertInvariant(), MinTradeSize = token["MinimumTrade"].ConvertInvariant(), + MinTradeSizeInQuoteCurrency = token["MinimumBaseTrade"].ConvertInvariant(), MinPrice = token["MinimumPrice"].ConvertInvariant(), IsActive = token["Status"].ToStringInvariant().Equals("OK") }); @@ -131,9 +135,9 @@ protected override async Task> OnGetSymbolsMetadataA return markets; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken result = await MakeJsonRequestAsync("/GetMarket/" + NormalizeSymbolForUrl(symbol)); + JToken result = await MakeJsonRequestAsync("/GetMarket/" + NormalizeSymbolForUrl(marketSymbol)); return ParseTicker(result); } @@ -145,27 +149,27 @@ protected override async Task>> return tickers; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { // {"TradePairId":100,"Label":"DOT/BTC","Price":0.00000317,"Volume":333389.57231468,"Total":1.05684494} - JToken token = await MakeJsonRequestAsync("/GetMarketOrders/" + NormalizeSymbolForUrl(symbol) + "/" + maxCount.ToStringInvariant()); + JToken token = await MakeJsonRequestAsync("/GetMarketOrders/" + NormalizeSymbolForUrl(marketSymbol) + "/" + maxCount.ToStringInvariant()); return ExchangeAPIExtensions.ParseOrderBookFromJTokenDictionaries(token, "Sell", "Buy", "Price", "Volume", maxCount: maxCount); } - protected override async Task> OnGetRecentTradesAsync(string symbol) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol) { List trades = new List(); // [{ "TradePairId":100,"Label":"LTC/BTC","Type":"Sell","Price":0.00006000, "Amount":499.99640000,"Total":0.02999978,"Timestamp": 1418297368}, ...] - JToken token = await MakeJsonRequestAsync("/GetMarketHistory/" + NormalizeSymbolForUrl(symbol)); // Default is last 24 hours + JToken token = await MakeJsonRequestAsync("/GetMarketHistory/" + NormalizeSymbolForUrl(marketSymbol)); // Default is last 24 hours foreach (JToken trade in token) trades.Add(ParseTrade(trade)); return trades; } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { string hours = startDate == null ? "24" : ((CryptoUtility.UtcNow - startDate.Value.ToUniversalTime()).TotalHours).ToStringInvariant(); List trades = new List(); - JToken token = await MakeJsonRequestAsync("/GetMarketHistory/" + NormalizeSymbolForUrl(symbol) + "/" + hours); + JToken token = await MakeJsonRequestAsync("/GetMarketHistory/" + NormalizeSymbolForUrl(marketSymbol) + "/" + hours); foreach (JToken trade in token) trades.Add(ParseTrade(trade)); var rc = callback?.Invoke(trades); // should we loop here to get additional more recent trades after a delay? @@ -176,13 +180,13 @@ protected override async Task OnGetHistoricalTradesAsync(Func - /// + /// /// /// /// /// /// - protected override Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { throw new NotImplementedException(); } @@ -231,14 +235,14 @@ protected override async Task> OnGetAmountsAvailable return amounts; } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { List orders = new List(); var payload = await GetNoncePayloadAsync(); - if (symbol.Length != 0) + if (marketSymbol.Length != 0) { - payload["Market"] = symbol; + payload["Market"] = marketSymbol; } else { @@ -252,7 +256,7 @@ protected override async Task> OnGetCompletedOr orders.Add(new ExchangeOrderResult() { OrderId = order["TradeId"].ConvertInvariant().ToStringInvariant(), - Symbol = order["Market"].ToStringInvariant(), + MarketSymbol = order["Market"].ToStringInvariant(), Amount = order["Amount"].ConvertInvariant(), AmountFilled = order["Amount"].ConvertInvariant(), // It doesn't look like partial fills are supplied on closed orders Price = order["Rate"].ConvertInvariant(), @@ -266,12 +270,12 @@ protected override async Task> OnGetCompletedOr return orders; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); var payload = await GetNoncePayloadAsync(); - payload["Market"] = string.IsNullOrEmpty(symbol) ? string.Empty : NormalizeSymbol(symbol); + payload["Market"] = string.IsNullOrEmpty(marketSymbol) ? string.Empty : NormalizeMarketSymbol(marketSymbol); //[ {"OrderId": 23467,"TradePairId": 100,"Market": "DOT/BTC","Type": "Buy","Rate": 0.00000034,"Amount": 145.98000000, "Total": "0.00004963", "Remaining": "23.98760000", "TimeStamp":"2014-12-07T20:04:05.3947572" }, ... ] JToken token = await MakeJsonRequestAsync("/GetOpenOrders", null, payload, "POST"); @@ -281,7 +285,7 @@ protected override async Task> OnGetOpenOrderDe { OrderId = data["OrderId"].ConvertInvariant().ToStringInvariant(), OrderDate = data["TimeStamp"].ToDateTimeInvariant(), - Symbol = data["Market"].ToStringInvariant(), + MarketSymbol = data["Market"].ToStringInvariant(), Amount = data["Amount"].ConvertInvariant(), Price = data["Rate"].ConvertInvariant(), IsBuy = data["Type"].ToStringInvariant() == "Buy" @@ -304,9 +308,9 @@ protected override async Task> OnGetOpenOrderDe /// /// /// - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { - var orders = await GetCompletedOrderDetailsAsync(symbol); + var orders = await GetCompletedOrderDetailsAsync(marketSymbol); return orders.Where(o => o.OrderId == orderId).FirstOrDefault(); } @@ -315,7 +319,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd ExchangeOrderResult newOrder = new ExchangeOrderResult() { Result = ExchangeAPIOrderResult.Error }; var payload = await GetNoncePayloadAsync(); - payload["Market"] = order.Symbol; + payload["Market"] = order.MarketSymbol; payload["Type"] = order.IsBuy ? "Buy" : "Sell"; payload["Rate"] = order.Price; payload["Amount"] = order.Amount; @@ -332,7 +336,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd } // This should have a return value for success - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { var payload = await GetNoncePayloadAsync(); payload["Type"] = "Trade"; // Cancel All by Market is supported. Here we're canceling by single Id @@ -345,9 +349,9 @@ protected override async Task OnCancelOrderAsync(string orderId, string symbol = /// Cryptopia does support filtering by Transaction Type (deposits and withdraws), but here we're returning both. The Tx Type will be returned in the Message field /// By Symbol isn't supported, so we'll filter. Also, the default limit is 100 transactions, we could possibly increase this to support the extra data we have to return for Symbol /// - /// + /// /// - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { List deposits = new List(); var payload = await GetNoncePayloadAsync(); @@ -361,7 +365,7 @@ protected override async Task> OnGetDepositHist JToken token = await MakeJsonRequestAsync("/GetTransactions", null, payload, "POST"); foreach (JToken data in token) { - if (data["Currency"].ToStringInvariant().Equals(symbol)) + if (data["Currency"].ToStringInvariant().Equals(currency)) { ExchangeTransaction tx = new ExchangeTransaction() { @@ -371,7 +375,7 @@ protected override async Task> OnGetDepositHist Notes = data["Type"].ToStringInvariant(), PaymentId = data["Id"].ToStringInvariant(), Timestamp = data["TimeStamp"].ToDateTimeInvariant(), - Symbol = data["Currency"].ToStringInvariant(), + Currency = data["Currency"].ToStringInvariant(), TxFee = data["Fee"].ConvertInvariant() }; // They may support more status types, but it's not documented @@ -387,15 +391,15 @@ protected override async Task> OnGetDepositHist return deposits; } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { var payload = await GetNoncePayloadAsync(); - payload["Currency"] = symbol; + payload["Currency"] = currency; JToken token = await MakeJsonRequestAsync("/GetDepositAddress", null, payload, "POST"); if (token["Address"] == null) return null; return new ExchangeDepositDetails() { - Symbol = symbol, + Currency = currency, Address = token["Address"].ToStringInvariant(), AddressTag = token["BaseAddress"].ToStringInvariant() }; @@ -424,8 +428,8 @@ protected override async Task OnWithdrawAsync(Exchan private ExchangeTicker ParseTicker(JToken token) { // [{ "TradePairId":100,"Label":"LTC/BTC","AskPrice":0.00006000,"BidPrice":0.02000000,"Low":0.00006000,"High":0.00006000,"Volume":1000.05639978,"LastPrice":0.00006000,"BuyVolume":34455.678,"SellVolume":67003436.37658233,"Change":-400.00000000,"Open": 0.00000500,"Close": 0.00000600, "BaseVolume": 3.58675866,"BaseBuyVolume": 11.25364758, "BaseSellVolume": 3456.06746543 }, ... ] - string symbol = token["Label"].ToStringInvariant(); - return this.ParseTicker(token, symbol, "AskPrice", "BidPrice", "LastPrice", "Volume", "BaseVolume"); + string marketSymbol = token["Label"].ToStringInvariant(); + return this.ParseTicker(token, marketSymbol, "AskPrice", "BidPrice", "LastPrice", "Volume", "BaseVolume"); } private ExchangeTrade ParseTrade(JToken token) diff --git a/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs b/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs index 88d4bd86..cfec1b04 100644 --- a/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs +++ b/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs @@ -28,20 +28,21 @@ public sealed partial class ExchangeGeminiAPI : ExchangeAPI public ExchangeGeminiAPI() { - SymbolIsUppercase = false; - SymbolSeparator = string.Empty; + MarketSymbolIsUppercase = false; + MarketSymbolSeparator = string.Empty; } - private ExchangeVolume ParseVolume(JToken token) + private ExchangeVolume ParseVolume(JToken token, string symbol) { ExchangeVolume vol = new ExchangeVolume(); JProperty[] props = token.Children().ToArray(); if (props.Length == 3) { - vol.BaseSymbol = props[0].Name; - vol.BaseVolume = props[0].Value.ConvertInvariant(); - vol.ConvertedSymbol = props[1].Name; - vol.ConvertedVolume = props[1].Value.ConvertInvariant(); + var (baseCurrency, quoteCurrency) = ExchangeMarketSymbolToCurrencies(symbol); + vol.QuoteCurrency = quoteCurrency.ToUpperInvariant(); + vol.QuoteCurrencyVolume = token[quoteCurrency.ToUpperInvariant()].ConvertInvariant(); + vol.BaseCurrency = baseCurrency.ToUpperInvariant(); + vol.BaseCurrencyVolume = token[baseCurrency.ToUpperInvariant()].ConvertInvariant(); vol.Timestamp = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(props[2].Value.ConvertInvariant()); } @@ -62,7 +63,7 @@ private ExchangeOrderResult ParseOrder(JToken result) OrderId = result["id"].ToStringInvariant(), Result = (amountFilled == amount ? ExchangeAPIOrderResult.Filled : (amountFilled == 0 ? ExchangeAPIOrderResult.Pending : ExchangeAPIOrderResult.FilledPartially)), OrderDate = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(result["timestampms"].ConvertInvariant()), - Symbol = result["symbol"].ToStringInvariant(), + MarketSymbol = result["symbol"].ToStringInvariant(), IsBuy = result["side"].ToStringInvariant() == "buy" }; } @@ -85,35 +86,36 @@ protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary< return base.ProcessRequestAsync(request, payload); } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { return await MakeJsonRequestAsync("/symbols"); } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken obj = await MakeJsonRequestAsync("/pubticker/" + symbol); + JToken obj = await MakeJsonRequestAsync("/pubticker/" + marketSymbol); if (obj == null || obj.Count() == 0) { return null; } ExchangeTicker t = new ExchangeTicker { + MarketSymbol = marketSymbol, Ask = obj["ask"].ConvertInvariant(), Bid = obj["bid"].ConvertInvariant(), Last = obj["last"].ConvertInvariant() }; - t.Volume = ParseVolume(obj["volume"]); + t.Volume = ParseVolume(obj["volume"], marketSymbol); return t; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - JToken obj = await MakeJsonRequestAsync("/book/" + symbol + "?limit_bids=" + maxCount + "&limit_asks=" + maxCount); + JToken obj = await MakeJsonRequestAsync("/book/" + marketSymbol + "?limit_bids=" + maxCount + "&limit_asks=" + maxCount); return ExchangeAPIExtensions.ParseOrderBookFromJTokenDictionaries(obj, maxCount: maxCount); } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { ExchangeHistoricalTradeHelper state = new ExchangeHistoricalTradeHelper(this) { @@ -122,9 +124,9 @@ protected override async Task OnGetHistoricalTradesAsync(Func token.ParseTrade("amount", "price", "type", "timestampms", TimestampType.UnixMilliseconds), StartDate = startDate, - Symbol = symbol, + MarketSymbol = marketSymbol, TimestampFunction = (DateTime dt) => ((long)CryptoUtility.UnixTimestampFromDateTimeMilliseconds(dt)).ToStringInvariant(), - Url = "/trades/[symbol]?limit_trades=100×tamp={0}" + Url = "/trades/[marketSymbol]?limit_trades=100×tamp={0}" }; await state.ProcessHistoricalTrades(); } @@ -173,7 +175,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd { { "nonce", nonce }, { "client_order_id", "ExchangeSharp_" + CryptoUtility.UtcNow.ToString("s", System.Globalization.CultureInfo.InvariantCulture) }, - { "symbol", order.Symbol }, + { "symbol", order.MarketSymbol }, { "amount", order.RoundAmount().ToStringInvariant() }, { "price", order.Price.ToStringInvariant() }, { "side", (order.IsBuy ? "buy" : "sell") }, @@ -184,7 +186,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return ParseOrder(obj); } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { if (string.IsNullOrWhiteSpace(orderId)) { @@ -196,7 +198,7 @@ protected override async Task OnGetOrderDetailsAsync(string return ParseOrder(result); } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); object nonce = await GenerateNonceAsync(); @@ -205,7 +207,7 @@ protected override async Task> OnGetOpenOrderDe { foreach (JToken token in array) { - if (symbol == null || token["symbol"].ToStringInvariant() == symbol) + if (marketSymbol == null || token["symbol"].ToStringInvariant() == marketSymbol) { orders.Add(ParseOrder(token)); } @@ -215,7 +217,7 @@ protected override async Task> OnGetOpenOrderDe return orders; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { object nonce = await GenerateNonceAsync(); await MakeJsonRequestAsync("/order/cancel", null, new Dictionary{ { "nonce", nonce }, { "order_id", orderId } }); diff --git a/ExchangeSharp/API/Exchanges/Hitbtc/ExchangeHitbtcAPI.cs b/ExchangeSharp/API/Exchanges/Hitbtc/ExchangeHitbtcAPI.cs index e768c953..ce9220fc 100644 --- a/ExchangeSharp/API/Exchanges/Hitbtc/ExchangeHitbtcAPI.cs +++ b/ExchangeSharp/API/Exchanges/Hitbtc/ExchangeHitbtcAPI.cs @@ -32,7 +32,7 @@ public ExchangeHitbtcAPI() { RequestContentType = "application/json"; NonceStyle = NonceStyle.UnixMillisecondsString; - SymbolSeparator = string.Empty; + MarketSymbolSeparator = string.Empty; } public override string PeriodSecondsToString(int seconds) @@ -98,7 +98,7 @@ protected override async Task> OnG return currencies; } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); // [ {"id": "ETHBTC","baseCurrency": "ETH","quoteCurrency": "BTC", "quantityIncrement": "0.001", "tickSize": "0.000001", "takeLiquidityRate": "0.001", "provideLiquidityRate": "-0.0001", "feeCurrency": "BTC" } ... ] @@ -107,7 +107,7 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { List markets = new List(); // [ {"id": "ETHBTC","baseCurrency": "ETH","quoteCurrency": "BTC", "quantityIncrement": "0.001", "tickSize": "0.000001", "takeLiquidityRate": "0.001", "provideLiquidityRate": "-0.0001", "feeCurrency": "BTC" } ... ] @@ -116,9 +116,9 @@ protected override async Task> OnGetSymbolsMetadataA { markets.Add(new ExchangeMarket() { - MarketName = token["id"].ToStringInvariant(), - MarketCurrency = token["baseCurrency"].ToStringInvariant(), // should be LTC in BTC-LTC. Hitbtc & ExchangeSharp definitions of base are reversed. - BaseCurrency = token["quoteCurrency"].ToStringInvariant(), // should be BTC in BTC-LTC + MarketSymbol = token["id"].ToStringInvariant(), + BaseCurrency = token["baseCurrency"].ToStringInvariant(), + QuoteCurrency = token["quoteCurrency"].ToStringInvariant(), QuantityStepSize = token["quantityIncrement"].ConvertInvariant(), PriceStepSize = token["tickSize"].ConvertInvariant(), IsActive = true @@ -128,10 +128,10 @@ protected override async Task> OnGetSymbolsMetadataA } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken obj = await MakeJsonRequestAsync("/public/ticker/" + symbol); - return ParseTicker(obj, symbol); + JToken obj = await MakeJsonRequestAsync("/public/ticker/" + marketSymbol); + return ParseTicker(obj, marketSymbol); } protected override async Task>> OnGetTickersAsync() @@ -140,48 +140,48 @@ protected override async Task>> JToken obj = await MakeJsonRequestAsync("/public/ticker"); foreach (JToken token in obj) { - string symbol = NormalizeSymbol(token["symbol"].ToStringInvariant()); - tickers.Add(new KeyValuePair(symbol, ParseTicker(token, symbol))); + string marketSymbol = NormalizeMarketSymbol(token["symbol"].ToStringInvariant()); + tickers.Add(new KeyValuePair(marketSymbol, ParseTicker(token, marketSymbol))); } return tickers; } - protected override async Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { // [ {"timestamp": "2017-10-20T20:00:00.000Z","open": "0.050459","close": "0.050087","min": "0.050000","max": "0.050511","volume": "1326.628", "volumeQuote": "66.555987736"}, ... ] List candles = new List(); string periodString = PeriodSecondsToString(periodSeconds); limit = limit ?? 100; - JToken obj = await MakeJsonRequestAsync("/public/candles/" + symbol + "?period=" + periodString + "&limit=" + limit); + JToken obj = await MakeJsonRequestAsync("/public/candles/" + marketSymbol + "?period=" + periodString + "&limit=" + limit); foreach (JToken token in obj) { - candles.Add(this.ParseCandle(token, symbol, periodSeconds, "open", "max", "min", "close", "timestamp", TimestampType.Iso8601, "volumeQuote", "volume")); + candles.Add(this.ParseCandle(token, marketSymbol, periodSeconds, "open", "max", "min", "close", "timestamp", TimestampType.Iso8601, "volume", "volumeQuote")); } return candles; } - protected override async Task> OnGetRecentTradesAsync(string symbol) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol) { List trades = new List(); // Putting an arbitrary limit of 10 for 'recent' - JToken obj = await MakeJsonRequestAsync("/public/trades/" + symbol + "?limit=10"); + JToken obj = await MakeJsonRequestAsync("/public/trades/" + marketSymbol + "?limit=10"); foreach (JToken token in obj) trades.Add(ParseExchangeTrade(token)); return trades; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - JToken token = await MakeJsonRequestAsync("/public/orderbook/" + symbol + "?limit=" + maxCount.ToStringInvariant()); + JToken token = await MakeJsonRequestAsync("/public/orderbook/" + marketSymbol + "?limit=" + maxCount.ToStringInvariant()); return ExchangeAPIExtensions.ParseOrderBookFromJTokenDictionaries(token, asks: "ask", bids: "bid", amount: "size", maxCount: maxCount); } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { List trades = new List(); long? lastTradeID = null; // TODO: Can't get Hitbtc to return other than the last 50 trades even though their API says it should (by orderid or timestamp). When passing either of these parms, it still returns the last 50 // So until there is an update, that's what we'll go with - JToken obj = await MakeJsonRequestAsync("/public/trades/" + symbol); + JToken obj = await MakeJsonRequestAsync("/public/trades/" + marketSymbol); if (obj.HasValues) { foreach (JToken token in obj) @@ -238,20 +238,20 @@ protected override async Task> OnGetAmountsAvailable /// /// /// - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { JToken obj = await MakeJsonRequestAsync("/history/order/" + orderId + "/trades", null, await GetNoncePayloadAsync()); if (obj != null && obj.HasValues) return ParseCompletedOrder(obj); return null; } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { List orders = new List(); var payload = await GetNoncePayloadAsync(); - if (!string.IsNullOrEmpty(symbol)) + if (!string.IsNullOrEmpty(marketSymbol)) { - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; } if (afterDate != null) { @@ -268,13 +268,13 @@ protected override async Task> OnGetCompletedOr return orders; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); var payload = await GetNoncePayloadAsync(); - if (!string.IsNullOrEmpty(symbol)) + if (!string.IsNullOrEmpty(marketSymbol)) { - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; } JToken obj = await MakeJsonRequestAsync("/order", null, payload); if (obj != null && obj.HasValues) @@ -292,7 +292,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd var payload = await GetNoncePayloadAsync(); //payload["clientOrderId"] = "neuMedia" + payload["nonce"]; Currently letting hitbtc assign this, but may not be unique for more than 24 hours payload["quantity"] = order.Amount; - payload["symbol"] = order.Symbol; + payload["symbol"] = order.MarketSymbol; payload["side"] = order.IsBuy ? "buy" : "sell"; payload["type"] = order.OrderType == OrderType.Limit ? "limit" : "market"; if (order.OrderType == OrderType.Limit) @@ -307,7 +307,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd ExchangeOrderResult result = new ExchangeOrderResult { OrderId = token["clientOrderId"].ToStringInvariant(), - Symbol = token["symbol"].ToStringInvariant(), + MarketSymbol = token["symbol"].ToStringInvariant(), OrderDate = token["createdAt"].ToDateTimeInvariant(), Amount = token["quantity"].ConvertInvariant(), Price = token["price"].ConvertInvariant(), @@ -331,7 +331,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return result; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { // this call returns info about the success of the cancel. Sure would be nice have a return type on this method. JToken token = await MakeJsonRequestAsync("/order/" + orderId, null, await GetNoncePayloadAsync(), "DELETE"); @@ -358,10 +358,10 @@ private void ParseAveragePriceAndFeesFromFills(ExchangeOrderResult result, JToke result.AveragePrice = (totalQuantity == 0 ? 0 : totalCost / totalQuantity); } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { - ExchangeDepositDetails deposit = new ExchangeDepositDetails() { Symbol = symbol }; - JToken token = await MakeJsonRequestAsync("/payment/address/" + symbol, null, await GetNoncePayloadAsync()); + ExchangeDepositDetails deposit = new ExchangeDepositDetails() { Currency = currency }; + JToken token = await MakeJsonRequestAsync("/payment/address/" + currency, null, await GetNoncePayloadAsync()); if (token != null) { deposit.Address = token["address"].ToStringInvariant(); @@ -379,9 +379,9 @@ protected override async Task OnGetDepositAddressAsync(s /// This returns both Deposit and Withdawl history for the Bank and Trading Accounts. Currently returning everything and not filtering. /// There is no support for retrieving by Symbol, so we'll filter that after reteiving all symbols /// - /// + /// /// - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { List transactions = new List(); // [ {"id": "6a2fb54d-7466-490c-b3a6-95d8c882f7f7","index": 20400458,"currency": "ETH","amount": "38.616700000000000000000000","fee": "0.000880000000000000000000", "address": "0xfaEF4bE10dDF50B68c220c9ab19381e20B8EEB2B", "hash": "eece4c17994798939cea9f6a72ee12faa55a7ce44860cfb95c7ed71c89522fe8","status": "pending","type": "payout", "createdAt": "2017-05-18T18:05:36.957Z", "updatedAt": "2017-05-18T19:21:05.370Z" }, ... ] @@ -390,12 +390,12 @@ protected override async Task> OnGetDepositHist { foreach (JToken token in result) { - if (token["currency"].ToStringInvariant().Equals(symbol)) + if (token["currency"].ToStringInvariant().Equals(currency)) { ExchangeTransaction transaction = new ExchangeTransaction { PaymentId = token["id"].ToStringInvariant(), - Symbol = token["currency"].ToStringInvariant(), + Currency = token["currency"].ToStringInvariant(), Address = token["address"].ToStringInvariant(), // Address Tag isn't returned BlockchainTxId = token["hash"].ToStringInvariant(), // not sure about this Amount = token["amount"].ConvertInvariant(), @@ -480,7 +480,7 @@ public async Task AccountTransfer(string Symbol, decimal Amount, bool ToBa private ExchangeTicker ParseTicker(JToken token, string symbol) { // [ {"ask": "0.050043","bid": "0.050042","last": "0.050042","open": "0.047800","low": "0.047052","high": "0.051679","volume": "36456.720","volumeQuote": "1782.625000","timestamp": "2017-05-12T14:57:19.999Z","symbol": "ETHBTC"} ] - return this.ParseTicker(token, symbol, "ask", "bid", "last", "volumeQuote", "volume", "timestamp", TimestampType.Iso8601); + return this.ParseTicker(token, symbol, "ask", "bid", "last", "volume", "volumeQuote", "timestamp", TimestampType.Iso8601); } private ExchangeTrade ParseExchangeTrade(JToken token) @@ -495,7 +495,7 @@ private ExchangeOrderResult ParseCompletedOrder(JToken token) return new ExchangeOrderResult() { OrderId = token["orderId"].ToStringInvariant(), - Symbol = token["symbol"].ToStringInvariant(), + MarketSymbol = token["symbol"].ToStringInvariant(), IsBuy = token["side"].ToStringInvariant().Equals("buy"), Amount = token["quantity"].ConvertInvariant(), AmountFilled = token["quantity"].ConvertInvariant(), // these are closed, so I guess the filled quantity matches the order quantiity @@ -512,7 +512,7 @@ private ExchangeOrderResult ParseOpenOrder(JToken token) ExchangeOrderResult result = new ExchangeOrderResult() { OrderId = token["clientOrderId"].ToStringInvariant(), // here we're using ClientOrderId in order to get order details by open orders - Symbol = token["symbol"].ToStringInvariant(), + MarketSymbol = token["symbol"].ToStringInvariant(), IsBuy = token["side"].ToStringInvariant().Equals("buy"), Amount = token["quantity"].ConvertInvariant(), AmountFilled = token["cumQuantity"].ConvertInvariant(), diff --git a/ExchangeSharp/API/Exchanges/Huobi/ExchangeHuobiAPI.cs b/ExchangeSharp/API/Exchanges/Huobi/ExchangeHuobiAPI.cs index 10d36d45..f7510a00 100644 --- a/ExchangeSharp/API/Exchanges/Huobi/ExchangeHuobiAPI.cs +++ b/ExchangeSharp/API/Exchanges/Huobi/ExchangeHuobiAPI.cs @@ -37,22 +37,22 @@ public ExchangeHuobiAPI() { RequestContentType = "application/x-www-form-urlencoded"; NonceStyle = NonceStyle.UnixMilliseconds; - SymbolSeparator = string.Empty; - SymbolIsUppercase = false; + MarketSymbolSeparator = string.Empty; + MarketSymbolIsUppercase = false; WebSocketOrderBookType = WebSocketOrderBookType.FullBookAlways; } - public override string ExchangeSymbolToGlobalSymbol(string symbol) + public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) { - if (symbol.Length < 6) + if (marketSymbol.Length < 6) { - throw new ArgumentException("Invalid symbol " + symbol); + throw new ArgumentException("Invalid market symbol " + marketSymbol); } - else if (symbol.Length == 6) + else if (marketSymbol.Length == 6) { - return ExchangeSymbolToGlobalSymbolWithSeparator(symbol.Substring(0, 3) + GlobalSymbolSeparator + symbol.Substring(3, 3), GlobalSymbolSeparator); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(marketSymbol.Substring(0, 3) + GlobalMarketSymbolSeparator + marketSymbol.Substring(3, 3), GlobalMarketSymbolSeparator); } - return ExchangeSymbolToGlobalSymbolWithSeparator(symbol.Substring(3) + GlobalSymbolSeparator + symbol.Substring(0, 3), GlobalSymbolSeparator); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(marketSymbol.Substring(3) + GlobalMarketSymbolSeparator + marketSymbol.Substring(0, 3), GlobalMarketSymbolSeparator); } public override string PeriodSecondsToString(int seconds) @@ -115,13 +115,13 @@ protected override Uri ProcessRequestUrl(UriBuilder url, Dictionary> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { - var m = await GetSymbolsMetadataAsync(); - return m.Select(x => x.MarketName); + var m = await GetMarketSymbolsMetadataAsync(); + return m.Select(x => x.MarketSymbol); } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { /* { @@ -145,35 +145,35 @@ protected override async Task> OnGetSymbolsMetadataA */ List markets = new List(); - JToken allSymbols = await MakeJsonRequestAsync("/common/symbols", BaseUrlV1, null); - foreach (var symbol in allSymbols) + JToken allMarketSymbols = await MakeJsonRequestAsync("/common/symbols", BaseUrlV1, null); + foreach (var marketSymbol in allMarketSymbols) { - var marketCurrency = symbol["base-currency"].ToStringLowerInvariant(); - var baseCurrency = symbol["quote-currency"].ToStringLowerInvariant(); - var price_precision = symbol["price-precision"].ConvertInvariant(); - var priceStepSize = Math.Pow(10, -price_precision); - var amount_precision = symbol["amount-precision"].ConvertInvariant(); - var quantityStepSize = Math.Pow(10, -amount_precision); - - var market = new ExchangeMarket() - { - MarketCurrency = marketCurrency, - BaseCurrency = baseCurrency, - MarketName = marketCurrency + baseCurrency, - IsActive = true, - }; + var baseCurrency = marketSymbol["base-currency"].ToStringLowerInvariant(); + var quoteCurrency = marketSymbol["quote-currency"].ToStringLowerInvariant(); + var pricePrecision = marketSymbol["price-precision"].ConvertInvariant(); + var priceStepSize = Math.Pow(10, -pricePrecision).ConvertInvariant(); + var amountPrecision = marketSymbol["amount-precision"].ConvertInvariant(); + var quantityStepSize = Math.Pow(10, -amountPrecision).ConvertInvariant(); + + var market = new ExchangeMarket + { + BaseCurrency = baseCurrency, + QuoteCurrency = quoteCurrency, + MarketSymbol = baseCurrency + quoteCurrency, + IsActive = true, + PriceStepSize = priceStepSize, + QuantityStepSize = quantityStepSize, + MinPrice = priceStepSize, + MinTradeSize = quantityStepSize, + }; - market.PriceStepSize = priceStepSize.ConvertInvariant(); - market.QuantityStepSize = quantityStepSize.ConvertInvariant(); - market.MinPrice = market.PriceStepSize.Value; - market.MinTradeSize = market.QuantityStepSize.Value; markets.Add(market); } return markets; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { /* {{ @@ -201,8 +201,8 @@ protected override async Task OnGetTickerAsync(string symbol) } }} */ - JToken ticker = await MakeJsonRequestAsync("/market/detail/merged?symbol=" + symbol); - return this.ParseTicker(ticker["tick"], symbol, "ask", "bid", "close", "amount", "vol", "ts", TimestampType.UnixMillisecondsDouble, idKey: "id"); + JToken ticker = await MakeJsonRequestAsync("/market/detail/merged?symbol=" + marketSymbol); + return this.ParseTicker(ticker["tick"], marketSymbol, "ask", "bid", "close", "amount", "vol", "ts", TimestampType.UnixMillisecondsDouble, idKey: "id"); } protected override Task>> OnGetTickersAsync() @@ -210,7 +210,7 @@ protected override Task>> OnGet throw new NotImplementedException("Too many pairs and this exchange does not support a single call to get all the tickers"); } - protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] symbols) + protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) { return ConnectWebSocket(string.Empty, async (_socket, msg) => { @@ -251,7 +251,7 @@ protected override IWebSocket OnGetTradesWebSocket(Action(); @@ -261,24 +261,24 @@ protected override IWebSocket OnGetTradesWebSocket(Action(symbol, trade)); + callback(new KeyValuePair(marketSymbol, trade)); } }, async (_socket) => { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = (await GetSymbolsAsync()).ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - foreach (string symbol in symbols) + foreach (string marketSymbol in marketSymbols) { long id = System.Threading.Interlocked.Increment(ref webSocketId); - string channel = $"market.{symbol}.trade.detail"; + string channel = $"market.{marketSymbol}.trade.detail"; await _socket.SendMessageAsync(new { sub = channel, id = "id" + id.ToStringInvariant() }); } }); } - protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols) + protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) { return ConnectWebSocket(string.Empty, async (_socket, msg) => { @@ -336,27 +336,27 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action } var ch = token["ch"].ToStringInvariant(); var sArray = ch.Split('.'); - var symbol = sArray[1].ToStringInvariant(); + var marketSymbol = sArray[1].ToStringInvariant(); ExchangeOrderBook book = ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token["tick"], maxCount: maxCount); - book.Symbol = symbol; + book.MarketSymbol = marketSymbol; callback(book); }, async (_socket) => { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = (await GetSymbolsAsync()).ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - foreach (string symbol in symbols) + foreach (string symbol in marketSymbols) { long id = System.Threading.Interlocked.Increment(ref webSocketId); - var normalizedSymbol = NormalizeSymbol(symbol); + var normalizedSymbol = NormalizeMarketSymbol(symbol); string channel = $"market.{normalizedSymbol}.depth.step0"; await _socket.SendMessageAsync(new { sub = channel, id = "id" + id.ToStringInvariant() }); } }); } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { /* { @@ -397,11 +397,11 @@ protected override async Task OnGetOrderBookAsync(string symb [7995, 0.88], */ ExchangeOrderBook orders = new ExchangeOrderBook(); - JToken obj = await MakeJsonRequestAsync("/market/depth?symbol=" + symbol + "&type=step0", BaseUrl, null); + JToken obj = await MakeJsonRequestAsync("/market/depth?symbol=" + marketSymbol + "&type=step0", BaseUrl, null); return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(obj["tick"], sequence: "ts", maxCount: maxCount); } - protected override async Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { /* { @@ -422,7 +422,7 @@ protected override async Task> OnGetCandlesAsync(strin */ List candles = new List(); - string url = "/market/history/kline?symbol=" + symbol; + string url = "/market/history/kline?symbol=" + marketSymbol; if (limit != null) { // default is 150, max: 2000 @@ -433,7 +433,7 @@ protected override async Task> OnGetCandlesAsync(strin JToken allCandles = await MakeJsonRequestAsync(url, BaseUrl, null); foreach (var token in allCandles) { - candles.Add(this.ParseCandle(token, symbol, periodSeconds, "open", "high", "low", "close", "id", TimestampType.UnixSeconds, null, "vol")); + candles.Add(this.ParseCandle(token, marketSymbol, periodSeconds, "open", "high", "low", "close", "id", TimestampType.UnixSeconds, null, "vol")); } candles.Reverse(); @@ -572,7 +572,7 @@ protected override async Task> OnGetAmountsAvailable return amounts; } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { /* {{ @@ -600,13 +600,13 @@ protected override async Task OnGetOrderDetailsAsync(string return ParseOrder(data); } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { - if (symbol == null) { throw new APIException("symbol cannot be null"); } + if (marketSymbol == null) { throw new APIException("symbol cannot be null"); } List orders = new List(); var payload = await GetNoncePayloadAsync(); - payload.Add("symbol", symbol); + payload.Add("symbol", marketSymbol); payload.Add("states", "partial-canceled,filled,canceled"); if (afterDate != null) { @@ -620,13 +620,13 @@ protected override async Task> OnGetCompletedOr return orders; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { - if (symbol == null) { throw new APIException("symbol cannot be null"); } + if (marketSymbol == null) { throw new APIException("symbol cannot be null"); } List orders = new List(); var payload = await GetNoncePayloadAsync(); - payload.Add("symbol", symbol); + payload.Add("symbol", marketSymbol); payload.Add("states", "pre-submitted,submitting,submitted,partial-filled"); JToken data = await MakeJsonRequestAsync("/order/orders", PrivateUrlV1, payload); foreach (var prop in data) @@ -638,16 +638,16 @@ protected override async Task> OnGetOpenOrderDe protected override async Task OnPlaceOrderAsync(ExchangeOrderRequest order) { - var account_id = await GetAccountID(order.IsMargin, order.Symbol); + var account_id = await GetAccountID(order.IsMargin, order.MarketSymbol); var payload = await GetNoncePayloadAsync(); payload.Add("account-id", account_id); - payload.Add("symbol", order.Symbol); + payload.Add("symbol", order.MarketSymbol); payload.Add("type", order.IsBuy ? "buy" : "sell"); payload.Add("source", order.IsMargin ? "margin-api" : "api"); - decimal outputQuantity = await ClampOrderQuantity(order.Symbol, order.Amount); - decimal outputPrice = await ClampOrderPrice(order.Symbol, order.Price); + decimal outputQuantity = await ClampOrderQuantity(order.MarketSymbol, order.Amount); + decimal outputPrice = await ClampOrderPrice(order.MarketSymbol, order.Price); payload["amount"] = outputQuantity.ToStringInvariant(); @@ -669,18 +669,18 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return ParsePlaceOrder(obj, order); } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { var payload = await GetNoncePayloadAsync(); await MakeJsonRequestAsync($"/order/orders/{orderId}/submitcancel", PrivateUrlV1, payload, "POST"); } - protected override Task> OnGetDepositHistoryAsync(string symbol) + protected override Task> OnGetDepositHistoryAsync(string currency) { throw new NotImplementedException("Huobi does not provide a deposit API"); } - protected override Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { throw new NotImplementedException("Huobi does not provide a deposit API"); @@ -733,7 +733,7 @@ private ExchangeOrderResult ParsePlaceOrder(JToken token, ExchangeOrderRequest o Price = order.Price, IsBuy = order.IsBuy, OrderId = token.ToStringInvariant(), - Symbol = order.Symbol + MarketSymbol = order.MarketSymbol }; result.AveragePrice = result.Price; result.Result = ExchangeAPIOrderResult.Pending; @@ -766,7 +766,7 @@ private ExchangeOrderResult ParseOrder(JToken token) ExchangeOrderResult result = new ExchangeOrderResult() { OrderId = token["id"].ToStringInvariant(), - Symbol = token["symbol"].ToStringInvariant(), + MarketSymbol = token["symbol"].ToStringInvariant(), Amount = token["amount"].ConvertInvariant(), AmountFilled = token["field-amount"].ConvertInvariant(), Price = token["price"].ConvertInvariant(), diff --git a/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs b/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs index 3308caa8..68eb4bef 100644 --- a/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs +++ b/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs @@ -16,6 +16,7 @@ The above copyright notice and this permission notice shall be included in all c using System.Net; using System.Security.Cryptography; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Newtonsoft.Json.Linq; @@ -42,35 +43,35 @@ public ExchangeKrakenAPI() { RequestMethod = "POST"; RequestContentType = "application/x-www-form-urlencoded"; - SymbolSeparator = string.Empty; + MarketSymbolSeparator = string.Empty; NonceStyle = NonceStyle.UnixMilliseconds; } - public override string ExchangeSymbolToGlobalSymbol(string symbol) + public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) { - if (exchangeSymbolToNormalizedSymbol.TryGetValue(symbol, out string normalizedSymbol)) + if (exchangeSymbolToNormalizedSymbol.TryGetValue(marketSymbol, out string normalizedSymbol)) { - return base.ExchangeSymbolToGlobalSymbolWithSeparator(normalizedSymbol.Substring(0, 3) + GlobalSymbolSeparator + normalizedSymbol.Substring(3), GlobalSymbolSeparator); + return base.ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(normalizedSymbol.Substring(0, 3) + GlobalMarketSymbolSeparator + normalizedSymbol.Substring(3), GlobalMarketSymbolSeparator); } - throw new ArgumentException($"Symbol {symbol} not found in Kraken lookup table"); + throw new ArgumentException($"Symbol {marketSymbol} not found in Kraken lookup table"); } - public override string GlobalSymbolToExchangeSymbol(string symbol) + public override string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol) { - if (normalizedSymbolToExchangeSymbol.TryGetValue(symbol.Replace(GlobalSymbolSeparator.ToString(), string.Empty), out string exchangeSymbol)) + if (normalizedSymbolToExchangeSymbol.TryGetValue(marketSymbol.Replace(GlobalMarketSymbolSeparator.ToString(), string.Empty), out string exchangeSymbol)) { return exchangeSymbol; } // not found, reverse the pair - int idx = symbol.IndexOf(GlobalSymbolSeparator); - symbol = symbol.Substring(idx + 1) + symbol.Substring(0, idx); - if (normalizedSymbolToExchangeSymbol.TryGetValue(symbol.Replace(GlobalSymbolSeparator.ToString(), string.Empty), out exchangeSymbol)) + int idx = marketSymbol.IndexOf(GlobalMarketSymbolSeparator); + marketSymbol = marketSymbol.Substring(idx + 1) + marketSymbol.Substring(0, idx); + if (normalizedSymbolToExchangeSymbol.TryGetValue(marketSymbol.Replace(GlobalMarketSymbolSeparator.ToString(), string.Empty), out exchangeSymbol)) { return exchangeSymbol; } - throw new ArgumentException($"Symbol {symbol} not found in Kraken lookup table"); + throw new ArgumentException($"Symbol {marketSymbol} not found in Kraken lookup table"); } /// @@ -164,7 +165,7 @@ private ExchangeOrderResult ParseOrder(string orderId, JToken order) } orderResult.Message = (orderResult.Message ?? order["reason"].ToStringInvariant()); orderResult.OrderDate = CryptoUtility.UnixTimeStampToDateTimeSeconds(order["opentm"].ConvertInvariant()); - orderResult.Symbol = order["descr"]["pair"].ToStringInvariant(); + orderResult.MarketSymbol = order["descr"]["pair"].ToStringInvariant(); orderResult.IsBuy = (order["descr"]["type"].ToStringInvariant() == "buy"); orderResult.Amount = order["vol"].ConvertInvariant(); orderResult.AmountFilled = order["vol_exec"].ConvertInvariant(); @@ -227,13 +228,35 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti } } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetCurrenciesAsync() + { + // https://api.kraken.com/0/public/Assets + Dictionary allCoins = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var currencies = new Dictionary(StringComparer.OrdinalIgnoreCase); + JToken array = await MakeJsonRequestAsync("/0/public/Assets"); + foreach (JProperty token in array) + { + var coin = new ExchangeCurrency + { + CoinType = token.Value["aclass"].ToStringInvariant(), + Name = token.Name, + FullName = token.Value["altname"].ToStringInvariant() + }; + + currencies[coin.Name] = coin; + } + + return currencies; + } + + protected override async Task> OnGetMarketSymbolsAsync() { JToken result = await MakeJsonRequestAsync("/0/public/AssetPairs"); return (from prop in result.Children() where !prop.Name.Contains(".d") select prop.Name).ToArray(); } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { // { // "BCHEUR": { @@ -331,24 +354,23 @@ protected override async Task> OnGetSymbolsMetadataA //} var markets = new List(); JToken allPairs = await MakeJsonRequestAsync("/0/public/AssetPairs"); - var res = (from prop in allPairs.Children() select prop.Value).ToArray(); + var res = (from prop in allPairs.Children() select prop).ToArray(); - foreach (JToken pair in res) + foreach (JProperty prop in res) { - + JToken pair = prop.Value; + var quantityStepSize = Math.Pow(0.1, pair["lot_decimals"].ConvertInvariant()).ConvertInvariant(); var market = new ExchangeMarket - { - IsActive = true, - MarketName = NormalizeSymbol(pair["altname"].ToStringInvariant()), - MinTradeSize = pair["lot_decimals"].ConvertInvariant() - - }; - market.MarketCurrency = pair["quote"].ToStringInvariant(); - market.BaseCurrency = pair["base"].ToStringInvariant(); - int quantityPrecision = pair["lot_decimals"].ConvertInvariant(); - market.QuantityStepSize = (decimal)Math.Pow(0.1, quantityPrecision); - int pricePrecision = pair["pair_decimals"].ConvertInvariant(); - market.PriceStepSize = (decimal)Math.Pow(0.1, pricePrecision); + { + IsActive = !prop.Name.Contains(".d"), + MarketSymbol = prop.Name, + MinTradeSize = quantityStepSize, + MarginEnabled = pair["leverage_buy"].Children().Any() || pair["leverage_sell"].Children().Any(), + BaseCurrency = pair["base"].ToStringInvariant(), + QuoteCurrency = pair["quote"].ToStringInvariant(), + QuantityStepSize = quantityStepSize, + PriceStepSize = Math.Pow(0.1, pair["pair_decimals"].ConvertInvariant()).ConvertInvariant() + }; markets.Add(market); } @@ -359,54 +381,56 @@ protected override async Task> OnGetSymbolsMetadataA protected override async Task>> OnGetTickersAsync() { - var symbols = await GetSymbolsAsync(); - var normalizedPairsList = symbols.Select(symbol => NormalizeSymbol(symbol)).ToList(); + var marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); + var normalizedPairsList = marketSymbols.Select(symbol => NormalizeMarketSymbol(symbol)).ToList(); var csvPairsList = string.Join(",", normalizedPairsList); JToken apiTickers = await MakeJsonRequestAsync("/0/public/Ticker", null, new Dictionary { { "pair", csvPairsList } }); var tickers = new List>(); - foreach (string symbol in symbols) + foreach (string marketSymbol in marketSymbols) { - JToken ticker = apiTickers[symbol]; - tickers.Add(new KeyValuePair(symbol, ConvertToExchangeTicker(symbol, ticker))); + JToken ticker = apiTickers[marketSymbol]; + tickers.Add(new KeyValuePair(marketSymbol, ConvertToExchangeTicker(marketSymbol, ticker))); } return tickers; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken apiTickers = await MakeJsonRequestAsync("/0/public/Ticker", null, new Dictionary { { "pair", NormalizeSymbol(symbol) } }); - JToken ticker = apiTickers[symbol]; - return ConvertToExchangeTicker(symbol, ticker); + JToken apiTickers = await MakeJsonRequestAsync("/0/public/Ticker", null, new Dictionary { { "pair", NormalizeMarketSymbol(marketSymbol) } }); + JToken ticker = apiTickers[marketSymbol]; + return ConvertToExchangeTicker(marketSymbol, ticker); } - private static ExchangeTicker ConvertToExchangeTicker(string symbol, JToken ticker) + private ExchangeTicker ConvertToExchangeTicker(string symbol, JToken ticker) { decimal last = ticker["c"][0].ConvertInvariant(); + var (baseCurrency, quoteCurrency) = ExchangeMarketSymbolToCurrencies(symbol); return new ExchangeTicker { + MarketSymbol = symbol, Ask = ticker["a"][0].ConvertInvariant(), Bid = ticker["b"][0].ConvertInvariant(), Last = last, Volume = new ExchangeVolume { - BaseVolume = ticker["v"][1].ConvertInvariant(), - BaseSymbol = symbol, - ConvertedVolume = ticker["v"][1].ConvertInvariant() * last, - ConvertedSymbol = symbol, + QuoteCurrencyVolume = ticker["v"][1].ConvertInvariant(), + QuoteCurrency = quoteCurrency, + BaseCurrencyVolume = ticker["v"][1].ConvertInvariant() * ticker["p"][1].ConvertInvariant(), + BaseCurrency = baseCurrency, Timestamp = CryptoUtility.UtcNow } }; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - JToken obj = await MakeJsonRequestAsync("/0/public/Depth?pair=" + symbol + "&count=" + maxCount); - return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(obj[symbol], maxCount: maxCount); + JToken obj = await MakeJsonRequestAsync("/0/public/Depth?pair=" + marketSymbol + "&count=" + maxCount); + return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(obj[marketSymbol], maxCount: maxCount); } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { - string baseUrl = "/0/public/Trades?pair=" + symbol; + string baseUrl = "/0/public/Trades?pair=" + marketSymbol; string url; List trades = new List(); while (true) @@ -421,7 +445,7 @@ protected override async Task OnGetHistoricalTradesAsync(Func> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { if (limit != null) { @@ -459,14 +483,14 @@ protected override async Task> OnGetCandlesAsync(strin // array of array entries( - /// + /// /// /// /// /// /// - protected override async Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { List candles = new List(); @@ -208,7 +208,7 @@ protected override async Task> OnGetCandlesAsync(strin // this is a little tricky. The call is private, but not a POST. We need the payload for the sig, but also for the uri // so, we'll do both... This is the only ExchangeAPI public call (private on Kucoin) like this. var payload = await GetNoncePayloadAsync(); - payload.Add("symbol", symbol); + payload.Add("symbol", marketSymbol); payload.Add("resolution", periodString); payload.Add("from", (long)startDate.Value.UnixTimestampFromDateTimeSeconds()); // the nonce is milliseconds, this is seconds without decimal payload.Add("to", (long)endDate.Value.UnixTimestampFromDateTimeSeconds()); // the nonce is milliseconds, this is seconds without decimal @@ -225,15 +225,15 @@ protected override async Task> OnGetCandlesAsync(strin candles.Add(new MarketCandle { ExchangeName = this.Name, - Name = symbol, + Name = marketSymbol, PeriodSeconds = periodSeconds, Timestamp = DateTimeOffset.FromUnixTimeSeconds(token["t"][i].ConvertInvariant()).DateTime, ClosePrice = token["c"][i].ConvertInvariant(), HighPrice = token["h"][i].ConvertInvariant(), LowPrice = token["l"][i].ConvertInvariant(), OpenPrice = token["o"][i].ConvertInvariant(), - ConvertedVolume = token["v"][i].ConvertInvariant(), - BaseVolume = token["v"][i].ConvertInvariant() * token["c"][i].ConvertInvariant() + BaseCurrencyVolume = token["v"][i].ConvertInvariant(), + QuoteCurrencyVolume = token["v"][i].ConvertInvariant() * token["c"][i].ConvertInvariant() }); } } @@ -254,18 +254,18 @@ protected override async Task> OnGetAmountsAvailable return await OnGetAmountsInternalAsync(false); } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { List orders = new List(); // "datas": [ {"createdAt": 1508219588000, "amount": 92.79323381, "dealValue": 0.00927932, "dealPrice": 0.0001, "fee": 1e-8,"feeRate": 0, "oid": "59e59ac49bd8d31d09f85fa8", "orderOid": "59e59ac39bd8d31d093d956a", "coinType": "KCS", "coinTypePair": "BTC", "direction": "BUY", "dealDirection": "BUY" }, ... ] var payload = await GetNoncePayloadAsync(); - if (string.IsNullOrWhiteSpace(symbol)) + if (string.IsNullOrWhiteSpace(marketSymbol)) { payload["limit"] = 100; } else { - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; payload["limit"] = 20; } @@ -280,15 +280,15 @@ protected override async Task> OnGetCompletedOr return orders; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); // { "SELL": [{ "oid": "59e59b279bd8d31d093d956e", "type": "SELL", "userOid": null, "coinType": "KCS", "coinTypePair": "BTC", "direction": "SELL","price": 0.1,"dealAmount": 0,"pendingAmount": 100, "createdAt": 1508219688000, "updatedAt": 1508219688000 } ... ], // "BUY": [{ "oid": "59e42bf09bd8d374c9956caa", "type": "BUY", "userOid": null, "coinType": "KCS", "coinTypePair": "BTC", "direction": "BUY", "price": 0.00009727,"dealAmount": 31.14503, "pendingAmount": 16.94827, "createdAt": 1508125681000, "updatedAt": 1508125681000 } ... ] var payload = await GetNoncePayloadAsync(); - if (symbol != null) + if (marketSymbol != null) { - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; } JToken token = await MakeJsonRequestAsync("/order/active-map?" + CryptoUtility.GetFormForPayload(payload, false), null, payload); @@ -313,10 +313,10 @@ protected override async Task> OnGetOpenOrderDe /// /// /// - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { - var orders = await GetCompletedOrderDetailsAsync(symbol); - orders = orders.Concat(await GetOpenOrderDetailsAsync(symbol)).ToList(); + var orders = await GetCompletedOrderDetailsAsync(marketSymbol); + orders = orders.Concat(await GetOpenOrderDetailsAsync(marketSymbol)).ToList(); return orders?.Where(o => o.OrderId == orderId).FirstOrDefault(); } @@ -326,7 +326,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd var payload = await GetNoncePayloadAsync(); payload["amount"] = order.Amount; payload["price"] = order.Price; - payload["symbol"] = order.Symbol; + payload["symbol"] = order.MarketSymbol; payload["type"] = order.IsBuy ? "BUY" : "SELL"; order.ExtraParameters.CopyTo(payload); @@ -340,10 +340,10 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd /// /// The Original Order Id return from Place Order /// - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { // Find order detail - ExchangeOrderResult order = await GetOrderDetailsAsync(orderId, symbol); + ExchangeOrderResult order = await GetOrderDetailsAsync(orderId, marketSymbol); // There is no order to be cancelled if (order == null) @@ -353,20 +353,20 @@ protected override async Task OnCancelOrderAsync(string orderId, string symbol = var payload = await GetNoncePayloadAsync(); payload["orderOid"] = order.OrderId; - payload["symbol"] = order.Symbol; + payload["symbol"] = order.MarketSymbol; payload["type"] = order.IsBuy ? "BUY" : "SELL"; JToken token = await MakeJsonRequestAsync("/cancel-order?" + CryptoUtility.GetFormForPayload(payload, false), null, payload, "POST"); } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { // { "oid": "598aeb627da3355fa3e851ca", "address": "598aeb627da3355fa3e851ca", "context": null, "userOid": "5969ddc96732d54312eb960e", "coinType": "KCS", "createdAt": 1502276446000, "deletedAt": null, "updatedAt": 1502276446000, "lastReceivedAt": 1502276446000 } - JToken token = await MakeJsonRequestAsync("/account/" + symbol + "/wallet/address", null, await GetNoncePayloadAsync()); + JToken token = await MakeJsonRequestAsync("/account/" + currency + "/wallet/address", null, await GetNoncePayloadAsync()); if (token != null && token.HasValues) { return new ExchangeDepositDetails() { - Symbol = symbol, + Currency = currency, Address = token["address"].ToStringInvariant(), AddressTag = token["userOid"].ToStringInvariant() // this isn't in their documentation, but is how it's being used on other interfaces }; @@ -395,7 +395,7 @@ protected override async Task OnWithdrawAsync(Exchan #region Websockets - protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] symbols) + protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] marketSymbols) { var websocketUrlToken = GetWebsocketBulletToken(); return ConnectWebSocket( @@ -405,21 +405,21 @@ protected override IWebSocket OnGetTickersWebSocket(Action() == "message") { var dataToken = token["data"]; - var symbol = dataToken["symbol"].ToStringInvariant(); - ExchangeTicker ticker = this.ParseTicker(dataToken, symbol, "sell", "buy", "lastDealPrice", "vol", "volValue", "datetime", TimestampType.UnixMilliseconds); - callback(new List>() {new KeyValuePair(symbol, ticker)}); + var marketSymbol = dataToken["symbol"].ToStringInvariant(); + ExchangeTicker ticker = this.ParseTicker(dataToken, marketSymbol, "sell", "buy", "lastDealPrice", "vol", "volValue", "datetime", TimestampType.UnixMilliseconds); + callback(new List>() {new KeyValuePair(marketSymbol, ticker)}); } return Task.CompletedTask; }, async (_socket) => { //need to subscribe to tickers one by one - symbols = symbols == null || symbols.Length == 0 ? (await GetSymbolsAsync()).ToArray() : symbols; + marketSymbols = marketSymbols == null || marketSymbols.Length == 0 ? (await GetMarketSymbolsAsync()).ToArray() : marketSymbols; var id = DateTime.UtcNow.Ticks; - foreach (var symbol in symbols) + foreach (var marketSymbol in marketSymbols) { // subscribe to tick topic - await _socket.SendMessageAsync(new {id = id++, type = "subscribe", topic = $"/market/{symbol}_TICK" }); + await _socket.SendMessageAsync(new {id = id++, type = "subscribe", topic = $"/market/{marketSymbol}_TICK" }); } } ); @@ -440,7 +440,7 @@ private ExchangeOrderResult ParseOpenOrder(JToken token) ExchangeOrderResult order = new ExchangeOrderResult() { OrderId = token["oid"].ToStringInvariant(), - Symbol = token["coinType"].ToStringInvariant() + "-" + token["coinTypePair"].ToStringInvariant(), + MarketSymbol = token["coinType"].ToStringInvariant() + "-" + token["coinTypePair"].ToStringInvariant(), IsBuy = token["direction"].ToStringInvariant().Equals("BUY"), Price = token["price"].ConvertInvariant(), AveragePrice = token["price"].ConvertInvariant(), @@ -465,7 +465,7 @@ private ExchangeOrderResult ParseCompletedOrder(JToken token) return new ExchangeOrderResult() { OrderId = token["oid"].ToStringInvariant(), - Symbol = token["coinType"].ToStringInvariant() + "-" + token["coinTypePair"].ToStringInvariant(), + MarketSymbol = token["coinType"].ToStringInvariant() + "-" + token["coinTypePair"].ToStringInvariant(), IsBuy = token["direction"].ToStringInvariant().Equals("BUY"), Amount = token["amount"].ConvertInvariant(), AmountFilled = token["amount"].ConvertInvariant(), diff --git a/ExchangeSharp/API/Exchanges/Livecoin/ExchangeLivecoinAPI.cs b/ExchangeSharp/API/Exchanges/Livecoin/ExchangeLivecoinAPI.cs index 2d0145c1..574fc6ff 100644 --- a/ExchangeSharp/API/Exchanges/Livecoin/ExchangeLivecoinAPI.cs +++ b/ExchangeSharp/API/Exchanges/Livecoin/ExchangeLivecoinAPI.cs @@ -26,7 +26,7 @@ public sealed partial class ExchangeLivecoinAPI : ExchangeAPI public ExchangeLivecoinAPI() { RequestContentType = "application/x-www-form-urlencoded"; - SymbolSeparator = "/"; + MarketSymbolSeparator = "/"; } #region ProcessRequest @@ -70,7 +70,7 @@ protected override async Task> OnG return currencies; } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); // {"success": true,"minBtcVolume": 0.0005,"restrictions": [{"currencyPair": "BTC/USD","priceScale": 5}, ... ]} @@ -79,7 +79,7 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { List markets = new List(); // {"success": true,"minBtcVolume": 0.0005,"restrictions": [{"currencyPair": "BTC/USD","priceScale": 5}, ... ]} @@ -89,12 +89,12 @@ protected override async Task> OnGetSymbolsMetadataA var split = market["currencyPair"].ToStringInvariant().Split('/'); var exchangeMarket = new ExchangeMarket { - MarketName = market["currencyPair"].ToStringInvariant(), - BaseCurrency = split[1], - MarketCurrency = split[0], + MarketSymbol = market["currencyPair"].ToStringInvariant(), + BaseCurrency = split[0], + QuoteCurrency = split[1], IsActive = true, - MinTradeSize = (decimal) market["minLimitQuantity"], - PriceStepSize = (decimal?) (1 / Math.Pow(10, (int) market["priceScale"])) + MinTradeSize = market["minLimitQuantity"].ConvertInvariant(), + PriceStepSize = Math.Pow(.1, market["priceScale"].ConvertInvariant()).ConvertInvariant() }; markets.Add(exchangeMarket); @@ -102,9 +102,9 @@ protected override async Task> OnGetSymbolsMetadataA return markets; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken token = await MakeJsonRequestAsync("/exchange/ticker?currencyPair=" + symbol.UrlEncode()); + JToken token = await MakeJsonRequestAsync("/exchange/ticker?currencyPair=" + marketSymbol.UrlEncode()); return ParseTicker(token); } @@ -116,21 +116,21 @@ protected override async Task>> return tickers; } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - JToken token = await MakeJsonRequestAsync("/exchange/order_book?currencyPair=" + symbol.UrlEncode() + "&depth=" + maxCount.ToStringInvariant()); + JToken token = await MakeJsonRequestAsync("/exchange/order_book?currencyPair=" + marketSymbol.UrlEncode() + "&depth=" + maxCount.ToStringInvariant()); return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token); } /// /// Returns trades from the last minute /// - /// + /// /// - protected override async Task> OnGetRecentTradesAsync(string symbol) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol) { List trades = new List(); - JToken token = await MakeJsonRequestAsync("/exchange/last_trades?currencyPair=" + symbol.UrlEncode()); + JToken token = await MakeJsonRequestAsync("/exchange/last_trades?currencyPair=" + marketSymbol.UrlEncode()); foreach (JToken trade in token) { trades.Add(ParseTrade(trade)); @@ -142,14 +142,14 @@ protected override async Task> OnGetRecentTradesAsync /// Max returns is trades from the last hour only /// /// - /// + /// /// /// - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { List trades = new List(); // Not directly supported so we'll return what they have and filter if necessary - JToken token = await MakeJsonRequestAsync("/exchange/last_trades?currencyPair=" + symbol.UrlEncode() + "&minutesOrHour=false"); + JToken token = await MakeJsonRequestAsync("/exchange/last_trades?currencyPair=" + marketSymbol.UrlEncode() + "&minutesOrHour=false"); foreach (JToken trade in token) { ExchangeTrade rc = ParseTrade(trade); @@ -163,13 +163,13 @@ protected override async Task OnGetHistoricalTradesAsync(Func - /// + /// /// /// /// /// /// - protected override Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { throw new NotImplementedException(); } @@ -214,19 +214,19 @@ protected override async Task> OnGetAmountsAvailable return amounts; } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { JToken token = await MakeJsonRequestAsync("/exchange/order?orderId=" + orderId, null, await GetNoncePayloadAsync()); return ParseOrder(token); } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { // We can increase the number of orders returned by including a limit parameter if desired List orders = new List(); var payload = await GetNoncePayloadAsync(); payload.Add("openClosed", "CLOSED"); // returns both closed and cancelled - if (symbol != null) payload.Add("currencyPair", symbol); + if (marketSymbol != null) payload.Add("currencyPair", marketSymbol); if (afterDate != null) payload.Add("issuedFrom", ((DateTime)afterDate).UnixTimestampFromDateTimeMilliseconds()); JToken token = await MakeJsonRequestAsync("/exchange/client_orders?" + CryptoUtility.GetFormForPayload(payload, false), null, await GetNoncePayloadAsync()); @@ -240,14 +240,14 @@ protected override async Task> OnGetCompletedOr /// /// Limited to the last 100 open orders /// - /// + /// /// - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); var payload = await GetNoncePayloadAsync(); payload.Add("openClosed", "OPEM"); - if (symbol != null) payload.Add("currencyPair", symbol); + if (marketSymbol != null) payload.Add("currencyPair", marketSymbol); JToken token = await MakeJsonRequestAsync("/exchange/client_orders?" + CryptoUtility.GetFormForPayload(payload, false), null, await GetNoncePayloadAsync()); foreach (JToken order in token) @@ -270,7 +270,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd orderType += order.IsBuy ? "buylimit" : "selllimit"; payload["price"] = order.Price; } - payload["currencyPair"] = order.Symbol; + payload["currencyPair"] = order.MarketSymbol; payload["quantity"] = order.Amount; order.ExtraParameters.CopyTo(payload); @@ -279,7 +279,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return new ExchangeOrderResult() { OrderId = token["orderId"].ToStringInvariant(), Result = ExchangeAPIOrderResult.Pending }; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { // can only cancel limit orders, which kinda makes sense, but we also need the currency pair, which requires a lookup var order = await OnGetOrderDetailsAsync(orderId); @@ -287,11 +287,11 @@ protected override async Task OnCancelOrderAsync(string orderId, string symbol = { // { "success": true,"cancelled": true,"message": null,"quantity": 0.0005,"tradeQuantity": 0} await MakeJsonRequestAsync("/exchange/cancel_limit?currencyPair=" + - NormalizeSymbol(order.Symbol).UrlEncode() + "&orderId=" + orderId, null, await GetNoncePayloadAsync()); + NormalizeMarketSymbol(order.MarketSymbol).UrlEncode() + "&orderId=" + orderId, null, await GetNoncePayloadAsync()); } } - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { List deposits = new List(); @@ -307,12 +307,12 @@ protected override async Task> OnGetDepositHist return deposits; } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { - JToken token = await MakeJsonRequestAsync("/payment/get/address?" + "currency=" + symbol.UrlEncode(), BaseUrl, await GetNoncePayloadAsync()); - if (token != null && token.HasValues && token["currency"].ToStringInvariant() == symbol && token["wallet"].ToStringInvariant().Length != 0) + JToken token = await MakeJsonRequestAsync("/payment/get/address?" + "currency=" + currency.UrlEncode(), BaseUrl, await GetNoncePayloadAsync()); + if (token != null && token.HasValues && token["currency"].ToStringInvariant() == currency && token["wallet"].ToStringInvariant().Length != 0) { - ExchangeDepositDetails address = new ExchangeDepositDetails() {Symbol = symbol }; + ExchangeDepositDetails address = new ExchangeDepositDetails() {Currency = currency }; if (token["wallet"].ToStringInvariant().Contains("::")) { // address tags are separated with a '::' @@ -345,8 +345,8 @@ protected override async Task OnWithdrawAsync(Exchan private ExchangeTicker ParseTicker(JToken token) { // [{"symbol": "LTC/BTC","last": 0.00805061,"high": 0.00813633,"low": 0.00784855,"volume": 14729.48452951,"vwap": 0.00795126,"max_bid": 0.00813633,"min_ask": 0.00784855,"best_bid": 0.00798,"best_ask": 0.00811037}, ... ] - string symbol = token["symbol"].ToStringInvariant(); - return this.ParseTicker(token, symbol, "best_ask", "best_bid", "last", "volume"); + string marketSymbol = token["symbol"].ToStringInvariant(); + return this.ParseTicker(token, marketSymbol, "best_ask", "best_bid", "last", "volume"); } private ExchangeTrade ParseTrade(JToken token) @@ -376,7 +376,7 @@ private ExchangeOrderResult ParseClientOrder(JToken token) ExchangeOrderResult order = new ExchangeOrderResult() { OrderId = token["id"].ToStringInvariant(), - Symbol = token["currencyPair"].ToStringInvariant(), + MarketSymbol = token["currencyPair"].ToStringInvariant(), OrderDate = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(token["issueTime"].ConvertInvariant()), IsBuy = token["type"].ToStringInvariant().Contains("BUY"), Price = token["price"].ConvertInvariant(), diff --git a/ExchangeSharp/API/Exchanges/Okex/ExchangeOkexAPI.cs b/ExchangeSharp/API/Exchanges/Okex/ExchangeOkexAPI.cs index b1705a4a..92389a33 100644 --- a/ExchangeSharp/API/Exchanges/Okex/ExchangeOkexAPI.cs +++ b/ExchangeSharp/API/Exchanges/Okex/ExchangeOkexAPI.cs @@ -33,8 +33,8 @@ public sealed partial class ExchangeOkexAPI : ExchangeAPI public ExchangeOkexAPI() { RequestContentType = "application/x-www-form-urlencoded"; - SymbolSeparator = "_"; - SymbolIsUppercase = false; + MarketSymbolSeparator = "_"; + MarketSymbolIsUppercase = false; WebSocketOrderBookType = WebSocketOrderBookType.FullBookFirstThenDeltas; } @@ -90,21 +90,21 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti } } - private async Task> MakeRequestOkexAsync(string symbol, string subUrl, string baseUrl = null) + private async Task> MakeRequestOkexAsync(string marketSymbol, string subUrl, string baseUrl = null) { - symbol = NormalizeSymbol(symbol); - JToken obj = await MakeJsonRequestAsync(subUrl.Replace("$SYMBOL$", symbol ?? string.Empty), baseUrl); - return new Tuple(obj, symbol); + marketSymbol = NormalizeMarketSymbol(marketSymbol); + JToken obj = await MakeJsonRequestAsync(subUrl.Replace("$SYMBOL$", marketSymbol ?? string.Empty), baseUrl); + return new Tuple(obj, marketSymbol); } #endregion #region Public APIs - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { - var m = await GetSymbolsMetadataAsync(); - return m.Select(x => x.MarketName); + var m = await GetMarketSymbolsMetadataAsync(); + return m.Select(x => x.MarketSymbol); } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { /* {"code":0,"data":[{"baseCurrency":1,"collect":"0","isMarginOpen":false,"listDisplay": 0, @@ -125,30 +125,31 @@ protected override async Task> OnGetSymbolsMetadataA }, */ List markets = new List(); - JToken allSymbols = await MakeJsonRequestAsync("/markets/products", BaseUrlV2); - foreach (JToken symbol in allSymbols) + JToken allMarketSymbolTokens = await MakeJsonRequestAsync("/markets/products", BaseUrlV2); + foreach (JToken marketSymbolToken in allMarketSymbolTokens) { - var marketName = symbol["symbol"].ToStringLowerInvariant(); - string[] pieces = marketName.Split('_'); + var marketName = marketSymbolToken["symbol"].ToStringInvariant(); + string[] pieces = marketName.ToStringUpperInvariant().Split('_'); var market = new ExchangeMarket { - MarketName = marketName, - IsActive = symbol["online"].ConvertInvariant(), - BaseCurrency = pieces[1], - MarketCurrency = pieces[0], + MarketSymbol = marketName, + IsActive = marketSymbolToken["online"].ConvertInvariant(), + QuoteCurrency = pieces[1], + BaseCurrency = pieces[0], + MarginEnabled = marketSymbolToken["isMarginOpen"].ConvertInvariant(false) }; - var quotePrecision = symbol["quotePrecision"].ConvertInvariant(); + var quotePrecision = marketSymbolToken["quotePrecision"].ConvertInvariant(); var quantityStepSize = Math.Pow(10, -quotePrecision); market.QuantityStepSize = quantityStepSize.ConvertInvariant(); - var maxSizeDigit = symbol["maxSizeDigit"].ConvertInvariant(); + var maxSizeDigit = marketSymbolToken["maxSizeDigit"].ConvertInvariant(); var maxTradeSize = Math.Pow(10, maxSizeDigit); market.MaxTradeSize = maxTradeSize.ConvertInvariant() - 1.0m; - market.MinTradeSize = symbol["minTradeSize"].ConvertInvariant(); + market.MinTradeSize = marketSymbolToken["minTradeSize"].ConvertInvariant(); - market.PriceStepSize = symbol["quoteIncrement"].ConvertInvariant(); + market.PriceStepSize = marketSymbolToken["quoteIncrement"].ConvertInvariant(); market.MinPrice = market.PriceStepSize.Value; - var maxPriceDigit = symbol["maxPriceDigit"].ConvertInvariant(); + var maxPriceDigit = marketSymbolToken["maxPriceDigit"].ConvertInvariant(); var maxPrice = Math.Pow(10, maxPriceDigit); market.MaxPrice = maxPrice.ConvertInvariant() - 1.0m; @@ -157,9 +158,9 @@ protected override async Task> OnGetSymbolsMetadataA return markets; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - var data = await MakeRequestOkexAsync(symbol, "/ticker.do?symbol=$SYMBOL$"); + var data = await MakeRequestOkexAsync(marketSymbol, "/ticker.do?symbol=$SYMBOL$"); return ParseTicker(data.Item2, data.Item1); } @@ -167,16 +168,16 @@ protected override async Task>> { var data = await MakeRequestOkexAsync(null, "/markets/index-tickers?limit=100000000", BaseUrlV2); List> tickers = new List>(); - string symbol; + string marketSymbol; foreach (JToken token in data.Item1) { - symbol = token["symbol"].ToStringInvariant(); - tickers.Add(new KeyValuePair(symbol, ParseTickerV2(symbol, token))); + marketSymbol = token["symbol"].ToStringInvariant(); + tickers.Add(new KeyValuePair(marketSymbol, ParseTickerV2(marketSymbol, token))); } return tickers; } - protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] symbols) + protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) { /* {[ @@ -225,7 +226,7 @@ protected override IWebSocket OnGetTradesWebSocket(Action { - symbols = await AddSymbolsToChannel(_socket, "ok_sub_spot_{0}_deals", symbols); + marketSymbols = await AddMarketSymbolsToChannel(_socket, "ok_sub_spot_{0}_deals", marketSymbols); }, (_socket, symbol, sArray, token) => { IEnumerable trades = ParseTradesWebSocket(token); @@ -237,7 +238,7 @@ protected override IWebSocket OnGetTradesWebSocket(Action callback, int maxCount = 20, params string[] symbols) + protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) { /* {[ @@ -284,26 +285,26 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action return ConnectWebSocketOkex(async (_socket) => { - symbols = await AddSymbolsToChannel(_socket, $"ok_sub_spot_{{0}}_depth_{maxCount}", symbols); + marketSymbols = await AddMarketSymbolsToChannel(_socket, $"ok_sub_spot_{{0}}_depth_{maxCount}", marketSymbols); }, (_socket, symbol, sArray, token) => { ExchangeOrderBook book = ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token, sequence: "timestamp", maxCount: maxCount); - book.Symbol = symbol; + book.MarketSymbol = symbol; callback(book); return Task.CompletedTask; }); } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - var token = await MakeRequestOkexAsync(symbol, "/depth.do?symbol=$SYMBOL$"); + var token = await MakeRequestOkexAsync(marketSymbol, "/depth.do?symbol=$SYMBOL$"); return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token.Item1, maxCount: maxCount); } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { List allTrades = new List(); - var trades = await MakeRequestOkexAsync(symbol, "/trades.do?symbol=$SYMBOL$"); + var trades = await MakeRequestOkexAsync(marketSymbol, "/trades.do?symbol=$SYMBOL$"); foreach (JToken trade in trades.Item1) { // [ { "date": "1367130137", "date_ms": "1367130137000", "price": 787.71, "amount": 0.003, "tid": "230433", "type": "sell" } ] @@ -312,7 +313,7 @@ protected override async Task OnGetHistoricalTradesAsync(Func> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { /* [ @@ -326,7 +327,7 @@ protected override async Task> OnGetCandlesAsync(strin */ List candles = new List(); - string url = "/kline.do?symbol=" + symbol; + string url = "/kline.do?symbol=" + marketSymbol; if (startDate != null) { url += "&since=" + (long)startDate.Value.UnixTimestampFromDateTimeMilliseconds(); @@ -340,7 +341,7 @@ protected override async Task> OnGetCandlesAsync(strin JToken obj = await MakeJsonRequestAsync(url); foreach (JArray token in obj) { - candles.Add(this.ParseCandle(token, symbol, periodSeconds, 1, 2, 3, 4, 0, TimestampType.UnixMilliseconds, 5)); + candles.Add(this.ParseCandle(token, marketSymbol, periodSeconds, 1, 2, 3, 4, 0, TimestampType.UnixMilliseconds, 5)); } return candles; } @@ -406,12 +407,12 @@ protected override async Task> OnGetAmountsAvailable protected override async Task OnPlaceOrderAsync(ExchangeOrderRequest order) { Dictionary payload = await GetNoncePayloadAsync(); - payload["symbol"] = order.Symbol; + payload["symbol"] = order.MarketSymbol; payload["type"] = (order.IsBuy ? "buy" : "sell"); // Okex has strict rules on which prices and quantities are allowed. They have to match the rules defined in the market definition. - decimal outputQuantity = await ClampOrderQuantity(order.Symbol, order.Amount); - decimal outputPrice = await ClampOrderPrice(order.Symbol, order.Price); + decimal outputQuantity = await ClampOrderQuantity(order.MarketSymbol, order.Amount); + decimal outputPrice = await ClampOrderPrice(order.MarketSymbol, order.Price); if (order.OrderType == OrderType.Market) { @@ -446,27 +447,27 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return ParsePlaceOrder(obj, order); } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { Dictionary payload = await GetNoncePayloadAsync(); - if (symbol.Length == 0) + if (marketSymbol.Length == 0) { throw new InvalidOperationException("Okex cancel order request requires symbol"); } - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; payload["order_id"] = orderId; await MakeJsonRequestAsync("/cancel_order.do", BaseUrl, payload, "POST"); } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { List orders = new List(); Dictionary payload = await GetNoncePayloadAsync(); - if (symbol.Length == 0) + if (marketSymbol.Length == 0) { throw new InvalidOperationException("Okex single order details request requires symbol"); } - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; payload["order_id"] = orderId; JToken token = await MakeJsonRequestAsync("/order_info.do", BaseUrl, payload, "POST"); foreach (JToken order in token["orders"]) @@ -478,12 +479,12 @@ protected override async Task OnGetOrderDetailsAsync(string return orders[0]; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol) { List orders = new List(); Dictionary payload = await GetNoncePayloadAsync(); - payload["symbol"] = symbol; + payload["symbol"] = marketSymbol; // if order_id is -1, then return all unfilled orders, otherwise return the order specified payload["order_id"] = -1; JToken token = await MakeJsonRequestAsync("/order_info.do", BaseUrl, payload, "POST"); @@ -541,7 +542,7 @@ private ExchangeOrderResult ParsePlaceOrder(JToken token, ExchangeOrderRequest o Price = order.Price, IsBuy = order.IsBuy, OrderId = token["order_id"].ToStringInvariant(), - Symbol = order.Symbol + MarketSymbol = order.MarketSymbol }; result.AveragePrice = result.Price; result.Result = ExchangeAPIOrderResult.Pending; @@ -599,7 +600,7 @@ private ExchangeOrderResult ParseOrder(JToken token) IsBuy = token["type"].ToStringInvariant().StartsWith("buy"), OrderDate = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(token["create_date"].ConvertInvariant()), OrderId = token["order_id"].ToStringInvariant(), - Symbol = token["symbol"].ToStringInvariant(), + MarketSymbol = token["symbol"].ToStringInvariant(), Result = ParseOrderStatus(token["status"].ConvertInvariant()), }; @@ -651,8 +652,8 @@ private IWebSocket ConnectWebSocketOkex(Func connected, Func { @@ -678,23 +679,23 @@ private IWebSocket ConnectPrivateWebSocketOkex(Func connected, }, 0); } - private async Task AddSymbolsToChannel(IWebSocket socket, string channelFormat, string[] symbols, bool useJustFirstSymbol = false) + private async Task AddMarketSymbolsToChannel(IWebSocket socket, string channelFormat, string[] marketSymbols, bool useJustFirstSymbol = false) { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = (await GetSymbolsAsync()).ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - foreach (string symbol in symbols) + foreach (string marketSymbol in marketSymbols) { - string normalizedSymbol = NormalizeSymbol(symbol); + string normalizedSymbol = NormalizeMarketSymbol(marketSymbol); if (useJustFirstSymbol) { - normalizedSymbol = normalizedSymbol.Substring(0, normalizedSymbol.IndexOf(SymbolSeparator[0])); + normalizedSymbol = normalizedSymbol.Substring(0, normalizedSymbol.IndexOf(MarketSymbolSeparator[0])); } string channel = string.Format(channelFormat, normalizedSymbol); await socket.SendMessageAsync(new { @event = "addChannel", channel }); } - return symbols; + return marketSymbols; } #endregion diff --git a/ExchangeSharp/API/Exchanges/Poloniex/ExchangePoloniexAPI.cs b/ExchangeSharp/API/Exchanges/Poloniex/ExchangePoloniexAPI.cs index 1da5b60c..3414b5b1 100644 --- a/ExchangeSharp/API/Exchanges/Poloniex/ExchangePoloniexAPI.cs +++ b/ExchangeSharp/API/Exchanges/Poloniex/ExchangePoloniexAPI.cs @@ -54,8 +54,8 @@ static ExchangePoloniexAPI() public ExchangePoloniexAPI() { RequestContentType = "application/x-www-form-urlencoded"; - SymbolSeparator = "_"; - SymbolIsReversed = true; + MarketSymbolSeparator = "_"; + MarketSymbolIsReversed = true; WebSocketOrderBookType = WebSocketOrderBookType.DeltasOnly; } @@ -100,9 +100,9 @@ public ExchangeOrderResult ParsePlacedOrder(JToken result) /// Parses an order which has not been filled. /// /// The JToken to parse. - /// Symbol or null if it's in the result + /// Market symbol or null if it's in the result /// ExchangeOrderResult with the open order and how much is remaining to fill - public ExchangeOrderResult ParseOpenOrder(JToken result, string symbol = null) + public ExchangeOrderResult ParseOpenOrder(JToken result, string marketSymbol = null) { ExchangeOrderResult order = new ExchangeOrderResult { @@ -112,7 +112,7 @@ public ExchangeOrderResult ParseOpenOrder(JToken result, string symbol = null) OrderId = result["orderNumber"].ToStringInvariant(), Price = result["rate"].ConvertInvariant(), Result = ExchangeAPIOrderResult.Pending, - Symbol = (symbol ?? result.Parent.Path) + MarketSymbol = (marketSymbol ?? result.Parent.Path) }; decimal amount = result["amount"].ConvertInvariant(); @@ -137,13 +137,13 @@ public void ParseOrderTrades(IEnumerable trades, ExchangeOrderResult ord { parsedSymbol = trade.Parent.Path; } - if (order.Symbol == "all" || !string.IsNullOrWhiteSpace(parsedSymbol)) + if (order.MarketSymbol == "all" || !string.IsNullOrWhiteSpace(parsedSymbol)) { - order.Symbol = parsedSymbol; + order.MarketSymbol = parsedSymbol; } - if (!string.IsNullOrWhiteSpace(order.Symbol)) + if (!string.IsNullOrWhiteSpace(order.MarketSymbol)) { - order.FeesCurrency = ParseFeesCurrency(order.IsBuy, order.Symbol); + order.FeesCurrency = ParseFeesCurrency(order.IsBuy, order.MarketSymbol); } orderMetadataSet = true; } @@ -178,9 +178,9 @@ public void ParseClosePositionTrades(IEnumerable trades, ExchangeCloseMa { closePosition.IsBuy = trade["type"].ToStringLowerInvariant() != "sell"; - if (!string.IsNullOrWhiteSpace(closePosition.Symbol)) + if (!string.IsNullOrWhiteSpace(closePosition.MarketSymbol)) { - closePosition.FeesCurrency = ParseFeesCurrency(closePosition.IsBuy, closePosition.Symbol); + closePosition.FeesCurrency = ParseFeesCurrency(closePosition.IsBuy, closePosition.MarketSymbol); } closePositionMetadataSet = true; @@ -212,13 +212,13 @@ private static decimal CalculateFees(decimal tradeAmt, decimal tradeRate, bool i return Math.Round(amount, 8, MidpointRounding.AwayFromZero); } - private void ParseCompletedOrderDetails(List orders, JToken trades, string symbol) + private void ParseCompletedOrderDetails(List orders, JToken trades, string marketSymbol) { IEnumerable orderNumsInTrades = trades.Select(x => x["orderNumber"].ToStringInvariant()).Distinct(); foreach (string orderNum in orderNumsInTrades) { IEnumerable tradesForOrder = trades.Where(x => x["orderNumber"].ToStringInvariant() == orderNum); - ExchangeOrderResult order = new ExchangeOrderResult { OrderId = orderNum, Symbol = symbol }; + ExchangeOrderResult order = new ExchangeOrderResult { OrderId = orderNum, MarketSymbol = marketSymbol }; ParseOrderTrades(tradesForOrder, order); order.Price = order.AveragePrice; order.Result = ExchangeAPIOrderResult.Filled; @@ -291,7 +291,7 @@ protected override async Task> OnG return currencies; } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); var tickers = await GetTickersAsync(); @@ -302,7 +302,7 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { //https://poloniex.com/public?command=returnOrderBook¤cyPair=all&depth=0 /* @@ -322,7 +322,7 @@ protected override async Task> OnGetSymbolsMetadataA foreach (var kvp in lookup) { - var market = new ExchangeMarket { MarketName = kvp.Key, IsActive = false }; + var market = new ExchangeMarket { MarketSymbol = kvp.Key, IsActive = false }; string isFrozen = kvp.Value["isFrozen"].ToStringInvariant(); if (string.Equals(isFrozen, "0")) @@ -333,8 +333,8 @@ protected override async Task> OnGetSymbolsMetadataA string[] pairs = kvp.Key.Split('_'); if (pairs.Length == 2) { - market.BaseCurrency = pairs[0]; - market.MarketCurrency = pairs[1]; + market.QuoteCurrency = pairs[0]; + market.BaseCurrency = pairs[1]; market.PriceStepSize = StepSize; market.QuantityStepSize = StepSize; market.MinPrice = StepSize; @@ -347,12 +347,12 @@ protected override async Task> OnGetSymbolsMetadataA return markets; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { IEnumerable> tickers = await GetTickersAsync(); foreach (var kv in tickers) { - if (kv.Key == symbol) + if (kv.Key == marketSymbol) { return kv.Value; } @@ -367,10 +367,11 @@ protected override async Task>> JToken obj = await MakeJsonRequestAsync("/public?command=returnTicker"); foreach (JProperty prop in obj.Children()) { - string symbol = prop.Name; + string marketSymbol = prop.Name; JToken values = prop.Value; - ExchangeTicker ticker = this.ParseTicker(values, symbol, "lowestAsk", "highestBid", "last", "baseVolume", "quoteVolume", idKey: "id"); - tickers.Add(new KeyValuePair(symbol, this.ParseTicker(values, symbol, "lowestAsk", "highestBid", "last", "baseVolume", "quoteVolume", idKey: "id"))); + //NOTE: Poloniex uses the term "caseVolume" when referring to the QuoteCurrencyVolume + ExchangeTicker ticker = this.ParseTicker(values, marketSymbol, "lowestAsk", "highestBid", "last", "quoteVolume", "baseVolume", idKey: "id"); + tickers.Add(new KeyValuePair(marketSymbol, ticker)); } return tickers; } @@ -405,7 +406,7 @@ protected override IWebSocket OnGetTickersWebSocket(Action> callback, params string[] symbols) + protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) { Dictionary> messageIdToSymbol = new Dictionary>(); return ConnectWebSocket(string.Empty, (_socket, msg) => @@ -450,19 +451,19 @@ protected override IWebSocket OnGetTradesWebSocket(Action { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = (await GetSymbolsAsync()).ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } // subscribe to order book and trades channel for each symbol - foreach (var sym in symbols) + foreach (var sym in marketSymbols) { - await _socket.SendMessageAsync(new { command = "subscribe", channel = NormalizeSymbol(sym) }); + await _socket.SendMessageAsync(new { command = "subscribe", channel = NormalizeMarketSymbol(sym) }); } }); } - protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols) + protected override IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) { Dictionary> messageIdToSymbol = new Dictionary>(); return ConnectWebSocket(string.Empty, (_socket, msg) => @@ -520,9 +521,9 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action var depth = new ExchangeOrderPrice { Price = data[2].ConvertInvariant(), Amount = data[3].ConvertInvariant() }; var list = (type == 1 ? book.Bids : book.Asks); list[depth.Price] = depth; - book.Symbol = symbol.Item1; + book.MarketSymbol = symbol.Item1; book.SequenceId = symbol.Item2 + 1; - messageIdToSymbol[msgId] = new Tuple(book.Symbol, book.SequenceId); + messageIdToSymbol[msgId] = new Tuple(book.MarketSymbol, book.SequenceId); } } else @@ -537,22 +538,22 @@ protected override IWebSocket OnGetOrderBookWebSocket(Action return Task.CompletedTask; }, async (_socket) => { - if (symbols == null || symbols.Length == 0) + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = (await GetSymbolsAsync()).ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } // subscribe to order book and trades channel for each symbol - foreach (var sym in symbols) + foreach (var sym in marketSymbols) { - await _socket.SendMessageAsync(new { command = "subscribe", channel = NormalizeSymbol(sym) }); + await _socket.SendMessageAsync(new { command = "subscribe", channel = NormalizeMarketSymbol(sym) }); } }); } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { // {"asks":[["0.01021997",22.83117932],["0.01022000",82.3204],["0.01022480",140],["0.01023054",241.06436945],["0.01023057",140]],"bids":[["0.01020233",164.195],["0.01020232",66.22565096],["0.01020200",5],["0.01020010",66.79296968],["0.01020000",490.19563761]],"isFrozen":"0","seq":147171861} - JToken token = await MakeJsonRequestAsync("/public?command=returnOrderBook¤cyPair=" + symbol + "&depth=" + maxCount); + JToken token = await MakeJsonRequestAsync("/public?command=returnOrderBook¤cyPair=" + marketSymbol + "&depth=" + maxCount); return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token); } @@ -578,7 +579,7 @@ protected override async Task, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { // [{"globalTradeID":245321705,"tradeID":11501281,"date":"2017-10-20 17:39:17","type":"buy","rate":"0.01022188","amount":"0.00954454","total":"0.00009756"},...] ExchangeHistoricalTradeHelper state = new ExchangeHistoricalTradeHelper(this) @@ -588,14 +589,14 @@ protected override async Task OnGetHistoricalTradesAsync(Func token.ParseTrade("amount", "rate", "type", "date", TimestampType.Iso8601, "globalTradeID"), StartDate = startDate, - Symbol = symbol, + MarketSymbol = marketSymbol, TimestampFunction = (DateTime dt) => ((long)CryptoUtility.UnixTimestampFromDateTimeSeconds(dt)).ToStringInvariant(), - Url = "/public?command=returnTradeHistory¤cyPair=[symbol]&start={0}&end={1}" + Url = "/public?command=returnTradeHistory¤cyPair=[marketSymbol]&start={0}&end={1}" }; await state.ProcessHistoricalTrades(); } - protected override async Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { if (limit != null) { @@ -604,7 +605,7 @@ protected override async Task> OnGetCandlesAsync(strin // https://poloniex.com/public?command=returnChartData¤cyPair=BTC_XMR&start=1405699200&end=9999999999&period=14400 // [{"date":1405699200,"high":0.0045388,"low":0.00403001,"open":0.00404545,"close":0.00435873,"volume":44.34555992,"quoteVolume":10311.88079097,"weightedAverage":0.00430043}] - string url = "/public?command=returnChartData¤cyPair=" + symbol; + string url = "/public?command=returnChartData¤cyPair=" + marketSymbol; if (startDate != null) { url += "&start=" + (long)startDate.Value.UnixTimestampFromDateTimeSeconds(); @@ -615,7 +616,7 @@ protected override async Task> OnGetCandlesAsync(strin List candles = new List(); foreach (JToken candle in token) { - candles.Add(this.ParseCandle(candle, symbol, periodSeconds, "open", "high", "low", "close", "date", TimestampType.UnixSeconds, "volume", "quoteVolume", "weightedAverage")); + candles.Add(this.ParseCandle(candle, marketSymbol, periodSeconds, "open", "high", "low", "close", "date", TimestampType.UnixSeconds, "quoteVolume", "volume", "weightedAverage")); } return candles; } @@ -667,11 +668,11 @@ protected override async Task> OnGetMarginAmountsAva return amounts; } - protected override async Task OnGetOpenPositionAsync(string symbol) + protected override async Task OnGetOpenPositionAsync(string marketSymbol) { List orderParams = new List { - "currencyPair", symbol + "currencyPair", marketSymbol }; JToken result = await MakePrivateAPIRequestAsync("getMarginPosition", orderParams); @@ -684,16 +685,16 @@ protected override async Task OnGetOpenPositionAsy ProfitLoss = result["pl"].ConvertInvariant(), LendingFees = result["lendingFees"].ConvertInvariant(), Type = result["type"].ToStringInvariant(), - Symbol = symbol + MarketSymbol = marketSymbol }; return marginPositionResult; } - protected override async Task OnCloseMarginPositionAsync(string symbol) + protected override async Task OnCloseMarginPositionAsync(string marketSymbol) { List orderParams = new List { - "currencyPair", symbol + "currencyPair", marketSymbol }; JToken result = await MakePrivateAPIRequestAsync("closeMarginPosition", orderParams); @@ -702,14 +703,14 @@ protected override async Task OnCloseMarginPo { Success = result["success"].ConvertInvariant(), Message = result["message"].ToStringInvariant(), - Symbol = symbol + MarketSymbol = marketSymbol }; JToken symbolTrades = result["resultingTrades"]; if (symbolTrades == null || !symbolTrades.Any()) return closePositionResult; - JToken trades = symbolTrades[symbol]; + JToken trades = symbolTrades[marketSymbol]; if (trades != null && trades.Children().Count() != 0) { ParseClosePositionTrades(trades, closePositionResult); @@ -725,12 +726,12 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd throw new NotSupportedException("Order type " + order.OrderType + " not supported"); } - decimal orderAmount = await ClampOrderQuantity(order.Symbol, order.Amount); - decimal orderPrice = await ClampOrderPrice(order.Symbol, order.Price); + decimal orderAmount = await ClampOrderQuantity(order.MarketSymbol, order.Amount); + decimal orderPrice = await ClampOrderPrice(order.MarketSymbol, order.Price); List orderParams = new List { - "currencyPair", order.Symbol, + "currencyPair", order.MarketSymbol, "rate", orderPrice.ToStringInvariant(), "amount", orderAmount.ToStringInvariant() }; @@ -742,21 +743,21 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd JToken result = await MakePrivateAPIRequestAsync(order.IsBuy ? (order.IsMargin ? "marginBuy" : "buy") : (order.IsMargin ? "marginSell" : "sell"), orderParams); ExchangeOrderResult exchangeOrderResult = ParsePlacedOrder(result); - exchangeOrderResult.Symbol = order.Symbol; - exchangeOrderResult.FeesCurrency = ParseFeesCurrency(order.IsBuy, order.Symbol); + exchangeOrderResult.MarketSymbol = order.MarketSymbol; + exchangeOrderResult.FeesCurrency = ParseFeesCurrency(order.IsBuy, order.MarketSymbol); return exchangeOrderResult; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { - if (symbol.Length == 0) + if (marketSymbol.Length == 0) { - symbol = "all"; + marketSymbol = "all"; } List orders = new List(); - JToken result = await MakePrivateAPIRequestAsync("returnOpenOrders", new object[] { "currencyPair", symbol }); - if (symbol == "all") + JToken result = await MakePrivateAPIRequestAsync("returnOpenOrders", new object[] { "currencyPair", marketSymbol }); + if (marketSymbol == "all") { foreach (JProperty prop in result) { @@ -773,14 +774,14 @@ protected override async Task> OnGetOpenOrderDe { foreach (JToken token in array) { - orders.Add(ParseOpenOrder(token, symbol)); + orders.Add(ParseOpenOrder(token, marketSymbol)); } } return orders; } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { JToken resultArray = await MakePrivateAPIRequestAsync("returnOrderTrades", new object[] { "orderNumber", orderId }); string tickerSymbol = resultArray[0]["currencyPair"].ToStringInvariant(); @@ -796,17 +797,17 @@ protected override async Task OnGetOrderDetailsAsync(string return orders[0]; } - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { - symbol = string.IsNullOrWhiteSpace(symbol) ? "all" : NormalizeSymbol(symbol); + marketSymbol = string.IsNullOrWhiteSpace(marketSymbol) ? "all" : NormalizeMarketSymbol(marketSymbol); List orders = new List(); afterDate = afterDate ?? CryptoUtility.UtcNow.Subtract(TimeSpan.FromDays(365.0)); long afterTimestamp = (long)afterDate.Value.UnixTimestampFromDateTimeSeconds(); - JToken result = await MakePrivateAPIRequestAsync("returnTradeHistory", new object[] { "currencyPair", symbol, "limit", 10000, "start", afterTimestamp }); - if (symbol != "all") + JToken result = await MakePrivateAPIRequestAsync("returnTradeHistory", new object[] { "currencyPair", marketSymbol, "limit", 10000, "start", afterTimestamp }); + if (marketSymbol != "all") { - ParseCompletedOrderDetails(orders, result as JArray, symbol); + ParseCompletedOrderDetails(orders, result as JArray, marketSymbol); } else { @@ -818,7 +819,7 @@ protected override async Task> OnGetCompletedOr return orders; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { await MakePrivateAPIRequestAsync("cancelOrder", new object[] { "orderNumber", orderId.ConvertInvariant() }); } @@ -842,7 +843,7 @@ protected override async Task OnWithdrawAsync(Exchan } } - var paramsList = new List { "currency", NormalizeSymbol(withdrawalRequest.Currency), "amount", withdrawalRequest.Amount, "address", withdrawalRequest.Address }; + var paramsList = new List { "currency", NormalizeMarketSymbol(withdrawalRequest.Currency), "amount", withdrawalRequest.Amount, "address", withdrawalRequest.Address }; if (!string.IsNullOrWhiteSpace(withdrawalRequest.AddressTag)) { paramsList.Add("paymentId"); @@ -854,33 +855,33 @@ protected override async Task OnWithdrawAsync(Exchan return resp; } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { // Never reuse IOTA addresses - if (symbol.Equals("MIOTA", StringComparison.OrdinalIgnoreCase)) + if (currency.Equals("MIOTA", StringComparison.OrdinalIgnoreCase)) { forceRegenerate = true; } IReadOnlyDictionary currencies = await GetCurrenciesAsync(); var depositAddresses = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (!forceRegenerate && !(await TryFetchExistingAddresses(symbol, currencies, depositAddresses))) + if (!forceRegenerate && !(await TryFetchExistingAddresses(currency, currencies, depositAddresses))) { return null; } - if (!depositAddresses.TryGetValue(symbol, out var depositDetails)) + if (!depositAddresses.TryGetValue(currency, out var depositDetails)) { - depositDetails = await CreateDepositAddress(symbol, currencies); + depositDetails = await CreateDepositAddress(currency, currencies); } return depositDetails; } /// Gets the deposit history for a symbol - /// (ignored) The symbol to check. + /// (ignored) The currency to check. /// Collection of ExchangeCoinTransfers - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { JToken result = await MakePrivateAPIRequestAsync("returnDepositsWithdrawals", new object[] @@ -895,7 +896,7 @@ protected override async Task> OnGetDepositHist { var deposit = new ExchangeTransaction { - Symbol = token["currency"].ToStringUpperInvariant(), + Currency = token["currency"].ToStringUpperInvariant(), Address = token["address"].ToStringInvariant(), Amount = token["amount"].ConvertInvariant(), BlockchainTxId = token["txid"].ToStringInvariant(), @@ -937,28 +938,28 @@ private static string ParseFeesCurrency(bool isBuy, string symbol) return feesCurrency; } - private async Task TryFetchExistingAddresses(string symbol, IReadOnlyDictionary currencies, Dictionary depositAddresses) + private async Task TryFetchExistingAddresses(string currency, IReadOnlyDictionary currencies, Dictionary depositAddresses) { JToken result = await MakePrivateAPIRequestAsync("returnDepositAddresses"); foreach (JToken jToken in result) { var token = (JProperty)jToken; - var details = new ExchangeDepositDetails { Symbol = token.Name }; + var details = new ExchangeDepositDetails { Currency = token.Name }; - if (!TryPopulateAddressAndTag(symbol, currencies, details, token.Value.ToStringInvariant())) + if (!TryPopulateAddressAndTag(currency, currencies, details, token.Value.ToStringInvariant())) { return false; } - depositAddresses[details.Symbol] = details; + depositAddresses[details.Currency] = details; } return true; } - private static bool TryPopulateAddressAndTag(string symbol, IReadOnlyDictionary currencies, ExchangeDepositDetails details, string address) + private static bool TryPopulateAddressAndTag(string currency, IReadOnlyDictionary currencies, ExchangeDepositDetails details, string address) { - if (currencies.TryGetValue(symbol, out ExchangeCurrency coin)) + if (currencies.TryGetValue(currency, out ExchangeCurrency coin)) { if (!string.IsNullOrWhiteSpace(coin.BaseAddress)) { @@ -982,18 +983,18 @@ private static bool TryPopulateAddressAndTag(string symbol, IReadOnlyDictionary< /// /// Create a deposit address /// - /// Symbol to create an address for + /// Currency to create an address for /// Lookup of existing currencies /// ExchangeDepositDetails with an address or a BaseAddress/AddressTag pair. - private async Task CreateDepositAddress(string symbol, IReadOnlyDictionary currencies) + private async Task CreateDepositAddress(string currency, IReadOnlyDictionary currencies) { - JToken result = await MakePrivateAPIRequestAsync("generateNewAddress", new object[] { "currency", symbol }); + JToken result = await MakePrivateAPIRequestAsync("generateNewAddress", new object[] { "currency", currency }); var details = new ExchangeDepositDetails { - Symbol = symbol, + Currency = currency, }; - if (!TryPopulateAddressAndTag(symbol, currencies, details, result["response"].ToStringInvariant())) + if (!TryPopulateAddressAndTag(currency, currencies, details, result["response"].ToStringInvariant())) { return null; } diff --git a/ExchangeSharp/API/Exchanges/TuxExchange/ExchangeTuxExchangeAPI.cs b/ExchangeSharp/API/Exchanges/TuxExchange/ExchangeTuxExchangeAPI.cs index 38d52380..449d75d7 100644 --- a/ExchangeSharp/API/Exchanges/TuxExchange/ExchangeTuxExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/TuxExchange/ExchangeTuxExchangeAPI.cs @@ -33,8 +33,8 @@ public ExchangeTuxExchangeAPI() { RequestContentType = "application/x-www-form-urlencoded"; NonceStyle = NonceStyle.UnixMillisecondsString; - SymbolSeparator = "_"; - SymbolIsReversed = true; + MarketSymbolSeparator = "_"; + MarketSymbolIsReversed = true; } #region ProcessRequest @@ -86,7 +86,7 @@ protected override async Task> OnG /// Uses TuxExchange getticker method to return market names /// /// - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { // {"BTC_LTC":{"id":"2","last":"0.0068","lowestAsk":0,"highestBid":0,"percentChange":"6.249999999999989","quoteVolume":"0.5550265","isFrozen":0,"baseVolume":0,"high24hr":"0.0068","low24hr":"0.0064"}, ... JToken token = await MakeJsonRequestAsync("/api?method=getticker"); @@ -97,7 +97,7 @@ protected override async Task> OnGetSymbolsAsync() /// Uses TuxExchange getticker method to get as much Market info as provided /// /// - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { List markets = new List(); // {"BTC_LTC":{"id":"2","last":"0.0068","lowestAsk":0,"highestBid":0,"percentChange":"6.249999999999989","quoteVolume":"0.5550265","isFrozen":0,"baseVolume":0,"high24hr":"0.0068","low24hr":"0.0064"}, ... @@ -107,10 +107,11 @@ protected override async Task> OnGetSymbolsMetadataA var split = prop.Name.Split('_'); markets.Add(new ExchangeMarket() { - MarketName = prop.Name.ToStringInvariant(), + MarketSymbol = prop.Name.ToStringInvariant(), IsActive = prop.First["isFrozen"].ConvertInvariant() == 0, - BaseCurrency = split[0], - MarketCurrency = split[1] + //NOTE: they list the quote currency first which is unusual + QuoteCurrency = split[0], + BaseCurrency = split[1] }); } return markets; @@ -131,22 +132,22 @@ protected override async Task>> /// /// Uses TuxExchange getticker method and filters by symbol /// - /// + /// /// - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { var tickers = await OnGetTickersAsync(); - return tickers.Where(t => t.Key.Equals(symbol)).Select(t => t.Value).FirstOrDefault(); + return tickers.Where(t => t.Key.Equals(marketSymbol)).Select(t => t.Value).FirstOrDefault(); } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - var split = symbol.Split('_'); + var split = marketSymbol.Split('_'); JToken token = await MakeJsonRequestAsync("/api?method=getorders&coin=" + split[1] + "&market=" + split[0]); return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token); } - protected override async Task> OnGetRecentTradesAsync(string symbol) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol) { List trades = new List(); @@ -156,7 +157,7 @@ protected override async Task> OnGetRecentTradesAsync long end = (long)CryptoUtility.UtcNow.UnixTimestampFromDateTimeSeconds(); // All TuxExchange Market Symbols begin with "BTC_" as a base-currency. They only support getting Trades for the Market Currency Symbol, so we split it for the call - string cur = symbol.Split(SymbolSeparator[0])[1]; + string cur = marketSymbol.Split(MarketSymbolSeparator[0])[1]; // [{"tradeid":"3375","date":"2016-08-26 18:53:38","type":"buy","rate":"0.00000041","amount":"420.00000000","total":"0.00017220"}, ... ] // https://tuxexchange.com/api?method=gettradehistory&coin=DOGE&start=1472237476&end=1472237618 @@ -170,12 +171,12 @@ protected override async Task> OnGetRecentTradesAsync else return trades; } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { List trades = new List(); long start = startDate == null ? (long)CryptoUtility.UtcNow.AddDays(-1).UnixTimestampFromDateTimeSeconds() : new DateTimeOffset((DateTime)startDate).ToUnixTimeSeconds(); long end = (long)CryptoUtility.UtcNow.UnixTimestampFromDateTimeSeconds(); - string coin = symbol.Split(SymbolSeparator[0])[1]; + string coin = marketSymbol.Split(MarketSymbolSeparator[0])[1]; string url = "/api?method=gettradehistory&coin=" + coin + "&start=" + start + "&end=" + end; JToken token = await MakeJsonRequestAsync(url); foreach (JToken trade in token) @@ -189,13 +190,13 @@ protected override async Task OnGetHistoricalTradesAsync(Func - /// + /// /// /// /// /// /// - protected override Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { throw new NotImplementedException(); } @@ -244,10 +245,10 @@ protected override async Task> OnGetAmountsAvailable /// /// TODO: Exchange API Documentation is missing the return values of this call /// - /// + /// /// /// - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { List orders = new List(); @@ -265,9 +266,9 @@ protected override async Task> OnGetCompletedOr /// /// TODO: Exchange API Documentation is missing the return values of this call /// - /// + /// /// - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { List orders = new List(); @@ -279,7 +280,7 @@ protected override async Task> OnGetOpenOrderDe throw new NotImplementedException("API Interface Incomplete"); } - protected override Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { // see OnGetCompletedOrderDetailsAsync throw new NotImplementedException("API Interface Incomplete"); @@ -288,7 +289,7 @@ protected override Task OnGetOrderDetailsAsync(string order protected override async Task OnPlaceOrderAsync(ExchangeOrderRequest order) { - var split = order.Symbol.Split('_'); + var split = order.MarketSymbol.Split('_'); var payload = await GetNoncePayloadAsync(); payload.Add("method", order.IsBuy ? "buy" : "sell"); payload.Add("market", split[0]); @@ -314,7 +315,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd } // This should have a return value for success - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { var payload = await GetNoncePayloadAsync(); payload.Add("method", "cancelorder"); @@ -324,7 +325,7 @@ protected override async Task OnCancelOrderAsync(string orderId, string symbol = // nothing is returned on this call } - protected override async Task> OnGetDepositHistoryAsync(string symbol) + protected override async Task> OnGetDepositHistoryAsync(string currency) { List deposits = new List(); var payload = await GetNoncePayloadAsync(); @@ -335,9 +336,9 @@ protected override async Task> OnGetDepositHist JToken token = await MakeJsonRequestAsync("/api", null, payload, "POST"); foreach (JToken deposit in token.First) { - if (deposit["symbol"].ToStringInvariant().Equals(symbol)) deposits.Add(new ExchangeTransaction() + if (deposit["symbol"].ToStringInvariant().Equals(currency)) deposits.Add(new ExchangeTransaction() { - Symbol = symbol, + Currency = currency, Timestamp = deposit["date"].ToDateTimeInvariant(), Address = deposit["coin"].ToStringInvariant(), BlockchainTxId = deposit["txid"].ToStringInvariant(), @@ -349,19 +350,19 @@ protected override async Task> OnGetDepositHist } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { var payload = await GetNoncePayloadAsync(); payload.Add("method", "getmyaddresses"); // "addresses": { "BTC": "14iuWRBwB35HYG98vBxmVJoJZG73BZy4bZ", "LTC": "LXLWHFLpPbcKx69diMVEXVLAzSMXsyrQH2", "DOGE": "DGon17FjjTTVXaHeotm1gvw6ewUZ49WeZr", } JToken token = await MakeJsonRequestAsync("/api", null, payload, "POST"); - if (token != null && token.HasValues && token["addresses"][symbol] != null) + if (token != null && token.HasValues && token["addresses"][currency] != null) { return new ExchangeDepositDetails() { - Symbol = symbol, - Address = token["addresses"][symbol].ToStringInvariant() + Currency = currency, + Address = token["addresses"][currency].ToStringInvariant() }; } return null; diff --git a/ExchangeSharp/API/Exchanges/Yobit/ExchangeYobitAPI.cs b/ExchangeSharp/API/Exchanges/Yobit/ExchangeYobitAPI.cs index 08759a1c..a947e8d0 100644 --- a/ExchangeSharp/API/Exchanges/Yobit/ExchangeYobitAPI.cs +++ b/ExchangeSharp/API/Exchanges/Yobit/ExchangeYobitAPI.cs @@ -42,8 +42,8 @@ public ExchangeYobitAPI() // to add insult to injury you must always increment by exactly one from the last use of your API key, even when rebooting the computer and restarting your process NonceStyle = NonceStyle.Int32File; - SymbolSeparator = "_"; - SymbolIsUppercase = false; + MarketSymbolSeparator = "_"; + MarketSymbolIsUppercase = false; } #region ProcessRequest @@ -71,7 +71,7 @@ protected override Task> OnGetCurr throw new NotSupportedException("Yobit does not provide data about its currencies via the API"); } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); JToken token = await MakeJsonRequestAsync("/info", BaseUrl, null); @@ -79,7 +79,7 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task> OnGetSymbolsMetadataAsync() + protected override async Task> OnGetMarketSymbolsMetadataAsync() { List markets = new List(); // "pairs":{"ltc_btc":{"decimal_places":8,"min_price":0.00000001,"max_price":10000,"min_amount":0.0001,"hidden":0,"fee":0.2} ... } @@ -89,21 +89,22 @@ protected override async Task> OnGetSymbolsMetadataA var split = prop.Name.ToUpperInvariant().Split('_'); markets.Add(new ExchangeMarket() { - MarketName = prop.Name.ToStringInvariant(), - MarketCurrency = split[0], - BaseCurrency = split[1], + MarketSymbol = prop.Name.ToStringInvariant(), + BaseCurrency = split[0], + QuoteCurrency = split[1], IsActive = prop.First["hidden"].ConvertInvariant().Equals(0), MaxPrice = prop.First["max_price"].ConvertInvariant(), MinPrice = prop.First["min_price"].ConvertInvariant(), - MinTradeSize = prop.First["min_amount"].ConvertInvariant() + MinTradeSize = prop.First["min_amount"].ConvertInvariant(), + PriceStepSize = Math.Pow(.1, prop.First["decimal_places"].ConvertInvariant()).ConvertInvariant() }); } return markets; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - JToken token = await MakeJsonRequestAsync("/ticker/" + NormalizeSymbol(symbol), null, null, "POST"); + JToken token = await MakeJsonRequestAsync("/ticker/" + NormalizeMarketSymbol(marketSymbol), null, null, "POST"); if (token != null && token.HasValues) return ParseTicker(token.First as JProperty); return null; } @@ -120,25 +121,25 @@ protected override async Task>> return await base.OnGetTickersAsync(); } - protected override async Task OnGetOrderBookAsync(string symbol, int maxCount = 100) + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - JToken token = await MakeJsonRequestAsync("/depth/" + symbol + "?limit=" + maxCount, BaseUrl, null); - return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token[symbol]); + JToken token = await MakeJsonRequestAsync("/depth/" + marketSymbol + "?limit=" + maxCount, BaseUrl, null); + return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(token[marketSymbol]); } - protected override async Task> OnGetRecentTradesAsync(string symbol) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol) { List trades = new List(); - JToken token = await MakeJsonRequestAsync("/trades/" + symbol + "?limit=10", null, null, "POST"); // default is 150, max: 2000, let's do another arbitrary 10 for consistency + JToken token = await MakeJsonRequestAsync("/trades/" + marketSymbol + "?limit=10", null, null, "POST"); // default is 150, max: 2000, let's do another arbitrary 10 for consistency foreach (JToken prop in token.First.First) trades.Add(ParseTrade(prop)); return trades; } - protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + protected override async Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { List trades = new List(); // Not directly supported, but we'll return the max and filter if necessary - JToken token = await MakeJsonRequestAsync("/trades/" + symbol + "?limit=2000", null, null, "POST"); + JToken token = await MakeJsonRequestAsync("/trades/" + marketSymbol + "?limit=2000", null, null, "POST"); token = token.First.First; // bunch of nested foreach (JToken prop in token) { @@ -153,13 +154,13 @@ protected override async Task OnGetHistoricalTradesAsync(Func - /// + /// /// /// /// /// /// - protected override Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + protected override Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { throw new NotImplementedException(); } @@ -204,7 +205,7 @@ protected override async Task> OnGetAmountsAvailable return amounts; } - protected override async Task OnGetOrderDetailsAsync(string orderId, string symbol = null) + protected override async Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { var payload = await GetNoncePayloadAsync(); payload.Add("method", "getInfo"); @@ -217,32 +218,32 @@ protected override async Task OnGetOrderDetailsAsync(string /// /// Warning: Yobit will not return transactions over a week old via their api /// - /// + /// /// /// - protected override async Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + protected override async Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { - if (symbol == null) { throw new APIException("symbol cannot be null"); } // Seriously, they want you to loop through over 7500 symbol pairs to find your trades! Geez... + if (marketSymbol == null) { throw new APIException("market symbol cannot be null"); } // Seriously, they want you to loop through over 7500 symbol pairs to find your trades! Geez... List orders = new List(); var payload = await GetNoncePayloadAsync(); payload.Add("method", "TradeHistory"); - payload.Add("pair", symbol); + payload.Add("pair", marketSymbol); if (afterDate != null) payload.Add("since", new DateTimeOffset((DateTime)afterDate).ToUnixTimeSeconds()); JToken token = await MakeJsonRequestAsync("/", PrivateURL, payload, "POST"); if (token != null) foreach (JProperty prop in token) orders.Add(ParseOrder(prop)); return orders; } - protected override async Task> OnGetOpenOrderDetailsAsync(string symbol = null) + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { - if (symbol == null) { throw new APIException("symbol cannot be null"); } // Seriously, they want you to loop through over 7500 symbol pairs to find your trades! Geez... + if (marketSymbol == null) { throw new APIException("market symbol cannot be null"); } // Seriously, they want you to loop through over 7500 symbol pairs to find your trades! Geez... List orders = new List(); var payload = await GetNoncePayloadAsync(); payload.Add("method", "ActiveOrders"); - payload.Add("pair", symbol); + payload.Add("pair", marketSymbol); JToken token = await MakeJsonRequestAsync("/", PrivateURL, payload, "POST"); if (token != null) foreach (JProperty prop in token) orders.Add(ParseOrder(prop)); foreach (JProperty prop in token) orders.Add(ParseOrder(prop)); @@ -253,7 +254,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd { var payload = await GetNoncePayloadAsync(); payload.Add("method", "Trade"); - payload.Add("pair", order.Symbol); + payload.Add("pair", order.MarketSymbol); payload.Add("type", order.IsBuy ? "buy" : "sell"); payload.Add("rate", order.Price); payload.Add("amount", order.Amount); @@ -276,7 +277,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd return result; } - protected override async Task OnCancelOrderAsync(string orderId, string symbol = null) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { var payload = await GetNoncePayloadAsync(); payload.Add("method", "CancelOrder"); @@ -284,23 +285,23 @@ protected override async Task OnCancelOrderAsync(string orderId, string symbol = await MakeJsonRequestAsync("/", PrivateURL, payload, "POST"); } - protected override Task> OnGetDepositHistoryAsync(string symbol) + protected override Task> OnGetDepositHistoryAsync(string currency) { throw new NotImplementedException("Yobit does not provide a deposit history via the API"); // I don't wonder why } - protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) + protected override async Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) { var payload = await GetNoncePayloadAsync(); payload.Add("need_new", forceRegenerate ? 1 : 0); payload.Add("method", "GetDepositAddress"); - payload.Add("coinName", symbol); + payload.Add("coinName", currency); // "return":{"address": 1UHAnAWvxDB9XXETsi7z483zRRBmcUZxb3,"processed_amount": 1.00000000,"server_time": 1437146228 } JToken token = await MakeJsonRequestAsync("/", PrivateURL, payload, "POST"); return new ExchangeDepositDetails() { Address = token["address"].ToStringInvariant(), - Symbol = symbol + Currency = currency }; } @@ -333,8 +334,8 @@ protected override async Task OnWithdrawAsync(Exchan private ExchangeTicker ParseTicker(JProperty prop) { // "ltc_btc":{ "high":105.41,"low":104.67,"avg":105.04,"vol":43398.22251455,"vol_cur":4546.26962359,"last":105.11,"buy":104.2,"sell":105.11,"updated":1418654531 } - string symbol = prop.Name.ToUpperInvariant(); - return this.ParseTicker(prop.First, symbol, "sell", "buy", "last", "vol", "vol_cur", "updated", TimestampType.UnixSeconds); + string marketSymbol = prop.Name.ToUpperInvariant(); + return this.ParseTicker(prop.First, marketSymbol, "sell", "buy", "last", "vol", "vol_cur", "updated", TimestampType.UnixSeconds); } private ExchangeTrade ParseTrade(JToken prop) @@ -350,7 +351,7 @@ private ExchangeOrderResult ParseOrder(JProperty prop) ExchangeOrderResult result = new ExchangeOrderResult() { OrderId = prop.Name, - Symbol = prop["pair"].ToStringInvariant(), + MarketSymbol = prop["pair"].ToStringInvariant(), Amount = prop["start_amount"].ConvertInvariant(), AmountFilled = prop["amount"].ConvertInvariant(), Price = prop["rate"].ConvertInvariant(), diff --git a/ExchangeSharp/API/Exchanges/ZBcom/ExchangeZBcomAPI.cs b/ExchangeSharp/API/Exchanges/ZBcom/ExchangeZBcomAPI.cs index 2476bafe..c5e8eb40 100644 --- a/ExchangeSharp/API/Exchanges/ZBcom/ExchangeZBcomAPI.cs +++ b/ExchangeSharp/API/Exchanges/ZBcom/ExchangeZBcomAPI.cs @@ -12,6 +12,7 @@ The above copyright notice and this permission notice shall be included in all c using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json.Linq; @@ -25,8 +26,8 @@ public sealed partial class ExchangeZBcomAPI : ExchangeAPI public ExchangeZBcomAPI() { - SymbolSeparator = "_"; - SymbolIsUppercase = false; + MarketSymbolSeparator = "_"; + MarketSymbolIsUppercase = false; } private string NormalizeSymbolWebsocket(string symbol) @@ -38,10 +39,10 @@ private string NormalizeSymbolWebsocket(string symbol) #region publicAPI - private async Task> MakeRequestZBcomAsync(string symbol, string subUrl, string baseUrl = null) + private async Task> MakeRequestZBcomAsync(string marketSymbol, string subUrl, string baseUrl = null) { - JToken obj = await MakeJsonRequestAsync(subUrl.Replace("$SYMBOL$", symbol ?? string.Empty), baseUrl); - return new Tuple(obj, symbol); + JToken obj = await MakeJsonRequestAsync(subUrl.Replace("$SYMBOL$", marketSymbol ?? string.Empty), baseUrl); + return new Tuple(obj, marketSymbol); } private ExchangeTicker ParseTicker(string symbol, JToken data) @@ -56,7 +57,7 @@ private ExchangeTicker ParseTickerV2(string symbol, JToken data) return this.ParseTicker(data.First, symbol, "sell", "buy", "last", "vol"); } - protected override async Task> OnGetSymbolsAsync() + protected override async Task> OnGetMarketSymbolsAsync() { var data = await MakeRequestZBcomAsync(string.Empty, "/markets"); List symbols = new List(); @@ -67,9 +68,9 @@ protected override async Task> OnGetSymbolsAsync() return symbols; } - protected override async Task OnGetTickerAsync(string symbol) + protected override async Task OnGetTickerAsync(string marketSymbol) { - var data = await MakeRequestZBcomAsync(symbol, "/ticker?market=$SYMBOL$"); + var data = await MakeRequestZBcomAsync(marketSymbol, "/ticker?market=$SYMBOL$"); return ParseTicker(data.Item2, data.Item1); } @@ -79,16 +80,18 @@ protected override async Task>> var data = await MakeRequestZBcomAsync(null, "/allTicker", BaseUrl); List> tickers = new List>(); - string symbol; + var symbols = (await GetMarketSymbolsAsync()).ToArray(); + string marketSymbol; foreach (JToken token in data.Item1) { - symbol = token.Path; - tickers.Add(new KeyValuePair(symbol, ParseTickerV2(symbol, token))); + //for some reason when returning tickers, the api doesn't include the symbol separator like it does everywhere else so we need to convert it to the correct format + marketSymbol = symbols.First(s => s.Replace(MarketSymbolSeparator, string.Empty).Equals(token.Path)); + tickers.Add(new KeyValuePair(marketSymbol, ParseTickerV2(marketSymbol, token))); } return tickers; } - protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] symbols) + protected override IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) { return ConnectWebSocket(string.Empty, (_socket, msg) => { @@ -97,21 +100,21 @@ protected override IWebSocket OnGetTradesWebSocket(Action(symbol, trade)); + callback(new KeyValuePair(marketSymbol, trade)); } } return Task.CompletedTask; }, async (_socket) => { - foreach (var symbol in symbols) + foreach (var marketSymbol in marketSymbols) { - string normalizedSymbol = NormalizeSymbolWebsocket(symbol); + string normalizedSymbol = NormalizeSymbolWebsocket(marketSymbol); await _socket.SendMessageAsync(new { @event = "addChannel", channel = normalizedSymbol + "_trades" }); } }); diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs index ec1d0739..76f5075a 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs @@ -13,6 +13,7 @@ The above copyright notice and this permission notice shall be included in all c using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -26,7 +27,7 @@ public abstract partial class ExchangeAPI : BaseAPI, IExchangeAPI /// /// Separator for global symbols /// - public const char GlobalSymbolSeparator = '-'; + public const char GlobalMarketSymbolSeparator = '-'; /// /// Whether to use the default method cache policy, default is true. @@ -46,11 +47,11 @@ public abstract partial class ExchangeAPI : BaseAPI, IExchangeAPI protected virtual async Task>> OnGetTickersAsync() { List> tickers = new List>(); - var symbols = await GetSymbolsAsync(); - foreach (string symbol in symbols) + var marketSymbols = await GetMarketSymbolsAsync(); + foreach (string marketSymbol in marketSymbols) { - var ticker = await GetTickerAsync(symbol); - tickers.Add(new KeyValuePair(symbol, ticker)); + var ticker = await GetTickerAsync(marketSymbol); + tickers.Add(new KeyValuePair(marketSymbol, ticker)); } return tickers; } @@ -58,53 +59,53 @@ protected virtual async Task>> protected virtual async Task>> OnGetOrderBooksAsync(int maxCount = 100) { List> books = new List>(); - var symbols = await GetSymbolsAsync(); - foreach (string symbol in symbols) + var marketSymbols = await GetMarketSymbolsAsync(); + foreach (string marketSymbol in marketSymbols) { - var book = await GetOrderBookAsync(symbol); - books.Add(new KeyValuePair(symbol, book)); + var book = await GetOrderBookAsync(marketSymbol); + books.Add(new KeyValuePair(marketSymbol, book)); } return books; } - protected virtual async Task> OnGetRecentTradesAsync(string symbol) + protected virtual async Task> OnGetRecentTradesAsync(string marketSymbol) { - symbol = NormalizeSymbol(symbol); + marketSymbol = NormalizeMarketSymbol(marketSymbol); List trades = new List(); await GetHistoricalTradesAsync((e) => { trades.AddRange(e); return true; - }, symbol); + }, marketSymbol); return trades; } protected virtual Task> OnGetCurrenciesAsync() => throw new NotImplementedException(); - protected virtual Task> OnGetSymbolsAsync() => throw new NotImplementedException(); - protected virtual Task> OnGetSymbolsMetadataAsync() => throw new NotImplementedException(); - protected virtual Task OnGetTickerAsync(string symbol) => throw new NotImplementedException(); - protected virtual Task OnGetOrderBookAsync(string symbol, int maxCount = 100) => throw new NotImplementedException(); - protected virtual Task OnGetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) => throw new NotImplementedException(); - protected virtual Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) => throw new NotImplementedException(); - protected virtual Task> OnGetDepositHistoryAsync(string symbol) => throw new NotImplementedException(); - protected virtual Task> OnGetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) => throw new NotImplementedException(); + protected virtual Task> OnGetMarketSymbolsAsync() => throw new NotImplementedException(); + protected virtual Task> OnGetMarketSymbolsMetadataAsync() => throw new NotImplementedException(); + protected virtual Task OnGetTickerAsync(string marketSymbol) => throw new NotImplementedException(); + protected virtual Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) => throw new NotImplementedException(); + protected virtual Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) => throw new NotImplementedException(); + protected virtual Task OnGetDepositAddressAsync(string currency, bool forceRegenerate = false) => throw new NotImplementedException(); + protected virtual Task> OnGetDepositHistoryAsync(string currency) => throw new NotImplementedException(); + protected virtual Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) => throw new NotImplementedException(); protected virtual Task> OnGetAmountsAsync() => throw new NotImplementedException(); protected virtual Task> OnGetFeesAsync() => throw new NotImplementedException(); protected virtual Task> OnGetAmountsAvailableToTradeAsync() => throw new NotImplementedException(); protected virtual Task OnPlaceOrderAsync(ExchangeOrderRequest order) => throw new NotImplementedException(); protected virtual Task OnPlaceOrdersAsync(params ExchangeOrderRequest[] order) => throw new NotImplementedException(); - protected virtual Task OnGetOrderDetailsAsync(string orderId, string symbol = null) => throw new NotImplementedException(); - protected virtual Task> OnGetOpenOrderDetailsAsync(string symbol = null) => throw new NotImplementedException(); - protected virtual Task> OnGetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) => throw new NotImplementedException(); - protected virtual Task OnCancelOrderAsync(string orderId, string symbol = null) => throw new NotImplementedException(); + protected virtual Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) => throw new NotImplementedException(); + protected virtual Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) => throw new NotImplementedException(); + protected virtual Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) => throw new NotImplementedException(); + protected virtual Task OnCancelOrderAsync(string orderId, string marketSymbol = null) => throw new NotImplementedException(); protected virtual Task OnWithdrawAsync(ExchangeWithdrawalRequest withdrawalRequest) => throw new NotImplementedException(); protected virtual Task> OnGetMarginAmountsAvailableToTradeAsync(bool includeZeroBalances) => throw new NotImplementedException(); - protected virtual Task OnGetOpenPositionAsync(string symbol) => throw new NotImplementedException(); - protected virtual Task OnCloseMarginPositionAsync(string symbol) => throw new NotImplementedException(); + protected virtual Task OnGetOpenPositionAsync(string marketSymbol) => throw new NotImplementedException(); + protected virtual Task OnCloseMarginPositionAsync(string marketSymbol) => throw new NotImplementedException(); - protected virtual IWebSocket OnGetTickersWebSocket(Action>> tickers, params string[] symbols) => throw new NotImplementedException(); - protected virtual IWebSocket OnGetTradesWebSocket(Action> callback, params string[] symbols) => throw new NotImplementedException(); - protected virtual IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols) => throw new NotImplementedException(); + protected virtual IWebSocket OnGetTickersWebSocket(Action>> tickers, params string[] marketSymbols) => throw new NotImplementedException(); + protected virtual IWebSocket OnGetTradesWebSocket(Action> callback, params string[] marketSymbols) => throw new NotImplementedException(); + protected virtual IWebSocket OnGetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) => throw new NotImplementedException(); protected virtual IWebSocket OnGetOrderDetailsWebSocket(Action callback) => throw new NotImplementedException(); protected virtual IWebSocket OnGetCompletedOrderDetailsWebSocket(Action callback) => throw new NotImplementedException(); @@ -115,24 +116,24 @@ await GetHistoricalTradesAsync((e) => /// /// Clamp price using market info. If necessary, a network request will be made to retrieve symbol metadata. /// - /// Symbol + /// Market Symbol /// Price /// Clamped price - protected async Task ClampOrderPrice(string symbol, decimal outputPrice) + protected async Task ClampOrderPrice(string marketSymbol, decimal outputPrice) { - ExchangeMarket market = await GetExchangeMarketFromCacheAsync(symbol); + ExchangeMarket market = await GetExchangeMarketFromCacheAsync(marketSymbol); return market == null ? outputPrice : CryptoUtility.ClampDecimal(market.MinPrice, market.MaxPrice, market.PriceStepSize, outputPrice); } /// /// Clamp quantiy using market info. If necessary, a network request will be made to retrieve symbol metadata. /// - /// Symbol + /// Market Symbol /// Quantity /// Clamped quantity - protected async Task ClampOrderQuantity(string symbol, decimal outputQuantity) + protected async Task ClampOrderQuantity(string marketSymbol, decimal outputQuantity) { - ExchangeMarket market = await GetExchangeMarketFromCacheAsync(symbol); + ExchangeMarket market = await GetExchangeMarketFromCacheAsync(marketSymbol); return market == null ? outputQuantity : CryptoUtility.ClampDecimal(market.MinTradeSize, market.MaxTradeSize, market.QuantityStepSize, outputQuantity); } @@ -143,21 +144,21 @@ protected async Task ClampOrderQuantity(string symbol, decimal outputQu /// second (i.e. ETH). Example BTC-ETH, read as x BTC is worth y ETH. /// BTC is always first, then ETH, etc. Fiat pair is always first in global symbol too. /// - /// Exchange symbol + /// Exchange market symbol /// Separator /// Global symbol - protected string ExchangeSymbolToGlobalSymbolWithSeparator(string symbol, char separator = GlobalSymbolSeparator) + protected string ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(string marketSymbol, char separator = GlobalMarketSymbolSeparator) { - if (string.IsNullOrEmpty(symbol)) + if (string.IsNullOrEmpty(marketSymbol)) { throw new ArgumentException("Symbol must be non null and non empty"); } - string[] pieces = symbol.Split(separator); - if (SymbolIsReversed) + string[] pieces = marketSymbol.Split(separator); + if (MarketSymbolIsReversed) { - return ExchangeCurrencyToGlobalCurrency(pieces[0]).ToUpperInvariant() + GlobalSymbolSeparator + ExchangeCurrencyToGlobalCurrency(pieces[1]).ToUpperInvariant(); + return ExchangeCurrencyToGlobalCurrency(pieces[0]).ToUpperInvariant() + GlobalMarketSymbolSeparator + ExchangeCurrencyToGlobalCurrency(pieces[1]).ToUpperInvariant(); } - return ExchangeCurrencyToGlobalCurrency(pieces[1]).ToUpperInvariant() + GlobalSymbolSeparator + ExchangeCurrencyToGlobalCurrency(pieces[0]).ToUpperInvariant(); + return ExchangeCurrencyToGlobalCurrency(pieces[1]).ToUpperInvariant() + GlobalMarketSymbolSeparator + ExchangeCurrencyToGlobalCurrency(pieces[0]).ToUpperInvariant(); } /// @@ -195,8 +196,9 @@ public ExchangeAPI() { if (UseDefaultMethodCachePolicy) { - MethodCachePolicy.Add(nameof(GetSymbolsAsync), TimeSpan.FromHours(1.0)); - MethodCachePolicy.Add(nameof(GetSymbolsMetadataAsync), TimeSpan.FromHours(1.0)); + MethodCachePolicy.Add(nameof(GetCurrenciesAsync), TimeSpan.FromHours(1.0)); + MethodCachePolicy.Add(nameof(GetMarketSymbolsAsync), TimeSpan.FromHours(1.0)); + MethodCachePolicy.Add(nameof(GetMarketSymbolsMetadataAsync), TimeSpan.FromHours(1.0)); MethodCachePolicy.Add(nameof(GetTickerAsync), TimeSpan.FromSeconds(10.0)); MethodCachePolicy.Add(nameof(GetTickersAsync), TimeSpan.FromSeconds(10.0)); MethodCachePolicy.Add(nameof(GetOrderBookAsync), TimeSpan.FromSeconds(10.0)); @@ -335,28 +337,28 @@ public string GlobalCurrencyToExchangeCurrency(string currency) { currency = currency.Replace(kv.Value, kv.Key); } - return (SymbolIsUppercase ? currency.ToUpperInvariant() : currency.ToLowerInvariant()); + return (MarketSymbolIsUppercase ? currency.ToUpperInvariant() : currency.ToLowerInvariant()); } /// /// Normalize an exchange specific symbol. The symbol should already be in the correct order, /// this method just deals with casing and putting in the right separator. /// - /// Symbol + /// Symbol /// Normalized symbol - public virtual string NormalizeSymbol(string symbol) - { - symbol = (symbol ?? string.Empty).Trim(); - symbol = symbol.Replace("-", SymbolSeparator) - .Replace("/", SymbolSeparator) - .Replace("_", SymbolSeparator) - .Replace(" ", SymbolSeparator) - .Replace(":", SymbolSeparator); - if (SymbolIsUppercase) + public virtual string NormalizeMarketSymbol(string marketSymbol) + { + marketSymbol = (marketSymbol ?? string.Empty).Trim(); + marketSymbol = marketSymbol.Replace("-", MarketSymbolSeparator) + .Replace("/", MarketSymbolSeparator) + .Replace("_", MarketSymbolSeparator) + .Replace(" ", MarketSymbolSeparator) + .Replace(":", MarketSymbolSeparator); + if (MarketSymbolIsUppercase) { - return symbol.ToUpperInvariant(); + return marketSymbol.ToUpperInvariant(); } - return symbol.ToLowerInvariant(); + return marketSymbol.ToLowerInvariant(); } /// @@ -366,42 +368,107 @@ public virtual string NormalizeSymbol(string symbol) /// second (i.e. ETH). Example BTC-ETH, read as x BTC is worth y ETH. /// BTC is always first, then ETH, etc. Fiat pair is always first in global symbol too. /// - /// Exchange symbol + /// Exchange symbol /// Global symbol - public virtual string ExchangeSymbolToGlobalSymbol(string symbol) + public virtual string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) { - if (string.IsNullOrWhiteSpace(SymbolSeparator)) + if (string.IsNullOrWhiteSpace(MarketSymbolSeparator)) { - if (symbol.Length != 6) + if (marketSymbol.Length != 6) { - throw new InvalidOperationException(Name + " symbol must be 6 chars: '" + symbol + "' is not. Override this method to handle symbols that are not 6 chars in length."); + throw new InvalidOperationException(Name + " market symbol must be 6 chars: '" + marketSymbol + "' is not. Override this method to handle symbols that are not 6 chars in length."); } - return ExchangeSymbolToGlobalSymbolWithSeparator(symbol.Substring(0, symbol.Length - 3) + GlobalSymbolSeparator + (symbol.Substring(symbol.Length - 3, 3)), GlobalSymbolSeparator); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(marketSymbol.Substring(0, marketSymbol.Length - 3) + GlobalMarketSymbolSeparator + (marketSymbol.Substring(marketSymbol.Length - 3, 3)), GlobalMarketSymbolSeparator); } - return ExchangeSymbolToGlobalSymbolWithSeparator(symbol, SymbolSeparator[0]); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(marketSymbol, MarketSymbolSeparator[0]); + } + + public virtual string CurrenciesToExchangeMarketSymbol(string baseCurrency, string quoteCurrency) + { + var symbol = MarketSymbolIsReversed + ? $"{quoteCurrency}{MarketSymbolSeparator}{baseCurrency}" + : $"{baseCurrency}{MarketSymbolSeparator}{quoteCurrency}"; + + return MarketSymbolIsUppercase + ? symbol.ToUpperInvariant() + : symbol; + } + + /// + /// NOTE: This method can potentially make a call to GetSymbolsMetadataAsync + /// + /// + /// + public virtual (string BaseCurrency, string QuoteCurrency) ExchangeMarketSymbolToCurrencies(string marketSymbol) + { + string baseCurrency, quoteCurrency; + if (string.IsNullOrWhiteSpace(MarketSymbolSeparator)) + { + if (marketSymbol.Length != 6) + { + var errorMessage = Name + " symbol must be 6 chars: '" + marketSymbol + "' is not. Override this method to handle symbols that are not 6 chars in length."; + try + { + //let's try looking this up by the metadata.. + var symbols = GetMarketSymbolsMetadataAsync().Sync().ToArray();//.ToDictionary(market => market.MarketName, market => market); + var marketSymbolMetadata = symbols.First( + market => market.MarketSymbol.Equals(marketSymbol, StringComparison.InvariantCultureIgnoreCase) || CurrenciesToExchangeMarketSymbol(market.BaseCurrency, market.QuoteCurrency) + .Equals(marketSymbol, StringComparison.InvariantCultureIgnoreCase) + ); + + return (marketSymbolMetadata.BaseCurrency, marketSymbolMetadata.QuoteCurrency); + } + catch (Exception e) + { + throw new InvalidOperationException(errorMessage, e); + } + throw new InvalidOperationException(errorMessage); + } + + if (MarketSymbolIsReversed) + { + quoteCurrency = marketSymbol.Substring(0, marketSymbol.Length - 3); + baseCurrency = marketSymbol.Substring(marketSymbol.Length - 3, 3); + } + else + { + baseCurrency = marketSymbol.Substring(0, marketSymbol.Length - 3); + quoteCurrency = marketSymbol.Substring(marketSymbol.Length - 3, 3); + } + } + else + { + var pieces = marketSymbol.Split(MarketSymbolSeparator[0]); + if (pieces.Length != 2) + throw new InvalidOperationException($"Splitting {Name} symbol '{marketSymbol}' with symbol separator '{MarketSymbolSeparator}' must result in exactly 2 pieces."); + quoteCurrency = MarketSymbolIsReversed ? pieces[0] : pieces[1]; + baseCurrency = MarketSymbolIsReversed ? pieces[1] : pieces[0]; + } + + return (baseCurrency, quoteCurrency); } /// /// Convert a global symbol into an exchange symbol, which will potentially be different from other exchanges. /// - /// Global symbol - /// Exchange symbol - public virtual string GlobalSymbolToExchangeSymbol(string symbol) + /// Global market symbol + /// Exchange market symbol + public virtual string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol) { - if (string.IsNullOrWhiteSpace(symbol)) + if (string.IsNullOrWhiteSpace(marketSymbol)) { - throw new ArgumentException("Symbol must be non null and non empty"); + throw new ArgumentException("Market symbol must be non null and non empty"); } - int pos = symbol.IndexOf(GlobalSymbolSeparator); - if (SymbolIsReversed) + int pos = marketSymbol.IndexOf(GlobalMarketSymbolSeparator); + if (MarketSymbolIsReversed) { - symbol = GlobalCurrencyToExchangeCurrency(symbol.Substring(0, pos)) + SymbolSeparator + GlobalCurrencyToExchangeCurrency(symbol.Substring(pos + 1)); + marketSymbol = GlobalCurrencyToExchangeCurrency(marketSymbol.Substring(0, pos)) + MarketSymbolSeparator + GlobalCurrencyToExchangeCurrency(marketSymbol.Substring(pos + 1)); } else { - symbol = GlobalCurrencyToExchangeCurrency(symbol.Substring(pos + 1)) + SymbolSeparator + GlobalCurrencyToExchangeCurrency(symbol.Substring(0, pos)); + marketSymbol = GlobalCurrencyToExchangeCurrency(marketSymbol.Substring(pos + 1)) + MarketSymbolSeparator + GlobalCurrencyToExchangeCurrency(marketSymbol.Substring(0, pos)); } - return (SymbolIsUppercase ? symbol.ToUpperInvariant() : symbol.ToLowerInvariant()); + return (MarketSymbolIsUppercase ? marketSymbol.ToUpperInvariant() : marketSymbol.ToLowerInvariant()); } /// @@ -428,27 +495,27 @@ public virtual async Task> GetCurr /// Get exchange symbols /// /// Array of symbols - public virtual async Task> GetSymbolsAsync() + public virtual async Task> GetMarketSymbolsAsync() { - return await Cache.CacheMethod(MethodCachePolicy, async () => (await OnGetSymbolsAsync()).ToArray(), nameof(GetSymbolsAsync)); + return await Cache.CacheMethod(MethodCachePolicy, async () => (await OnGetMarketSymbolsAsync()).ToArray(), nameof(GetMarketSymbolsAsync)); } /// /// Get exchange symbols including available metadata such as min trade size and whether the market is active /// /// Collection of ExchangeMarkets - public virtual async Task> GetSymbolsMetadataAsync() + public virtual async Task> GetMarketSymbolsMetadataAsync() { - return await Cache.CacheMethod(MethodCachePolicy, async () => (await OnGetSymbolsMetadataAsync()).ToArray(), nameof(GetSymbolsMetadataAsync)); + return await Cache.CacheMethod(MethodCachePolicy, async () => (await OnGetMarketSymbolsMetadataAsync()).ToArray(), nameof(GetMarketSymbolsMetadataAsync)); } /// /// Gets the exchange market from this exchange's SymbolsMetadata cache. This will make a network request if needed to retrieve fresh markets from the exchange using GetSymbolsMetadataAsync(). /// Please note that sending a symbol that is not found over and over will result in many network requests. Only send symbols that you are confident exist on the exchange. /// - /// The symbol. Ex. ADA/BTC. This is assumed to be normalized and already correct for the exchange. + /// The market symbol. Ex. ADA/BTC. This is assumed to be normalized and already correct for the exchange. /// The ExchangeMarket or null if it doesn't exist in the cache or there was an error - public virtual async Task GetExchangeMarketFromCacheAsync(string symbol) + public virtual async Task GetExchangeMarketFromCacheAsync(string marketSymbol) { try { @@ -456,17 +523,17 @@ public virtual async Task GetExchangeMarketFromCacheAsync(string // not sure if this is needed, but adding it just in case await new SynchronizationContextRemover(); - ExchangeMarket[] markets = (await GetSymbolsMetadataAsync()).ToArray(); - ExchangeMarket market = markets.FirstOrDefault(m => m.MarketName == symbol); + ExchangeMarket[] markets = (await GetMarketSymbolsMetadataAsync()).ToArray(); + ExchangeMarket market = markets.FirstOrDefault(m => m.MarketSymbol == marketSymbol); if (market == null) { // try again with a fresh request, every symbol *should* be in the response from PopulateExchangeMarketsAsync - Cache.Remove(nameof(GetSymbolsMetadataAsync)); + Cache.Remove(nameof(GetMarketSymbolsMetadataAsync)); - markets = (await GetSymbolsMetadataAsync()).ToArray(); + markets = (await GetMarketSymbolsMetadataAsync()).ToArray(); // try and find the market again, this time if not found we give up and just return null - market = markets.FirstOrDefault(m => m.MarketName == symbol); + market = markets.FirstOrDefault(m => m.MarketSymbol == marketSymbol); } return market; } @@ -480,12 +547,12 @@ public virtual async Task GetExchangeMarketFromCacheAsync(string /// /// Get exchange ticker /// - /// Symbol to get ticker for + /// Symbol to get ticker for /// Ticker - public virtual async Task GetTickerAsync(string symbol) + public virtual async Task GetTickerAsync(string marketSymbol) { - NormalizeSymbol(symbol); - return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetTickerAsync(symbol), nameof(GetTickerAsync), nameof(symbol), symbol); + NormalizeMarketSymbol(marketSymbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetTickerAsync(marketSymbol), nameof(GetTickerAsync), nameof(marketSymbol), marketSymbol); } /// @@ -500,13 +567,13 @@ public virtual async Task>> Get /// /// Get exchange order book /// - /// Symbol to get order book for + /// Symbol to get order book for /// Max count, not all exchanges will honor this parameter /// Exchange order book or null if failure - public virtual async Task GetOrderBookAsync(string symbol, int maxCount = 100) + public virtual async Task GetOrderBookAsync(string marketSymbol, int maxCount = 100) { - symbol = NormalizeSymbol(symbol); - return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOrderBookAsync(symbol, maxCount), nameof(GetOrderBookAsync), nameof(symbol), symbol, nameof(maxCount), maxCount); + marketSymbol = NormalizeMarketSymbol(marketSymbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOrderBookAsync(marketSymbol, maxCount), nameof(GetOrderBookAsync), nameof(marketSymbol), marketSymbol, nameof(maxCount), maxCount); } /// @@ -523,44 +590,43 @@ public virtual async Task>> /// Get historical trades for the exchange. TODO: Change to async enumerator when available. /// /// Callback for each set of trades. Return false to stop getting trades immediately. - /// Symbol to get historical data for + /// Symbol to get historical data for /// Optional UTC start date time to start getting the historical data at, null for the most recent data. Not all exchanges support this. /// Optional UTC end date time to start getting the historical data at, null for the most recent data. Not all exchanges support this. - public virtual async Task GetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null) + public virtual async Task GetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null) { // *NOTE*: Do not wrap in CacheMethodCall, uses a callback with custom queries, not easy to cache await new SynchronizationContextRemover(); - await OnGetHistoricalTradesAsync(callback, NormalizeSymbol(symbol), startDate, endDate); + await OnGetHistoricalTradesAsync(callback, NormalizeMarketSymbol(marketSymbol), startDate, endDate); } /// /// Get recent trades on the exchange - the default implementation simply calls GetHistoricalTrades with a null sinceDateTime. /// - /// Symbol to get recent trades for + /// Symbol to get recent trades for /// An enumerator that loops through all recent trades - public virtual async Task> GetRecentTradesAsync(string symbol) + public virtual async Task> GetRecentTradesAsync(string marketSymbol) { - symbol = NormalizeSymbol(symbol); - return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetRecentTradesAsync(symbol), nameof(GetRecentTradesAsync), nameof(symbol), symbol); + marketSymbol = NormalizeMarketSymbol(marketSymbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetRecentTradesAsync(marketSymbol), nameof(GetRecentTradesAsync), nameof(marketSymbol), marketSymbol); } /// /// Gets the address to deposit to and applicable details. /// - /// Symbol to get address for. + /// Currency to get address for. /// Regenerate the address /// Deposit address details (including tag if applicable, such as XRP) - public virtual async Task GetDepositAddressAsync(string symbol, bool forceRegenerate = false) + public virtual async Task GetDepositAddressAsync(string currency, bool forceRegenerate = false) { - symbol = NormalizeSymbol(symbol); if (forceRegenerate) { // force regenetate, do not cache - return await OnGetDepositAddressAsync(symbol, forceRegenerate); + return await OnGetDepositAddressAsync(currency, forceRegenerate); } else { - return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetDepositAddressAsync(symbol, forceRegenerate), nameof(GetDepositAddressAsync), nameof(symbol), symbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetDepositAddressAsync(currency, forceRegenerate), nameof(GetDepositAddressAsync), nameof(currency), currency); } } @@ -568,26 +634,25 @@ public virtual async Task GetDepositAddressAsync(string /// Gets the deposit history for a symbol /// /// Collection of ExchangeCoinTransfers - public virtual async Task> GetDepositHistoryAsync(string symbol) + public virtual async Task> GetDepositHistoryAsync(string currency) { - symbol = NormalizeSymbol(symbol); - return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetDepositHistoryAsync(symbol), nameof(GetDepositHistoryAsync), nameof(symbol), symbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetDepositHistoryAsync(currency), nameof(GetDepositHistoryAsync), nameof(currency), currency); } /// /// Get candles (open, high, low, close) /// - /// Symbol to get candles for + /// Symbol to get candles for /// Period in seconds to get candles for. Use 60 for minute, 3600 for hour, 3600*24 for day, 3600*24*30 for month. /// Optional start date to get candles for /// Optional end date to get candles for /// Max results, can be used instead of startDate and endDate if desired /// Candles - public virtual async Task> GetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + public virtual async Task> GetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { - symbol = NormalizeSymbol(symbol); - return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetCandlesAsync(symbol, periodSeconds, startDate, endDate, limit), nameof(GetCandlesAsync), - nameof(symbol), symbol, nameof(periodSeconds), periodSeconds, nameof(startDate), startDate, nameof(endDate), endDate, nameof(limit), limit); + marketSymbol = NormalizeMarketSymbol(marketSymbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetCandlesAsync(marketSymbol, periodSeconds, startDate, endDate, limit), nameof(GetCandlesAsync), + nameof(marketSymbol), marketSymbol, nameof(periodSeconds), periodSeconds, nameof(startDate), startDate, nameof(endDate), endDate, nameof(limit), limit); } /// @@ -626,7 +691,7 @@ public virtual async Task PlaceOrderAsync(ExchangeOrderRequ { // *NOTE* do not wrap in CacheMethodCall await new SynchronizationContextRemover(); - order.Symbol = NormalizeSymbol(order.Symbol); + order.MarketSymbol = NormalizeMarketSymbol(order.MarketSymbol); return await OnPlaceOrderAsync(order); } @@ -641,7 +706,7 @@ public virtual async Task PlaceOrdersAsync(params Exchang await new SynchronizationContextRemover(); foreach (ExchangeOrderRequest request in orders) { - request.Symbol = NormalizeSymbol(request.Symbol); + request.MarketSymbol = NormalizeMarketSymbol(request.MarketSymbol); } return await OnPlaceOrdersAsync(orders); } @@ -650,48 +715,48 @@ public virtual async Task PlaceOrdersAsync(params Exchang /// Get order details /// /// Order id to get details for - /// Symbol of order (most exchanges do not require this) + /// Symbol of order (most exchanges do not require this) /// Order details - public virtual async Task GetOrderDetailsAsync(string orderId, string symbol = null) + public virtual async Task GetOrderDetailsAsync(string orderId, string marketSymbol = null) { - symbol = NormalizeSymbol(symbol); - return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOrderDetailsAsync(orderId, symbol), nameof(GetOrderDetailsAsync), nameof(orderId), orderId, nameof(symbol), symbol); + marketSymbol = NormalizeMarketSymbol(marketSymbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOrderDetailsAsync(orderId, marketSymbol), nameof(GetOrderDetailsAsync), nameof(orderId), orderId, nameof(marketSymbol), marketSymbol); } /// /// Get the details of all open orders /// - /// Symbol to get open orders for or null for all + /// Symbol to get open orders for or null for all /// All open order details - public virtual async Task> GetOpenOrderDetailsAsync(string symbol = null) + public virtual async Task> GetOpenOrderDetailsAsync(string marketSymbol = null) { - symbol = NormalizeSymbol(symbol); - return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOpenOrderDetailsAsync(symbol), nameof(GetOpenOrderDetailsAsync), nameof(symbol), symbol); + marketSymbol = NormalizeMarketSymbol(marketSymbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOpenOrderDetailsAsync(marketSymbol), nameof(GetOpenOrderDetailsAsync), nameof(marketSymbol), marketSymbol); } /// /// Get the details of all completed orders /// - /// Symbol to get completed orders for or null for all + /// Symbol to get completed orders for or null for all /// Only returns orders on or after the specified date/time /// All completed order details for the specified symbol, or all if null symbol - public virtual async Task> GetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null) + public virtual async Task> GetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { - symbol = NormalizeSymbol(symbol); - return await Cache.CacheMethod(MethodCachePolicy, async () => (await OnGetCompletedOrderDetailsAsync(symbol, afterDate)).ToArray(), nameof(GetCompletedOrderDetailsAsync), - nameof(symbol), symbol, nameof(afterDate), afterDate); + marketSymbol = NormalizeMarketSymbol(marketSymbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => (await OnGetCompletedOrderDetailsAsync(marketSymbol, afterDate)).ToArray(), nameof(GetCompletedOrderDetailsAsync), + nameof(marketSymbol), marketSymbol, nameof(afterDate), afterDate); } /// /// Cancel an order, an exception is thrown if error /// /// Order id of the order to cancel - /// Symbol of order (most exchanges do not require this) - public virtual async Task CancelOrderAsync(string orderId, string symbol = null) + /// Symbol of order (most exchanges do not require this) + public virtual async Task CancelOrderAsync(string orderId, string marketSymbol = null) { // *NOTE* do not wrap in CacheMethodCall await new SynchronizationContextRemover(); - await OnCancelOrderAsync(orderId, NormalizeSymbol(symbol)); + await OnCancelOrderAsync(orderId, NormalizeMarketSymbol(marketSymbol)); } /// @@ -702,7 +767,7 @@ public virtual async Task WithdrawAsync(ExchangeWith { // *NOTE* do not wrap in CacheMethodCall await new SynchronizationContextRemover(); - withdrawalRequest.Currency = NormalizeSymbol(withdrawalRequest.Currency); + withdrawalRequest.Currency = NormalizeMarketSymbol(withdrawalRequest.Currency); return await OnWithdrawAsync(withdrawalRequest); } @@ -720,24 +785,24 @@ public virtual async Task> GetMarginAmountsAvailable /// /// Get open margin position /// - /// Symbol + /// Symbol /// Open margin position result - public virtual async Task GetOpenPositionAsync(string symbol) + public virtual async Task GetOpenPositionAsync(string marketSymbol) { - symbol = NormalizeSymbol(symbol); - return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOpenPositionAsync(symbol), nameof(GetOpenPositionAsync), nameof(symbol), symbol); + marketSymbol = NormalizeMarketSymbol(marketSymbol); + return await Cache.CacheMethod(MethodCachePolicy, async () => await OnGetOpenPositionAsync(marketSymbol), nameof(GetOpenPositionAsync), nameof(marketSymbol), marketSymbol); } /// /// Close a margin position /// - /// Symbol + /// Symbol /// Close margin position result - public virtual async Task CloseMarginPositionAsync(string symbol) + public virtual async Task CloseMarginPositionAsync(string marketSymbol) { // *NOTE* do not wrap in CacheMethodCall await new SynchronizationContextRemover(); - return await OnCloseMarginPositionAsync(NormalizeSymbol(symbol)); + return await OnCloseMarginPositionAsync(NormalizeMarketSymbol(marketSymbol)); } #endregion REST API @@ -760,12 +825,12 @@ public virtual IWebSocket GetTickersWebSocket(Action /// Callback (symbol and trade) - /// Symbols + /// Market Symbols /// Web socket, call Dispose to close - public virtual IWebSocket GetTradesWebSocket(Action> callback, params string[] symbols) + public virtual IWebSocket GetTradesWebSocket(Action> callback, params string[] marketSymbols) { callback.ThrowIfNull(nameof(callback), "Callback must not be null"); - return OnGetTradesWebSocket(callback, symbols); + return OnGetTradesWebSocket(callback, marketSymbols); } /// @@ -773,12 +838,12 @@ public virtual IWebSocket GetTradesWebSocket(Action /// Callback of symbol, order book /// Max count of bids and asks - not all exchanges will honor this parameter - /// Ticker symbols or null/empty for all of them (if supported) + /// Market symbols or null/empty for all of them (if supported) /// Web socket, call Dispose to close - public virtual IWebSocket GetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols) + public virtual IWebSocket GetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) { callback.ThrowIfNull(nameof(callback), "Callback must not be null"); - return OnGetOrderBookWebSocket(callback, maxCount, symbols); + return OnGetOrderBookWebSocket(callback, maxCount, marketSymbols); } /// diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIDefinitions.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIDefinitions.cs index 74453517..0b080542 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIDefinitions.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIDefinitions.cs @@ -81,17 +81,17 @@ public abstract partial class ExchangeAPI /// /// Separator for exchange symbol. If not a hyphen, set in constructor. /// - public string SymbolSeparator { get; protected set; } = "-"; + public string MarketSymbolSeparator { get; protected set; } = "-"; /// /// Whether the symbol is reversed. Most exchanges do ETH-BTC, if your exchange does BTC-ETH, set to true in constructor. /// - public bool SymbolIsReversed { get; protected set; } + public bool MarketSymbolIsReversed { get; protected set; } /// /// Whether the symbol is uppercase. Most exchanges are true, but if your exchange is lowercase, set to false in constructor. /// - public bool SymbolIsUppercase { get; protected set; } = true; + public bool MarketSymbolIsUppercase { get; protected set; } = true; /// /// The type of web socket order book supported diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs index 7e6b3c48..959f2e08 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs @@ -81,7 +81,7 @@ async Task innerCallback(ExchangeOrderBook newOrderBook) // ideally all exchanges would send the full order book on first message, followed by delta order books // but this is not the case - bool foundFullBook = fullBooks.TryGetValue(newOrderBook.Symbol, out ExchangeOrderBook fullOrderBook); + bool foundFullBook = fullBooks.TryGetValue(newOrderBook.MarketSymbol, out ExchangeOrderBook fullOrderBook); switch (api.WebSocketOrderBookType) { case WebSocketOrderBookType.DeltasOnly: @@ -95,10 +95,10 @@ async Task innerCallback(ExchangeOrderBook newOrderBook) // attempt to find the right queue to put the partial order book in to be processed later lock (partialOrderBookQueues) { - if (!partialOrderBookQueues.TryGetValue(newOrderBook.Symbol, out partialOrderBookQueue)) + if (!partialOrderBookQueues.TryGetValue(newOrderBook.MarketSymbol, out partialOrderBookQueue)) { // no queue found, make a new one - partialOrderBookQueues[newOrderBook.Symbol] = partialOrderBookQueue = new Queue(); + partialOrderBookQueues[newOrderBook.MarketSymbol] = partialOrderBookQueue = new Queue(); requestFullOrderBook = !foundFullBook; } @@ -109,9 +109,9 @@ async Task innerCallback(ExchangeOrderBook newOrderBook) // request the entire order book if we need it if (requestFullOrderBook) { - fullOrderBook = await api.GetOrderBookAsync(newOrderBook.Symbol, maxCount); - fullOrderBook.Symbol = newOrderBook.Symbol; - fullBooks[newOrderBook.Symbol] = fullOrderBook; + fullOrderBook = await api.GetOrderBookAsync(newOrderBook.MarketSymbol, maxCount); + fullOrderBook.MarketSymbol = newOrderBook.MarketSymbol; + fullBooks[newOrderBook.MarketSymbol] = fullOrderBook; } else if (!foundFullBook) { @@ -125,7 +125,7 @@ async Task innerCallback(ExchangeOrderBook newOrderBook) // lock dictionary of queues for lookup only lock (partialOrderBookQueues) { - partialOrderBookQueues.TryGetValue(newOrderBook.Symbol, out partialOrderBookQueue); + partialOrderBookQueues.TryGetValue(newOrderBook.MarketSymbol, out partialOrderBookQueue); } if (partialOrderBookQueue != null) @@ -147,7 +147,7 @@ async Task innerCallback(ExchangeOrderBook newOrderBook) // Subsequent updates will be deltas, at least some exchanges have their heads on straight if (!foundFullBook) { - fullBooks[newOrderBook.Symbol] = fullOrderBook = newOrderBook; + fullBooks[newOrderBook.MarketSymbol] = fullOrderBook = newOrderBook; } else { @@ -158,7 +158,7 @@ async Task innerCallback(ExchangeOrderBook newOrderBook) case WebSocketOrderBookType.FullBookAlways: { // Websocket always returns full order book, WTF...? - fullBooks[newOrderBook.Symbol] = fullOrderBook = newOrderBook; + fullBooks[newOrderBook.MarketSymbol] = fullOrderBook = newOrderBook; } break; } @@ -269,7 +269,7 @@ public static async Task PlaceSafeMarketOrderAsync(this Exc OrderType = OrderType.Limit, Price = CryptoUtility.RoundAmount((isBuy ? highPrice : lowPrice) * priceThreshold), ShouldRoundAmount = true, - Symbol = symbol + MarketSymbol = symbol }; ExchangeOrderResult result = await api.PlaceOrderAsync(request); @@ -387,22 +387,22 @@ internal static ExchangeOrderBook ParseOrderBookFromJTokenDictionaries /// /// ExchangeAPI /// Token - /// Symbol + /// Symbol /// Ask key /// Bid key /// Last key - /// Base volume key - /// Convert volume key + /// Base currency volume key + /// Quote currency volume key /// Timestamp key /// Timestamp type /// Base currency key - /// Convert currency key + /// Quote currency key /// Id key /// ExchangeTicker - internal static ExchangeTicker ParseTicker(this ExchangeAPI api, JToken token, string symbol, + internal static ExchangeTicker ParseTicker(this ExchangeAPI api, JToken token, string marketSymbol, object askKey, object bidKey, object lastKey, object baseVolumeKey, - object convertVolumeKey = null, object timestampKey = null, TimestampType timestampType = TimestampType.None, - object baseCurrencyKey = null, object convertCurrencyKey = null, object idKey = null) + object quoteVolumeKey = null, object timestampKey = null, TimestampType timestampType = TimestampType.None, + object baseCurrencyKey = null, object quoteCurrencyKey = null, object idKey = null) { if (token == null || !token.HasValues) { @@ -411,36 +411,26 @@ internal static ExchangeTicker ParseTicker(this ExchangeAPI api, JToken token, s decimal last = token[lastKey].ConvertInvariant(); // parse out volumes, handle cases where one or both do not exist - token.ParseVolumes(baseVolumeKey, convertVolumeKey, last, out decimal baseVolume, out decimal convertVolume); + token.ParseVolumes(baseVolumeKey, quoteVolumeKey, last, out decimal baseCurrencyVolume, out decimal quoteCurrencyVolume); // pull out timestamp DateTime timestamp = (timestampKey == null ? CryptoUtility.UtcNow : CryptoUtility.ParseTimestamp(token[timestampKey], timestampType)); // split apart the symbol if we have a separator, otherwise just put the symbol for base and convert symbol - string baseSymbol; - string convertSymbol; - if (baseCurrencyKey != null && convertCurrencyKey != null) + string baseCurrency; + string quoteCurrency; + if (baseCurrencyKey != null && quoteCurrencyKey != null) { - baseSymbol = token[baseCurrencyKey].ToStringInvariant(); - convertSymbol = token[convertCurrencyKey].ToStringInvariant(); + baseCurrency = token[baseCurrencyKey].ToStringInvariant(); + quoteCurrency = token[quoteCurrencyKey].ToStringInvariant(); } - else if (string.IsNullOrWhiteSpace(symbol)) + else if (string.IsNullOrWhiteSpace(marketSymbol)) { - throw new ArgumentNullException(nameof(symbol)); - } - else if (api.SymbolSeparator.Length != 0) - { - string[] pieces = symbol.Split(api.SymbolSeparator[0]); - if (pieces.Length != 2) - { - throw new ArgumentException($"Symbol does not have the correct symbol separator of '{api.SymbolSeparator}'"); - } - baseSymbol = pieces[0]; - convertSymbol = pieces[1]; + throw new ArgumentNullException(nameof(marketSymbol)); } else { - baseSymbol = convertSymbol = symbol; + (baseCurrency, quoteCurrency) = api.ExchangeMarketSymbolToCurrencies(marketSymbol); } // create the ticker and return it @@ -456,16 +446,17 @@ internal static ExchangeTicker ParseTicker(this ExchangeAPI api, JToken token, s } ExchangeTicker ticker = new ExchangeTicker { + MarketSymbol = marketSymbol, Ask = askValue.ConvertInvariant(), Bid = bidValue.ConvertInvariant(), Id = (idKey == null ? null : token[idKey].ToStringInvariant()), Last = last, Volume = new ExchangeVolume { - BaseVolume = baseVolume, - BaseSymbol = baseSymbol, - ConvertedVolume = convertVolume, - ConvertedSymbol = convertSymbol, + BaseCurrencyVolume = baseCurrencyVolume, + BaseCurrency = baseCurrency, + QuoteCurrencyVolume = quoteCurrencyVolume, + QuoteCurrency = quoteCurrency, Timestamp = timestamp } }; @@ -516,36 +507,36 @@ internal static ExchangeTrade ParseTrade(this JToken token, object amountKey, ob /// Parse volume from JToken /// /// JToken - /// Base volume key - /// Convert volume key + /// Base currency volume key + /// Quote currency volume key /// Last volume value - /// Receive base volume - /// Receive convert volume - internal static void ParseVolumes(this JToken token, object baseVolumeKey, object convertVolumeKey, decimal last, out decimal baseVolume, out decimal convertVolume) + /// Receive base currency volume + /// Receive quote currency volume + internal static void ParseVolumes(this JToken token, object baseVolumeKey, object quoteVolumeKey, decimal last, out decimal baseCurrencyVolume, out decimal quoteCurrencyVolume) { // parse out volumes, handle cases where one or both do not exist if (baseVolumeKey == null) { - if (convertVolumeKey == null) + if (quoteVolumeKey == null) { - baseVolume = convertVolume = 0m; + baseCurrencyVolume = quoteCurrencyVolume = 0m; } else { - convertVolume = token[convertVolumeKey].ConvertInvariant(); - baseVolume = (last <= 0m ? 0m : convertVolume / last); + quoteCurrencyVolume = token[quoteVolumeKey].ConvertInvariant(); + baseCurrencyVolume = (last <= 0m ? 0m : quoteCurrencyVolume / last); } } else { - baseVolume = token[baseVolumeKey].ConvertInvariant(); - if (convertVolumeKey == null) + baseCurrencyVolume = token[baseVolumeKey].ConvertInvariant(); + if (quoteVolumeKey == null) { - convertVolume = baseVolume * last; + quoteCurrencyVolume = baseCurrencyVolume * last; } else { - convertVolume = token[convertVolumeKey].ConvertInvariant(); + quoteCurrencyVolume = token[quoteVolumeKey].ConvertInvariant(); } } } @@ -555,7 +546,7 @@ internal static void ParseVolumes(this JToken token, object baseVolumeKey, objec /// /// Named item /// JToken - /// Symbol + /// Symbol /// Period seconds /// Open key /// High key @@ -563,12 +554,12 @@ internal static void ParseVolumes(this JToken token, object baseVolumeKey, objec /// Close key /// Timestamp key /// Timestamp type - /// Base volume key - /// Convert volume key + /// Base currency volume key + /// Quote currency volume key /// Weighted average key /// MarketCandle - internal static MarketCandle ParseCandle(this INamed named, JToken token, string symbol, int periodSeconds, object openKey, object highKey, object lowKey, - object closeKey, object timestampKey, TimestampType timestampType, object baseVolumeKey, object convertVolumeKey = null, object weightedAverageKey = null) + internal static MarketCandle ParseCandle(this INamed named, JToken token, string marketSymbol, int periodSeconds, object openKey, object highKey, object lowKey, + object closeKey, object timestampKey, TimestampType timestampType, object baseVolumeKey, object quoteVolumeKey = null, object weightedAverageKey = null) { MarketCandle candle = new MarketCandle { @@ -576,15 +567,15 @@ internal static MarketCandle ParseCandle(this INamed named, JToken token, string ExchangeName = named.Name, HighPrice = token[highKey].ConvertInvariant(), LowPrice = token[lowKey].ConvertInvariant(), - Name = symbol, + Name = marketSymbol, OpenPrice = token[openKey].ConvertInvariant(), PeriodSeconds = periodSeconds, Timestamp = CryptoUtility.ParseTimestamp(token[timestampKey], timestampType) }; - token.ParseVolumes(baseVolumeKey, convertVolumeKey, candle.ClosePrice, out decimal baseVolume, out decimal convertVolume); - candle.BaseVolume = (double)baseVolume; - candle.ConvertedVolume = (double)convertVolume; + token.ParseVolumes(baseVolumeKey, quoteVolumeKey, candle.ClosePrice, out decimal baseVolume, out decimal convertVolume); + candle.BaseCurrencyVolume = (double)baseVolume; + candle.QuoteCurrencyVolume = (double)convertVolume; if (weightedAverageKey != null) { candle.WeightedAverage = token[weightedAverageKey].ConvertInvariant(); diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeHistoricalTradeHelper.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeHistoricalTradeHelper.cs index 496005ce..a97ecff3 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeHistoricalTradeHelper.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeHistoricalTradeHelper.cs @@ -26,10 +26,10 @@ protected class ExchangeHistoricalTradeHelper private ExchangeAPI api; public Func, bool> Callback { get; set; } - public string Symbol { get; set; } + public string MarketSymbol { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } - public string Url { get; set; } // url with format [symbol], {0} = start timestamp, {1} = end timestamp + public string Url { get; set; } // url with format [marketSymbol], {0} = start timestamp, {1} = end timestamp public int DelayMilliseconds { get; set; } = 1000; public TimeSpan BlockTime { get; set; } = TimeSpan.FromHours(1.0); // how much time to move for each block of data, default 1 hour public bool MillisecondGranularity { get; set; } = true; @@ -62,9 +62,9 @@ public async Task ProcessHistoricalTrades() throw new ArgumentException("Missing required parameter", nameof(Url)); } - Symbol = api.NormalizeSymbol(Symbol); + MarketSymbol = api.NormalizeMarketSymbol(MarketSymbol); string url; - Url = Url.Replace("[symbol]", Symbol); + Url = Url.Replace("[marketSymbol]", MarketSymbol); List trades = new List(); ExchangeTrade trade; EndDate = (EndDate ?? CryptoUtility.UtcNow); diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs index aa9029d4..d7c6a3e5 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs @@ -58,15 +58,15 @@ private BinaryWriter CreateLogWriter(string path, bool compress) /// Constructor /// /// API - /// The symbol to log, i.e. btcusd + /// The symbol to log, i.e. btcusd /// Interval in seconds between updates /// The path to write the log files to /// Whether to compress the log files using gzip compression - public ExchangeLogger(IExchangeAPI api, string symbol, float intervalSeconds, string path, bool compress = false) + public ExchangeLogger(IExchangeAPI api, string marketSymbol, float intervalSeconds, string path, bool compress = false) { string compressExtension = (compress ? ".gz" : string.Empty); API = api; - Symbol = symbol; + MarketSymbol = marketSymbol; Interval = TimeSpan.FromSeconds(intervalSeconds); sysTimeWriter = CreateLogWriter(Path.Combine(path, api.Name + "_time.bin" + compressExtension), compress); tickerWriter = CreateLogWriter(Path.Combine(path, api.Name + "_ticker.bin" + compressExtension), compress); @@ -84,7 +84,7 @@ public void Update() try { - if (Symbol == "*") + if (MarketSymbol == "*") { // get all symbols Tickers = API.GetTickersAsync().Sync().ToArray(); @@ -98,9 +98,9 @@ public void Update() else { // make API calls first, if they fail we will try again later - Tickers = new KeyValuePair[1] { new KeyValuePair(Symbol, API.GetTickerAsync(Symbol).Sync()) }; - OrderBook = API.GetOrderBookAsync(Symbol).Sync(); - Trades = API.GetRecentTradesAsync(Symbol).Sync().OrderBy(t => t.Timestamp).ToArray(); + Tickers = new KeyValuePair[1] { new KeyValuePair(MarketSymbol, API.GetTickerAsync(MarketSymbol).Sync()) }; + OrderBook = API.GetOrderBookAsync(MarketSymbol).Sync(); + Trades = API.GetRecentTradesAsync(MarketSymbol).Sync().OrderBy(t => t.Timestamp).ToArray(); // all API calls succeeded, we can write to files @@ -303,7 +303,7 @@ public static IEnumerable> ReadMultiTickers(s /// /// The symbol being logged /// - public string Symbol { get; private set; } + public string MarketSymbol { get; private set; } /// /// The interval in between log calls diff --git a/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs b/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs index c028b09d..e7be4497 100644 --- a/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs @@ -27,9 +27,9 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// /// Normalize a symbol for use on this exchange /// - /// Symbol + /// Symbol /// Normalized symbol - string NormalizeSymbol(string symbol); + string NormalizeMarketSymbol(string marketSymbol); /// /// Convert an exchange symbol into a global symbol, which will be the same for all exchanges. @@ -37,16 +37,16 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// Global symbols list the base currency first (i.e. BTC) and conversion currency /// second (i.e. USD). Example BTC-USD, read as x BTC is worth y USD. /// - /// Exchange symbol + /// Exchange symbol /// Global symbol - string ExchangeSymbolToGlobalSymbol(string symbol); + string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol); /// /// Convert a global symbol into an exchange symbol, which will potentially be different from other exchanges. /// - /// Global symbol + /// Global symbol /// Exchange symbol - string GlobalSymbolToExchangeSymbol(string symbol); + string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol); /// /// Convert seconds to a period string, or throw exception if seconds invalid. Example: 60 seconds becomes 1m. @@ -68,36 +68,36 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// /// Gets the address to deposit to and applicable details. /// - /// Symbol to get address for. + /// Currency to get address for. /// True to regenerate the address /// Deposit address details (including tag if applicable, such as XRP) - Task GetDepositAddressAsync(string symbol, bool forceRegenerate = false); + Task GetDepositAddressAsync(string currency, bool forceRegenerate = false); /// - /// Gets the deposit history for a symbol + /// Gets the deposit history for a currency /// - /// The symbol to check. May be null. + /// The currency to check. May be null. /// Collection of ExchangeCoinTransfers - Task> GetDepositHistoryAsync(string symbol); + Task> GetDepositHistoryAsync(string currency); /// - /// Get symbols for the exchange + /// Get symbols for the exchange markets /// /// Symbols - Task> GetSymbolsAsync(); + Task> GetMarketSymbolsAsync(); /// - /// Get exchange symbols including available metadata such as min trade size and whether the market is active + /// Get exchange market symbols including available metadata such as min trade size and whether the market is active /// /// Collection of ExchangeMarkets - Task> GetSymbolsMetadataAsync(); + Task> GetMarketSymbolsMetadataAsync(); /// /// Get latest ticker /// - /// Symbol + /// Symbol /// Latest ticker - Task GetTickerAsync(string symbol); + Task GetTickerAsync(string marketSymbol); /// /// Get all tickers, not all exchanges support this @@ -109,28 +109,28 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// Get historical trades for the exchange /// /// Callback for each set of trades. Return false to stop getting trades immediately. - /// Symbol to get historical data for + /// Symbol to get historical data for /// Optional start date time to start getting the historical data at, null for the most recent data. Not all exchanges support this. /// Optional UTC end date time to start getting the historical data at, null for the most recent data. Not all exchanges support this. - Task GetHistoricalTradesAsync(Func, bool> callback, string symbol, DateTime? startDate = null, DateTime? endDate = null); + Task GetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null); /// /// Get the latest trades /// - /// Symbol + /// Market Symbol /// Trades - Task> GetRecentTradesAsync(string symbol); + Task> GetRecentTradesAsync(string marketSymbol); /// /// Get candles (open, high, low, close) /// - /// Symbol to get candles for + /// Market symbol to get candles for /// Period in seconds to get candles for. Use 60 for minute, 3600 for hour, 3600*24 for day, 3600*24*30 for month. /// Optional start date to get candles for /// Optional end date to get candles for /// Max results, can be used instead of startDate and endDate if desired /// Candles - Task> GetCandlesAsync(string symbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null); + Task> GetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null); /// /// Get total amounts, symbol / amount dictionary @@ -162,30 +162,31 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// Get details of an order /// /// order id + /// Market Symbol /// Order details - Task GetOrderDetailsAsync(string orderId, string symbol = null); + Task GetOrderDetailsAsync(string orderId, string marketSymbol = null); /// /// Get the details of all open orders /// - /// Symbol to get open orders for or null for all + /// Market symbol to get open orders for or null for all /// All open order details for the specified symbol - Task> GetOpenOrderDetailsAsync(string symbol = null); + Task> GetOpenOrderDetailsAsync(string marketSymbol = null); /// /// Get the details of all completed orders /// - /// Symbol to get completed orders for or null for all + /// Market symbol to get completed orders for or null for all /// Only returns orders on or after the specified date/time /// All completed order details for the specified symbol, or all if null symbol - Task> GetCompletedOrderDetailsAsync(string symbol = null, DateTime? afterDate = null); + Task> GetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null); /// /// Cancel an order, an exception is thrown if failure /// /// Order id of the order to cancel - /// Order symbol of the order to cancel (not required for most exchanges) - Task CancelOrderAsync(string orderId, string symbol = null); + /// Market symbol of the order to cancel (not required for most exchanges) + Task CancelOrderAsync(string orderId, string marketSymbol = null); /// /// Get margin amounts available to trade, symbol / amount dictionary @@ -197,16 +198,16 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// /// Get open margin position /// - /// Symbol + /// Market Symbol /// Open margin position result - Task GetOpenPositionAsync(string symbol); + Task GetOpenPositionAsync(string marketSymbol); /// /// Close a margin position /// - /// Symbol + /// Market Symbol /// Close margin position result - Task CloseMarginPositionAsync(string symbol); + Task CloseMarginPositionAsync(string marketSymbol); /// /// Get fees @@ -230,9 +231,9 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// Get information about trades via web socket /// /// Callback (symbol and trade) - /// Symbols + /// Market symbols /// Web socket, call Dispose to close - IWebSocket GetTradesWebSocket(Action> callback, params string[] symbols); + IWebSocket GetTradesWebSocket(Action> callback, params string[] marketSymbols); /// /// Get the details of all changed orders via web socket diff --git a/ExchangeSharp/API/Exchanges/_Base/Interfaces/IOrderBookProvider.cs b/ExchangeSharp/API/Exchanges/_Base/Interfaces/IOrderBookProvider.cs index 23018f7b..2490cfcf 100644 --- a/ExchangeSharp/API/Exchanges/_Base/Interfaces/IOrderBookProvider.cs +++ b/ExchangeSharp/API/Exchanges/_Base/Interfaces/IOrderBookProvider.cs @@ -25,10 +25,10 @@ public interface IOrderBookProvider /// /// Get pending orders. Depending on the exchange, the number of bids and asks will have different counts, typically 50-100. /// - /// Symbol + /// Symbol /// Max count of bids and asks - not all exchanges will honor this parameter /// Orders - Task GetOrderBookAsync(string symbol, int maxCount = 100); + Task GetOrderBookAsync(string marketSymbol, int maxCount = 100); /// /// Get exchange order book for all symbols. Not all exchanges support this. Depending on the exchange, the number of bids and asks will have different counts, typically 50-100. @@ -42,9 +42,9 @@ public interface IOrderBookProvider /// /// Callback with the full ExchangeOrderBook /// Max count of bids and asks - not all exchanges will honor this parameter - /// Order book symbols or null/empty for all of them (if supported) + /// Market symbols or null/empty for all of them (if supported) /// Web socket, call Dispose to close - IWebSocket GetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols); + IWebSocket GetOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols); /// /// What type of web socket order book is provided diff --git a/ExchangeSharp/API/Services/CryptowatchAPI.cs b/ExchangeSharp/API/Services/CryptowatchAPI.cs index 3979f635..db56de21 100644 --- a/ExchangeSharp/API/Services/CryptowatchAPI.cs +++ b/ExchangeSharp/API/Services/CryptowatchAPI.cs @@ -102,12 +102,12 @@ public async Task> GetMarketSummaries() return summaries; } - public async Task GetOrderBookAsync(string exchange, string symbol, int maxCount = 100) + public async Task GetOrderBookAsync(string exchange, string marketSymbol, int maxCount = 100) { await new SynchronizationContextRemover(); ExchangeOrderBook book = new ExchangeOrderBook(); - JToken result = await MakeJsonRequestAsync("/markets/" + exchange.ToLowerInvariant() + "/" + symbol + "/orderbook"); + JToken result = await MakeJsonRequestAsync("/markets/" + exchange.ToLowerInvariant() + "/" + marketSymbol + "/orderbook"); int count = 0; foreach (JArray array in result["asks"]) { diff --git a/ExchangeSharp/Model/ExchangeCloseMarginPositionResult.cs b/ExchangeSharp/Model/ExchangeCloseMarginPositionResult.cs index abb882e4..37ba3310 100644 --- a/ExchangeSharp/Model/ExchangeCloseMarginPositionResult.cs +++ b/ExchangeSharp/Model/ExchangeCloseMarginPositionResult.cs @@ -21,7 +21,7 @@ public class ExchangeCloseMarginPositionResult public bool Success { get; set; } public string Message { get; set; } public bool IsBuy { get; set; } - public string Symbol { get; set; } + public string MarketSymbol { get; set; } public string FeesCurrency { get; set; } public decimal AmountFilled { get; set; } public decimal AveragePrice { get; set; } diff --git a/ExchangeSharp/Model/ExchangeDepositDetails.cs b/ExchangeSharp/Model/ExchangeDepositDetails.cs index 71ecdb99..3a8e066b 100644 --- a/ExchangeSharp/Model/ExchangeDepositDetails.cs +++ b/ExchangeSharp/Model/ExchangeDepositDetails.cs @@ -15,8 +15,8 @@ namespace ExchangeSharp /// Class to encapsulate details required to make a deposit. public sealed class ExchangeDepositDetails { - /// The symbol of the currency. Ex. ETH - public string Symbol; + /// The name of the currency. Ex. ETH + public string Currency; /// The address to deposit to public string Address; @@ -29,7 +29,7 @@ public sealed class ExchangeDepositDetails /// A that represents this instance. public override string ToString() { - return $"{Symbol}: Address: {Address} AddressTag: {AddressTag}"; + return $"{Currency}: Address: {Address} AddressTag: {AddressTag}"; } } } \ No newline at end of file diff --git a/ExchangeSharp/Model/ExchangeInfo.cs b/ExchangeSharp/Model/ExchangeInfo.cs index 41ba6b28..53110e09 100644 --- a/ExchangeSharp/Model/ExchangeInfo.cs +++ b/ExchangeSharp/Model/ExchangeInfo.cs @@ -27,12 +27,12 @@ public sealed class ExchangeInfo /// Constructor /// /// Exchange API - /// The symbol to trade by default, can be null - public ExchangeInfo(IExchangeAPI api, string symbol = null) + /// The market symbol to trade by default, can be null + public ExchangeInfo(IExchangeAPI api, string marketSymbol = null) { API = api; - Symbols = api.GetSymbolsAsync().Sync().ToArray(); - TradeInfo = new ExchangeTradeInfo(this, symbol); + MarketSymbols = api.GetMarketSymbolsAsync().Sync().ToArray(); + TradeInfo = new ExchangeTradeInfo(this, marketSymbol); } /// @@ -54,9 +54,9 @@ public void Update() public int Id { get; set; } /// - /// Symbols of the exchange + /// Market symbols of the exchange /// - public IReadOnlyCollection Symbols { get; private set; } + public IReadOnlyCollection MarketSymbols { get; private set; } /// /// Latest trade info for the exchange diff --git a/ExchangeSharp/Model/ExchangeMarginPositionResult.cs b/ExchangeSharp/Model/ExchangeMarginPositionResult.cs index 2ed9892c..cce2ae00 100644 --- a/ExchangeSharp/Model/ExchangeMarginPositionResult.cs +++ b/ExchangeSharp/Model/ExchangeMarginPositionResult.cs @@ -18,9 +18,9 @@ namespace ExchangeSharp public class ExchangeMarginPositionResult { /// - /// Symbol + /// Market Symbol /// - public string Symbol { get; set; } + public string MarketSymbol { get; set; } /// /// Amount diff --git a/ExchangeSharp/Model/ExchangeMarket.cs b/ExchangeSharp/Model/ExchangeMarket.cs index 4fe2ba33..e9d12d73 100644 --- a/ExchangeSharp/Model/ExchangeMarket.cs +++ b/ExchangeSharp/Model/ExchangeMarket.cs @@ -18,25 +18,35 @@ public sealed class ExchangeMarket /// Id of the market (specific to the exchange), null if none public string MarketId { get; set; } - /// Gets or sets the name of the market. - public string MarketName { get; set; } + /// Gets or sets the symbol representing the market's currency pair. + public string MarketSymbol { get; set; } /// A value indicating whether the market is active. public bool IsActive { get; set; } - /// In a pair like ZRX/BTC, BTC is the base currency. - public string BaseCurrency { get; set; } + /// In a pair like ZRX/BTC, BTC is the quote currency. + public string QuoteCurrency { get; set; } - /// In a pair like ZRX/BTC, ZRX is the market currency. - public string MarketCurrency { get; set; } + /// In a pair like ZRX/BTC, ZRX is the base currency. + public string BaseCurrency { get; set; } - /// The minimum size of the trade in the unit of "MarketCurrency". For example, in + /// The minimum size of the trade in the unit of "BaseCurrency". For example, in /// DOGE/BTC the MinTradeSize is currently 423.72881356 DOGE public decimal MinTradeSize { get; set; } - /// The maximum size of the trade in the unit of "MarketCurrency". + /// The maximum size of the trade in the unit of "BaseCurrency". public decimal MaxTradeSize { get; set; } = decimal.MaxValue; + /// The minimum size of the trade in the unit of "QuoteCurrency". To determine an order's + /// trade size in terms of the Quote Currency, you need to calculate: price * quantity + /// NOTE: Not all exchanges provide this information + public decimal? MinTradeSizeInQuoteCurrency { get; set; } + + /// The maximum size of the trade in the unit of "QuoteCurrency". To determine an order's + /// trade size in terms of the Quote Currency, you need to calculate: price * quantity + /// NOTE: Not all exchanges provide this information + public decimal? MaxTradeSizeInQuoteCurrency { get; set; } + /// The minimum price of the pair. public decimal MinPrice { get; set; } @@ -52,9 +62,14 @@ public sealed class ExchangeMarket /// if unknown or not applicable. public decimal? QuantityStepSize { get; set; } + /// + /// Margin trading enabled for this market + /// + public bool MarginEnabled { get; set; } + public override string ToString() { - return $"{MarketName}, {MarketCurrency}-{BaseCurrency}"; + return $"{MarketSymbol}, {BaseCurrency}-{QuoteCurrency}"; } } } \ No newline at end of file diff --git a/ExchangeSharp/Model/ExchangeOrderBook.cs b/ExchangeSharp/Model/ExchangeOrderBook.cs index ea79dc2d..004becce 100644 --- a/ExchangeSharp/Model/ExchangeOrderBook.cs +++ b/ExchangeSharp/Model/ExchangeOrderBook.cs @@ -74,10 +74,10 @@ public sealed class ExchangeOrderBook public long SequenceId { get; set; } /// - /// The symbol. + /// The market symbol. /// This property is not serialized using the ToBinary and FromBinary methods. /// - public string Symbol { get; set; } + public string MarketSymbol { get; set; } /// The last updated UTC public DateTime LastUpdatedUtc { get; set; } = DateTime.MinValue; diff --git a/ExchangeSharp/Model/ExchangeOrderRequest.cs b/ExchangeSharp/Model/ExchangeOrderRequest.cs index e0d1f891..fcd953f1 100644 --- a/ExchangeSharp/Model/ExchangeOrderRequest.cs +++ b/ExchangeSharp/Model/ExchangeOrderRequest.cs @@ -22,9 +22,9 @@ namespace ExchangeSharp public class ExchangeOrderRequest { /// - /// Symbol or pair for the order, i.e. btcusd + /// Market symbol or pair for the order, i.e. btcusd /// - public string Symbol { get; set; } + public string MarketSymbol { get; set; } /// /// Amount to buy or sell diff --git a/ExchangeSharp/Model/ExchangeOrderResult.cs b/ExchangeSharp/Model/ExchangeOrderResult.cs index c42bd098..35666e5a 100644 --- a/ExchangeSharp/Model/ExchangeOrderResult.cs +++ b/ExchangeSharp/Model/ExchangeOrderResult.cs @@ -47,8 +47,8 @@ public sealed class ExchangeOrderResult /// Fill datetime in UTC public DateTime FillDate { get; set; } - /// Symbol. E.g. ADA/ETH - public string Symbol { get; set; } + /// Market Symbol. E.g. ADA/ETH + public string MarketSymbol { get; set; } /// Whether the order is a buy or sell public bool IsBuy { get; set; } @@ -65,9 +65,9 @@ public sealed class ExchangeOrderResult /// Order to append public void AppendOrderWithOrder(ExchangeOrderResult other) { - if ((OrderId != null) && (Symbol != null) && ((OrderId != other.OrderId) || (IsBuy != other.IsBuy) || (Symbol != other.Symbol))) + if ((OrderId != null) && (MarketSymbol != null) && ((OrderId != other.OrderId) || (IsBuy != other.IsBuy) || (MarketSymbol != other.MarketSymbol))) { - throw new InvalidOperationException("Appending orders requires order id, symbol and is buy to match"); + throw new InvalidOperationException("Appending orders requires order id, market symbol and is buy to match"); } decimal tradeSum = Amount + other.Amount; @@ -79,7 +79,7 @@ public void AppendOrderWithOrder(ExchangeOrderResult other) AveragePrice = (AveragePrice * (baseAmount / tradeSum)) + (other.AveragePrice * (other.Amount / tradeSum)); OrderId = other.OrderId; OrderDate = OrderDate == default ? other.OrderDate : OrderDate; - Symbol = other.Symbol; + MarketSymbol = other.MarketSymbol; IsBuy = other.IsBuy; } @@ -87,7 +87,7 @@ public void AppendOrderWithOrder(ExchangeOrderResult other) /// A string that represents this instance. public override string ToString() { - return $"[{OrderDate}], {(IsBuy ? "Buy" : "Sell")} {AmountFilled} of {Amount} {Symbol} {Result} at {AveragePrice}, fees paid {Fees} {FeesCurrency}"; + return $"[{OrderDate}], {(IsBuy ? "Buy" : "Sell")} {AmountFilled} of {Amount} {MarketSymbol} {Result} at {AveragePrice}, fees paid {Fees} {FeesCurrency}"; } } } \ No newline at end of file diff --git a/ExchangeSharp/Model/ExchangeTicker.cs b/ExchangeSharp/Model/ExchangeTicker.cs index 5e978585..4ec48e8d 100644 --- a/ExchangeSharp/Model/ExchangeTicker.cs +++ b/ExchangeSharp/Model/ExchangeTicker.cs @@ -31,6 +31,11 @@ public sealed class ExchangeTicker /// public string Id { get; set; } + /// + /// The currency pair symbol that this ticker is in reference to + /// + public string MarketSymbol { get; set; } + /// /// The bid is the price to sell at /// @@ -97,28 +102,28 @@ public sealed class ExchangeVolume public DateTime Timestamp { get; set; } /// - /// Price symbol - will equal quantity symbol if exchange doesn't break it out by price unit and quantity unit - /// In BTC-USD, this would be BTC + /// Quote / Price currency - will equal base currency if exchange doesn't break it out by price unit and quantity unit + /// In BTC-USD, this would be USD /// - public string BaseSymbol { get; set; } + public string QuoteCurrency { get; set; } /// - /// Price amount - will equal QuantityAmount if exchange doesn't break it out by price unit and quantity unit - /// In BTC-USD, this would be BTC volume + /// Amount in units of the QuoteCurrency - will equal BaseCurrencyVolume if exchange doesn't break it out by price unit and quantity unit + /// In BTC-USD, this would be USD volume /// - public decimal BaseVolume { get; set; } + public decimal QuoteCurrencyVolume { get; set; } /// - /// Quantity symbol (converted into this unit) - /// In BTC-USD, this would be USD + /// Base currency + /// In BTC-USD, this would be BTC /// - public string ConvertedSymbol { get; set; } + public string BaseCurrency { get; set; } /// - /// Quantity amount (this many units total) - /// In BTC-USD this would be USD volume + /// Base currency amount (this many units total) + /// In BTC-USD this would be BTC volume /// - public decimal ConvertedVolume { get; set; } + public decimal BaseCurrencyVolume { get; set; } /// /// Write to a binary writer @@ -127,10 +132,10 @@ public sealed class ExchangeVolume public void ToBinary(BinaryWriter writer) { writer.Write(Timestamp.ToUniversalTime().Ticks); - writer.Write(BaseSymbol); - writer.Write((double)BaseVolume); - writer.Write(ConvertedSymbol); - writer.Write((double)ConvertedVolume); + writer.Write(QuoteCurrency); + writer.Write((double)QuoteCurrencyVolume); + writer.Write(BaseCurrency); + writer.Write((double)BaseCurrencyVolume); } /// @@ -140,10 +145,10 @@ public void ToBinary(BinaryWriter writer) public void FromBinary(BinaryReader reader) { Timestamp = new DateTime(reader.ReadInt64(), DateTimeKind.Utc); - BaseSymbol = reader.ReadString(); - BaseVolume = (decimal)reader.ReadDouble(); - ConvertedSymbol = reader.ReadString(); - ConvertedVolume = (decimal)reader.ReadDouble(); + QuoteCurrency = reader.ReadString(); + QuoteCurrencyVolume = (decimal)reader.ReadDouble(); + BaseCurrency = reader.ReadString(); + BaseCurrencyVolume = (decimal)reader.ReadDouble(); } } } diff --git a/ExchangeSharp/Model/ExchangeTradeInfo.cs b/ExchangeSharp/Model/ExchangeTradeInfo.cs index a6d09dc9..89242cef 100644 --- a/ExchangeSharp/Model/ExchangeTradeInfo.cs +++ b/ExchangeSharp/Model/ExchangeTradeInfo.cs @@ -27,11 +27,11 @@ public sealed class ExchangeTradeInfo /// Constructor /// /// Exchange info - /// The symbol to trade - public ExchangeTradeInfo(ExchangeInfo info, string symbol) + /// The symbol to trade + public ExchangeTradeInfo(ExchangeInfo info, string marketSymbol) { ExchangeInfo = info; - Symbol = symbol; + MarketSymbol = marketSymbol; } /// @@ -39,8 +39,8 @@ public ExchangeTradeInfo(ExchangeInfo info, string symbol) /// public void Update() { - Ticker = ExchangeInfo.API.GetTickerAsync(Symbol).Sync(); - RecentTrades = ExchangeInfo.API.GetRecentTradesAsync(Symbol).Sync().ToArray(); + Ticker = ExchangeInfo.API.GetTickerAsync(MarketSymbol).Sync(); + RecentTrades = ExchangeInfo.API.GetRecentTradesAsync(MarketSymbol).Sync().ToArray(); if (RecentTrades.Length == 0) { Trade = new Trade(); @@ -49,7 +49,7 @@ public void Update() { Trade = new Trade { Amount = (float)RecentTrades[RecentTrades.Length - 1].Amount, Price = (float)RecentTrades[RecentTrades.Length - 1].Price, Ticks = (long)CryptoUtility.UnixTimestampFromDateTimeMilliseconds(RecentTrades[RecentTrades.Length - 1].Timestamp) }; } - Orders = ExchangeInfo.API.GetOrderBookAsync(Symbol).Sync(); + Orders = ExchangeInfo.API.GetOrderBookAsync(MarketSymbol).Sync(); } /// @@ -78,8 +78,8 @@ public void Update() public Trade Trade { get; set; } /// - /// The current symbol being traded + /// The current market symbol being traded /// - public string Symbol { get; set; } + public string MarketSymbol { get; set; } } } diff --git a/ExchangeSharp/Model/ExchangeTransaction.cs b/ExchangeSharp/Model/ExchangeTransaction.cs index 2ae506bd..d4bf96e1 100644 --- a/ExchangeSharp/Model/ExchangeTransaction.cs +++ b/ExchangeSharp/Model/ExchangeTransaction.cs @@ -44,13 +44,13 @@ public sealed class ExchangeTransaction /// The fee on the transaction public decimal TxFee { get; set; } - /// The currency symbol (ex. BTC) - public string Symbol { get; set; } + /// The currency name (ex. BTC) + public string Currency { get; set; } public override string ToString() { return - $"{Amount} {Symbol} (fee: {TxFee}) sent to Address: {Address ?? "null"} with AddressTag: {AddressTag ?? "null"} BlockchainTxId: {BlockchainTxId ?? "null"} sent at {Timestamp} UTC. Status: {Status}. Exchange paymentId: {PaymentId ?? "null"}. Notes: {Notes ?? "null"}"; + $"{Amount} {Currency} (fee: {TxFee}) sent to Address: {Address ?? "null"} with AddressTag: {AddressTag ?? "null"} BlockchainTxId: {BlockchainTxId ?? "null"} sent at {Timestamp} UTC. Status: {Status}. Exchange paymentId: {PaymentId ?? "null"}. Notes: {Notes ?? "null"}"; } } } \ No newline at end of file diff --git a/ExchangeSharp/Model/MarketCandle.cs b/ExchangeSharp/Model/MarketCandle.cs index 19c86082..684b8cae 100644 --- a/ExchangeSharp/Model/MarketCandle.cs +++ b/ExchangeSharp/Model/MarketCandle.cs @@ -66,12 +66,12 @@ public class MarketCandle /// /// Base currency volume (i.e. in BTC-USD, this would be BTC volume) /// - public double BaseVolume { get; set; } + public double BaseCurrencyVolume { get; set; } /// - /// Conversion currency volume (i.e. in BTC-USD, this would be USD volume) + /// Quote currency volume (i.e. in BTC-USD, this would be USD volume) /// - public double ConvertedVolume { get; set; } + public double QuoteCurrencyVolume { get; set; } /// /// The weighted average price if provided @@ -84,7 +84,7 @@ public class MarketCandle /// String public override string ToString() { - return string.Format("{0}/{1}: {2}, {3}, {4}, {5}, {6}, {7}, {8}", Timestamp, PeriodSeconds, OpenPrice, HighPrice, LowPrice, ClosePrice, BaseVolume, ConvertedVolume, WeightedAverage); + return string.Format("{0}/{1}: {2}, {3}, {4}, {5}, {6}, {7}, {8}", Timestamp, PeriodSeconds, OpenPrice, HighPrice, LowPrice, ClosePrice, BaseCurrencyVolume, QuoteCurrencyVolume, WeightedAverage); } } } diff --git a/ExchangeSharp/Traders/Trader.cs b/ExchangeSharp/Traders/Trader.cs index 14538a01..c625ff25 100644 --- a/ExchangeSharp/Traders/Trader.cs +++ b/ExchangeSharp/Traders/Trader.cs @@ -82,7 +82,7 @@ private void UpdateAmounts() if (ProductionMode) { var dict = TradeInfo.ExchangeInfo.API.GetAmountsAvailableToTradeAsync().Sync(); - string[] tradeSymbols = TradeInfo.Symbol.Split('_'); + string[] tradeSymbols = TradeInfo.MarketSymbol.Split('_'); dict.TryGetValue(tradeSymbols[1], out decimal itemCount); dict.TryGetValue(tradeSymbols[0], out decimal cashFlow); ItemCount = itemCount; @@ -165,7 +165,7 @@ public decimal PerformBuy(decimal count = -1) IsBuy = true, Price = actualBuyPrice, ShouldRoundAmount = false, - Symbol = TradeInfo.Symbol + MarketSymbol = TradeInfo.MarketSymbol }).Sync(); } else @@ -199,7 +199,7 @@ public decimal PerformSell(decimal count = -1) IsBuy = false, Price = actualSellPrice, ShouldRoundAmount = false, - Symbol = TradeInfo.Symbol + MarketSymbol = TradeInfo.MarketSymbol }).Sync(); } else diff --git a/ExchangeSharp/Traders/TraderExchangeExport.cs b/ExchangeSharp/Traders/TraderExchangeExport.cs index 301293dc..d95ca61a 100644 --- a/ExchangeSharp/Traders/TraderExchangeExport.cs +++ b/ExchangeSharp/Traders/TraderExchangeExport.cs @@ -25,13 +25,13 @@ public static class TraderExchangeExport /// Export exchange data to csv and then to optimized bin files /// /// Exchange api, null to just convert existing csv files - /// Symbol to export + /// Market symbol to export /// Base path to export to, should not contain symbol, symbol will be appended /// Start date to begin export at /// Callback if api is not null to notify of progress - public static void ExportExchangeTrades(IExchangeAPI api, string symbol, string basePath, DateTime sinceDateTime, Action callback = null) + public static void ExportExchangeTrades(IExchangeAPI api, string marketSymbol, string basePath, DateTime sinceDateTime, Action callback = null) { - basePath = Path.Combine(basePath, symbol); + basePath = Path.Combine(basePath, marketSymbol); Directory.CreateDirectory(basePath); sinceDateTime = sinceDateTime.ToUniversalTime(); if (api != null) @@ -62,7 +62,7 @@ bool innerCallback(IEnumerable trades) } return true; } - api.GetHistoricalTradesAsync(innerCallback, symbol, sinceDateTime).Sync(); + api.GetHistoricalTradesAsync(innerCallback, marketSymbol, sinceDateTime).Sync(); writer.Close(); callback?.Invoke(count); } diff --git a/ExchangeSharp/Utility/CryptoUtility.cs b/ExchangeSharp/Utility/CryptoUtility.cs index 38677dc6..7df880c0 100644 --- a/ExchangeSharp/Utility/CryptoUtility.cs +++ b/ExchangeSharp/Utility/CryptoUtility.cs @@ -226,6 +226,7 @@ public static T ConvertInvariant(this object obj, T defaultValue = default) { // fallback to float conversion, i.e. 1E-1 for a decimal conversion will fail string stringValue = (jValue == null ? obj.ToStringInvariant() : jValue.Value.ToStringInvariant()); + if (string.IsNullOrWhiteSpace(stringValue)) return defaultValue; decimal decimalValue = decimal.Parse(stringValue, System.Globalization.NumberStyles.Float); return (T)Convert.ChangeType(decimalValue, typeof(T), CultureInfo.InvariantCulture); } diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Example.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Example.cs index 294d5c71..b8778c68 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Example.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Example.cs @@ -36,7 +36,7 @@ public static void RunExample(Dictionary dict) Amount = 0.01m, IsBuy = true, Price = ticker.Ask, - Symbol = "XXBTZUSD" + MarketSymbol = "XXBTZUSD" }).Sync(); // Kraken is a bit funny in that they don't return the order details in the initial request, so you have to follow up with an order details request @@ -49,32 +49,32 @@ public static void RunExample(Dictionary dict) Console.WriteLine("Placed an order on Kraken for 0.01 bitcoin at {0} USD. Status is {1}. Order id is {2}.", ticker.Ask, result.Result, result.OrderId); } - private static string[] GetSymbols(Dictionary dict, bool required = true) + private static string[] GetMarketSymbols(Dictionary dict, bool required = true) { if(required) - RequireArgs(dict, "symbols"); - if ((!dict.ContainsKey("symbols") && !required) || dict["symbols"] == "*") + RequireArgs(dict, "marketSymbols"); + if ((!dict.ContainsKey("marketSymbols") && !required) || dict["marketSymbols"] == "*") { return null; } - return dict["symbols"].Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + return dict["marketSymbols"].Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); } - private static string[] ValidateSymbols(IExchangeAPI api, string[] symbols) + private static string[] ValidateMarketSymbols(IExchangeAPI api, string[] marketSymbols) { - string[] apiSymbols = api.GetSymbolsAsync().Sync().ToArray(); - if (symbols == null || symbols.Length == 0) + string[] apiSymbols = api.GetMarketSymbolsAsync().Sync().ToArray(); + if (marketSymbols == null || marketSymbols.Length == 0) { - symbols = apiSymbols; + marketSymbols = apiSymbols; } - foreach (string symbol in symbols) + foreach (string marketSymbol in marketSymbols) { - if (!apiSymbols.Contains(symbol)) + if (!apiSymbols.Contains(marketSymbol)) { - throw new ArgumentException(string.Format("Symbol {0} does not exist in API {1}, valid symbols: {2}", symbol, api.Name, string.Join(",", apiSymbols.OrderBy(s => s)))); + throw new ArgumentException(string.Format("Symbol {0} does not exist in API {1}, valid symbols: {2}", marketSymbol, api.Name, string.Join(",", apiSymbols.OrderBy(s => s)))); } } - return symbols; + return marketSymbols; } private static void SetWebSocketEvents(IWebSocket socket) @@ -118,11 +118,11 @@ private static void RunWebSocket(Dictionary dict, Func dict) { - string[] symbols = GetSymbols(dict, false); + string[] symbols = GetMarketSymbols(dict, false); RunWebSocket(dict, (api) => { if(symbols != null) - symbols = ValidateSymbols(api, symbols); + symbols = ValidateMarketSymbols(api, symbols); return api.GetTickersWebSocket( freshTickers => { @@ -138,10 +138,10 @@ private static void RunWebSocketTickers(Dictionary dict) private static void RunTradesWebSocket(Dictionary dict) { - string[] symbols = GetSymbols(dict); + string[] symbols = GetMarketSymbols(dict); RunWebSocket(dict, (api) => { - symbols = ValidateSymbols(api, symbols); + symbols = ValidateMarketSymbols(api, symbols); return api.GetTradesWebSocket(message => { Console.WriteLine($"{message.Key}: {message.Value}"); @@ -151,16 +151,16 @@ private static void RunTradesWebSocket(Dictionary dict) private static void RunOrderBookWebSocket(Dictionary dict) { - string[] symbols = GetSymbols(dict); + string[] symbols = GetMarketSymbols(dict); RunWebSocket(dict, (api) => { - symbols = ValidateSymbols(api, symbols); + symbols = ValidateMarketSymbols(api, symbols); return ExchangeAPIExtensions.GetFullOrderBookWebSocket(api, message => { //print the top bid and ask with amount var topBid = message.Bids.FirstOrDefault(); var topAsk = message.Asks.FirstOrDefault(); - Console.WriteLine($"[{message.Symbol}:{message.SequenceId}] {topBid.Value.Price} ({topBid.Value.Amount}) | {topAsk.Value.Price} ({topAsk.Value.Amount})"); + Console.WriteLine($"[{message.MarketSymbol}:{message.SequenceId}] {topBid.Value.Price} ({topBid.Value.Amount}) | {topAsk.Value.Price} ({topAsk.Value.Amount})"); }, symbols: symbols); }); } @@ -186,5 +186,135 @@ public static void RunProcessEncryptedAPIKeys(Dictionary dict) throw new ArgumentException("Invalid mode: " + dict["mode"]); } } + + public static void RunGetSymbolsMetadata(Dictionary dict) + { + RequireArgs(dict, "exchangeName"); + using (var api = ExchangeAPI.GetExchangeAPI(dict["exchangeName"])) + { + if (api == null) + { + throw new ArgumentException("Cannot find exchange with name {0}", dict["exchangeName"]); + } + + try + { + var marketSymbols = api.GetMarketSymbolsMetadataAsync().Sync(); + + foreach (var marketSymbol in marketSymbols) + { + Console.WriteLine(marketSymbol); + } + + Console.WriteLine("Press any key to quit."); + Console.ReadKey(); + } + catch (Exception ex) + { + Logger.Error(ex); + } + } + } + + public static void RunGetMarketSymbols(Dictionary dict) + { + RequireArgs(dict, "exchangeName"); + using (var api = ExchangeAPI.GetExchangeAPI(dict["exchangeName"])) + { + if (api == null) + { + throw new ArgumentException("Cannot find exchange with name {0}", dict["exchangeName"]); + } + + try + { + var marketSymbols = api.GetMarketSymbolsAsync().Sync(); + + foreach (var marketSymbol in marketSymbols) + { + Console.WriteLine(marketSymbol); + } + + Console.WriteLine("Press any key to quit."); + Console.ReadKey(); + } + catch (Exception ex) + { + Logger.Error(ex); + } + } + } + + public static void RunGetTickers(Dictionary dict) + { + RequireArgs(dict, "exchangeName"); + using (var api = ExchangeAPI.GetExchangeAPI(dict["exchangeName"])) + { + if (api == null) + { + throw new ArgumentException("Cannot find exchange with name {0}", dict["exchangeName"]); + } + + try + { + IEnumerable> tickers; + if (dict.ContainsKey("marketSymbol")) + { + var marketSymbol = dict["marketSymbol"]; + var ticker = api.GetTickerAsync(marketSymbol).Sync(); + tickers = new List>() + { + new KeyValuePair(marketSymbol, ticker) + }; + } + else + { + tickers = api.GetTickersAsync().Sync(); + } + + foreach (var ticker in tickers) + { + Console.WriteLine(ticker); + } + + Console.WriteLine("Press any key to quit."); + Console.ReadKey(); + } + catch (Exception ex) + { + Logger.Error(ex); + } + } + } + + public static void RunGetCandles(Dictionary dict) + { + RequireArgs(dict, "exchangeName", "marketSymbol"); + using (var api = ExchangeAPI.GetExchangeAPI(dict["exchangeName"])) + { + if (api == null) + { + throw new ArgumentException("Cannot find exchange with name {0}", dict["exchangeName"]); + } + + try + { + var marketSymbol = dict["marketSymbol"]; + var candles = api.GetCandlesAsync(marketSymbol, 1800, DateTime.UtcNow.AddDays(-12), DateTime.UtcNow).Sync(); + + foreach (var candle in candles) + { + Console.WriteLine(candle); + } + + Console.WriteLine("Press any key to quit."); + Console.ReadKey(); + } + catch (Exception ex) + { + Logger.Error(ex); + } + } + } } } diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_ExchangeTests.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_ExchangeTests.cs index 14e74676..4cd56485 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_ExchangeTests.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_ExchangeTests.cs @@ -87,14 +87,14 @@ bool histTradeCallback(IEnumerable tradeEnum) // test all public API for each exchange try { - string symbol = api.NormalizeSymbol(GetSymbol(api)); + string marketSymbol = api.NormalizeMarketSymbol(GetSymbol(api)); if (functionRegex == null || Regex.IsMatch("symbol", functionRegex, RegexOptions.IgnoreCase)) { Console.Write("Test {0} GetSymbolsAsync... ", api.Name); - IReadOnlyCollection symbols = api.GetSymbolsAsync().Sync().ToArray(); - Assert(symbols != null && symbols.Count != 0 && symbols.Contains(symbol, StringComparer.OrdinalIgnoreCase)); - Console.WriteLine($"OK (default: {symbol}; {symbols.Count} symbols)"); + IReadOnlyCollection symbols = api.GetMarketSymbolsAsync().Sync().ToArray(); + Assert(symbols != null && symbols.Count != 0 && symbols.Contains(marketSymbol, StringComparer.OrdinalIgnoreCase)); + Console.WriteLine($"OK (default: {marketSymbol}; {symbols.Count} symbols)"); } if (functionRegex == null || Regex.IsMatch("orderbook", functionRegex, RegexOptions.IgnoreCase)) @@ -102,7 +102,7 @@ bool histTradeCallback(IEnumerable tradeEnum) try { Console.Write("Test {0} GetOrderBookAsync... ", api.Name); - var book = api.GetOrderBookAsync(symbol).Sync(); + var book = api.GetOrderBookAsync(marketSymbol).Sync(); Assert(book.Asks.Count != 0 && book.Bids.Count != 0 && book.Asks.First().Value.Amount > 0m && book.Asks.First().Value.Price > 0m && book.Bids.First().Value.Amount > 0m && book.Bids.First().Value.Price > 0m); Console.WriteLine($"OK ({book.Asks.Count} asks, {book.Bids.Count} bids)"); @@ -118,9 +118,9 @@ bool histTradeCallback(IEnumerable tradeEnum) try { Console.Write("Test {0} GetTickerAsync... ", api.Name); - var ticker = api.GetTickerAsync(symbol).Sync(); + var ticker = api.GetTickerAsync(marketSymbol).Sync(); Assert(ticker != null && ticker.Ask > 0m && ticker.Bid > 0m && ticker.Last > 0m && - ticker.Volume != null && ticker.Volume.BaseVolume > 0m && ticker.Volume.ConvertedVolume > 0m); + ticker.Volume != null && ticker.Volume.QuoteCurrencyVolume > 0m && ticker.Volume.BaseCurrencyVolume > 0m); Console.WriteLine($"OK (ask: {ticker.Ask}, bid: {ticker.Bid}, last: {ticker.Last})"); } catch @@ -134,12 +134,12 @@ bool histTradeCallback(IEnumerable tradeEnum) try { Console.Write("Test {0} GetHistoricalTradesAsync... ", api.Name); - api.GetHistoricalTradesAsync(histTradeCallback, symbol).Sync(); + api.GetHistoricalTradesAsync(histTradeCallback, marketSymbol).Sync(); Assert(trades.Length != 0 && trades[0].Price > 0m && trades[0].Amount > 0m); Console.WriteLine($"OK ({trades.Length})"); Console.Write("Test {0} GetRecentTradesAsync... ", api.Name); - trades = api.GetRecentTradesAsync(symbol).Sync().ToArray(); + trades = api.GetRecentTradesAsync(marketSymbol).Sync().ToArray(); Assert(trades.Length != 0 && trades[0].Price > 0m && trades[0].Amount > 0m); Console.WriteLine($"OK ({trades.Length} trades)"); } @@ -154,11 +154,11 @@ bool histTradeCallback(IEnumerable tradeEnum) try { Console.Write("Test {0} GetCandlesAsync... ", api.Name); - var candles = api.GetCandlesAsync(symbol, 86400, CryptoUtility.UtcNow.Subtract(TimeSpan.FromDays(7.0)), null).Sync().ToArray(); + var candles = api.GetCandlesAsync(marketSymbol, 86400, CryptoUtility.UtcNow.Subtract(TimeSpan.FromDays(7.0)), null).Sync().ToArray(); Assert(candles.Length != 0 && candles[0].ClosePrice > 0m && candles[0].HighPrice > 0m && candles[0].LowPrice > 0m && candles[0].OpenPrice > 0m && candles[0].HighPrice >= candles[0].LowPrice && candles[0].HighPrice >= candles[0].ClosePrice && candles[0].HighPrice >= candles[0].OpenPrice && - !string.IsNullOrWhiteSpace(candles[0].Name) && candles[0].ExchangeName == api.Name && candles[0].PeriodSeconds == 86400 && candles[0].BaseVolume > 0.0 && - candles[0].ConvertedVolume > 0.0 && candles[0].WeightedAverage >= 0m); + !string.IsNullOrWhiteSpace(candles[0].Name) && candles[0].ExchangeName == api.Name && candles[0].PeriodSeconds == 86400 && candles[0].BaseCurrencyVolume > 0.0 && + candles[0].QuoteCurrencyVolume > 0.0 && candles[0].WeightedAverage >= 0m); Console.WriteLine($"OK ({candles.Length})"); } diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Export.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Export.cs index a269b5d0..905957e0 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Export.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Export.cs @@ -20,11 +20,11 @@ public static partial class ExchangeSharpConsoleMain { public static void RunGetHistoricalTrades(Dictionary dict) { - RequireArgs(dict, "exchangeName", "symbol"); + RequireArgs(dict, "exchangeName", "marketSymbol"); string exchangeName = dict["exchangeName"]; IExchangeAPI api = ExchangeAPI.GetExchangeAPI(exchangeName); - string symbol = dict["symbol"]; + string marketSymbol = dict["marketSymbol"]; Console.WriteLine("Showing historical trades for exchange {0}...", exchangeName); DateTime? startDate = null; DateTime? endDate = null; @@ -43,7 +43,7 @@ public static void RunGetHistoricalTrades(Dictionary dict) Console.WriteLine("Trade at timestamp {0}: {1}/{2}/{3}", trade.Timestamp.ToLocalTime(), trade.Id, trade.Price, trade.Amount); } return true; - }, symbol, startDate, endDate).Sync(); + }, marketSymbol, startDate, endDate).Sync(); } public static void RunExportData(Dictionary dict) diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Orders.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Orders.cs index 550c2cf4..3f1c4945 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Orders.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Orders.cs @@ -12,20 +12,13 @@ public static partial class ExchangeSharpConsoleMain { public static void RunGetOrderHistory(Dictionary dict) { - RequireArgs(dict, "exchangeName", "symbol"); + RequireArgs(dict, "exchangeName", "marketSymbol"); string exchangeName = dict["exchangeName"]; IExchangeAPI api = ExchangeAPI.GetExchangeAPI(exchangeName); - string symbol = dict["symbol"]; + string marketSymbol = dict["marketSymbol"]; - Console.Write("Enter Public Api Key: "); - var publicApiKey = GetSecureInput(); - api.PublicApiKey = publicApiKey; - Console.WriteLine(); - Console.Write("Enter Private Api Key: "); - var privateApiKey = GetSecureInput(); - api.PrivateApiKey = privateApiKey; - Console.WriteLine(); + Authenticate(api); DateTime? startDate = null; if (dict.ContainsKey("startDate")) @@ -33,7 +26,7 @@ public static void RunGetOrderHistory(Dictionary dict) startDate = DateTime.Parse(dict["startDate"]).ToUniversalTime(); } - var completedOrders = api.GetCompletedOrderDetailsAsync(symbol, startDate).Sync(); + var completedOrders = api.GetCompletedOrderDetailsAsync(marketSymbol, startDate).Sync(); foreach (var completedOrder in completedOrders) { Console.WriteLine(completedOrder); @@ -43,6 +36,45 @@ public static void RunGetOrderHistory(Dictionary dict) Console.ReadLine(); } + public static void RunGetOrderDetails(Dictionary dict) + { + RequireArgs(dict, "exchangeName", "orderId"); + + string exchangeName = dict["exchangeName"]; + IExchangeAPI api = ExchangeAPI.GetExchangeAPI(exchangeName); + string orderId = dict["orderId"]; + + Authenticate(api); + + string marketSymbol = null; + if (dict.ContainsKey("marketSymbol")) + { + marketSymbol = dict["marketSymbol"]; + } + + var orderDetails = api.GetOrderDetailsAsync(orderId, marketSymbol).Sync(); + Console.WriteLine(orderDetails); + + Console.Write("Press enter to exit.."); + Console.ReadLine(); + } + + private static void Authenticate(IExchangeAPI api) + { + Console.Write("Enter Public Api Key: "); + var publicApiKey = GetSecureInput(); + api.PublicApiKey = publicApiKey; + Console.WriteLine(); + Console.Write("Enter Private Api Key: "); + var privateApiKey = GetSecureInput(); + api.PrivateApiKey = privateApiKey; + Console.WriteLine(); + Console.Write("Enter Passphrase: "); + var passphrase = GetSecureInput(); + api.Passphrase = passphrase; + Console.WriteLine(); + } + private static SecureString GetSecureInput() { var pwd = new SecureString(); diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Stats.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Stats.cs index b9e7ab35..1dd1782e 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Stats.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Stats.cs @@ -22,8 +22,8 @@ public static partial class ExchangeSharpConsoleMain { public static void RunShowExchangeStats(Dictionary dict) { - string symbol = "BTC-USD"; - string symbol2 = "XXBTZUSD"; + string marketSymbol = "BTC-USD"; + string marketSymbol2 = "XXBTZUSD"; IExchangeAPI apiCoinbase = new ExchangeCoinbaseAPI(); IExchangeAPI apiGemini = new ExchangeGeminiAPI(); IExchangeAPI apiKraken = new ExchangeKrakenAPI(); @@ -31,39 +31,39 @@ public static void RunShowExchangeStats(Dictionary dict) while (true) { - ExchangeTicker ticker = apiCoinbase.GetTickerAsync(symbol).Sync(); - ExchangeOrderBook orders = apiCoinbase.GetOrderBookAsync(symbol).Sync(); + ExchangeTicker ticker = apiCoinbase.GetTickerAsync(marketSymbol).Sync(); + ExchangeOrderBook orders = apiCoinbase.GetOrderBookAsync(marketSymbol).Sync(); decimal askAmountSum = orders.Asks.Values.Sum(o => o.Amount); decimal askPriceSum = orders.Asks.Values.Sum(o => o.Price); decimal bidAmountSum = orders.Bids.Values.Sum(o => o.Amount); decimal bidPriceSum = orders.Bids.Values.Sum(o => o.Price); - ExchangeTicker ticker2 = apiGemini.GetTickerAsync(symbol).Sync(); - ExchangeOrderBook orders2 = apiGemini.GetOrderBookAsync(symbol).Sync(); + ExchangeTicker ticker2 = apiGemini.GetTickerAsync(marketSymbol).Sync(); + ExchangeOrderBook orders2 = apiGemini.GetOrderBookAsync(marketSymbol).Sync(); decimal askAmountSum2 = orders2.Asks.Values.Sum(o => o.Amount); decimal askPriceSum2 = orders2.Asks.Values.Sum(o => o.Price); decimal bidAmountSum2 = orders2.Bids.Values.Sum(o => o.Amount); decimal bidPriceSum2 = orders2.Bids.Values.Sum(o => o.Price); - ExchangeTicker ticker3 = apiKraken.GetTickerAsync(symbol2).Sync(); - ExchangeOrderBook orders3 = apiKraken.GetOrderBookAsync(symbol2).Sync(); + ExchangeTicker ticker3 = apiKraken.GetTickerAsync(marketSymbol2).Sync(); + ExchangeOrderBook orders3 = apiKraken.GetOrderBookAsync(marketSymbol2).Sync(); decimal askAmountSum3 = orders3.Asks.Values.Sum(o => o.Amount); decimal askPriceSum3 = orders3.Asks.Values.Sum(o => o.Price); decimal bidAmountSum3 = orders3.Bids.Values.Sum(o => o.Amount); decimal bidPriceSum3 = orders3.Bids.Values.Sum(o => o.Price); - ExchangeTicker ticker4 = apiBitfinex.GetTickerAsync(symbol).Sync(); - ExchangeOrderBook orders4 = apiBitfinex.GetOrderBookAsync(symbol).Sync(); + ExchangeTicker ticker4 = apiBitfinex.GetTickerAsync(marketSymbol).Sync(); + ExchangeOrderBook orders4 = apiBitfinex.GetOrderBookAsync(marketSymbol).Sync(); decimal askAmountSum4 = orders4.Asks.Values.Sum(o => o.Amount); decimal askPriceSum4 = orders4.Asks.Values.Sum(o => o.Price); decimal bidAmountSum4 = orders4.Bids.Values.Sum(o => o.Amount); decimal bidPriceSum4 = orders4.Bids.Values.Sum(o => o.Price); Console.Clear(); - Console.WriteLine("GDAX: {0:0.00}, {1:0.00}, {2:0.00}, {3:0.00}, {4:0.00}, {5:0.00}", ticker.Last, ticker.Volume.BaseVolume, askAmountSum, askPriceSum, bidAmountSum, bidPriceSum); - Console.WriteLine("GEMI: {0:0.00}, {1:0.00}, {2:0.00}, {3:0.00}, {4:0.00}, {5:0.00}", ticker2.Last, ticker2.Volume.BaseVolume, askAmountSum2, askPriceSum2, bidAmountSum2, bidPriceSum2); - Console.WriteLine("KRAK: {0:0.00}, {1:0.00}, {2:0.00}, {3:0.00}, {4:0.00}, {5:0.00}", ticker3.Last, ticker3.Volume.BaseVolume, askAmountSum3, askPriceSum3, bidAmountSum3, bidPriceSum3); - Console.WriteLine("BITF: {0:0.00}, {1:0.00}, {2:0.00}, {3:0.00}, {4:0.00}, {5:0.00}", ticker4.Last, ticker4.Volume.BaseVolume, askAmountSum4, askPriceSum4, bidAmountSum4, bidPriceSum4); + Console.WriteLine("GDAX: {0:0.00}, {1:0.00}, {2:0.00}, {3:0.00}, {4:0.00}, {5:0.00}", ticker.Last, ticker.Volume.QuoteCurrencyVolume, askAmountSum, askPriceSum, bidAmountSum, bidPriceSum); + Console.WriteLine("GEMI: {0:0.00}, {1:0.00}, {2:0.00}, {3:0.00}, {4:0.00}, {5:0.00}", ticker2.Last, ticker2.Volume.QuoteCurrencyVolume, askAmountSum2, askPriceSum2, bidAmountSum2, bidPriceSum2); + Console.WriteLine("KRAK: {0:0.00}, {1:0.00}, {2:0.00}, {3:0.00}, {4:0.00}, {5:0.00}", ticker3.Last, ticker3.Volume.QuoteCurrencyVolume, askAmountSum3, askPriceSum3, bidAmountSum3, bidPriceSum3); + Console.WriteLine("BITF: {0:0.00}, {1:0.00}, {2:0.00}, {3:0.00}, {4:0.00}, {5:0.00}", ticker4.Last, ticker4.Volume.QuoteCurrencyVolume, askAmountSum4, askPriceSum4, bidAmountSum4, bidPriceSum4); Thread.Sleep(5000); } } diff --git a/ExchangeSharpConsole/ExchangeSharpConsole_Main.cs b/ExchangeSharpConsole/ExchangeSharpConsole_Main.cs index 655667c1..cb9cab9c 100644 --- a/ExchangeSharpConsole/ExchangeSharpConsole_Main.cs +++ b/ExchangeSharpConsole/ExchangeSharpConsole_Main.cs @@ -128,6 +128,26 @@ public static int ConsoleMain(string[] args) { RunGetOrderHistory(argsDictionary); } + else if (argsDictionary.ContainsKey("getOrderDetails")) + { + RunGetOrderDetails(argsDictionary); + } + else if (argsDictionary.ContainsKey("symbols-metadata")) + { + RunGetSymbolsMetadata(argsDictionary); + } + else if (argsDictionary.ContainsKey("symbols")) + { + RunGetMarketSymbols(argsDictionary); + } + else if (argsDictionary.ContainsKey("tickers")) + { + RunGetTickers(argsDictionary); + } + else if (argsDictionary.ContainsKey("candles")) + { + RunGetCandles(argsDictionary); + } else { Logger.Error("Unrecognized command line arguments."); diff --git a/ExchangeSharpTests/ExchangeBinanceAPITests.cs b/ExchangeSharpTests/ExchangeBinanceAPITests.cs index e4e0f0fa..dc59ccbc 100644 --- a/ExchangeSharpTests/ExchangeBinanceAPITests.cs +++ b/ExchangeSharpTests/ExchangeBinanceAPITests.cs @@ -129,7 +129,7 @@ private static void ValidateDiff(MarketDepthDiffUpdate diff) { diff.EventType.Should().Be("depthUpdate"); diff.EventTime.Should().Be(123456789); - diff.Symbol.Should().Be("BNBBTC"); + diff.MarketSymbol.Should().Be("BNBBTC"); diff.FirstUpdate.Should().Be(157); diff.FinalUpdate.Should().Be(160); diff.Bids[0][0].Should().Be("0.0024"); diff --git a/ExchangeSharpTests/ExchangePoloniexAPITests.cs b/ExchangeSharpTests/ExchangePoloniexAPITests.cs index eb3b8e5b..02fa6c1b 100644 --- a/ExchangeSharpTests/ExchangePoloniexAPITests.cs +++ b/ExchangeSharpTests/ExchangePoloniexAPITests.cs @@ -112,7 +112,7 @@ public void ReturnOrderTrades_Sell_HasCorrectValues() order.OrderId.Should().BeNullOrEmpty(); order.AveragePrice.Should().Be(0.0001716132563851949140701411m); order.Price.Should().Be(order.AveragePrice); - order.Symbol.Should().Be("BTC_VIA"); + order.MarketSymbol.Should().Be("BTC_VIA"); } [TestMethod] @@ -128,7 +128,7 @@ public void ReturnOrderTrades_Buy_HasCorrectValues() order.IsBuy.Should().BeTrue(); order.Fees.Should().Be(28.64470495m); order.FeesCurrency.Should().Be("XEM"); - order.Symbol.Should().Be("BTC_XEM"); + order.MarketSymbol.Should().Be("BTC_XEM"); order.Price.Should().Be(0.00005128m); order.AveragePrice.Should().Be(0.00005128m); } @@ -241,7 +241,7 @@ public void GetOrderDetails_HappyPath() order.IsBuy.Should().BeTrue(); order.Fees.Should().Be(28.64470495m); order.FeesCurrency.Should().Be("XEM"); - order.Symbol.Should().Be("BTC_XEM"); + order.MarketSymbol.Should().Be("BTC_XEM"); order.Price.Should().Be(0.00005128m); order.AveragePrice.Should().Be(0.00005128m); order.Result.Should().Be(ExchangeAPIOrderResult.Filled); @@ -293,7 +293,7 @@ public void GetCompletedOrderDetails_AllSymbols() // {"BTC_MAID": [ { "globalTradeID": 29251512, "tradeID": "1385888", "date": "2016-05-03 01:29:55", "rate": "0.00014243", "amount": "353.74692925", "total": "0.05038417", "fee": "0.00200000", "orderNumber": "12603322113", "type": "buy", "category": "settlement" }, { "globalTradeID": 29251511, "tradeID": "1385887", "date": "2016-05-03 01:29:55", "rate": "0.00014111", "amount": "311.24262497", "total": "0.04391944", "fee": "0.00200000", "orderNumber": "12603319116", "type": "sell", "category": "marginTrade" } var polo = CreatePoloniexAPI(GetCompletedOrderDetails_AllSymbolsOrders); ExchangeOrderResult order = polo.GetCompletedOrderDetailsAsync().Sync().First(); - order.Symbol.Should().Be("BTC_MAID"); + order.MarketSymbol.Should().Be("BTC_MAID"); order.OrderId.Should().Be("12603322113"); order.OrderDate.Should().Be(new DateTime(2016, 5, 3, 1, 29, 55)); order.AveragePrice.Should().Be(0.00014243m); diff --git a/ExchangeSharpTests/ExchangeTests.cs b/ExchangeSharpTests/ExchangeTests.cs index 708d14b8..8e36f3ad 100644 --- a/ExchangeSharpTests/ExchangeTests.cs +++ b/ExchangeSharpTests/ExchangeTests.cs @@ -37,7 +37,7 @@ private string GetAllSymbolsJson() Dictionary allSymbols = new Dictionary(); foreach (IExchangeAPI api in ExchangeAPI.GetExchangeAPIs()) { - allSymbols[api.Name] = api.GetSymbolsAsync().Sync().ToArray(); + allSymbols[api.Name] = api.GetMarketSymbolsAsync().Sync().ToArray(); } return JsonConvert.SerializeObject(allSymbols); } @@ -45,8 +45,8 @@ private string GetAllSymbolsJson() [TestMethod] public void GlobalSymbolTest() { - string globalSymbol = "BTC-ETH"; - string globalSymbolAlt = "KRW-BTC"; // WTF Bitthumb... + string globalMarketSymbol = "BTC-ETH"; + string globalMarketSymbolAlt = "KRW-BTC"; // WTF Bitthumb... Dictionary allSymbols = JsonConvert.DeserializeObject>(Resources.AllSymbolsJson); // sanity test that all exchanges return the same global symbol when converted back and forth @@ -55,11 +55,11 @@ public void GlobalSymbolTest() try { bool isBithumb = (api.Name == ExchangeName.Bithumb); - string exchangeSymbol = api.GlobalSymbolToExchangeSymbol(isBithumb ? globalSymbolAlt : globalSymbol); - string globalSymbol2 = api.ExchangeSymbolToGlobalSymbol(exchangeSymbol); - if ((!isBithumb && globalSymbol2.EndsWith("-BTC")) || - globalSymbol2.EndsWith("-USD") || - globalSymbol2.EndsWith("-USDT")) + string exchangeMarketSymbol = api.GlobalMarketSymbolToExchangeMarketSymbol(isBithumb ? globalMarketSymbolAlt : globalMarketSymbol); + string globalMarketSymbol2 = api.ExchangeMarketSymbolToGlobalMarketSymbol(exchangeMarketSymbol); + if ((!isBithumb && globalMarketSymbol2.EndsWith("-BTC")) || + globalMarketSymbol2.EndsWith("-USD") || + globalMarketSymbol2.EndsWith("-USDT")) { Assert.Fail($"Exchange {api.Name} has wrong SymbolIsReversed parameter"); } @@ -71,13 +71,13 @@ public void GlobalSymbolTest() "then apply this new string to Resources.AllSymbolsJson"); } string[] symbols = allSymbols[api.Name]; - Assert.IsTrue(symbols.Contains(exchangeSymbol), "Symbols does not contain exchange symbol"); + Assert.IsTrue(symbols.Contains(exchangeMarketSymbol), "Symbols does not contain exchange symbol"); } catch { Assert.Fail("Error getting symbols"); } - Assert.IsTrue(globalSymbol == globalSymbol2 || globalSymbolAlt == globalSymbol2); + Assert.IsTrue(globalMarketSymbol == globalMarketSymbol2 || globalMarketSymbolAlt == globalMarketSymbol2); } catch (NotImplementedException) { diff --git a/README.md b/README.md index 18f2cbbf..ee292ecc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ExchangeSharp is a C# console app and framework for trading and communicating wi ### Features - Many exchanges supported with public, private and web socket API - Easy to use and well documented code and API -- Optional global symbol normalization, since each exchange has their own way of doing symbols +- Optional global market symbol normalization, since each exchange has their own way of doing market symbols - Runs anywhere .NET core will run (Windows 8.1 or newer, MAC, Linux, iOS, Android, Unity 2018+, etc.) ### Exchanges @@ -41,7 +41,7 @@ The following cryptocurrency services are supported: - Cryptowatch (partial) ### Notes -ExchangeSharp uses 'symbol' to refer to markets, or pairs of currencies. +ExchangeSharp uses 'marketSymbol' to refer to markets, or pairs of currencies. Please send pull requests if you have made a change that you feel is worthwhile, want a bug fixed or want a new feature. You can also donate to get new features. @@ -82,7 +82,7 @@ ExchangeOrderResult result = api.PlaceOrderAsync(new ExchangeOrderRequest Amount = 0.01m, IsBuy = true, Price = ticker.Ask, - Symbol = "XXBTZUSD" + MarketSymbol = "XXBTZUSD" }).Sync(); // Kraken is a bit funny in that they don't return the order details in the initial request, so you have to follow up with an order details request