public void SimulateTradingReturnsExpectedSimulationResult(List <Company> testCompanies) { // Arrange var tested = Tested; var flattenedQuotes = testCompanies.SelectMany(x => x.Quotes).OrderBy(x => x.DateParsed).ToList(); var simulationConfig = new TradingSimulationConfig { FromDate = new DateTime(2020, 01, 01), ToDate = new DateTime(2021, 01, 01), StartingCash = 1000, TopN = 10 }; var allQuotesPrefiltered = flattenedQuotes.Where(z => !new Regex(@".*\d{3,}|WIG.*|RC.*|INTL.*|INTS.*|WIG.*|.*PP\d.*|.*BAHOLDING.*|CFI.*").IsMatch(z.Ticker) && z.DateParsed.InOpenRange(simulationConfig.FromDate.AddDays(-30), simulationConfig.ToDate)) .ToList(); // Act var result = tested.Simulate(allQuotesPrefiltered, simulationConfig); // Assert Assert.Equal(102.65, result.FinalBalance, 2); Assert.Equal(-89.73, result.ReturnOnInvestment, 2); Assert.Equal(0.33, result.ROC.Accuracy, 2); Assert.Equal(2394, result.ROC.All); Assert.True(result.TransactionsLedger.All(x => x.Date.InOpenRange(simulationConfig.FromDate, simulationConfig.ToDate))); Assert.Equal(result.ROC.All * 2, result.TransactionsLedger.Count); Assert.Equal(result.ROC.All, result.TransactionsLedger.Count(x => x.TransactionType == StockTransactionType.Sell)); Assert.Equal(result.ROC.All, result.TransactionsLedger.Count(x => x.TransactionType == StockTransactionType.Buy)); }
protected override List <StockQuote> GetTopN(TradingSimulationConfig tradingSimulationConfig, List <StockQuote> allQuotesPrefilterd, DateTime date) { var allQuotesBeforeTradeDay = allQuotesPrefilterd.Where(x => x.DateParsed.Date < date.Date).ToList(); var nMinusOneDay = allQuotesBeforeTradeDay.Select(x => x.DateParsed).Max(); var allQuotesFromMinusOneDay = allQuotesPrefilterd.Where(x => x.DateParsed.Date.Equals(nMinusOneDay.Date)).ToList(); var topN = allQuotesFromMinusOneDay .Where(x => (!tradingSimulationConfig.ExcludePennyStocks || x.AveragePrice > tradingSimulationConfig.ExcludePennyStocksThreshold) && (!ProjectSettings.ExcludeBlacklisted || !ProjectSettings.BlackListPattern.IsMatch(x.Ticker))) .OrderByDescending(x => x.AveragePriceChange) .Take(tradingSimulationConfig.TopN) .ToList(); return(topN); }
public async Task Execute(params string[] args) { _projectSettings.EnsureAllDirectoriesExist(); var command = args[0]; var fromDate = default(DateTime?); var toDate = default(DateTime?); var tradingSimulationConfig = TradingSimulationConfig.CreateFrom(_configuration); switch (command) { case "h": case "help": _logger.LogInfo(HelpMessage); break; case "getDir": _logger.LogInfo($"Working directory: {_projectSettings.WorkingDirectory.FullName}"); break; case "openDir": ProcessStartInfo startInfo = new ProcessStartInfo("explorer.exe", _projectSettings.WorkingDirectory.FullName); startInfo.Verb = "runas"; Process.Start(startInfo); break; case "printUnzipped": _logger.LogInfo(string.Join(Environment.NewLine, _projectSettings.GetFilesListInDirectory(_projectSettings.UnzippedFilesDirectory))); break; case "cleanDir": _projectSettings.CleanOutputDirectory(); _logger.LogInfo($"Cleaned directory {_projectSettings.UnzippedFilesDirectory.FullName}"); break; case "cleanLogs": _projectSettings.CleanLogs(); _logger.LogInfo($"Cleaned logs in {_projectSettings.LogDirectory.FullName}"); break; case "dropDb": await _databaseManagementService.EnsureDbDoesNotExist(_projectSettings); break; case "download": await _stockQuotesDownloadService.Download(_projectSettings); break; case "unzip": await _stockQuotesMigrationFromCsv.Unzip(_projectSettings); break; case "migrate": await _stockQuotesMigrationFromCsv.Migrate(_projectSettings, TargetLocation.Directory); _logger.LogInfo("Successfully migrated data to database."); break; case "dbVersion": var latestDateInDb = await _stockQuoteRepository.GetLatestSessionInDbDateAsync(); _logger.LogInfo(latestDateInDb.ToShortDateString()); break; case "update": await _stockUpdateService.PerformUpdateTillToday(); break; case "print": if (args.Length >= 2) { var found = _companyRepository.GetById(args[1]); if (found == null) { _logger.LogWarning($"{args[1]} not found."); } else { _logger.LogInfo(string.Join(Environment.NewLine, found.Quotes.Select(x => x.Summary()))); } } else { var summary = _companyRepository.Summary(); _logger.LogInfo($"{Environment.NewLine}{summary.Count} companies.{string.Join(Environment.NewLine, summary)}"); } break; case "simulate": if (args.Length >= 3) { fromDate = TryParseDateTime(args[1]); toDate = TryParseDateTime(args[2]); if (fromDate.HasValue && toDate.HasValue) { tradingSimulationConfig.FromDate = fromDate.Value; tradingSimulationConfig.ToDate = toDate.Value; } } var allQuotesPrefilterd = _stockQuoteRepository .GetAll(x => !_projectSettings.BlackListPattern.IsMatch(x.Ticker) && x.DateParsed.InOpenRange(tradingSimulationConfig.FromDate.AddDays(-30), tradingSimulationConfig.ToDate)) .ToList(); var simulationResult = _tradingSimulator.Simulate(allQuotesPrefilterd, tradingSimulationConfig, _progressReporter); _logger.LogInfo(simulationResult.ToString()); break; case "predict": if (args.Length >= 2) { var date = TryParseDateTime(args[1]); if (date.HasValue) { var prediction = _tradingSimulator.GetSignals(tradingSimulationConfig, date.Value); _logger.LogInfo($"Stonks to buy: {string.Join(", ", prediction.Select(x => x.Ticker).OrderBy(x => x))}. Used prediction made with data from session {prediction.Select(x => x.DateParsed).First()} and config = {tradingSimulationConfig}"); } else { _logger.LogError($"{args[1]} is not a valid date. Enter date in format YYYY-MM-DD"); } } else { _logger.LogError($"Enter date in format YYYY-MM-DD as second argument after empty space. Example: predict 2020-01-01"); } break; default: _logger.LogWarning(@$ "{command} not recognized as valid command. {HelpMessage}"); break; } }
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); }
protected abstract List <StockQuote> GetTopN(TradingSimulationConfig tradingSimulationConfig, List <StockQuote> allQuotesPrefilterd, DateTime date);
public virtual List <StockQuote> GetSignals(TradingSimulationConfig tradingSimulationConfig, DateTime date) { var allQuotesFromLastSession = StockQuoteRepository.GetAllQuotesFromPreviousSession(date); return(GetTopN(tradingSimulationConfig, allQuotesFromLastSession, date)); }
public override SimulationResult Simulate(List <StockQuote> allQuotesPrefilterd, TradingSimulationConfig tradingSimulationConfig, IProgressReportable progress = null) { var result = base.Simulate(allQuotesPrefilterd, tradingSimulationConfig, progress); return(result); }
protected override List <StockQuote> GetTopN(TradingSimulationConfig tradingSimulationConfig, List <StockQuote> allQuotesPrefilterd, DateTime date) { throw new NotImplementedException(); }