# Fee waterfall (fee_waterfall)

Desk: Fund terms and risk. Page: https://hedgefund.md/models/fee_waterfall

**What it asks.** What do the management and performance fees take from this return series, and at what return does a performance fee start?

Runs a series of gross returns through a management fee, a performance fee, a hurdle and a high-water mark, period by period. Shows net against gross, fees by kind, the mark and the drag. All numbers come from the caller. It is arithmetic on the terms sent, not a reading of any fund's documents and not a forecast.

Results are hypothetical: arithmetic on the numbers sent, a made-up template or a stated default. Nobody achieved them, and they aren't a forecast, a recommendation, an offer or a record of real trading. Don't show any output to investors or clients, or use it to market an investment. Terms: https://hedgefund.md/terms

## Inputs

| Input | Type | Range | Required | What it is |
| --- | --- | --- | --- | --- |
| `nav_start` | number | over 0 to 10000000000000 | yes | NAV at the start, in money. |
| `gross_returns_pct` | array | 1 to 240 items | yes | Gross return of each period in percent, oldest first. |
| `periods_per_year` | whole number | 1 to 365 | yes | Periods in a year: 12 for months, 4 for quarters. |
| `mgmt_fee_pct_annual` | number | 0 to 20 | yes | Management fee a year in percent. One period pays this divided by periods_per_year, on opening NAV. |
| `perf_fee_pct` | number | 0 to 100 | yes | Performance fee in percent of the gains it applies to. |
| `hurdle_pct_annual` | number | 0 to 100 | no | Hurdle a year in percent. Ignored when hurdle_type is none. |
| `hurdle_type` | string | none \| hard \| soft | no | none; hard, a fee only on gains above the hurdle line; soft, a fee on all gains above the mark once past the line. |
| `crystallize_every` | whole number | 1 to 240 | yes | Periods between dates when the accrued performance fee is paid. |
| `use_hwm` | boolean |  | no | true compares with the high-water mark. false compares with the NAV after fees at the last date, up or down. |

## Template

Made-up numbers that run as they stand, so you can see the shape of a result. They aren't market levels, typical values or estimates, and the names in them don't exist. Replace every number before you read anything into a run.

```json
{
  "nav_start": 100000000,
  "gross_returns_pct": [
    5, 3, 4, 2, -6, -5, 1, -2, 6, 5,
    4, 3, 2, 3
  ],
  "periods_per_year": 4,
  "mgmt_fee_pct_annual": 2,
  "perf_fee_pct": 20,
  "hurdle_pct_annual": 4,
  "hurdle_type": "hard",
  "crystallize_every": 4,
  "use_hwm": true
}
```

## What the template gives

On these numbers, a hypothetical 100,000,000 becomes 127,017,063.48 before fees and 116,287,292.81 after them over 14 periods, with 7,358,920.48 in management fees, 2,166,562.01 in performance fees and a drag of 2.67 points a year.

- Net NAV at the end: 116,287,293
- Gross NAV at the end: 127,017,064
- Management fees: 7,358,920
- Performance fees paid: 1,704,872
- Performance fee accrued, not paid: 461,690 (watch)
- Fee drag: 2.6662 points a year
- High-water mark at the end: 112,218,174
- Gain on assets before the next performance fee: 0%

Where it flips, with the other inputs held:

- `flat_gross_return_pct` at 1.485340655: At or below this flat gross return a period, no performance fee appears: it only covers the management fee and the hurdle. Above it, one does.

## Formula

Each period: assets = opening x (1 + r) - opening x m / p. Hurdle line = mark x (1 + h)^(k / p), k periods since the last date. Hard fee = f x (assets - line) above the line. Soft fee = f x (assets - mark) above the line. Between dates that fee is an accrual inside net NAV. At a date it is paid, then mark = max(mark, assets after the fee). Flip: r* = m / p + (1 + h)^(1 / p) - 1.

## Assumptions

- Order each period: gross return, then management fee, then the performance fee accrual.
- The management fee is simple: the annual rate divided by periods_per_year, on opening assets before any accrued performance fee.
- An accrued fee stays in the fund until it is paid, so the return applies to assets before the accrual. Net NAV is assets less the accrual.
- An accrual can shrink or vanish before a date. Only the fee at a date is paid, and it is not returned after a later loss.
- The hurdle compounds at the annual rate: over k periods the line is mark x (1 + h)^(k / p), so one full year is exactly h.
- The hurdle restarts from the mark at every date. A missed year is not carried forward.
- The mark rises to the NAV after fees whenever that is above it, even in a period with no fee. With use_hwm false, it moves to the NAV after fees at every date, up or down.
- A series that ends between dates pays nothing for the last part. That accrual is shown apart and is inside net NAV, with the hurdle prorated.
- Fee drag is compound gross growth a year less compound net growth a year, with years = periods / periods_per_year.

## Not modeled

- Subscriptions and redemptions. One holder from day one, so no equalization or series accounting.
- Clawback of fees paid earlier, after a later loss.
- A hurdle that carries a missed year forward.
- Partial catch-up tiers. A soft hurdle here is a full catch-up at once.
- Expenses other than these two fees: admin, audit, financing and trading costs.
- Taxes, on the fund or on the holder.
- Rebates, fee classes and side letters.

## Call it

Every result has the same fields: summary, readings, tables, charts, flags, flips, formula, assumptions, not_modeled, hypothetical and inputs_hash. Read flags, flips and not_modeled with the summary, and pass them along together.

### HTTP

No key. The tools keep nothing you send. Send positions by POST, because a URL can land in a server log.

```bash
curl -s https://hedgefund.md/api/v1/fee_waterfall \
  -H 'content-type: application/json' \
  -d '{"nav_start":100000000,"gross_returns_pct":[5,3,4,2,-6,-5,1,-2,6,5,4,3,2,3],"periods_per_year":4,"mgmt_fee_pct_annual":2,"perf_fee_pct":20,"hurdle_pct_annual":4,"hurdle_type":"hard","crystallize_every":4,"use_hwm":true}' \
  | jq '{summary, flags, flips, not_modeled, inputs_hash}'
```

### MCP

The tool is `fee_waterfall` on the server at https://hedgefund.md/mcp.

```bash
curl -s https://hedgefund.md/mcp \
  -H 'content-type: application/json' -H 'accept: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"fee_waterfall","arguments":{"nav_start":100000000,"gross_returns_pct":[5,3,4,2,-6,-5,1,-2,6,5,4,3,2,3],"periods_per_year":4,"mgmt_fee_pct_annual":2,"perf_fee_pct":20,"hurdle_pct_annual":4,"hurdle_type":"hard","crystallize_every":4,"use_hwm":true}}}' \
  | jq '.result.structuredContent | {summary, flags, flips, not_modeled, inputs_hash}'
```

### The library

The same code on your own machine, with no dependencies, and nothing is sent anywhere.

```bash
curl -fsSL https://hedgefund.md/hedgefund.mjs -o hedgefund.mjs   # read it before you run it
node hedgefund.mjs template fee_waterfall > mine.json           # replace every number in it
node hedgefund.mjs run fee_waterfall mine.json | jq '{summary, flags, flips, not_modeled, inputs_hash}'
```

In a script:

```js
import { run, template } from "./hedgefund.mjs";

const inputs = template("fee_waterfall"); // replace every number in it
const { summary, flags, flips, not_modeled } = run("fee_waterfall", inputs);
```
