コード例 #1
0
        public Tuple <News, bool> ProcessNewsMessage(Security security, NewsMessage message)
        {
            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            var isNew = false;

            News news;

            if (!message.Id.IsEmpty())
            {
                news = _newsById.SafeAdd(message.Id, key =>
                {
                    isNew = true;
                    var n = EntityFactory.CreateNews();
                    n.Id  = key;
                    return(n);
                });
            }
            else
            {
                isNew = true;

                news = EntityFactory.CreateNews();
                _newsWithoutId.Add(news);
            }

            if (isNew)
            {
                news.ServerTime = message.ServerTime;
                news.LocalTime  = message.LocalTime;
            }

            if (!message.Source.IsEmpty())
            {
                news.Source = message.Source;
            }

            if (!message.Headline.IsEmpty())
            {
                news.Headline = message.Headline;
            }

            if (security != null)
            {
                news.Security = security;
            }

            if (!message.Story.IsEmpty())
            {
                news.Story = message.Story;
            }

            if (!message.BoardCode.IsEmpty())
            {
                news.Board = ExchangeBoard.GetOrCreateBoard(message.BoardCode);
            }

            if (message.Url != null)
            {
                news.Url = message.Url;
            }

            message.CopyExtensionInfo(news);

            return(Tuple.Create(news, isNew));
        }
コード例 #2
0
        public Tuple <OrderFail, bool> ProcessOrderFailMessage(Security security, ExecutionMessage message)
        {
            if (security == null)
            {
                throw new ArgumentNullException(nameof(security));
            }

            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            var data = GetData(security);

            Order order = null;

            if (!message.OrderStringId.IsEmpty())
            {
                order = data.OrdersByStringId.TryGetValue(message.OrderStringId);
            }

            bool isCancelled;

            if (order == null)
            {
                if (message.OriginalTransactionId == 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(message), message.OriginalTransactionId, LocalizedStrings.Str715);
                }

                var orders = data.Orders;

                order = (Order)orders.TryGetValue(CreateOrderKey(message.OrderType, message.OriginalTransactionId, true));

                if (order != null && order.Id == message.OrderId)
                {
                    isCancelled = true;
                }
                else
                {
                    order       = (Order)orders.TryGetValue(CreateOrderKey(message.OrderType, message.OriginalTransactionId, false));
                    isCancelled = false;
                }

                if (order == null)
                {
                    return(null);
                }
            }
            else
            {
                var pair = data.Orders.LastOrDefault(p => p.Value.Order == order);
                isCancelled = pair.Key.Item3;
            }

            // ServerTime для заявки - это время регистрации
            order.LastChangeTime = message.LocalTime;
            order.LocalTime      = message.LocalTime;

            if (message.OrderStatus != null)
            {
                order.Status = message.OrderStatus;
            }

            //для ошибок снятия не надо менять состояние заявки
            if (!isCancelled)
            {
                order.State = OrderStates.Failed;
            }

            if (message.Commission != null)
            {
                order.Commission = message.Commission;
            }

            message.CopyExtensionInfo(order);

            var error = message.Error ?? new InvalidOperationException(
                isCancelled ? LocalizedStrings.Str716 : LocalizedStrings.Str717);

            var fail = EntityFactory.CreateOrderFail(order, error);

            fail.ServerTime = message.ServerTime;
            fail.LocalTime  = message.LocalTime;
            return(Tuple.Create(fail, isCancelled));
        }
