/// <summary> /// The update trade submitted trading histories. /// </summary> /// <param name="order"> /// The order. /// </param> /// <param name="tradingHistory"> /// The trading history. /// </param> /// <param name="backwardWindowSize"> /// The backward window size. /// </param> /// <param name="forwardWindowSize"> /// The forward window size. /// </param> /// <returns> /// The <see cref="ITradingHistoryStack"/>. /// </returns> private ITradingHistoryStack UpdateTradeSubmittedTradingHistories( Order order, ConcurrentDictionary <InstrumentIdentifiers, ITradingHistoryStack> tradingHistory, TimeSpan backwardWindowSize, TimeSpan?forwardWindowSize) { if (!tradingHistory.ContainsKey(order.Instrument.Identifiers)) { ITradingHistoryStack history = new TradingHistoryStack( backwardWindowSize, i => i.PlacedDate.GetValueOrDefault(), this.tradingStackLogger); ITradingHistoryStack historyDecorator = forwardWindowSize != null ? new TradingHistoryDelayedDecorator(history, forwardWindowSize.GetValueOrDefault()) : null; var stack = historyDecorator ?? history; stack.Add(order, order.PlacedDate.GetValueOrDefault()); tradingHistory.TryAdd(order.Instrument.Identifiers, stack); } else { tradingHistory.TryGetValue(order.Instrument.Identifiers, out var history); history?.Add(order, order.PlacedDate.GetValueOrDefault()); history?.ArchiveExpiredActiveItems(order.PlacedDate.GetValueOrDefault()); } tradingHistory.TryGetValue(order.Instrument.Identifiers, out var updatedHistory); return(updatedHistory); }
/// <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 rule guard. /// </summary> /// <param name="history"> /// The history. /// </param> /// <returns> /// The <see cref="bool"/>. /// </returns> protected override bool RunRuleGuard(ITradingHistoryStack history) { var activeWindow = history.ActiveTradeHistory(); if (!activeWindow.Any()) { return(false); } var baseOrder = activeWindow.Any() ? activeWindow.Peek() : null; var tradeBuy = activeWindow.Where(aw => aw != null).Where( aw => aw.OrderDirection == OrderDirections.BUY || aw.OrderDirection == OrderDirections.COVER).ToList(); var tradeSell = activeWindow.Where(aw => aw != null).Where( aw => aw.OrderDirection == OrderDirections.SELL || aw.OrderDirection == OrderDirections.SHORT).ToList(); var securitiesBrought = tradeBuy.Sum(tb => tb.OrderFilledVolume); var securitiesSold = tradeSell.Sum(tb => tb.OrderFilledVolume); if (securitiesBrought > securitiesSold) { this.Logger.LogInformation( $"RunRuleGuard securities brought {securitiesBrought} exceeded securities sold {securitiesSold}. Proceeding to evaluate market closure rule."); return(true); } this.Logger.LogInformation( $"RunRuleGuard securities brought {securitiesBrought} exceeded or equaled securities sold {securitiesSold}. Not proceeding to evaluate market closure rule."); this.SetNoLiveTradesJudgement(baseOrder); return(false); }
/// <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) { this.logger.LogInformation( $"{nameof(FixedIncomeHighVolumeRule)} RunInitialSubmissionRule called at {this.UniverseDateTime}"); this.logger.LogInformation( $"{nameof(FixedIncomeHighVolumeRule)} RunInitialSubmissionRule completed for {this.UniverseDateTime}"); }
/// <summary> /// The run order filled event. /// </summary> /// <param name="history"> /// The history. /// </param> public override void RunOrderFilledEvent(ITradingHistoryStack history) { this.logger.LogInformation( $"{nameof(FixedIncomeHighVolumeRule)} RunOrderFilledEvent called at {this.UniverseDateTime}"); this.logger.LogInformation( $"{nameof(FixedIncomeHighVolumeRule)} RunOrderFilledEvent completed for {this.UniverseDateTime}"); }
/// <summary> /// The run initial submission event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunInitialSubmissionEvent(ITradingHistoryStack history) { var activeTrades = history?.ActiveTradeHistory(); var portfolio = this.portfolioFactory.Build(); portfolio.Add(activeTrades); var lastTrade = history?.ActiveTradeHistory()?.Any() ?? false ? history?.ActiveTradeHistory()?.Peek() : null; if (lastTrade == null) { return; } if (lastTrade.OrderStatus() != OrderStatus.Filled) { this.logger.LogInformation("Order under analysis was not in filled state, exiting spoofing rule"); return; } var lastTradeSentiment = this.analysisService.ResolveSentiment(lastTrade); var otherTrades = activeTrades.Where(i => i != lastTrade).ToList(); var orderLedgerSentiment = this.analysisService.ResolveSentiment(otherTrades); if (lastTradeSentiment == orderLedgerSentiment) { this.logger.LogInformation("Order under analysis was consistent with a priori pricing sentiment"); return; } if (lastTradeSentiment == PriceSentiment.Neutral) { this.logger.LogInformation("Order under analysis was considered price neutral on sentiment"); return; } var analyzedOrders = this.analysisService.AnalyseOrder(activeTrades); var alignedSentimentPortfolio = this.AlignedSentimentPortfolio(analyzedOrders, lastTradeSentiment); var unalignedSentimentPortfolio = this.UnalignedSentimentPortfolio(analyzedOrders, lastTradeSentiment); if (!this.UnalignedPortfolioOverCancellationThreshold(unalignedSentimentPortfolio)) { return; } if (!this.CancellationVolumeOverThreshold(alignedSentimentPortfolio, unalignedSentimentPortfolio)) { return; } this.logger.LogInformation( $"Rule breach for {lastTrade?.Instrument?.Identifiers} at {this.UniverseDateTime}. Passing to alert stream."); this.RecordRuleBreach(lastTrade, alignedSentimentPortfolio, unalignedSentimentPortfolio); }
/// <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 run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { this.logger.LogInformation($"{nameof(FixedIncomeHighVolumeRule)} RunRule called at {this.UniverseDateTime}"); var tradeWindow = history?.ActiveTradeHistory() ?? new Stack <Order>(); if (this.HasEmptyTradeWindow(tradeWindow)) { this.logger.LogInformation($"RunPostOrderEvent had an empty trade window"); return; } var tradedSecurities = tradeWindow.Where(_ => _.OrderFilledVolume.GetValueOrDefault() > 0).ToList(); tradedSecurities = this.FilterOutOtc(tradedSecurities); var tradedVolume = tradedSecurities.Sum(_ => _.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); if (this.HasNoBreach(dailyBreach, windowBreach)) { this.logger.LogInformation($"RunPostOrderEvent passing judgement with no daily or window breach for {mostRecentTrade.Instrument.Identifiers}"); this.PassJudgementForNoBreachAsync(mostRecentTrade, tradePosition).Wait(); } if (windowBreach.VolumeBreach) { this.logger.LogInformation($"RunPostOrderEvent passing judgement with window breach for {mostRecentTrade.Instrument.Identifiers}"); this.PassJudgementForWindowBreachAsync(mostRecentTrade, tradePosition, windowBreach).Wait(); } if (dailyBreach.VolumeBreach) { this.logger.LogInformation($"RunPostOrderEvent passing judgement with no daily breach for {mostRecentTrade.Instrument.Identifiers}"); this.PassJudgementForDailyBreachAsync(mostRecentTrade, tradePosition, dailyBreach)?.Wait(); } }
/// <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 order filled event. /// </summary> /// <param name="history"> /// The history. /// </param> public override void RunOrderFilledEvent(ITradingHistoryStack history) { }
/// <summary> /// The run initial submission event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunInitialSubmissionEvent(ITradingHistoryStack history) { }
/// <summary> /// The run rule guard, always true but provides override to derived classes. /// </summary> /// <param name="history"> /// The history. /// </param> /// <returns> /// The <see cref="bool"/>. /// </returns> protected virtual bool RunRuleGuard(ITradingHistoryStack history) { return(true); }
/// <summary> /// The run initial submission event delayed. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunInitialSubmissionEventDelayed(ITradingHistoryStack history) { // do nothing }
/// <summary> /// The run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { this.FlushFilterEvents(); if (history == null) { this.logger.LogInformation($"null history received by run post order event"); return; } var activeHistory = history.ActiveTradeHistory(); if (activeHistory == null || !activeHistory.Any()) { return; } if (this.decimalRangeRuleFilter.Type == RuleFilterType.None) { this.UpdatePassedFilterWithOrders(activeHistory); return; } var mostRecentTrade = activeHistory.Peek(); var tradingHours = this.tradingHoursService.GetTradingHoursForMic(mostRecentTrade.Market?.MarketIdentifierCode); if (!tradingHours.IsValid) { this.logger.LogError($"Request for trading hours was invalid. MIC - {mostRecentTrade.Market?.MarketIdentifierCode}"); this.UpdatePassedFilterWithOrders(activeHistory); return; } var marketTradedVolume = this.RetrieveMarketTradedVolume(mostRecentTrade, tradingHours, activeHistory); if (marketTradedVolume == null) { return; } var volumeTraded = activeHistory.Sum(_ => _.OrderFilledVolume ?? 0); if (marketTradedVolume <= 0) { this.logger.LogInformation($"market traded volume was {marketTradedVolume} for {mostRecentTrade.Instrument.Identifiers}"); this.UpdatePassedFilterWithOrders(activeHistory); return; } if (volumeTraded <= 0) { this.logger.LogInformation($"market traded volume was {volumeTraded} for {mostRecentTrade.Instrument.Identifiers}"); this.UpdatePassedFilterWithOrders(activeHistory); return; } var proportionOfTradedVolume = (volumeTraded / (decimal)marketTradedVolume); var passedFilter = false; if (this.decimalRangeRuleFilter.Type == RuleFilterType.Include) { passedFilter = (this.decimalRangeRuleFilter.Max == null || this.decimalRangeRuleFilter.Max == 1 || proportionOfTradedVolume <= this.decimalRangeRuleFilter.Max) && (this.decimalRangeRuleFilter.Min == null || proportionOfTradedVolume >= this.decimalRangeRuleFilter.Min); } else if (this.decimalRangeRuleFilter.Type == RuleFilterType.Exclude) { passedFilter = (this.decimalRangeRuleFilter.Max == null || this.decimalRangeRuleFilter.Max == 1 || proportionOfTradedVolume > this.decimalRangeRuleFilter.Max) || (this.decimalRangeRuleFilter.Min == null || proportionOfTradedVolume < this.decimalRangeRuleFilter.Min); } if (passedFilter) { this.UpdatePassedFilterWithOrders(activeHistory); } }
/// <summary> /// The run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { // we don't analyse rules based on when their status last changed in the layering rule }
public TradingHistoryDelayedDecorator(ITradingHistoryStack stack, TimeSpan delay) { this._stack = stack ?? throw new ArgumentNullException(nameof(stack)); this._delay = delay; this._delayedOrders = new Queue <DelayedOrder>(); }
/// <summary> /// Main method for high profit analysis /// </summary> /// <param name="history">Trading history qualified for high profit analysis</param> /// <param name="intradayCache">Market data for analysis</param> protected void EvaluateHighProfits(ITradingHistoryStack history, IUniverseFixedIncomeIntraDayCache intradayCache) { if (!this.RunRuleGuard(history)) { this.Logger.LogInformation($"EvaluateHighProfits did not pass the rule run guard exiting"); return; } var orderUnderAnalysis = this.UniverseEvent.UnderlyingEvent as Order; var activeTrades = history.ActiveTradeHistory(); this.Logger.LogInformation($"EvaluateHighProfits about to filter over Filled with {activeTrades.Count} trades"); var liveTrades = activeTrades.Where(at => at.OrderStatus() == OrderStatus.Filled).ToList(); this.Logger.LogInformation($"EvaluateHighProfits about to filter over clean / dirty with {liveTrades.Count} trades"); var cleanTrades = liveTrades.Where(_ => _.OrderCleanDirty == OrderCleanDirty.CLEAN).ToList(); this.Logger.LogInformation($"EvaluateHighProfits filtered by clean and had {cleanTrades.Count} trades"); if (orderUnderAnalysis == null) { orderUnderAnalysis = activeTrades.LastOrDefault(); } if (!cleanTrades.Any()) { this.Logger.LogInformation($"EvaluateHighProfits had no active and filled trades, exiting"); this.SetNoLiveTradesJudgement(orderUnderAnalysis); return; } var targetCurrency = new Currency(this.FixedIncomeParameters.HighProfitCurrencyConversionTargetCurrency); var allTradesInCommonCurrency = this.CheckTradesInCommonCurrency(cleanTrades, targetCurrency); var costCalculator = this.GetCostCalculator(allTradesInCommonCurrency, targetCurrency); var revenueCalculator = this.GetRevenueCalculator(allTradesInCommonCurrency, targetCurrency); var marketCache = this.MarketClosureRule ? this.marketDataCacheFactory.InterdayStrategy(this.UniverseFixedIncomeInterdayCache) : this.marketDataCacheFactory.IntradayStrategy(intradayCache); var costTask = costCalculator.CalculateCostOfPosition(cleanTrades, this.UniverseDateTime, this.RuleCtx); var revenueTask = revenueCalculator.CalculateRevenueOfPosition(cleanTrades, this.UniverseDateTime, this.RuleCtx, marketCache); var cost = costTask.Result; var revenueResponse = revenueTask.Result; if (revenueResponse.HadMissingMarketData) { this.Logger.LogInformation($"Had missing market data for fixed income high profits, exiting {cleanTrades.FirstOrDefault()?.Instrument?.Identifiers}"); this.SetMissingMarketDataJudgement(orderUnderAnalysis); this.hasMissingData = true; return; } var revenue = revenueResponse.Money; if (revenue == null || revenue.Value.Value <= 0) { this.Logger.LogInformation($"rule had null for revenues for {cleanTrades.FirstOrDefault()?.Instrument?.Identifiers} at {this.UniverseDateTime}. Returning."); this.NoRevenueOrCostJudgement(orderUnderAnalysis); return; } if (cost == null || cost.Value.Value <= 0) { this.Logger.LogInformation( $"We have calculable revenues but not costs for {cleanTrades.FirstOrDefault()?.Instrument?.Identifiers} at {this.UniverseDateTime}. Returning."); this.NoRevenueOrCostJudgement(orderUnderAnalysis); return; } if (!revenue.Value.DenominatedInCommonCurrency(cost.Value)) { var convertedCostTask = this.currencyConverterService.Convert( new[] { cost.Value }, revenue.Value.Currency, UniverseDateTime, this.RuleCtx); var convertedCost = convertedCostTask.Result; if (convertedCost == null || !revenue.Value.DenominatedInCommonCurrency(convertedCost.Value)) { this.Logger.LogError($"Could not convert cost to revenue currency. Expected currency '{revenue.Value.Currency}' but received currency '{convertedCost.Value.Currency}'. For trade {liveTrades.FirstOrDefault()?.Instrument?.Identifiers} at {this.UniverseDateTime}."); return; } cost = convertedCost; } this.Logger.LogInformation($"Absolute profit calculating...currency of revenue {revenue.Value.Currency} - currency of costs {cost.Value.Currency} for trade {liveTrades.FirstOrDefault()?.Instrument?.Identifiers} at {this.UniverseDateTime}."); var absoluteProfit = revenue.Value - cost.Value; this.Logger.LogInformation($"Profit ratio calculating...currency of revenue {revenue.Value.Currency} - currency of costs {cost.Value.Currency} for trade {liveTrades.FirstOrDefault()?.Instrument?.Identifiers} at {this.UniverseDateTime}."); var profitRatio = (revenue.Value.Value / cost.Value.Value) - 1; this.Logger.LogInformation($"Currency conversion success of revenue {revenue.Value.Currency} - currency of costs {cost.Value.Currency} for trade {liveTrades.FirstOrDefault()?.Instrument?.Identifiers} at {this.UniverseDateTime}."); var hasHighProfitAbsolute = this.HasHighProfitAbsolute(absoluteProfit); var hasHighProfitPercentage = this.HasHighProfitPercentage(profitRatio); IExchangeRateProfitBreakdown exchangeRateProfits = null; if (this.FixedIncomeParameters.UseCurrencyConversions && !string.IsNullOrEmpty(this.FixedIncomeParameters.HighProfitCurrencyConversionTargetCurrency)) { this.Logger.LogInformation( $"is set to use currency conversions and has a target conversion currency to {this.FixedIncomeParameters.HighProfitCurrencyConversionTargetCurrency}. Calling set exchange rate profits."); exchangeRateProfits = this.SetExchangeRateProfits(cleanTrades); } RuleBreachContext ruleBreachContext = null; if (hasHighProfitAbsolute || hasHighProfitPercentage) { this.Logger.LogInformation( $"had a breach for {cleanTrades.FirstOrDefault()?.Instrument?.Identifiers} at {this.UniverseDateTime}. High Profit Absolute {hasHighProfitAbsolute} and High Profit Percentage {hasHighProfitPercentage}."); ruleBreachContext = new RuleBreachContext( this.FixedIncomeParameters.Windows.BackwardWindowSize + this.FixedIncomeParameters.Windows.FutureWindowSize, new TradePosition(cleanTrades), cleanTrades.FirstOrDefault(_ => _?.Instrument != null)?.Instrument, this.RuleCtx.IsBackTest(), this.RuleCtx.RuleParameterId(), this.RuleCtx.SystemProcessOperationContext().Id.ToString(), this.RuleCtx.CorrelationId(), this.OrganisationFactorValue, this.FixedIncomeParameters, this.UniverseDateTime); } this.SetJudgementForFullAnalysis( absoluteProfit, profitRatio, hasHighProfitAbsolute, hasHighProfitPercentage, exchangeRateProfits, ruleBreachContext, orderUnderAnalysis); }
/// <summary> /// The run order filled event. /// </summary> /// <param name="history"> /// The history. /// </param> public override void RunOrderFilledEvent(ITradingHistoryStack history) { // placing order with no intent to execute will not use this }
/// <summary> /// The run initial submission event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunInitialSubmissionEvent(ITradingHistoryStack history) { // placing order with no intent to execute will not use this }
/// <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); }
/// <summary> /// The run post order event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEvent(ITradingHistoryStack history) { // we don't use post order event in ramping rule }
/// <summary> /// The run initial submission event. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunInitialSubmissionEvent(ITradingHistoryStack history) { // we don't use post order event in ramping rule }
/// <summary> /// The run order filled event. /// </summary> /// <param name="history"> /// The history. /// </param> public override void RunOrderFilledEvent(ITradingHistoryStack history) { // we don't analyse rules based on fills in the layering rule }
/// <summary> /// The run post order event delayed. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEventDelayed(ITradingHistoryStack history) { // do nothing }
/// <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 order filled event delayed. /// </summary> /// <param name="history"> /// The history. /// </param> public override void RunOrderFilledEventDelayed(ITradingHistoryStack history) { // do nothing }
/// <summary> /// The run post order event delayed triggered method with future window delay offset. /// </summary> /// <param name="history"> /// The history. /// </param> protected override void RunPostOrderEventDelayed(ITradingHistoryStack history) { this.EvaluateHighProfits(history, this.FutureUniverseFixedIncomeIntradayCache); }