public async Task <ActionResult> GetLatestPrice(string symbol) { try { var price = await _marketDataClient.GetLatestPrice(symbol); Response.ContentType = "application/json"; return(Ok(price)); } catch (Exception e) { Log.Fatal(e, $"{nameof(GetLatestPrice)}"); return(StatusCode(500)); } }
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); } }