private string BuildDescription(IWashTradeRuleBreach breach)
        {
            if (breach == null)
            {
                return(string.Empty);
            }

            var preamble = $"Wash trade rule breach. Traded {breach.Security?.Name}.";

            var positionAverage    = string.Empty;
            var positionPaired     = string.Empty;
            var positionClustering = string.Empty;

            if (breach.AveragePositionBreach != null && breach.AveragePositionBreach.AveragePositionRuleBreach)
            {
                positionAverage = this.BuildAveragePositionDescription(breach);
            }

            if (breach.ClusteringPositionBreach != null && breach.ClusteringPositionBreach.ClusteringPositionBreach)
            {
                positionClustering = this.BuildPositionClusteringDescription(breach);
            }

            return($"{preamble}{positionAverage}{positionPaired}{positionClustering}");
        }
        private string BuildAveragePositionDescription(IWashTradeRuleBreach breach)
        {
            var trades = breach.AveragePositionBreach.AveragePositionAmountOfTrades.GetValueOrDefault(0);

            var percentageChange = Math.Round(
                breach.AveragePositionBreach.AveragePositionRelativeValueChange.GetValueOrDefault(0) * 100,
                2,
                MidpointRounding.AwayFromZero);

            var percentageChangeMax = Math.Round(
                breach.EquitiesParameters.AveragePositionMaximumPositionValueChange.GetValueOrDefault(0) * 100,
                2,
                MidpointRounding.AwayFromZero);

            var averagePosition =
                $" {trades} trades appeared to be part of a series of wash trade activity. These trades netted a total of {percentageChange}% in the value of the traders position, lower values of change are considered to be stronger evidence of wash trading. {percentageChangeMax}% was the configured maximum value change for this to be considered an alert.";

            if (breach.EquitiesParameters.AveragePositionMaximumAbsoluteValueChangeAmount != null &&
                breach.AveragePositionBreach.AveragePositionAbsoluteValueChange != null)
            {
                var absoluteChange = Math.Round(
                    breach.AveragePositionBreach.AveragePositionAbsoluteValueChange.Value.Value,
                    2,
                    MidpointRounding.AwayFromZero);

                averagePosition =
                    $"{averagePosition} The absolute value change of the traders position in {breach.Security.Name} changed by ({breach.AveragePositionBreach.AveragePositionAbsoluteValueChange.Value.Currency.Code}){absoluteChange} against a maximum position value change of ({breach.EquitiesParameters.AveragePositionMaximumAbsoluteValueChangeCurrency}){breach.EquitiesParameters.AveragePositionMaximumAbsoluteValueChangeAmount}.";
            }

            return(averagePosition);
        }
Beispiel #3
0
        /// <summary>
        ///     Receive and cache rule breach in memory
        /// </summary>
        public void Send(IWashTradeRuleBreach ruleBreach)
        {
            if (ruleBreach == null)
            {
                // ReSharper disable once InconsistentlySynchronizedField
                this._logger.LogInformation("received a rule breach. Returning.");
                return;
            }

            lock (this._lock)
            {
                this._logger.LogInformation($"received rule breach for {ruleBreach.Security.Identifiers}");

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

                this._logger.LogInformation(
                    $"deduplicated {this._messages.Count} alerts when processing {ruleBreach.Security.Identifiers}.");

                this._messages.Add(ruleBreach);
            }
        }
        private string BuildPositionClusteringDescription(IWashTradeRuleBreach breach)
        {
            var percentageChangeMax = Math.Round(
                breach.EquitiesParameters.ClusteringPercentageValueDifferenceThreshold.GetValueOrDefault(0) * 100,
                2,
                MidpointRounding.AwayFromZero);

            var centroids =
                breach.ClusteringPositionBreach?.CentroidsOfBreachingClusters.Select(
                    ce => Math.Round(ce, 2, MidpointRounding.AwayFromZero)) ?? new List <decimal>();

            var initial =
                $" A clustering (k-means) rule breach was found with {breach.ClusteringPositionBreach.AmountOfBreachingClusters} clusters detected to be trading at thin margins per cluster defined as less than a {percentageChangeMax}% difference between cost and revenues when buying and selling a position.";

            if (breach.ClusteringPositionBreach != null &&
                breach.ClusteringPositionBreach.CentroidsOfBreachingClusters.Any())
            {
                initial =
                    $"{initial} The centroids of the clusters were at the prices {centroids.Aggregate(string.Empty, (a, b) => $"{a}{b}")}";
            }

            return(initial);
        }
        public async Task Send(IWashTradeRuleBreach breach)
        {
            var description = this.BuildDescription(breach);

            await this.Send(breach, description);
        }