<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Feasibity.pro]]></title><description><![CDATA[Feasibity.pro]]></description><link>https://feasibilitypr.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 24 Oct 2026 02:28:14 GMT</lastBuildDate><atom:link href="https://feasibilitypr.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a Real Estate Development Cash Flow Engine With Python and Pandas]]></title><description><![CDATA[Build a monthly real estate development cash flow engine with Python and pandas. Model construction costs, sales receipts, cumulative cash flow, and peak…]]></description><link>https://feasibilitypr.hashnode.dev/building-a-real-estate-development-cash-flow-engine-with-python-and-pandas</link><guid isPermaLink="true">https://feasibilitypr.hashnode.dev/building-a-real-estate-development-cash-flow-engine-with-python-and-pandas</guid><category><![CDATA[Python]]></category><category><![CDATA[pandas]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Feasibilitypro]]></dc:creator><pubDate>Mon, 19 Oct 2026 08:43:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aa795eec12a54c029810fa4/86047832-9fd9-4b93-bc87-81f6ae1b678e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A financial model often starts with a spreadsheet containing construction budgets, sales assumptions, payment dates, and project costs. As the model grows, maintaining these assumptions across multiple sheets becomes difficult. Calculations become harder to reproduce, scenarios require manual changes, and it becomes less obvious where a particular output originated.</p>
<p>Python and pandas offer another approach: represent the project as structured data, align every financial event to a timeline, and calculate the resulting cash flows programmatically.</p>
<p>In this tutorial, we'll build a simple monthly cash flow engine for a real estate development project. It will distribute construction costs across a project timeline, record sales receipts in their expected months, calculate net and cumulative cash flow, and estimate the project's peak funding requirement.</p>
<p>The example is intentionally focused on the data model and calculation logic. It does not attempt to implement a complete development appraisal, financing model, or investment-return engine.</p>
<h2>1. Define the project assumptions</h2>
<p>Assume a development project has a construction budget of $1.2 million and a 12-month construction program. Costs are incurred at different rates throughout the project rather than being distributed evenly.</p>
<p>The project is also expected to receive three sales payments during the timeline:</p>
<table>
<thead>
<tr>
<th>Month</th>
<th>Construction cost allocation</th>
<th>Expected sales receipts</th>
</tr>
</thead>
<tbody><tr>
<td>January</td>
<td>5%</td>
<td>$0</td>
</tr>
<tr>
<td>February</td>
<td>5%</td>
<td>$0</td>
</tr>
<tr>
<td>March</td>
<td>7%</td>
<td>$0</td>
</tr>
<tr>
<td>April</td>
<td>8%</td>
<td>$0</td>
</tr>
<tr>
<td>May</td>
<td>10%</td>
<td>$0</td>
</tr>
<tr>
<td>June</td>
<td>12%</td>
<td>$0</td>
</tr>
<tr>
<td>July</td>
<td>12%</td>
<td>$0</td>
</tr>
<tr>
<td>August</td>
<td>10%</td>
<td>$0</td>
</tr>
<tr>
<td>September</td>
<td>10%</td>
<td>$450,000</td>
</tr>
<tr>
<td>October</td>
<td>8%</td>
<td>$0</td>
</tr>
<tr>
<td>November</td>
<td>7%</td>
<td>$500,000</td>
</tr>
<tr>
<td>December</td>
<td>6%</td>
<td>$600,000</td>
</tr>
</tbody></table>
<p>The construction allocations add up to 100% of the budget. The expected sales receipts total $1.55 million.</p>
<p>These are illustrative assumptions, not market estimates. For this first version, we'll exclude land acquisition, taxes, financing costs, operating expenses, and other cash flows. Those items can be added as separate components later.</p>
<p>The objective is to convert these assumptions into a monthly schedule that the application can calculate and inspect consistently.</p>
<h2>2. Set up the Python environment</h2>
<p>Install pandas if it is not already available:</p>
<pre><code class="language-bash">python -m pip install pandas
</code></pre>
<p>Create a Python file named <code>cash_flow_engine.py</code>.</p>
<p>We'll use pandas to manage the timeline and combine the construction and sales schedules. Python's standard library will handle input validation and numerical checks.</p>
<pre><code class="language-python">from math import isclose

import pandas as pd
</code></pre>
<p>Using a date-based index is preferable to relying on labels such as <code>Month 1</code>, <code>Month 2</code>, and <code>Month 3</code>. Actual month-start dates make it easier to combine schedules, filter periods, and eventually connect the model to other time-based data.</p>
<h2>3. Generate the project timeline</h2>
<p>The first component creates the monthly periods for the development.</p>
<pre><code class="language-python">def create_timeline(start_month, periods):
    if periods &lt;= 0:
        raise ValueError("periods must be positive")

    start = pd.Timestamp(start_month)

    # Normalize the start date to the first day of its month.
    start = start.to_period("M").to_timestamp()

    return pd.date_range(
        start=start,
        periods=periods,
        freq="MS",
    )
</code></pre>
<p>The <code>MS</code> frequency represents month start. If the supplied start date is February 15, for example, the timeline begins on February 1.</p>
<p>Normalizing dates at the beginning prevents mismatches when one schedule uses the first day of a month and another uses a date later in the same month.</p>
<p>For this project, the timeline is generated as follows:</p>
<pre><code class="language-python">timeline = create_timeline(
    start_month="2027-01-01",
    periods=12,
)