コード例 #3
0
        public Tuple <MyTrade, bool> ProcessMyTradeMessage(Security security, ExecutionMessage message)
        {
            if (security == null)
            {
                throw new ArgumentNullException(nameof(security));
            }

            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            var originalTransactionId = _orderStatusTransactions.Contains(message.OriginalTransactionId)
                                ? 0 : message.OriginalTransactionId;

            if (originalTransactionId == 0 && message.OrderId == null && message.OrderStringId.IsEmpty())
            {
                throw new ArgumentOutOfRangeException(nameof(message), originalTransactionId, LocalizedStrings.Str715);
            }

            var securityData = GetData(security);

            var myTrade = securityData.MyTrades.TryGetValue(Tuple.Create(originalTransactionId, message.TradeId ?? 0));

            if (myTrade != null)
            {
                return(Tuple.Create(myTrade, false));
            }

            var order = GetOrder(security, originalTransactionId, message.OrderId, message.OrderStringId);

            if (order == null)
            {
                return(null);
            }

            var trade = message.ToTrade(EntityFactory.CreateTrade(security, message.TradeId, message.TradeStringId));

            var isNew = false;

            myTrade = securityData.MyTrades.SafeAdd(Tuple.Create(order.TransactionId, trade.Id), key =>
            {
                isNew = true;

                var t = EntityFactory.CreateMyTrade(order, trade);

                if (t.ExtensionInfo == null)
                {
                    t.ExtensionInfo = new Dictionary <object, object>();
                }

                if (message.Commission != null)
                {
                    t.Commission = message.Commission;
                }

                if (message.Slippage != null)
                {
                    t.Slippage = message.Slippage;
                }

                if (message.PnL != null)
                {
                    t.PnL = message.PnL;
                }

                if (message.Position != null)
                {
                    t.Position = message.Position;
                }

                message.CopyExtensionInfo(t);

                //trades.Add(t);
                _myTrades.Add(t);

                return(t);
            });

            return(Tuple.Create(myTrade, isNew));

            // mika
            // http://stocksharp.com/forum/yaf_postst1072_Probliemy-so-sdielkami--pozitsiiami.aspx
            // из-за того, что сделки по заявке иногда приходит быстрее события NewOrders, неправильно расчитывается поза по стратегиям

            //var raiseOrderChanged = false;

            //trades.SyncDo(d =>
            //{
            //    var newBalance = order.Volume - d.Sum(t => t.Trade.Volume);

            //    if (order.Balance > newBalance)
            //    {
            //        raiseOrderChanged = true;

            //        order.Balance = newBalance;

            //        if (order.Balance == 0)
            //            order.State = OrderStates.Done;
            //    }
            //});

            //if (raiseOrderChanged)
            //    RaiseOrderChanged(order);
        }
