Esempio n. 1
0
        /// <summary>
        /// Преобразовать тиковую сделку.
        /// </summary>
        /// <param name="message">Тиковая сделка.</param>
        /// <returns>Поток <see cref="ExecutionMessage"/>.</returns>
        public IEnumerable <ExecutionMessage> ToExecutionLog(ExecutionMessage message)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

            if (!_stepsUpdated)
            {
                _securityDefinition.PriceStep  = message.GetTradePrice().GetDecimalInfo().EffectiveScale.GetPriceStep();
                _securityDefinition.VolumeStep = message.GetVolume().GetDecimalInfo().EffectiveScale.GetPriceStep();
                _stepsUpdated = true;
            }

            //if (message.DataType != ExecutionDataTypes.Trade)
            //	throw new ArgumentOutOfRangeException("Тип данных не может быть {0}.".Put(message.DataType), "message");

            _lastTradeDate = message.LocalTime.Date;

            return(ProcessExecution(message));
        }
Esempio n. 2
0
        /// <summary>
        /// To convert the tick trade.
        /// </summary>
        /// <param name="tick">Tick trade.</param>
        /// <returns>Stream <see cref="ExecutionMessage"/>.</returns>
        public IEnumerable <ExecutionMessage> ToExecutionLog(ExecutionMessage tick)
        {
            if (tick == null)
            {
                throw new ArgumentNullException(nameof(tick));
            }

            if (!_priceStepUpdated)
            {
                _securityDefinition.PriceStep = tick.GetTradePrice().GetDecimalInfo().EffectiveScale.GetPriceStep();
                _priceStepUpdated             = true;
            }

            if (!_volumeStepUpdated)
            {
                var tickVolume = tick.TradeVolume;

                if (tickVolume != null)
                {
                    _securityDefinition.VolumeStep = tickVolume.Value.GetDecimalInfo().EffectiveScale.GetPriceStep();
                    _volumeStepUpdated             = true;
                }
            }

            //if (tick.ExecutionType != ExecutionTypes.Tick)
            //	throw new ArgumentOutOfRangeException(nameof(tick), tick.ExecutionType, LocalizedStrings.Str1655);

            //_lastTradeDate = message.LocalTime.Date;

            var retVal = new List <ExecutionMessage>();

            var bestBid = _bids.FirstOrDefault();
            var bestAsk = _asks.FirstOrDefault();

            var tradePrice = tick.GetTradePrice();
            var volume     = tick.TradeVolume ?? 1;
            var time       = tick.LocalTime;

            if (bestBid.Value != null && tradePrice <= bestBid.Key)
            {
                // тик попал в биды, значит была крупная заявка по рынку на продажу,
                // которая возможна исполнила наши заявки

                ProcessMarketOrder(retVal, _bids, tick.ServerTime, tick.LocalTime, Sides.Sell, tradePrice, volume);

                // подтягиваем противоположные котировки и снимаем лишние заявки
                TryCreateOppositeOrder(retVal, _asks, time, tick.ServerTime, tradePrice, volume, Sides.Buy);
            }
            else if (bestAsk.Value != null && tradePrice >= bestAsk.Key)
            {
                // тик попал в аски, значит была крупная заявка по рынку на покупку,
                // которая возможна исполнила наши заявки

                ProcessMarketOrder(retVal, _asks, tick.ServerTime, tick.LocalTime, Sides.Buy, tradePrice, volume);

                TryCreateOppositeOrder(retVal, _bids, time, tick.ServerTime, tradePrice, volume, Sides.Sell);
            }
            else if (bestBid.Value != null && bestAsk.Value != null && bestBid.Key < tradePrice && tradePrice < bestAsk.Key)
            {
                // тик попал в спред, значит в спреде до сделки была заявка.
                // создаем две лимитки с разных сторон, но одинаковой ценой.
                // если в эмуляторе есть наша заявка на этом уровне, то она исполниться.
                // если нет, то эмулятор взаимно исполнит эти заявки друг об друга

                var originSide = GetOrderSide(tick);

                retVal.Add(CreateMessage(time, tick.ServerTime, originSide, tradePrice, volume + (_securityDefinition.VolumeStep ?? 1 * _settings.VolumeMultiplier), tif: TimeInForce.MatchOrCancel));

                var spreadStep = _settings.SpreadSize * GetPriceStep();

                // try to fill depth gaps

                var newBestPrice = tradePrice + spreadStep;

                var depth = _settings.MaxDepth;
                while (--depth > 0)
                {
                    var diff = bestAsk.Key - newBestPrice;

                    if (diff > 0)
                    {
                        retVal.Add(CreateMessage(time, tick.ServerTime, Sides.Sell, newBestPrice, 0));
                        newBestPrice += spreadStep * _priceRandom.Next(1, _settings.SpreadSize);
                    }
                    else
                    {
                        break;
                    }
                }

                newBestPrice = tradePrice - spreadStep;

                depth = _settings.MaxDepth;
                while (--depth > 0)
                {
                    var diff = newBestPrice - bestBid.Key;

                    if (diff > 0)
                    {
                        retVal.Add(CreateMessage(time, tick.ServerTime, Sides.Buy, newBestPrice, 0));
                        newBestPrice -= spreadStep * _priceRandom.Next(1, _settings.SpreadSize);
                    }
                    else
                    {
                        break;
                    }
                }

                retVal.Add(CreateMessage(time, tick.ServerTime, originSide.Invert(), tradePrice, volume, tif: TimeInForce.MatchOrCancel));
            }
            else
            {
                // если у нас стакан был полу пустой, то тик формирует некий ценовой уровень в стакана,
                // так как прошедщая заявка должна была обо что-то удариться. допускаем, что после
                // прохождения сделки на этом ценовом уровне остался объем равный тиковой сделки

                var hasOpposite = true;

                Sides originSide;

                // определяем направление псевдо-ранее существовавшей заявки, из которой получился тик
                if (bestBid.Value != null)
                {
                    originSide = Sides.Sell;
                }
                else if (bestAsk.Value != null)
                {
                    originSide = Sides.Buy;
                }
                else
                {
                    originSide  = GetOrderSide(tick);
                    hasOpposite = false;
                }

                retVal.Add(CreateMessage(time, tick.ServerTime, originSide, tradePrice, volume));

                // если стакан был полностью пустой, то формируем сразу уровень с противоположной стороны
                if (!hasOpposite)
                {
                    var oppositePrice = tradePrice + _settings.SpreadSize * GetPriceStep() * (originSide == Sides.Buy ? 1 : -1);

                    if (oppositePrice > 0)
                    {
                        retVal.Add(CreateMessage(time, tick.ServerTime, originSide.Invert(), oppositePrice, volume));
                    }
                }
            }

            if (!HasDepth(time))
            {
                // если стакан слишком разросся, то удаляем его хвосты (не удаляя пользовательские заявки)
                CancelWorstQuote(retVal, time, tick.ServerTime, Sides.Buy, _bids);
                CancelWorstQuote(retVal, time, tick.ServerTime, Sides.Sell, _asks);
            }

            _prevTickPrice = tradePrice;

            return(retVal);
        }
        private IEnumerable <ExecutionMessage> ProcessExecution(ExecutionMessage message)
        {
            var retVal = new List <ExecutionMessage>();

            var bestBid = _bids.FirstOrDefault();
            var bestAsk = _asks.FirstOrDefault();

            var tradePrice = message.GetTradePrice();
            var volume     = message.GetVolume();

            if (bestBid.Value != null && tradePrice <= bestBid.Key)
            {
                // тик попал в биды, значит была крупная заявка по рынку на продажу,
                // которая возможна исполнила наши заявки

                ProcessMarketOrder(retVal, _bids, message, Sides.Sell);

                // подтягиваем противоположные котировки и снимаем лишние заявки
                TryCreateOppositeOrder(retVal, _asks, message.LocalTime, tradePrice, volume, Sides.Buy);
                CancelWorstQuotes(retVal, message.LocalTime);
            }
            else if (bestAsk.Value != null && tradePrice >= bestAsk.Key)
            {
                // тик попал в аски, значит была крупная заявка по рынку на покупку,
                // которая возможна исполнила наши заявки

                ProcessMarketOrder(retVal, _asks, message, Sides.Buy);

                TryCreateOppositeOrder(retVal, _bids, message.LocalTime, tradePrice, volume, Sides.Sell);
                CancelWorstQuotes(retVal, message.LocalTime);
            }
            else if (bestBid.Value != null && bestAsk.Value != null && bestBid.Key < tradePrice && tradePrice < bestAsk.Key)
            {
                // тик попал в спред, значит в спреде до сделки была заявка.
                // создаем две лимитки с разных сторон, но одинаковой ценой.
                // если в эмуляторе есть наша заявка на этом уровне, то она исполниться.
                // если нет, то эмулятор взаимно исполнит эти заявки друг об друга

                var originSide = GetOrderSide(message);

                retVal.Add(CreateMessage(message.LocalTime, originSide, tradePrice, volume + (_securityDefinition.VolumeStep ?? 1 * _settings.VolumeMultiplier), tif: TimeInForce.MatchOrCancel));

                var spreadStep = _settings.SpreadSize * GetPriceStep();

                // try to fill depth gaps

                var newBestPrice = tradePrice + spreadStep;

                while (true)
                {
                    var diff = bestAsk.Key - newBestPrice;

                    if (diff > 0)
                    {
                        retVal.Add(CreateMessage(message.LocalTime, Sides.Sell, newBestPrice, 0));
                        newBestPrice += spreadStep * _priceRandom.Next(1, _settings.SpreadSize);
                    }
                    else
                    {
                        break;
                    }
                }

                newBestPrice = tradePrice - spreadStep;

                while (true)
                {
                    var diff = newBestPrice - bestBid.Key;

                    if (diff > 0)
                    {
                        retVal.Add(CreateMessage(message.LocalTime, Sides.Buy, newBestPrice, 0));
                        newBestPrice -= spreadStep * _priceRandom.Next(1, _settings.SpreadSize);
                    }
                    else
                    {
                        break;
                    }
                }

                retVal.Add(CreateMessage(message.LocalTime, originSide.Invert(), tradePrice, volume, tif: TimeInForce.MatchOrCancel));

                CancelWorstQuotes(retVal, message.LocalTime);
            }
            else
            {
                // если у нас стакан был полу пустой, то тик формирует некий ценовой уровень в стакана,
                // так как прошедщая заявка должна была обо что-то удариться. допускаем, что после
                // прохождения сделки на этом ценовом уровне остался объем равный тиковой сделки

                var hasOpposite = true;

                Sides originSide;

                // определяем направление псевдо-ранее существовавшей заявки, из которой получился тик
                if (bestBid.Value != null)
                {
                    originSide = Sides.Sell;
                }
                else if (bestAsk.Value != null)
                {
                    originSide = Sides.Buy;
                }
                else
                {
                    originSide  = GetOrderSide(message);
                    hasOpposite = false;
                }

                retVal.Add(CreateMessage(message.LocalTime, originSide, tradePrice, volume));

                // если стакан был полностью пустой, то формируем сразу уровень с противоположной стороны
                if (!hasOpposite)
                {
                    var oppositePrice = tradePrice + _settings.SpreadSize * GetPriceStep() * (originSide == Sides.Buy ? 1 : -1);

                    if (oppositePrice > 0)
                    {
                        retVal.Add(CreateMessage(message.LocalTime, originSide.Invert(), oppositePrice, volume));
                    }
                }
            }

            _prevTickPrice = tradePrice;

            return(retVal);
        }