print(timeline)
</code></pre>
<p>The result is a sequence of 12 monthly timestamps, from January through December 2027.</p>
<p>This function does not decide when construction or sales occur. It only defines the periods into which the model will place those events.</p>
<h2>4. Allocate construction costs across the timeline</h2>
<p>Construction expenditure rarely follows a perfectly uniform pattern. Early periods may involve site preparation and foundations, while later periods may include structural work, finishes, and final completion activities.</p>
<p>Rather than hard-coding a cost for every month, we'll represent the construction schedule using allocation weights.</p>
<pre><code class="language-python">def build_construction_schedule(
    timeline,
    construction_budget,
    monthly_weights,
):
    if construction_budget &lt; 0:
        raise ValueError(
            "construction_budget cannot be negative"
        )

    if len(timeline) != len(monthly_weights):
        raise ValueError(
            "Each timeline period needs one cost weight"
        )

    if not monthly_weights:
        raise ValueError(
            "monthly_weights cannot be empty"
        )

    if any(
        not isinstance(weight, (int, float))
        or not pd.notna(weight)
        or weight &lt; 0
        for weight in monthly_weights
    ):
        raise ValueError(
            "Cost weights must be finite, non-negative numbers"
        )

    if not isclose(
        sum(monthly_weights),
        1.0,
        rel_tol=1e-9,
        abs_tol=1e-9,
    ):
        raise ValueError(
            "Monthly cost weights must add up to 1"
        )

    return pd.DataFrame(
        {
            "month": timeline,
            "construction_cost": [
                construction_budget * weight
                for weight in monthly_weights
            ],
        }
    )
</code></pre>
<p>The validation matters because an incorrect allocation can silently change the total construction budget represented in the model. If the weights add up to 95%, the schedule will allocate only 95% of the intended budget. If they add up to 110%, it will allocate too much.</p>
<p>We can now define the project's construction assumptions:</p>
<pre><code class="language-python">construction_budget = 1_200_000

monthly_weights = [
    0.05,
    0.05,
    0.07,
    0.08,
    0.10,
    0.12,
    0.12,
    0.10,
    0.10,
    0.08,
    0.07,
    0.06,
]

timeline = create_timeline(
    start_month="2027-01-01",
    periods=12,
)

construction = build_construction_schedule(
    timeline,
    construction_budget,
    monthly_weights,
)

print(construction)
</code></pre>
<p>Each weight is multiplied by the total construction budget. A 5% allocation therefore represents $60,000, while a 12% allocation represents $144,000.</p>
<p>The output is a table containing one row per month and a positive construction-cost amount. We'll treat costs as positive values in the component schedule and subtract them when calculating net cash flow.</p>
<p>This convention makes individual components easier to read. The sign convention for the combined cash flow will be applied in one place rather than being mixed into every input.</p>
<h2>5. Represent sales receipts as dated events</h2>
<p>Sales receipts have a different structure from construction costs. A construction schedule may allocate costs across every month, while sales revenue may arrive in only a few periods.</p>
<p>We can represent receipts as a sequence of dated events and aggregate them by month.</p>
<pre><code class="language-python">def build_sales_schedule(timeline, sales_receipts):
    if any(
        not isinstance(amount, (int, float))
        or not pd.notna(amount)
        or amount &lt; 0
        for amount in sales_receipts.values()
    ):
        raise ValueError(
            "Sales receipts must be finite, non-negative numbers"
        )

    sales = pd.DataFrame(
        [
            {
                "month": pd.Timestamp(date)
                .to_period("M")
                .to_timestamp(),
                "sales_revenue": amount,
            }
            for date, amount in sales_receipts.items()
        ],
        columns=["month", "sales_revenue"],
    )

    if sales.empty:
        return pd.DataFrame(
            {
                "month": timeline,
                "sales_revenue": 0.0,
            }
        )

    valid_months = set(timeline)

    if not sales["month"].isin(valid_months).all():
        raise ValueError(
            "Every sales receipt must fall within the timeline"
        )

    sales = (
        sales.groupby("month", as_index=False)["sales_revenue"]
        .sum()
    )

    return pd.DataFrame(
        {"month": timeline}
    ).merge(
        sales,
        on="month",
        how="left",
    ).fillna(
        {"sales_revenue": 0.0}
    )
</code></pre>
<p>This function allows the input dates to fall on any day of a month. Each date is normalized to the first day of its month before being matched to the timeline.</p>
<p>For example, a receipt dated September 18 is assigned to September. This is a monthly modeling convention, not a claim that the money is received on September 1.</p>
<p>Let's define the sales assumptions:</p>
<pre><code class="language-python">sales_receipts = {
    "2027-09-18": 450_000,
    "2027-11-12": 500_000,
    "2027-12-20": 600_000,
}

sales = build_sales_schedule(
    timeline,
    sales_receipts,
)

print(sales)
</code></pre>
<p>The resulting schedule contains all 12 months. Months without receipts receive a value of zero, which means the construction and sales schedules can be combined without losing periods.</p>
<h2>6. Combine the schedules into one cash flow model</h2>
<p>We now have two independent components:</p>
<ul>
<li>A construction schedule containing monthly expenditure.</li>
<li>A sales schedule containing monthly receipts.</li>
</ul>
<p>The next step is to combine them into a single project timeline.</p>
<pre><code class="language-python">def build_project_cash_flow(construction, sales):
    cash_flow = construction.merge(
        sales,
        on="month",
        how="outer",
        validate="one_to_one",
    )

    cash_flow = cash_flow.fillna(
        {
            "construction_cost": 0.0,
            "sales_revenue": 0.0,
        }
    )

    cash_flow = cash_flow.sort_values(
        "month"
    ).reset_index(drop=True)

    cash_flow["net_cash_flow"] = (
        cash_flow["sales_revenue"]
        - cash_flow["construction_cost"]
    )

    cash_flow["cumulative_cash_flow"] = (
        cash_flow["net_cash_flow"].cumsum()
    )

    return cash_flow
