diff --git a/samples/BinanceConsoleApp/Controllers/IntegrationTests.cs b/samples/BinanceConsoleApp/Controllers/IntegrationTests.cs index 259b89e8..b2db31c2 100644 --- a/samples/BinanceConsoleApp/Controllers/IntegrationTests.cs +++ b/samples/BinanceConsoleApp/Controllers/IntegrationTests.cs @@ -19,6 +19,21 @@ public async Task HandleAsync(string command, CancellationToken token = de /////////////////////////////////////////////////////////////////// + var valid = symbol.IsPriceQuantityValid(5000.01m, 0.1m); + var _valid = symbol.IsPriceQuantityValid(50000.01m, 0.1m); + var __valid = symbol.IsPriceQuantityValid(50.01m, 0.1m); + + lock (Program.ConsoleSync) + { + Console.WriteLine(); + Console.WriteLine($"Price/Quantity Valid: {valid}"); + Console.WriteLine($"Price/Quantity Valid: {_valid}"); + Console.WriteLine($"Price/Quantity Valid: {__valid}"); + } + /////////////////////////////////////////////////////////////////// + + + /*///////////////////////////////////////////////////////////////// var price = await Program.Api.GetAvgPriceAsync(symbol, token); lock (Program.ConsoleSync) @@ -26,10 +41,10 @@ public async Task HandleAsync(string command, CancellationToken token = de Console.WriteLine(); Program.Display(price); } - /////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////*/ - /////////////////////////////////////////////////////////////////// + /*///////////////////////////////////////////////////////////////// var aggTrades = (await Program.Api.GetAggregateTradesAsync(symbol, endTime.Subtract(TimeSpan.FromMinutes(1)), endTime, token)) .Reverse().ToArray(); @@ -52,10 +67,10 @@ public async Task HandleAsync(string command, CancellationToken token = de } } } - /////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////*/ - /////////////////////////////////////////////////////////////////// + /*///////////////////////////////////////////////////////////////// var trades = (await Program.Api.GetAccountTradesAsync(Program.User, symbol, endTime.Subtract(TimeSpan.FromHours(24)), endTime, token: token)) .Reverse().ToArray(); @@ -78,10 +93,10 @@ public async Task HandleAsync(string command, CancellationToken token = de } } } - /////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////*/ - /////////////////////////////////////////////////////////////////// + /*///////////////////////////////////////////////////////////////// var orders = await Program.Api .GetOrdersAsync(Program.User, symbol, endTime.Subtract(TimeSpan.FromHours(24)), endTime, token: token); @@ -104,7 +119,7 @@ public async Task HandleAsync(string command, CancellationToken token = de } } } - /////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////*/ return true; diff --git a/src/Binance/Api/BinanceApi.cs b/src/Binance/Api/BinanceApi.cs index 147791b4..742c850e 100644 --- a/src/Binance/Api/BinanceApi.cs +++ b/src/Binance/Api/BinanceApi.cs @@ -419,17 +419,38 @@ public virtual async Task> GetSymbolsAsync(CancellationToken var filters = jToken["filters"]; - var quoteMinPrice = filters[0]["minPrice"].Value(); - var quoteMaxPrice = filters[0]["maxPrice"].Value(); - var quoteIncrement = filters[0]["tickSize"].Value(); - - var baseMinQty = filters[1]["minQty"].Value(); - var baseMaxQty = filters[1]["maxQty"].Value(); - var baseIncrement = filters[1]["stepSize"].Value(); - - var minNotional = filters[2]["minNotional"].Value(); - - var symbol = new Symbol(status, baseAsset, quoteAsset, (baseMinQty, baseMaxQty, baseIncrement), (quoteMinPrice, quoteMaxPrice, quoteIncrement), minNotional, icebergAllowed, orderTypes); + decimal quoteIncrement = 0; + var priceFilter = filters.FirstOrDefault(f => f["filterType"].Value() == "PRICE_FILTER"); + if (priceFilter != null) + { + quoteIncrement = priceFilter["tickSize"].Value(); + } + + decimal multiplierUp = 0, multiplierDown = 0; + var percentFilter = filters.FirstOrDefault(f => f["filterType"].Value() == "PERCENT_PRICE"); + if (percentFilter != null) + { + multiplierUp = percentFilter["multiplierUp"].Value(); + multiplierDown = percentFilter["multiplierDown"].Value(); + } + + decimal baseMinQty = 0, baseMaxQty = 0, baseIncrement = 0; + var quantityFilter = filters.FirstOrDefault(f => f["filterType"].Value() == "LOT_SIZE"); + if (quantityFilter != null) + { + baseMinQty = quantityFilter["minQty"].Value(); + baseMaxQty = quantityFilter["maxQty"].Value(); + baseIncrement = quantityFilter["stepSize"].Value(); + } + + decimal minNotional = 0; + var minNotionalFilter = filters.FirstOrDefault(f => f["filterType"].Value() == "MIN_NOTIONAL"); + if (minNotionalFilter != null) + { + minNotional = minNotionalFilter["minNotional"].Value(); + } + + var symbol = new Symbol(status, baseAsset, quoteAsset, (baseMinQty, baseMaxQty, baseIncrement), new PriceRange(this, jToken["symbol"].Value(), multiplierUp, multiplierDown, quoteIncrement), minNotional, icebergAllowed, orderTypes); if (symbol.ToString() == jToken["symbol"].Value()) return symbol; diff --git a/src/Binance/InclusiveRange.cs b/src/Binance/InclusiveRange.cs index e2d55f5c..91bdd42c 100644 --- a/src/Binance/InclusiveRange.cs +++ b/src/Binance/InclusiveRange.cs @@ -2,19 +2,19 @@ namespace Binance { - public sealed class InclusiveRange + public class InclusiveRange { #region Public Properties /// /// Get the miniumum value. /// - public decimal Minimum { get; } + public virtual decimal Minimum => _minimum; /// /// Get the maximum value. /// - public decimal Maximum { get; } + public virtual decimal Maximum => _maximum; /// /// Get the increment value. @@ -33,6 +33,13 @@ public static implicit operator InclusiveRange((decimal, decimal, decimal) range #endregion Implicit Operators + #region Private Fields + + private decimal _minimum; + private decimal _maximum; + + #endregion Private Fields + #region Constructors /// @@ -43,15 +50,16 @@ public static implicit operator InclusiveRange((decimal, decimal, decimal) range /// public InclusiveRange(decimal minimum, decimal maximum, decimal increment) { - if (minimum <= 0) - throw new ArgumentException($"{nameof(InclusiveRange)}: value must be greater than 0.", nameof(minimum)); - if (maximum <= 0) - throw new ArgumentException($"{nameof(InclusiveRange)}: value must be greater than 0.", nameof(maximum)); + if (minimum < 0) + throw new ArgumentException($"{nameof(InclusiveRange)}: value must not be less than 0.", nameof(minimum)); + if (maximum < 0) + throw new ArgumentException($"{nameof(InclusiveRange)}: value must not be less than 0.", nameof(maximum)); if (increment <= 0) throw new ArgumentException($"{nameof(InclusiveRange)}: value must be greater than 0.", nameof(increment)); - Minimum = minimum; - Maximum = maximum; + _minimum = minimum; + _maximum = maximum; + Increment = increment; } diff --git a/src/Binance/PriceRange.cs b/src/Binance/PriceRange.cs new file mode 100644 index 00000000..6c2393ec --- /dev/null +++ b/src/Binance/PriceRange.cs @@ -0,0 +1,63 @@ +using System; + +namespace Binance +{ + public sealed class PriceRange : InclusiveRange + { + #region Public Properties + + public decimal MultiplierUp { get; } + + public decimal MultiplierDown { get; } + + public override decimal Minimum => Math.Floor(GetAveragePrice() * MultiplierDown / Increment) * Increment; + + public override decimal Maximum => Math.Ceiling(GetAveragePrice() * MultiplierUp / Increment) * Increment; + + #endregion Public Properties + + #region Private Fields + + private IBinanceApi _api; + + private string _symbol; + + private decimal _averagePrice; + + private DateTime _lastUpdate = DateTime.MinValue; + + #endregion Private Fields + + #region Constructor + + public PriceRange(IBinanceApi api, string symbol, decimal multiplierUp, decimal multiplierDown, decimal increment) + : base(0, 0, increment) + { + Throw.IfNull(api, nameof(api)); + Throw.IfNullOrWhiteSpace(symbol, nameof(symbol)); + + _api = api; + _symbol = symbol; + + MultiplierUp = multiplierUp; + MultiplierDown = multiplierDown; + } + + #endregion Constructor + + #region Private Methods + + private decimal GetAveragePrice() + { + if (_averagePrice == 0 || DateTime.UtcNow - _lastUpdate > TimeSpan.FromSeconds(1)) + { + _averagePrice = _api.GetAvgPriceAsync(_symbol).GetAwaiter().GetResult().Value; + _lastUpdate = DateTime.UtcNow; + } + + return _averagePrice; + } + + #endregion Private Methods + } +} diff --git a/test/Binance.Tests/InclusiveRangeTest.cs b/test/Binance.Tests/InclusiveRangeTest.cs index 5113ac72..26e4f50f 100644 --- a/test/Binance.Tests/InclusiveRangeTest.cs +++ b/test/Binance.Tests/InclusiveRangeTest.cs @@ -9,9 +9,7 @@ public class InclusiveRangeTest public void Throws() { Assert.Throws("minimum", () => new InclusiveRange(-1, 1, 1)); - Assert.Throws("minimum", () => new InclusiveRange(0, 1, 1)); Assert.Throws("maximum", () => new InclusiveRange(1, -1, 1)); - Assert.Throws("maximum", () => new InclusiveRange(1, 0, 1)); Assert.Throws("increment", () => new InclusiveRange(1, 1, -1)); Assert.Throws("increment", () => new InclusiveRange(1, 1, 0)); }