added dcf growth (#2244)

Co-authored-by: Jeroen Bouma <jer.bouma@gmail.com>
This commit is contained in:
Colin Delahunty
2022-08-05 08:20:32 -04:00
committed by GitHub
parent 775d218891
commit 1e19ab5cc5
2 changed files with 152 additions and 85 deletions

View File

@@ -34,6 +34,7 @@ class CreateExcelFA:
len_pred: int = 10,
max_similars: int = 3,
no_filter: bool = False,
growth: bool = False,
):
"""
Creates a detailed DCF for a given company
@@ -49,9 +50,11 @@ class CreateExcelFA:
len_pred : int
The number of years to make predictions for before assuming a terminal value
max_similars : int
The maximum number of similar companies to show, will be less if there are not enough similar companies
The maximum number of similar companies to show, will be less if there are not enough
no_filter : bool
Disable filtering of similar companies to being in the same market cap category
growth : bool
When true this turns the revenue assumption from linear regression to percentage growth
"""
self.info: Dict[str, Any] = {
"len_data": 0,
@@ -64,6 +67,7 @@ class CreateExcelFA:
"no_filter": no_filter,
}
self.letter: int = 0
self.growth = growth
self.starts: Dict[str, int] = {"IS": 4, "BS": 18, "CF": 47}
self.wb: Workbook = Workbook()
self.ws: Dict[int, Any] = {
@@ -215,12 +219,13 @@ class CreateExcelFA:
)
dcf_model.set_cell(self.ws[1], f"{dcf_static.letters[col]}4", "m")
dcf_model.set_cell(self.ws[1], f"{dcf_static.letters[col+1]}4", "b")
self.get_linear("Date", "Revenue")
self.get_linear("Revenue", "Cost of Revenue")
rev_pred_type = "growth" if self.growth else "linear"
self.get_growth("Date", "Revenue", pred_type=rev_pred_type)
self.get_growth("Revenue", "Cost of Revenue")
self.get_sum("Gross Profit", "Revenue", [], ["Cost of Revenue"])
self.get_linear("Revenue", "Selling, General & Admin", True)
self.get_linear("Revenue", "Research & Development", True)
self.get_linear("Revenue", "Other Operating Expenses")
self.get_growth("Revenue", "Selling, General & Admin", True)
self.get_growth("Revenue", "Research & Development", True)
self.get_growth("Revenue", "Other Operating Expenses")
self.get_sum(
"Operating Income",
"Gross Profit",
@@ -231,10 +236,10 @@ class CreateExcelFA:
"Other Operating Expenses",
],
)
self.get_linear("Revenue", "Preferred Dividends")
self.get_linear("Revenue", "Interest Expense / Income")
self.get_linear("Revenue", "Other Expense / Income")
self.get_linear("Operating Income", "Income Tax")
self.get_growth("Revenue", "Preferred Dividends")
self.get_growth("Revenue", "Interest Expense / Income")
self.get_growth("Revenue", "Other Expense / Income")
self.get_growth("Operating Income", "Income Tax")
self.get_sum(
"Net Income",
"Operating Income",
@@ -245,27 +250,27 @@ class CreateExcelFA:
"Preferred Dividends",
"Preferred Dividends are not important in a DCF so we do not attempt to predict them.",
)
self.get_linear("Revenue", "Cash & Equivalents", True)
self.get_linear("Revenue", "Short-Term Investments", True)
self.get_growth("Revenue", "Cash & Equivalents", True)
self.get_growth("Revenue", "Short-Term Investments", True)
self.get_sum(
"Cash & Cash Equivalents",
"Cash & Equivalents",
["Short-Term Investments"],
[],
)
self.get_linear("Revenue", "Receivables", True)
self.get_linear("Revenue", "Inventory", True)
self.get_linear("Revenue", "Other Current Assets")
self.get_growth("Revenue", "Receivables", True)
self.get_growth("Revenue", "Inventory", True)
self.get_growth("Revenue", "Other Current Assets")
self.get_sum(
"Total Current Assets",
"Cash & Cash Equivalents",
["Receivables", "Inventory", "Other Current Assets"],
[],
)
self.get_linear("Revenue", "Property, Plant & Equipment", True)
self.get_linear("Revenue", "Long-Term Investments", True)
self.get_linear("Revenue", "Goodwill and Intangibles", True)
self.get_linear("Revenue", "Other Long-Term Assets")
self.get_growth("Revenue", "Property, Plant & Equipment", True)
self.get_growth("Revenue", "Long-Term Investments", True)
self.get_growth("Revenue", "Goodwill and Intangibles", True)
self.get_growth("Revenue", "Other Long-Term Assets")
self.get_sum(
"Total Long-Term Assets",
"Property, Plant & Equipment",
@@ -279,10 +284,10 @@ class CreateExcelFA:
self.get_sum(
"Total Assets", "Total Current Assets", ["Total Long-Term Assets"], []
)
self.get_linear("Revenue", "Accounts Payable")
self.get_linear("Revenue", "Deferred Revenue")
self.get_linear("Revenue", "Current Debt")
self.get_linear("Revenue", "Other Current Liabilities")
self.get_growth("Revenue", "Accounts Payable")
self.get_growth("Revenue", "Deferred Revenue")
self.get_growth("Revenue", "Current Debt")
self.get_growth("Revenue", "Other Current Liabilities")
self.get_sum(
"Total Current Liabilities",
"Accounts Payable",
@@ -303,7 +308,7 @@ class CreateExcelFA:
"nceinstitute.com/resources/questions/model-questions/financial-modeling-plug/"
),
) # This is the plug
self.get_linear("Revenue", "Other Long-Term Liabilities")
self.get_growth("Revenue", "Other Long-Term Liabilities")
self.get_sum(
"Total Long-Term Liabilities",
"Long-Term Debt",
@@ -316,7 +321,7 @@ class CreateExcelFA:
["Total Long-Term Liabilities"],
[],
)
self.get_linear("Revenue", "Common Stock")
self.get_growth("Revenue", "Common Stock")
col = self.info["len_data"] + 1
rer = self.title_to_row("Retained Earnings")
nir = self.title_to_row("Net Income")
@@ -328,7 +333,7 @@ class CreateExcelFA:
num_form="[$$-409]#,##0.00;[RED]-[$$-409]#,##0.00",
)
self.get_linear("Revenue", "Comprehensive Income")
self.get_growth("Revenue", "Comprehensive Income")
self.get_sum(
"Shareholders' Equity",
"Common Stock",
@@ -771,38 +776,27 @@ class CreateExcelFA:
)
@log_start_end(log=logger)
def get_linear(self, x_ind: str, y_ind: str, no_neg: bool = False):
def get_growth(
self, x_ind: str, y_ind: str, no_neg: bool = False, pred_type: str = "linear"
):
"""Add growth to a column. Usually this is linear but other options are allowed
x_ind: str
The x variable to use. This is unused if growth is the pred_type
y_ind: str
The y variable to use
no_neg: bool
Whether or not the value can have negative numbers (profit can be revenue cannot)
pred_type: str
This is assumed to be linear but growth is allowed as well
"""
x_type = "IS" if x_ind in self.df["IS"].index else "BS"
y_type = "IS" if y_ind in self.df["IS"].index else "BS"
x_df = self.df["IS"] if x_type == "IS" else self.df["BS"]
y_df = self.df["IS"] if y_type == "IS" else self.df["BS"]
pre_x = (
x_df.columns.to_numpy() if x_ind == "Date" else x_df.loc[x_ind].to_numpy()
)
vfunc = np.vectorize(dcf_model.string_float)
pre_x = vfunc(pre_x)
if x_ind == "Date":
pre_x = pre_x - np.min(pre_x)
x = pre_x.reshape((-1, 1))
pre_y = y_df.loc[y_ind].to_numpy()
y = vfunc(pre_y)
model = LinearRegression().fit(x, y)
r_sq = model.score(x, y)
r = abs(r_sq**0.5)
if r > 0.9:
strength = "very strong"
elif r > 0.7:
strength = "strong"
elif r > 0.5:
strength = "moderate"
elif r > 0.3:
strength = "weak"
else:
strength = "very weak"
analy_message = ""
row1 = (
y_df.index.get_loc(y_ind)
+ 1
@@ -810,38 +804,101 @@ class CreateExcelFA:
)
col = self.info["len_pred"] + self.info["len_data"] + 3
dcf_model.set_cell(
self.ws[1], f"{dcf_static.letters[col]}{row1}", float(model.coef_)
)
dcf_model.set_cell(
self.ws[1], f"{dcf_static.letters[col+1]}{row1}", float(model.intercept_)
)
dcf_model.set_cell(
self.ws[1],
f"{dcf_static.letters[col+2]}{row1}",
dcf_static.letters[self.letter],
font=dcf_static.red,
)
dcf_model.set_cell(
self.ws[3],
f"A{self.letter+4}",
dcf_static.letters[self.letter],
font=dcf_static.red,
)
dcf_model.set_cell(
self.ws[3],
f"B{self.letter+4}",
(
if pred_type == "linear":
pre_x = (
x_df.columns.to_numpy()
if x_ind == "Date"
else x_df.loc[x_ind].to_numpy()
)
vfunc = np.vectorize(dcf_model.string_float)
pre_x = vfunc(pre_x)
if x_ind == "Date":
pre_x = pre_x - np.min(pre_x)
x = pre_x.reshape((-1, 1))
pre_y = y_df.loc[y_ind].to_numpy()
y = vfunc(pre_y)
model = LinearRegression().fit(x, y)
r_sq = model.score(x, y)
r = abs(r_sq**0.5)
if r > 0.9:
strength = "very strong"
elif r > 0.7:
strength = "strong"
elif r > 0.5:
strength = "moderate"
elif r > 0.3:
strength = "weak"
else:
strength = "very weak"
analy_message = (
f"The correlation between {x_ind.lower()} and {y_ind.lower()}"
f" is {strength} with a correlation coefficient of {r:.4f}."
),
)
)
dcf_model.set_cell(
self.ws[1], f"{dcf_static.letters[col]}{row1}", float(model.coef_)
)
dcf_model.set_cell(
self.ws[1],
f"{dcf_static.letters[col+1]}{row1}",
float(model.intercept_),
)
dcf_model.set_cell(
self.ws[1],
f"{dcf_static.letters[col+2]}{row1}",
dcf_static.letters[self.letter],
font=dcf_static.red,
)
dcf_model.set_cell(
self.ws[3],
f"A{self.letter+4}",
dcf_static.letters[self.letter],
font=dcf_static.red,
)
elif pred_type == "growth":
pre_y = y_df.loc[y_ind].to_numpy()
fin_y = []
for item in pre_y:
try:
fin_y.append(float(item))
except TypeError:
fin_y.append(0)
overall_growth = (fin_y[-1] - fin_y[0]) / fin_y[0]
yearly_growth = overall_growth / len(fin_y)
analy_message = (
f"Assuming growth percentage of {(yearly_growth * 100):.2f}%"
)
dcf_model.set_cell(
self.ws[1], f"{dcf_static.letters[col]}{row1}", yearly_growth
)
dcf_model.set_cell(
self.ws[1],
f"{dcf_static.letters[col+2]}{row1}",
dcf_static.letters[self.letter],
font=dcf_static.red,
)
dcf_model.set_cell(self.ws[3], f"B{self.letter+4}", analy_message)
col = self.info["len_data"] + 1
for i in range(self.info["len_pred"]):
if x_ind == "Date":
if pred_type == "growth":
row_n = (
y_df.index.get_loc(y_ind) + 1 + self.starts["IS"]
if y_type == "IS"
else self.starts["BS"]
)
base = (
f"(({dcf_static.letters[col+i]}4-B4)*{dcf_static.letters[col+self.info['len_pred']+2]}"
f"({dcf_static.letters[col+i-1]}{row1}* (1+"
f"{dcf_static.letters[col+self.info['len_pred']+2]}{row1}))"
)
elif x_ind == "Date":
base = (
f"(({dcf_static.letters[col+i]}4-B4)*"
f"{dcf_static.letters[col+self.info['len_pred']+2]}"
f"{row1})+{dcf_static.letters[col+self.info['len_pred']+3]}{row1}"
)
else:
@@ -851,7 +908,8 @@ class CreateExcelFA:
else self.starts["BS"]
)
base = (
f"({dcf_static.letters[col+i]}{row_n}*{dcf_static.letters[col+self.info['len_pred']+2]}{row1})"
f"({dcf_static.letters[col+i]}{row_n}*"
f"{dcf_static.letters[col+self.info['len_pred']+2]}{row1})"
f"+{dcf_static.letters[col+self.info['len_pred']+3]}{row1}"
)
dcf_model.set_cell(

View File

@@ -1262,17 +1262,26 @@ class FundamentalAnalysisController(StockBaseController):
default=6,
help="Number of similar companies to generate ratios for.",
)
parser.add_argument(
"-g",
"--growth",
action="store_true",
dest="growth",
default=False,
help="Whether to replace a linear regression estimate with a growth estimate.",
)
ns_parser = self.parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
dcf = dcf_view.CreateExcelFA(
self.ticker,
ns_parser.audit,
ns_parser.ratios,
ns_parser.prediction,
ns_parser.similar,
ticker=self.ticker,
audit=ns_parser.audit,
ratios=ns_parser.ratios,
len_pred=ns_parser.prediction,
max_similars=ns_parser.similar,
growth=ns_parser.growth,
)
dcf.create_workbook()