</code></pre>
<p>The <code>validate="one_to_one"</code> argument checks that each input contains no more than one row for a given month. This helps detect duplicate monthly records rather than allowing an accidental many-to-many merge to multiply rows.</p>
<p>The net cash flow formula is:</p>
<h1>[
\text{Net Cash Flow}_t</h1>
<h2>\text{Sales Revenue}_t</h2>
<p>\text{Construction Cost}_t
]</p>
<p>Cumulative cash flow is the running total of net cash flow through the current period:</p>
<h1>[
\text{Cumulative Cash Flow}_t</h1>
<p>\sum_{i=0}^{t}\text{Net Cash Flow}_i
]</p>
<p>Let's create the complete schedule:</p>
<pre><code class="language-python">project_cash_flow = build_project_cash_flow(
    construction,
    sales,
)

print(
    project_cash_flow[
        [
            "month",
            "construction_cost",
            "sales_revenue",
            "net_cash_flow",
            "cumulative_cash_flow",
        ]
    ].to_string(index=False)
)
</code></pre>
<p>The result brings the cost and revenue assumptions together. Each row represents one month, and every calculated amount can be traced back to the component schedules.</p>
<p>This is an important architectural separation. The construction-cost logic does not need to know how sales receipts are generated, and the sales schedule does not need to calculate cumulative project cash flow.</p>
<p>Each component has one responsibility, while the combined model handles the relationship between them.</p>
<h2>7. Calculate the peak funding requirement</h2>
<p>A project can have positive total cash flow over its entire timeline and still require substantial funding before receipts arrive.</p>
<p>For example, construction expenditure may accumulate for several months before the first major sales payment. Looking only at the final project balance would hide that interim cash requirement.</p>
<p>A simple estimate of peak funding need can be derived from the lowest cumulative cash flow:</p>
<h1>[
\text{Peak Funding Need}</h1>
<p>\max\left(0,-\min_t(\text{Cumulative Cash Flow}_t)\right)
]</p>
<p>We can implement this as a separate function:</p>
<pre><code class="language-python">def calculate_peak_funding_need(project_cash_flow):
    if project_cash_flow.empty:
        raise ValueError(
            "project_cash_flow cannot be empty"
        )

    lowest_balance = (
        project_cash_flow["cumulative_cash_flow"].min()
    )

    return max(0.0, -lowest_balance)
</code></pre>
<p>Now calculate the result:</p>
<pre><code class="language-python">peak_funding = calculate_peak_funding_need(
    project_cash_flow
)

print(f"Peak funding need: ${peak_funding:,.2f}")
</code></pre>
<p>With the assumptions in this example, the cumulative cash flow reaches its lowest point in August, at negative $828,000. The estimated peak funding need is therefore $828,000.</p>
<p>This is a simplified project cash requirement, not a financing recommendation. The calculation excludes land costs, financing charges, taxes, working capital, and any other items that were not included in the input schedules. If those costs or receipts are relevant, they must be added before the result can represent the broader project.</p>
<p>It also assumes that the modeled receipts arrive as scheduled. Delayed collections could increase the actual funding requirement.</p>
<h2>8. Add a basic consistency check</h2>
<p>Before using the model in a larger application, we should verify that the generated schedule reconciles to the assumptions.</p>
<p>For this example, total construction expenditure should equal the original construction budget, total sales receipts should equal the sum of the input receipts, and the final cumulative cash flow should equal total receipts less total construction costs.</p>
<pre><code class="language-python">assert isclose(
    project_cash_flow["construction_cost"].sum(),
    construction_budget,
    rel_tol=1e-9,
    abs_tol=1e-6,
)

assert isclose(
    project_cash_flow["sales_revenue"].sum(),
    sum(sales_receipts.values()),
    rel_tol=1e-9,
    abs_tol=1e-6,
)

expected_final_cash_flow = (
    sum(sales_receipts.values())
    - construction_budget
)

assert isclose(
    project_cash_flow["cumulative_cash_flow"].iloc[-1],
    expected_final_cash_flow,
    rel_tol=1e-9,
    abs_tol=1e-6,
)

