A rental yield calculator should show the scenario that loses money
A positive rental yield is easy to demonstrate with a friendly input. The more useful question for a property-data tool is what happens when occupancy falls while fixed costs and loan payments remain due.
We added a browser-based Rental Cashflow Stress Lab to our existing open-source calculator. It compares explicit scenarios instead of replacing uncertain inputs with country presets.
This is a project from NovAsia Estate, where our property research covers Cambodia, Thailand and Bali. All the figures below are synthetic test inputs, not observations about those markets.
Keep capacity rent separate from collected rent
The input we call full-occupancy rent is a capacity assumption:
scenario capacity = full-occupancy annual rent × rent-level multiplier
collected annual rent = scenario capacity × occupancy
For example, 24,000 at 70% occupancy becomes 16,800 of collected annual rent. A downside scenario uses a lower rent level of 85% and occupancy of 35%, giving 7,140.
This simple annual fraction does not model seasonality, cancellation patterns or the difference between asking and achieved rent. Making that limitation explicit is preferable to presenting the result as a forecast.
Fixed costs should survive zero occupancy
Our example has 1,200 of annual common-area charges, 500 of other fixed costs and 6,000 of annual debt service. Management and maintenance-reserve assumptions are 20% and 5% of collected rent.
The core engine calculates operating income and applies the caller's chosen simplified tax base. The stress extension subtracts debt service afterwards. It does not infer tax deductibility of interest, depreciation, a loan balance or an equity-return denominator.
With an illustrative 10% tax on positive operating profit, the three fixture outcomes are:
| Scenario | Occupancy | Rent level | Fixed-cost level | Net annual cash flow after debt |
|---|---|---|---|---|
| Base | 70% | 100% | 100% | 3,810 |
| Vacancy shock | 35% | 85% | 125% | -3,093 |
| Stronger demand | 90% | 110% | 100% | 8,508 |
At zero occupancy, those original fixed costs and debt still produce a loss of 7,700. The tool retains that number. A missing cost is an input error; zero must be entered deliberately.
Break-even needs the same assumptions as the comparison
Let F be fixed operating costs, D annual debt service, k the fraction of collected rent remaining after management and reserve, and t the supplied tax rate.
For a gross-rent tax assumption, the continuous threshold is (F + D) / (k - t) when the retained margin is positive. For tax on positive operating profit, with k > 0 and t < 1, it is (F + D / (1 - t)) / k.
The example's base case needs about 11,155.56 of collected annual rent, or about 46.48% of its stated capacity. A requirement above 100% occupancy means the entered rent capacity cannot cover the costs. Impossible margins and zero capacity have explicit states rather than a misleading percentage.
The exact edge cases, including 100% tax and zero costs, are covered by tests. These thresholds are approximate continuous results before cent rounding. Breaking even is not the same as earning a positive return.
Keep the experiment reproducible
The extension uses the same engine as the original calculator:
const Stress = require('./docs/stress.js');
const input = require('./examples/stress.json');
const report = Stress.compare(input.base, input.annualDebtService, input.scenarios);
console.log(report.rows.map(row => [row.name, row.netAfterDebt]));
The source repository contains the fixture and 36 passing contracts, including zero rent, retained losses, both tax bases, impossible break-even states, CSV formula protection and browser/Node consistency. JSON exports retain the assumptions and itemized core results. Browser inputs are calculated locally without upload requests.
For the earlier acquisition stage, our Payment Milestone Studio allocates dated installments with exact integer arithmetic. That is a separate problem: a purchase payment schedule does not establish rental income.
The Stress Lab is a repository and browser extension. The published novasia-sea-yield npm package remains version 2.0.1; we are not describing unshipped npm exports. Contributions that make assumptions clearer are welcome.
Comments
No comments yet. Start the discussion.