mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-30 14:02:06 +00:00
Bankruptcy Scores (#1271)
* Refacotred and expanded analytics * Updated tests * Updated hugo * Updated tests
This commit is contained in:
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
- run: pip install types-pytz types-requests types-termcolor types-tabulate types-PyYAML types-python-dateutil types-setuptools
|
||||
- run: bandit -x ./tests -r . || true
|
||||
- run: black --check .
|
||||
- run: codespell --ignore-words-list=zlot,ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld,shold --quiet-level=2 --skip=./tests,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml
|
||||
- run: codespell --ignore-words-list=zlot,ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld,shold,ist --quiet-level=2 --skip=./tests,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml
|
||||
- run: flake8 . --count --ignore=E203,W503 --max-line-length=122 --show-source --statistics
|
||||
- run: mypy --ignore-missing-imports --exclude="/setup\.py$" .
|
||||
- run: shopt -s globstar && pyupgrade --py36-plus **/*.py
|
||||
|
||||
2
.pre-commit-config.yaml
vendored
2
.pre-commit-config.yaml
vendored
@@ -25,7 +25,7 @@ repos:
|
||||
entry: codespell
|
||||
args:
|
||||
[
|
||||
"--ignore-words-list=zlot,ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld,shold",
|
||||
"--ignore-words-list=zlot,ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld,shold,ist",
|
||||
"--quiet-level=2",
|
||||
"--skip=./tests,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml",
|
||||
]
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
"""Alpha Vantage Model"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
from typing import Dict, Tuple
|
||||
from typing import Dict, Tuple, List
|
||||
|
||||
import requests
|
||||
|
||||
from alpha_vantage.fundamentaldata import FundamentalData
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from gamestonk_terminal.stocks.fundamental_analysis.fa_helper import clean_df_index
|
||||
from gamestonk_terminal.helper_funcs import long_number_format
|
||||
from gamestonk_terminal import config_terminal as cfg
|
||||
@@ -301,7 +302,27 @@ def get_earnings(ticker: str, quarterly: bool = False) -> pd.DataFrame:
|
||||
return pd.DataFrame()
|
||||
|
||||
|
||||
def get_fraud_ratios(ticker: str) -> Tuple[Dict[str, float], float]:
|
||||
def df_values(df: pd.DataFrame, item: str) -> List[int]:
|
||||
"""Clean the values from the df
|
||||
|
||||
Parameters
|
||||
----------
|
||||
df : pd.DataFrame
|
||||
The Dataframe to use
|
||||
item : str
|
||||
The item to select
|
||||
|
||||
Returns
|
||||
-------
|
||||
values : List[int]
|
||||
The values for the dataframe
|
||||
"""
|
||||
selection = df[item]
|
||||
values = selection.apply(lambda x: int(x) if x else 0).values
|
||||
return values.tolist()
|
||||
|
||||
|
||||
def get_fraud_ratios(ticker: str) -> Tuple[Dict[str, float], float, float]:
|
||||
"""Get fraud ratios based on fundamentals
|
||||
|
||||
Parameters
|
||||
@@ -326,42 +347,23 @@ def get_fraud_ratios(ticker: str) -> Tuple[Dict[str, float], float]:
|
||||
df_bs = df_bs.set_index("fiscalDateEnding").iloc[:2]
|
||||
df_is = df_is.set_index("fiscalDateEnding").iloc[:2]
|
||||
|
||||
ar = df_bs["currentNetReceivables"].apply(lambda x: 0 if x else int(x)).values
|
||||
sales = df_is["totalRevenue"].apply(lambda x: 0 if x else int(x)).values
|
||||
cogs = (
|
||||
df_is["costofGoodsAndServicesSold"].apply(lambda x: 0 if x else int(x)).values
|
||||
)
|
||||
ni = df_is["netIncome"].apply(lambda x: 0 if x else int(x)).values
|
||||
ca = df_bs["totalCurrentAssets"].apply(lambda x: 0 if x else int(x)).values
|
||||
cl = df_bs["totalCurrentLiabilities"].apply(lambda x: 0 if x else int(x)).values
|
||||
ppe = df_bs["propertyPlantEquipment"].apply(lambda x: 0 if x else int(x)).values
|
||||
cash = (
|
||||
df_bs["cashAndCashEquivalentsAtCarryingValue"]
|
||||
.apply(lambda x: 0 if x else int(x))
|
||||
.values
|
||||
)
|
||||
cash_and_sec = (
|
||||
df_bs["cashAndShortTermInvestments"].apply(lambda x: 0 if x else int(x)).values
|
||||
)
|
||||
ar = df_values(df_bs, "currentNetReceivables")
|
||||
sales = df_values(df_is, "totalRevenue")
|
||||
cogs = df_values(df_is, "costofGoodsAndServicesSold")
|
||||
ni = df_values(df_is, "netIncome")
|
||||
ca = df_values(df_bs, "totalCurrentAssets")
|
||||
cl = df_values(df_bs, "totalCurrentLiabilities")
|
||||
ppe = df_values(df_bs, "propertyPlantEquipment")
|
||||
cash = df_values(df_bs, "cashAndCashEquivalentsAtCarryingValue")
|
||||
cash_and_sec = df_values(df_bs, "cashAndShortTermInvestments")
|
||||
sec = [y - x for (x, y) in zip(cash, cash_and_sec)]
|
||||
ta = df_bs["totalAssets"].apply(lambda x: 0 if x else int(x)).values
|
||||
dep = (
|
||||
df_bs["accumulatedDepreciationAmortizationPPE"]
|
||||
.apply(lambda x: 0 if x else int(x))
|
||||
.values
|
||||
)
|
||||
sga = (
|
||||
df_is["sellingGeneralAndAdministrative"]
|
||||
.apply(lambda x: 0 if x else int(x))
|
||||
.values
|
||||
)
|
||||
tl = df_bs["totalLiabilities"].apply(lambda x: 0 if x else int(x)).values
|
||||
icfo = (
|
||||
df_is["netIncomeFromContinuingOperations"]
|
||||
.apply(lambda x: 0 if x else int(x))
|
||||
.values
|
||||
)
|
||||
cfo = df_cf["operatingCashflow"].apply(lambda x: 0 if x else int(x)).values
|
||||
ta = df_values(df_bs, "totalAssets")
|
||||
dep = df_values(df_bs, "accumulatedDepreciationAmortizationPPE")
|
||||
sga = df_values(df_is, "sellingGeneralAndAdministrative")
|
||||
tl = df_values(df_bs, "totalLiabilities")
|
||||
icfo = df_values(df_is, "netIncomeFromContinuingOperations")
|
||||
cfo = df_values(df_cf, "operatingCashflow")
|
||||
|
||||
ratios: Dict = {}
|
||||
ratios["DSRI"] = (ar[0] / sales[0]) / (ar[1] / sales[1])
|
||||
ratios["GMI"] = ((sales[1] - cogs[1]) / sales[1]) / (
|
||||
@@ -392,5 +394,13 @@ def get_fraud_ratios(ticker: str) -> Tuple[Dict[str, float], float]:
|
||||
+ (5.679 * (tl[0] / ta[0]))
|
||||
+ (0.004 * (ca[0] / cl[0]))
|
||||
)
|
||||
v1 = np.log(ta[0] / 1000)
|
||||
v2 = ni[0] / ta[0]
|
||||
v3 = cash[0] / cl[0]
|
||||
|
||||
return ratios, zscore
|
||||
x = ((v1 + 0.85) * v2) - 0.85
|
||||
y = 1 + v3
|
||||
|
||||
mckee = x ** 2 / (x ** 2 + y ** 2)
|
||||
|
||||
return ratios, zscore, mckee
|
||||
|
||||
@@ -171,19 +171,18 @@ def display_fraud(ticker: str):
|
||||
ticker : str
|
||||
Fundamental analysis ticker symbol
|
||||
"""
|
||||
ratios, zscore = av_model.get_fraud_ratios(ticker)
|
||||
ratios, zscore, mckee = av_model.get_fraud_ratios(ticker)
|
||||
|
||||
if ratios["MSCORE"] > -1.78:
|
||||
chanceM = "high"
|
||||
chance_m = "high"
|
||||
elif ratios["MSCORE"] > -2.22:
|
||||
chanceM = "moderate"
|
||||
chance_m = "moderate"
|
||||
else:
|
||||
chanceM = "low"
|
||||
chance_m = "low"
|
||||
|
||||
if zscore < 0.5:
|
||||
chanceZ = "high"
|
||||
else:
|
||||
chanceZ = "low"
|
||||
chance_z = "high" if zscore < 0.5 else "low"
|
||||
|
||||
chance_mcke = "low" if mckee < 0.5 else "high"
|
||||
|
||||
if np.isnan(ratios["MSCORE"]) or np.isnan(zscore):
|
||||
console.print("Data incomplete for this ticker. Unable to calculate risk")
|
||||
@@ -196,8 +195,10 @@ def display_fraud(ticker: str):
|
||||
|
||||
console.print(
|
||||
"\n" + "MSCORE: ",
|
||||
f"{ratios['MSCORE']:.2f} ({chanceM} chance of fraud)",
|
||||
f"{ratios['MSCORE']:.2f} ({chance_m} chance of fraud)",
|
||||
)
|
||||
|
||||
console.print("ZSCORE: ", f"{zscore:.2f} ({chanceZ} chance of bankruptcy)", "\n")
|
||||
console.print("ZSCORE: ", f"{zscore:.2f} ({chance_z} chance of bankruptcy)", "\n")
|
||||
|
||||
console.print("McKee: ", f"{mckee:.2f} ({chance_mcke} chance of bankruptcy)", "\n")
|
||||
return
|
||||
|
||||
@@ -721,6 +721,10 @@ Ticker: [/param] {self.ticker} [cmds]
|
||||
" higher probability of default. One of the criticisms that Zmijewski made was that"
|
||||
" other bankruptcy scoring models oversampled distressed firms and favored situations"
|
||||
" with more complete data.[Source: YCharts]"
|
||||
"McKee-score:\n------------------------------------------------\n"
|
||||
"The McKee Score is a bankruptcy model used to predict a firm's bankruptcy in one year"
|
||||
"It looks at a companie's size, profitability, and liquidity to determine the probability."
|
||||
"This model is 80% accurate in predicting bankruptcy."
|
||||
),
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
|
||||
@@ -1 +1 @@
|
||||
[{"DSRI": NaN, "GMI": NaN, "AQI": NaN, "SGI": NaN, "DEPI": NaN, "SGAI": NaN, "LVGI": NaN, "TATA": NaN, "MSCORE": NaN}, NaN]
|
||||
[{"DSRI": 1.1154783925070624, "GMI": 1.0013237094668648, "AQI": 0.7066050530188391, "SGI": 1.2830987061599806, "DEPI": 0.9225617507161853, "SGAI": 0.9263406938399691, "LVGI": 0.713640022276499, "TATA": -0.09743422566541382, "MSCORE": -2.545490667269407}, -1.2961310063630496, 0.05928338445454072]
|
||||
@@ -110,7 +110,7 @@ def test_check_empty_df(func, kwargs_dict, mocked_func, mocker):
|
||||
@pytest.mark.vcr(record_mode="none")
|
||||
@pytest.mark.record_stdout
|
||||
@pytest.mark.parametrize(
|
||||
"ratios, zscore",
|
||||
"ratios, zscore, mckscore",
|
||||
[
|
||||
(
|
||||
{
|
||||
@@ -125,15 +125,16 @@ def test_check_empty_df(func, kwargs_dict, mocked_func, mocker):
|
||||
"MSCORE": np.nan,
|
||||
},
|
||||
np.nan,
|
||||
np.nan,
|
||||
),
|
||||
({"MSCORE": -1}, 0.1),
|
||||
({"AQI": 1, "MSCORE": -2}, 0.1),
|
||||
({"MSCORE": -3}, 1),
|
||||
({"MSCORE": -1}, 0.1, 0.2),
|
||||
({"AQI": 1, "MSCORE": -2}, 0.1, 0.2),
|
||||
({"MSCORE": -3}, 1, 0.2),
|
||||
],
|
||||
)
|
||||
def test_display_fraud(mocker, ratios, zscore):
|
||||
def test_display_fraud(mocker, ratios, zscore, mckscore):
|
||||
mocker.patch(
|
||||
"gamestonk_terminal.stocks.fundamental_analysis.av_view.av_model.get_fraud_ratios",
|
||||
return_value=(ratios, zscore),
|
||||
return_value=(ratios, zscore, mckscore),
|
||||
)
|
||||
av_view.display_fraud(ticker="TSLA")
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Data incomplete for this ticker. Unable to calculate risk
|
||||
@@ -0,0 +1,7 @@
|
||||
Mscore Sub Stats:
|
||||
|
||||
MSCORE: -1.00 (high chance of fraud)
|
||||
ZSCORE: 0.10 (high chance of bankruptcy)
|
||||
|
||||
McKee: 0.20 (low chance of bankruptcy)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
Mscore Sub Stats:
|
||||
AQI : 1.00
|
||||
|
||||
MSCORE: -2.00 (moderate chance of fraud)
|
||||
ZSCORE: 0.10 (high chance of bankruptcy)
|
||||
|
||||
McKee: 0.20 (low chance of bankruptcy)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Mscore Sub Stats:
|
||||
|
||||
MSCORE: -3.00 (low chance of fraud)
|
||||
ZSCORE: 1.00 (low chance of bankruptcy)
|
||||
|
||||
McKee: 0.20 (low chance of bankruptcy)
|
||||
|
||||
@@ -34,6 +34,11 @@ Zscore:
|
||||
------------------------------------------------
|
||||
The Zmijewski Score is a bankruptcy model used to predict a firm's bankruptcy in two years. The ratio uses in the Zmijewski score were determined by probit analysis (think of probit as probability unit). In this case, scores less than .5 represent a higher probability of default. One of the criticisms that Zmijewski made was that other bankruptcy scoring models oversampled distressed firms and favored situations with more complete data.[Source: YCharts]
|
||||
|
||||
McKee Score:
|
||||
------------------------------------------------
|
||||
The McKee Score is a bankruptcy model used to predict a firm's bankruptcy in one year. It looks at a companie's size, profitability, and liquidity to determine the probability. This model is 80% accurate in predicting bankruptcy.
|
||||
[Source: McKee and Lensberg](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.619.594&rep=rep1&type=pdf)
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
-h, --help show this help message
|
||||
|
||||
Reference in New Issue
Block a user