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); }
/// <summary> /// Creates a new order /// </summary> /// <param name="order"></param> /// <returns></returns> public override bool PlaceOrder(Orders.Order order) { LockStream(); var req = new RestRequest("/orders", Method.POST); dynamic payload = new ExpandoObject(); payload.size = Math.Abs(order.Quantity); payload.side = order.Direction.ToString().ToLower(); payload.type = ConvertOrderType(order.Type); payload.price = order is LimitOrder ? ((LimitOrder)order).LimitPrice : order is StopMarketOrder ? ((StopMarketOrder)order).StopPrice : 0; payload.product_id = ConvertSymbol(order.Symbol); if (_algorithm.BrokerageModel.AccountType == AccountType.Margin) { payload.overdraft_enabled = true; } req.AddJsonBody(payload); GetAuthenticationToken(req); var response = RestClient.Execute(req); if (response.StatusCode == System.Net.HttpStatusCode.OK && response.Content != null) { var raw = JsonConvert.DeserializeObject <Messages.Order>(response.Content); if (raw == null || raw.Id == null) { OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, (int)response.StatusCode, "GDAXBrokerage.PlaceOrder: Error parsing response from place order: " + response.Content)); UnlockStream(); return(false); } var brokerId = raw.Id; if (CachedOrderIDs.ContainsKey(order.Id)) { CachedOrderIDs[order.Id].BrokerId.Add(brokerId); } else { order.BrokerId.Add(brokerId); CachedOrderIDs.TryAdd(order.Id, order); } // Add fill splits in all cases; we'll need to handle market fills too. FillSplit.TryAdd(order.Id, new GDAXFill(order)); // Generate submitted event OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, 0, "GDAX Order Event") { Status = OrderStatus.Submitted }); OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Information, -1, "GDAXBrokerage.PlaceOrder: Order completed successfully orderid:" + order.Id.ToString())); UnlockStream(); return(true); } OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, 0, "GDAX Order Event") { Status = OrderStatus.Invalid }); var message = $"GDAXBrokerage.PlaceOrder: Order failed Order Id: {order.Id} timestamp: {order.Time} quantity: {order.Quantity} content: {response.Content}"; OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Error, -1, message)); UnlockStream(); return(false); }
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); }
/// <summary> /// Creates a new order /// </summary> /// <param name="order"></param> /// <returns></returns> public override bool PlaceOrder(Order order) { LockStream(); var req = new RestRequest("/orders", Method.POST); dynamic payload = new ExpandoObject(); payload.size = Math.Abs(order.Quantity); payload.side = order.Direction.ToString().ToLower(); payload.type = ConvertOrderType(order.Type); payload.price = (order as LimitOrder)?.LimitPrice ?? ((order as StopMarketOrder)?.StopPrice ?? 0); payload.product_id = ConvertSymbol(order.Symbol); if (_algorithm.BrokerageModel.AccountType == AccountType.Margin) { payload.overdraft_enabled = true; } var orderProperties = order.Properties as GDAXOrderProperties; if (orderProperties != null) { if (order.Type == OrderType.Limit) { payload.post_only = orderProperties.PostOnly; } } req.AddJsonBody(payload); GetAuthenticationToken(req); var response = ExecuteRestRequest(req, GdaxEndpointType.Private); if (response.StatusCode == HttpStatusCode.OK && response.Content != null) { var raw = JsonConvert.DeserializeObject <Messages.Order>(response.Content); if (raw?.Id == null) { var errorMessage = $"Error parsing response from place order: {response.Content}"; OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, 0, "GDAX Order Event") { Status = OrderStatus.Invalid, Message = errorMessage }); OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, (int)response.StatusCode, errorMessage)); UnlockStream(); return(true); } if (raw.Status == "rejected") { var errorMessage = "Reject reason: " + raw.RejectReason; OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, 0, "GDAX Order Event") { Status = OrderStatus.Invalid, Message = errorMessage }); OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, (int)response.StatusCode, errorMessage)); UnlockStream(); return(true); } var brokerId = raw.Id; if (CachedOrderIDs.ContainsKey(order.Id)) { CachedOrderIDs[order.Id].BrokerId.Add(brokerId); } else { order.BrokerId.Add(brokerId); CachedOrderIDs.TryAdd(order.Id, order); } // Add fill splits in all cases; we'll need to handle market fills too. FillSplit.TryAdd(order.Id, new GDAXFill(order)); // Generate submitted event OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, 0, "GDAX Order Event") { Status = OrderStatus.Submitted }); OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Information, -1, "Order completed successfully orderid:" + order.Id)); UnlockStream(); return(true); } var message = $"Order failed, Order Id: {order.Id} timestamp: {order.Time} quantity: {order.Quantity} content: {response.Content}"; OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, 0, "GDAX Order Event") { Status = OrderStatus.Invalid }); OnMessage(new BrokerageMessageEvent(BrokerageMessageType.Warning, -1, message)); UnlockStream(); return(true); }
/// <summary> /// Creates a new order /// </summary> /// <param name="order"></param> /// <returns></returns> public override bool PlaceOrder(Orders.Order order) { var req = new RestRequest("/orders", Method.POST); dynamic payload = new ExpandoObject(); payload.size = Math.Abs(order.Quantity); payload.side = order.Direction.ToString().ToLower(); payload.type = ConvertOrderType(order.Type); payload.price = order is LimitOrder ? ((LimitOrder)order).LimitPrice : order is StopMarketOrder ? ((StopMarketOrder)order).StopPrice : 0; payload.product_id = ConvertSymbol(order.Symbol); if (_algorithm.BrokerageModel.AccountType == AccountType.Margin) { payload.overdraft_enabled = true; } req.AddJsonBody(payload); GetAuthenticationToken(req); var response = RestClient.Execute(req); if (response.StatusCode == System.Net.HttpStatusCode.OK && response.Content != null) { dynamic raw = JsonConvert.DeserializeObject <dynamic>(response.Content); if (raw == null || raw.id == null) { Log.Error("GDAXBrokerage.PlaceOrder: Error parsing response from place order"); return(false); } string brokerId = raw.id; if (CachedOrderIDs.ContainsKey(order.Id)) { CachedOrderIDs[order.Id].BrokerId.Add(brokerId); } else { order.BrokerId.Add(brokerId); CachedOrderIDs.TryAdd(order.Id, order); } if (order.Type != OrderType.Market) { FillSplit.TryAdd(order.Id, new GDAXFill(order)); } OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, 0, "GDAX Order Event") { Status = OrderStatus.Submitted }); if (order.Type == OrderType.Market) { OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, (decimal)raw.fill_fees, "GDAX Order Event") { Status = OrderStatus.Filled }); Orders.Order outOrder = null; CachedOrderIDs.TryRemove(order.Id, out outOrder); } Log.Trace("GDAXBrokerage.PlaceOrder: Order completed successfully orderid:" + order.Id.ToString()); return(true); } OnOrderEvent(new OrderEvent(order, DateTime.UtcNow, 0, "GDAX Order Event") { Status = OrderStatus.Invalid }); Log.Trace("GDAXBrokerage.PlaceOrder: Order failed Order Id: " + order.Id + " timestamp:" + order.Time + " quantity: " + order.Quantity.ToString() + " content:" + response.Content); return(false); }