/// <summary>
        /// The retrieve market traded volume.
        /// </summary>
        /// <param name="mostRecentTrade">
        /// The most recent trade.
        /// </param>
        /// <param name="tradingHours">
        /// The trading hours.
        /// </param>
        /// <param name="activeHistory">
        /// The active history.
        /// </param>
        /// <returns>
        /// The <see cref="long?"/>.
        /// </returns>
        private long?RetrieveMarketTradedVolume(Order mostRecentTrade, ITradingHours tradingHours, Stack <Order> activeHistory)
        {
            var closeTime =
                this.source == DataSource.AnyIntraday
                    ? UniverseDateTime
                    : tradingHours.ClosingInUtcForDay(UniverseDateTime);

            var marketDataRequest =
                new MarketDataRequest(
                    mostRecentTrade.Market?.MarketIdentifierCode,
                    mostRecentTrade.Instrument.Cfi,
                    mostRecentTrade.Instrument.Identifiers,
                    tradingHours.OpeningInUtcForDay(UniverseDateTime.Subtract(this.TradeBackwardWindowSize)),
                    closeTime,
                    RuleCtx.Id(),
                    this.source);

            var  hadMissingData     = false;
            long?marketTradedVolume = null;

            switch (this.source)
            {
            case DataSource.AnyInterday:
                var securityResultInterday = UniverseEquityInterdayCache.GetMarkets(marketDataRequest);
                hadMissingData = securityResultInterday.HadMissingData;

                if (!hadMissingData)
                {
                    marketTradedVolume = this.InterdayMarketTradedVolume(securityResultInterday);
                }

                break;

            case DataSource.AnyIntraday:
                var securityResultIntraday = UniverseEquityIntradayCache.GetMarkets(marketDataRequest);
                hadMissingData = securityResultIntraday.HadMissingData;

                if (!hadMissingData)
                {
                    marketTradedVolume = this.IntradayMarketTradedVolume(securityResultIntraday);
                }

                break;
            }

            if (hadMissingData && RunMode == RuleRunMode.ForceRun)
            {
                this.UpdatePassedFilterWithOrders(activeHistory);
                return(null);
            }

            if (hadMissingData && RunMode == RuleRunMode.ValidationRun)
            {
                this.logger.LogInformation($"market traded volume was not calculable for {mostRecentTrade.Instrument.Identifiers} due to missing data");
                this.hadMissingData = true;
                return(null);
            }

            return(marketTradedVolume);
        }
Пример #2
0
        /// <summary>
        /// The check window volume traded.
        /// </summary>
        /// <param name="securities">
        /// The securities.
        /// </param>
        /// <returns>
        /// The <see cref="VolumeBreach"/>.
        /// </returns>
        private VolumeBreach CheckWindowVolumeTraded(Stack <Order> securities)
        {
            if (!securities.Any())
            {
                return(new VolumeBreach());
            }

            var tradingHours = this.tradingHoursService.GetTradingHoursForMic(securities.Peek().Market?.MarketIdentifierCode);

            if (!tradingHours.IsValid)
            {
                this.logger.LogError($"Request for trading hours was invalid. MIC - {securities.Peek().Market?.MarketIdentifierCode}");
                return(new VolumeBreach());
            }

            var tradingDates = this.tradingHoursService.GetTradingDaysWithinRangeAdjustedToTime(
                tradingHours.OpeningInUtcForDay(UniverseDateTime.Subtract(this.TradeBackwardWindowSize)),
                tradingHours.ClosingInUtcForDay(UniverseDateTime),
                securities.Peek().Market?.MarketIdentifierCode);

            var marketDataRequest =
                new MarketDataRequest(
                    securities.Peek().Market.MarketIdentifierCode,
                    securities.Peek().Instrument.Cfi,
                    securities.Peek().Instrument.Identifiers,
                    UniverseDateTime.Subtract(this.TradeBackwardWindowSize), // implicitly correct (market closure event trigger)
                    UniverseDateTime,
                    this.ruleContext?.Id(),
                    DataSource.AnyIntraday);

            // marking the close should not have windows exceeding a few hours
            var marketResult = UniverseEquityIntradayCache.GetMarketsForRange(marketDataRequest, tradingDates, RunMode);

            if (marketResult.HadMissingData)
            {
                this.hadMissingData = true;
                return(new VolumeBreach());
            }

            var securityUpdates       = marketResult.Response;
            var securityVolume        = securityUpdates.Sum(su => su.SpreadTimeBar.Volume.Traded);
            var thresholdVolumeTraded = securityVolume * this.equitiesParameters.PercentageThresholdWindowVolume;

            if (thresholdVolumeTraded == null)
            {
                this.hadMissingData = true;
                return(new VolumeBreach());
            }

            var result =
                this.CalculateVolumeBreaches(
                    securities,
                    thresholdVolumeTraded.GetValueOrDefault(0),
                    securityVolume);

            return(result);
        }
