public static void GetAverageDaysPerTrade(this BacktestResult results, GetFitnessArgs args) { double sum = 0; int trades = 0; double winSum = 0; int winningTrades = 0; double lossSum = 0; int losingTrades = 0; foreach (var trade in args.History) { if (double.IsNaN(trade.ClosingPrice)) { continue; } sum += (trade.ClosingTime - trade.EntryTime).TotalDays; trades++; if (trade.NetProfit >= 0) { winSum += (trade.ClosingTime - trade.EntryTime).TotalDays; winningTrades++; } else { lossSum += (trade.ClosingTime - trade.EntryTime).TotalDays; losingTrades++; } } results.AverageDaysPerTrade = sum / trades; results.AverageDaysPerWinningTrade = winSum / winningTrades; results.AverageDaysPerLosingTrade = lossSum / losingTrades; }
protected virtual void DoLogBacktest(GetFitnessArgs args, BacktestResult backtestResult) { double fitness = backtestResult.Fitness; double initialBalance = backtestResult.InitialBalance; TimeSpan timeSpan = backtestResult.Duration; double aroi = backtestResult.Aroi; if (!double.IsNaN(Template.LogBacktestThreshold) && fitness > Template.LogBacktestThreshold) { BacktestLogger = this.GetLogger(this.ToString().Replace(' ', '.') + ".Backtest"); try { string resultJson = ""; resultJson = Newtonsoft.Json.JsonConvert.SerializeObject(backtestResult); var profit = args.Equity / initialBalance; this.BacktestLogger.LogInformation($"${args.Equity} ({profit.ToString("N1")}x) #{args.History.Count} {args.MaxEquityDrawdownPercentages.ToString("N2")}%dd [from ${initialBalance.ToString("N2")} to ${args.Equity.ToString("N2")}] [fit {fitness.ToString("N1")}] {Environment.NewLine} result = {resultJson} "); var id = Template.Id; backtestResult.GetAverageDaysPerTrade(args); SaveResult(args, backtestResult, fitness, resultJson, id, timeSpan); } catch (Exception ex) { this.BacktestLogger.LogError(ex.ToString()); throw; } } }
protected override double GetFitness(GetFitnessArgs args) { List <bool> constrains = new List <bool> { args.AverageTrade > 0, args.LosingTrades > 0 }; if (!constrains.Contains(false)) { List <double> netReturns = new List <double>(); foreach (HistoricalTrade trade in History) { if (trade.Label.StartsWith(RobotId)) { netReturns.Add(trade.NetProfit / trade.VolumeInUnits); } } double geoMeanReturn = Math.Pow(Account.Balance / (Account.Balance - args.NetProfit), 1 / args.TotalTrades) - 1; double geostdev = Math.Sqrt(netReturns.Sum(val => (val - geoMeanReturn) * (val - geoMeanReturn)) / netReturns.Count); double geomodSharpe = geoMeanReturn / geostdev; double winRatio = args.WinningTrades / args.TotalTrades; return(Math.Log(args.TotalTrades) * winRatio * geomodSharpe); } else { return(double.NegativeInfinity); } }
private async void SaveResult(GetFitnessArgs args, BacktestResult backtestResult, double fitness, string json, string id, TimeSpan timeSpan) { //var filename = DateTime.Now.ToString("yyyy.MM.dd HH-mm-ss.fff ") + this.GetType().Name + " " + Symbol.Code + " " + id; var sym = #if cAlgo MarketSeries.SymbolCode; #else Template.Symbol; #endif var tf = #if cAlgo TimeFrame.ToShortString(); #else (this as IHasSingleSeries)?.MarketSeries?.TimeFrame?.Name; #endif var tradesPerMonth = (args.TotalTrades / (timeSpan.TotalDays / 31)).ToString("F1"); var filename = fitness.ToString("00.0") + $"ad {tradesPerMonth}tpm {timeSpan.TotalDays.ToString("F0")}d {backtestResult.AverageDaysPerWinningTrade.ToString("F2")}adwt bot={this.GetType().Name} sym={sym} tf={tf} id={id}"; var ext = ".json"; int i = 0; var dir = BacktestResultSaveDir; var path = Path.Combine(dir, filename + ext); if (CreateResultsDirIfMissing && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } for (; File.Exists(path); i++, path = Path.Combine(dir, filename + $" ({i})" + ext)) { ; } using (var sw = new StreamWriter(new FileStream(path, FileMode.Create))) { await sw.WriteAsync(json).ConfigureAwait(false); } }
double GetFitness(GetFitnessArgs args) { try { var dd = args.MaxEquityDrawdownPercentages; dd = Math.Max(FitnessMinDrawdown, dd); var initialBalance = args.History.Count == 0 ? args.Equity : args.History[0].Balance - args.History[0].NetProfit; if (dd > FitnessMaxDrawdown || args.Equity < initialBalance) { return(-dd); } var botType = this.GetType().FullName; #if cAlgo if (this.GetType().FullName.StartsWith("cAlgo.")) { botType = this.GetType().GetTypeInfo().BaseType.FullName; } if (Template.TimeFrame == null) { Template.TimeFrame = this.TimeFrame.ToShortString(); } if (Template.Symbol == null) { Template.Symbol = this.Symbol.Code; } if (!StartDate.HasValue || !EndDate.HasValue) { throw new ArgumentException("StartDate or EndDate not set. Are you calling base.OnBar in OnBar?"); } #endif var backtestResult = new BacktestResult() { BacktestDate = DateTime.UtcNow, BotType = botType, BotConfigType = this.Template.GetType().AssemblyQualifiedName, Config = this.Template, InitialBalance = initialBalance, //Start = this.MarketSeries?.OpenTime?[0], //End = this.MarketSeries?.OpenTime?.LastValue, Start = StartDate.Value, End = EndDate.Value, AverageTrade = args.AverageTrade, Equity = args.Equity, //History LosingTrades = args.LosingTrades, MaxBalanceDrawdown = args.MaxBalanceDrawdown, MaxBalanceDrawdownPercentages = args.MaxBalanceDrawdownPercentages, MaxEquityDrawdown = args.MaxEquityDrawdown, MaxEquityDrawdownPercentages = args.MaxEquityDrawdownPercentages, NetProfit = args.NetProfit, //PendingOrders //Positions ProfitFactor = args.ProfitFactor, SharpeRatio = args.SharpeRatio, SortinoRatio = args.SortinoRatio, TotalTrades = args.TotalTrades, WinningTrades = args.WinningTrades, }; double fitness; if (!EndDate.HasValue || !StartDate.HasValue) { fitness = 0.0; } else { var timeSpan = EndDate.Value - StartDate.Value; var totalMonths = timeSpan.TotalDays / 31; var tradesPerMonth = backtestResult.TradesPerMonth; //var aroi = (args.NetProfit / initialBalance) / (timeSpan.TotalDays / 365); var aroi = backtestResult.Aroi; if (args.LosingTrades == 0) { fitness = aroi; } else { fitness = aroi / dd; } var minTrades = Template.BacktestMinTradesPerMonth; //#if cAlgo // logger.LogInformation($"dd: {args.MaxEquityDrawdownPercentages } Template.BacktestMinTradesPerMonth {Template.BacktestMinTradesPerMonth} tradesPerMonth {tradesPerMonth}"); //#endif if (minTrades > 0 && tradesPerMonth < minTrades && fitness > 0) { fitness *= tradesPerMonth / minTrades; } //#if cAlgo // Print($"Fitness: {StartDate.Value} - {EndDate.Value} profit: {args.NetProfit} years: {((EndDate.Value - StartDate.Value).TotalDays / 365)} constrained eqDD:{dd} aroi:{aroi} aroi/DD:{aroiVsDD}"); //#endif fitness *= 100.0; backtestResult.Fitness = fitness; DoLogBacktest(args, backtestResult); } #if BackTestResult_Debug this.BacktestResult = backtestResult; #endif return(fitness); } catch (Exception ex) { Logger.LogError("GetFitness threw exception: " + ex); throw; //return 0; } }