private void ProcessMyTradeMessage(Order order, Security security, ExecutionMessage message, long transactionId) { var tuple = _entityCache.ProcessMyTradeMessage(order, security, message, transactionId); if (tuple == null) { List<ExecutionMessage> nonOrderedMyTrades; if (message.OrderId != null) nonOrderedMyTrades = _nonAssociatedByIdMyTrades.SafeAdd(message.OrderId.Value); else if (message.OriginalTransactionId != 0) nonOrderedMyTrades = _nonAssociatedByTransactionIdMyTrades.SafeAdd(message.OriginalTransactionId); else nonOrderedMyTrades = _nonAssociatedByStringIdMyTrades.SafeAdd(message.OrderStringId); this.AddInfoLog("My trade delayed: {0}", message); nonOrderedMyTrades.Add((ExecutionMessage)message.Clone()); return; } if (!tuple.Item2) return; RaiseNewMyTrade(tuple.Item1); }
private void ProcessExecutionMessage(ExecutionMessage message) { if (message.ExecutionType == null) throw new ArgumentException(LocalizedStrings.Str688Params.Put(message)); switch (message.ExecutionType) { case ExecutionTypes.Transaction: { if (_entityCache.IsMassCancelation(message.OriginalTransactionId)) { if (message.Error == null) RaiseMassOrderCanceled(message.OriginalTransactionId); else RaiseMassOrderCancelFailed(message.OriginalTransactionId, message.Error); break; } long transactionId; var order = _entityCache.GetOrder(message, out transactionId); if (order == null) { var security = LookupSecurity(message.SecurityId); ProcessTransactionMessage(null, security, message, transactionId); } else { ProcessTransactionMessage(order, order.Security, message, transactionId); } break; } case ExecutionTypes.Tick: case ExecutionTypes.OrderLog: //case null: { var security = LookupSecurity(message.SecurityId); switch (message.ExecutionType) { case ExecutionTypes.Tick: ProcessTradeMessage(security, message); break; case ExecutionTypes.OrderLog: ProcessOrderLogMessage(security, message); break; } if (CreateAssociatedSecurity && !IsAssociated(message.SecurityId.BoardCode)) { var clone = (ExecutionMessage)message.Clone(); clone.SecurityId = CreateAssociatedId(clone.SecurityId); ProcessExecutionMessage(clone); } break; } default: throw new ArgumentOutOfRangeException(LocalizedStrings.Str1695Params.Put(message.ExecutionType)); } }
/// <summary> /// Обработать сообщение. /// </summary> /// <param name="message">Сообщение.</param> /// <returns>Результат обработки. Если будет возрвщено <see langword="null"/>, /// то генератору пока недостаточно данных для генерации нового сообщения.</returns> protected override Message OnProcess(Message message) { DateTimeOffset time; switch (message.Type) { case MessageTypes.Level1Change: { var l1Msg = (Level1ChangeMessage)message; var value = l1Msg.Changes.TryGetValue(Level1Fields.LastTradePrice); if (value != null) { _lastOrderPrice = (decimal)value; } TradeGenerator.Process(message); time = l1Msg.ServerTime; break; } case MessageTypes.Execution: { var execMsg = (ExecutionMessage)message; switch (execMsg.ExecutionType) { case ExecutionTypes.Tick: _lastOrderPrice = execMsg.GetTradePrice(); break; default: return(null); } time = execMsg.ServerTime; break; } case MessageTypes.Time: { var timeMsg = (TimeMessage)message; time = timeMsg.ServerTime; break; } default: return(null); } if (!IsTimeToGenerate(time)) { return(null); } // TODO более реалистичную генерацию, так как сейчас объемы, цены и сделки c потолка var action = RandomGen.GetInt(0, 5); var isNew = action < 3 || _activeOrders.IsEmpty(); ExecutionMessage item; if (isNew) { var priceStep = SecurityDefinition.PriceStep ?? 0.01m; _lastOrderPrice += RandomGen.GetInt(-MaxPriceStepCount, MaxPriceStepCount) * priceStep; if (_lastOrderPrice <= 0) { _lastOrderPrice = priceStep; } item = new ExecutionMessage { OrderId = IdGenerator.GetNextId(), SecurityId = SecurityId, ServerTime = time, OrderState = OrderStates.Active, Volume = Volumes.Next(), Side = RandomGen.GetEnum <Sides>(), Price = _lastOrderPrice, ExecutionType = ExecutionTypes.OrderLog, }; _activeOrders.Enqueue((ExecutionMessage)item.Clone()); } else { var activeOrder = _activeOrders.Peek(); item = (ExecutionMessage)activeOrder.Clone(); item.ServerTime = time; var isMatched = action == 5; ExecutionMessage trade = null; if (isMatched) { trade = (ExecutionMessage)TradeGenerator.Process(message); } if (isMatched && trade != null) { item.Volume = RandomGen.GetInt(1, (int)activeOrder.GetVolume()); item.TradeId = trade.TradeId; item.TradePrice = trade.TradePrice; item.TradeStatus = trade.TradeStatus; // TODO //quote.Trade = TradeGenerator.Generate(time); //item.Volume = activeOrder.Volume; //if (item.Side == Sides.Buy && quote.Trade.Price > quote.Order.Price) // item.TradePrice = item.Price; //else if (item.Side == Sides.Sell && quote.Trade.Price < quote.Order.Price) // item.TradePrice = item.Price; activeOrder.Volume -= item.Volume; if (activeOrder.Volume == 0) { item.OrderState = OrderStates.Done; _activeOrders.Dequeue(); } else { item.OrderState = OrderStates.Active; } } else { item.OrderState = OrderStates.Done; item.IsCancelled = true; _activeOrders.Dequeue(); } } LastGenerationTime = time; return(item); }
/// <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); }