print("Cash flow schedule reconciles to the input assumptions.")
</code></pre>
<p>The assertions check the relationship between the input data and the generated output. They do not prove that the assumptions are economically realistic, but they help detect common implementation errors such as missing periods, incorrect allocations, or duplicated amounts.</p>
<p>For a production system, these checks should be incorporated into automated tests rather than left as temporary statements in the execution script.</p>
<h2>9. Understand what this model does not calculate</h2>
<p>A monthly cash flow engine is a foundation for financial analysis, but it is not a complete feasibility model.</p>
<p>The current implementation assumes that construction expenditure is represented by monthly allocation weights and that sales receipts occur in specified months. It does not generate construction schedules from milestones, model individual unit sales, calculate installment collections, or forecast financing drawdowns.</p>
<p>It also does not calculate NPV or IRR. Those metrics require additional assumptions about discount rates, timing conventions, and the cash flow definition being evaluated.</p>
<p>One important extension would be to distinguish the date of a sale from the date cash is collected. A unit might be contracted in one month, generate installment payments in later months, and produce a final receipt at handover. Modeling these events separately would make the revenue schedule more useful for projects with staged collections.</p>
<p>Another extension would be to add financing as its own component. The model could then represent debt drawdowns, interest, principal repayments, and equity contributions as separate cash flow events instead of mixing them into construction expenditure.</p>
<p>These additions should build on the same structure: separate assumptions, transform them into dated schedules, combine the schedules, and validate the resulting cash flows.</p>
<h2>Conclusion</h2>
<p>Python and pandas make it possible to represent a development project's financial timeline as structured, reproducible data. By separating construction expenditure from sales receipts, combining the schedules by month, and calculating cumulative cash flow, we have built a small cash flow engine that can be extended without putting every rule into one large function.</p>
<p>The most useful part of this approach is not the final number. It is the structure behind the calculation. Each input schedule has a defined purpose, each transformation can be inspected, and the final output can be reconciled to the original assumptions.</p>
<p>From here, the same architecture can support more detailed construction schedules, staged sales collections, financing events, scenario analysis, and investment-return calculations. Each addition introduces new modeling requirements, but the underlying principle remains the same: keep the assumptions explicit, the transformations traceable, and the calculations reproducible.</p>
]]></content:encoded></item><item><title><![CDATA[Testing Financial Calculations in Python: Invariants, Edge Cases, and Reliable Results]]></title><description><![CDATA[Learn how to test Python financial calculations using unit tests, mathematical invariants, pytest parameterization, Hypothesis, and numerical-solver…]]></description><link>https://feasibilitypr.hashnode.dev/testing-financial-calculations-in-python-invariants-edge-cases-and-reliable-results</link><guid isPermaLink="true">https://feasibilitypr.hashnode.dev/testing-financial-calculations-in-python-invariants-edge-cases-and-reliable-results</guid><category><![CDATA[Python]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Programming Tips]]></category><dc:creator><![CDATA[Feasibilitypro]]></dc:creator><pubDate>Mon, 12 Oct 2026 06:44:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aa795eec12a54c029810fa4/91991e7b-090d-4372-a388-c11b6b307b34.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Financial calculations can produce plausible numbers even when the underlying implementation is wrong. A discounting function might mishandle the initial cash flow, a rounding operation might introduce errors, or a numerical solver might return a result that looks valid but does not satisfy the original equation.</p>
<p>These problems are particularly important in applications that generate financial projections. A small error in a calculation can propagate through reports, dashboards, scenario comparisons, and downstream decisions.</p>
<p>The usual response is to write unit tests for a few known inputs and outputs. That is a useful starting point, but financial software benefits from a broader testing strategy: test known results, verify mathematical properties, exercise edge cases, and check whether the output satisfies the assumptions of the calculation.</p>
<p>This tutorial uses Python and pytest to build a testing strategy for a net present value (NPV) function. The same approach can be applied to loan calculators, investment tools, pricing engines, accounting systems, and other applications that depend on numerical calculations.</p>
<h2>1. Start with a Small Financial Function</h2>
<p>Consider an NPV function that accepts a sequence of equally spaced cash flows. The first value represents the cash flow at period zero, so the initial investment is included in the sequence rather than supplied separately.</p>
<p>For cash flows (CF_0, CF_1, \ldots, CF_n), NPV is defined as:</p>
<p>[
NPV(r)=\sum_{t=0}^{n}\frac{CF_t}{(1+r)^t}
]</p>
<p>The implementation is straightforward:</p>
<pre><code class="language-python">def npv(cash_flows, discount_rate):
    """Calculate NPV for equally spaced cash flows."""
    if not cash_flows:
        raise ValueError("cash_flows cannot be empty")

    if discount_rate &lt;= -1:
        raise ValueError(
            "discount_rate must be greater than -1"
        )

    total = 0.0

    for period, cash_flow in enumerate(cash_flows):
        total += (
            cash_flow / (1 + discount_rate) ** period
        )

    return total
</code></pre>
<p>This function has a deliberately narrow contract. It expects equally spaced cash flows and a discount rate greater than -100%. It does not handle irregular dates, currency conversion, or project-specific assumptions.</p>
<p>Keeping that contract explicit makes testing easier because each test can focus on a clearly defined behavior.</p>
<p>Save the function in <code>financial_metrics.py</code>. The following examples assume that the tests are stored separately in <code>test_financial_metrics.py</code>.</p>
<h2>2. Test Known Results First</h2>
<p>The first layer of testing checks whether the function produces the expected result for inputs that can be calculated independently.</p>
<p>At a zero discount rate, every cash flow retains its original value. Therefore, NPV equals the sum of all cash flows.</p>
<pre><code class="language-python">from financial_metrics import npv


def test_npv_with_zero_discount_rate():
    cash_flows = [-100, 60, 60]

    assert npv(cash_flows, 0) == 20
</code></pre>
<p>A second test uses a cash flow sequence with a known break-even rate.</p>
<p>An initial investment of 100 followed by a receipt of 110 after one period has an NPV of zero at a 10% discount rate.</p>
<pre><code class="language-python">def test_npv_at_break_even_rate():
    cash_flows = [-100, 110]

    result = npv(cash_flows, 0.10)

    assert abs(result) &lt; 1e-10
</code></pre>
<p>Using a tolerance is important when comparing floating-point calculations. Decimal values such as 0.1 cannot always be represented exactly in binary floating-point arithmetic, so an exact equality comparison can fail even when the numerical result is effectively correct.</p>
<p>The tolerance should reflect the scale and requirements of the calculation. A very small tolerance may be appropriate for a simple calculation with small inputs, but larger financial models may require scale-aware comparisons.</p>
<h2>3. Test Mathematical Invariants</h2>
<p>Known examples test specific cases. Invariants test relationships that should remain true across many valid inputs.</p>
<p>This is a powerful technique because it checks the mathematical behavior of a function without requiring a manually calculated expected answer for every input.</p>
<h3>Invariant 1: Scaling cash flows scales NPV</h3>
<p>If every cash flow is multiplied by a constant (k), the resulting NPV should also be multiplied by (k), provided the discount rate remains unchanged.</p>
<p>[
NPV(k \cdot CF,r)=k \cdot NPV(CF,r)
]</p>
<p>This follows directly from the linearity of the NPV formula.</p>
<p>A test can verify that relationship:</p>
<pre><code class="language-python">def test_npv_scales_with_cash_flows():
    cash_flows = [-1000, 300, 400, 500]
    scale = 3

    original = npv(cash_flows, 0.08)

    scaled = npv(
        [cash_flow * scale for cash_flow in cash_flows],
        0.08,
    )

    assert abs(scaled - original * scale) &lt; 1e-8
