Bankruptcy Scores (#1271)

* Refacotred and expanded analytics

* Updated tests

* Updated hugo

* Updated tests
This commit is contained in:
Colin Delahunty
2022-01-27 11:56:53 -05:00
committed by GitHub
parent f681e5ec32
commit c5393e0875
12 changed files with 101 additions and 57 deletions

View File

@@ -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

View File

@@ -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",
]

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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]

View File

@@ -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")

View File

@@ -0,0 +1 @@
Data incomplete for this ticker. Unable to calculate risk

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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