public static byte[] Serialize(OrderWrapper order) { if (order == null) { throw new ArgumentNullException(nameof(order)); } byte[] msg = new byte[sizeOfMessage]; Write(msg, messageLengthOffset, sizeOfMessage); msg[messageTypeOffset] = (byte)MessageType.NewOrderRequest; Write(msg, versionOffset, version); Write(msg, sideOffset, order.Order.IsBuy); msg[orderConditionOffset] = (byte)order.OrderCondition; Write(msg, orderIdOffset, order.Order.OrderId); Write(msg, priceOffset, order.Order.Price); if (order.TipQuantity > 0 && order.TotalQuantity > 0) { Write(msg, quantityOffset, order.TipQuantity); } else { Write(msg, quantityOffset, order.Order.OpenQuantity); } Write(msg, stopPriceOffset, order.StopPrice); Write(msg, totalQuantityOffset, order.TotalQuantity); Write(msg, cancelOnOffset, order.Order.CancelOn); Write(msg, orderAmountOffset, order.Order.OrderAmount); return(msg); }
public static OrderWrapper Deserialize(byte[] bytes) { if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); } if (bytes.Length != sizeOfMessage) { throw new Exception("Order Message must be of Size : " + sizeOfMessage); } var messageType = (MessageType)(bytes[messageTypeOffset]); if (messageType != MessageType.NewOrderRequest) { throw new Exception("Invalid Message"); } var version = BitConverter.ToInt16(bytes, versionOffset); if (version != OrderSerializer.version) { throw new Exception("version mismatch"); } var order = new OrderWrapper(); order.Order = new Order(); order.Order.IsBuy = BitConverter.ToBoolean(bytes, sideOffset); order.OrderCondition = (OrderCondition)bytes[orderConditionOffset]; order.Order.OrderId = BitConverter.ToInt32(bytes, orderIdOffset); order.Order.Price = ReadPrice(bytes, priceOffset); order.Order.OpenQuantity = ReadQuantity(bytes, quantityOffset); order.StopPrice = ReadPrice(bytes, stopPriceOffset); if (order.StopPrice > 0) { order.Order.IsStop = true; } order.TotalQuantity = ReadQuantity(bytes, totalQuantityOffset); if (order.TotalQuantity > 0) { order.TipQuantity = order.Order.OpenQuantity; } order.Order.CancelOn = BitConverter.ToInt32(bytes, cancelOnOffset); order.OrderAmount = ReadQuantity(bytes, orderAmountOffset); order.Order.FeeId = BitConverter.ToInt16(bytes, feeIdOffset); return(order); }
public OrderMatchingResult AddOrder(OrderWrapper orderWrapper, bool isOrderTriggered = false) { var incomingOrder = orderWrapper.Order; if (incomingOrder == null) { throw new ArgumentNullException(nameof(incomingOrder)); } incomingOrder.IsTip = false; if (incomingOrder.Price < 0 || (incomingOrder.OpenQuantity <= 0 && orderWrapper.OrderAmount == 0) || (incomingOrder.OpenQuantity == 0 && orderWrapper.OrderAmount <= 0) || orderWrapper.StopPrice < 0 || orderWrapper.TotalQuantity < 0) { return(OrderMatchingResult.InvalidPriceQuantityStopPriceOrderAmountOrTotalQuantity); } if (orderWrapper.OrderCondition == OrderCondition.BookOrCancel && (incomingOrder.Price == 0 || orderWrapper.StopPrice != 0)) { return(OrderMatchingResult.BookOrCancelCannotBeMarketOrStopOrder); } if (incomingOrder.OpenQuantity % _stepSize != 0 || orderWrapper.TotalQuantity % _stepSize != 0) { return(OrderMatchingResult.QuantityAndTotalQuantityShouldBeMultipleOfStepSize); } if (orderWrapper.OrderCondition == OrderCondition.ImmediateOrCancel && (orderWrapper.StopPrice != 0)) { return(OrderMatchingResult.ImmediateOrCancelCannotBeStopOrder); } if (orderWrapper.OrderCondition == OrderCondition.FillOrKill && orderWrapper.StopPrice != 0) { return(OrderMatchingResult.FillOrKillCannotBeStopOrder); } if (incomingOrder.CancelOn < 0) { return(OrderMatchingResult.InvalidCancelOnForGTD); } if (incomingOrder.CancelOn > 0 && (orderWrapper.OrderCondition == OrderCondition.FillOrKill || orderWrapper.OrderCondition == OrderCondition.ImmediateOrCancel)) { return(OrderMatchingResult.GoodTillDateCannotBeIOCorFOK); } if (incomingOrder.Price == 0 && orderWrapper.OrderAmount != 0 && incomingOrder.OpenQuantity != 0) { return(OrderMatchingResult.MarketOrderOnlySupportedOrderAmountOrQuantityNoBoth); } if (orderWrapper.OrderAmount != 0 && (incomingOrder.Price != 0 || !incomingOrder.IsBuy)) { return(OrderMatchingResult.OrderAmountOnlySupportedForMarketBuyOrder); } if (orderWrapper.TotalQuantity > 0) { incomingOrder.OpenQuantity = orderWrapper.TipQuantity; if (orderWrapper.OrderCondition == OrderCondition.FillOrKill || orderWrapper.OrderCondition == OrderCondition.ImmediateOrCancel) { return(OrderMatchingResult.IcebergOrderCannotBeFOKorIOC); } if (orderWrapper.StopPrice != 0 || incomingOrder.Price == 0) { return(OrderMatchingResult.IcebergOrderCannotBeStopOrMarketOrder); } if (orderWrapper.TotalQuantity <= orderWrapper.TipQuantity) { return(OrderMatchingResult.InvalidIcebergOrderTotalQuantity); } } if (_acceptedOrders.Contains(incomingOrder.OrderId)) { return(OrderMatchingResult.DuplicateOrder); } _acceptedOrders.Add(incomingOrder.OrderId); _tradeListener?.OnAccept(incomingOrder.OrderId); if (orderWrapper.OrderAmount != 0 && orderWrapper.StopPrice != 0) { _orderAmount.Add(orderWrapper.Order.OrderId, orderWrapper.OrderAmount); } Quantity?quantity = null; bool canBeFilled = false; if (orderWrapper.Order.IsBuy && orderWrapper.Order.OpenQuantity == 0 && orderWrapper.StopPrice == 0) { var quantityAndFill = GetQuantity(orderWrapper.OrderAmount); if (quantityAndFill.Quantity.HasValue) { quantity = quantityAndFill.Quantity.Value; canBeFilled = quantityAndFill.CanFill; } } var timeNow = _timeProvider.GetSecondsFromEpoch(); CancelExpiredOrders(timeNow); if (orderWrapper.OrderCondition == OrderCondition.BookOrCancel && ((incomingOrder.IsBuy && _book.BestAskPrice <= incomingOrder.Price) || (!incomingOrder.IsBuy && incomingOrder.Price <= _book.BestBidPrice))) { if (orderWrapper.TotalQuantity == 0) { _tradeListener?.OnCancel(incomingOrder.OrderId, incomingOrder.OpenQuantity, incomingOrder.Cost, CancelReason.BookOrCancel); } else { _tradeListener?.OnCancel(incomingOrder.OrderId, orderWrapper.TotalQuantity, incomingOrder.Cost, CancelReason.BookOrCancel); } } else if (orderWrapper.OrderCondition == OrderCondition.FillOrKill && orderWrapper.OrderAmount == 0 && !_book.CheckCanFillOrder(incomingOrder.IsBuy, incomingOrder.OpenQuantity, incomingOrder.Price)) { _tradeListener?.OnCancel(incomingOrder.OrderId, incomingOrder.OpenQuantity, incomingOrder.Cost, CancelReason.FillOrKill); } else if (orderWrapper.OrderCondition == OrderCondition.FillOrKill && orderWrapper.OrderAmount != 0 && canBeFilled == false) { _tradeListener?.OnCancel(incomingOrder.OrderId, 0, 0, CancelReason.FillOrKill); } else if (incomingOrder.CancelOn > 0 && incomingOrder.CancelOn <= timeNow) { if (orderWrapper.TotalQuantity == 0) { _tradeListener?.OnCancel(incomingOrder.OrderId, incomingOrder.OpenQuantity, incomingOrder.Cost, CancelReason.ValidityExpired); } else { _tradeListener?.OnCancel(incomingOrder.OrderId, orderWrapper.TotalQuantity, incomingOrder.Cost, CancelReason.ValidityExpired); } } else { if (orderWrapper.TotalQuantity > 0) { var iceberg = new Iceberg() { TipQuantity = orderWrapper.TipQuantity, TotalQuantity = orderWrapper.TotalQuantity }; _currentIcebergOrders.Add(incomingOrder.OrderId, iceberg); incomingOrder = GetTip(incomingOrder, iceberg); } if (incomingOrder.CancelOn > 0) { AddGoodTillDateOrder(incomingOrder.CancelOn, incomingOrder.OrderId); } _currentOrders.Add(incomingOrder.OrderId, incomingOrder); if (orderWrapper.StopPrice != 0 && !isOrderTriggered && ((incomingOrder.IsBuy && orderWrapper.StopPrice > _marketPrice) || (!incomingOrder.IsBuy && (orderWrapper.StopPrice < _marketPrice || _marketPrice == 0)))) { _book.AddStopOrder(incomingOrder, orderWrapper.StopPrice); } else { if (orderWrapper.Order.IsBuy && orderWrapper.Order.OpenQuantity == 0) { if (quantity.HasValue) { orderWrapper.Order.OpenQuantity = quantity.Value; MatchAndAddOrder(incomingOrder, orderWrapper.OrderCondition); } else { _currentOrders.Remove(orderWrapper.Order.OrderId); _tradeListener?.OnCancel(orderWrapper.Order.OrderId, 0, 0, CancelReason.MarketOrderNoLiquidity); } } else { MatchAndAddOrder(incomingOrder, orderWrapper.OrderCondition); } } } return(OrderMatchingResult.OrderAccepted); }