</code></pre>
<p>This test would catch certain implementation errors that ordinary example-based tests might miss, such as accidentally applying a fixed adjustment to the final result.</p>
<h3>Invariant 2: Adding a zero cash flow at the beginning shifts the schedule</h3>
<p>Suppose a zero cash flow is inserted before the original sequence. Every original cash flow moves one period later, so each amount receives one additional period of discounting.</p>
<p>For a discount rate (r), the relationship is:</p>
<p>[
NPV([0,CF_0,\ldots,CF_n],r)
=\frac{NPV([CF_0,\ldots,CF_n],r)}{1+r}
]</p>
<p>This provides another useful test:</p>
<pre><code class="language-python">def test_npv_when_schedule_is_shifted():
    cash_flows = [-1000, 400, 500, 600]
    rate = 0.10

    original = npv(cash_flows, rate)
    shifted = npv([0, *cash_flows], rate)

    expected = original / (1 + rate)

    assert abs(shifted - expected) &lt; 1e-8
</code></pre>
<p>The test verifies that the implementation respects period positions rather than simply discounting each cash flow according to its value.</p>
<h2>4. Test Invalid Inputs Explicitly</h2>
<p>A reliable calculation function should define how it responds to invalid input. Silently accepting an invalid discount rate or an empty schedule can allow errors to propagate into other parts of an application.</p>
<p>The function already rejects empty cash flow sequences and discount rates at or below -100%. Those behaviors should be tested directly.</p>
<pre><code class="language-python">import pytest

from financial_metrics import npv


def test_npv_rejects_empty_cash_flows():
    with pytest.raises(ValueError):
        npv([], 0.10)


def test_npv_rejects_discount_rate_of_minus_one():
    with pytest.raises(ValueError):
        npv([-100, 110], -1)


def test_npv_rejects_discount_rate_below_minus_one():
    with pytest.raises(ValueError):
        npv([-100, 110], -1.1)
</code></pre>
<p>These tests document the function's input contract. If someone changes the implementation later, the tests help prevent accidental changes to its error-handling behavior.</p>
<p>There is also a distinction between validating the mathematical domain and validating the data type. The current implementation does not explicitly reject <code>NaN</code>, infinity, or non-numeric cash flows. A production implementation should decide how to handle those values and add tests for the chosen behavior.</p>
<p>For example, if the application requires all inputs to be finite numbers, that requirement should be enforced explicitly rather than left to incidental Python behavior.</p>
<h2>5. Use Parameterized Tests for More Coverage</h2>
<p>Several tests may differ only in their inputs. Pytest's parameterization feature lets developers express these cases without duplicating the test logic.</p>
<pre><code class="language-python">import pytest

from financial_metrics import npv


@pytest.mark.parametrize(
    "cash_flows, rate, expected",
    [
        ([-100, 110], 0.10, 0.0),
        ([-100, 50, 50], 0.0, 0.0),
        ([100, 0, 0], 0.10, 100.0),
        ([-100, 0, 121], 0.10, 0.0),
    ],
)
def test_npv_known_cases(cash_flows, rate, expected):
    result = npv(cash_flows, rate)

    assert abs(result - expected) &lt; 1e-8
</code></pre>
<p>Each row describes a separate test case. The same assertion is executed against all four inputs.</p>
<p>The third case verifies that a cash flow at period zero is not discounted. The fourth checks that a receipt of 121 after two periods offsets an initial investment of 100 at a 10% rate.</p>
<p>Parameterization is especially useful when a calculation has many meaningful boundary cases. It keeps the test suite compact while making the coverage easier to inspect.</p>
<h2>6. Add Property-Based Testing</h2>
<p>When the number of possible inputs becomes large, manually selecting test cases is not enough. Property-based testing generates many inputs and verifies that defined properties hold for them.</p>
<p>The Hypothesis library can generate cash flow sequences and discount rates automatically.</p>
<p>Install the dependencies:</p>
<pre><code class="language-bash">python -m pip install pytest hypothesis
</code></pre>
<p>The following example tests the scaling property across generated cash flow sequences and discount rates.</p>
<pre><code class="language-python">import pytest
from hypothesis import given, strategies as st

from financial_metrics import npv


@given(
    cash_flows=st.lists(
        st.floats(
            min_value=-1_000_000,
            max_value=1_000_000,
            allow_nan=False,
            allow_infinity=False,
        ),
        min_size=1,
        max_size=12,
    ),
    rate=st.floats(
        min_value=0,
        max_value=1,
        allow_nan=False,
        allow_infinity=False,
    ),
    scale=st.integers(min_value=1, max_value=10),
)
def test_npv_scaling_property(cash_flows, rate, scale):
    original = npv(cash_flows, rate)

    scaled_cash_flows = [
        cash_flow * scale
        for cash_flow in cash_flows
    ]

    scaled = npv(scaled_cash_flows, rate)

    assert scaled == pytest.approx(
        original * scale,
        rel=1e-9,
        abs=1e-7,
    )
</code></pre>
<p>Hypothesis generates different schedules, rates, and scaling factors. If it discovers a failing input, it attempts to reduce that input to a smaller example that still demonstrates the failure.</p>
<p>This can reveal defects that were not anticipated when the test suite was first written.</p>
<p>The example deliberately restricts discount rates to the range from zero to one and uses finite cash flows. Those bounds keep the generated cases focused on ordinary discounting behavior. Broader input ranges should be introduced only when the implementation is designed to support them.</p>
<h2>7. Test IRR Against the Original Equation</h2>
<p>Numerical solvers require a different kind of validation. A solver can return a rate, but the important question is whether that rate satisfies the equation it is supposed to solve.</p>
<p>For a conventional cash flow sequence with a unique IRR in the selected interval, the NPV evaluated at the calculated IRR should be close to zero.</p>
<p>Assuming an <code>irr()</code> function is available in <code>financial_metrics.py</code>, a test can verify that property:</p>
<pre><code class="language-python">from financial_metrics import npv, irr


def test_irr_produces_near_zero_npv():
    cash_flows = [-1000, 300, 400, 500]

    rate = irr(cash_flows)

    assert abs(npv(cash_flows, rate)) &lt; 1e-5