コード例 #4
0
        public Tuple <Order, bool, bool> ProcessOrderMessage(Security security, ExecutionMessage message)
        {
            if (security == null)
            {
                throw new ArgumentNullException(nameof(security));
            }

            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            if (message.Error != null)
            {
                throw new ArgumentException(LocalizedStrings.Str714Params.PutEx(message));
            }

            bool isNew;

            var transactionId = message.TransactionId;

            if (transactionId == 0)
            {
                // ExecMsg.OriginalTransactionId == OrderStatMsg.TransactionId when orders info requested by OrderStatMsg
                transactionId = _orderStatusTransactions.Contains(message.OriginalTransactionId) ? 0 : message.OriginalTransactionId;
            }

            var securityData = GetData(security);

            var orderInfo = GetOrderInfo(securityData, message.OrderType, transactionId, message.OrderId, message.OrderStringId, trId =>
            {
                var o = EntityFactory.CreateOrder(security, message.OrderType, trId);

                o.Time        = message.ServerTime;
                o.Price       = message.OrderPrice;
                o.Volume      = message.OrderVolume ?? 0;
                o.Direction   = message.Side;
                o.Comment     = message.Comment;
                o.ExpiryDate  = message.ExpiryDate;
                o.Condition   = message.Condition;
                o.UserOrderId = message.UserOrderId;
                o.Portfolio   = message.PortfolioName.IsEmpty()
                                        ? _portfolios.FirstOrDefault().Value
                                        : ProcessPortfolio(message.PortfolioName).Item1;
                o.ClientCode = message.ClientCode;
                o.BrokerCode = message.BrokerCode;

                return(o);
            }, out isNew, true);

            if (orderInfo == null)
            {
                return(null);
            }

            var order       = orderInfo.Item1;
            var isCancelled = orderInfo.Item2;
            //var isReReregisterCancelled = orderInfo.Item3;
            var raiseNewOrder = orderInfo.Item3;

            var isPending = order.State == OrderStates.Pending;
            //var isPrevIdSet = (order.Id != null || !order.StringId.IsEmpty());

            bool isChanged;

            if (order.State == OrderStates.Done)
            {
                // данные о заявке могут приходить из маркет-дата и транзакционного адаптеров
                isChanged = false;
                //throw new InvalidOperationException("Изменение заявки в состоянии Done невозможно.");
            }
            else
            {
                if (message.OrderId != null)
                {
                    order.Id = message.OrderId.Value;
                }

                if (!message.OrderStringId.IsEmpty())
                {
                    order.StringId = message.OrderStringId;
                }

                if (!message.OrderBoardId.IsEmpty())
                {
                    order.BoardId = message.OrderBoardId;
                }

                //// некоторые коннекторы не транслируют при отмене отмененный объем
                //// esper. при перерегистрации заявок необходимо обновлять баланс
                //if (message.Balance > 0 || !isCancelled || isReReregisterCancelled)
                //{
                //	// BTCE коннектор не транслирует баланс заявки
                //	if (!(message.OrderState == OrderStates.Active && message.Balance == 0))
                //		order.Balance = message.Balance;
                //}

                if (message.Balance != null)
                {
                    order.Balance = message.Balance.Value;
                }

                // IB коннектор не транслирует состояние заявки в одном из своих сообщений
                if (message.OrderState != null)
                {
                    order.State = message.OrderState.Value;
                }

                if (order.Time == DateTimeOffset.MinValue)
                {
                    order.Time = message.ServerTime;
                }

                // для новых заявок используем серверное время,
                // т.к. заявка получена первый раз и не менялась
                // ServerTime для заявки - это время регистрации
                order.LastChangeTime = isNew ? message.ServerTime : message.LocalTime;
                order.LocalTime      = message.LocalTime;

                //нулевой объем может быть при перерегистрации
                if (order.Volume == 0 && message.OrderVolume != null)
                {
                    order.Volume = message.OrderVolume.Value;
                }

                if (message.Commission != null)
                {
                    order.Commission = message.Commission;
                }

                if (message.TimeInForce != null)
                {
                    order.TimeInForce = message.TimeInForce.Value;
                }

                if (isPending)
                {
                    if (order.State != OrderStates.Pending && message.Latency != null)
                    {
                        order.LatencyRegistration = message.Latency.Value;
                    }
                }
                else if (isCancelled && order.State == OrderStates.Done)
                {
                    if (message.Latency != null)
                    {
                        order.LatencyCancellation = message.Latency.Value;
                    }
                }

                isChanged = true;
            }

            //if (isNew || (!isPrevIdSet && (order.Id != null || !order.StringId.IsEmpty())))
            //{

            // так как биржевые идентифиаторы могут повторяться, то переписываем старые заявки новыми как наиболее актуальными

            if (order.Id != null)
            {
                securityData.OrdersById[order.Id.Value] = order;
                _allOrdersById[order.Id.Value]          = order;
            }

            if (!order.StringId.IsEmpty())
            {
                securityData.OrdersByStringId[order.StringId] = order;
                _allOrdersByStringId[order.StringId]          = order;
            }

            //}

            //if (message.OrderType == OrderTypes.Conditional && (message.DerivedOrderId != null || !message.DerivedOrderStringId.IsEmpty()))
            //{
            //	var derivedOrder = GetOrder(security, 0L, message.DerivedOrderId ?? 0, message.DerivedOrderStringId);

            //	if (order == null)
            //		_orderStopOrderAssociations.Add(Tuple.Create(message.DerivedOrderId ?? 0, message.DerivedOrderStringId), new RefPair<Order, Action<Order, Order>>(order, (s, o) => s.DerivedOrder = o));
            //	else
            //		order.DerivedOrder = derivedOrder;
            //}

            message.CopyExtensionInfo(order);

            return(Tuple.Create(order, raiseNewOrder, isChanged));
        }
