using System; using System.Collections.Generic; using System.Linq; using System.Text; using MarketData.DataAccess; using MarketData.Numerical; using MarketData.MarketDataModel; using MarketData.Utils; namespace MarketData.Generator { public class DCFGenerator { private DCFGenerator() { } // This just calculates the WACC public static double CalculateWACC(String symbol,DateTime asOf) { try { DCFValuation dcfValuation = new DCFValuation(); dcfValuation.Symbol = symbol; Fundamental fundamental = FundamentalDA.GetFundamentalMaxDate(symbol,asOf); if (null == fundamental) {return double.NaN; } if (!CalculateCostOfEquity(dcfValuation,asOf)) return double.NaN; if (!CalculateCostOfDebt(dcfValuation,fundamental.TotalDebt,asOf)) return double.NaN; dcfValuation.MarketValueOfDebt = fundamental.TotalDebt; dcfValuation.MarketValueOfEquity = fundamental.MarketCap; dcfValuation.OutstandingShares = fundamental.SharesOutstanding; dcfValuation.TotalCapitalInvested = dcfValuation.MarketValueOfDebt + dcfValuation.MarketValueOfEquity; if (!CalculateWACC(dcfValuation)) return double.NaN; return dcfValuation.WACC; } catch(Exception) { return double.NaN; } } // This calculates the full discounted cashflow public static DCFValuation GenerateDCFValuation(String symbol) { DCFValuation dcfValuation = new DCFValuation(); dcfValuation.Symbol = symbol; Fundamental fundamental = FundamentalDA.GetFundamental(symbol); if (null == fundamental) { dcfValuation.Message = "Missing fundamental data."; return dcfValuation; } if (!CalculateCostOfEquity(dcfValuation)) return dcfValuation; if (!CalculateCostOfDebt(dcfValuation, fundamental.TotalDebt)) return dcfValuation; dcfValuation.MarketValueOfDebt = fundamental.TotalDebt; dcfValuation.MarketValueOfEquity = fundamental.MarketCap; dcfValuation.OutstandingShares = fundamental.SharesOutstanding; dcfValuation.TotalCapitalInvested = dcfValuation.MarketValueOfDebt + dcfValuation.MarketValueOfEquity; if (!CalculateWACC(dcfValuation)) return dcfValuation; if (!Discount(dcfValuation)) return dcfValuation; EnhanceValuation(dcfValuation,fundamental); // add ROIC, EPSGrowth, EarningsGrowth dcfValuation.success = true; return dcfValuation; } public static void EnhanceValuation(DCFValuation dcfValuation,Fundamental fundamental) { TimeSeriesCollection bookValuePerShareCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.BVPS); TimeSeriesCollection roicCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.ROIC); TimeSeriesCollection revenueCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.Revenue); TimeSeriesCollection epsCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.EPS); YieldCurve yieldCurve=YieldCurveDA.GetYieldCurve(); if (null != roicCollection && 0 != roicCollection.Count) { dcfValuation.Enhancements.ROICGrowth = Numerics.AverageReturn(roicCollection); dcfValuation.Enhancements.ROIC = roicCollection[0].Value/100.00; } dcfValuation.Enhancements.DebtLoad = fundamental.Revenue * 3 > fundamental.TotalDebt ? true : false; dcfValuation.Enhancements.EPSGrowth = Numerics.AverageReturn(epsCollection); dcfValuation.Enhancements.RevenueGrowth = Numerics.AverageReturn(revenueCollection); dcfValuation.Enhancements.BVPS = fundamental.BookValuePerShare; if (null == dcfValuation.CurrentPrice || double.NaN.Equals(dcfValuation.CurrentPrice.Close)) dcfValuation.Enhancements.PBVPS = double.NaN; else dcfValuation.Enhancements.PBVPS = dcfValuation.CurrentPrice.Close / dcfValuation.Enhancements.BVPS; dcfValuation.Enhancements.EPS = fundamental.EPS; dcfValuation.Enhancements.PE = fundamental.PE; dcfValuation.Enhancements.PEG = fundamental.PEG; dcfValuation.Enhancements.MOS = dcfValuation.StockPriceValuation * .5; dcfValuation.Enhancements.MOS80 = dcfValuation.StockPriceValuation * .8; dcfValuation.Enhancements.IntrinsicValue = GrahamGenerator.IntrinsicValue(fundamental.EPS,dcfValuation.Enhancements.EPSGrowth); if (null == yieldCurve || double.NaN.Equals(yieldCurve[0].Yr20)) dcfValuation.Enhancements.IntrinsicValueRevised = double.NaN; else dcfValuation.Enhancements.IntrinsicValueRevised = GrahamGenerator.IntrinsicValueRevised(fundamental.EPS,dcfValuation.Enhancements.EPSGrowth,yieldCurve[0].Yr30); if (null == dcfValuation.CurrentPrice || double.NaN.Equals(dcfValuation.CurrentPrice.Close)) dcfValuation.Enhancements.RGVIntrinsic = double.NaN; else dcfValuation.Enhancements.RGVIntrinsic = dcfValuation.Enhancements.IntrinsicValue / dcfValuation.CurrentPrice.Close; if (null == dcfValuation.CurrentPrice || double.NaN.Equals(dcfValuation.CurrentPrice.Close)) dcfValuation.Enhancements.RGVIntrinsicRevised = double.NaN; else dcfValuation.Enhancements.RGVIntrinsicRevised = dcfValuation.Enhancements.IntrinsicValueRevised / dcfValuation.CurrentPrice.Close; } private static bool Discount(DCFValuation dcfValuation,int years=10) { List sourceElements = new List(); List destElements = null; // pull cashflow history from database and apply filters TimeSeriesCollection freeCashflows = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.FreeCashflow); if (null == freeCashflows || 0 == freeCashflows.Count) { dcfValuation.Message = "Missing Freecashflows"; return false; } dcfValuation.FreeCashflowGrowth = Numerics.AverageReturnWithSpline(freeCashflows,dcfValuation.ReturnItems); // if free cashflow growth > 100% perform an average throwing away all returns in the set > 100% if (dcfValuation.FreeCashflowGrowth.Equals(double.NaN)) { dcfValuation.Message = "Freecashflow growth is undefined"; return false; } // set up spline parameters. Apply 3% cap to freecashflow growth in the 10th year and spline to that. sourceElements.Add(new Element(1, dcfValuation.FreeCashflowGrowth)); sourceElements.Add(new Element(years, .03 < dcfValuation.FreeCashflowGrowth ? .03 : dcfValuation.FreeCashflowGrowth)); // set the starting date and the initial freecashflow value, run interpolation DateTime startingDate = freeCashflows[0].AsOf; double runningCashflow = freeCashflows[0].Value; for (int year = 1; year <= years; year++) { startingDate = new DateTime(startingDate.Year + 1, startingDate.Month, startingDate.Day); destElements = new List(); destElements.Add(new Element(year, 0)); CatmullRom.PerformSpline((Element[])sourceElements.ToArray(), (Element[])destElements.ToArray()); double cashflowGrowth = destElements[0].Row; if (year < years) runningCashflow = runningCashflow * (1.00 + cashflowGrowth); else runningCashflow = (runningCashflow * (1.00 + cashflowGrowth)) / (Math.Abs(dcfValuation.WACC - cashflowGrowth)); dcfValuation.Cashflows.Add(new CashflowElement(startingDate, runningCashflow)); } // Discount cashflows by WACC for (int index = 0; index < dcfValuation.Cashflows.Count; index++) { dcfValuation.Cashflows[index].Cashflow /= Math.Pow(1.00 + dcfValuation.WACC, index + 1); } // sum up the discounted values to get the present value; dcfValuation.PresentValue = 0.00; for (int index = 0; index < dcfValuation.Cashflows.Count; index++) { dcfValuation.PresentValue += dcfValuation.Cashflows[index].Cashflow; } // subtract debt dcfValuation.PresentValue -= dcfValuation.TotalDebt; // divide by outstanding shares dcfValuation.StockPriceValuation=dcfValuation.PresentValue/dcfValuation.OutstandingShares; // Apply validation filters if (dcfValuation.PresentValue.Equals(double.NaN)) { dcfValuation.Message = "PresentValue is undefined"; return false; } if (dcfValuation.StockPriceValuation.Equals(double.NaN)) { dcfValuation.Message = "Stock price valuation is undefined"; return false; } return true; } // Calculate the WACC private static bool CalculateWACC(DCFValuation dcfValuation) { dcfValuation.WACC=((dcfValuation.MarketValueOfEquity/dcfValuation.TotalCapitalInvested)*dcfValuation.CostOfEquity)+ ((dcfValuation.MarketValueOfDebt/dcfValuation.TotalCapitalInvested)*dcfValuation.CostOfDebt*(1.00-dcfValuation.CorporateTaxRate)); return true; } private static bool CalculateCostOfEquity(DCFValuation dcfValuation) { String marketSymbol = "SPY"; Prices stockPrices = null; Prices marketPrices=null; // calculate 3-year beta of stock vs SP500 stockPrices = PricingDA.GetPrices(dcfValuation.Symbol, 365 * 3); if (null == stockPrices || 0 == stockPrices.Count) { dcfValuation.Message = "Missing prices for symbol."; return false; } dcfValuation.CurrentPrice = stockPrices[0]; marketPrices=PricingDA.GetPrices(marketSymbol,stockPrices.Count); if (null == stockPrices || 0 == stockPrices.Count || null == marketPrices || 0 == marketPrices.Count) { dcfValuation.Message = "Missing prices for market."; return false; } dcfValuation.Beta = BetaGenerator.Beta(dcfValuation.Symbol, PricingDA.GetLatestDate(dcfValuation.Symbol)); // get the risk free rate, 90 day treasury note dcfValuation.RiskFreeRate = YieldCurveDA.GetRiskFreeRate(); dcfValuation.RiskFreeRate /= 100.00; // calculate historic market returns if (dcfValuation.UseMarketReturn) { marketPrices = PricingDA.GetPrices(marketSymbol); float[] marketPricesArray = marketPrices.GetPrices(); dcfValuation.MarketReturn = Numerics.AnnualReturn(ref marketPricesArray); } else dcfValuation.MarketReturn = DCFValuation.MARKET_RETURN; // calculate cost of equity dcfValuation.CostOfEquity = dcfValuation.RiskFreeRate + dcfValuation.Beta * (dcfValuation.MarketReturn - dcfValuation.RiskFreeRate); return true; } private static bool CalculateCostOfEquity(DCFValuation dcfValuation,DateTime asOf) { String marketSymbol = "SPY"; Prices stockPrices = null; Prices marketPrices=null; // calculate 3-year beta of stock vs SP500 stockPrices = PricingDA.GetPrices(dcfValuation.Symbol,asOf, 365 * 3); if (null == stockPrices || 0 == stockPrices.Count) { dcfValuation.Message = "Missing prices for symbol."; return false; } dcfValuation.CurrentPrice = stockPrices[0]; marketPrices=PricingDA.GetPrices(marketSymbol,asOf,stockPrices.Count); if (null == stockPrices || 0 == stockPrices.Count || null == marketPrices || 0 == marketPrices.Count) { dcfValuation.Message = "Missing prices for market."; return false; } //float[] stockPricesArray=stockPrices.GetPrices(); //float[] marketPricesArray = marketPrices.GetPrices(); //dcfValuation.Beta = Numerics.Beta(ref stockPricesArray, ref marketPricesArray); dcfValuation.Beta = BetaGenerator.Beta(dcfValuation.Symbol, PricingDA.GetLatestDate(dcfValuation.Symbol)); // get the risk free rate, 90 day treasury note dcfValuation.RiskFreeRate = YieldCurveDA.GetRiskFreeRate(asOf); dcfValuation.RiskFreeRate /= 100.00; // calculate historic market returns if (dcfValuation.UseMarketReturn) { marketPrices = PricingDA.GetPricesOnOrBefore(marketSymbol,asOf); float[] marketPricesArray = marketPrices.GetPrices(); dcfValuation.MarketReturn = Numerics.AnnualReturn(ref marketPricesArray); } else dcfValuation.MarketReturn = DCFValuation.MARKET_RETURN; // calculate cost of equity dcfValuation.CostOfEquity = dcfValuation.RiskFreeRate + dcfValuation.Beta * (dcfValuation.MarketReturn - dcfValuation.RiskFreeRate); return true; } // **YAHOO InterestExpense is totally wrong for some stocks. AER is a good example // This prioritizes MorningStar InterestExpense number over Yahoo Interest Expense number private static bool CalculateCostOfDebt(DCFValuation dcfValuation,double totalDebt) { TimeSeriesCollection interestExpenseSeries = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.InterestExpense); if (null == interestExpenseSeries || 0 == interestExpenseSeries.Count) { IncomeStatement incomeStatement = IncomeStatementDA.GetIncomeStatement(dcfValuation.Symbol, IncomeStatement.PeriodType.Annual); if(null!=incomeStatement&&incomeStatement.InterestExpense.Equals(double.NaN)) { dcfValuation.InterestExpense=0.00; } else if(null==incomeStatement) { dcfValuation.Message = "Missing IncomeStatement or Interest Expense "; return false; } else dcfValuation.InterestExpense=incomeStatement.InterestExpense; } else { if(interestExpenseSeries[0].Value.Equals(double.NaN))dcfValuation.InterestExpense=0.00; else dcfValuation.InterestExpense=interestExpenseSeries[0].Value; } TimeSeriesCollection timeSeriesCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.TaxRate); if (null == timeSeriesCollection || 0 == timeSeriesCollection.Count) timeSeriesCollection.Add(new TimeSeriesElement() {Value=37 }); TimeSeriesElement timeSeriesElement = timeSeriesCollection[0]; dcfValuation.CorporateTaxRate = timeSeriesElement.Value / 100.00; dcfValuation.TotalDebt = totalDebt; if(0.00==totalDebt) { dcfValuation.InterestRate=0.00; dcfValuation.CostOfDebt=0.00; } else { dcfValuation.InterestRate=dcfValuation.InterestExpense/dcfValuation.TotalDebt; dcfValuation.CostOfDebt=dcfValuation.InterestRate*(1-dcfValuation.CorporateTaxRate); } return true; } private static bool CalculateCostOfDebt(DCFValuation dcfValuation,double totalDebt,DateTime asOf) { TimeSeriesCollection interestExpenseSeries = HistoricalDA.GetTimeSeriesMaxAsOf(dcfValuation.Symbol,asOf, TimeSeriesElement.ElementType.InterestExpense); if (null == interestExpenseSeries || 0 == interestExpenseSeries.Count) { IncomeStatement incomeStatement = IncomeStatementDA.GetIncomeStatementMaxAsOf(dcfValuation.Symbol,asOf, IncomeStatement.PeriodType.Annual); if(null!=incomeStatement&&incomeStatement.InterestExpense.Equals(double.NaN)) { dcfValuation.InterestExpense=0.00; } else if(null==incomeStatement) { dcfValuation.Message = "Missing IncomeStatement or Interest Expense "; return false; } else dcfValuation.InterestExpense=incomeStatement.InterestExpense; } else { if(interestExpenseSeries[0].Value.Equals(double.NaN))dcfValuation.InterestExpense=0.00; else dcfValuation.InterestExpense=interestExpenseSeries[0].Value; } TimeSeriesCollection timeSeriesCollection = HistoricalDA.GetTimeSeriesMaxAsOf(dcfValuation.Symbol,asOf, TimeSeriesElement.ElementType.TaxRate); if (null == timeSeriesCollection || 0 == timeSeriesCollection.Count) timeSeriesCollection.Add(new TimeSeriesElement() {Value=37 }); TimeSeriesElement timeSeriesElement = timeSeriesCollection[0]; dcfValuation.CorporateTaxRate = timeSeriesElement.Value / 100.00; dcfValuation.TotalDebt = totalDebt; if(0.00==totalDebt) { dcfValuation.InterestRate=0.00; dcfValuation.CostOfDebt=0.00; } else { dcfValuation.InterestRate=dcfValuation.InterestExpense/dcfValuation.TotalDebt; dcfValuation.CostOfDebt=dcfValuation.InterestRate*(1-dcfValuation.CorporateTaxRate); } return true; } } }