public TradeEventProcessor( TradingOrderService tradingOrderService, EventHistoryService eventHistoryService, UserService userService, ILogger <TradeEventProcessor> logger) { _tradingOrderService = tradingOrderService; _eventHistoryService = eventHistoryService; _userService = userService; _logger = logger; }
/// <summary> /// Created via ProcessorFactory. /// </summary> public TradeCommandProcessor( VersionControl versionControl, TradingOrderService tradingOrderService, UserService userService, EventHistoryService eventHistoryService, ILogger <TradeCommandProcessor> logger) { _logger = logger; VersionControl = versionControl; TradingOrderService = tradingOrderService; UserService = userService; EventHistoryService = eventHistoryService; }
public ProcessorFactory( VersionControl versionControl, TradingOrderService tradingOrderService, UserService userService, EventHistoryService eventHistoryService, ILogger<TradeCommandProcessor> tradeOrderPersistenceProcessorLogger) { _tradeOrderPersistenceProcessorLogger = tradeOrderPersistenceProcessorLogger; VersionControl = versionControl; TradingOrderService = tradingOrderService; UserService = userService; EventHistoryService = eventHistoryService; }
private (List <MatchOrderEventEntry>, decimal) PlanMatchOrdersLocked( string user, string accountId, string instrument, OrderSide orderSide, decimal?limitPriceValue, decimal quantityRemaining, long lockedEventVersionNumber, string requestId, Func <string, Exception> reportInvalidMessage) { var plannedEvents = new List <MatchOrderEventEntry>(); var baseCurrency = instrument.Split("_")[0]; var quoteCurrency = instrument.Split("_")[1]; // Start the process of matching relevant offers var matchingOffers = orderSide == OrderSide.Buy ? TradingOrderService.MatchSellers(limitPriceValue, instrument).Result : TradingOrderService.MatchBuyers(limitPriceValue, instrument).Result; matchingOffers.MoveNext(); var matchingOfferBatch = matchingOffers.Current.ToList(); while (quantityRemaining > 0 && matchingOfferBatch.Count > 0) { _logger.LogInformation( $"Request {requestId} matched a batch of {matchingOfferBatch.Count} {(orderSide == OrderSide.Buy ? "buyers" : "sellers")}"); foreach (var other in matchingOfferBatch) { var otherRemaining = other.Qty - other.FilledQty; decimal matchedQuantity; if (otherRemaining >= quantityRemaining) { // Entire command order remainder is consumed by the seller offer matchedQuantity = quantityRemaining; _logger.LogInformation( $"New {instrument} {(orderSide == OrderSide.Buy ? "buy" : "sell")} limit order planning entirely matched order id {other.Id}"); } else { // Fraction of order will remain, but the seller offer will be consumed matchedQuantity = otherRemaining; _logger.LogInformation( $"New {instrument} {(orderSide == OrderSide.Buy ? "buy" : "sell")} limit order planning partially matched order id {other.Id}"); } quantityRemaining -= matchedQuantity; var matchEvent = new MatchOrderEventEntry { VersionNumber = lockedEventVersionNumber, ActionUser = user, ActionAccountId = accountId, TargetOrderOnVersionNumber = other.CreatedOnVersionId, TargetUser = other.User, TargetAccountId = other.AccountId, Instrument = instrument, Qty = matchedQuantity, ActionSide = orderSide, Price = other.LimitPrice, ActionOrderQtyRemaining = quantityRemaining, TargetOrderQtyRemaining = other.Qty - other.FilledQty - matchedQuantity, }; // Calculating new balances for double-check purposes var(actionBaseMod, actionQuoteMod, targetBaseMod, targetQuoteMod) = TradeEventProcessor.MatchOrderBalanceModifications(matchEvent); try { matchEvent.ActionBaseNewBalance = UserService.GetBalanceAndReservedBalance( matchEvent.ActionUser, matchEvent.ActionAccountId, baseCurrency ).Item1 + actionBaseMod; matchEvent.ActionQuoteNewBalance = UserService.GetBalanceAndReservedBalance( matchEvent.ActionUser, matchEvent.ActionAccountId, quoteCurrency ).Item1 + actionQuoteMod; matchEvent.TargetBaseNewBalance = UserService.GetBalanceAndReservedBalance( matchEvent.TargetUser, matchEvent.TargetAccountId, baseCurrency ).Item1 + targetBaseMod; matchEvent.TargetQuoteNewBalance = UserService.GetBalanceAndReservedBalance( matchEvent.TargetUser, matchEvent.TargetAccountId, quoteCurrency ).Item1 + targetQuoteMod; } catch (Exception e) { // This can happen if a user didn't generate his balances yet, so it's not a fatal error throw reportInvalidMessage( $"There was a problem with your coin balances. {e.GetType().Name}: {e.Message}"); } plannedEvents.Add(matchEvent); if (quantityRemaining == 0) { break; } } if (quantityRemaining == 0) { break; } // Keep the iteration going in order to find further matching orders as long as remaining qty > 0 if (!matchingOffers.MoveNext()) { break; } matchingOfferBatch = matchingOffers.Current.ToList(); } return(plannedEvents, quantityRemaining); }
private async Task <IList <EventEntry> > PlanCancelOrder( string user, string accountId, long orderCreatedOnVersionNumber, string requestId, Func <string, Exception> reportInvalidMessage) { var plannedEvents = new List <EventEntry>(); VersionControl.ExecuteUsingFixedVersion(currentVersionNumber => { var eventVersionNumber = currentVersionNumber + 1; object openOrder; try { openOrder = TradingOrderService.FindOpenOrderCreatedByVersionNumber( orderCreatedOnVersionNumber ); } catch (InvalidOperationException) { openOrder = null; } if (openOrder is OrderBookEntry limitOrder) { if (!user.Equals(limitOrder.User) || !accountId.Equals(limitOrder.AccountId)) { throw reportInvalidMessage("Couldn't cancel limit order, user or accountId differs"); } plannedEvents.Add(new CancelOrderEventEntry { VersionNumber = eventVersionNumber, User = user, AccountId = accountId, Instrument = limitOrder.Instrument, CancelOrderCreatedOnVersionNumber = orderCreatedOnVersionNumber, }); return; } if (openOrder is HiddenOrderEntry stopOrder) { if (!user.Equals(stopOrder.User) || !accountId.Equals(stopOrder.AccountId)) { throw reportInvalidMessage("Couldn't cancel stop order, user or accountId differs"); } plannedEvents.Add(new CancelOrderEventEntry { VersionNumber = eventVersionNumber, User = user, AccountId = accountId, Instrument = stopOrder.Instrument, CancelOrderCreatedOnVersionNumber = orderCreatedOnVersionNumber, }); return; } throw reportInvalidMessage("Couldn't find a matching limit or stop order to close"); }); return(plannedEvents); }