Build a reusable financial ratio module with Python
Put twenty financial ratio calculations into a reusable Python module with explicit input definitions and error handling. Test the functions, distinguish percentages from multiples, and understand which accounting conventions changed from the original tutorial.

Before you start
Python required
A tested reusable module and explicit conventions for its twenty ratios.
- Basic Python functions and imports
- Ability to identify financial-statement inputs
- Python 3.12.14 tested runtime — access: free
- Code editor — access: varies
A repeated calculation deserves one definition you can inspect and test. In this optional Python exercise, you will put twenty ratios into a module and call them from another script. AI can help explain the code, but the formulas and accounting inputs need your review.
Originally published 12 August 2020; legacy WordPress last modification was 20 July 2023. Refresh draft prepared 10 October 2026; the replacement module’s tests ran on that date. Neither a CMS modification date nor this test date replaces the original publication date.
You need basic Python functions/imports and familiarity with the relevant financial-statement inputs. The module uses only Python’s standard library. Python 3.12.14 was tested; hands-on completion time remains unmeasured. The related liquidity script has separate external-library requirements.
1. Understand what the module does
A Python module is a file containing reusable code; see the official Python modules tutorial. Save this file as financialratios.py beside the calling script so import financialratios as fr can find it.
The historical Financial Ratios Module repository remains available as an original reference. Use the refreshed local learning kit with this guide. Revision 2.0 changes names and input conventions intentionally; do not replace historical code without reviewing its callers.
2. Choose the financial definitions first
Use matching periods, currencies, and monetary scales. For example, a numerator in millions divided by equity in thousands produces the wrong ratio. For per-share results, monetary amounts and share counts must have matching scales. Returns and turnovers combine period flows with average balances, usually opening plus closing divided by two for this exercise; a more representative weighted average may be appropriate in real analysis.
| Function suffix after get_ | Numerator / denominator convention | Output |
|---|---|---|
| current_ratio | Current assets / current liabilities | Times |
| quick_ratio | (Current assets − inventory − optional other nonquick assets) / current liabilities | Times |
| cash_ratio | (Cash + separately supplied equivalents) / current liabilities | Times |
| operating_cashflow_ratio | Period operating cash flow / period-end current liabilities | Times |
| debt_to_equity_ratio | Interest-bearing debt / equity | Times |
| debt_to_total_assets_ratio | Interest-bearing debt / assets | Fraction |
| interest_coverage_ratio | EBIT / same-period interest expense | Times |
| debt_service_coverage_ratio | Defined cash available / same-period debt service | Times |
| asset_turnover_ratio | Net sales / average total assets | Times |
| inventory_turnover_ratio | Cost of goods sold / average inventory | Times |
| receivable_turnover_ratio | Net credit sales / average receivables | Times |
| day_sale_inventory_ratio | Chosen days × average inventory / cost of goods sold | Days |
| gross_margin_ratio | Gross profit / net sales | Fraction |
| operating_margin_ratio | Operating income / net sales | Fraction |
| return_on_asset_ratio | Net income / average total assets | Fraction |
| return_on_equity_ratio | Common-available net income / average common equity | Fraction |
| bookvalue_per_share_ratio | Common equity / period-end common shares | Currency/share |
| dividend_yield_ratio | Annual dividend/share / share price | Fraction |
| earning_per_share_ratio | Common-available net income / weighted-average common shares | Currency/share |
| price_earning_ratio | Share price / positive EPS | Times |
This table is the module’s explicit educational contract. The CFI ratio overview supplies general background; definitions vary by analytical purpose. Debt-service coverage is not automatically a covenant calculation: use the agreement’s cash-available and debt-service definitions. Simple EPS here is not diluted EPS.
Important changes from the original tutorial:
- Debt measures take interest-bearing debt. Total liabilities include other obligations and are not silently substituted.
- Turnover and returns use average balances rather than automatically accepting year-end balances.
- EPS uses common-available earnings and weighted-average shares; book value uses period-end shares.
- Asset, inventory, and receivable turnover function names correct
turnonvertoturnover. Book value removes the historical double underscore. No silent aliases are supplied. - The quick-ratio default preserves inventory-only exclusion for the historical exercise; optionally provide documented other nonquick assets.
3. Save the complete module
"""Scalar educational ratios, revision 2.0. See README for conventions.
Returns fractions (0.25 means 25%) without rounding. Inputs must be finite
real numbers; denominators must be positive. Invalid inputs raise ValueError.
This revision intentionally clarifies definitions and is not drop-in compatible
with all names/semantics in the historical repositories.
"""
from math import isfinite
from numbers import Real
def _number(value):
if isinstance(value, bool) or not isinstance(value, Real) or not isfinite(value):
raise ValueError("Supply a finite numeric value, not missing/text/bool.")
return float(value)
def _divide(numerator, denominator):
numerator, denominator = _number(numerator), _number(denominator)
if denominator <= 0:
raise ValueError("Denominator must be positive; review the ratio's meaning.")
result = numerator / denominator
if not isfinite(result):
raise ValueError("Result is not finite; review input scale.")
return result
def get_current_ratio(current_assets, current_liabilities):
return _divide(current_assets, current_liabilities)
def get_quick_ratio(current_assets, inventories, current_liabilities, other_nonquick_assets=0):
"""Legacy convention excludes inventory; optionally exclude other nonquick assets."""
return _divide(_number(current_assets) - _number(inventories) - _number(other_nonquick_assets), current_liabilities)
def get_cash_ratio(cash, current_liabilities, cash_equivalent=0):
"""Pass combined cash/equivalents as cash, leaving optional argument zero."""
return _divide(_number(cash) + _number(cash_equivalent), current_liabilities)
def get_operating_cashflow_ratio(operating_cashflow, current_liabilities):
return _divide(operating_cashflow, current_liabilities)
def get_debt_to_equity_ratio(interest_bearing_debt, shareholders_equity):
return _divide(interest_bearing_debt, shareholders_equity)
def get_debt_to_total_assets_ratio(interest_bearing_debt, total_assets):
return _divide(interest_bearing_debt, total_assets)
def get_interest_coverage_ratio(ebit, interest_expense):
return _divide(ebit, interest_expense)
def get_debt_service_coverage_ratio(cash_available_for_debt_service, total_debt_service):
"""Use the same-period cash numerator defined by the applicable agreement."""
return _divide(cash_available_for_debt_service, total_debt_service)
def get_asset_turnover_ratio(net_sales, average_total_assets):
return _divide(net_sales, average_total_assets)
def get_inventory_turnover_ratio(cost_of_goods_sold, average_inventory):
return _divide(cost_of_goods_sold, average_inventory)
def get_receivable_turnover_ratio(net_credit_sales, average_receivables):
return _divide(net_credit_sales, average_receivables)
def get_day_sale_inventory_ratio(cost_of_goods_sold, average_inventory, days=365):
"""days is an explicitly chosen analysis convention, not a calendar inference."""
return _divide(_number(days) * _number(average_inventory), cost_of_goods_sold)
def get_gross_margin_ratio(gross_profit, net_sales):
return _divide(gross_profit, net_sales)
def get_operating_margin_ratio(operating_income, net_sales):
return _divide(operating_income, net_sales)
def get_return_on_asset_ratio(net_income, average_total_assets):
return _divide(net_income, average_total_assets)
def get_return_on_equity_ratio(net_income_available_to_common, average_common_equity):
return _divide(net_income_available_to_common, average_common_equity)
def get_bookvalue_per_share_ratio(common_equity, period_end_common_shares):
return _divide(common_equity, period_end_common_shares)
def get_dividend_yield_ratio(annual_dividend_per_share, share_price):
return _divide(annual_dividend_per_share, share_price)
def get_earning_per_share_ratio(net_income_available_to_common, weighted_average_common_shares):
"""Simple basic EPS exercise, not a full diluted EPS calculation."""
return _divide(net_income_available_to_common, weighted_average_common_shares)
def get_price_earning_ratio(share_price, earning_per_share):
"""Nonpositive EPS is unavailable under this module's positive-denominator policy."""
return _divide(share_price, earning_per_share)
The shared helper checks finite numeric inputs and a positive denominator. Missing values, text, booleans, nonfinite values, zero denominators, and negative denominators raise ValueError. This avoids presenting an unavailable ratio as zero. A valid zero numerator returns zero, and negative profit numerators remain negative. The functions accept individual values, not pandas Series; validate account relationships before applying them to each row.
Fraction outputs are not multiplied by 100 inside the functions. Format them as percentages only when presenting them. Days-in-inventory uses an explicit 365-day analysis default, not an inferred calendar length.
4. Call and check the functions
Save as example.py beside the module:
import financialratios as fr
print(f"Current ratio: {fr.get_current_ratio(400, 200):.2f}")
print(f"Quick ratio: {fr.get_quick_ratio(400, 80, 200):.2f}")
print(f"Gross margin: {fr.get_gross_margin_ratio(420, 1200):.1%}")
print(f"Basic EPS: {fr.get_earning_per_share_ratio(100, 50):.2f}")
try:
fr.get_current_ratio(400, 0)
except ValueError as exc:
print(f"Unavailable: {exc}")
Expected numeric results are 2.00, 1.60, 35.0%, and 2.00 currency units/share. These are synthetic examples, not Apple values. The final call should report unavailability instead of a numeric result. Run python example.py from the folder, then python -m unittest -v with the supplied test file.
The tests cover twenty independent examples, invalid denominators for every ratio, missing first inputs, valid zero/loss numerators, and optional conventions. They check implementation against the stated formulas; they cannot confirm that a caller chose the right accounts or that the formulas match a lending agreement.
5. Optional AI-assisted code review
Explain these financial-ratio functions to a business analyst learning Python.
[Paste the specific function, its helper, and its definition-table row.]
Separate programming behavior from accounting assumptions. Identify the required
period, units, denominator restrictions, and one hand-checkable example.
Do not change the formula or invent financial inputs. Suggest questions where
the intended accounting convention is unclear. Do not execute or modify files.
Review the answer against the actual code. An explanation cannot certify a formula. If AI suggests returning zero for a division error, preserve the unavailable state and investigate the inputs instead.
Reuse and troubleshoot
If import fails, check the filename and folder; avoid naming your calling script financialratios.py. If a negative-equity or nonpositive-EPS case fails, that is the module’s explicit review policy, not proof that the company has no ratio under any convention. Select and document an appropriate alternative before changing the helper.
For practice, change gross profit from 420 to 360 while keeping sales at 1,200: margin becomes 30.0%. Use the module in the historical liquidity guide to see how individual functions form a complete data workflow. For a no-code alternative, return to the financial-statement guide.
Practice resource
Legacy Python finance learning kit
Reproduce the refreshed Python finance exercises with a twenty-function ratio module, tests, a historical Apple dataset, and an export-and-chart script. The README explains setup, formula changes, provenance, and the limits of the historical example.
You may copy and adapt this original resource for learning and internal business work. Redistribution or resale requires permission from Not A Programmer.