Files
marketdata/MarketDataLib/Generator/DCFGenerator.cs
2024-02-22 14:52:53 -05:00

311 lines
16 KiB
C#

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;
}
}
}