/// <summary> /// The record rule breach. /// </summary> /// <param name="lastTrade"> /// The last trade. /// </param> /// <param name="alignedSentiment"> /// The aligned sentiment. /// </param> /// <param name="opposingSentiment"> /// The opposing sentiment. /// </param> private void RecordRuleBreach(Order lastTrade, IPortfolio alignedSentiment, IPortfolio opposingSentiment) { this.logger.LogInformation($"rule breach detected for {lastTrade.Instrument?.Identifiers}"); var tradingPosition = new TradePosition(alignedSentiment.Ledger.FullLedger().ToList()); var opposingPosition = new TradePosition(opposingSentiment.Ledger.FullLedger().ToList()); // wrong but should be a judgement anyway var ruleBreach = new SpoofingRuleBreach( this.OrganisationFactorValue, this.ruleContext.SystemProcessOperationContext(), this.ruleContext.CorrelationId(), this.equitiesParameters.Windows?.BackwardWindowSize ?? TimeSpan.FromMinutes(30), tradingPosition, opposingPosition, lastTrade.Instrument, lastTrade, this.equitiesParameters, null, null, this.UniverseDateTime); var alert = new UniverseAlertEvent(Rules.Spoofing, ruleBreach, this.ruleContext); this.alertStream.Add(alert); }
/// <summary> /// The run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { var tradeWindow = history?.ActiveTradeHistory(); if (tradeWindow == null || !tradeWindow.Any()) { return; } var mostRecentTrade = tradeWindow.Pop(); var tradingPosition = new TradePositionCancellations( new List <Order>(), this.parameters.CancelledOrderPercentagePositionThreshold, this.parameters.CancelledOrderCountPercentageThreshold, this.logger); tradingPosition.Add(mostRecentTrade); var ruleBreach = this.CheckPositionForCancellations(tradeWindow, mostRecentTrade, tradingPosition); if (ruleBreach.HasBreachedRule()) { this.logger.LogInformation( $"RunRule has breached parameter conditions for {mostRecentTrade?.Instrument?.Identifiers}. Adding message to alert stream."); var message = new UniverseAlertEvent(Rules.CancelledOrders, ruleBreach, this.operationContext); this.alertStream.Add(message); } else { this.logger.LogInformation( $"RunRule did not breach parameter conditions for {mostRecentTrade?.Instrument?.Identifiers}."); } }
/// <summary> /// The run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { var activeTrades = history.ActiveTradeHistory(); if (!activeTrades.Any()) { return; } var liveTrades = this.FilterByClientAccount( history.ActiveTradeHistory().Pop(), history.ActiveTradeHistory()); if (!liveTrades?.Any() ?? true) { return; } var tradePosition = new TradePosition( this.FilterByClientAccount(history.ActiveTradeHistory().Pop(), history.ActiveTradeHistory())); // Net change analysis var averagePositionCheckTask = this.NettingTrades(liveTrades); averagePositionCheckTask.Wait(); var averagePositionCheck = averagePositionCheckTask.Result; // Clustering trade analysis var clusteringPositionCheck = this.ClusteringTrades(liveTrades); if ((averagePositionCheck == null || !averagePositionCheck.AveragePositionRuleBreach) && (clusteringPositionCheck == null || !clusteringPositionCheck.ClusteringPositionBreach)) { return; } var security = liveTrades?.FirstOrDefault()?.Instrument; this.logger.LogInformation( $"incrementing alerts because of security {security?.Name} at {this.UniverseDateTime}"); // wrong but should be a judgement anyway var breach = new WashTradeRuleBreach( this.equitiesParameters.Windows.BackwardWindowSize, this.OrganisationFactorValue, this.RuleCtx.SystemProcessOperationContext(), this.RuleCtx.CorrelationId(), this.equitiesParameters, tradePosition, security, averagePositionCheck, clusteringPositionCheck, null, null, this.UniverseDateTime); var universeAlert = new UniverseAlertEvent(Rules.WashTrade, breach, this.RuleCtx); this.alertStream.Add(universeAlert); }
/// <summary> /// The run initial submission event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunInitialSubmissionEvent(ITradingHistoryStack history) { var tradeWindow = history?.ActiveTradeHistory(); if (tradeWindow == null || !tradeWindow.Any()) { return; } if (tradeWindow.All(trades => trades.OrderDirection == tradeWindow.First().OrderDirection)) { return; } var mostRecentTrade = tradeWindow.Pop(); if (mostRecentTrade.OrderStatus() != OrderStatus.Filled) { return; } var buyPosition = new TradePosition(new List <Order>()); var sellPosition = new TradePosition(new List <Order>()); this.AddToPositions(buyPosition, sellPosition, mostRecentTrade); var tradingPosition = (mostRecentTrade.OrderDirection == OrderDirections.BUY || mostRecentTrade.OrderDirection == OrderDirections.COVER) ? buyPosition : sellPosition; var opposingPosition = (mostRecentTrade.OrderDirection == OrderDirections.SELL || mostRecentTrade.OrderDirection == OrderDirections.SHORT) ? buyPosition : sellPosition; var layeringRuleBreach = this.CheckPositionForLayering( tradeWindow, buyPosition, sellPosition, tradingPosition, opposingPosition, mostRecentTrade); if (layeringRuleBreach != null) { this.logger.LogInformation($"RunInitialSubmissionRule had a breach for {mostRecentTrade?.Instrument?.Identifiers}. Passing to alert stream."); var universeAlert = new UniverseAlertEvent(Domain.Surveillance.Scheduling.Rules.Layering, layeringRuleBreach, this.ruleContext); this.alertStream.Add(universeAlert); } }
/// <summary> /// The run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { var tradeWindow = history?.ActiveTradeHistory(); if (tradeWindow == null || !tradeWindow.Any()) { return; } var tradedSecurities = tradeWindow .Where(tr => tr.OrderFilledVolume.GetValueOrDefault() > 0) .ToList(); var tradedVolume = tradedSecurities.Sum(tr => tr.OrderFilledVolume.GetValueOrDefault(0)); var tradePosition = new TradePosition(tradedSecurities.ToList()); var mostRecentTrade = tradeWindow.Peek(); var dailyBreach = this.CheckDailyVolume(mostRecentTrade, tradedVolume); var windowBreach = this.CheckWindowVolume(mostRecentTrade, tradedVolume); var marketCapBreach = this.CheckMarketCap(mostRecentTrade, tradedSecurities); if (this.HasNoBreach(dailyBreach, windowBreach, marketCapBreach)) { return; } // wrong should use a judgement var breach = new HighVolumeRuleBreach( this.OrganisationFactorValue, this.RuleCtx.SystemProcessOperationContext(), this.RuleCtx.CorrelationId(), this.EquitiesParameters?.Windows?.BackwardWindowSize ?? TimeSpan.FromDays(1), tradePosition, mostRecentTrade?.Instrument, this.EquitiesParameters, dailyBreach, windowBreach, marketCapBreach, tradedVolume, null, null, this.UniverseDateTime); this.Logger.LogInformation($"RunRule had a breach for {mostRecentTrade?.Instrument?.Identifiers}. Daily Breach {dailyBreach?.HasBreach} | Window Breach {windowBreach?.HasBreach} | Market Cap Breach {marketCapBreach?.HasBreach}. Passing to alert stream."); var message = new UniverseAlertEvent(Domain.Surveillance.Scheduling.Rules.HighVolume, breach, this.RuleCtx); this.AlertStream.Add(message); }
/// <summary> /// The end of universe. /// </summary> protected override void EndOfUniverse() { this.logger.LogInformation("Eschaton occured"); if (this.hadMissingData && RunMode == RuleRunMode.ValidationRun) { // delete event var alert = new UniverseAlertEvent(Domain.Surveillance.Scheduling.Rules.Ramping, null, this.ruleContext, false, true); this.alertStream.Add(alert); this.dataRequestSubscriber.SubmitRequest(); } this.ruleContext?.EndEvent(); }
/// <summary> /// The end of universe. /// </summary> protected override void EndOfUniverse() { this.Logger.LogInformation("Eschaton occured"); if (this.HadMissingData && RunMode == RuleRunMode.ValidationRun) { // delete event var alert = new UniverseAlertEvent(Domain.Surveillance.Scheduling.Rules.HighVolume, null, this.RuleCtx, false, true); this.AlertStream.Add(alert); this.DataRequestSubscriber.SubmitRequest(); this.RuleCtx.EndEvent(); } else { this.RuleCtx?.EndEvent(); } }
/// <summary> /// The end of universe. /// </summary> protected override void EndOfUniverse() { this.logger.LogInformation("Eschaton occured"); var universeAlert = new UniverseAlertEvent(Domain.Surveillance.Scheduling.Rules.Layering, null, this.ruleContext, true); this.alertStream.Add(universeAlert); if (this.hadMissingData) { this.logger.LogInformation($"had missing data. Updating rule context with state."); this.ruleContext.EndEvent(); } else { this.ruleContext?.EndEvent(); } }
/// <summary> /// The end of universe. /// </summary> protected override void EndOfUniverse() { this.logger.LogInformation("Eschaton occured"); if (this.hadMissingData && RunMode == RuleRunMode.ValidationRun) { // delete event this.logger.LogInformation("had missing data at eschaton. Recording to op ctx."); var alert = new UniverseAlertEvent(Domain.Surveillance.Scheduling.Rules.MarkingTheClose, null, this.ruleContext, false, true); this.alertStream.Add(alert); this.dataRequestSubscriber.SubmitRequest(); this.ruleContext.EndEvent(); } else { this.ruleContext?.EndEvent(); } }
/// <summary> /// The run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { this.logger.LogInformation($"RunPostOrderEvent called at {this.UniverseDateTime}"); var filteredOrders = this.FilterByClientAccount( history.ActiveTradeHistory().Any() ? history.ActiveTradeHistory().Peek() : null, history.ActiveTradeHistory().ToList()); var clusteringAnalysis = this.ClusteringAnalysis(filteredOrders); var averageNettingAnalysis = this.NettingTrades(filteredOrders); if ((clusteringAnalysis == null || !clusteringAnalysis.ClusteringPositionBreach) && (averageNettingAnalysis == null || !averageNettingAnalysis.AveragePositionRuleBreach)) { return; } var security = filteredOrders?.FirstOrDefault()?.Instrument; // wrong but should be a judgement anyway var breach = new WashTradeRuleBreach( this.parameters.Windows.BackwardWindowSize, this.OrganisationFactorValue, this.RuleCtx.SystemProcessOperationContext(), this.RuleCtx.CorrelationId(), this.parameters, new TradePosition(filteredOrders), security, averageNettingAnalysis, clusteringAnalysis, null, null, this.UniverseDateTime); var universeAlert = new UniverseAlertEvent(Rules.FixedIncomeWashTrades, breach, this.RuleCtx); this.alertStream.Add(universeAlert); this.logger.LogInformation($"RunPostOrderEvent completed for {this.UniverseDateTime}"); }
/// <summary> /// The run order filled event. /// </summary> /// <param name="history"> /// The history. /// </param> public override void RunOrderFilledEvent(ITradingHistoryStack history) { var tradeWindow = history?.ActiveTradeHistory(); if (tradeWindow == null || !tradeWindow.Any()) { return; } if (!this.ExceedsTradingFrequencyThreshold(tradeWindow)) { // LOG THEN EXIT this.logger.LogInformation($"Trading Frequency of {this.rampingParameters.ThresholdOrdersExecutedInWindow} was not exceeded. Returning."); return; } if (!this.ExceedsTradingVolumeInWindowThreshold(tradeWindow.ToList(), tradeWindow.Any() ? tradeWindow.Peek() : null)) { // LOG THEN EXIT this.logger.LogInformation($"Trading Volume of {this.rampingParameters.ThresholdVolumePercentageWindow} was not exceeded. Returning."); return; } var lastTrade = tradeWindow.Any() ? tradeWindow.Peek() : null; var tradingHours = this.tradingHoursService.GetTradingHoursForMic(lastTrade.Market?.MarketIdentifierCode); if (!tradingHours.IsValid) { this.logger.LogError($"Request for trading hours was invalid. MIC - {lastTrade.Market?.MarketIdentifierCode}"); return; } var tradingDates = this.tradingHoursService.GetTradingDaysWithinRangeAdjustedToTime( tradingHours.OpeningInUtcForDay(UniverseDateTime.Subtract(this.TradeBackwardWindowSize)), tradingHours.ClosingInUtcForDay(UniverseDateTime), lastTrade.Market?.MarketIdentifierCode); var subtractDate = this.TradeBackwardWindowSize > TimeSpan.FromDays(30) ? this.TradeBackwardWindowSize : TimeSpan.FromDays(30); var marketDataRequest = new MarketDataRequest( lastTrade.Market?.MarketIdentifierCode, lastTrade.Instrument.Cfi, lastTrade.Instrument.Identifiers, tradingHours.OpeningInUtcForDay(UniverseDateTime.Subtract(subtractDate)), tradingHours.ClosingInUtcForDay(UniverseDateTime), this.ruleContext?.Id(), DataSource.AnyIntraday); var marketData = UniverseEquityIntradayCache.GetMarketsForRange(marketDataRequest, tradingDates, RunMode); if (marketData.HadMissingData) { this.hadMissingData = true; this.logger.LogWarning($"Missing data for {marketDataRequest}."); return; } var windowStackCopy = new Stack <Order>(tradeWindow); var rampingOrders = new Stack <Order>(windowStackCopy); var rampingAnalysisResults = new List <IRampingStrategySummaryPanel>(); while (rampingOrders.Any()) { var rampingOrderList = rampingOrders.ToList(); var rootOrder = rampingOrders.Any() ? rampingOrders.Peek() : null; var marketDataSubset = marketData.Response.Where(_ => _.TimeStamp <= rootOrder.FilledDate).ToList(); var rampingAnalysisResult = this.rampingAnalyzer.Analyse(rampingOrderList, marketDataSubset); rampingAnalysisResults.Add(rampingAnalysisResult); rampingOrders.Pop(); } if (!rampingAnalysisResults.Any() || !rampingAnalysisResults.First().HasRampingStrategy() || rampingAnalysisResults.All(_ => !_.HasRampingStrategy())) { // LOG THEN EXIT this.logger.LogInformation($"A rule breach was not detected for {lastTrade?.Instrument?.Identifiers}. Returning."); return; } var rampingPrevalence = this.RampingPrevalence(rampingAnalysisResults); if (rampingPrevalence < this.rampingParameters.AutoCorrelationCoefficient) { // LOG THEN EXIT this.logger.LogInformation($"A rule breach was not detected due to an auto correlation of {rampingPrevalence} for {lastTrade?.Instrument?.Identifiers}. Returning."); return; } var tradePosition = new TradePosition(tradeWindow.ToList()); // wrong but should be a judgement var breach = new RampingRuleBreach( this.TradeBackwardWindowSize, tradePosition, lastTrade.Instrument, this.rampingParameters.Id, this.ruleContext?.Id(), this.ruleContext?.CorrelationId(), this.OrganisationFactorValue, rampingAnalysisResults.Last(), this.rampingParameters, null, null, this.UniverseDateTime); this.logger.LogInformation($"RunRule has breached parameter conditions for {lastTrade?.Instrument?.Identifiers}. Adding message to alert stream."); var message = new UniverseAlertEvent(Domain.Surveillance.Scheduling.Rules.Ramping, breach, this.ruleContext); this.alertStream.Add(message); }
/// <summary> /// The run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { if (!this.processingMarketClose || this.latestMarketClosure == null) { return; } history.ArchiveExpiredActiveItems(this.latestMarketClosure.MarketClose); var securities = history.ActiveTradeHistory(); if (!securities.Any()) { // no securities were being traded within the market closure time window return; } // filter the security list by the mic of the closing market.... var filteredMarketSecurities = securities .Where(i => string.Equals( i.Market?.MarketIdentifierCode, this.latestMarketClosure.MarketId, StringComparison.InvariantCultureIgnoreCase)) .ToList(); if (!filteredMarketSecurities.Any()) { // no relevant securities were being traded within the market closure time window return; } var marketSecurities = new Stack <Order>(filteredMarketSecurities); VolumeBreach dailyVolumeBreach = null; if (this.equitiesParameters.PercentageThresholdDailyVolume != null) { dailyVolumeBreach = this.CheckDailyVolumeTraded(marketSecurities); } VolumeBreach windowVolumeBreach = null; if (this.equitiesParameters.PercentageThresholdWindowVolume != null) { windowVolumeBreach = this.CheckWindowVolumeTraded(marketSecurities); } if ((dailyVolumeBreach == null || !dailyVolumeBreach.HasBreach()) && (windowVolumeBreach == null || !windowVolumeBreach.HasBreach())) { this.logger.LogInformation($"had no breaches for {marketSecurities.FirstOrDefault()?.Instrument?.Identifiers} at {UniverseDateTime}"); return; } var position = new TradePosition(marketSecurities.ToList()); // wrong but should be a judgement var breach = new MarkingTheCloseBreach( this.OrganisationFactorValue, this.ruleContext.SystemProcessOperationContext(), this.ruleContext.CorrelationId(), this.equitiesParameters.Windows.BackwardWindowSize, marketSecurities.FirstOrDefault()?.Instrument, this.latestMarketClosure, position, this.equitiesParameters, dailyVolumeBreach ?? new VolumeBreach(), windowVolumeBreach ?? new VolumeBreach(), null, null, this.UniverseDateTime); this.logger.LogInformation($"had a breach for {marketSecurities.FirstOrDefault()?.Instrument?.Identifiers} at {UniverseDateTime}. Adding to alert stream."); var alertEvent = new UniverseAlertEvent(Domain.Surveillance.Scheduling.Rules.MarkingTheClose, breach, this.ruleContext); this.alertStream.Add(alertEvent); }
/// <summary> /// The run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { if (!this.processingMarketClose || this.latestMarketClosure == null) { return; } var ordersToCheck = history .ActiveTradeHistory() .Where(_ => _.OrderLimitPrice?.Value != null && _.OrderType == OrderTypes.LIMIT && ( _.OrderStatus() == OrderStatus.Placed || _.OrderStatus() == OrderStatus.Booked || _.OrderStatus() == OrderStatus.Amended || _.OrderStatus() == OrderStatus.Cancelled || _.OrderStatus() == OrderStatus.Rejected)) .ToList(); if (!ordersToCheck.Any()) { this.logger.LogInformation("RunPostOrderEvent did not have any orders to check after filtering for invalid order status values"); return; } var openingHours = this.latestMarketClosure.MarketClose - this.latestMarketClosure.MarketOpen; var benchmarkOrder = ordersToCheck.First(); var tradingHours = this.tradingHoursService.GetTradingHoursForMic(benchmarkOrder.Market?.MarketIdentifierCode); if (!tradingHours.IsValid) { this.logger.LogError($"Request for trading hours was invalid. MIC - {benchmarkOrder.Market?.MarketIdentifierCode}"); } var tradingDates = this.tradingHoursService.GetTradingDaysWithinRangeAdjustedToTime( tradingHours.OpeningInUtcForDay(UniverseDateTime.Subtract(this.TradeBackwardWindowSize)), tradingHours.ClosingInUtcForDay(UniverseDateTime), benchmarkOrder.Market?.MarketIdentifierCode); var marketDataRequest = new MarketDataRequest( benchmarkOrder.Market.MarketIdentifierCode, benchmarkOrder.Instrument.Cfi, benchmarkOrder.Instrument.Identifiers, UniverseDateTime.Subtract(openingHours), // implicitly correct (market closure event trigger) UniverseDateTime, this.ruleContext?.Id(), DataSource.AnyIntraday); var dataResponse = UniverseEquityIntradayCache.GetMarketsForRange(marketDataRequest, tradingDates, RunMode); if (dataResponse.HadMissingData || dataResponse.Response == null || !dataResponse.Response.Any()) { this.logger.LogInformation($"RunPostOrderEvent could not find relevant market data for {benchmarkOrder.Instrument?.Identifiers} on {this.latestMarketClosure?.MarketId}"); this.hadMissingData = true; return; } // ReSharper disable once AssignNullToNotNullAttribute var pricesInTimeBars = dataResponse.Response.Select(_ => (double)_.SpreadTimeBar.Price.Value).ToList(); var sd = (decimal)MathNet.Numerics.Statistics.Statistics.StandardDeviation(pricesInTimeBars); var mean = (decimal)MathNet.Numerics.Statistics.Statistics.Mean(pricesInTimeBars); var ruleBreaches = ordersToCheck .Select(_ => this.ReferenceOrderSigma(_, sd, mean)) .Where(_ => _.Item1 > 0 && _.Item1 > this.parameters.Sigma) .Where(_ => this.CheckIfOrderWouldNotOfExecuted(_, dataResponse.Response)) .ToList(); if (!ruleBreaches.Any()) { return; } var position = new TradePosition(ruleBreaches.Select(_ => _.Item2).ToList()); var poe = ruleBreaches.Select(_ => this.ExecutionUnderNormalDistribution(_.Item2, mean, sd, _.Item1)).ToList(); // wrong but should be a judgement var breach = new PlacingOrderWithNoIntentToExecuteRuleRuleBreach( this.parameters.Windows.BackwardWindowSize, position, benchmarkOrder.Instrument, this.OrganisationFactorValue, mean, sd, poe, this.parameters, this.ruleContext, null, null, this.UniverseDateTime); var alertEvent = new UniverseAlertEvent(Domain.Surveillance.Scheduling.Rules.PlacingOrderWithNoIntentToExecute, breach, this.ruleContext); this.alertStream.Add(alertEvent); }