예제 #1
0
        /// <summary>
        /// Рассчитать комиссию.
        /// </summary>
        /// <param name="message">Сообщение, содержащее информацию по заявке или собственной сделке.</param>
        /// <returns>Комиссия. Если комиссию рассчитать невозможно, то будет возвращено <see langword="null"/>.</returns>
        protected override decimal?OnProcessExecution(ExecutionMessage message)
        {
            if (message.ExecutionType != ExecutionTypes.Trade)
            {
                return(null);
            }

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

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

            return((decimal)Value);
        }
예제 #2
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));
        }
예제 #3
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.GetVolume()));
            }
        }
예제 #4
0
        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);
        }
예제 #5
0
        /// <summary>
        /// Рассчитать прибыльность сделки. Если сделка уже ранее была обработана, то возвращается предыдущая информация.
        /// </summary>
        /// <param name="trade">Сделка.</param>
        /// <returns>Информация о новой сделке.</returns>
        public PnLInfo Process(ExecutionMessage trade)
        {
            if (trade == null)
            {
                throw new ArgumentNullException("trade");
            }

            var closedVolume = 0m;
            var pnl          = 0m;
            var volume       = trade.GetVolume();
            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 += TraderHelper.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>
        /// Добавить новую строчку из лога заявок к стакану.
        /// </summary>
        /// <param name="item">Строчка лога заявок.</param>
        /// <returns>Был ли изменен стакан.</returns>
        public bool Update(ExecutionMessage item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }

            if (item.ExecutionType != ExecutionTypes.OrderLog)
            {
                throw new ArgumentException("item");
            }

            var volume = item.GetVolume();

            var changed = false;

            try
            {
                // Очистить стакан в вечерний клиринг
                if (item.ServerTime.TimeOfDay >= _clearingBeginTime)
                {
                    // Garic - переделал
                    // Очищаем только в рабочие дни поскольку в субботу/воскресенье допустима отмена заявок

                    if (_lastUpdateTime != null && _lastUpdateTime.Value.TimeOfDay < _clearingBeginTime && _exchange.WorkingTime.IsTradeDate(item.ServerTime.LocalDateTime, true))
                    {
                        _depth.ServerTime = item.ServerTime;
                        _depth.Bids       = Enumerable.Empty <QuoteChange>();
                        _depth.Asks       = Enumerable.Empty <QuoteChange>();

                        _matchingOrder = null;
                        changed        = true;
                    }
                }

                _lastUpdateTime = item.ServerTime.LocalDateTime;

                if (item.IsSystem == false || item.TradePrice != null || item.Price == 0 /* нулевая цена может появится при поставке опционов */)
                {
                    return(changed);
                }

                if (item.IsOrderLogRegistered())
                {
                    changed = TryApplyTrades(null);

                    if (
                        (item.Side == Sides.Buy && (_depth.Asks.IsEmpty() || item.Price < _depth.Asks.First().Price)) ||
                        (item.Side == Sides.Sell && (_depth.Bids.IsEmpty() || item.Price > _depth.Bids.First().Price))
                        )
                    {
                        if (item.TimeInForce == TimeInForce.PutInQueue)
                        {
                            var quotes = (item.Side == Sides.Buy ? _bids : _asks);
                            var quote  = quotes.TryGetValue(item.Price);

                            if (quote == null)
                            {
                                quote = new QuoteChange
                                {
                                    Side   = item.Side,
                                    Price  = item.Price,
                                    Volume = volume,
                                };

                                quotes.Add(item.Price, quote);

                                if (item.Side == Sides.Buy)
                                {
                                    _depth.Bids = GetArray(quotes);
                                }
                                else
                                {
                                    _depth.Asks = GetArray(quotes);
                                }
                            }
                            else
                            {
                                quote.Volume += volume;
                            }

                            changed = true;
                        }
                    }
                    else
                    {
                        _matchingOrder = (ExecutionMessage)item.Clone();

                        // mika
                        // из-за того, что могут быть кросс-сделки, матчинг только по заявкам невозможен
                        // (сначала идет регистрация вглубь стакана, затем отмена по причине кросс-сделки)
                        // http://forum.rts.ru/viewtopic.asp?t=24197
                        //
                    }
                }
                else if (item.IsOrderLogCanceled())
                {
                    var isSame = _matchingOrder != null && _matchingOrder.OrderId == item.OrderId;

                    changed = TryApplyTrades(item);

                    if (!isSame && item.TimeInForce == TimeInForce.PutInQueue)
                    {
                        // http://forum.rts.ru/viewtopic.asp?t=24197
                        if (item.GetOrderLogCancelReason() != OrderLogCancelReasons.CrossTrade)
                        {
                            var quotes = (item.Side == Sides.Buy ? _bids : _asks);
                            var quote  = quotes.TryGetValue(item.Price);

                            if (quote != null)
                            {
                                quote.Volume -= volume;

                                if (quote.Volume <= 0)
                                {
                                    quotes.Remove(item.Price);

                                    if (item.Side == Sides.Buy)
                                    {
                                        _depth.Bids = GetArray(quotes);
                                    }
                                    else
                                    {
                                        _depth.Asks = GetArray(quotes);
                                    }
                                }
                            }
                        }

                        changed = true;
                    }
                }
                else
                {
                    throw new ArgumentException(LocalizedStrings.Str943Params.Put(item), "item");

                    // для одной сделки соответствуют две строчки в ОЛ
                    //_trades[item.Trade.Id] = item;
                }
            }
            finally
            {
                if (changed)
                {
                    _depth.ServerTime = item.ServerTime;
                }
            }

            return(changed);
        }
        private bool TryApplyTrades(ExecutionMessage item)
        {
            if (_matchingOrder == null)
            {
                return(false);
            }

            try
            {
                var volume = _matchingOrder.GetVolume();

                if (item != null && _matchingOrder.OrderId == item.OrderId)
                {
                    volume -= item.GetVolume();
                }

                // если заявка была вся отменена. например, по причине http://forum.rts.ru/viewtopic.asp?t=24197
                if (volume == 0)
                {
                    return(false);
                }

                var removingQuotes = new List <Tuple <QuoteChange, decimal> >();

                foreach (var quote in (_matchingOrder.Side == Sides.Buy ? _depth.Asks : _depth.Bids))
                {
                    if ((_matchingOrder.Side == Sides.Buy && _matchingOrder.Price < quote.Price) || (_matchingOrder.Side == Sides.Sell && _matchingOrder.Price > quote.Price))
                    {
                        break;
                    }

                    if (volume >= quote.Volume)
                    {
                        removingQuotes.Add(Tuple.Create(quote, quote.Volume));

                        volume -= quote.Volume;

                        if (volume == 0)
                        {
                            break;
                        }
                    }
                    else
                    {
                        removingQuotes.Add(Tuple.Create(quote, volume));

                        volume = 0;
                        break;
                    }
                }

                // в текущей момент Плаза не транслирует признак MatchOrCancel через ОЛ, поэтому сделано на будущее
                if (_matchingOrder.TimeInForce == TimeInForce.MatchOrCancel && volume > 0)
                {
                    return(false);
                }

                foreach (var removingQuote in removingQuotes)
                {
                    var quotes = (_matchingOrder.Side.Invert() == Sides.Buy ? _bids : _asks);

                    var quote = quotes.TryGetValue(removingQuote.Item1.Price);

                    if (quote != null)
                    {
                        quote.Volume -= removingQuote.Item2;

                        if (quote.Volume <= 0)
                        {
                            quotes.Remove(removingQuote.Item1.Price);
                        }
                    }
                }

                if (volume > 0)
                {
                    if (_matchingOrder.TimeInForce == TimeInForce.PutInQueue)
                    {
                        var quote = new QuoteChange
                        {
                            Side   = _matchingOrder.Side,
                            Price  = _matchingOrder.Price,
                            Volume = volume,
                        };

                        (quote.Side == Sides.Buy ? _bids : _asks).Add(quote.Price, quote);
                    }
                }

                _depth.Bids = GetArray(_bids);
                _depth.Asks = GetArray(_asks);

                return(true);
            }
            finally
            {
                _matchingOrder = null;
            }
        }