private EquityCurve EcFromEquitySummaries(List <EquitySummary> summaries, Dictionary <DateTime, decimal> depositsWithdrawals) { if (summaries.Count == 0) { return(new EquityCurve()); } var ec = new EquityCurve(); decimal lastTotal = summaries[0].Total; for (int i = 1; i < summaries.Count; i++) { var es = summaries[i]; decimal externalCashFlow = depositsWithdrawals.ContainsKey(es.Date) ? depositsWithdrawals[es.Date] : 0; if (lastTotal != 0) //make sure we avoid division by zero...equity can't change if we have 0 to start with anyway { double ret = (double)(((es.Total - externalCashFlow) - lastTotal) / lastTotal); ec.AddReturn(ret, es.Date); } lastTotal = es.Total; } ec.CalcFinalValues(summaries.Last().Date); return(ec); }
private EquityCurve EcFromEquitySummaries(Dictionary <DateTime, decimal> summaries, Dictionary <DateTime, decimal> depositsWithdrawals) { if (summaries.Count == 0) { return(new EquityCurve(100, summaries.First().Key)); } var ec = new EquityCurve(100, summaries.First().Key); decimal lastTotal = summaries.First().Value; bool first = true; foreach (var kvp in summaries) { if (first) { first = false; continue; } DateTime date = kvp.Key; decimal total = kvp.Value; decimal externalCashFlow = depositsWithdrawals.ContainsKey(date) ? depositsWithdrawals[date] : 0; if (lastTotal != 0) //make sure we avoid division by zero...equity can't change if we have 0 to start with anyway { double ret = (double)(((total - externalCashFlow) - lastTotal) / lastTotal); ec.AddReturn(ret, date); } lastTotal = total; } ec.CalcFinalValues(summaries.Last().Key); return(ec); }
/// <summary> /// Returns false is parsing failed. /// </summary> /// <returns></returns> public bool Import() { var dates = new List <DateTime>(); var equityValues = new List <double>(); int count = 0; foreach (var kvp in RawSplitData) { DateTime date; if (!DateTime.TryParseExact(kvp.Key, DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out date)) { _dialogService.ShowMessageAsync("Parsing error.", string.Format("Failed to date value at line {0}: {1}", count, kvp.Key)); return(false); } dates.Add(date); double equity; if (!double.TryParse(kvp.Value, out equity)) { _dialogService.ShowMessageAsync("Parsing error.", string.Format("Failed to parse equity value at line {0}: {1}", count, kvp.Value)); return(false); } equityValues.Add(equity); count++; } //Make sure there's sufficient data if (dates.Count <= 1) { _dialogService.ShowMessageAsync("Parsing error.", "Parsed backtest data too short."); EquityCurve = null; return(false); } //Then generate an EquityCurve using the data EquityCurve = new EquityCurve(100, dates[0]); for (int i = 1; i < dates.Count; i++) { EquityCurve.AddReturn(equityValues[i] / equityValues[i - 1] - 1, dates[i]); } return(true); }
public PortfolioTracker(Dictionary <int, TimeSeries> data, Dictionary <int, TimeSeries> fxData, List <Trade> trades, string name) { _data = data; _fxData = fxData; Name = name; Trades = trades; TradeTrackers = trades.ToDictionary(t => t.ID, t => new TradeTracker(t)); ProfitLossEquityCurve = new EquityCurve(0); ProfitLossLongEquityCurve = new EquityCurve(0); ProfitLossShortEquityCurve = new EquityCurve(0); RoacEquityCurve = new EquityCurve(1); RotcEquityCurve = new EquityCurve(1); Capital = new AllocatedCapital(); Positions = new Dictionary <int, Position> { //dummy position used for cash transactions without a related instrument { NullInstrumentId, new Position(new Instrument()) } }; //group cash transactions by date so they're easily accessible _cashTransactionsByDate = trades .Where(x => x.CashTransactions != null) .SelectMany(x => x.CashTransactions) .Where(x => x.Type != "Deposits & Withdrawals") .GroupBy(x => x.TransactionDate.Date) .ToDictionary(x => x.Key, x => x.ToList()); //group fx transactions _fxTransactionsByDate = trades .Where(x => x.FXTransactions != null) .SelectMany(x => x.FXTransactions) .GroupBy(x => x.DateTime.Date) .ToDictionary(x => x.Key, x => x.ToList()); _allOrders = Trades.Where(x => x.Orders != null).SelectMany(x => x.Orders).ToList(); }
public PortfolioTracker(Dictionary<int, TimeSeries> data, Dictionary<int, TimeSeries> fxData, List<Trade> trades, string name, DateTime firstDate, decimal optionsCapitalUsageMultiplier) { _data = data; _fxData = fxData; Name = name; _optionsCapitalUsageMultiplier = optionsCapitalUsageMultiplier; Trades = trades; TradeTrackers = trades.ToDictionary(t => t.ID, t => new TradeTracker(t, optionsCapitalUsageMultiplier)); ProfitLossEquityCurve = new EquityCurve(0, firstDate); ProfitLossLongEquityCurve = new EquityCurve(0, firstDate); ProfitLossShortEquityCurve = new EquityCurve(0, firstDate); RoacEquityCurve = new EquityCurve(1, firstDate); RotcEquityCurve = new EquityCurve(1, firstDate); Capital = new AllocatedCapital(); Positions = new Dictionary<int, Position> { //dummy position used for cash transactions without a related instrument { NullInstrumentId, new Position(new Instrument(), _optionsCapitalUsageMultiplier)} }; //group cash transactions by date so they're easily accessible _cashTransactionsByDate = trades .Where(x => x.CashTransactions != null) .SelectMany(x => x.CashTransactions) .Where(x => x.Type != "Deposits & Withdrawals") .GroupBy(x => x.TransactionDate.Date) .ToDictionary(x => x.Key, x => x.ToList()); //group fx transactions _fxTransactionsByDate = trades .Where(x => x.FXTransactions != null) .SelectMany(x => x.FXTransactions) .GroupBy(x => x.DateTime.Date) .ToDictionary(x => x.Key, x => x.ToList()); _allOrders = Trades.Where(x => x.Orders != null).SelectMany(x => x.Orders).ToList(); }
/// <summary> /// Returns false is parsing failed. /// </summary> /// <returns></returns> public bool Import() { var dates = new List<DateTime>(); var equityValues = new List<double>(); int count = 0; foreach(var kvp in RawSplitData) { DateTime date; if(!DateTime.TryParseExact(kvp.Key, DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out date)) { _dialogService.ShowMessageAsync(this, "Parsing error.", string.Format("Failed to date value at line {0}: {1}", count, kvp.Key)); return false; } dates.Add(date); double equity; if(!double.TryParse(kvp.Value, out equity)) { _dialogService.ShowMessageAsync(this, "Parsing error.", string.Format("Failed to parse equity value at line {0}: {1}", count, kvp.Value)); return false; } equityValues.Add(equity); count++; } //Make sure there's sufficient data if (dates.Count <= 1) { _dialogService.ShowMessageAsync(this, "Parsing error.", "Parsed backtest data too short."); EquityCurve = null; return false; } //Then generate an EquityCurve using the data EquityCurve = new EquityCurve(100, dates[0]); for (int i = 1; i < dates.Count; i++) { EquityCurve.AddReturn(equityValues[i] / equityValues[i - 1] - 1, dates[i]); } return true; }
/// <summary> /// generate EC and other curves for a benchmark /// </summary> public static EquityCurve GetBenchmarkReturns( int benchmarkID, DBContext context, List<DateTime> datesInPeriod, IDataSourcer dataSourcer, out Dictionary<DateTime, double> benchmarkSeries, out List<double> benchmarkReturns) { Logger logger = LogManager.GetCurrentClassLogger(); List<BenchmarkComponent> components = context.BenchmarkComponents.Where(x => x.BenchmarkID == benchmarkID).ToList(); DateTime earliestDate = datesInPeriod[0].Date; DateTime latestDate = datesInPeriod.Last(); Dictionary<int, TimeSeries> data = components .ToDictionary( component => component.QDMSInstrumentID, component => new TimeSeries( dataSourcer.GetExternalData(component.QDMSInstrumentID, earliestDate, latestDate))); Dictionary<int, decimal> weights = components.ToDictionary(x => x.QDMSInstrumentID, x => (decimal)x.Weight); benchmarkSeries = new Dictionary<DateTime, double>(); benchmarkReturns = new List<double>(); var benchmarkEC = new EquityCurve(1, null); decimal equity = 1; bool first = true; foreach (DateTime today in datesInPeriod) { decimal ret = 0; foreach (var kvp in data) { var ts = kvp.Value; ts.ProgressTo(today); if (ts.CurrentBar > 0) { decimal todayClose = ts[0].AdjClose.HasValue ? ts[0].AdjClose.Value : ts[0].Close; decimal lastClose = ts[1].AdjClose.HasValue ? ts[1].AdjClose.Value : ts[1].Close; ret += weights[kvp.Key] * (todayClose / lastClose - 1); #if DEBUG logger.Log(LogLevel.Trace, "Benchmark component: Date: {0} Close: {1} PrevClose: {2} Ret: {3}", today, todayClose, lastClose, ret); #endif } } benchmarkEC.AddReturn((double)ret, today); if (first) { first = false; benchmarkReturns.Add((double)(1 + ret)); benchmarkSeries.Add(today, (double)equity); continue; } equity *= 1 + ret; benchmarkReturns.Add((double)(1 + ret)); benchmarkSeries.Add(today, (double)equity); } return benchmarkEC; }
/// <summary> /// generate EC and other curves for a benchmark /// </summary> public static EquityCurve GetBenchmarkReturns( int benchmarkID, DBContext context, List <DateTime> datesInPeriod, IDataSourcer dataSourcer, out Dictionary <DateTime, double> benchmarkSeries, out List <double> benchmarkReturns) { Logger logger = LogManager.GetCurrentClassLogger(); List <BenchmarkComponent> components = context.BenchmarkComponents.Where(x => x.BenchmarkID == benchmarkID).ToList(); DateTime earliestDate = datesInPeriod[0].Date; DateTime latestDate = datesInPeriod.Last(); Dictionary <int, TimeSeries> data = components .ToDictionary( component => component.QDMSInstrumentID, component => new TimeSeries( dataSourcer.GetExternalData(component.QDMSInstrumentID, earliestDate, latestDate))); Dictionary <int, decimal> weights = components.ToDictionary(x => x.QDMSInstrumentID, x => (decimal)x.Weight); benchmarkSeries = new Dictionary <DateTime, double>(); benchmarkReturns = new List <double>(); var benchmarkEC = new EquityCurve(1); decimal equity = 1; bool first = true; foreach (DateTime today in datesInPeriod) { decimal ret = 0; foreach (var kvp in data) { var ts = kvp.Value; ts.ProgressTo(today); if (ts.CurrentBar > 0) { decimal todayClose = ts[0].AdjClose.HasValue ? ts[0].AdjClose.Value : ts[0].Close; decimal lastClose = ts[1].AdjClose.HasValue ? ts[1].AdjClose.Value : ts[1].Close; ret += weights[kvp.Key] * (todayClose / lastClose - 1); #if DEBUG logger.Log(LogLevel.Trace, "Benchmark component: Date: {0} Close: {1} PrevClose: {2} Ret: {3}", today, todayClose, lastClose, ret); #endif } } benchmarkEC.AddReturn((double)ret, today); if (first) { first = false; benchmarkReturns.Add((double)(1 + ret)); benchmarkSeries.Add(today, (double)equity); continue; } equity *= 1 + ret; benchmarkReturns.Add((double)(1 + ret)); benchmarkSeries.Add(today, (double)equity); } return(benchmarkEC); }
public static void GetRatios(EquityCurve ec, int daysInPeriod, out double sharpeRatio, out double marRatio, out double kRatio) { GetRatios(ec.Equity, ec.DrawdownPct, daysInPeriod, out sharpeRatio, out marRatio, out kRatio); }
public static Dictionary <string, string> EquityCurveStats(EquityCurve ec, int calendarDaysInPeriod) { if (ec.Returns.Count <= 1) { return(new Dictionary <string, string>()); } var stats = new Dictionary <string, string>(); var ds = new DescriptiveStatistics(ec.Returns); stats.Add("Average Return", ds.Mean.ToString("p3")); stats.Add("Total Period Return", ((ec.Equity.Last() / ec.Equity.First()) - 1).ToString("p2")); double cagr = Math.Pow(ec.Equity.Last() / ec.Equity.First(), 365.0 / calendarDaysInPeriod) - 1; stats.Add("CAGR", cagr.ToString("p2")); stats.Add("Standard Deviation (Daily)", ds.StandardDeviation.ToString("p2")); stats.Add("Standard Deviation (Annualized)", (ds.StandardDeviation * Math.Sqrt(252)).ToString("p2")); stats.Add("Skewness", ds.Skewness.ToString("0.00")); stats.Add("Best day", ds.Maximum.ToString("p2")); stats.Add("Worst day", ds.Minimum.ToString("p2")); stats.Add("Average Up Day", ec.Returns.Any(x => x > 0) ? ec.Returns.Where(x => x > 0).Average().ToString("p2") : "-"); stats.Add("Average Down Day", ec.Returns.Any(x => x < 0) ? ec.Returns.Where(x => x < 0).Average().ToString("p2") : "-"); if (ec.Returns.Any(x => x > 0) && ec.Returns.Any(x => x < 0)) { stats.Add("Risk:Reward", (ec.Returns.Where(x => x > 0).Average() / -ec.Returns.Where(x => x < 0).Average()).ToString("0.00")); } else { stats.Add("Risk:Reward", "-"); } double zeroLimit = 0.0000001; stats.Add("% Up Days", ((double)ec.Returns.Count(x => x > zeroLimit) / ec.Returns.Count).ToString("p1")); stats.Add("% Down Days", ((double)ec.Returns.Count(x => x < -zeroLimit) / ec.Returns.Count).ToString("p1")); stats.Add("% Flat Days", ((double)ec.Returns.Count(x => Math.Abs(x) < zeroLimit) / ec.Returns.Count).ToString("p1")); double grossWin = ec.Returns.Where(x => x > 0).Sum(); stats.Add("Gross Win", grossWin.ToString("p2")); double grossLoss = ec.Returns.Where(x => x < 0).Sum(); stats.Add("Gross Loss", grossLoss.ToString("p2")); stats.Add("Profit Factor", grossLoss != 0 ? (grossWin / -grossLoss).ToString("0.00") : "-"); double downsideDeviation = ec.Returns.DownsideDeviation(0); double upsideDeviation = ec.Returns.Where(x => x > 0).StandardDeviation(); stats.Add("Volatility Skewness", (upsideDeviation / downsideDeviation).ToString("0.00")); stats.Add("Max Drawdown", ec.DrawdownPct.Min().ToString("p2")); stats.Add("Average Drawdown", ec.DrawdownPct.Average().ToString("p2")); if (ec.DrawdownLengths.Count > 0) { stats.Add("Longest Drawdown", ec.DrawdownLengths.Max().TotalDays.ToString("0.0") + " days"); } else { stats.Add("Longest Drawdown", "N/A"); } string avgDDLengthStat = "N/A"; if (ec.DrawdownLengths.Count > 0) { avgDDLengthStat = ec.DrawdownLengths.Select(x => x.TotalDays).Average().ToString("0.00") + " days"; } stats.Add("Average Drawdown Length", avgDDLengthStat); double sharpe, mar, kratio; GetRatios(ec, calendarDaysInPeriod, out sharpe, out mar, out kratio); stats.Add("Sharpe ratio", sharpe.ToString("0.00")); double sortino = (cagr - Properties.Settings.Default.assumedInterestRate) / (downsideDeviation * Math.Sqrt(252)); stats.Add("Sortino ratio", sortino.ToString("0.00")); stats.Add("MAR Ratio", mar.ToString("0.00")); stats.Add("K Ratio", kratio.ToString("0.00")); double ulcerIndex = Math.Sqrt(ec.DrawdownPct.Sum(x => x * x) / ec.DrawdownPct.Count); stats.Add("Ulcer Index", (ulcerIndex * 100).ToString("0.00")); stats.Add("UPI/Martin Ratio", ((cagr - Properties.Settings.Default.assumedInterestRate) / ulcerIndex).ToString("0.00")); return(stats); }
public static Dictionary<string, string> EquityCurveStats(EquityCurve ec, int calendarDaysInPeriod) { if (ec.Returns.Count <= 1) return new Dictionary<string, string>(); var stats = new Dictionary<string, string>(); var ds = new DescriptiveStatistics(ec.Returns); stats.Add("Average Return", ds.Mean.ToString("p3")); stats.Add("Total Period Return", ((ec.Equity.Last() / ec.Equity.First()) - 1).ToString("p2")); double cagr = Math.Pow(ec.Equity.Last() / ec.Equity.First(), 365.0 / calendarDaysInPeriod) - 1; stats.Add("CAGR", cagr.ToString("p2")); stats.Add("Standard Deviation (Daily)", ds.StandardDeviation.ToString("p2")); stats.Add("Standard Deviation (Annualized)", (ds.StandardDeviation * Math.Sqrt(252)).ToString("p2")); stats.Add("Skewness", ds.Skewness.ToString("0.00")); stats.Add("Best day", ds.Maximum.ToString("p2")); stats.Add("Worst day", ds.Minimum.ToString("p2")); stats.Add("Average Up Day", ec.Returns.Any(x => x > 0) ? ec.Returns.Where(x => x > 0).Average().ToString("p2") : "-"); stats.Add("Average Down Day", ec.Returns.Any(x => x < 0) ? ec.Returns.Where(x => x < 0).Average().ToString("p2") : "-"); if (ec.Returns.Any(x => x > 0) && ec.Returns.Any(x => x < 0)) { stats.Add("Risk:Reward", (ec.Returns.Where(x => x > 0).Average() / -ec.Returns.Where(x => x < 0).Average()).ToString("0.00")); } else { stats.Add("Risk:Reward", "-"); } double zeroLimit = 0.0000001; stats.Add("% Up Days", ((double)ec.Returns.Count(x => x > zeroLimit) / ec.Returns.Count).ToString("p1")); stats.Add("% Down Days", ((double)ec.Returns.Count(x => x < -zeroLimit) / ec.Returns.Count).ToString("p1")); stats.Add("% Flat Days", ((double)ec.Returns.Count(x => Math.Abs(x) < zeroLimit) / ec.Returns.Count).ToString("p1")); double grossWin = ec.Returns.Where(x => x > 0).Sum(); stats.Add("Gross Win", grossWin.ToString("p2")); double grossLoss = ec.Returns.Where(x => x < 0).Sum(); stats.Add("Gross Loss", grossLoss.ToString("p2")); stats.Add("Profit Factor", grossLoss != 0 ? (grossWin / -grossLoss).ToString("0.00") : "-"); double downsideDeviation = ec.Returns.DownsideDeviation(0); double upsideDeviation = ec.Returns.Where(x => x > 0).StandardDeviation(); stats.Add("Volatility Skewness", (upsideDeviation / downsideDeviation).ToString("0.00")); stats.Add("Max Drawdown", ec.DrawdownPct.Min().ToString("p2")); stats.Add("Average Drawdown", ec.DrawdownPct.Average().ToString("p2")); if(ec.DrawdownLengths.Count > 0) stats.Add("Longest Drawdown", ec.DrawdownLengths.Max().TotalDays.ToString("0.0") + " days"); else stats.Add("Longest Drawdown", "N/A"); string avgDDLengthStat = "N/A"; if (ec.DrawdownLengths.Count > 0) { avgDDLengthStat = ec.DrawdownLengths.Select(x => x.TotalDays).Average().ToString("0.00") + " days"; } stats.Add("Average Drawdown Length", avgDDLengthStat); double sharpe, mar, kratio; GetRatios(ec, calendarDaysInPeriod, out sharpe, out mar, out kratio); stats.Add("Sharpe ratio", sharpe.ToString("0.00")); double sortino = (cagr - Properties.Settings.Default.assumedInterestRate) / (downsideDeviation * Math.Sqrt(252)); stats.Add("Sortino ratio", sortino.ToString("0.00")); stats.Add("MAR Ratio", mar.ToString("0.00")); stats.Add("K Ratio", kratio.ToString("0.00")); double ulcerIndex = Math.Sqrt(ec.DrawdownPct.Sum(x => x * x) / ec.DrawdownPct.Count); stats.Add("Ulcer Index", (ulcerIndex * 100).ToString("0.00")); stats.Add("UPI/Martin Ratio", ((cagr - Properties.Settings.Default.assumedInterestRate) / ulcerIndex).ToString("0.00")); return stats; }
private EquityCurve EcFromEquitySummaries(Dictionary<DateTime, decimal> summaries, Dictionary<DateTime, decimal> depositsWithdrawals) { if (summaries.Count == 0) return new EquityCurve(100, DateTime.Now); var ec = new EquityCurve(100, summaries.First().Key); decimal lastTotal = summaries.First().Value; bool first = true; foreach(var kvp in summaries) { if (first) { first = false; continue; } DateTime date = kvp.Key; decimal total = kvp.Value; decimal externalCashFlow = depositsWithdrawals.ContainsKey(date) ? depositsWithdrawals[date] : 0; if (lastTotal != 0) //make sure we avoid division by zero...equity can't change if we have 0 to start with anyway { double ret = (double)(((total - externalCashFlow) - lastTotal) / lastTotal); ec.AddReturn(ret, date); } lastTotal = total; } ec.CalcFinalValues(summaries.Last().Key); return ec; }