The M-score was created by Professor Messod Beneish. It uses financial ratios and eight variables to identify whether a company has manipulated its earnings. The eight variables are:
Once calculated, the eight variables are combined together to achieve an M-Score for the company. An M-Score of less than -1.78 suggests that the company will not be a manipulator. An M-Score of greater than -2.22 signals that the company is likely to be a manipulator.
Link to the Paper: http://papers.ssrn.com/sol3/papers.cfm?abstract_id=1006840
Cloned from: https://www.quantopian.com/posts/beneish-model-probability-of-accounting-manipulation Added windsorization per: https://www.quantopian.com/posts/beneish-model-probability-of-accounting-manipulation#56198c78ff81b2ffbc000086
# Initialisation
import scipy as sp
import pandas as pd
import numpy as np
import datetime
from collections import OrderedDict
fundamentals = init_fundamentals()
def three_month_ago(t):
m = t.month - 3
y = t.year
if m < 1:
m += 12
y -= 1
return datetime.date(y, m, 1)
#today = datetime.datetime.now()
today = datetime.date(2015, 8, 15) # set the date to compare to the previous notebook
lag = 0
n = 8
tt = []
tt.append(datetime.date(today.year - lag, today.month, 1))
for i in range(1, n):
tt.append(three_month_ago(tt[i-1]))
for t in tt:
print t
def fund_df(t):
return get_fundamentals(
query(
fundamentals.valuation.enterprise_value,
fundamentals.cash_flow_statement.free_cash_flow,
fundamentals.cash_flow_statement.operating_cash_flow,
fundamentals.income_statement.net_income,
fundamentals.valuation.market_cap,
fundamentals.valuation.shares_outstanding,
fundamentals.cash_flow_statement.financing_cash_flow,
fundamentals.income_statement.operating_income,
fundamentals.balance_sheet.invested_capital,
fundamentals.balance_sheet.cash_and_cash_equivalents,
# Rations for M-Score
fundamentals.operation_ratios.days_in_sales,
fundamentals.operation_ratios.gross_margin,
fundamentals.balance_sheet.current_assets,
fundamentals.balance_sheet.long_term_debt,
fundamentals.balance_sheet.current_liabilities,
fundamentals.balance_sheet.net_ppe,
fundamentals.balance_sheet.total_assets,
fundamentals.income_statement.total_revenue,
fundamentals.cash_flow_statement.depreciation_amortization_depletion,
fundamentals.income_statement.selling_general_and_administration,
fundamentals.balance_sheet.total_debt,
fundamentals.balance_sheet.total_liabilities,
fundamentals.income_statement.non_operating_income
)
# No Financials (103), Real Estate (104), Utilities (207) and ADR
.filter(fundamentals.company_reference.industry_template_code != 'B')
.filter(fundamentals.company_reference.industry_template_code != 'I')
.filter(fundamentals.company_reference.industry_template_code != 'F')
.filter(fundamentals.asset_classification.morningstar_sector_code != 103)
.filter(fundamentals.asset_classification.morningstar_sector_code != 104)
.filter(fundamentals.asset_classification.morningstar_sector_code != 207)
.filter(fundamentals.share_class_reference.is_depositary_receipt == False)
.filter(fundamentals.share_class_reference.is_primary_share == True)
# Only pick active common stocks
.filter(fundamentals.share_class_reference.share_class_status == "A")
.filter(fundamentals.share_class_reference.security_type == "ST00000001")
# Exclude When Distributed(WD), When Issued(WI) and VJ - usuallly companies in bankruptcy
.filter(~fundamentals.share_class_reference.symbol.like('%\_WI'))
.filter(~fundamentals.share_class_reference.symbol.like('%\_WD'))
.filter(~fundamentals.share_class_reference.symbol.like('%\_VJ'))
# Exclude Halted stocks
.filter(~fundamentals.share_class_reference.symbol.like('%\_V'))
.filter(~fundamentals.share_class_reference.symbol.like('%\_H'))
# Only NYSE, AMEX and Nasdaq
.filter(fundamentals.company_reference.primary_exchange_id.in_(['NYSE', 'NAS', 'AMEX']))
# Sanity check
# TODO better None or > 0 ?
.filter(fundamentals.valuation.market_cap > 0)
.filter(fundamentals.valuation.shares_outstanding > 0)
.filter(fundamentals.balance_sheet.invested_capital > 0)
.filter(fundamentals.balance_sheet.cash_and_cash_equivalents > 0)
.filter(fundamentals.balance_sheet.current_assets > 0)
#.filter(fundamentals.balance_sheet.current_assets is not None)
.filter(fundamentals.balance_sheet.total_assets > 0)
#.filter(fundamentals.balance_sheet.total_assets is not None)
.filter(fundamentals.cash_flow_statement.free_cash_flow is not None)
.filter(fundamentals.valuation.enterprise_value > 0),
t)
fund_dff = []
for i in range(0, n):
fund_dff.append(fund_df(tt[i]))
fundamental_dict = OrderedDict()
for i in reversed(range(0, n)):
qtr = "%d-%02d" % (tt[i].year, tt[i].month)
fundamental_dict[qtr] = fund_dff[i]
f = fund_dff[0]
fundamental_data = pd.Panel(fundamental_dict)
items = fundamental_data.items
items
# The most recent 4 quarters (TTM)
ttm = items[-4:]
ttm_data = fundamental_data.ix[ttm].sum(axis=0)
# Previous year TTM data
py_ttm = items[-8:-4]
py_ttm_data = fundamental_data.ix[py_ttm].sum(axis=0)
# One year ago data, previous year quarter (PYQ)
pyq = items[-5]
pyq_data = fundamental_data.ix[pyq]
def replaceInf(x):
x = x.replace(np.inf, np.nan)
x = x.replace(-np.inf, np.nan)
return x
# 1. DSRI = Days Sales in Receivables Index = (Receivables_t / Revenue_t) / (Receivables_t-1 / Revenue_t-1)
dsri = f.loc['days_in_sales'].fillna(0) / pyq_data.loc['days_in_sales']
dsri = replaceInf(dsri)
dsri.name = 'dsri'
f = f.append(dsri)
f.loc['dsri'] = sp.stats.mstats.winsorize(f.loc['dsri'], limits=[.01,.01], inplace=True)
f.loc['dsri']
# 2. GMI = Gross Margin Index = GrossMargin_t-1 / GrossMargin_t
gmi = pyq_data.loc['gross_margin'].fillna(0) / f.loc['gross_margin']
gmi = replaceInf(gmi)
gmi.name = 'gmi'
f = f.append(gmi)
f.loc['gmi'] = sp.stats.mstats.winsorize(f.loc['gmi'], limits=[.01,.01], inplace=True)
f.loc['gmi']
# 3. AQI = Asset Quality Index
# AQI = (1 - (CurrentAssets_t + PPE_t) / TotalAssets_t) / (1 - (CurrentAssets_t-1 + PPE_t-1) / TotalAssets_t-1)
aqi_num = 1.0 - (f.loc['current_assets'].fillna(0) + f.loc['net_ppe'].fillna(0)) / f.loc['total_assets']
aqi_den = 1.0 - (pyq_data.loc['current_assets'].fillna(0) + pyq_data.loc['net_ppe'].fillna(0)) / pyq_data.loc['total_assets']
aqi = aqi_num / aqi_den
aqi = replaceInf(aqi)
aqi.name = 'aqi'
f = f.append(aqi)
f.loc['aqi'] = sp.stats.mstats.winsorize(f.loc['aqi'], limits=[.01,.01], inplace=True)
f.loc['aqi']
# 4. SGI = Sales Growth Index = Sales_t / Sales_t-1
sgi = ttm_data.loc['total_revenue'].fillna(0) / py_ttm_data.loc['total_revenue']
sgi = replaceInf(sgi)
sgi.name = 'sgi'
f = f.append(sgi)
f.loc['sgi'] = sp.stats.mstats.winsorize(f.loc['sgi'], limits=[.01,.01], inplace=True)
f.loc['sgi']
# 5. DEPI = Depreciation Index
# DEPI = (Depreciation_t-1 / (Depreciaton_t-1 + PPE_t-1)) / (Depreciation_t / (Depreciaton_t + PPE_t))
depr_py = py_ttm_data.loc['depreciation_amortization_depletion'].fillna(0)
depr_ttm = ttm_data.loc['depreciation_amortization_depletion'].fillna(0)
depi_num = depr_py / (depr_py + pyq_data.loc['net_ppe'].fillna(0))
depi_den = depr_ttm / (depr_ttm + f.loc['net_ppe'].fillna(0))
depi = depi_num / (depi_den)
depi = replaceInf(depi)
depi.name = 'depi'
f = f.append(depi)
f.loc['depi'] = sp.stats.mstats.winsorize(f.loc['depi'], limits=[.01,.01], inplace=True)
f.loc['depi']
# 6. SGAI = Sales, General and Administrative expenses Index
# SGAI = (SGA_t / Sales_t) / (SGA_t-1 /Sales_t-1)
sgai = (ttm_data.loc['selling_general_and_administration'].fillna(0) / ttm_data.loc['total_revenue']) / (
py_ttm_data.loc['selling_general_and_administration'].fillna(0) / py_ttm_data.loc['total_revenue'])
sgai = replaceInf(sgai)
sgai.name = 'sgai'
f = f.append(sgai)
f.loc['sgai'] = sp.stats.mstats.winsorize(f.loc['sgai'], limits=[.01,.01], inplace=True)
f.loc['sgai']
# 7. LVGI = Leverage Index
# LVGI = (TotalDebt_t / TotalAssets_t) / (TotalDebt_t-1 / TotalAssets_t-1)
lvgi_num = (f.loc['long_term_debt'].fillna(0) + f.loc['current_liabilities'].fillna(0))/ f.loc['total_assets']
lvgi_den = (pyq_data.loc['long_term_debt'].fillna(0) + pyq_data.loc['current_liabilities'].fillna(0)) / pyq_data.loc['total_assets']
lvgi = (lvgi_num) / (lvgi_den)
lvgi = replaceInf(lvgi)
lvgi.name = 'lvgi'
f = f.append(lvgi)
f.loc['lvgi'] = sp.stats.mstats.winsorize(f.loc['lvgi'], limits=[.01,.01], inplace=True)
f.loc['lvgi']
# 8. TATA = Total Accruals to Total Assets
# TATA = (NetIncome_t - NonOperatingIncome_t - CashFlowsfromOperations_t) / TotalAssets_t
tata = (ttm_data.loc['net_income'].fillna(0) - ttm_data.loc['non_operating_income'].fillna(0) - ttm_data.loc[
'operating_cash_flow'].fillna(0)) / f.loc['total_assets']
tata = replaceInf(tata)
tata.name = 'tata'
f = f.append(tata)
f.loc['tata'] = sp.stats.mstats.winsorize(f.loc['tata'], limits=[.01,.01], inplace=True)
f.loc['tata']
m_score = -4.84 + 0.92 * f.loc['dsri'] + 0.528 * f.loc['gmi'] + 0.404 * f.loc['aqi'].fillna(
0) + 0.892 * f.loc['sgi'] + 0.115 * f.loc['depi'] - 0.172 * f.loc['sgai'].fillna(
0) + 4.679 * f.loc['tata'] - 0.327 * f.loc['lvgi']
# An M-Score of greater than -2.22 signals that the company is likely an accounting manipulator.
manipulators = m_score[m_score > -2.22].order(ascending=False)
for equity in manipulators.index:
print "%-5s %-40s %8.2f" % (equity.symbol, equity.asset_name, m_score[equity])