using CryptoExchange.Net.Objects; using System; using System.Security.Cryptography; using System.Threading; namespace CryptoExchange.Net { /// /// General helpers functions /// public static class ExchangeHelpers { private const string _allowedRandomChars = "ABCDEFGHIJKLMONOPQRSTUVWXYZabcdefghijklmonopqrstuvwxyz0123456789"; /// /// The last used id, use NextId() to get the next id and up this /// private static int _lastId; /// /// Clamp a value between a min and max /// /// /// /// /// public static decimal ClampValue(decimal min, decimal max, decimal value) { value = Math.Min(max, value); value = Math.Max(min, value); return value; } /// /// Adjust a value to be between the min and max parameters and rounded to the closest step. /// /// The min value /// The max value /// The step size the value should be floored to. For example, value 2.548 with a step size of 0.01 will output 2.54 /// How to round /// The input value /// public static decimal AdjustValueStep(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal value) { if(step == 0) throw new ArgumentException($"0 not allowed for parameter {nameof(step)}, pass in null to ignore the step size", nameof(step)); value = Math.Min(max, value); value = Math.Max(min, value); if (step == null) return value; var offset = value % step.Value; if(roundingType == RoundingType.Down) { value -= offset; } else { if (offset < step / 2) value -= offset; else value += (step.Value - offset); } value = RoundDown(value, 8); return value.Normalize(); } /// /// Adjust a value to be between the min and max parameters and rounded to the closest precision. /// /// The min value /// The max value /// The precision the value should be rounded to. For example, value 2.554215 with a precision of 5 will output 2.5542 /// How to round /// The input value /// public static decimal AdjustValuePrecision(decimal min, decimal max, int? precision, RoundingType roundingType, decimal value) { value = Math.Min(max, value); value = Math.Max(min, value); if (precision == null) return value; return RoundToSignificantDigits(value, precision.Value, roundingType); } /// /// Round a value to have the provided total number of digits. For example, value 253.12332 with 5 digits would be 253.12 /// /// The value to round /// The total amount of digits (NOT decimal places) to round to /// How to round /// public static decimal RoundToSignificantDigits(decimal value, int digits, RoundingType roundingType) { var val = (double)value; if (value == 0) return 0; double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(val))) + 1); if(roundingType == RoundingType.Closest) return (decimal)(scale * Math.Round(val / scale, digits)); else return (decimal)(scale * (double)RoundDown((decimal)(val / scale), digits)); } /// /// Rounds a value down to /// /// /// /// public static decimal RoundDown(decimal i, double decimalPlaces) { var power = Convert.ToDecimal(Math.Pow(10, decimalPlaces)); return Math.Floor(i * power) / power; } /// /// Strips any trailing zero's of a decimal value, useful when converting the value to string. /// /// /// public static decimal Normalize(this decimal value) { return value / 1.000000000000000000000000000000000m; } /// /// Generate a new unique id. The id is staticly stored so it is guarenteed to be unique /// /// public static int NextId() => Interlocked.Increment(ref _lastId); /// /// Return the last unique id that was generated /// /// public static int LastId() => _lastId; /// /// Generate a random string of specified length /// /// Length of the random string /// public static string RandomString(int length) { var randomChars = new char[length]; #if NETSTANDARD2_1_OR_GREATER for (int i = 0; i < length; i++) randomChars[i] = _allowedRandomChars[RandomNumberGenerator.GetInt32(0, _allowedRandomChars.Length)]; #else var random = new Random(); for (int i = 0; i < length; i++) randomChars[i] = _allowedRandomChars[random.Next(0, _allowedRandomChars.Length)]; #endif return new string(randomChars); } /// /// Generate a random string of specified length /// /// The initial string /// Total length of the resulting string /// public static string AppendRandomString(string source, int totalLength) { if (totalLength < source.Length) throw new ArgumentException("Total length smaller than source string length", nameof(totalLength)); if (totalLength == source.Length) return source; return source + RandomString(totalLength - source.Length); } } }