コード例 #5
0
        public Tuple <MyTrade, bool> ProcessMyTradeMessage(Order order, Security security, ExecutionMessage message, long transactionId)
        {
            if (security == null)
            {
                throw new ArgumentNullException(nameof(security));
            }

            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            var securityData = GetData(security);

            if (transactionId == 0 && message.OrderId == null && message.OrderStringId.IsEmpty())
            {
                throw new ArgumentOutOfRangeException(nameof(message), transactionId, LocalizedStrings.Str715);
            }

            var myTrade = securityData.MyTrades.TryGetValue(Tuple.Create(transactionId, message.TradeId ?? 0));

            if (myTrade != null)
            {
                return(Tuple.Create(myTrade, false));
            }

            if (order == null)
            {
                order = GetOrder(security, transactionId, message.OrderId, message.OrderStringId);

                if (order == null)
                {
                    return(null);
                }
            }

            var trade = message.ToTrade(EntityFactory.CreateTrade(security, message.TradeId, message.TradeStringId));

            var isNew = false;

            myTrade = securityData.MyTrades.SafeAdd(Tuple.Create(order.TransactionId, trade.Id), key =>
            {
                isNew = true;

                var t = EntityFactory.CreateMyTrade(order, trade);

                if (t.ExtensionInfo == null)
                {
                    t.ExtensionInfo = new Dictionary <object, object>();
                }

                if (message.Commission != null)
                {
                    t.Commission = message.Commission;
                }

                if (message.Slippage != null)
                {
                    t.Slippage = message.Slippage;
                }

                if (message.PnL != null)
                {
                    t.PnL = message.PnL;
                }

                if (message.Position != null)
                {
                    t.Position = message.Position;
                }

                message.CopyExtensionInfo(t);

                _myTrades.Add(t);

                return(t);
            });

            return(Tuple.Create(myTrade, isNew));
        }
コード例 #6
0
        public IEnumerable <Tuple <OrderFail, bool> > ProcessOrderFailMessage(Order order, Security security, ExecutionMessage message)
        {
            if (security == null)
            {
                throw new ArgumentNullException(nameof(security));
            }

            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            var data = GetData(security);

            var orders = new List <Tuple <Order, bool> >();

            if (message.OriginalTransactionId == 0)
            {
                throw new ArgumentOutOfRangeException(nameof(message), message.OriginalTransactionId, LocalizedStrings.Str715);
            }

            var orderType = message.OrderType;

            if (order == null)
            {
                var cancelledOrder = data.Orders.TryGetValue(CreateOrderKey(orderType, message.OriginalTransactionId, true))?.Order;

                if (cancelledOrder == null && orderType == null)
                {
                    cancelledOrder = data.Orders.TryGetValue(CreateOrderKey(OrderTypes.Conditional, message.OriginalTransactionId, true))?.Order;

                    if (cancelledOrder != null)
                    {
                        orderType = OrderTypes.Conditional;
                    }
                }

                if (cancelledOrder != null /*&& order.Id == message.OrderId*/)
                {
                    orders.Add(Tuple.Create(cancelledOrder, true));
                }

                var registeredOrder = data.Orders.TryGetValue(CreateOrderKey(orderType, message.OriginalTransactionId, false))?.Order;

                if (registeredOrder == null && orderType == null)
                {
                    registeredOrder = data.Orders.TryGetValue(CreateOrderKey(OrderTypes.Conditional, message.OriginalTransactionId, false))?.Order;
                }

                if (registeredOrder != null)
                {
                    orders.Add(Tuple.Create(registeredOrder, false));
                }

                if (cancelledOrder == null && registeredOrder == null)
                {
                    if (!message.OrderStringId.IsEmpty())
                    {
                        order = data.OrdersByStringId.TryGetValue(message.OrderStringId);

                        if (order != null)
                        {
                            var pair = data.Orders.LastOrDefault(p => p.Value.Order == order);

                            if (pair.Key != null)
                            {
                                orders.Add(Tuple.Create(pair.Value.Order, pair.Key.Item3));
                            }
                        }
                    }
                }
            }
            else
            {
                if (data.Orders.ContainsKey(CreateOrderKey(order.Type, message.OriginalTransactionId, true)))
                {
                    orders.Add(Tuple.Create(order, true));
                }

                var registeredOrder = data.Orders.TryGetValue(CreateOrderKey(order.Type, message.OriginalTransactionId, false))?.Order;
                if (registeredOrder != null)
                {
                    orders.Add(Tuple.Create(registeredOrder, false));
                }
            }

            if (orders.Count == 0)
            {
                return(Enumerable.Empty <Tuple <OrderFail, bool> >());
            }

            return(orders.Select(t =>
            {
                var o = t.Item1;
                var isCancelTransaction = t.Item2;

                o.LastChangeTime = message.ServerTime;
                o.LocalTime = message.LocalTime;

                if (message.OrderStatus != null)
                {
                    o.Status = message.OrderStatus;
                }

                //для ошибок снятия не надо менять состояние заявки
                if (!isCancelTransaction)
                {
                    o.State = o.State.CheckModification(OrderStates.Failed);
                }

                if (message.Commission != null)
                {
                    o.Commission = message.Commission;
                }

                message.CopyExtensionInfo(o);

                var error = message.Error ?? new InvalidOperationException(isCancelTransaction ? LocalizedStrings.Str716 : LocalizedStrings.Str717);

                var fail = EntityFactory.CreateOrderFail(o, error);
                fail.ServerTime = message.ServerTime;
                fail.LocalTime = message.LocalTime;
                return Tuple.Create(fail, isCancelTransaction);
            }));
        }
