311 lines
16 KiB
C#
Executable File
311 lines
16 KiB
C#
Executable File
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<Element> sourceElements = new List<Element>();
|
|
List<Element> 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<Element>();
|
|
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;
|
|
}
|
|
}
|
|
}
|