public async Task <ActionResult> ExecuteTrade([FromBody] TradeExecInput input) { var authResult = await _unifiedAuth.AuthenticateUser(Request.HttpContext); var tokenStatus = authResult.TokenStatus; switch (tokenStatus) { case TokenHelper.TokenStatus.Valid: { TradeExecOutput response = default; // Use the Consolidated Trading Platform Service (Real-time and/or RDS) if (_consolidatedTradingPlatformEnabled) { if (authResult.Account != null) { var consolidatedTradeRequest = new ConsolidatedTradeRequest() { AccountId = authResult.Account.Id, OrderAction = input.Type, OrderOrigin = ConsolidatedTradeEnums.ConsolidatedOrderOrigin.PseudoMarkets, OrderTiming = ConsolidatedTradeEnums.ConsolidatedOrderTiming.DayOnly, OrderType = ConsolidatedTradeEnums.ConsolidatedOrderType.Market, Quantity = input.Quantity, Symbol = input.Symbol, EnforceMarketOpenCheck = true }; _tradingPlatformClient.SendTradeRequest(consolidatedTradeRequest); var ctpResponse = _tradingPlatformClient.GetTradeResponse(); if (ctpResponse != null) { response = new TradeExecOutput() { Order = ctpResponse?.Order, StatusCode = GetTradeStatusCodeUsing(ctpResponse.StatusCode), StatusMessage = ctpResponse?.StatusMessage }; } } } else { // Fallback to legacy trading code using Relational Data Store (RDS) response = await RdsFallback.ProcessTradingRequestUsingRdsFallback(authResult.User, authResult.Account, input, _context, _marketDataService, _dateTimeHelper); } return(Ok(response)); } case TokenHelper.TokenStatus.Expired: { TradeExecOutput status = new TradeExecOutput { Order = null, StatusCode = TradeStatusCodes.ExecutionError, StatusMessage = StatusMessages.ExpiredTokenMessage }; return(Ok(status)); } default: { TradeExecOutput status = new TradeExecOutput { Order = null, StatusCode = TradeStatusCodes.ExecutionError, StatusMessage = StatusMessages.InvalidTokenMessage }; return(Ok(status)); } } }
public static async Task <TradeExecOutput> ProcessTradingRequestUsingRdsFallback(Users user, Accounts account, TradeExecInput input, PseudoMarketsDbContext context, MarketDataServiceClient marketDataService, DateTimeHelper dateTimeHelper) { try { var transactionId = Guid.NewGuid().ToString(); var latestPrice = await marketDataService.GetLatestPrice(input.Symbol); double price = latestPrice.price; double value = price * input.Quantity; bool hasSufficientBalance = account.Balance >= value; TradeExecOutput output = new TradeExecOutput(); // TODO: Extract trade logic into a separate static class if (hasSufficientBalance) { if (input.Type.ToUpper() == "BUY" || input.Type.ToUpper() == "SELL" || input.Type.ToUpper() == "SELLSHORT") { if (price > 0 && input.Quantity > 0) { if (dateTimeHelper.IsMarketOpen()) { Orders order = new Orders { Symbol = input.Symbol, Type = input.Type, Price = price, Quantity = input.Quantity, Date = DateTime.Now, TransactionID = transactionId, EnvironmentId = RDSEnums.EnvironmentId.RdsFallback, OriginId = RDSEnums.OriginId.PseudoMarkets, SecurityTypeId = RDSEnums.SecurityType.RealWorld }; Transactions transaction = new Transactions { AccountId = account.Id, TransactionId = transactionId, EnvironmentId = RDSEnums.EnvironmentId.RdsFallback, OriginId = RDSEnums.OriginId.PseudoMarkets }; context.Orders.Add(order); context.Transactions.Add(transaction); await context.SaveChangesAsync(); var createdOrder = await context.Orders.FirstOrDefaultAsync(x => x.TransactionID == transactionId); if (input.Type.ToUpper() == "BUY") { var doesAccountHaveExistingPosition = context.Positions.Any(x => x.AccountId == account.Id && x.Symbol == input.Symbol); if (doesAccountHaveExistingPosition) { var existingPosition = await context.Positions .Where(x => x.AccountId == account.Id && x.Symbol == input.Symbol) .FirstOrDefaultAsync(); // Long position if (existingPosition.Quantity > 0) { existingPosition.Value += value; existingPosition.Quantity += input.Quantity; context.Entry(existingPosition).State = EntityState.Modified; account.Balance = account.Balance - value; context.Entry(account).State = EntityState.Modified; await context.SaveChangesAsync(); } else // Short position { if (Math.Abs(existingPosition.Quantity) == input.Quantity) { double gainOrLoss = existingPosition.Value - value; account.Balance += gainOrLoss; context.Entry(account).State = EntityState.Modified; context.Entry(existingPosition).State = EntityState.Deleted; await context.SaveChangesAsync(); } else { existingPosition.Value = existingPosition.Value - value; existingPosition.Quantity += input.Quantity; account.Balance += existingPosition.Value - value; context.Entry(existingPosition).State = EntityState.Modified; context.Entry(account).State = EntityState.Modified; await context.SaveChangesAsync(); } } } else { Positions position = new Positions { AccountId = account.Id, OrderId = createdOrder.Id, Value = value, Symbol = input.Symbol, Quantity = input.Quantity, EnvironmentId = RDSEnums.EnvironmentId.RdsFallback, OriginId = RDSEnums.OriginId.PseudoMarkets, SecurityTypeId = RDSEnums.SecurityType.RealWorld }; context.Positions.Add(position); await context.SaveChangesAsync(); account.Balance = account.Balance - value; context.Entry(account).State = EntityState.Modified; await context.SaveChangesAsync(); } } else if (input.Type.ToUpper() == "SELL") { var doesAccountHaveExistingPosition = context.Positions.Any(x => x.AccountId == account.Id && x.Symbol == input.Symbol); if (doesAccountHaveExistingPosition) { var existingPosition = await context.Positions .Where(x => x.AccountId == account.Id && x.Symbol == input.Symbol) .FirstOrDefaultAsync(); if (input.Quantity == existingPosition.Quantity) { account.Balance = account.Balance + value; context.Entry(existingPosition).State = EntityState.Deleted; context.Entry(account).State = EntityState.Modified; await context.SaveChangesAsync(); } else { existingPosition.Value -= value; existingPosition.Quantity -= input.Quantity; context.Entry(existingPosition).State = EntityState.Modified; account.Balance = account.Balance + value; context.Entry(account).State = EntityState.Modified; await context.SaveChangesAsync(); } } else { output.StatusCode = TradeStatusCodes.ExecutionError; output.StatusMessage = StatusMessages.InvalidPositionsMessage + input.Symbol; return(output); } } else if (input.Type.ToUpper() == "SELLSHORT") { var doesAccountHaveExistingPosition = context.Positions.Any(x => x.AccountId == account.Id && x.Symbol == input.Symbol); if (doesAccountHaveExistingPosition) { var existingPosition = await context.Positions .Where(x => x.AccountId == account.Id && x.Symbol == input.Symbol) .FirstOrDefaultAsync(); if (existingPosition.Quantity > 0) { context.Entry(createdOrder).State = EntityState.Deleted; await context.SaveChangesAsync(); output.StatusCode = TradeStatusCodes.ExecutionError; output.StatusMessage = StatusMessages.InvalidShortPositionMessage; return(output); } existingPosition.Value += value; existingPosition.Quantity += input.Quantity * -1; context.Entry(existingPosition).State = EntityState.Modified; await context.SaveChangesAsync(); } else { Positions position = new Positions { AccountId = account.Id, OrderId = createdOrder.Id, Value = value, Symbol = input.Symbol, Quantity = input.Quantity * -1, EnvironmentId = RDSEnums.EnvironmentId.RdsFallback, OriginId = RDSEnums.OriginId.PseudoMarkets, SecurityTypeId = RDSEnums.SecurityType.RealWorld }; context.Positions.Add(position); account.Balance = account.Balance - value; context.Entry(account).State = EntityState.Modified; await context.SaveChangesAsync(); } } output.StatusCode = TradeStatusCodes.ExecutionOk; output.StatusMessage = StatusMessages.SuccessMessage; output.Order = createdOrder; return(output); } CreateQueuedOrder(input, user.Id, context); output.StatusMessage = "Market is closed, order has been queued to be filled on next market open"; output.StatusCode = TradeStatusCodes.ExecutionQueued; return(output); } output.StatusCode = TradeStatusCodes.ExecutionError; output.StatusMessage = StatusMessages.InvalidSymbolOrQuantityMessage; return(output); } output.StatusCode = TradeStatusCodes.ExecutionError; output.StatusMessage = StatusMessages.InvalidOrderTypeMessage; return(output); } output.StatusCode = TradeStatusCodes.ExecutionError; output.StatusMessage = StatusMessages.InsufficientBalanceMessage; return(output); } catch (Exception e) { TradeExecOutput status = new TradeExecOutput { Order = null, StatusCode = TradeStatusCodes.ExecutionError, StatusMessage = StatusMessages.FailureMessage }; Log.Fatal(e, $"{nameof(ProcessTradingRequestUsingRdsFallback)}"); return(status); } }