</code></pre>
<p>The tolerance depends on the stopping criteria used by the solver and the magnitude of the cash flows. The threshold above is illustrative and should be adjusted to match the implementation's numerical guarantees.</p>
<p>This test is more general than checking whether IRR equals a hard-coded percentage. It verifies that the returned rate satisfies the defining equation.</p>
<p>However, it does not prove that the returned IRR is unique. Cash flow sequences with multiple sign changes can have multiple valid IRRs. Tests should account for that behavior rather than assuming every project has exactly one solution.</p>
<h2>8. Run the Test Suite in a Repeatable Environment</h2>
<p>A useful test suite should be easy to run locally and in continuous integration.</p>
<p>With the calculation module and test file in the same directory, execute:</p>
<pre><code class="language-bash">python -m pytest -v
</code></pre>
<p>For a larger project, organize the tests by responsibility. Keep basic calculation tests separate from validation tests, numerical-solver tests, and integration tests that verify how the financial model assembles its cash flows.</p>
<p>It is also useful to run tests automatically whenever code changes. A continuous integration workflow can install the project's dependencies, run the test suite, and report failures before a change is merged.</p>
<p>The goal is not to test every possible number. It is to establish confidence that the implementation respects its mathematical contract, handles expected invalid inputs, and behaves consistently as the code evolves.</p>
<h2>Conclusion</h2>
<p>Testing financial calculations requires more than confirming that a few examples return the expected numbers. Known-result tests establish a baseline, mathematical invariants verify deeper relationships, parameterized tests expand coverage, and property-based tests explore a wider range of inputs.</p>
<p>For numerical solvers such as IRR, the result should also be checked against the original equation. This helps detect incorrect outputs even when the returned value looks reasonable.</p>
<p>These techniques are useful wherever software turns financial assumptions into calculated results. By making mathematical properties and input contracts executable as tests, developers can detect errors earlier and make financial calculation code easier to maintain.</p>
]]></content:encoded></item><item><title><![CDATA[Implementing IRR and NPV Calculations in Python: A Real Estate Development Example]]></title><description><![CDATA[Learn how to implement NPV and IRR calculations in Python with a real estate development cash flow example, error handling, and unit tests.]]></description><link>https://feasibilitypr.hashnode.dev/implementing-irr-and-npv-calculations-in-python-a-real-estate-development-example</link><guid isPermaLink="true">https://feasibilitypr.hashnode.dev/implementing-irr-and-npv-calculations-in-python-a-real-estate-development-example</guid><category><![CDATA[Python]]></category><category><![CDATA[Financial Modeling]]></category><category><![CDATA[Real Estate]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Feasibilitypro]]></dc:creator><pubDate>Fri, 09 Oct 2026 13:21:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aa795eec12a54c029810fa4/f48a3b0a-5d8f-401a-800e-50e6e03b1b7a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Financial models often look straightforward until you need to turn their formulas into reliable software. Net present value (NPV) and internal rate of return (IRR) are two common examples. Both use projected cash flows to evaluate an investment, but implementing them correctly requires more than translating a formula into Python.</p>
<p>A real estate development project makes the distinction particularly clear. Construction costs may occur over several years, revenue may arrive in stages, and the initial investment may be followed by additional capital requirements before the project generates positive cash flow. A useful implementation must preserve the timing and sign of each cash flow, handle invalid inputs, and make its assumptions explicit.</p>
<p>This tutorial implements NPV and IRR using Python's standard library. We will build a small cash flow model, calculate both metrics, test the results, and examine limitations that matter when these functions become part of a larger financial application.</p>
<h2>1. Define the cash flow model</h2>
<p>Suppose a development project requires an initial investment of $1,000,000. The project is expected to generate the following net cash flows over the next five years.</p>
<table>
<thead>
<tr>
<th>Period</th>
<th>Net cash flow</th>
</tr>
</thead>
<tbody><tr>
<td>Year 0</td>
<td>-$1,000,000</td>
</tr>
<tr>
<td>Year 1</td>
<td>$250,000</td>
</tr>
<tr>
<td>Year 2</td>
<td>$300,000</td>
</tr>
<tr>
<td>Year 3</td>
<td>$350,000</td>
</tr>
<tr>
<td>Year 4</td>
<td>$400,000</td>
</tr>
<tr>
<td>Year 5</td>
<td>$450,000</td>
</tr>
</tbody></table>
<p>These figures are illustrative. They represent net project cash flows rather than gross property revenue. Depending on the model's purpose, cash flows might incorporate construction costs, operating expenses, sale proceeds, financing, taxes, or other project-specific items.</p>
<p>The negative initial cash flow represents capital invested at the beginning of the analysis. The positive amounts represent subsequent cash received by the investor.</p>
<p>We can represent the schedule as a Python list:</p>
<pre><code class="language-python">cash_flows = [
    -1_000_000,
    250_000,
    300_000,
    350_000,
    400_000,
    450_000,
]
</code></pre>
<p>Each position corresponds to a period. Index <code>0</code> represents the initial investment, index <code>1</code> represents the first year's cash flow, and so on.</p>
<p>This representation assumes equally spaced periods. It is suitable for a basic annual model, but it is not sufficient when cash flows occur on irregular dates. We will return to that distinction later.</p>
<h2>2. Implement net present value</h2>
<p>NPV measures the present value of future cash flows after accounting for the initial investment. It discounts each cash flow according to the required rate of return.</p>
<p>For equally spaced periods, the formula is:</p>
<p>[
NPV(r)=\sum_{t=0}^{n}\frac{CF_t}{(1+r)^t}
]</p>
<p>Here, (CF_t) is the cash flow at period (t), (r) is the discount rate per period, and (n) is the final period.</p>
<p>The discount rate reflects the return required for the investment or the rate used to evaluate its cash flows. It should be chosen consistently with the cash flow definition and the period length.</p>
<p>For example, if the model uses annual cash flows, the discount rate should also be annual.</p>
<h3>Build the function</h3>
<pre><code class="language-python">def npv(cash_flows, discount_rate):
    """Calculate NPV for equally spaced cash flows.

    The first cash flow occurs at period 0.
    """
    if not cash_flows:
        raise ValueError("cash_flows cannot be empty")

    if discount_rate &lt;= -1:
        raise ValueError("discount_rate must be greater than -1")

    total = 0.0

    for period, cash_flow in enumerate(cash_flows):
        total += cash_flow / (1 + discount_rate) ** period

    return total