コード例 #7
0
        public IEnumerable <Tuple <Order, bool, bool> > ProcessOrderMessage(Order order, Security security, ExecutionMessage message, long transactionId, out Tuple <Portfolio, bool, bool> pfInfo)
        {
            if (security == null)
            {
                throw new ArgumentNullException(nameof(security));
            }

            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            if (message.Error != null)
            {
                throw new ArgumentException(LocalizedStrings.Str714Params.PutEx(message));
            }

            var securityData = GetData(security);

            if (transactionId == 0 && message.OrderId == null && message.OrderStringId.IsEmpty())
            {
                throw new ArgumentException(LocalizedStrings.Str719);
            }

            pfInfo = null;

            var orders = securityData.Orders;

            if (transactionId == 0)
            {
                var info = orders.CachedValues.FirstOrDefault(i =>
                {
                    if (order != null)
                    {
                        return(i.Order == order);
                    }

                    if (message.OrderId != null)
                    {
                        return(i.Order.Id == message.OrderId);
                    }
                    else
                    {
                        return(i.Order.StringId.CompareIgnoreCase(message.OrderStringId));
                    }
                });

                if (info == null)
                {
                    return(null);
                    //throw new InvalidOperationException(LocalizedStrings.Str1156Params.Put(orderId.To<string>() ?? orderStringId));
                }

                var orderInfo = info.ApplyChanges(message, false);
                UpdateOrderIds(info.Order, securityData);
                return(new[] { orderInfo });
            }
            else
            {
                var cancelKey   = CreateOrderKey(message.OrderType, transactionId, true);
                var registerKey = CreateOrderKey(message.OrderType, transactionId, false);

                var cancelledInfo  = orders.TryGetValue(cancelKey);
                var registetedInfo = orders.TryGetValue(registerKey);

                // проверяем не отмененная ли заявка пришла
                if (cancelledInfo != null)                 // && (cancelledOrder.Id == orderId || (!cancelledOrder.StringId.IsEmpty() && cancelledOrder.StringId.CompareIgnoreCase(orderStringId))))
                {
                    var cancellationOrder = cancelledInfo.Order;

                    if (registetedInfo == null)
                    {
                        var i = cancelledInfo.ApplyChanges(message, true);
                        UpdateOrderIds(cancellationOrder, securityData);
                        return(new[] { i });
                    }

                    var retVal     = new List <Tuple <Order, bool, bool> >();
                    var orderState = message.OrderState;

                    if (orderState != null && cancellationOrder.State != OrderStates.Done && orderState != OrderStates.None && orderState != OrderStates.Pending)
                    {
                        cancellationOrder.State = cancellationOrder.State.CheckModification(OrderStates.Done);

                        if (message.Latency != null)
                        {
                            cancellationOrder.LatencyCancellation = message.Latency.Value;
                        }

                        retVal.Add(Tuple.Create(cancellationOrder, false, true));
                    }

                    var isCancelOrder = (message.OrderId != null && message.OrderId == cancellationOrder.Id) ||
                                        (message.OrderStringId != null && message.OrderStringId == cancellationOrder.StringId) ||
                                        (message.OrderBoardId != null && message.OrderBoardId == cancellationOrder.BoardId);

                    var regOrder = registetedInfo.Order;

                    if (!isCancelOrder)
                    {
                        var replacedInfo = registetedInfo.ApplyChanges(message, false);
                        UpdateOrderIds(regOrder, securityData);
                        retVal.Add(replacedInfo);
                    }

                    return(retVal);
                }

                if (registetedInfo == null)
                {
                    var o = EntityFactory.CreateOrder(security, message.OrderType, registerKey.Item1);

                    if (o == null)
                    {
                        throw new InvalidOperationException(LocalizedStrings.Str720Params.Put(registerKey.Item1));
                    }

                    o.Time        = message.ServerTime;
                    o.Price       = message.OrderPrice;
                    o.Volume      = message.OrderVolume ?? 0;
                    o.Direction   = message.Side;
                    o.Comment     = message.Comment;
                    o.ExpiryDate  = message.ExpiryDate;
                    o.Condition   = message.Condition;
                    o.UserOrderId = message.UserOrderId;
                    o.ClientCode  = message.ClientCode;
                    o.BrokerCode  = message.BrokerCode;

                    if (message.PortfolioName.IsEmpty())
                    {
                        o.Portfolio = _portfolios.FirstOrDefault().Value;
                    }
                    else
                    {
                        pfInfo      = ProcessPortfolio(message.PortfolioName);
                        o.Portfolio = ProcessPortfolio(message.PortfolioName).Item1;
                    }

                    if (o.ExtensionInfo == null)
                    {
                        o.ExtensionInfo = new Dictionary <object, object>();
                    }

                    AddOrder(o);
                    _allOrdersByTransactionId.Add(Tuple.Create(transactionId, false), o);

                    registetedInfo = new OrderInfo(o);
                    orders.Add(registerKey, registetedInfo);
                }

                var orderInfo = registetedInfo.ApplyChanges(message, false);

                if (orderInfo != null)
                {
                    UpdateOrderIds(registetedInfo.Order, securityData);
                    return(new[] { orderInfo });
                }
                else
                {
                    return(Enumerable.Empty <Tuple <Order, bool, bool> >());
                }
            }
        }
