예제 #1
0
        /// <summary>
        /// To convert the tick trade.
        /// </summary>
        /// <param name="message">Tick trade.</param>
        /// <returns>Stream <see cref="ExecutionMessage"/>.</returns>
        public IEnumerable <ExecutionMessage> ToExecutionLog(ExecutionMessage message)
        {
            if (message == null)
            {
                throw new ArgumentNullException("message");
            }

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

            if (!_volumeStepUpdated)
            {
                _securityDefinition.VolumeStep = message.SafeGetVolume().GetDecimalInfo().EffectiveScale.GetPriceStep();
                _volumeStepUpdated             = true;
            }

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

            //_lastTradeDate = message.LocalTime.Date;

            return(ProcessExecution(message));
        }
예제 #2
0
        /// <summary>
        /// To calculate commission.
        /// </summary>
        /// <param name="message">The message containing the information about the order or own trade.</param>
        /// <returns>The commission. If the commission can not be calculated then <see langword="null" /> will be returned.</returns>
        protected override decimal?OnProcessExecution(ExecutionMessage message)
        {
            if (!message.HasTradeInfo())
            {
                return(null);
            }

            _currentTurnOver += message.GetTradePrice() * message.SafeGetVolume();

            if (_currentTurnOver < TurnOver)
            {
                return(null);
            }

            return((decimal)Value);
        }
예제 #3
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));
        }
        /// <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)
            {
                _securityDefinition.VolumeStep = tick.SafeGetVolume().GetDecimalInfo().EffectiveScale.GetPriceStep();
                _volumeStepUpdated             = true;
            }

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

            //_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);
        }
예제 #5
0
        private void ProcessMarketOrder(List <ExecutionMessage> retVal, SortedDictionary <decimal, RefPair <List <ExecutionMessage>, QuoteChange> > quotes, ExecutionMessage tradeMessage, Sides orderSide)
        {
            // вычисляем объем заявки по рынку, который смог бы пробить текущие котировки.

            var tradePrice = tradeMessage.GetTradePrice();
            // bigOrder - это наша большая рыночная заявка, которая способствовала появлению tradeMessage
            var bigOrder  = CreateMessage(tradeMessage.LocalTime, orderSide, tradePrice, 0, tif: TimeInForce.MatchOrCancel);
            var sign      = orderSide == Sides.Buy ? -1 : 1;
            var hasQuotes = false;

            foreach (var pair in quotes)
            {
                var quote = pair.Value.Second;

                if (quote.Price * sign > tradeMessage.TradePrice * sign)
                {
                    bigOrder.Volume += quote.Volume;
                }
                else
                {
                    if (quote.Price == tradeMessage.TradePrice)
                    {
                        bigOrder.Volume += tradeMessage.Volume;

                        //var diff = tradeMessage.Volume - quote.Volume;

                        //// если объем котиовки был меньше объема сделки
                        //if (diff > 0)
                        //	retVal.Add(CreateMessage(tradeMessage.LocalTime, quote.Side, quote.Price, diff));
                    }
                    else
                    {
                        if ((tradePrice - quote.Price).Abs() == _securityDefinition.PriceStep)
                        {
                            // если на один шаг цены выше/ниже есть котировка, то не выполняем никаких действий
                            // иначе добавляем новый уровень в стакан, чтобы не было большого расхождения цен.
                            hasQuotes = true;
                        }

                        break;
                    }

                    //// если котировки с ценой сделки вообще не было в стакане
                    //else if (quote.Price * sign < tradeMessage.TradePrice * sign)
                    //{
                    //	retVal.Add(CreateMessage(tradeMessage.LocalTime, quote.Side, tradeMessage.Price, tradeMessage.Volume));
                    //}
                }
            }

            retVal.Add(bigOrder);

            // если собрали все котировки, то оставляем заявку в стакане по цене сделки
            if (!hasQuotes)
            {
                retVal.Add(CreateMessage(tradeMessage.LocalTime, orderSide.Invert(), tradePrice, tradeMessage.SafeGetVolume()));
            }
        }