/// <summary>
        ///     Receive and cache rule breach in memory
        /// </summary>
        public void Send(ICancelledOrderRuleBreach ruleBreach)
        {
            if (ruleBreach == null)
            {
                // ReSharper disable once InconsistentlySynchronizedField
                this._logger.LogInformation(
                    $"Cancelled Order Rule Cached Message Sender received a null rule breach {ruleBreach.Security.Identifiers}. Returning.");
                return;
            }

            lock (this._lock)
            {
                this._logger.LogInformation(
                    $"Cancelled Order Rule Cached Message Sender received rule breach for {ruleBreach.Security.Identifiers}");

                var duplicates = this._messages.Where(msg => msg.Trades.PositionIsSubsetOf(ruleBreach.Trades)).ToList();

                if (duplicates != null && duplicates.Any())
                {
                    this._logger.LogInformation(
                        $"Cancelled Order Rule Cached Message Sender removing {duplicates.Count} duplicates for  {ruleBreach.Security.Identifiers}");
                }

                this._messages = this._messages.Except(duplicates).ToList();
                this._messages.Add(ruleBreach);
            }
        }
 private string PositionSizeText(
     ICancelledOrderRuleEquitiesParameters parameters,
     ICancelledOrderRuleBreach ruleBreach,
     decimal percentagePositionCancelled)
 {
     return(ruleBreach.ExceededPercentagePositionCancellations
                ? $" Position cancelled exceeded threshold at {percentagePositionCancelled}% cancelled. Limit was set at {parameters.CancelledOrderPercentagePositionThreshold * 100}%. {ruleBreach.AmountOfPositionInTotal} orders in the security in during the breach in total of which {ruleBreach.AmountOfPositionCancelled} were cancelled."
                : string.Empty);
 }
 private string OrderRatioText(
     ICancelledOrderRuleEquitiesParameters parameters,
     ICancelledOrderRuleBreach ruleBreach,
     decimal tradeCountCancelled)
 {
     return(ruleBreach.ExceededPercentageTradeCountCancellations
                ? $" Number of orders cancelled exceeded threshold at {tradeCountCancelled}% cancelled. Limit was set at {parameters.CancelledOrderCountPercentageThreshold * 100}%."
                : string.Empty);
 }
        public async Task Send(ICancelledOrderRuleBreach ruleBreach)
        {
            if (ruleBreach?.Trades == null || !ruleBreach.Trades.Get().Any())
            {
                return;
            }

            var description = this.BuildDescription(
                ruleBreach.Parameters,
                ruleBreach,
                ruleBreach.Trades.Get().FirstOrDefault());

            await this.Send(ruleBreach, description);
        }
        private string BuildDescription(
            ICancelledOrderRuleEquitiesParameters parameters,
            ICancelledOrderRuleBreach ruleBreach,
            Order anyOrder)
        {
            var percentagePositionCancelled = Math.Round(
                ruleBreach.PercentagePositionCancelled.GetValueOrDefault(0) * 100m,
                2,
                MidpointRounding.AwayFromZero);

            var tradeCountCancelled = Math.Round(
                ruleBreach.PercentageTradeCountCancelled.GetValueOrDefault(0) * 100m,
                2,
                MidpointRounding.AwayFromZero);

            var positionSizeSegment = this.PositionSizeText(parameters, ruleBreach, percentagePositionCancelled);
            var orderRatioSegment   = this.OrderRatioText(parameters, ruleBreach, tradeCountCancelled);

            var description =
                $"Cancelled Order Rule Breach. Traded ({anyOrder?.OrderDirection.GetDescription()}) security {anyOrder?.Instrument?.Name} with excessive cancellations in {parameters.Windows.BackwardWindowSize.TotalMinutes} minute time period.{positionSizeSegment}{orderRatioSegment}";

            return(description);
        }