530 likes | 623 Views
STAT682 Final Project Group 2. Iliya Atanasov Matthew Ginley Aaron Mielke Lisa Stryjewski John Varghese. The Basics. Hold 50 stocks for 1 year Rebalance every year according to fundamentals. Reproducing O’Shaughnessy. Assisted by ClariFI ( backtesting software) Sales / Price
E N D
STAT682 Final ProjectGroup 2 IliyaAtanasov Matthew Ginley Aaron Mielke Lisa Stryjewski John Varghese
The Basics • Hold 50 stocks for 1 year • Rebalance every year according to fundamentals
Reproducing O’Shaughnessy • Assisted by ClariFI (backtesting software) • Sales / Price • Earnings Per Share / Price • Book / Price • Dividends Per Share / Price
Our Approach • Created our own backtesting code • Started with Sales / Price • Exhaustive coverage of all Compustat Variables • Reviewed high coverage and high correlation ratios • Selected an additional ratio to complement Sales / Price
Writing our Own Simulator C# Source Code
1. Variables varpriceIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] =="prcc_c"); vardividendIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] =="dvpsx_c"); varyearIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] == "fyear"); varsharesIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] =="CSHO"); varepsIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] == "EPSPX"); varidIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] == "gvkey"); varajexIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] =="adjex_c");
2. Market Capitalization varmarketCapMinimums = new int[Years]; yearToStocks= Enumerable.Range(0, Years).Select(i => new Dictionary<int, Stock>()).ToArray(); for (var i = 0; i < marketCapMinimums.Length; i++) { var year = i + MinYear; if (year < 1955) marketCapMinimums[i] = 27; else if (year < 1959) marketCapMinimums[i] = 27; else if (year < 1964) marketCapMinimums[i] = 28; else if (year < 1969) marketCapMinimums[i] = 31; else if (year < 1974) marketCapMinimums[i] = 34; else if (year < 1979) marketCapMinimums[i] = 44; else if (year < 1984) marketCapMinimums[i] = 64; else if (year < 1989) marketCapMinimums[i] = 97; else if (year < 1994) marketCapMinimums[i] = 117; else if (year < 1996) marketCapMinimums[i] = 150; elsemarketCapMinimums[i] = 185; }
3.A Reading Data while ((line = reader.ReadLine()) != null) { if (++count%10000 == 0) Console.WriteLine(count + ": " + (double) reader.BaseStream.Position/(double) reader.BaseStream.Length); var parts = line.Split(','); double price, shares, dividend, eps, ajex; int year; if (double.TryParse(parts[priceIndex], out price) == false || price == 0.0) continue; if (double.TryParse(parts[sharesIndex], out shares) == false) continue; if (double.TryParse(parts[epsIndex], out eps) == false) continue; if (int.TryParse(parts[yearIndex], out year) == false) continue; varmarketCap = price*shares; yearToMarketCaps[year - MinYear].Add(marketCap); varmarketCapMinimum = marketCapMinimums[year - MinYear]; if (marketCap < marketCapMinimum) continue;
3.B Reading Data double.TryParse(parts[ajexIndex], out ajex); double.TryParse(parts[dividendIndex], out dividend); var id = int.Parse(parts[idIndex]); if (ajex <= 0.0) ajex = 1.0; var values = Enumerable.Range(0, parts.Length).Select( i => { double value; if (double.TryParse(parts[i], out value) == false) value =double.NaN; return value; }).ToArray(); yearToStocks[year - MinYear].Add(id, new Stock(id, price, shares, dividend, ajex, values)); yearToMeanMarketCap= yearToMarketCaps.Select( marketCaps=> marketCaps.Count == 0 ? 0.0 : marketCaps.Average() ).ToArray(); }
4. Calculating Returns public void CalculateReturns(Stock nextYears) { if (nextYears == null) return; Return = Ajex * (nextYears.Price + nextYears.Dividend) / Price / nextYears.Ajex - 1.0; }
5.A Ranking Stocks Func<Func<Stock, double>, List<RanksAndStock>> rankStocks = getTestValue => { varrankAndStock = new List<RanksAndStock>(count); for (var year = 0; year < Years - 1; year++) { vartestSortedStockList = yearToStocks[year].Values.ToArray(); varreturnSortedIndexes = Enumerable.Range(0, testSortedStockList.Length).ToArray(); Array.Sort(testSortedStockList, (x, y) => getTestValue(x).CompareTo(getTestValue(y))); Array.Sort(testSortedStockList.Select(s => s.Return.Value).ToArray(), returnSortedIndexes);
5.B Ranking Stocks varreturnRanks = GenerateRanks(testSortedStockList.Length, i => testSortedStockList[returnSortedIndexes[i]], stock => stock.Return.Value); vartestRanks = GenerateRanks(testSortedStockList.Length, i => testSortedStockList[i], getTestValue); for (varreturnSortedIndex = 0; returnSortedIndex < returnSortedIndexes.Length; returnSortedIndex++) { vartestSortedIndex = returnSortedIndexes[returnSortedIndex]; var stock = testSortedStockList[testSortedIndex]; if (double.IsNaN(getTestValue(stock))) continue; rankAndStock.Add(new RanksAndStock(testRanks[testSortedIndex], returnRanks[returnSortedIndex], stock)); } } return rankAndStock; };
6. Pearson private static double CalculatePearson(IEnumerable<RanksAndStock> ranksAndStocks) { var n = ranksAndStocks.Count(); varsumReturn = ranksAndStocks.Sum(ranksAndStock => ranksAndStock.ReturnRank); varsumReturnSquared = ranksAndStocks.Sum( ranksAndStock=> ranksAndStock.ReturnRank*ranksAndStock.ReturnRank); varreturnDifferences = ((n*sumReturnSquared) - (sumReturn*sumReturn)); varsumTest = ranksAndStocks.Sum(ranksAndStock => ranksAndStock.TestRank); varsumTestSquared = ranksAndStocks.Sum( ranksAndStock=> ranksAndStock.TestRank*ranksAndStock.TestRank); varsumTestReturn = ranksAndStocks.Sum( ranksAndStock=> ranksAndStock.TestRank*ranksAndStock.ReturnRank); vardenom = Math.Sqrt(returnDifferences*((n*sumTestSquared) - (sumTest*sumTest))); varpearson = (n*sumTestReturn - (sumReturn*sumTest))/denom; return pearson; }
7. Single Values using (var writer = new StreamWriter("singles.csv")) { writer.WriteLine("column,r,%"); Parallel.For( 0, headers.Length, //new ParallelOptions{ MaxDegreeOfParallelism = 1}, header => { Func<Stock, double> getTestValue = stock => stock.Values[header]; varrankAndStock = rankStocks(getTestValue); varpearson = CalculatePearson(rankAndStock); var temp = header + ":" + headers[header] + "," + pearson + "," + ((double) rankAndStock.Count/count); Console.WriteLine(temp); lock (writer) writer.WriteLine(temp); if (double.IsNaN(pearson) == false) lock (validColumns) validColumns.Add(header); }); }
8.A Ratios using (var writer = new StreamWriter("ratios.csv")) { writer.WriteLine("numerator,denominator,r,%"); Parallel.For( 0, validColumns.Count, //new ParallelOptions{ MaxDegreeOfParallelism = 1}, numeratorIndex => Parallel.For( 0, validColumns.Count, //new ParallelOptions{ MaxDegreeOfParallelism = 1}, denominatorIndex => { try { var numerator = validColumns[numeratorIndex]; var denominator = validColumns[denominatorIndex]; if (numerator == denominator) return;
8.B Ratios if (System.Threading.Interlocked.Increment(ref progress)%1000 == 0) Console.WriteLine("******* " + progress + "/" + total + "=" + ((double) progress/total) + " *******"); Func<Stock, double> getTestValue = stock => stock.Values[numerator]/stock.Values[denominator]; varrankAndStock = rankStocks(getTestValue); varpearson = CalculatePearson(rankAndStock); vartemp = numerator + ":" + headers[numerator] + "," + denominator + ":" + headers[denominator] + "," + pearson + "," + ((double) rankAndStock.Count/count); Console.WriteLine(temp); lock (writer) writer.WriteLine(temp); } catch (Exception e) { try { using (var errors = new StreamWriter("errors.txt", true)) errors.WriteLine(e.ToString()); } catch {} } })); }
9.A Multipliers using (var writer = new StreamWriter("multipliers.csv")) { writer.WriteLine("first,second,r,%"); Parallel.For( 0, validColumns.Count, //new ParallelOptions{ MaxDegreeOfParallelism = 1}, firstIndex => Parallel.For( 0, validColumns.Count, //new ParallelOptions{ MaxDegreeOfParallelism = 1}, secondIndex => { try { var first = validColumns[firstIndex]; var second = validColumns[secondIndex]; if (System.Threading.Interlocked.Increment(ref progress)%1000 == 0)Console.WriteLine("******* " + progress + "/" + total + "=” + ((double) progress/total) + " *******");
9.B Multipliers Func<Stock, double> getTestValue = stock => stock.Values[first]*stock.Values[second]; varrankAndStock = rankStocks(getTestValue); varpearson = CalculatePearson(rankAndStock); vartemp = first + ":" + headers[first] + "," + second + ":" + headers[second] + "," + pearson+ "," + ((double) rankAndStock.Count/count); Console.WriteLine(temp); lock (writer) writer.WriteLine(temp); } catch (Exception e) { try { using (var errors = new StreamWriter("errors.txt", true)) errors.WriteLine(e.ToString()); } catch {} } ]})); }
10. Strategies varnameToTest = new Dictionary<string, Func<Stock, double>> { { "EPS_OVER_Price", stock => -stock.Values[258] / stock.Values[944]}, { "-EPS_OVER_Price", stock => stock.Values[258] / stock.Values[944]}, { "EBITDA_over_EV", stock => -stock.Values[250] / (stock.Values[944] * stock.Values[149] + stock.Values[194] + stock.Values[153] - stock.Values[109])}, { "Sales_OVER_Price", stock => -stock.Values[705] / stock.Values[944]}, { "Sales_OVER_Price_PLUS_IBCOM_OVER_NI", stock => -stock.Values[705] / stock.Values[944] + -stock.Values[352] / stock.Values[513]}, { "Dividends_OVER_Price", stock => -stock.Values[943] / stock.Values[944]}, { "IBCOM_OVER_NI", stock => -stock.Values[352] / stock.Values[513]} };
11.A Strategy Simulation for (var large = 0; large < 2; large++) { var strategies = nameToTest.ToArray(); for (vari = 0; i < nameToTest.Count; i++) { Func<Stock, double> strategy; string name; if(i < nameToTest.Count) { strategy = strategies[i].Value; name = strategies[i].Key; } else { name = "Combo”; strategy = stock => { return strategies.Select(s => s.Value(stock)).Where(d => double.IsNaN(d) == false).Average(); }; }
11.B Strategy Simulation varlargeName = large == 0 ? "_large" : string.Empty; using (var writer = new StreamWriter("returns_" + name + largeName + ".csv")) { writer.WriteLine("Year,Average Return"); Parallel.For( 1, Years - 1, new ParallelOptions {MaxDegreeOfParallelism = 1}, year => { vartestSortedStockList = yearToStocks[year - 1].Values.ToArray(); if (large == 0) testSortedStockList= testSortedStockList.Where(s => s.Price * s.Shares >= yearToMeanMarketCap[year]).ToArray(); Array.Sort(testSortedStockList, (x, y) => strategy(x).CompareTo(strategy(y)));
11.C Strategy Simulation varreturns = new List<double>(); for (var index = 0; index < testSortedStockList.Length && returns.Count < 50; index++) { varlastYearsStock = testSortedStockList[index]; if (double.IsNaN(strategy(lastYearsStock))) continue; Stock thisYearsStock; if (yearToStocks[year].TryGetValue(lastYearsStock.ID, out thisYearsStock) == false) continue; returns.Add(thisYearsStock.Return.Value); } varline = (year + MinYear) + "," + (returns.Count() == 0 ? 0.0 : returns.Average()) + "," + string.Join(",", returns.Select(r => r.ToString())); Console.WriteLine(line); lock (writer) writer.WriteLine(line); }); } } } }
12.A Unused CRSP Data using (var reader = new StreamReader(@"d:\data\stat\just price2.csv")) { string line; varpriceHeaders = reader.ReadLine().Split(','); varidIndex = Enumerable.Range(0, priceHeaders.Length).First(i => priceHeaders[i] == "gvkey"); vardateIndex = Enumerable.Range(0, priceHeaders.Length).First(i => priceHeaders[i] == "datadate"); varpriceIndex = Enumerable.Range(0, priceHeaders.Length).First(i => priceHeaders[i] == "PRCCM"); count = 0;
12.B Unused CRSP Data while ((line = reader.ReadLine()) != null) { if (++count%10000 == 0) Console.WriteLine(count + ": " + (double) reader.BaseStream.Position/(double) reader.BaseStream.Length); var parts = line.Split(','); double price; var date = DateTime.ParseExact(parts[dateIndex], "yyyyMMdd”, CultureInfo.InvariantCulture, DateTimeStyles.None); if (date.Month != 12) continue; var id = int.Parse(parts[idIndex]); if (double.TryParse(parts[priceIndex], out price) == false || price == 0.0) continue; yearToStockToPrice[date.Year - MinYear][id] = price; } }