Esempio n. 1
0
        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)}");
            }
        }
Esempio n. 2
0
        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));
            }
            }
        }
Esempio n. 3
0
        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);
        }
Esempio n. 4
0
        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);
            }
        }