</code></pre>
<p>The function iterates through the cash flow schedule and discounts each amount according to its position. Because the first cash flow occurs at period zero, it is not discounted.</p>
<p>That detail is important. Some spreadsheet functions use a convention in which the initial investment is supplied separately from the sequence of future cash flows. Our Python function instead accepts the complete schedule, including the initial investment. Mixing these conventions can produce incorrect results.</p>
<p>We can calculate the project's NPV using a 10% annual discount rate:</p>
<pre><code class="language-python">cash_flows = [
    -1_000_000,
    250_000,
    300_000,
    350_000,
    400_000,
    450_000,
]

project_npv = npv(cash_flows, 0.10)

print(f"NPV: ${project_npv:,.2f}")
</code></pre>
<p>A positive NPV means the discounted value of the modeled cash flows exceeds the initial investment at the selected discount rate. A negative NPV means the modeled cash flows fall short of that threshold.</p>
<p>NPV is therefore conditional on the assumptions in the model. Changing the discount rate, development costs, sale values, or timing of receipts can change the result.</p>
<h2>3. Implement internal rate of return</h2>
<p>IRR is the discount rate at which NPV equals zero.</p>
<p>[
0=\sum_{t=0}^{n}\frac{CF_t}{(1+IRR)^t}
]</p>
<p>Unlike NPV, IRR is not calculated by supplying a discount rate. The rate must be found by solving an equation.</p>
<p>For a general cash flow schedule, there may not be a convenient closed-form solution. A numerical method is often used instead.</p>
<p>We will implement the bisection method. It repeatedly narrows a range of possible rates until the NPV is sufficiently close to zero.</p>
<h3>Why use bisection?</h3>
<p>Bisection is straightforward to implement and does not require derivatives. It works when the chosen interval brackets a root and the NPV function is continuous over that interval.</p>
<p>For a conventional project with one initial outflow followed by positive inflows, this can be a practical approach. However, bisection does not automatically find every possible IRR. The interval must contain a root, and cash flows with multiple sign changes may have multiple IRRs.</p>
<h3>Build the function</h3>
<pre><code class="language-python">def irr(
    cash_flows,
    low=-0.9999,
    high=1.0,
    tolerance=1e-7,
    max_iterations=1_000,
):
    """Estimate IRR for equally spaced cash flows using bisection.

    The selected interval must bracket an IRR.
    """
    if len(cash_flows) &lt; 2:
        raise ValueError("At least two cash flows are required")

    if not any(cf &lt; 0 for cf in cash_flows):
        raise ValueError("Cash flows must include a negative value")

    if not any(cf &gt; 0 for cf in cash_flows):
        raise ValueError("Cash flows must include a positive value")

    if low &lt;= -1:
        raise ValueError("low must be greater than -1")

    if high &lt;= low:
        raise ValueError("high must be greater than low")

    if tolerance &lt;= 0:
        raise ValueError("tolerance must be positive")

    if max_iterations &lt;= 0:
        raise ValueError("max_iterations must be positive")

    npv_low = npv(cash_flows, low)
    npv_high = npv(cash_flows, high)

    if npv_low == 0:
        return low

    if npv_high == 0:
        return high

    if npv_low * npv_high &gt; 0:
        raise ValueError(
            "The interval does not bracket an IRR. "
            "Try a different low/high range."
        )

    for _ in range(max_iterations):
        midpoint = (low + high) / 2
        npv_midpoint = npv(cash_flows, midpoint)

        if abs(npv_midpoint) &lt; tolerance:
            return midpoint

        if npv_low * npv_midpoint &lt; 0:
            high = midpoint
            npv_high = npv_midpoint
        else:
            low = midpoint
            npv_low = npv_midpoint

        if high - low &lt; tolerance:
            return (low + high) / 2

    raise RuntimeError("IRR calculation did not converge")
</code></pre>
<p>The lower bound must be greater than <code>-1</code>, because a discount rate of <code>-100%</code> makes the discounting denominator zero for periods after the initial period.</p>
<p>The upper bound is a search parameter, not a statement about the project's expected return. If the interval does not bracket a root, the function raises an error rather than silently returning an unreliable value.</p>
<p>The function also checks for both positive and negative cash flows. This is a basic validity check, not proof that a unique IRR exists.</p>
<h2>4. Calculate NPV and IRR for the project</h2>
<p>We can now calculate both metrics from the same cash flow schedule.</p>
<pre><code class="language-python">cash_flows = [
    -1_000_000,
    250_000,
    300_000,
    350_000,
    400_000,
    450_000,
]

discount_rate = 0.10

project_npv = npv(cash_flows, discount_rate)
project_irr = irr(cash_flows)