Пример #3
0
        /// <summary>
        /// The exceeds trading volume in window threshold.
        /// </summary>
        /// <param name="orders">
        /// The orders.
        /// </param>
        /// <param name="mostRecentTrade">
        /// The most recent trade.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        private bool ExceedsTradingVolumeInWindowThreshold(List <Order> orders, Order mostRecentTrade)
        {
            if (this.rampingParameters?.ThresholdVolumePercentageWindow == null ||
                this.rampingParameters.ThresholdVolumePercentageWindow <= 0 ||
                orders == null ||
                !orders.Any())
            {
                return(true);
            }

            var tradingHours = this.tradingHoursService.GetTradingHoursForMic(mostRecentTrade.Market?.MarketIdentifierCode);

            if (!tradingHours.IsValid)
            {
                this.logger.LogError($"Request for trading hours was invalid. MIC - {mostRecentTrade.Market?.MarketIdentifierCode}");
            }

            var tradingDates = this.tradingHoursService.GetTradingDaysWithinRangeAdjustedToTime(
                tradingHours.OpeningInUtcForDay(UniverseDateTime.Subtract(this.TradeBackwardWindowSize)),
                tradingHours.ClosingInUtcForDay(UniverseDateTime),
                mostRecentTrade.Market?.MarketIdentifierCode);

            var marketRequest =
                new MarketDataRequest(
                    mostRecentTrade.Market?.MarketIdentifierCode,
                    mostRecentTrade.Instrument.Cfi,
                    mostRecentTrade.Instrument.Identifiers,
                    tradingHours.OpeningInUtcForDay(UniverseDateTime.Subtract(this.TradeBackwardWindowSize)),
                    tradingHours.ClosingInUtcForDay(UniverseDateTime),
                    this.ruleContext?.Id(),
                    DataSource.AnyIntraday);

            var marketResult = UniverseEquityIntradayCache.GetMarketsForRange(marketRequest, tradingDates, RunMode);

            if (marketResult.HadMissingData)
            {
                this.logger.LogTrace($"Unable to fetch market data frames for {mostRecentTrade.Market.MarketIdentifierCode} at {UniverseDateTime}.");

                this.hadMissingData = true;
                return(false);
            }

            var securityDataTicks = marketResult.Response;
            var windowVolume      = securityDataTicks.Sum(sdt => sdt.SpreadTimeBar.Volume.Traded);
            var threshold         = (long)Math.Ceiling(this.rampingParameters.ThresholdVolumePercentageWindow.GetValueOrDefault(0) * windowVolume);

            if (threshold <= 0)
            {
                this.hadMissingData = true;
                this.logger.LogInformation($"Daily volume threshold of {threshold} was recorded.");
                return(false);
            }

            var tradedVolume = orders.Sum(_ => _.OrderFilledVolume.GetValueOrDefault(0));

            if (tradedVolume >= threshold)
            {
                return(true);
            }

            return(false);
        }
Пример #4
0
        /// <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);
        }
Пример #5
0
        /// <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));
        }
Пример #6
0
        /// <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());
        }
Пример #7
0
        /// <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);
        }