How the Hong Kong sole proprietor calculator is verified
Inputs
| Input | Field | Type |
|---|---|---|
| Annual business profit before your own MPF (HKD) | profitsAnnual | number |
| Allowance under personal assessment | household | string |
| Children you claim for | children | number |
The formula
Each derived value below is computed by the Valem engine from the expression shown, in this order. Nothing else runs — there is no hidden code path.
| Derived value | Field | Formula (JSONata) |
|---|---|---|
| Your MPF (5%, capped) | mpfContribution | ($p := $n(profitsAnnual); $round($p < $const.mpfMinRelevantAnnual ? 0 : ($p < $const.mpfMaxRelevantAnnual ? $p : $const.mpfMaxRelevantAnnual) * $const.mpfRate, 2)) |
| Assessable profits (after the MPF deduction) | assessableProfits | ($v := $n(profitsAnnual) - (mpfContribution < $const.mpfDeductionCap ? mpfContribution : $const.mpfDeductionCap); $v < 0 ? 0 : $v) |
| Profits tax (7.5% / 15%) | profitsTax | $round($bandedCharge(assessableProfits, [{"upTo": $const.profitsTaxTierThreshold, "rate": $const.profitsTaxLowerRate}, {"upTo": null, "rate": $const.profitsTaxRate}]), 2) |
| Allowances (personal assessment) | allowances | (household = 'marriedSoleEarner' ? $const.marriedAllowance : $const.basicAllowance) + $floor($n(children)) * $const.childAllowance |
| Chargeable under personal assessment | paChargeable | ($v := assessableProfits - allowances; $v < 0 ? 0 : $v) |
| Tax under personal assessment | personalAssessmentTax | $round($bandedCharge(paChargeable, [{"upTo": $const.bandWidth, "rate": $const.rateBand1}, {"upTo": $const.bandWidth * 2, "rate": $const.rateBand2}, {"upTo": $const.bandWidth * 3, "rate": $const.rateBand3}, {"upTo": $const.bandWidth * 4, "rate": $const.rateBand4}, {"upTo": null, "rate": $const.rateTop}]), 2) |
| Tax payable (the lower) | taxPayable | personalAssessmentTax < profitsTax ? personalAssessmentTax : profitsTax |
| Net income / year | netIncome | $round($n(profitsAnnual) - mpfContribution - taxPayable, 2) |
Rates and thresholds
The expressions above read these as $const. They are the figures the annual refresh replaces, so they live as data rather than being written into the formulas — which is what makes a year-on-year change a one-line diff instead of an edit to arithmetic.
| Constant | Value |
|---|---|
profitsTaxLowerRate | 0.075 |
profitsTaxRate | 0.15 |
profitsTaxTierThreshold | 2000000 |
basicAllowance | 145000 |
marriedAllowance | 290000 |
childAllowance | 140000 |
bandWidth | 50000 |
rateBand1 | 0.02 |
rateBand2 | 0.06 |
rateBand3 | 0.1 |
rateBand4 | 0.14 |
rateTop | 0.17 |
mpfRate | 0.05 |
mpfMinRelevantAnnual | 85200 |
mpfMaxRelevantAnnual | 360000 |
mpfDeductionCap | 18000 |
Test vectors (5)
Every case is executed against this model on each build, by the Java engine and independently by a JavaScript one. A mismatch of a single cent fails the build, so the page cannot ship advertising a figure the model no longer produces.
Case 1 — P1 $480,000 single: profits tax $34,650 beats personal assessment $35,890
given profitsAnnual | 480000 |
|---|---|
given household | single |
given children | 0 |
expect mpfContribution | 18000 |
expect assessableProfits | 462000 |
expect profitsTax | 34650 |
expect paChargeable | 317000 |
expect personalAssessmentTax | 35890 |
expect taxPayable | 34650 |
expect netIncome | 427350 |
Case 2 — P2 $300,000 single: personal assessment $8,000 beats profits tax $21,375
given profitsAnnual | 300000 |
|---|---|
given household | single |
given children | 0 |
expect mpfContribution | 15000 |
expect profitsTax | 21375 |
expect personalAssessmentTax | 8000 |
expect taxPayable | 8000 |
expect netIncome | 277000 |
Case 3 — P3 $480,000 with the married person’s allowance: personal assessment $12,080
given profitsAnnual | 480000 |
|---|---|
given household | marriedSoleEarner |
given children | 0 |
expect paChargeable | 172000 |
expect taxPayable | 12080 |
expect netIncome | 449920 |
Case 4 — P4 below the $85,200 minimum relevant income: no MPF and no tax
given profitsAnnual | 80000 |
|---|---|
given household | single |
given children | 0 |
expect mpfContribution | 0 |
expect taxPayable | 0 |
expect netIncome | 80000 |
Case 5 — P5 $3m: the 15% upper tier applies above $2m of assessable profits
given profitsAnnual | 3000000 |
|---|---|
given household | single |
given children | 0 |
expect assessableProfits | 2982000 |
expect profitsTax | 297300 |
expect personalAssessmentTax | 464290 |
expect taxPayable | 297300 |
expect netIncome | 2684700 |
Sources
- Inland Revenue Department, A Brief Guide to Hong Kong Taxes 2025/2026 — profits tax rates for unincorporated businesses (two-tiered), personal assessment, progressive rates and the $18,000 maximum MPF deduction for a sole proprietor · checked 2026-10-12
- Inland Revenue Department, 2026-27 Budget — Tax Measures (FAQ): basic, married person’s and child allowances from 2026/27 · checked 2026-10-12
- MPFA, Mandatory contributions — self-employed persons: 5% of relevant income between $85,200 and $360,000 a year · checked 2026-10-12
Check it yourself — and reuse it
The whole model as JSON — inputs, outputs, formulas, vectors and sources in one re-runnable document.
This model is published under CC BY 4.0: use it, adapt it, build on it, with attribution. The licence covers the model — the expression of each rule and the arrangement of the catalog. The rates and thresholds are law, and nobody licenses those; the sources above are where they come from.
← Back to the Hong Kong sole proprietor calculator
Built with Valem — a live, verifiable calculator you can fork and embed.