print(f"NPV at {discount_rate:.0%}: ${project_npv:,.2f}")
print(f"IRR: {project_irr:.2%}")
</code></pre>
<p>The NPV tells us how much value the modeled project creates or loses in present-value terms at the selected 10% discount rate. The IRR identifies the rate at which the modeled NPV is zero.</p>
<p>These measures answer different questions. NPV evaluates the cash flows against a specified return threshold, while IRR expresses the break-even discount rate implied by those cash flows.</p>
<p>Neither metric should be interpreted without examining the assumptions behind the schedule. In a development model, a delayed completion date, higher construction costs, lower sale proceeds, or slower collections can materially affect the results.</p>
<h2>5. Test the implementation</h2>
<p>Financial calculations should be tested independently of the user interface or the larger application. A function that produces a plausible number is not necessarily correct.</p>
<p>The following tests use Python's built-in <code>unittest</code> module. They cover a known NPV result, a simple IRR case, and invalid input conditions.</p>
<p>Save the calculation functions in a file named <code>financial_metrics.py</code>. Then create a file named <code>test_financial_metrics.py</code> with the following code:</p>
<pre><code class="language-python">import unittest

from financial_metrics import npv, irr


class TestFinancialMetrics(unittest.TestCase):

    def test_npv_with_zero_discount_rate(self):
        cash_flows = [-100, 60, 60]

        self.assertAlmostEqual(
            npv(cash_flows, 0),
            20.0,
        )

    def test_npv_discounting(self):
        cash_flows = [-100, 110]

        self.assertAlmostEqual(
            npv(cash_flows, 0.10),
            0.0,
            places=8,
        )

    def test_irr_simple_project(self):
        cash_flows = [-100, 110]

        self.assertAlmostEqual(
            irr(cash_flows),
            0.10,
            places=6,
        )

    def test_npv_rejects_empty_cash_flows(self):
        with self.assertRaises(ValueError):
            npv([], 0.10)

    def test_npv_rejects_invalid_discount_rate(self):
        with self.assertRaises(ValueError):
            npv([-100, 110], -1)

    def test_irr_rejects_missing_sign_change(self):
        with self.assertRaises(ValueError):
            irr([-100, -50])

    def test_irr_rejects_unbracketed_root(self):
        with self.assertRaises(ValueError):
            irr([-100, 110], low=0.20, high=0.50)


if __name__ == "__main__":
    unittest.main()
</code></pre>
<p>Run the tests from the directory containing both files:</p>
<pre><code class="language-bash">python -m unittest -v
</code></pre>
<p>The tests verify that a zero discount rate returns the sum of the cash flows, that a 10% return makes the NPV of <code>[-100, 110]</code> equal to zero, and that invalid inputs raise errors.</p>
<p>The test suite should pass before the functions are integrated into a larger application. Additional tests are appropriate for extreme values, multiple sign changes, and the specific cash flow patterns supported by the model.</p>
<h2>6. Understand the limitations of IRR</h2>
<p>IRR is useful, but it has limitations that matter when implementing financial software.</p>
<h3>Multiple IRRs</h3>
<p>Consider a cash flow schedule that changes sign more than once:</p>
<pre><code class="language-python">cash_flows = [-100, 230, -132]
</code></pre>
<p>Its NPV equation can have more than one valid rate. In this case, the equation has roots at 10% and 20%.</p>
<p>A bisection function can return a root only when its chosen interval brackets one. It cannot establish that the root is unique, and different intervals can produce different answers.</p>
<p>For projects with irregular patterns of capital expenditure, operating cash flow, and later investment, a single IRR may not communicate the economics clearly. NPV at a justified discount rate is often easier to interpret alongside the full cash flow schedule.</p>
<h3>No IRR</h3>
<p>Some cash flow schedules have no IRR within the valid rate range. For example, cash flows that are all negative cannot produce an NPV of zero at a conventional finite discount rate greater than <code>-100%</code>.</p>
<p>The implementation rejects schedules without both positive and negative values, but that check is not sufficient to prove that a root exists. The interval check provides another safeguard.</p>
<h3>Periodic cash flows versus dated cash flows</h3>
<p>The functions in this tutorial assume equal intervals between cash flows. That is reasonable for a basic annual projection in which all amounts are assigned to year-end periods.</p>
<p>Real estate development cash flows can be more complicated. A construction payment may occur in March, a buyer installment may arrive in September, and a final sale receipt may be collected several months later. Treating these events as if they occurred at evenly spaced annual intervals can distort the results.</p>
<p>For actual-date calculations, use a dated cash flow model and a method designed for irregular timing, such as XIRR. A dated implementation must define its day-count convention and the treatment of the initial date explicitly.</p>
<h2>7. Keep calculation logic separate from project assumptions</h2>
<p>A financial model becomes easier to maintain when its calculation engine is separated from the data that describes the project.</p>
<p>The <code>npv()</code> and <code>irr()</code> functions above do not know whether a cash flow comes from land acquisition, construction, financing, rent, or a property sale. They operate only on the cash flow sequence.</p>
<p>A separate model layer can generate that sequence from project assumptions. For example, it could combine monthly construction costs, scheduled unit sales, collection percentages, operating expenses, and financing flows into a consolidated schedule. The calculation layer can then evaluate the resulting cash flows without duplicating project-specific rules.</p>
<p>This separation also makes testing more effective. You can test the cost schedule independently, test revenue collection logic separately, and verify that the final cash flow schedule produces the expected financial metrics.</p>
<p>For a larger application, it is useful to define a clear contract for the calculation functions: what the inputs represent, which periods they use, which cash flow conventions apply, and which errors are raised. That contract prevents different parts of the application from quietly using incompatible assumptions.</p>
<h2>Conclusion</h2>
<p>Implementing NPV and IRR in Python requires more than writing two formulas. The functions must handle period timing consistently, reject invalid inputs, expose numerical limitations, and be tested against known results.</p>
<p>For a real estate development model, these calculations are only as reliable as the cash flows supplied to them. Construction timing, revenue collection, project costs, and the chosen discount rate all influence the result. A well-structured calculation engine makes these relationships easier to test and maintain, but it cannot compensate for incorrect project assumptions.</p>
<p>The next step is to extend the example from a fixed list of cash flows into a project schedule that generates cash flows from dated construction costs, sales, collections, and financing events. That is where the calculation functions become part of a more complete financial modeling system.</p>
]]></content:encoded></item></channel></rss>