mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-31 06:22:18 +00:00
added dcf growth (#2244)
Co-authored-by: Jeroen Bouma <jer.bouma@gmail.com>
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user