public LayeringRuleBreach( IFactorValue factorValue, ISystemProcessOperationContext operationContext, string correlationId, ILayeringRuleEquitiesParameters equitiesParameters, TimeSpan window, ITradePosition trades, FinancialInstrument security, RuleBreachDescription bidirectionalTradeBreach, RuleBreachDescription dailyVolumeTradeBreach, RuleBreachDescription windowVolumeTradeBreach, RuleBreachDescription priceMovementBreach, string description, string caseTitle, DateTime universeDateTime) { this.FactorValue = factorValue; this.EquitiesParameters = equitiesParameters; this.Window = window; this.Trades = trades; this.Security = security; this.BidirectionalTradeBreach = bidirectionalTradeBreach; this.DailyVolumeTradeBreach = dailyVolumeTradeBreach; this.WindowVolumeTradeBreach = windowVolumeTradeBreach; this.PriceMovementBreach = priceMovementBreach; this.RuleParameterId = equitiesParameters?.Id ?? string.Empty; this.SystemOperationId = operationContext.Id.ToString(); this.CorrelationId = correlationId; this.RuleParameters = equitiesParameters; this.Description = description ?? string.Empty; this.CaseTitle = caseTitle ?? string.Empty; this.UniverseDateTime = universeDateTime; }
/// <summary> /// The build description. /// </summary> /// <param name="mostRecentTrade"> /// The most recent trade. /// </param> /// <param name="priceMovement"> /// The price movement. /// </param> /// <param name="startTick"> /// The start tick. /// </param> /// <param name="endTick"> /// The end tick. /// </param> /// <returns> /// The <see cref="RuleBreachDescription"/>. /// </returns> private RuleBreachDescription BuildDescription( Order mostRecentTrade, decimal priceMovement, EquityInstrumentIntraDayTimeBar startTick, EquityInstrumentIntraDayTimeBar endTick) { switch (mostRecentTrade.OrderDirection) { case OrderDirections.BUY: case OrderDirections.COVER: return(priceMovement < 0 ? new RuleBreachDescription { RuleBreached = true, Description = $" Prices in {mostRecentTrade.Instrument.Name} moved from ({endTick.SpreadTimeBar.Price.Currency}) {endTick.SpreadTimeBar.Price.Value} to ({startTick.SpreadTimeBar.Price.Currency}) {startTick.SpreadTimeBar.Price.Value} for a net change of {startTick.SpreadTimeBar.Price.Currency} {priceMovement} in line with the layering price pressure influence." } : RuleBreachDescription.False()); case OrderDirections.SELL: case OrderDirections.SHORT: return(priceMovement > 0 ? new RuleBreachDescription { RuleBreached = true, Description = $" Prices in {mostRecentTrade.Instrument.Name} moved from ({endTick.SpreadTimeBar.Price.Currency}) {endTick.SpreadTimeBar.Price.Value} to ({startTick.SpreadTimeBar.Price.Currency}) {startTick.SpreadTimeBar.Price.Value} for a net change of {startTick.SpreadTimeBar.Price.Currency} {priceMovement} in line with the layering price pressure influence." } : RuleBreachDescription.False()); default: this.logger.LogError($"Layering rule is not taking into account a new order position value (handles buy/sell) {mostRecentTrade.OrderDirection} (Arg Out of Range)"); this.ruleContext.EventException($"Layering rule is not taking into account a new order position value (handles buy/sell) {mostRecentTrade.OrderDirection} (Arg Out of Range)"); return(RuleBreachDescription.False()); } }
/// <summary> /// The check daily volume breach. /// </summary> /// <param name="opposingPosition"> /// The opposing position. /// </param> /// <param name="mostRecentTrade"> /// The most recent trade. /// </param> /// <returns> /// The <see cref="RuleBreachDescription"/>. /// </returns> private RuleBreachDescription CheckDailyVolumeBreach( ITradePosition opposingPosition, Order mostRecentTrade) { var tradingHoursManager = this.tradingHoursService.GetTradingHoursForMic(mostRecentTrade.Market.MarketIdentifierCode); if (!tradingHoursManager.IsValid) { this.logger.LogInformation($"unable to fetch market data for ({mostRecentTrade.Market.MarketIdentifierCode}) for the most recent trade {mostRecentTrade?.Instrument?.Identifiers} the market data did not contain the security indicated as trading in that market"); this.hadMissingData = true; return(RuleBreachDescription.False()); } var marketRequest = new MarketDataRequest( mostRecentTrade.Market.MarketIdentifierCode, mostRecentTrade.Instrument.Cfi, mostRecentTrade.Instrument.Identifiers, tradingHoursManager.OpeningInUtcForDay(UniverseDateTime.Subtract(this.TradeBackwardWindowSize)), tradingHoursManager.ClosingInUtcForDay(UniverseDateTime), this.ruleContext?.Id(), DataSource.AnyInterday); var marketResult = UniverseEquityInterdayCache.Get(marketRequest); if (marketResult.HadMissingData) { this.logger.LogInformation($"unable to fetch market data for ({mostRecentTrade.Market.MarketIdentifierCode}) for the most recent trade {mostRecentTrade?.Instrument?.Identifiers} the market data did not contain the security indicated as trading in that market"); this.hadMissingData = true; return(RuleBreachDescription.False()); } var marketSecurityData = marketResult.Response; if (marketSecurityData?.DailySummaryTimeBar?.DailyVolume.Traded <= 0 || opposingPosition.TotalVolumeOrderedOrFilled() <= 0) { this.logger.LogInformation($"unable to evaluate for {mostRecentTrade?.Instrument?.Identifiers} either the market daily volume data was not available or the opposing position had a bad total volume value (daily volume){marketSecurityData?.DailySummaryTimeBar?.DailyVolume.Traded} - (opposing position){opposingPosition.TotalVolumeOrderedOrFilled()}"); this.hadMissingData = true; return(RuleBreachDescription.False()); } var percentageDailyVolume = (decimal)opposingPosition.TotalVolumeOrderedOrFilled() / (decimal)marketSecurityData?.DailySummaryTimeBar?.DailyVolume.Traded; if (percentageDailyVolume >= this.equitiesParameters.PercentageOfMarketDailyVolume) { return(new RuleBreachDescription { RuleBreached = true, Description = $" Percentage of market daily volume traded within a {this.equitiesParameters.Windows.BackwardWindowSize.TotalSeconds} second window exceeded the layering window threshold of {this.equitiesParameters.PercentageOfMarketDailyVolume * 100}%." }); } return(RuleBreachDescription.False()); }
/// <summary> /// The has rule breach. /// </summary> /// <param name="hasBidirectionalBreach"> /// The has bidirectional breach. /// </param> /// <param name="hasDailyVolumeBreach"> /// The has daily volume breach. /// </param> /// <param name="hasWindowVolumeBreach"> /// The has window volume breach. /// </param> /// <param name="priceMovementBreach"> /// The price movement breach. /// </param> /// <returns> /// The <see cref="bool"/>. /// </returns> private bool HasRuleBreach( RuleBreachDescription hasBidirectionalBreach, RuleBreachDescription hasDailyVolumeBreach, RuleBreachDescription hasWindowVolumeBreach, RuleBreachDescription priceMovementBreach) { return((hasBidirectionalBreach?.RuleBreached ?? false) || (hasDailyVolumeBreach?.RuleBreached ?? false) || (hasWindowVolumeBreach?.RuleBreached ?? false) || (priceMovementBreach?.RuleBreached ?? false)); }
/// <summary> /// The check for price movement. /// </summary> /// <param name="opposingPosition"> /// The opposing position. /// </param> /// <param name="mostRecentTrade"> /// The most recent trade. /// </param> /// <returns> /// The <see cref="RuleBreachDescription"/>. /// </returns> private RuleBreachDescription CheckForPriceMovement( ITradePosition opposingPosition, Order mostRecentTrade) { var startDate = opposingPosition.Get().Where(op => op.PlacedDate != null).Min(op => op.PlacedDate).GetValueOrDefault(); var endDate = opposingPosition.Get().Where(op => op.PlacedDate != null).Max(op => op.PlacedDate).GetValueOrDefault(); if (endDate.Subtract(startDate) < TimeSpan.FromMinutes(1)) { endDate = endDate.AddMinutes(1); } var marketRequest = new MarketDataRequest( mostRecentTrade.Market.MarketIdentifierCode, mostRecentTrade.Instrument.Cfi, mostRecentTrade.Instrument.Identifiers, startDate.Subtract(this.TradeBackwardWindowSize), endDate, this.ruleContext?.Id(), DataSource.AnyIntraday); var tradingDays = this.tradingHoursService.GetTradingDaysWithinRangeAdjustedToTime( UniverseDateTime.Subtract(this.TradeBackwardWindowSize), UniverseDateTime, mostRecentTrade.Market.MarketIdentifierCode); var marketResult = UniverseEquityIntradayCache.GetMarketsForRange(marketRequest, tradingDays, RunMode); if (marketResult.HadMissingData) { this.logger.LogInformation($"unable to fetch market data frames for {mostRecentTrade.Market.MarketIdentifierCode} at {UniverseDateTime}."); this.hadMissingData = true; return(RuleBreachDescription.False()); } if (mostRecentTrade.PlacedDate > endDate) { endDate = mostRecentTrade.PlacedDate.GetValueOrDefault(); } var securityDataTicks = marketResult.Response; var startTick = this.StartTick(securityDataTicks, startDate); if (startTick == null) { this.logger.LogInformation($"unable to fetch starting exchange tick data for ({startDate}) {mostRecentTrade.Market.MarketIdentifierCode} at {UniverseDateTime}."); this.hadMissingData = true; return(RuleBreachDescription.False()); } var endTick = this.EndTick(securityDataTicks, endDate); if (endTick == null) { this.logger.LogInformation($"unable to fetch ending exchange tick data for ({endDate}) {mostRecentTrade.Market.MarketIdentifierCode} at {UniverseDateTime}."); this.hadMissingData = true; return(RuleBreachDescription.False()); } var priceMovement = endTick.SpreadTimeBar.Price.Value - startTick.SpreadTimeBar.Price.Value; return(this.BuildDescription(mostRecentTrade, priceMovement, startTick, endTick)); }
/// <summary> /// The check window volume breach. /// </summary> /// <param name="opposingPosition"> /// The opposing position. /// </param> /// <param name="mostRecentTrade"> /// The most recent trade. /// </param> /// <returns> /// The <see cref="RuleBreachDescription"/>. /// </returns> private RuleBreachDescription CheckWindowVolumeBreach( ITradePosition opposingPosition, Order mostRecentTrade) { var marketDataRequest = new MarketDataRequest( mostRecentTrade.Market.MarketIdentifierCode, mostRecentTrade.Instrument.Cfi, mostRecentTrade.Instrument.Identifiers, UniverseDateTime.Subtract(this.TradeBackwardWindowSize), UniverseDateTime, this.ruleContext?.Id(), DataSource.AnyIntraday); var tradingDays = this.tradingHoursService.GetTradingDaysWithinRangeAdjustedToTime( UniverseDateTime.Subtract(this.TradeBackwardWindowSize), UniverseDateTime, mostRecentTrade.Market.MarketIdentifierCode); var securityResult = UniverseEquityIntradayCache.GetMarketsForRange(marketDataRequest, tradingDays, RunMode); if (securityResult.HadMissingData) { this.logger.LogWarning($"unable to fetch market data frames for {mostRecentTrade.Market.MarketIdentifierCode} at {UniverseDateTime}."); this.hadMissingData = true; return(RuleBreachDescription.False()); } var windowVolume = securityResult.Response.Sum(sdt => sdt?.SpreadTimeBar.Volume.Traded); if (windowVolume <= 0) { this.logger.LogInformation($"unable to sum meaningful volume from market data frames for volume window in {mostRecentTrade.Market.MarketIdentifierCode} at {UniverseDateTime}."); this.hadMissingData = true; return(RuleBreachDescription.False()); } if (opposingPosition.TotalVolumeOrderedOrFilled() <= 0) { this.logger.LogInformation($"unable to calculate opposing position volume window in {mostRecentTrade.Market.MarketIdentifierCode} at {UniverseDateTime}."); this.hadMissingData = true; return(RuleBreachDescription.False()); } var percentageWindowVolume = (decimal)opposingPosition.TotalVolumeOrderedOrFilled() / (decimal)windowVolume; if (percentageWindowVolume >= this.equitiesParameters.PercentageOfMarketWindowVolume) { return(new RuleBreachDescription { RuleBreached = true, Description = $" Percentage of market volume traded within a {this.equitiesParameters.Windows.BackwardWindowSize.TotalSeconds} second window exceeded the layering window threshold of {this.equitiesParameters.PercentageOfMarketWindowVolume * 100}%." }); } return(RuleBreachDescription.False()); }
/// <summary> /// The check position for layering. /// </summary> /// <param name="tradeWindow"> /// The trade window. /// </param> /// <param name="buyPosition"> /// The buy position. /// </param> /// <param name="sellPosition"> /// The sell position. /// </param> /// <param name="tradingPosition"> /// The trading position. /// </param> /// <param name="opposingPosition"> /// The opposing position. /// </param> /// <param name="mostRecentTrade"> /// The most recent trade. /// </param> /// <returns> /// The <see cref="ILayeringRuleBreach"/>. /// </returns> private ILayeringRuleBreach CheckPositionForLayering( Stack <Order> tradeWindow, ITradePosition buyPosition, ITradePosition sellPosition, ITradePosition tradingPosition, ITradePosition opposingPosition, Order mostRecentTrade) { var hasTradesInWindow = tradeWindow.Any(); RuleBreachDescription hasBidirectionalBreach = RuleBreachDescription.False(); RuleBreachDescription hasDailyVolumeBreach = RuleBreachDescription.False(); RuleBreachDescription hasWindowVolumeBreach = RuleBreachDescription.False(); RuleBreachDescription priceMovementBreach = RuleBreachDescription.False(); // ReSharper disable once LoopVariableIsNeverChangedInsideLoop while (hasTradesInWindow) { if (!tradeWindow.Any()) { // ReSharper disable once RedundantAssignment hasTradesInWindow = false; break; } var nextTrade = tradeWindow.Pop(); this.AddToPositions(buyPosition, sellPosition, nextTrade); if (!tradingPosition.Get().Any() || !opposingPosition.Get().Any()) { continue; } // IF ALL PARAMETERS ARE NULL JUST DO THE BIDIRECTIONAL TRADE CHECK if (this.equitiesParameters.PercentageOfMarketDailyVolume == null && this.equitiesParameters.PercentageOfMarketWindowVolume == null && this.equitiesParameters.CheckForCorrespondingPriceMovement == null) { hasBidirectionalBreach = new RuleBreachDescription { RuleBreached = true, Description = " Trading in both buy/sell positions simultaneously was detected." }; } if (this.equitiesParameters.PercentageOfMarketDailyVolume != null) { hasDailyVolumeBreach = this.CheckDailyVolumeBreach(opposingPosition, mostRecentTrade); } if (this.equitiesParameters.PercentageOfMarketWindowVolume != null) { hasWindowVolumeBreach = this.CheckWindowVolumeBreach(opposingPosition, mostRecentTrade); } if (this.equitiesParameters.CheckForCorrespondingPriceMovement != null && this.equitiesParameters.CheckForCorrespondingPriceMovement.Value) { priceMovementBreach = this.CheckForPriceMovement(opposingPosition, mostRecentTrade); } } var allTradesInPositions = opposingPosition.Get().Concat(tradingPosition.Get()).ToList(); var allTrades = new TradePosition(allTradesInPositions); if (!this.HasRuleBreach( hasBidirectionalBreach, hasDailyVolumeBreach, hasWindowVolumeBreach, priceMovementBreach)) { return(null); } return(new LayeringRuleBreach( this.OrganisationFactorValue, this.ruleContext.SystemProcessOperationContext(), this.ruleContext.CorrelationId(), this.equitiesParameters, this.equitiesParameters.Windows?.BackwardWindowSize ?? TimeSpan.Zero, allTrades, mostRecentTrade.Instrument, hasBidirectionalBreach, hasDailyVolumeBreach, hasWindowVolumeBreach, priceMovementBreach, null, null, UniverseDateTime)); }