/// <summary> /// Throw an exception if value is not within range or an increment multiple. /// </summary> /// <param name="range"></param> /// <param name="value"></param> public static void Validate(this InclusiveRange range, decimal value, string paramName = null) { Throw.IfNull(range, nameof(range)); if (value < range.Minimum) { throw new ArgumentOutOfRangeException(paramName ?? nameof(value), $"Value ({value}) must be greater than or equal to minimum ({range.Minimum})."); } if (value > range.Maximum) { throw new ArgumentOutOfRangeException(paramName ?? nameof(value), $"Value ({value}) must be less than or equal to maximum ({range.Maximum})."); } if ((value - range.Minimum) % range.Increment > 0) { throw new ArgumentOutOfRangeException(paramName ?? nameof(value), $"Value ({value}) must be a multiple of the increment ({range.Increment})."); } }
/// <summary> /// Get the nearest valid value within range coercing remainders to the closest increment. /// Specify a midpoint rounding behavior for values with remainder equals increment / 2. /// The default behavior rounds midpoint remainders to the nearest even increment. /// /// For example: /// 1.234 => 1.230 given range of [0.01 - 10.00] with increment of 0.01. /// 2.345 => 2.340 given range of [0.01 - 10.00] with increment of 0.01 (midpoint rounding to even). /// 2.345 => 2.350 given range of [0.01 - 10.00] with increment of 0.01 (midpoint rounding away from 0). /// 9.876 => 9.880 given range of [0.01 - 10.00] with increment of 0.01. /// </summary> /// <param name="range"></param> /// <param name="value"></param> /// <returns></returns> public static decimal GetValidValue(this InclusiveRange range, decimal value, MidpointRounding midpointRounding = MidpointRounding.ToEven) { if (value <= range.Minimum) { return(range.Minimum); } if (value >= range.Maximum) { return(range.Maximum); } var remainder = value % range.Increment; if (remainder == 0) { return(value); } var midpoint = range.Increment / 2; var lower = value - remainder; if (remainder < midpoint) { return(lower); } if (remainder > midpoint) { return(lower + range.Increment); } // Otherwise, remainder equals increment / 2... if (midpointRounding == MidpointRounding.AwayFromZero) { return(lower + range.Increment); } // Round to nearest even increment... return((lower % (range.Increment * 2) == 0) ? lower // ...if lower is even. : lower + range.Increment); }
/// <summary> /// Get the nearest valid value within range coercing remainders UP to the next increment. /// /// For example, use this to get a valid quantity given an expected minimum amount: /// 1.234 => 1.240 given range of [0.01 - 10.00] with increment of 0.01. /// </summary> /// <param name="range"></param> /// <param name="value"></param> /// <returns></returns> public static decimal GetUpperValidValue(this InclusiveRange range, decimal value) { if (value <= range.Minimum) { return(range.Minimum); } if (value >= range.Maximum) { return(range.Maximum); } var remainder = value % range.Increment; if (remainder == 0) { return(value); } return(value - value % range.Increment + range.Increment); }
/// <summary> /// Constructor. /// </summary> /// <param name="status">The symbol status.</param> /// <param name="baseAsset">The symbol base asset.</param> /// <param name="quoteAsset">The symbol quote asset.</param> /// <param name="quantity">The minimum, maximum, and incremental quantity values.</param> /// <param name="price">The minimum, maximum, and incremental price values.</param> /// <param name="notionalMinimumValue">The minimum notional value.</param> /// <param name="isIcebergAllowed">The flag indicating if iceberg orders are allowed.</param> /// <param name="orderTypes">The list of allowed order types.</param> public Symbol(SymbolStatus status, Asset baseAsset, Asset quoteAsset, InclusiveRange quantity, InclusiveRange price, decimal notionalMinimumValue, bool isIcebergAllowed, IEnumerable <OrderType> orderTypes) { Throw.IfNull(baseAsset, nameof(baseAsset)); Throw.IfNull(quoteAsset, nameof(quoteAsset)); Throw.IfNull(quantity, nameof(quantity)); Throw.IfNull(price, nameof(price)); Throw.IfNull(orderTypes, nameof(orderTypes)); Status = status; BaseAsset = baseAsset; QuoteAsset = quoteAsset; Quantity = quantity; Price = price; NotionalMinimumValue = notionalMinimumValue; IsIcebergAllowed = isIcebergAllowed; OrderTypes = orderTypes; _symbol = $"{baseAsset}{quoteAsset}"; }
/// <summary> /// Verify a value is within range and of a valid increment. /// </summary> /// <param name="range"></param> /// <param name="value"></param> /// <returns></returns> public static bool IsValid(this InclusiveRange range, decimal value) { Throw.IfNull(range, nameof(range)); return(value >= range.Minimum && value <= range.Maximum && (value - range.Minimum) % range.Increment == 0); }