private static void CreateQueuedOrder(TradeExecInput input, int userId, PseudoMarketsDbContext _context) { try { QueuedOrders order = new QueuedOrders { OrderDate = DateTime.Today, OrderType = input.Type, Quantity = input.Quantity, Symbol = input.Symbol, UserId = userId, IsOpenOrder = true, EnvironmentId = RDSEnums.EnvironmentId.RdsFallback }; _context.QueuedOrders.Add(order); _context.SaveChanges(); } catch (Exception e) { Log.Fatal(e, $"{nameof(CreateQueuedOrder)}"); } }
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)); } } }
private static int PerformQueuedOrderExecution() { int numRecordsAffected = 0; ServiceCollection serviceCollection = new ServiceCollection(); // Configure the config service so we can fetch the connection string from it ConfigureServices(serviceCollection); var httpClient = new HttpClient() { BaseAddress = new Uri(BaseUrl) }; try { using (var db = new PseudoMarketsDbContext()) { List <QueuedOrders> queuedOrders = new List <QueuedOrders>(); // Step 0: Perform filtering based on the day of the week. Executions on Monday morning need to include orders placed after hours on Friday through Sunday night. if (DateTime.Now.DayOfWeek == DayOfWeek.Monday) { queuedOrders = db.QueuedOrders.Where(x => x.IsOpenOrder && x.OrderDate >= DateTime.Today.AddDays(-3) && x.OrderDate <= DateTime.Today) .ToList(); } else { queuedOrders = db.QueuedOrders.Where(x => x.IsOpenOrder && x.OrderDate == DateTime.Today.AddDays(-1)) .ToList(); } Log.Information($"Found {queuedOrders.Count} queued orders on {DateTime.Today}"); foreach (QueuedOrders order in queuedOrders) { // Step 1: Create a new special auth token to place a proxied order string tempToken = GenerateToken($"SODOrderProxy-{order.UserId}", TokenType.Special); httpClient.DefaultRequestHeaders.Add("UnifiedAuth", tempToken); var token = db.Tokens.FirstOrDefault(x => x.UserID == order.UserId); if (token != null) { token.Token = tempToken; db.Entry(token).State = EntityState.Modified; db.SaveChanges(); } // Step 2: Create an order object and POST it to the Trading API TradeExecInput orderInput = new TradeExecInput() { Symbol = order.Symbol, Quantity = order.Quantity, Type = order.OrderType }; var request = new HttpRequestMessage(HttpMethod.Post, "/api/Trade/Execute"); string jsonRequest = JsonConvert.SerializeObject(orderInput); var stringContent = new StringContent(jsonRequest, Encoding.UTF8, "application/json"); request.Content = stringContent; var response = httpClient.SendAsync(request); var responseString = response.Result.Content.ReadAsStringAsync(); // Step 3: Log the result Log.Information(responseString.Result); // Step 4: Remove the queued order db.Entry(order).State = EntityState.Deleted; numRecordsAffected++; } db.SaveChanges(); } } catch (Exception e) { Log.Error(e, $"{nameof(PerformQueuedOrderExecution)}"); return(-1); } return(numRecordsAffected); }
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); } }