Exercise · m06_forensics.py
Forensic and quality scores: Beneish M-score, Piotroski F-score, Coffee Can, cash checks
not run yet
Not saved: sign up to keep your code"""M06 · Forensic and quality scores: Beneish M-score, Piotroski F-score, Coffee Can, cash checks.
Exercise stubs. See notes.md §6.8–§6.9 for every definition.
"""
from __future__ import annotations
BENEISH_COEF = {
"const": -4.84, "DSRI": 0.920, "GMI": 0.528, "AQI": 0.404, "SGI": 0.892,
"DEPI": 0.115, "SGAI": -0.172, "TATA": 4.679, "LVGI": -0.327,
}
def beneish_components(cur: dict, prev: dict) -> dict:
"""Return the eight Beneish (1999) indices as dict(DSRI, GMI, AQI, SGI, DEPI, SGAI, TATA, LVGI).
cur = year t, prev = year t-1. Keys in both dicts: sales, cogs, receivables,
current_assets, ppe, securities, total_assets, depreciation, sga, net_income, cfo,
current_liabilities, lt_debt.
DSRI = (Rec_t/Sales_t) / (Rec_{t-1}/Sales_{t-1})
GMI = GM_{t-1} / GM_t, GM = (Sales - COGS)/Sales
AQI = AQ_t / AQ_{t-1}, AQ = 1 - (CA + PPE + Securities)/TA
SGI = Sales_t / Sales_{t-1}
DEPI = DR_{t-1} / DR_t, DR = Dep/(Dep + PPE)
SGAI = (SGA_t/Sales_t) / (SGA_{t-1}/Sales_{t-1})
TATA = (NI_t - CFO_t) / TA_t
LVGI = LEV_t / LEV_{t-1}, LEV = (CL + LTD)/TA
"""
raise NotImplementedError
def beneish_m(cur: dict, prev: dict):
"""Return (M, components) with M = const + sum_k BENEISH_COEF[k] * component_k.
Reference fixture in the tests gives M = -1.1197.
"""
raise NotImplementedError
def beneish_flag(m: float, threshold: float = -1.78) -> bool:
"""True if m > threshold (likely manipulator)."""
raise NotImplementedError
def piotroski_signals(cur: dict, prev: dict) -> dict:
"""The nine binary Piotroski (2000) signals as a dict of bools with keys
F_ROA, F_CFO, F_dROA, F_ACCRUAL, F_dLEVER, F_dLIQUID, F_EQ_OFFER, F_dMARGIN, F_dTURN.
Keys in cur/prev: net_income, cfo, total_assets, total_assets_begin, lt_debt,
current_assets, current_liabilities, shares_out, sales, cogs.
ROA = NI / TA_begin; CFO scaled by TA_begin; asset turnover = Sales / TA_begin;
leverage = LTD / TA (end of year); current ratio = CA/CL; GM = (Sales-COGS)/Sales.
F_EQ_OFFER is True when shares_out did not increase. Changes are strict (>, <).
"""
raise NotImplementedError
def piotroski_f(cur: dict, prev: dict) -> int:
"""F-score = number of True signals (0..9). Reference fixture: 9; swapped years: 4."""
raise NotImplementedError
def coffee_can_pass(rev, roce, min_g: float = 0.10, min_roce: float = 0.15) -> bool:
"""True iff revenue growth >= min_g in EVERY one of the 10 years AND ROCE >= min_roce every year.
rev: 11 annual revenues, oldest first. roce: 10 annual ROCE values.
"""
raise NotImplementedError
def cfo_to_pat(cfo, pat) -> float:
"""Cumulative cash conversion sum(CFO)/sum(PAT)."""
raise NotImplementedError
def implied_cash_yield(interest_income: float, cash_begin: float, cash_end: float) -> float:
"""Interest income divided by average of opening and closing cash + liquid investments."""
raise NotImplementedError
No runs yet
The stub's functions raise
NotImplementedError until you write them. Fill in one, press
Run tests, and watch its tests turn green.Reference solution
Available after your first run, with an account approved for code runs.