コード例 #8
0
		private void ProcessQuotesMessage(Security security, QuoteChangeMessage message)
		{
			if (MarketDepthChanged != null || MarketDepthsChanged != null)
			{
				var marketDepth = GetMarketDepth(security, message.IsFiltered);

				message.ToMarketDepth(marketDepth, GetSecurity);

				if (!message.IsFiltered)
					RaiseMarketDepthChanged(marketDepth);
			}
			else
			{
				lock (_marketDepths.SyncRoot)
				{
					var info = _marketDepths.SafeAdd(Tuple.Create(security, message.IsFiltered), key => new MarketDepthInfo(EntityFactory.CreateMarketDepth(security)));

					info.First.LocalTime = message.LocalTime;
					info.First.LastChangeTime = message.ServerTime;

					info.Second = message.Bids;
					info.Third = message.Asks;
				}
			}

			if (message.IsFiltered)
				return;

			var bestBid = message.GetBestBid();
			var bestAsk = message.GetBestAsk();
			var fromLevel1 = message.IsByLevel1;

			if (!fromLevel1 && (bestBid != null || bestAsk != null))
			{
				var values = GetSecurityValues(security);
				var changes = new List<KeyValuePair<Level1Fields, object>>(4);

				lock (values.SyncRoot)
				{
					if (bestBid != null)
					{
						values[(int)Level1Fields.BestBidPrice] = bestBid.Price;
						changes.Add(new KeyValuePair<Level1Fields, object>(Level1Fields.BestBidPrice, bestBid.Price));

						if (bestBid.Volume != 0)
						{
							values[(int)Level1Fields.BestBidVolume] = bestBid.Volume;
							changes.Add(new KeyValuePair<Level1Fields, object>(Level1Fields.BestBidVolume, bestBid.Volume));
						}
					}

					if (bestAsk != null)
					{
						values[(int)Level1Fields.BestAskPrice] = bestAsk.Price;
						changes.Add(new KeyValuePair<Level1Fields, object>(Level1Fields.BestAskPrice, bestAsk.Price));

						if (bestAsk.Volume != 0)
						{
							values[(int)Level1Fields.BestAskVolume] = bestAsk.Volume;
							changes.Add(new KeyValuePair<Level1Fields, object>(Level1Fields.BestAskVolume, bestAsk.Volume));
						}
					}
				}

				RaiseValuesChanged(security, changes, message.ServerTime, message.LocalTime);
			}

			if (UpdateSecurityLastQuotes)
			{
				var updated = false;

				if (!fromLevel1 || bestBid != null)
				{
					updated = true;
					security.BestBid = bestBid == null ? null : new Quote(security, bestBid.Price, bestBid.Volume, Sides.Buy);
				}

				if (!fromLevel1 || bestAsk != null)
				{
					updated = true;
					security.BestAsk = bestAsk == null ? null : new Quote(security, bestAsk.Price, bestAsk.Volume, Sides.Sell);
				}

				if (updated)
				{
					security.LocalTime = message.LocalTime;
					security.LastChangeTime = message.ServerTime;

					RaiseSecurityChanged(security);

					// стаканы по ALL обновляют BestXXX по конкретным инструментам
					if (security.Board.Code == AssociatedBoardCode)
					{
						var changedSecurities = new Dictionary<Security, RefPair<bool, bool>>();

						foreach (var bid in message.Bids)
						{
							if (bid.BoardCode.IsEmpty())
								continue;

							var innerSecurity = GetSecurity(new SecurityId
							{
								SecurityCode = security.Code,
								BoardCode = bid.BoardCode
							});

							var info = changedSecurities.SafeAdd(innerSecurity);

							if (info.First)
								continue;

							info.First = true;

							innerSecurity.BestBid = new Quote(innerSecurity, bid.Price, bid.Volume, Sides.Buy);
							innerSecurity.LocalTime = message.LocalTime;
							innerSecurity.LastChangeTime = message.ServerTime;
						}

						foreach (var ask in message.Asks)
						{
							if (ask.BoardCode.IsEmpty())
								continue;

							var innerSecurity = GetSecurity(new SecurityId
							{
								SecurityCode = security.Code,
								BoardCode = ask.BoardCode
							});

							var info = changedSecurities.SafeAdd(innerSecurity);

							if (info.Second)
								continue;

							info.Second = true;

							innerSecurity.BestAsk = new Quote(innerSecurity, ask.Price, ask.Volume, Sides.Sell);
							innerSecurity.LocalTime = message.LocalTime;
							innerSecurity.LastChangeTime = message.ServerTime;
						}
						
						RaiseSecuritiesChanged(changedSecurities.Keys.ToArray());
					}
				}
			}
		}