private void ProcessLevel1Depth(Level1ChangeMessage message, decimal bestBidPrice, decimal bestBidVolume, decimal bestAskPrice, decimal bestAskVolume, List <ExecutionMessage> retVal) { if (message.LocalTime.Date == _lastDepthDate) { return; } QuoteChange ask = null; QuoteChange bid = null; if (bestAskPrice != 0 && bestAskVolume != 0) { ask = new QuoteChange(Sides.Sell, bestAskPrice, bestAskVolume); } if (bestBidPrice != 0 && bestBidVolume != 0) { bid = new QuoteChange(Sides.Buy, bestBidPrice, bestBidVolume); } if (ask == null && bid == null) { return; } retVal.AddRange(ProcessQuoteChange(message.LocalTime, bid != null ? new[] { bid } : ArrayHelper <QuoteChange> .EmptyArray, ask != null ? new[] { ask } : ArrayHelper <QuoteChange> .EmptyArray)); }
private void AddExecMsg(List <ExecutionMessage> diff, DateTime time, QuoteChange quote, decimal volume, bool isSpread) { if (volume > 0) { diff.Add(CreateMessage(time, quote.Side, quote.Price, volume)); } else { volume = volume.Abs(); // matching only top orders (spread) if (isSpread && volume > 1 && _isMatch.Next()) { var tradeVolume = (int)volume / 2; diff.Add(new ExecutionMessage { Side = quote.Side, Volume = tradeVolume, ExecutionType = ExecutionTypes.Tick, SecurityId = SecurityId, LocalTime = time, TradePrice = quote.Price, }); // that tick will not affect on order book //volume -= tradeVolume; } diff.Add(CreateMessage(time, quote.Side, quote.Price, volume, true)); } }
private static QuoteChange[] DeserializeQuotes(BitArrayReader reader, QuoteMetaInfo metaInfo, Sides side, bool useLong, bool nonAdjustPrice) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } if (metaInfo == null) { throw new ArgumentNullException(nameof(metaInfo)); } var deltaCount = reader.ReadInt(); if (deltaCount == 0) { return(ArrayHelper.Empty <QuoteChange>()); } var quotes = new QuoteChange[deltaCount]; for (var i = 0; i < deltaCount; i++) { var prevPrice = metaInfo.FirstPrice; var price = reader.ReadPrice(ref prevPrice, metaInfo, useLong, nonAdjustPrice); metaInfo.FirstPrice = prevPrice; var volume = reader.ReadVolume(metaInfo); quotes[i] = new QuoteChange(side, price, volume); } return(quotes); }
/// <summary> /// Initializes a new instance of the <see cref="TimeQuoteChange"/>. /// </summary> /// <param name="quote">The quote, from which changes will be copied.</param> /// <param name="message">The message with quotes.</param> public TimeQuoteChange(QuoteChange quote, QuoteChangeMessage message) { if (quote == null) throw new ArgumentNullException("quote"); SecurityId = message.SecurityId; ServerTime = message.ServerTime; LocalTime = message.LocalTime; Price = quote.Price; Volume = quote.Volume; Side = quote.Side; }
private void OnProcessQuotes(string where, string[] quotes) { // paper_no нужно парсить из условия where, так как в поле paper_no передается всегда 0 var paperNo = where.Split('=')[1].Trim().To <int>(); var f = Wrapper.FieldsDepth; var bids = new List <QuoteChange>(); var asks = new List <QuoteChange>(); foreach (var quoteStr in quotes) { var cols = quoteStr.ToColumns(); var sellQty = f.SellQty.GetValue(cols); var price = f.Price.GetValue(cols); var buyQty = f.BuyQty.GetValue(cols); if (sellQty == 0 && buyQty == 0) { // If the sellQty and buyQty are 0 - that is our limit order // which is not a part of the market depth. Just skip it. continue; } QuoteChange quote; if (sellQty == 0) { quote = new QuoteChange(Sides.Buy, price, buyQty); bids.Insert(0, quote); } else { quote = new QuoteChange(Sides.Sell, price, sellQty); asks.Add(quote); } } if (bids.Count > 0 || asks.Count > 0) { SendOutMessage(new QuoteChangeMessage { Bids = bids, Asks = asks, SecurityId = new SecurityId { Native = paperNo }, IsSorted = true, ServerTime = CurrentTime.Convert(TimeHelper.Moscow), }); } }
/// <summary> /// Initializes a new instance of the <see cref="TimeQuoteChange"/>. /// </summary> /// <param name="side">Direction (buy or sell).</param> /// <param name="quote">The quote, from which changes will be copied.</param> /// <param name="message">The message with quotes.</param> public TimeQuoteChange(Sides side, QuoteChange quote, QuoteChangeMessage message) { if (message is null) { throw new ArgumentNullException(nameof(message)); } SecurityId = message.SecurityId; ServerTime = message.ServerTime; LocalTime = message.LocalTime; Quote = quote; Side = side; }
public NullableTimeQuoteChange(QuoteChange quote, QuoteChangeMessage message) { if (quote == null) { throw new ArgumentNullException(nameof(quote)); } ServerTime = message.ServerTime; LocalTime = message.LocalTime; Price = quote.Price; Volume = quote.Volume; Side = quote.Side; }
private void SessionOnStil2Update(ref structSTIL2Update structL2Update) { var depth = _depths[structL2Update.bstrSymbol]; var quote = new QuoteChange(structL2Update.bstrSide.ToSide(), (decimal)structL2Update.fPrice, structL2Update.nQty); var quotes = quote.Side == Sides.Sell ? depth.Item1 : depth.Item2; switch (structL2Update.bstrAction) { case "A": // add { quotes.Add(quote); break; } case "C": // change { quotes.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); quotes.Add(quote); break; } case "D": // delete { quotes.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); break; } } var board = structL2Update.bstrMaker; if (board.IsEmpty()) { board = AssociatedBoardCode; } var message = new QuoteChangeMessage { SecurityId = new SecurityId { SecurityCode = structL2Update.bstrSymbol, BoardCode = board, }, Asks = depth.Item1.ToArray(), Bids = depth.Item2.ToArray(), ServerTime = structL2Update.bstrTime.StrToTime(), }; SendOutMessage(message); }
/// <summary> /// Создать <see cref="TimeQuoteChange"/>. /// </summary> /// <param name="quote">Котировка, из которой будут скопированы изменения.</param> /// <param name="message">Сообщение с котировками.</param> public TimeQuoteChange(QuoteChange quote, QuoteChangeMessage message) { if (quote == null) { throw new ArgumentNullException("quote"); } SecurityId = message.SecurityId; ServerTime = message.ServerTime; LocalTime = message.LocalTime; Price = quote.Price; Volume = quote.Volume; Side = quote.Side; }
public static Quote ToQuote(this QuoteChange change, Sides side, Security security, Func <SecurityId, Security> getSecurity = null) { if (!change.BoardCode.IsEmpty() && getSecurity != null) { security = getSecurity(new SecurityId { SecurityCode = security.Code, BoardCode = change.BoardCode }); } var quote = new Quote(security, change.Price, change.Volume, side, change.OrdersCount, change.Condition); change.CopyExtensionInfo(quote); return(quote); }
private NullableTimeQuoteChange ToNullQuote(Sides side, QuoteChange quote, QuoteChangeMessage message) { if (message is null) { throw new ArgumentNullException(nameof(message)); } return(new NullableTimeQuoteChange { ServerTime = message.ServerTime, LocalTime = message.LocalTime, Side = side, Quote = quote, }); }
public NullableTimeQuoteChange(Sides side, QuoteChange quote, QuoteChangeMessage message) { if (quote == null) { throw new ArgumentNullException(nameof(quote)); } ServerTime = message.ServerTime; LocalTime = message.LocalTime; Price = quote.Price; Volume = quote.Volume; Side = side; OrdersCount = quote.OrdersCount; Condition = quote.Condition; }
/// <inheritdoc /> protected override NullableTimeQuoteChange Read(FastCsvReader reader, IMarketDataMetaInfo metaInfo) { var quote = new NullableTimeQuoteChange { ServerTime = reader.ReadTime(metaInfo.Date), }; var price = reader.ReadNullableDecimal(); var volume = reader.ReadNullableDecimal(); quote.Side = reader.ReadEnum <Sides>(); int?ordersCount = null; if ((reader.ColumnCurr + 1) < reader.ColumnCount) { ordersCount = reader.ReadNullableInt(); } QuoteConditions condition = default; if ((reader.ColumnCurr + 1) < reader.ColumnCount) { condition = reader.ReadNullableEnum <QuoteConditions>() ?? default; } if (price != null) { var qq = new QuoteChange { Price = price.Value, Volume = volume ?? 0, OrdersCount = ordersCount, Condition = condition, }; quote.Quote = qq; if ((reader.ColumnCurr + 1) < reader.ColumnCount) { qq.StartPosition = reader.ReadNullableInt(); qq.EndPosition = reader.ReadNullableInt(); qq.Action = reader.ReadNullableEnum <QuoteChangeActions>(); } } return(quote); }
private NullableTimeQuoteChange ToNullQuote(Sides side, QuoteChange quote, QuoteChangeMessage message) { if (message is null) { throw new ArgumentNullException(nameof(message)); } return(new NullableTimeQuoteChange { ServerTime = message.ServerTime, LocalTime = message.LocalTime, Side = side, State = message.State, Quote = quote, BuildFrom = message.BuildFrom, SeqNum = message.SeqNum.DefaultAsNull(), }); }
private static QuoteChange[] DeserializeQuotes(BitArrayReader reader, QuoteMetaInfo metaInfo, bool useLong, bool nonAdjustPrice) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } if (metaInfo == null) { throw new ArgumentNullException(nameof(metaInfo)); } var deltaCount = reader.ReadInt(); if (deltaCount == 0) { return(ArrayHelper.Empty <QuoteChange>()); } var quotes = new QuoteChange[deltaCount]; for (var i = 0; i < deltaCount; i++) { var prevPrice = metaInfo.FirstPrice; var price = reader.ReadPrice(ref prevPrice, metaInfo, useLong, nonAdjustPrice); metaInfo.FirstPrice = prevPrice; var volume = reader.ReadVolume(metaInfo); var ordersCount = metaInfo.Version >= MarketDataVersions.Version56 ? reader.ReadNullableInt() : null; var condition = metaInfo.Version >= MarketDataVersions.Version57 ? reader.Read() ? (QuoteConditions)reader.ReadInt() : default : default; quotes[i] = new QuoteChange(price, volume, ordersCount, condition); } return(quotes); }
public void ShowWarningQuoteChanged(string username, decimal quote, OrderType orderType) { if (messagesTextBox.InvokeRequired) //randomly chosen component from ui thread { QuoteChange del = new QuoteChange(ShowWarningQuoteChanged); messagesTextBox.BeginInvoke(del, new object[] { username, quote, orderType }); } else { if (username != this.username) { int numOrders = 0; if (orderType == OrderType.BUYING) { numOrders = coordinator.GetAmountBuyingOrders(this.username); } else { numOrders = coordinator.GetAmountSellingOrders(this.username); } if (numOrders > 0) { string message = username + " changed the diginote quote to\n" + quote.ToString() + "\nDo you want to keep your pending orders? If you don't answer in 30s, your orders will be kept."; DialogResult result = MessageBoxEx.Show(message, "Quote Change", MessageBoxButtons.YesNo, MessageBoxIcon.Information, 30000); if (result == DialogResult.No) { if (orderType == OrderType.BUYING) { coordinator.CancelPurchasingOrders(numOrders, this.username); } else { coordinator.CancelSellingOrders(numOrders, this.username); } } } } updateInfo(); } }
private void OnSecurityChanged(string smartId, Tuple<decimal, decimal, DateTime> lastTrade, decimal open, decimal high, decimal low, decimal close, decimal volume, QuoteChange bid, QuoteChange ask, decimal openInt, Tuple<decimal, decimal> goBuySell, Tuple<decimal, decimal> goBase, Tuple<decimal, decimal> limits, int tradingStatus, Tuple<decimal, decimal> volatTheorPrice) { var secId = new SecurityId { Native = smartId }; var message = new Level1ChangeMessage { SecurityId = secId, ExtensionInfo = new Dictionary<object, object> { { SmartComExtensionInfoHelper.SecurityOptionsMargin, goBase.Item1 }, { SmartComExtensionInfoHelper.SecurityOptionsSyntheticMargin, goBase.Item2 } }, ServerTime = CurrentTime.Convert(TimeHelper.Moscow), }; message.TryAdd(Level1Fields.LastTradePrice, lastTrade.Item1); message.TryAdd(Level1Fields.LastTradeVolume, lastTrade.Item2); message.Add(Level1Fields.LastTradeTime, lastTrade.Item3.ApplyTimeZone(TimeHelper.Moscow)); var prevQuotes = _bestQuotes.TryGetValue(secId); if (bid.Price != 0) { message.Add(Level1Fields.BestBidPrice, bid.Price); if (prevQuotes != null && prevQuotes.First != null && prevQuotes.First.Item1 == bid.Price) message.Add(Level1Fields.BestBidVolume, prevQuotes.First.Item2); } if (ask.Price != 0) { message.Add(Level1Fields.BestAskPrice, ask.Price); if (prevQuotes != null && prevQuotes.Second != null && prevQuotes.Second.Item1 == ask.Price) message.Add(Level1Fields.BestAskVolume, prevQuotes.Second.Item2); } message.TryAdd(Level1Fields.BidsVolume, bid.Volume); message.TryAdd(Level1Fields.AsksVolume, ask.Volume); message.TryAdd(Level1Fields.OpenPrice, open); message.TryAdd(Level1Fields.LowPrice, low); message.TryAdd(Level1Fields.HighPrice, high); message.TryAdd(Level1Fields.ClosePrice, close); message.TryAdd(Level1Fields.MinPrice, limits.Item1); message.TryAdd(Level1Fields.MaxPrice, limits.Item2); message.TryAdd(Level1Fields.MarginBuy, goBuySell.Item1); message.TryAdd(Level1Fields.MarginSell, goBuySell.Item2); message.TryAdd(Level1Fields.OpenInterest, openInt); message.TryAdd(Level1Fields.ImpliedVolatility, volatTheorPrice.Item1); message.TryAdd(Level1Fields.TheorPrice, volatTheorPrice.Item2); message.TryAdd(Level1Fields.Volume, volume); message.Add(Level1Fields.State, tradingStatus == 0 ? SecurityStates.Trading : SecurityStates.Stoped); SendOutMessage(message); }
private IEnumerable<ExecutionMessage> ProcessQuoteChange(DateTime time, QuoteChange[] newBids, QuoteChange[] newAsks) { var retVal = _bids.Select(p => p.Value.Second) .GetDiff(newBids, Sides.Buy, true) .Concat(_asks.Select(p => p.Value.Second).GetDiff(newAsks, Sides.Sell, true)) .Select(q => CreateMessage(time, q.Side, q.Price, q.Volume.Abs(), !(q.Volume > 0))); var bestBidPrice = 0m; foreach (var bid in newBids) { if (bid.Price > bestBidPrice) bestBidPrice = bid.Price; } var bestAskPrice = 0m; foreach (var ask in newAsks) { if (bestAskPrice == 0 || ask.Price < bestAskPrice) bestAskPrice = ask.Price; } var spreadPrice = bestAskPrice == 0 ? bestBidPrice : (bestBidPrice == 0 ? bestAskPrice : (bestAskPrice - bestBidPrice) / 2 + bestBidPrice); //при обновлении стакана необходимо учитывать направление сдвига, чтобы не было ложного исполнения при наложении бидов и асков. //т.е. если цена сдвинулась вниз, то обновление стакана необходимо начинать с минимального бида. retVal = (spreadPrice < _currSpreadPrice) ? retVal.OrderBy(m => m.Price) : retVal.OrderByDescending(m => m.Price); _currSpreadPrice = spreadPrice; return retVal.ToArray(); }
private void AddExecMsg(List<ExecutionMessage> diff, DateTime time, DateTimeOffset serverTime, QuoteChange quote, decimal volume, bool isSpread) { if (volume > 0) diff.Add(CreateMessage(time, serverTime, quote.Side, quote.Price, volume)); else { volume = volume.Abs(); // matching only top orders (spread) if (isSpread && volume > 1 && _isMatch.Next()) { var tradeVolume = (int)volume / 2; diff.Add(new ExecutionMessage { Side = quote.Side, Volume = tradeVolume, ExecutionType = ExecutionTypes.Tick, SecurityId = SecurityId, LocalTime = time, ServerTime = serverTime, TradePrice = quote.Price, }); // that tick will not affect on order book //volume -= tradeVolume; } diff.Add(CreateMessage(time, serverTime, quote.Side, quote.Price, volume, true)); } }
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; } }
private void OnSecurityChanged(string smartId, Tuple <decimal?, decimal?, DateTime> lastTrade, decimal?open, decimal?high, decimal?low, decimal?close, decimal?volume, QuoteChange bid, QuoteChange ask, decimal?openInt, Tuple <decimal?, decimal?> goBuySell, Tuple <decimal?, decimal?> goBase, Tuple <decimal?, decimal?> limits, int tradingStatus, Tuple <decimal?, decimal?> volatTheorPrice) { var secId = new SecurityId { Native = smartId }; var message = new Level1ChangeMessage { SecurityId = secId, ExtensionInfo = new Dictionary <object, object> { { SmartComExtensionInfoHelper.SecurityOptionsMargin, goBase.Item1 }, { SmartComExtensionInfoHelper.SecurityOptionsSyntheticMargin, goBase.Item2 } }, ServerTime = CurrentTime.Convert(TimeHelper.Moscow), }; message.TryAdd(Level1Fields.LastTradePrice, lastTrade.Item1); message.TryAdd(Level1Fields.LastTradeVolume, lastTrade.Item2); message.Add(Level1Fields.LastTradeTime, lastTrade.Item3.ApplyTimeZone(TimeHelper.Moscow)); var prevQuotes = _bestQuotes.TryGetValue(secId); if (bid.Price != 0) { message.Add(Level1Fields.BestBidPrice, bid.Price); if (prevQuotes != null && prevQuotes.First != null && prevQuotes.First.Item1 == bid.Price) { message.Add(Level1Fields.BestBidVolume, prevQuotes.First.Item2); } } if (ask.Price != 0) { message.Add(Level1Fields.BestAskPrice, ask.Price); if (prevQuotes != null && prevQuotes.Second != null && prevQuotes.Second.Item1 == ask.Price) { message.Add(Level1Fields.BestAskVolume, prevQuotes.Second.Item2); } } message.TryAdd(Level1Fields.BidsVolume, bid.Volume); message.TryAdd(Level1Fields.AsksVolume, ask.Volume); message.TryAdd(Level1Fields.OpenPrice, open); message.TryAdd(Level1Fields.LowPrice, low); message.TryAdd(Level1Fields.HighPrice, high); message.TryAdd(Level1Fields.ClosePrice, close); message.TryAdd(Level1Fields.MinPrice, limits.Item1); message.TryAdd(Level1Fields.MaxPrice, limits.Item2); message.TryAdd(Level1Fields.MarginBuy, goBuySell.Item1); message.TryAdd(Level1Fields.MarginSell, goBuySell.Item2); message.TryAdd(Level1Fields.OpenInterest, openInt); message.TryAdd(Level1Fields.ImpliedVolatility, volatTheorPrice.Item1); message.TryAdd(Level1Fields.TheorPrice, volatTheorPrice.Item2); message.TryAdd(Level1Fields.Volume, volume); message.Add(Level1Fields.State, tradingStatus == 0 ? SecurityStates.Trading : SecurityStates.Stoped); SendOutMessage(message); }
private void GetDiff(List <ExecutionMessage> diff, DateTimeOffset time, DateTimeOffset serverTime, SortedDictionary <decimal, RefPair <LevelQuotes, QuoteChange> > from, IEnumerable <QuoteChange> to, Sides side, out decimal newBestPrice) { newBestPrice = 0; var canProcessFrom = true; var canProcessTo = true; QuoteChange currFrom = null; QuoteChange currTo = null; // TODO //List<ExecutionMessage> currOrders = null; var mult = side == Sides.Buy ? -1 : 1; bool?isSpread = null; using (var fromEnum = from.GetEnumerator()) using (var toEnum = to.GetEnumerator()) { while (true) { if (canProcessFrom && currFrom == null) { if (!fromEnum.MoveNext()) { canProcessFrom = false; } else { currFrom = fromEnum.Current.Value.Second; isSpread = isSpread == null; } } if (canProcessTo && currTo == null) { if (!toEnum.MoveNext()) { canProcessTo = false; } else { currTo = toEnum.Current; if (newBestPrice == 0) { newBestPrice = currTo.Price; } } } if (currFrom == null) { if (currTo == null) { break; } else { AddExecMsg(diff, time, serverTime, currTo, currTo.Volume, side, false); currTo = null; } } else { if (currTo == null) { AddExecMsg(diff, time, serverTime, currFrom, -currFrom.Volume, side, isSpread.Value); currFrom = null; } else { if (currFrom.Price == currTo.Price) { if (currFrom.Volume != currTo.Volume) { AddExecMsg(diff, time, serverTime, currTo, currTo.Volume - currFrom.Volume, side, isSpread.Value); } currFrom = currTo = null; } else if (currFrom.Price * mult > currTo.Price * mult) { AddExecMsg(diff, time, serverTime, currTo, currTo.Volume, side, isSpread.Value); currTo = null; } else { AddExecMsg(diff, time, serverTime, currFrom, -currFrom.Volume, side, isSpread.Value); currFrom = null; } } } } } }
private void OnProcessQuotes(string where, string[] quotes) { // paper_no нужно парсить из условия where, так как в поле paper_no передается всегда 0 var paperNo = where.Split('=')[1].Trim().To<int>(); var f = Wrapper.FieldsDepth; var bids = new List<QuoteChange>(); var asks = new List<QuoteChange>(); foreach (var quoteStr in quotes) { var cols = quoteStr.ToColumns(); var sellQty = f.SellQty.GetValue(cols); var price = f.Price.GetValue(cols); var buyQty = f.BuyQty.GetValue(cols); if (sellQty == 0 && buyQty == 0) { // If the sellQty and buyQty are 0 - that is our limit order // which is not a part of the market depth. Just skip it. continue; } QuoteChange quote; if (sellQty == 0) { quote = new QuoteChange(Sides.Buy, price, buyQty); bids.Insert(0, quote); } else { quote = new QuoteChange(Sides.Sell, price, sellQty); asks.Add(quote); } } if (bids.Count > 0 || asks.Count > 0) { SendOutMessage(new QuoteChangeMessage { Bids = bids, Asks = asks, SecurityId = new SecurityId { Native = paperNo }, IsSorted = true, ServerTime = CurrentTime.Convert(TimeHelper.Moscow), }); } }
private void SessionOnStil2Update(ref structSTIL2Update structL2Update) { var asksUpdate = _depths[structL2Update.bstrSymbol].Item1; var bidsUpdate = _depths[structL2Update.bstrSymbol].Item2; var quote = new QuoteChange(structL2Update.bstrSide.ToSide(), (decimal) structL2Update.fPrice, structL2Update.nQty) {BoardCode = structL2Update.bstrMaker}; switch (structL2Update.bstrSide.ToSide()) { case Sides.Buy: { switch (structL2Update.bstrAction) { case "A": // add { bidsUpdate.Add(quote); break; } case "C": // change { bidsUpdate.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); bidsUpdate.Add(quote); break; } case "D": // delete { bidsUpdate.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); break; } } break; } case Sides.Sell: { switch (structL2Update.bstrAction) { case "A": // add { asksUpdate.Add(quote); break; } case "C": // change { asksUpdate.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); asksUpdate.Add(quote); break; } case "D": // delete { asksUpdate.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); break; } } break; } } var message = new QuoteChangeMessage { SecurityId = new SecurityId { SecurityCode = structL2Update.bstrSymbol, BoardCode = "All", }, Asks = asksUpdate, Bids = bidsUpdate, ServerTime = structL2Update.bstrTime.StrToTime(), }; SendOutMessage(message); }
/// <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 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 || item.TradePrice != 0 || 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 = item.Volume, }; quotes.Add(item.Price, quote); if (item.Side == Sides.Buy) { _depth.Bids = GetArray(quotes); } else { _depth.Asks = GetArray(quotes); } } else { quote.Volume += item.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 -= item.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.Volume; if (item != null && _matchingOrder.OrderId == item.OrderId) { volume -= item.Volume; } // если заявка была вся отменена. например, по причине 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; } }
private IEnumerable<ExecutionMessage> ProcessQuoteChange(DateTime time, QuoteChange[] newBids, QuoteChange[] newAsks) { decimal bestBidPrice; decimal bestAskPrice; var retVal = GetDiff(time, _bids, newBids, Sides.Buy, out bestBidPrice) .Concat(GetDiff(time, _asks, newAsks, Sides.Sell, out bestAskPrice)); var spreadPrice = bestAskPrice == 0 ? bestBidPrice : (bestBidPrice == 0 ? bestAskPrice : (bestAskPrice - bestBidPrice) / 2 + bestBidPrice); //при обновлении стакана необходимо учитывать направление сдвига, чтобы не было ложного исполнения при наложении бидов и асков. //т.е. если цена сдвинулась вниз, то обновление стакана необходимо начинать с минимального бида. retVal = (spreadPrice < _currSpreadPrice) ? retVal.OrderBy(m => m.Price) : retVal.OrderByDescending(m => m.Price); _currSpreadPrice = spreadPrice; return retVal.ToArray(); }
private void SessionOnStil2Update(ref structSTIL2Update structL2Update) { var depth = _depths[structL2Update.bstrSymbol]; var quote = new QuoteChange(structL2Update.bstrSide.ToSide(), (decimal)structL2Update.fPrice, structL2Update.nQty); var quotes = quote.Side == Sides.Sell ? depth.Item1 : depth.Item2; switch (structL2Update.bstrAction) { case "A": // add { quotes.Add(quote); break; } case "C": // change { quotes.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); quotes.Add(quote); break; } case "D": // delete { quotes.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); break; } } var board = structL2Update.bstrMaker; if (board.IsEmpty()) board = AssociatedBoardCode; var message = new QuoteChangeMessage { SecurityId = new SecurityId { SecurityCode = structL2Update.bstrSymbol, BoardCode = board, }, Asks = depth.Item1.ToArray(), Bids = depth.Item2.ToArray(), ServerTime = structL2Update.bstrTime.StrToTime(), }; SendOutMessage(message); }
/// <summary> /// Вычислить приращение между котировками. /// </summary> /// <param name="time"></param> /// <param name="from">Первые котировки.</param> /// <param name="to">Вторые котировки.</param> /// <param name="side">Направление, показывающее тип котировок.</param> /// <param name="newBestPrice"></param> /// <returns>Изменения.</returns> private IEnumerable <ExecutionMessage> GetDiff(DateTime time, IEnumerable <QuoteChange> from, IEnumerable <QuoteChange> to, Sides side, out decimal newBestPrice) { //if (!isSorted) //{ // if (side == Sides.Sell) // { // from = from.OrderBy(q => q.Price); // to = to.OrderBy(q => q.Price); // } // else // { // from = from.OrderByDescending(q => q.Price); // to = to.OrderByDescending(q => q.Price); // } //} newBestPrice = 0; var diff = new List <ExecutionMessage>(); var canProcessFrom = true; var canProcessTo = true; QuoteChange currFrom = null; QuoteChange currTo = null; var mult = side == Sides.Buy ? -1 : 1; using (var fromEnum = from.GetEnumerator()) using (var toEnum = to.GetEnumerator()) { while (true) { if (canProcessFrom && currFrom == null) { if (!fromEnum.MoveNext()) { canProcessFrom = false; } else { currFrom = fromEnum.Current; } } if (canProcessTo && currTo == null) { if (!toEnum.MoveNext()) { canProcessTo = false; } else { currTo = toEnum.Current; if (newBestPrice == 0) { newBestPrice = currTo.Price; } } } if (currFrom == null) { if (currTo == null) { break; } else { //diff.Add(currTo.Clone()); AddExecMsg(diff, time, currTo, currTo.Volume); currTo = null; } } else { if (currTo == null) { //var clone = currFrom.Clone(); //clone.Volume = -clone.Volume; //diff.Add(clone); AddExecMsg(diff, time, currFrom, -currFrom.Volume); currFrom = null; } else { if (currFrom.Price == currTo.Price) { if (currFrom.Volume != currTo.Volume) { //var clone = currTo.Clone(); //clone.Volume -= currFrom.Volume; //diff.Add(clone); AddExecMsg(diff, time, currTo, currTo.Volume - currFrom.Volume); } currFrom = currTo = null; } else if (currFrom.Price * mult > currTo.Price * mult) { //diff.Add(currTo.Clone()); AddExecMsg(diff, time, currTo, currTo.Volume); currTo = null; } else { //var clone = currFrom.Clone(); //clone.Volume = -clone.Volume; //diff.Add(clone); AddExecMsg(diff, time, currFrom, -currFrom.Volume); currFrom = null; } } } } } return(diff); }
private static QuoteChange[] DeserializeQuotes(BitArrayReader reader, QuoteMetaInfo metaInfo, bool useLong, bool nonAdjustPrice) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } if (metaInfo == null) { throw new ArgumentNullException(nameof(metaInfo)); } var count = reader.ReadInt(); if (count == 0) { return(ArrayHelper.Empty <QuoteChange>()); } var is56 = metaInfo.Version >= MarketDataVersions.Version56; var is57 = metaInfo.Version >= MarketDataVersions.Version57; var is58 = metaInfo.Version >= MarketDataVersions.Version58; var quotes = new QuoteChange[count]; for (var i = 0; i < count; i++) { var prevPrice = metaInfo.FirstPrice; var price = reader.ReadPrice(ref prevPrice, metaInfo, useLong, nonAdjustPrice); metaInfo.FirstPrice = prevPrice; var volume = reader.ReadVolume(metaInfo); var ordersCount = is56 ? reader.ReadNullableInt() : null; var condition = is57 ? (QuoteConditions)(reader.ReadNullableInt() ?? 0) : default; var quote = new QuoteChange(price, volume, ordersCount, condition); if (is58) { if (reader.Read()) { quote.Action = (QuoteChangeActions)reader.ReadInt(); } if (reader.Read()) { quote.StartPosition = reader.ReadInt(); } if (reader.Read()) { quote.EndPosition = reader.ReadInt(); } } quotes[i] = quote; } return(quotes); }
/// <summary> /// Try create full book. /// </summary> /// <param name="change">Book change.</param> /// <returns>Full book.</returns> public QuoteChangeMessage TryApply(QuoteChangeMessage change) { if (change is null) { throw new ArgumentNullException(nameof(change)); } if (change.State == null) { throw new ArgumentException(nameof(change)); } var currState = _state; var newState = change.State.Value; void CheckSwitch() { switch (currState) { case _none: case QuoteChangeStates.SnapshotStarted: { if (newState != QuoteChangeStates.SnapshotBuilding && newState != QuoteChangeStates.SnapshotComplete) { _logs.AddDebugLog($"{currState}->{newState}"); } break; } case QuoteChangeStates.SnapshotBuilding: { if (newState != QuoteChangeStates.SnapshotBuilding && newState != QuoteChangeStates.SnapshotComplete) { _logs.AddDebugLog($"{currState}->{newState}"); } break; } case QuoteChangeStates.SnapshotComplete: case QuoteChangeStates.Increment: { if (newState == QuoteChangeStates.SnapshotBuilding) { _logs.AddDebugLog($"{currState}->{newState}"); } break; } } } if (currState != newState || newState == QuoteChangeStates.SnapshotComplete) { CheckSwitch(); if (newState == QuoteChangeStates.SnapshotStarted) { _bids.Clear(); _asks.Clear(); } switch (currState) { case _none: { _bids.Clear(); _asks.Clear(); break; } case QuoteChangeStates.SnapshotStarted: break; case QuoteChangeStates.SnapshotBuilding: break; case QuoteChangeStates.SnapshotComplete: { if (newState == QuoteChangeStates.SnapshotComplete) { _bids.Clear(); _asks.Clear(); } break; } case QuoteChangeStates.Increment: break; default: throw new ArgumentOutOfRangeException(currState.ToString()); } _state = currState = newState; } void Apply(IEnumerable <QuoteChange> from, SortedList <decimal, QuoteChange> to) { foreach (var quote in from) { if (quote.Volume == 0) { to.Remove(quote.Price); } else { to[quote.Price] = quote; } } } void ApplyByPos(IEnumerable <QuoteChange> from, List <QuoteChange> to) { foreach (var quote in from) { var startPos = quote.StartPosition.Value; switch (quote.Action) { case QuoteChangeActions.New: { var tuple = new QuoteChange(quote.Price, quote.Volume, quote.OrdersCount, quote.Condition); if (startPos > to.Count) { throw new InvalidOperationException($"Pos={startPos}>Count={to.Count}"); } else if (startPos == to.Count) { to.Add(tuple); } else { to.Insert(startPos, tuple); } break; } case QuoteChangeActions.Update: { to[startPos] = new QuoteChange(quote.Price, quote.Volume, quote.OrdersCount, quote.Condition); break; } case QuoteChangeActions.Delete: { if (quote.EndPosition == null) { to.RemoveAt(startPos); } else { to.RemoveRange(startPos, (quote.EndPosition.Value - startPos) + 1); } break; } default: throw new ArgumentOutOfRangeException(nameof(from), quote.Action, LocalizedStrings.Str1219); } } } if (change.HasPositions) { ApplyByPos(change.Bids, _bidsByPos); ApplyByPos(change.Asks, _asksByPos); } else { Apply(change.Bids, _bids); Apply(change.Asks, _asks); } if (currState == QuoteChangeStates.SnapshotStarted || currState == QuoteChangeStates.SnapshotBuilding) { return(null); } QuoteChange[] bids; QuoteChange[] asks; if (change.HasPositions) { bids = _bidsByPos.ToArray(); asks = _asksByPos.ToArray(); } else { bids = _bids.Values.ToArray(); asks = _asks.Values.ToArray(); } return(new QuoteChangeMessage { SecurityId = SecurityId, Bids = bids, Asks = asks, ServerTime = change.ServerTime, OriginalTransactionId = change.OriginalTransactionId, }); }
private void SessionOnStil2Update(ref structSTIL2Update structL2Update) { var asksUpdate = _depths[structL2Update.bstrSymbol].Item1; var bidsUpdate = _depths[structL2Update.bstrSymbol].Item2; var quote = new QuoteChange(structL2Update.bstrSide.ToSide(), (decimal)structL2Update.fPrice, structL2Update.nQty) { BoardCode = structL2Update.bstrMaker }; switch (structL2Update.bstrSide.ToSide()) { case Sides.Buy: { switch (structL2Update.bstrAction) { case "A": // add { bidsUpdate.Add(quote); break; } case "C": // change { bidsUpdate.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); bidsUpdate.Add(quote); break; } case "D": // delete { bidsUpdate.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); break; } } break; } case Sides.Sell: { switch (structL2Update.bstrAction) { case "A": // add { asksUpdate.Add(quote); break; } case "C": // change { asksUpdate.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); asksUpdate.Add(quote); break; } case "D": // delete { asksUpdate.RemoveWhere(q => q.Price == quote.Price && q.BoardCode == quote.BoardCode); break; } } break; } } var message = new QuoteChangeMessage { SecurityId = new SecurityId { SecurityCode = structL2Update.bstrSymbol, BoardCode = "All", }, Asks = asksUpdate, Bids = bidsUpdate, ServerTime = structL2Update.bstrTime.StrToTime(), }; SendOutMessage(message); }
private IEnumerable<ExecutionMessage> ProcessQuoteChange(DateTime time, DateTimeOffset serverTime, QuoteChange[] newBids, QuoteChange[] newAsks) { decimal bestBidPrice; decimal bestAskPrice; var diff = new List<ExecutionMessage>(); GetDiff(diff, time, serverTime, _bids, newBids, Sides.Buy, out bestBidPrice); GetDiff(diff, time, serverTime, _asks, newAsks, Sides.Sell, out bestAskPrice); var spreadPrice = bestAskPrice == 0 ? bestBidPrice : (bestBidPrice == 0 ? bestAskPrice : (bestAskPrice - bestBidPrice) / 2 + bestBidPrice); try { //при обновлении стакана необходимо учитывать направление сдвига, чтобы не было ложного исполнения при наложении бидов и асков. //т.е. если цена сдвинулась вниз, то обновление стакана необходимо начинать с минимального бида. return (spreadPrice < _currSpreadPrice) ? diff.OrderBy(m => m.OrderPrice) : diff.OrderByDescending(m => m.OrderPrice); } finally { _currSpreadPrice = spreadPrice; } }
private void ProcessLevel1Depth(Level1ChangeMessage message, decimal bestBidPrice, decimal bestBidVolume, decimal bestAskPrice, decimal bestAskVolume, List<ExecutionMessage> retVal) { if (message.LocalTime.Date == _lastDepthDate) return; QuoteChange ask = null; QuoteChange bid = null; if (bestAskPrice != 0 && bestAskVolume != 0) ask = new QuoteChange(Sides.Sell, bestAskPrice, bestAskVolume); if (bestBidPrice != 0 && bestBidVolume != 0) bid = new QuoteChange(Sides.Buy, bestBidPrice, bestBidVolume); if (ask == null && bid == null) return; retVal.AddRange(ProcessQuoteChange(message.LocalTime, message.ServerTime, bid != null ? new[] { bid } : ArrayHelper.Empty<QuoteChange>(), ask != null ? new[] { ask } : ArrayHelper.Empty<QuoteChange>())); }
// mika // debug code for check build algo //private readonly Dictionary<long, decimal> _pendingMatch = new Dictionary<long, decimal>(); //private readonly Dictionary<long, Tuple<Sides, decimal, decimal>> _activeOrders = new Dictionary<long, Tuple<Sides, decimal, decimal>>(); //private QuoteChangeMessage BuildDepth() //{ // var bids = new SortedDictionary<decimal, QuoteChange>(new BackwardComparer<decimal>()); // var asks = new SortedDictionary<decimal, QuoteChange>(); // foreach (var pair in _activeOrders) // { // var quotes = pair.Value.Item1 == Sides.Buy ? bids : asks; // var quote = quotes.TryGetValue(pair.Value.Item2); // if (quote == null) // { // quote = new QuoteChange(pair.Value.Item1, pair.Value.Item2, pair.Value.Item3); // quotes.Add(pair.Value.Item2, quote); // } // else // quote.Volume += pair.Value.Item3; // } // return new QuoteChangeMessage // { // Bids = bids.Values.ToArray(), // Asks = asks.Values.ToArray() // }; //} /// <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"); // mika // debug code for check build algo //var orderId = item.OrderId.Value; //var orderVol = item.Volume.Value; //if (item.IsOrderLogRegistered()) //{ // var vol = _pendingMatch.TryGetValue2(orderId); // if (vol != null) // vol -= orderVol; // else // vol = orderVol; // if (vol > 0) // _activeOrders.Add(orderId, Tuple.Create(item.Side, item.Price, vol.Value)); //} //else if (item.IsOrderLogMatched()) //{ // var t = _activeOrders.TryGetValue(orderId); // if (t != null) // { // var vol = t.Item3; // vol -= orderVol; // if (vol < 0) // throw new Exception(); // if (vol == 0) // _activeOrders.Remove(orderId); // else // _activeOrders[orderId] = Tuple.Create(item.Side, item.Price, vol); // } // else // { // var vol = _pendingMatch.TryGetValue2(orderId); // if (vol == null) // vol = orderVol; // else // vol += orderVol; // _pendingMatch[orderId] = vol.Value; // } //} //else if (item.IsOrderLogCanceled()) // _activeOrders.Remove(orderId); 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; }