private static RangeBarModel[] LoadBars(BacktestConfig config, string file) { CsvReader csv = null; if (file.Contains(".gz", StringComparison.OrdinalIgnoreCase)) { var fStream = new FileStream(file, FileMode.Open); var gzStream = new GZipStream(fStream, CompressionMode.Decompress); var reader = new StreamReader(gzStream); csv = new CsvReader(reader, CultureInfo.InvariantCulture); } else { var reader = new StreamReader(file); csv = new CsvReader(reader, CultureInfo.InvariantCulture); } csv.Configuration.PrepareHeaderForMatch = (header, index) => header.ToLower(); csv.Configuration.HeaderValidated = null; csv.Configuration.MissingFieldFound = null; var bars = csv.GetRecords <RangeBarModel>().ToArray(); FixTimestamp(config, bars); var ordered = bars.OrderBy(x => x.TimestampDate).ToArray(); csv.Dispose(); return(ordered); }
private static void FixTimestamp(BacktestConfig config, RangeBarModel[] bars) { if (string.IsNullOrWhiteSpace(config.TimestampType) || config.TimestampType == "unix-sec" || config.TimestampType == "date") { // default valid timestamp format, do nothing return; } foreach (var bar in bars) { var t = bar.TimestampUnix; var d = config.TimestampDecimals ?? 0; var converted = t; switch (config.TimestampType) { case "unix-ms": case "ms": converted = t / (Math.Pow(10, d)); break; } bar.TimestampUnix = converted; } }
public ProfitComputer(IStrategy strategy, BacktestConfig config, int?maxLimitInventory) { _orderSize = config.Amount ?? 1; _strategy = strategy; _feePercentage = config.FeePercentage ?? 0; _quoteSymbol = config.QuoteSymbol; _baseSymbol = config.BaseSymbol; _config = config; _maxLimitInventory = maxLimitInventory; }
private static void SaveTextReport(string report, BacktestConfig backtest, IStrategy strategy) { var filename = Path.GetFileName(backtest.DirectoryPath); var strategyName = strategy.GetType().Name; var pattern = ExtractFromPattern(backtest); pattern = string.IsNullOrWhiteSpace(pattern) ? filename : pattern; var targetFile = Path.Combine(GetPathToReportDir(backtest), $"{pattern}__{strategyName}.txt"); File.WriteAllText(targetFile, report); }
private static RangeBarModel[] LoadBars(BacktestConfig config, string file) { using var reader = new StreamReader(file); using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); csv.Configuration.PrepareHeaderForMatch = (header, index) => header.ToLower(); csv.Configuration.HeaderValidated = null; csv.Configuration.MissingFieldFound = null; var bars = csv.GetRecords <RangeBarModel>().ToArray(); FixTimestamp(config, bars); var ordered = bars.OrderBy(x => x.TimestampDate).ToArray(); return(ordered); }
private static string ExtractFromPattern(BacktestConfig backtest) { var pattern = backtest.FilePattern ?? string.Empty; var split = pattern.Split("*"); if (split.Length > 1) { return(split[0] .Trim('_') .Trim('_') .Trim('/') .Trim('\\') .Trim()); } return(pattern); }
private static void Visualize(BacktestConfig backtest, ProfitComputer computer, IStrategy strategy, int maxInv, ProfitInfo report, ProfitInfo[] days, ProfitInfo[] months) { if (computer == null || backtest.Visualize == null || !backtest.Visualize.Value) { return; } PrepareWebVisualization(backtest, computer, strategy, maxInv, report, report, days, months); var chart = new ChartVisualizer(); var filename = Path.GetFileName(backtest.DirectoryPath); var strategyName = strategy.GetType().Name.ToLower(); var pnl = report.Pnl; var name = $"{pnl:#.00} {backtest.QuoteSymbol} (max inv: {maxInv}) "; var nameWithFee = $"{report.PnlWithFee:#.00} {backtest.QuoteSymbol} (max inv: {maxInv}) "; var dir = GetPathToReportDir(backtest); var pattern = ExtractFromPattern(backtest); var targetFile = Path.Combine(dir, $"{pattern}__{strategyName}__{maxInv}__{pnl:0}"); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var bars = computer.Bars; var totalBars = bars.Length; if (backtest.VisualizeSkipBars.HasValue) { bars = bars.Skip(backtest.VisualizeSkipBars.Value).ToArray(); } if (backtest.VisualizeLimitBars.HasValue) { bars = bars.Take(backtest.VisualizeLimitBars.Value).ToArray(); } var minIndex = bars.Min(x => x.Index); var maxIndex = bars.Max(x => x.Index); var trades = computer.Trades .Where(x => x.BarIndex >= minIndex && x.BarIndex <= maxIndex) .ToArray(); chart.Plot(name, nameWithFee, targetFile, totalBars, bars, trades, days, months); }
private static void PrepareWebVisualization(BacktestConfig backtest, ProfitComputer computer, IStrategy strategy, in int maxInv,
private static void RunBacktest(BacktestConfig backtest, Func <IStrategy> strategyFactory) { var files = LoadAllFiles(backtest.DirectoryPath, backtest.FilePattern); if (backtest.SkipFiles.HasValue) { files = files.Skip(backtest.SkipFiles.Value).ToArray(); } if (backtest.LimitFiles.HasValue) { files = files.Take(backtest.LimitFiles.Value).ToArray(); } if (!files.Any()) { return; } Console.WriteLine(); Console.WriteLine("====================================================================="); Console.WriteLine($" Running for {files.Length} files from dir '{backtest.DirectoryPath}' and pattern: '{backtest.FilePattern}'"); var builderTop = new StringBuilder(); var builder = new StringBuilder(); foreach (var maxInventory in backtest.MaxInventory) { RangeBarModel lastBar = null; var strategy = strategyFactory(); var computer = new ProfitComputer(strategy, backtest, maxInventory); foreach (var file in files) { var bars = LoadBars(backtest, file); computer.ProcessBars(bars); lastBar = bars.LastOrDefault(); } if (lastBar != null) { computer.ProcessLastBar(lastBar); } var report = computer.GetReport(); builderTop.AppendLine(report.ToString()); builder.AppendLine( $"==== MAX INV: {maxInventory} {new string('=', 133)}"); builder.AppendLine(); builder.AppendLine(report.ToString()); Console.WriteLine($" {report}"); var reportDays = new List <ProfitInfo>(); var perMonth = computer.GetReportByMonth(); foreach (var month in perMonth) { builder.AppendLine($" {month.Report}"); if (month.Month == null) { builderTop.AppendLine($"{month.Report}"); continue; } var perDay = computer.GetReportPerDays(month.Year.Value, month.Month.Value); reportDays.AddRange(perDay.Where(x => x.Day != null)); foreach (var day in perDay) { builder.AppendLine($" {day.Report}"); } } //var totalReportAllDays = computer.GetTotalReport(reportDays.ToArray()); //builderTop.AppendLine($"{totalReportAllDays.Report}"); //builderTop.AppendLine(); builder.AppendLine(); builder.AppendLine(); Visualize(backtest, computer, strategy, maxInventory, report, reportDays.ToArray(), perMonth.Where(x => x.Month.HasValue).ToArray()); } var mergedReport = $"{builderTop}{Environment.NewLine}{builder}"; SaveTextReport(mergedReport, backtest, strategyFactory()); Console.WriteLine(); }
private static string GetPathToReportDir(BacktestConfig backtest) { return(Path.Combine(Path.GetDirectoryName(backtest.DirectoryPath), "reports")); }
private static void RunBacktest(BacktestConfig backtest, Func <IStrategy> strategyFactory) { var files = LoadAllFiles(backtest.DirectoryPath, backtest.FilePattern); if (backtest.SkipFiles.HasValue) { files = files.Skip(backtest.SkipFiles.Value).ToArray(); } if (backtest.LimitFiles.HasValue) { files = files.Take(backtest.LimitFiles.Value).ToArray(); } if (!files.Any()) { return; } Console.WriteLine(); Console.WriteLine("====================================================================="); Console.WriteLine($" Running for {files.Length} files from dir '{backtest.DirectoryPath}' and pattern: '{backtest.FilePattern}'"); var builderTop = new StringBuilder(); var builder = new StringBuilder(); foreach (var maxInventory in backtest.MaxInventory) { RangeBarModel lastBar = null; var strategy = strategyFactory(); var computer = new ProfitComputer(strategy, backtest, maxInventory); foreach (var file in files) { var bars = LoadBars(backtest, file); computer.ProcessBars(bars); lastBar = bars.LastOrDefault(); } if (lastBar != null) { computer.ProcessLastBar(lastBar); } var primaryReport = computer.GetReport(); var perMonth = computer.GetReportByMonth(); var reportDays = new List <ProfitInfo>(); var maxTotalPnl = maxInventory * computer.InitialPrice; var minTotalPnl = maxTotalPnl; var totalPnl = maxTotalPnl; foreach (var month in perMonth) { if (month.Month == null) { continue; } var perDay = computer.GetReportPerDays(month.Year.Value, month.Month.Value, ref maxTotalPnl, ref minTotalPnl, ref totalPnl); reportDays.AddRange(perDay.Where(x => x.Day != null)); month.MaxDrawdownPercentage = perDay.Min(x => x.MaxDrawdownPercentage); month.SubProfits = perDay; } primaryReport.MaxDrawdownPercentage = perMonth.Min(x => x.MaxDrawdownPercentage); builderTop.AppendLine(primaryReport.ToString()); builder.AppendLine( $"==== MAX INV: {maxInventory} {new string('=', 133)}"); builder.AppendLine(); builder.AppendLine(primaryReport.ToString()); var consoleDefaultColor = Console.ForegroundColor; var reportColor = Console.ForegroundColor; var pnl = backtest.DisplayFee != null && backtest.DisplayFee.Value ? primaryReport.PnlWithFee : primaryReport.Pnl; if (pnl < 0) { reportColor = ConsoleColor.DarkRed; } else if (pnl > 0 && primaryReport.MaxDrawdownPercentage >= -0.03) { reportColor = ConsoleColor.Green; } else if (pnl > 0) { reportColor = ConsoleColor.DarkGreen; } Console.ForegroundColor = reportColor; Console.WriteLine($" {primaryReport}"); Console.ForegroundColor = consoleDefaultColor; foreach (var month in perMonth) { if (month.Month == null) { builder.AppendLine($" {month.Report}"); builderTop.AppendLine($"{month.Report}"); continue; } builder.AppendLine($" {month.Report}"); foreach (var day in month.SubProfits) { builder.AppendLine($" {day.Report}"); } } //var totalReportAllDays = computer.GetTotalReport(reportDays.ToArray()); //builderTop.AppendLine($"{totalReportAllDays.Report}"); //builderTop.AppendLine(); builder.AppendLine(); builder.AppendLine(); Visualize(backtest, computer, strategy, maxInventory, primaryReport, reportDays.ToArray(), perMonth.Where(x => x.Month.HasValue).ToArray()); } var mergedReport = $"{builderTop}{Environment.NewLine}{builder}"; SaveTextReport(mergedReport, backtest, strategyFactory()); Console.WriteLine(); }