Example #1
0
        public void PlaceBuyOrderDoesNotPlaceOrderWhenBalanceIsTooLow()
        {
            // Arrange

            const int volume   = 10;
            const int buyPrice = 11;
            var       tested   = new TransactionsLedger(0);

            // Act

            var result = tested.PlaceBuyOrder(quote1, buyPrice, volume);

            // Assert
            Assert.Multiple(() =>
            {
                Assert.AreEqual(OrderStatusType.DeniedInsufficientFunds, result);
                Assert.False(tested.TheLedger.Any());
                Assert.False(tested.OpenedPositions.Any());
            });
        }
Example #2
0
        public virtual SimulationResult Simulate(List <StockQuote> allQuotesPrefilterd,
                                                 TradingSimulationConfig tradingSimulationConfig,
                                                 IProgressReportable progress = null)
        {
            var ledger = new TransactionsLedger(tradingSimulationConfig.StartingCash);
            var result = new SimulationResult {
                TradingSimulationConfig = tradingSimulationConfig
            };

            var filteredQuotes = allQuotesPrefilterd.Where(x =>
                                                           x.DateParsed.InOpenRange(tradingSimulationConfig.FromDate, tradingSimulationConfig.ToDate)).ToList();

            var tradingStartingDate = filteredQuotes.Min(x => x.DateParsed);
            var tradingEndDate      = filteredQuotes.Max(x => x.DateParsed);
            var datesToTrade        = filteredQuotes
                                      .Where(x => x.DateParsed.InOpenRange(tradingStartingDate, tradingEndDate))
                                      .Select(x => x.DateParsed)
                                      .Distinct()
                                      .OrderBy(x => x)
                                      .ToList();


            progress?.Restart(datesToTrade.Count);
            foreach (var date in datesToTrade)
            {
                var topN = GetTopN(tradingSimulationConfig, allQuotesPrefilterd, date);

                var topNTickers = Enumerable.ToHashSet(topN.Select(quote => quote.Ticker));
                var tradingDayQuotesForMostRising = allQuotesPrefilterd
                                                    .Where(x => x.DateParsed.Date.Equals(date.Date) && topNTickers.Contains(x.Ticker) && x.IsValid());

                foreach (var stockQuoteForToday in tradingDayQuotesForMostRising)
                {
                    var volume         = (ledger.Balance / tradingSimulationConfig.TopN) / (stockQuoteForToday.Open);
                    var buyOrderStatus = ledger.PlaceBuyOrder(stockQuoteForToday, stockQuoteForToday.Open, volume);

                    switch (buyOrderStatus)
                    {
                    case OrderStatusType.Accepted:
                        var sellOrderStatus = ledger.ClosePosition(stockQuoteForToday, stockQuoteForToday.Close);
                        if (sellOrderStatus != OrderStatusType.Accepted)
                        {
                            Logger.LogWarning($"Could not perform closing sell on {stockQuoteForToday.Ticker} {stockQuoteForToday.DateParsed}. Selling the trade for buy price...");
                            ledger.ClosePosition(stockQuoteForToday, stockQuoteForToday.Open);
                        }

                        switch (sellOrderStatus)
                        {
                        case OrderStatusType.DeniedNoOpenPosition:
                            Logger.LogError($"Sell order of {volume} stocks denied due to lack of open position on {stockQuoteForToday.Ticker}.");
                            break;

                        case OrderStatusType.DeniedOutOfRange:
                            Logger.LogWarning($"Sell order for of {volume} stocks denied due to price being out of range of today's {stockQuoteForToday.Ticker} prices ({stockQuoteForToday.Low} - {stockQuoteForToday.High})." +
                                              "Selling the trade for buy price...");
                            ledger.ClosePosition(stockQuoteForToday, stockQuoteForToday.Open);
                            break;
                        }
                        break;

                    case OrderStatusType.DeniedInsufficientFunds:
                        Logger.LogWarning($"Buy order for {stockQuoteForToday.Ticker} price = {stockQuoteForToday.Open} vol = {volume} denied due to insufficient funds ({ledger.Balance}).");
                        break;

                    case OrderStatusType.DeniedOutOfRange:
                        Logger.LogWarning($"Buy order for {stockQuoteForToday.Open * volume} denied due to price being out of range of today's {stockQuoteForToday.Ticker} prices ({stockQuoteForToday.Low} - {stockQuoteForToday.High}.");
                        break;
                    }
                    result.ROC.Activate(true, stockQuoteForToday.Close > stockQuoteForToday.Open);
                }
                progress?.ReportProgress();
            }

            result.TransactionsLedger        = ledger.TheLedger;
            result.FinalBalance              = ledger.Balance;
            result.CompaniesUsedInSimulation = filteredQuotes.DistinctBy(x => x.Ticker).Count();
            result.SimulatorName             = this.GetType().Name;

            return(result);
        }