private void OrderMatch(string data) { var message = JsonConvert.DeserializeObject <Messages.Matched>(data, JsonSettings); var cached = CachedOrderIDs.Where(o => o.Value.BrokerId.Contains(message.MakerOrderId) || o.Value.BrokerId.Contains(message.TakerOrderId)); var symbol = ConvertProductId(message.ProductId); if (!cached.Any()) { return; } Log.Trace($"GDAXBrokerage.OrderMatch(): Match: {message.ProductId} {data}"); var orderId = cached.First().Key; var orderObj = cached.First().Value; if (!FillSplit.ContainsKey(orderId)) { FillSplit[orderId] = new GDAXFill(orderObj); } var split = FillSplit[orderId]; split.Add(message); //is this the total order at once? Is this the last split fill? var status = Math.Abs(message.Size) == Math.Abs(cached.Single().Value.Quantity) || Math.Abs(split.OrderQuantity) == Math.Abs(split.TotalQuantity()) ? OrderStatus.Filled : OrderStatus.PartiallyFilled; OrderDirection direction; // Messages are always from the perspective of the market maker. Flip it in cases of a market order. if (orderObj.Type == OrderType.Market) { direction = message.Side == "sell" ? OrderDirection.Buy : OrderDirection.Sell; } else { direction = message.Side == "sell" ? OrderDirection.Sell : OrderDirection.Buy; } var orderEvent = new OrderEvent ( cached.First().Key, symbol, message.Time, status, direction, message.Price, direction == OrderDirection.Sell ? -message.Size : message.Size, GetFee(cached.First().Value), $"GDAX Match Event {direction}" ); //if we're filled we won't wait for done event if (orderEvent.Status == OrderStatus.Filled) { Orders.Order outOrder = null; CachedOrderIDs.TryRemove(cached.First().Key, out outOrder); } OnOrderEvent(orderEvent); }
private void EmitFillOrderEvent(Messages.Fill fill, Order order) { var symbol = _symbolMapper.GetLeanSymbol(fill.ProductId, SecurityType.Crypto, Market.GDAX); if (!FillSplit.ContainsKey(order.Id)) { FillSplit[order.Id] = new GDAXFill(order); } var split = FillSplit[order.Id]; split.Add(fill); // is this the total order at once? Is this the last split fill? var isFinalFill = Math.Abs(fill.Size) == Math.Abs(order.Quantity) || Math.Abs(split.OrderQuantity) == Math.Abs(split.TotalQuantity); var status = isFinalFill ? OrderStatus.Filled : OrderStatus.PartiallyFilled; var direction = fill.Side == "sell" ? OrderDirection.Sell : OrderDirection.Buy; var fillPrice = fill.Price; var fillQuantity = direction == OrderDirection.Sell ? -fill.Size : fill.Size; var currency = order.PriceCurrency == string.Empty ? _algorithm.Securities[symbol].SymbolProperties.QuoteCurrency : order.PriceCurrency; var orderFee = new OrderFee(new CashAmount(fill.Fee, currency)); var orderEvent = new OrderEvent ( order.Id, symbol, fill.CreatedAt, status, direction, fillPrice, fillQuantity, orderFee, $"GDAX Fill Event {direction}" ); // when the order is completely filled, we no longer need it in the active order list if (orderEvent.Status == OrderStatus.Filled) { Order outOrder; CachedOrderIDs.TryRemove(order.Id, out outOrder); PendingOrder removed; _pendingOrders.TryRemove(fill.OrderId, out removed); } OnOrderEvent(orderEvent); }
private void OnMatch(string data) { // deserialize the current match (trade) message var message = JsonConvert.DeserializeObject <Messages.Matched>(data, JsonSettings); if (string.IsNullOrEmpty(message.UserId)) { // message received from the "matches" channel if (_isDataQueueHandler) { EmitTradeTick(message); } return; } // message received from the "user" channel, this trade is ours // check the list of currently active orders, if the current trade is ours we are either a maker or a taker var currentOrder = CachedOrderIDs .FirstOrDefault(o => o.Value.BrokerId.Contains(message.MakerOrderId) || o.Value.BrokerId.Contains(message.TakerOrderId)); if (currentOrder.Value == null) { // should never happen, log just in case Log.Error($"GDAXBrokerage.OrderMatch(): Unexpected match: {message.ProductId} {data}"); return; } Log.Trace($"GDAXBrokerage.OrderMatch(): Match: {message.ProductId} {data}"); var order = currentOrder.Value; if (!FillSplit.ContainsKey(order.Id)) { FillSplit[order.Id] = new GDAXFill(order); } var split = FillSplit[order.Id]; split.Add(message); var symbol = ConvertProductId(message.ProductId); // is this the total order at once? Is this the last split fill? var isFinalFill = Math.Abs(message.Size) == Math.Abs(order.Quantity) || Math.Abs(split.OrderQuantity) == Math.Abs(split.TotalQuantity); EmitFillOrderEvent(message, symbol, split, isFinalFill); }
private void OrderMatch(string data) { // deserialize the current match (trade) message var message = JsonConvert.DeserializeObject <Messages.Matched>(data, JsonSettings); if (_isDataQueueHandler) { EmitTradeTick(message); } // check the list of currently active orders, if the current trade is ours we are either a maker or a taker var currentOrder = CachedOrderIDs .FirstOrDefault(o => o.Value.BrokerId.Contains(message.MakerOrderId) || o.Value.BrokerId.Contains(message.TakerOrderId)); if (_pendingGdaxMarketOrderId != null && // order fill for other users (currentOrder.Value == null || // order fill for other order of ours (less likely but may happen) currentOrder.Value.BrokerId[0] != _pendingGdaxMarketOrderId)) { // process all fills for our pending market order var fills = FillSplit[_pendingLeanMarketOrderId]; var fillMessages = fills.Messages; for (var i = 0; i < fillMessages.Count; i++) { var fillMessage = fillMessages[i]; var isFinalFill = i == fillMessages.Count - 1; // emit all order events with OrderStatus.PartiallyFilled except for the last one which has OrderStatus.Filled EmitFillOrderEvent(fillMessage, fills.Order.Symbol, fills, isFinalFill); } // clear the pending market order _pendingGdaxMarketOrderId = null; _pendingLeanMarketOrderId = 0; } if (currentOrder.Value == null) { // not our order, nothing else to do here return; } Log.Trace($"GDAXBrokerage.OrderMatch(): Match: {message.ProductId} {data}"); var order = currentOrder.Value; if (order.Type == OrderType.Market) { // Fill events for this order will be delayed until we receive messages for a different order, // so we can know which is the last fill. // The market order total filled quantity can be less than the total order quantity, // details here: https://github.com/QuantConnect/Lean/issues/1751 // do not process market order fills immediately, save off the order ids _pendingGdaxMarketOrderId = order.BrokerId[0]; _pendingLeanMarketOrderId = order.Id; } if (!FillSplit.ContainsKey(order.Id)) { FillSplit[order.Id] = new GDAXFill(order); } var split = FillSplit[order.Id]; split.Add(message); if (order.Type != OrderType.Market) { var symbol = ConvertProductId(message.ProductId); // is this the total order at once? Is this the last split fill? var isFinalFill = Math.Abs(message.Size) == Math.Abs(order.Quantity) || Math.Abs(split.OrderQuantity) == Math.Abs(split.TotalQuantity); EmitFillOrderEvent(message, symbol, split, isFinalFill); } }
private void OrderMatch(string data) { var message = JsonConvert.DeserializeObject <Messages.Matched>(data, JsonSettings); if (_isDataQueueHandler) { EmitTradeTick(message); } var cached = CachedOrderIDs .Where(o => o.Value.BrokerId.Contains(message.MakerOrderId) || o.Value.BrokerId.Contains(message.TakerOrderId)) .ToList(); var symbol = ConvertProductId(message.ProductId); if (cached.Count == 0) { return; } Log.Trace($"GDAXBrokerage.OrderMatch(): Match: {message.ProductId} {data}"); var orderId = cached[0].Key; var order = cached[0].Value; if (!FillSplit.ContainsKey(orderId)) { FillSplit[orderId] = new GDAXFill(order); } var split = FillSplit[orderId]; split.Add(message); //is this the total order at once? Is this the last split fill? var status = Math.Abs(message.Size) == Math.Abs(order.Quantity) || Math.Abs(split.OrderQuantity) == Math.Abs(split.TotalQuantity()) ? OrderStatus.Filled : OrderStatus.PartiallyFilled; OrderDirection direction; // Messages are always from the perspective of the market maker. Flip direction if executed as a taker. if (order.BrokerId[0] == message.TakerOrderId) { direction = message.Side == "sell" ? OrderDirection.Buy : OrderDirection.Sell; } else { direction = message.Side == "sell" ? OrderDirection.Sell : OrderDirection.Buy; } decimal totalOrderFee; if (!_orderFees.TryGetValue(orderId, out totalOrderFee)) { totalOrderFee = GetFee(order); _orderFees[orderId] = totalOrderFee; } // apply order fee on a pro rata basis var orderFee = totalOrderFee * Math.Abs(message.Size) / Math.Abs(order.Quantity); var orderEvent = new OrderEvent ( orderId, symbol, message.Time, status, direction, message.Price, direction == OrderDirection.Sell ? -message.Size : message.Size, orderFee, $"GDAX Match Event {direction}" ); //if we're filled we won't wait for done event if (orderEvent.Status == OrderStatus.Filled) { Order outOrder; CachedOrderIDs.TryRemove(orderId, out outOrder); decimal outOrderFee; _orderFees.TryRemove(orderId, out outOrderFee); } OnOrderEvent(orderEvent); }