STAT682 Final ProjectGroup 2
Iliya AtanasovMatthew Ginley
Aaron MielkeLisa StryjewskiJohn 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
Using ClariFI
ClariFI
ClariFI
Results from ClariFI
Sales to Price
Sales to Price
Sales to Price
EPS to Price
EPS to Price
EPS to Price
Book to Price
Book to Price
Book to Price
Dividend Yield
Dividend Yield
Dividend Yield
Writing our Own Simulator
C# Source Code
1. Variables
var priceIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] =="prcc_c");
var dividendIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] =="dvpsx_c");
var yearIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] == "fyear");
var sharesIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] =="CSHO");
var epsIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] == "EPSPX");
var idIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] == "gvkey");
var ajexIndex = Enumerable.Range(0, headers.Length).First(i => headers[i] =="adjex_c");
2. Market Capitalizationvar marketCapMinimums = 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;else marketCapMinimums[i] = 185;
}
3.A Reading Datawhile ((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;
var marketCap = price*shares;yearToMarketCaps[year - MinYear].Add(marketCap);var marketCapMinimum = marketCapMinimums[year - MinYear];if (marketCap < marketCapMinimum) continue;
3.B Reading Datadouble.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 Returnspublic void CalculateReturns(Stock nextYears){ if (nextYears == null) return; Return = Ajex * (nextYears.Price + nextYears.Dividend)
/ Price / nextYears.Ajex - 1.0;}
5.A Ranking StocksFunc<Func<Stock, double>, List<RanksAndStock>> rankStocks =
getTestValue =>
{
var rankAndStock = new List<RanksAndStock>(count);
for (var year = 0; year < Years - 1; year++)
{
var testSortedStockList = yearToStocks[year].Values.ToArray();
var returnSortedIndexes = 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 var returnRanks = GenerateRanks(testSortedStockList.Length,
i => testSortedStockList[returnSortedIndexes[i]],stock => stock.Return.Value);
var testRanks = GenerateRanks(testSortedStockList.Length, i => testSortedStockList[i], getTestValue); for (var returnSortedIndex = 0; returnSortedIndex < returnSortedIndexes.Length; returnSortedIndex++) { var testSortedIndex = returnSortedIndexes[returnSortedIndex]; var stock = testSortedStockList[testSortedIndex]; if (double.IsNaN(getTestValue(stock))) continue;
rankAndStock.Add(new RanksAndStock(testRanks[testSortedIndex],returnRanks[returnSortedIndex], stock));
} } return rankAndStock; };
6. Pearsonprivate static double CalculatePearson(IEnumerable<RanksAndStock> ranksAndStocks){ var n = ranksAndStocks.Count(); var sumReturn = ranksAndStocks.Sum(ranksAndStock => ranksAndStock.ReturnRank); var sumReturnSquared = ranksAndStocks.Sum(
ranksAndStock => ranksAndStock.ReturnRank*ranksAndStock.ReturnRank); var returnDifferences = ((n*sumReturnSquared) - (sumReturn*sumReturn)); var sumTest = ranksAndStocks.Sum(ranksAndStock => ranksAndStock.TestRank); var sumTestSquared = ranksAndStocks.Sum(
ranksAndStock => ranksAndStock.TestRank*ranksAndStock.TestRank); var sumTestReturn = ranksAndStocks.Sum(
ranksAndStock => ranksAndStock.TestRank*ranksAndStock.ReturnRank); var denom = Math.Sqrt(returnDifferences*((n*sumTestSquared) - (sumTest*sumTest)));
var pearson = (n*sumTestReturn - (sumReturn*sumTest))/denom; return pearson;}
7. Single Valuesusing (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]; var rankAndStock = rankStocks(getTestValue); var pearson = 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 Ratiosusing (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];var rankAndStock = rankStocks(getTestValue);var pearson = CalculatePearson(rankAndStock);var temp = 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 Multipliersusing (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];var rankAndStock = rankStocks(getTestValue);var pearson = CalculatePearson(rankAndStock);var temp = 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. Strategiesvar nameToTest = 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 Simulationfor (var large = 0; large < 2; large++) { var strategies = nameToTest.ToArray(); for (var i = 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 var largeName = 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 => { var testSortedStockList = 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 Simulationvar returns = new List<double>();for (var index = 0; index < testSortedStockList.Length && returns.Count < 50; index++){ var lastYearsStock = 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);}var line = (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 Datausing (var reader = new StreamReader(@"d:\data\stat\just price2.csv")){
string line;
var priceHeaders = reader.ReadLine().Split(',');
var idIndex = Enumerable.Range(0, priceHeaders.Length).First(i => priceHeaders[i] == "gvkey");
var dateIndex = Enumerable.Range(0, priceHeaders.Length).First(i => priceHeaders[i] == "datadate");
var priceIndex = Enumerable.Range(0, priceHeaders.Length).First(i => priceHeaders[i] == "PRCCM");
count = 0;
12.B Unused CRSP Datawhile ((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;}
}
Our Results
Sales to Price (All Stocks)
Sales to Price (All Stocks)
Sales to Price (Large Stocks)
Sales to Price (Large Stocks)
IBCOM to NI (All Stocks)
IBCOM to NI (All Stocks)
IBCOM to NI (Large Stocks)
IBCOM to NI (Large Stocks)
S/P + IBCOM/NI (All Stocks)
S/P + IBCOM/NI (All Stocks)
S/P + IBCOM/NI (Large Stocks)
S/P + IBCOM/NI (Large Stocks)