private async Task <IList <EventEntry> > PlanStopOrderEvents( string user, string accountId, string instrument, decimal quantity, OrderSide orderSide, decimal stopPriceValue, string durationType, decimal?duration, decimal?stopLoss, decimal?takeProfit, string requestId, Func <string, Exception> reportInvalidMessage) { if (quantity <= 0) { throw reportInvalidMessage("You cannot create an order with a quantity of 0 or less"); } if (stopPriceValue <= 0 || stopLoss.HasValue && stopLoss.Value <= 0 || takeProfit.HasValue && takeProfit.Value <= 0) { throw reportInvalidMessage("You cannot create a stop order with a price of 0 or less"); } var stopOrderEvent = new CreateOrderEventEntry { User = user, AccountId = accountId, Instrument = instrument, Qty = quantity, Side = orderSide, Type = OrderType.Stop, LimitPrice = null, StopPrice = stopPriceValue, DurationType = durationType, Duration = duration, StopLoss = stopLoss, TakeProfit = takeProfit, }; var plannedEvents = new List <EventEntry> { stopOrderEvent }; VersionControl.ExecuteUsingFixedVersion(currentVersionNumber => { var eventVersionNumber = currentVersionNumber + 1; stopOrderEvent.VersionNumber = eventVersionNumber; AssertUnreservedBalance( user, accountId, instrument, quantity, orderSide, stopPriceValue, reportInvalidMessage); }); return(plannedEvents); }
public void ProcessEvent(CreateOrderEventEntry eventEntry) { _tradingOrderService.CreateOrder(eventEntry); if (eventEntry.Type == OrderType.Limit || eventEntry.Type == OrderType.Stop) { var price = eventEntry.Type == OrderType.Limit ? eventEntry.LimitPrice.Value : eventEntry.StopPrice.Value; _userService.ModifyReservedBalance( eventEntry.User, eventEntry.AccountId, eventEntry.Instrument.Split("_")[ eventEntry.Side == OrderSide.Buy ? 1 : 0 ], eventEntry.Side == OrderSide.Buy ? eventEntry.Qty * price : eventEntry.Qty ).Wait(); } }
private async Task <IList <EventEntry> > PlanMarketOrderEvents( string user, string accountId, string instrument, decimal quantity, OrderSide orderSide, string durationType, decimal?duration, decimal?stopLoss, decimal?takeProfit, string requestId, Func <string, Exception> reportInvalidMessage) { if (quantity <= 0) { throw reportInvalidMessage("You cannot create an order with a quantity of 0 or less"); } if (stopLoss.HasValue && stopLoss.Value <= 0 || takeProfit.HasValue && takeProfit.Value <= 0) { throw reportInvalidMessage("You cannot create a market order with a price of 0 or less"); } var marketOrderEvent = new CreateOrderEventEntry { User = user, AccountId = accountId, Instrument = instrument, Qty = quantity, //FilledMarketOrderQty Side = orderSide, Type = OrderType.Market, //LimitPrice, StopPrice = null, DurationType = durationType, Duration = duration, StopLoss = stopLoss, TakeProfit = takeProfit, }; var plannedEvents = new List <EventEntry> { marketOrderEvent }; VersionControl.ExecuteUsingFixedVersion(currentVersionNumber => { var eventVersionNumber = currentVersionNumber + 1; marketOrderEvent.VersionNumber = eventVersionNumber; // In the case of buying we cannot be sure that the entire quantity will get matched if (orderSide == OrderSide.Sell) { AssertUnreservedBalance( user, accountId, instrument, quantity, orderSide, null, reportInvalidMessage); } var matchedOrders = PlanMatchOrdersLocked( user, accountId, instrument, orderSide, null, quantity, eventVersionNumber, requestId, reportInvalidMessage ); if (matchedOrders.Item1.Count == 0) { throw reportInvalidMessage("Cannot execute a market order, as there are no other orders"); } matchedOrders.Item1.ForEach(plannedEvents.Add); marketOrderEvent.FilledMarketOrderQty = quantity - matchedOrders.Item2; marketOrderEvent.LimitPrice = matchedOrders.Item1.Last().Price; }); return(plannedEvents); }
private async Task <IList <EventEntry> > PlanLimitOrderEvents( string user, string accountId, string instrument, decimal quantity, OrderSide orderSide, decimal limitPriceValue, string durationType, decimal?duration, decimal?stopLoss, decimal?takeProfit, string requestId, Func <string, Exception> reportInvalidMessage) { if (quantity <= 0) { throw reportInvalidMessage("You cannot create an order with a quantity of 0 or less"); } if (limitPriceValue <= 0 || stopLoss.HasValue && stopLoss.Value <= 0 || takeProfit.HasValue && takeProfit.Value <= 0) { throw reportInvalidMessage("You cannot create a limit order with a price of 0 or less"); } var limitOrderEvent = new CreateOrderEventEntry { User = user, AccountId = accountId, Instrument = instrument, Qty = quantity, Side = orderSide, Type = OrderType.Limit, LimitPrice = limitPriceValue, StopPrice = null, DurationType = durationType, Duration = duration, StopLoss = stopLoss, TakeProfit = takeProfit, }; var plannedEvents = new List <EventEntry> { limitOrderEvent }; var finished = false; // Note: using async prefix on the lambda caused it to never finish! VersionControl.ExecuteUsingFixedVersion(currentVersionNumber => { var eventVersionNumber = currentVersionNumber + 1; // We are now locked to a specific version number limitOrderEvent.VersionNumber = eventVersionNumber; AssertUnreservedBalance( user, accountId, instrument, quantity, orderSide, limitPriceValue, reportInvalidMessage); PlanMatchOrdersLocked( user, accountId, instrument, orderSide, limitPriceValue, quantity, eventVersionNumber, requestId, reportInvalidMessage ).Item1.ForEach(plannedEvents.Add); finished = true; }); if (finished == false) { throw new Exception($"{nameof(VersionControl.ExecuteUsingFixedVersion)} didn't finish"); } return(plannedEvents); }
internal void CreateOrder(CreateOrderEventEntry createOrder) { _logger.LogDebug("Called create order @ version number " + createOrder.VersionNumber); switch (createOrder.Type) { case OrderType.Limit: var limitOrder = new OrderBookEntry { EntryTime = createOrder.EntryTime, CreatedOnVersionId = createOrder.VersionNumber, User = createOrder.User, AccountId = createOrder.AccountId, Instrument = createOrder.Instrument, Qty = createOrder.Qty, Side = createOrder.Side, FilledQty = 0m, LimitPrice = createOrder.LimitPrice.Value, // TODO from stop loss and take profit //ChildrenIds DurationType = createOrder.DurationType, Duration = createOrder.Duration, }; OrderBook.InsertOne(limitOrder); break; case OrderType.Stop: var stopOrder = new HiddenOrderEntry { EntryTime = createOrder.EntryTime, CreatedOnVersionId = createOrder.VersionNumber, User = createOrder.User, AccountId = createOrder.AccountId, Instrument = createOrder.Instrument, Qty = createOrder.Qty, Side = createOrder.Side, StopPrice = createOrder.StopPrice.Value, // TODO from stop loss and take profit //ChildrenIds DurationType = createOrder.DurationType, Duration = createOrder.Duration, }; HiddenOrders.InsertOne(stopOrder); break; case OrderType.Market: OrderHistory.InsertOne( new OrderHistoryEntry { CreateTime = createOrder.EntryTime, CloseTime = createOrder.EntryTime, User = createOrder.User, AccountId = createOrder.AccountId, Instrument = createOrder.Instrument, Qty = createOrder.Qty, Side = createOrder.Side, // Closed market order Type = OrderType.Market, // The market order will be filled by the following match orders FilledQty = createOrder.FilledMarketOrderQty, // The most expensive accepted limit price will be shown to the user LimitPrice = createOrder.LimitPrice.Value, StopPrice = null, // TODO from stop loss and take profit //ChildrenIds DurationType = createOrder.DurationType, Duration = createOrder.Duration, Status = OrderStatus.Filled, } ); break; default: throw new ArgumentOutOfRangeException(nameof(createOrder.Type)); } _logger.LogDebug("Persisted create order @ version number " + createOrder.VersionNumber); }