Functional Weave
Code in Python

finance.margin

Profit on a sale with both the margin and the markup, so the two are never confused.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 13 tests, run in TypeScript, Python and Rust.

What it does

Margin is profit over PRICE. Markup is profit over COST. They are not the same number and they are not interchangeable: buy at 10.00, sell at 15.00, and that is a 50% markup but only a 33.33% margin.

Quoting the markup where a margin was meant is how a retailer prices a whole range to a target it never actually hits, so this returns both and the caller has to pick one on purpose.

For example

  • margin(£60.00, £100.00) → cost £60.00, price £100.00, profit £40.00, margin basis points 40%, markup basis points 66.67% buy at 60.00, sell at 100.00: a 40 percent margin is a 66.67 percent markup
  • margin(£100.00, £150.00) → cost £100.00, price £150.00, profit £50.00, margin basis points 33.33%, markup basis points 50% the classic confusion: 50 percent markup is only a 33.33 percent margin
  • margin(£50.00, £100.00) → cost £50.00, price £100.00, profit £50.00, margin basis points 50%, markup basis points 100% double the cost is a 50 percent margin and a 100 percent markup

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def margin(cost: Money, price: Money) -> MarginBreakdown
costMoneywhat the item cost to buy or make
priceMoneywhat it is sold for, same currency as cost
returnsMarginBreakdown

The type it declares, generated into your project

@dataclass(frozen=True)
class MarginBreakdown:
    cost: Money
    price: Money
    profit: Money
    #: profit / price, in basis points; null when price is zero
    margin_basis_points: Optional[int]
    #: profit / cost, in basis points; null when cost is zero
    markup_basis_points: Optional[int]

Your code names it in one line, in the file that uses it

from fune.finance.margin import margin  # finance.margin@^1
impl/python.py · 43 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from .finance_margin_types import MarginBreakdown
from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_add import subtract_money  ← from money.add ^1.0.0 · built alongside by fune
from .money_amount import Money  ← from money.amount ^1.0.0 · built alongside by fune


def margin(cost: Money, price: Money) -> MarginBreakdown:
    """Profit on a sale, expressed both ways.

    Margin divides by the price, markup divides by the cost, and the gap between
    them is wide enough to price a whole range wrongly: 10.00 to 15.00 is a 50%
    markup and a 33.33% margin. Returning only one of them invites the caller to
    use it as the other, so both are returned and neither is called "the rate".

    Both ratios are in basis points and come out of integer division, so no
    float ever touches the money.
    """
    # subtract_money also rejects a GBP cost against a EUR price, which is the
    # only way this function can be called with genuinely unanswerable inputs.
    profit = subtract_money(price, cost)

    # A zero denominator is not an error here: a giveaway (price 0) and a free
    # sample (cost 0) are real trade. The ratio is undefined, not wrong, and
    # the rest of the breakdown is still correct.
    return MarginBreakdown(
        cost=cost,
        price=price,
        profit=profit,
        margin_basis_points=None if price.minor == 0 else round_div(profit.minor * 10000, price.minor, "half-up"),
        markup_basis_points=None if cost.minor == 0 else round_div(profit.minor * 10000, cost.minor, "half-up"),
    )


def margin_to_markup_basis_points(margin_basis_points: int) -> int:
    """Convert a margin to the markup that produces it. Undefined at 100% margin."""
    if margin_basis_points >= 10000:
        raise ValueError("a margin of %d basis points has no finite markup" % (margin_basis_points,))
    return round_div(margin_basis_points * 10000, 10000 - margin_basis_points, "half-up")


def markup_to_margin_basis_points(markup_basis_points: int) -> int:
    """Convert a markup to the margin it produces."""
    return round_div(markup_basis_points * 10000, 10000 + markup_basis_points, "half-up")

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add finance.margin
Download for Python finance.margin-1.0.0-python.fune · 10,568 bytes sha256 4532ae2da6e2ecbc91216cf5d57c80274bed3f2d14c3dc450c1854587a870a79

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./finance.margin-1.0.0-python.fune, or fetch it from a terminal with fune pull finance.margin@1.0.0:python.

The whole function, every language, is one file too: finance.margin-1.0.0.fune, 16,021 bytes, sha256 b34d55a2236e931c52e6bd1d5122aef0d799949537ccf7131318fafe63cedf69. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before finance.margin

after — your function gets the result and the arguments, and returns the final result.

# fune: after finance.margin

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace math.round-div in finance.margin
# fune: replace money.add in finance.margin
# fune: replace money.amount in finance.margin

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show finance.margin --steps.

# fune: step finance.margin after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
buy at 60.00, sell at 100.00: a 40 percent margin is a 66.67 percent markup £60.00, £100.00 → cost £60.00, price £100.00, profit £40.00, margin basis points 40%, markup basis points 66.67%
the classic confusion: 50 percent markup is only a 33.33 percent margin £100.00, £150.00 → cost £100.00, price £150.00, profit £50.00, margin basis points 33.33%, markup basis points 50%
double the cost is a 50 percent margin and a 100 percent markup £50.00, £100.00 → cost £50.00, price £100.00, profit £50.00, margin basis points 50%, markup basis points 100%
6.99 to 9.99 divides evenly into nothing and both ratios still land on a whole basis point £6.99, £9.99 → cost £6.99, price £9.99, profit £3.00, margin basis points 30.03%, markup basis points 42.92%
an exact half basis point rounds up, away from zero £0.31, £0.32 → cost £0.31, price £0.32, profit £0.01, margin basis points 3.13%, markup basis points 3.23%
a loss rounds symmetrically, also away from zero £0.32, £0.31 → cost £0.32, price £0.31, profit -£0.01, margin basis points -3.23%, markup basis points -3.13%
selling below cost gives a negative margin, not an error £120.00, £100.00 → cost £120.00, price £100.00, profit -£20.00, margin basis points -20%, markup basis points -16.67%
selling at cost is zero margin and zero markup £50.00, £50.00 → cost £50.00, price £50.00, profit £0.00, margin basis points 0%, markup basis points 0%
a free sample has a 100 percent margin and no meaningful markup £0.00, £9.99 → cost £0.00, price £9.99, profit £9.99, margin basis points 100%, markup basis points —
a giveaway has no meaningful margin but a minus 100 percent markup £5.00, £0.00 → cost £5.00, price £0.00, profit -£5.00, margin basis points —, markup basis points -100%
Show the other 3 tests
CaseArgumentsExpected
nothing for nothing has neither ratio and no profit £0.00, £0.00 → cost £0.00, price £0.00, profit £0.00, margin basis points —, markup basis points —
yen has no minor unit and still works, one is one ¥800, ¥1,000 → cost ¥800, price ¥1,000, profit ¥200, margin basis points 20%, markup basis points 25%
costing in one currency and pricing in another is an error, not a number £60.00, €100.00 → error: currency mismatch

More from the author

To go the other way: markup = margin / (1 - margin), margin = markup / (1 + markup).

Both ratios are returned in basis points (10000 = 100%), rounded half-up, because the registry never puts a rate in a float.

Division by zero is reported as null rather than an error, because both zeroes are real trade: a free sample has zero cost and therefore no meaningful markup (any profit is infinitely more than nothing), and a giveaway has zero price and therefore no meaningful margin. The other ratio, the profit and the currency are still correct in both cases, so failing the whole call would throw away good answers. Callers that treat null as 0% will understate; treat it as "not applicable".

Files

PathBytes
README.md1,097
impl/python.py2,035
impl/rust.rs3,211
impl/typescript.ts2,034
vectors.json4,648