Skip to main content

A Python package for financial modeling and reporting

Project description

pyproforma

A Python library for building financial models — a code-first alternative to Excel for pro formas, projections, and structured financial tables.

pip install pyproforma

Why

Spreadsheets are the default tool for financial modeling, but they have real problems: no version control, no testing, formulas hidden inside cells, and no easy way to generate the same model for multiple scenarios. pyproforma is designed for analysts who want the benefits of code — reproducibility, testability, version history — without giving up the tabular output that finance people actually use.


How it works

Define a model by subclassing ProformaModel and declaring line items as class attributes. Instantiate it with a list of periods and the library calculates everything.

from pyproforma import ProformaModel, FixedLine, FormulaLine, Assumption

class IncomeStatement(ProformaModel):
    growth_rate = Assumption(value=0.10)

    revenue = FixedLine(
        values={2024: 500_000, 2025: 550_000, 2026: 605_000},
        label="Revenue",
        tags=["operating"],
    )
    cogs = FormulaLine(
        formula=lambda li, t: li.revenue[t] * 0.55,
        label="Cost of Goods Sold",
        tags=["operating"],
    )
    gross_profit = FormulaLine(
        formula=lambda li, t: li.revenue[t] - li.cogs[t],
        label="Gross Profit",
    )
    tax_expense = FormulaLine(
        formula=lambda li, t: li.gross_profit[t] * 0.21,
        label="Tax Expense",
    )
    net_income = FormulaLine(
        formula=lambda li, t: li.gross_profit[t] - li.tax_expense[t],
        label="Net Income",
    )

model = IncomeStatement(periods=[2024, 2025, 2026])

Access results directly:

model["net_income"][2025]   # 218_130.0
model["gross_profit"][2024] # 225_000.0

Tables

Generate formatted tables for display or export. Tables are the primary output — they render to HTML (for Jupyter notebooks), Excel, or a pandas DataFrame.

# All line items
model.tables.line_items().show()

# Single item with period-over-period analysis
model.tables.line_item("net_income", include_percent_change=True).show()

# Show what feeds into a calculated line item
model.tables.precedents("net_income").show()

# Custom layout using a template
from pyproforma import Format
from pyproforma.tables import HeaderRow, LabelRow, ItemRow

table = model.tables.from_template([
    HeaderRow(background_color="lightblue"),
    LabelRow(label="Income Statement"),
    ItemRow(name="revenue", value_format=Format.THOUSANDS_K),
    ItemRow(name="cogs",    value_format=Format.THOUSANDS_K, bottom_border="single"),
    ItemRow(name="gross_profit", bold=True, value_format=Format.THOUSANDS_K),
])
table.show()

Example table

Export to Excel with formatting preserved:

model.tables.line_items().to_excel("income_statement.xlsx")

Scenarios and comparisons

A model is just a class — create multiple instances with different inputs to compare scenarios.

class FlexModel(ProformaModel):
    cogs_rate = Assumption(value=0.55)
    revenue = FixedLine(values={2024: 500_000, 2025: 550_000})
    cogs = FormulaLine(formula=lambda li, t: li.revenue[t] * li.cogs_rate)
    gross_profit = FormulaLine(formula=lambda li, t: li.revenue[t] - li.cogs[t])

base = FlexModel(periods=[2024, 2025])
# InputAssumption lets callers override at instantiation time — coming soon

Use ModelComparison to diff two model instances:

from pyproforma import ModelComparison

# (requires two separately instantiated models with different inputs)
comparison = ModelComparison(base, upside)
comparison.diff("gross_profit")

Time-series formulas

Formulas receive the full model namespace and the current period t. Reference prior periods with t-1:

# Compound growth from a seeded base year
revenue = FormulaLine(
    formula=lambda li, t: li.revenue[t-1] * (1 + li.growth_rate),
    values={2024: 500_000},  # seed value for first period
    label="Revenue",
)

Tags

Tag line items to group them flexibly without fixed categories:

revenue = FixedLine(values={...}, tags=["operating", "top_line"])
other_income = FixedLine(values={...}, tags=["operating"])

# Sum all items tagged "operating" in a formula
ebit = FormulaLine(formula=lambda li, t: li.tag["operating"][t])

# Or in a table
from pyproforma.tables import TagTotalRow
table = model.tables.from_template([
    HeaderRow(),
    ItemRow(name="revenue"),
    ItemRow(name="other_income"),
    TagTotalRow(tag="operating", label="Total Operating"),
])

Number formatting

A Format class provides named format constants that flow through to both HTML and Excel output:

from pyproforma import Format

ItemRow(name="revenue",    value_format=Format.THOUSANDS_K)   # 500K
ItemRow(name="margin",     value_format=Format.PERCENT_ONE_DECIMAL)  # 45.0%
ItemRow(name="net_income", value_format=Format.CURRENCY_NO_DECIMALS) # $218,130

Custom formats via NumberFormatSpec:

from pyproforma import NumberFormatSpec

fmt = NumberFormatSpec(decimals=1, scale="millions", suffix="M")
# 500_000 → "0.5M"

Installation

pip install pyproforma

Requires Python 3.9+. Dependencies: pandas, openpyxl.


Status

This project is in active development. The core modeling and table export features are stable. Charts, extended documentation, and additional row types are on the roadmap. Feedback welcome — open an issue or reach out directly.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyproforma-0.2.0.tar.gz (55.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyproforma-0.2.0-py3-none-any.whl (66.8 kB view details)

Uploaded Python 3

File details

Details for the file pyproforma-0.2.0.tar.gz.

File metadata

  • Download URL: pyproforma-0.2.0.tar.gz
  • Upload date:
  • Size: 55.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for pyproforma-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2324fb4c287a31a9ce9859d99f84c821064b524b1aa8c126ee0287e03a6fc2a2
MD5 0554108c19255cf75fe3b5f124e7736d
BLAKE2b-256 4d911bc43ec59a96e35d7d04946caa7a4a904d72ee4e7a021e684cf461a9c127

See more details on using hashes here.

File details

Details for the file pyproforma-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyproforma-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 66.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for pyproforma-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bf635aed0a0fef871152a941df4a205ad89fad125bd3acc55437e4b4f7d76f4b
MD5 c8ad9e2a8b6f1287bc61e96cd25d016c
BLAKE2b-256 ebd29bba8f7bd611f876f05f37fc68d1da46f741d056db62704738e3a2e69900

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page