Esempio n. 4
0
        /// <summary>
        /// To calculate trade profitability. If the trade was already processed earlier, previous information returns.
        /// </summary>
        /// <param name="trade">Trade.</param>
        /// <returns>Information on new trade.</returns>
        public PnLInfo Process(ExecutionMessage trade)
        {
            if (trade == null)
            {
                throw new ArgumentNullException(nameof(trade));
            }

            var closedVolume = 0m;
            var pnl          = 0m;
            var volume       = trade.SafeGetVolume();
            var price        = trade.GetTradePrice();

            _unrealizedPnL = null;

            lock (_openedTrades.SyncRoot)
            {
                if (_openedTrades.Count > 0)
                {
                    var currTrade = _openedTrades.Peek();

                    if (_openedPosSide != trade.Side)
                    {
                        while (volume > 0)
                        {
                            if (currTrade == null)
                            {
                                currTrade = _openedTrades.Peek();
                            }

                            var diff = currTrade.Second.Min(volume);
                            closedVolume += diff;

                            pnl += GetPnL(currTrade.First, diff, _openedPosSide, price);

                            volume           -= diff;
                            currTrade.Second -= diff;

                            if (currTrade.Second != 0)
                            {
                                continue;
                            }

                            currTrade = null;
                            _openedTrades.Pop();

                            if (_openedTrades.Count == 0)
                            {
                                break;
                            }
                        }
                    }
                }

                if (volume > 0)
                {
                    _openedPosSide = trade.Side;
                    _openedTrades.Push(RefTuple.Create(price, volume));
                }

                RealizedPnL += _multiplier * pnl;
            }

            return(new PnLInfo(trade, closedVolume, pnl));
        }