# HEDGEFUND.md, version 1

HEDGEFUND.md is a plain Markdown file that describes a book: its NAV, its limits, an assumed correlation, and sleeves. Each sleeve names one asset model, the capital it gets, and the numbers that model runs on. This spec is exact enough to write a second reader. The test vectors in section 11 check that the two agree.

**What a result is not.** A result is arithmetic on the numbers in the file. It is hypothetical. It is not a forecast, an offer, a recommendation, a record of real trading, or advice. Nothing in this format fetches data, places an order, approves anything or certifies anything. Terms: https://hedgefund.md/terms

Contents: 1 The file. 2 Frontmatter. 3 Reading the body. 4 Sections. 5 JSON blocks. 6 The parsed book. 7 Canonical JSON and the book id. 8 Check codes. 9 Running a book. 10 Checking an order. 11 Test vectors. 12 Versioning. 13 A complete example.

## 1. The file

A file is UTF-8 text, named `HEDGEFUND.md`, of at most 524288 characters (512 KB). Hosted tools stop sooner: a request body is limited to 64 KB. Line endings may be LF, CRLF or CR. A byte-order mark at the start is ignored, and so are blank lines before the frontmatter.

A reader takes in text and returns data. It never executes anything from a file, follows a link in one, or returns its prose.

```
frontmatter
free prose
## Limits       optional, one json block
## Correlation  optional, one json block
## Sleeves      required
### <sleeve id> one json block per sleeve
free prose anywhere else
```

The sections may come in any order. The order of the sleeves is the order in the file, and it counts: it is part of the book's id, and a correlation matrix follows it.

## 2. Frontmatter

The first non-blank line is `---`. The frontmatter ends at the next line that is `---` once trimmed, which must come within 64 lines. Between them, a blank line or a line whose first non-space character is `#` is skipped. Every other line is `key: value`, at most 300 characters, split at the first colon. A key is letters, digits and underscores, starting with a letter or underscore. A key may not repeat.

A value is trimmed, then read one of three ways:

- It starts with `"`: a JSON string, with nothing after it but an optional `#` comment.
- It starts with `'`: everything up to the next `'`, with nothing after it but an optional `#` comment.
- Otherwise: plain text, up to the first space or tab followed by `#`.

| Key | Required | Value |
| --- | --- | --- |
| `hedgefund` | yes | The format version: exactly `1`. |
| `name` | yes | Up to 120 characters, on one line. Trimmed. |
| `currency` | yes | Three capital letters. A label: nothing converts it. |
| `nav` | yes | A plain number above 0 and at most 1e15, matching `-?(0\|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?`. No commas, underscores or currency signs. |
| `updated` | no | A real calendar date, `YYYY-MM-DD`. Without it, `W_NO_UPDATED`. |

Any other key gives `W_UNKNOWN_FRONTMATTER` and is left out of the parsed book.

## 3. Reading the body

Lines after the frontmatter are read in order. For each line, the first rule that applies decides:

1. **Inside a fence.** A fence closes at a line with up to 3 leading spaces, then at least as many of the opening character as opened it, then only spaces and tabs. Nothing inside a fence is a heading or a comment.
2. **Inside an HTML comment.** The line is prose, and `-->` closes the comment. If the line would have opened a `json` fence, been one of the three section headings, or been a `###` heading while reading Sleeves, the file has `E_HIDDEN_BLOCK`: a file must not hide what it runs.
3. **A fence opens.** Up to 3 leading spaces, then at least 3 backticks or at least 3 tildes, then an info string. A backtick fence's info string has no backticks. The fence is `json` if the first word of the info string, in any case, is `json`. Any other fence is prose.
4. **A heading.** Up to 3 leading spaces, 1 to 6 `#`, then a space or tab, then the text, at most 300 characters on the line. A closing run of `#` set off by a space is dropped. A line with no text after the `#` is a heading with empty text.
5. **Anything else** is prose. A line that contains `<!--` with no later `-->` opens a comment from there.

A fence still open at the end of the file is `E_UNCLOSED_FENCE`. Indented code blocks, block quotes, lists and underlined headings have no meaning here. A fence inside one isn't read.

## 4. Sections

A heading of level 1 or 2 ends the section in progress. A level 2 heading whose text is `Limits`, `Correlation` or `Sleeves`, in any case, opens that section. Any other heading of level 1 or 2 opens prose. A section may appear once. A second is `E_DUPLICATE_SECTION`.

In `## Sleeves`, each level 3 heading opens a sleeve, and its text is the sleeve's id: up to 40 of `A-Z a-z 0-9 _ . -`, starting with a letter or digit. A bad id is `E_BAD_SLEEVE_ID`, and a repeated one is `E_DUPLICATE_SLEEVE`. A level 3 heading elsewhere, and any level 4 to 6 heading, is prose.

Each of `## Limits`, `## Correlation` and each sleeve holds exactly one `json` fence. None is `E_NO_BLOCK`. Two is `E_EXTRA_BLOCK`, and so is a `json` fence in `## Sleeves` before the first sleeve. A `json` fence outside these places is prose, with `W_IGNORED_BLOCK` so a reader knows it does not run.

- **Limits** holds an object of limit names and numbers (section 6). The section may be left out. With no limit that applies to a book run, `W_NO_LIMITS`.
- **Correlation** holds one number, or a matrix with one row per sleeve. Left out, it is `0.3`.
- A **sleeve** holds an object with `model`, `capital_pct`, `inputs` and optionally `assumed`. The heading is the id, so `id` may not appear in the block.

## 5. JSON blocks

A block is the lines between its fences, joined with line feeds. `E_BAD_JSON` applies if a block:

- isn't valid JSON;
- has the same key twice in one object, after unescaping (a file must not show one number and run another);
- holds a number that isn't finite, such as `1e999`;
- has a key named `__proto__`, `constructor` or `prototype`, at any depth;
- nests more than 16 levels below its top, or holds more than 400000 values.

## 6. The parsed book

Reading a file gives this object. Writing it back gives a file that reads to the same object.

| Field | Type | Meaning |
| --- | --- | --- |
| `hedgefund` | `1` | Always written. |
| `name` | string | From the frontmatter. |
| `currency` | string | From the frontmatter. |
| `nav` | number | From the frontmatter. |
| `updated` | string | Present only if the file has it. |
| `limits` | object | Always written, and may be empty. |
| `correlation` | number or matrix | Always written. `0.3` when the file has none. |
| `sleeves` | list | 1 to 40 sleeves, in file order. None is `E_NO_SLEEVES`, and over 40 is `E_TOO_MANY_SLEEVES`. |

A sleeve:

| Field | Type | Meaning |
| --- | --- | --- |
| `id` | string | From the heading. |
| `model` | string | A model's id. Up to 60 characters. Whether it exists is `E_UNKNOWN_MODEL`, checked by the checker. |
| `capital_pct` | number | Percent of NAV, 0 to 1000. |
| `inputs` | object | The model's own inputs, as written. Required: nothing is filled in from a template. |
| `assumed` | object | Optional. Left out when empty. Fills gaps where the model gives no figure of its own. |

`assumed` takes any of these, each per 100 of the sleeve's capital:

| Figure | Range | Meaning |
| --- | --- | --- |
| `vol_pct` | 0 to 1000 | Volatility a year. |
| `gross_pct` | 0 to 100000 | Gross exposure. |
| `net_pct` | -100000 to 100000 | Net exposure. |
| `tail_loss_pct` | 0 to 1000 | A tail loss, positive. |
| `liquidity_days` | 0 to 36500 | Days until the whole sleeve is reachable as cash. |
| `carry_pct` | -1000 to 1000 | Carry a year. |

**Limits.** A limit is a number from 0 to its maximum, and 0 is a real limit. An unknown name is `E_UNKNOWN_LIMIT`, so a typo can't switch a limit off. A value that isn't a number in range is `E_BAD_LIMIT`. Two kinds of run read limits, and a file can hold both, so one `## Limits` block serves a book run and `check_order`.

| Limit | Read by | Maximum | Over or under |
| --- | --- | --- | --- |
| `max_capital_pct` | book | 100000 | Capital used over it. |
| `max_gross_pct` | book, order | 100000 | Gross over it. |
| `max_net_pct` | book, order | 100000 | The size of net over it. |
| `max_vol_pct` | book | 100000 | Volatility over it. |
| `max_var99_pct` | book, order | 100000 | The one-day 99% tail over it. |
| `max_tail_sum_pct` | book | 100000 | The sum of sleeve tails over it. |
| `max_sleeve_capital_pct` | book | 100000 | Any one sleeve's capital over it. |
| `max_risk_share_pct` | book | 100000 | Any one sleeve's share of variance over it. |
| `min_liquid_1d_pct` | book | 100000 | Share of NAV reachable within 1 day under it. |
| `min_liquid_7d_pct` | book | 100000 | Within 7 days. |
| `min_liquid_30d_pct` | book | 100000 | Within 30 days. |
| `min_liquid_90d_pct` | book | 100000 | Within 90 days. |
| `min_liquid_365d_pct` | book | 100000 | Within 365 days. |
| `drawdown_stop_pct` | order | 100000 | Drawdown at or past it stops adding. |
| `max_adv_participation_pct` | order | 100000 | The order's share of daily volume over it. |
| `max_settle_days` | order | 36500 | The order's settlement days over it. |
| `max_days_to_exit` | order | 36500 | Days to exit by trading over it. |
| `min_liquidity_cover` | order | 1000 | Liquidity cover under it. |
| `needs_yes_over_pct_nav` | order | 100000 | An order larger than this needs a person's yes. |

**Correlation.** One number must be from -1 to 1 and, with n sleeves, at least -1/(n-1) to within 1e-12, or no set of returns could have it. A matrix has n rows of n numbers, each from -1 to 1, with 1 on the diagonal and symmetric, both to within 1e-9, and positive semi-definite: a Cholesky factorization of the matrix with 1e-9 added to the diagonal must not meet a pivot of 1e-14 or less. Anything else is `E_BAD_CORRELATION`. Perfectly correlated sleeves are allowed.

## 7. Canonical JSON and the book id

Canonical JSON writes a value with no spaces:

- `null`, `true`, `false` as JSON.
- A number as ECMAScript `Number::toString` does: the shortest digits that round-trip, `1e+21` for large, `1.5e-7` for small. `-0` is `0`. A value that isn't finite is an error.
- A string as `JSON.stringify` does.
- A list in order.
- An object with keys sorted by UTF-16 code unit, and any `undefined` value left out.

The sha256 is taken of the canonical text as UTF-8, written in lowercase hex.

The **book id** is `hf1_` followed by the first 24 hex characters of the sha256 of the canonical JSON of the parsed book in section 6. So prose, key order, whitespace, line endings and how a number was written make no difference, and a default written out makes no difference. A changed number, a reordered sleeve or a changed `updated` gives a new id. The id lets a person say which exact book was run. It proves nothing about who wrote it or whether its numbers are right.

## 8. Check codes

A reader returns errors and warnings, each with a code, a message, an optional line and an optional hint. Codes are stable within a format version. Messages can change. With any error, a file has no parsed book and doesn't run. A checker reads the file, then runs every sleeve, and returns `ok`, `format`, `errors`, `warnings` and `summary`, plus `book_id`, `book` and `sleeve_count` once the file has read.

| Code | Meaning |
| --- | --- |
| `E_NOT_TEXT` | The file is empty, or isn't text. |
| `E_TOO_LARGE` | The file is over the size limit. |
| `E_NO_FRONTMATTER` | The file doesn't open with a frontmatter block. |
| `E_BAD_FRONTMATTER` | A frontmatter line can't be read, is too long, or repeats a key. |
| `E_BAD_VERSION` | The frontmatter doesn't say `hedgefund: 1`. |
| `E_BAD_NAME` | `name` is missing or too long. |
| `E_BAD_CURRENCY` | `currency` is missing or isn't three capital letters. |
| `E_BAD_NAV` | `nav` is missing, or isn't a plain number above 0 and at most 1e15. |
| `E_BAD_DATE` | `updated` isn't a real date. |
| `E_UNCLOSED_FENCE` | A code fence never closes. |
| `E_HIDDEN_BLOCK` | A section, a sleeve or a `json` fence sits inside an HTML comment. |
| `E_DUPLICATE_SECTION` | Limits, Correlation or Sleeves appears twice. |
| `E_NO_BLOCK` | A section or sleeve has no `json` fence. |
| `E_EXTRA_BLOCK` | A section or sleeve has two, or Sleeves has one outside any sleeve. |
| `E_BAD_JSON` | See section 5. |
| `E_UNKNOWN_LIMIT` | The Limits block names a limit this format doesn't have. |
| `E_BAD_LIMIT` | The Limits block isn't an object, or a limit isn't a number in range. |
| `E_BAD_CORRELATION` | See section 6. |
| `E_NO_SLEEVES` | No `## Sleeves`, or no sleeve in it. |
| `E_TOO_MANY_SLEEVES` | More than 40 sleeves. |
| `E_BAD_SLEEVE_ID` | A `###` heading in Sleeves isn't a valid id. |
| `E_DUPLICATE_SLEEVE` | Two sleeves share an id. |
| `E_BAD_SLEEVE` | A sleeve's block isn't an object, has an unknown field, has `id`, or has a bad `model`, `capital_pct`, `inputs` or `assumed`. |
| `E_UNKNOWN_MODEL` | A sleeve names a model this reader doesn't have. Checker only. |
| `E_BAD_INPUTS` | A model refused a sleeve's inputs. The message is the model's own, with the sleeve's name in front. Checker only. |
| `E_MODEL_FAILED` | A model stopped on a sleeve's inputs without saying why. Checker only. No test file triggers this one: it marks a bug in the model. |
| `E_TOO_MUCH_WORK` | The book is too much work to run at once (section 9). Checker only. |
| `W_NO_LIMITS` | No limit here applies to a book run, so a run checks nothing against one. |
| `W_NO_UPDATED` | The frontmatter has no `updated`. |
| `W_UNKNOWN_FRONTMATTER` | A frontmatter key isn't part of the format. |
| `W_IGNORED_BLOCK` | A `json` fence sits outside Limits, Correlation and the sleeves. |
| `W_CAPITAL_OVER_100` | The sleeves take more than 100% of NAV. Checker only. |
| `W_CAPITAL_ZERO` | A sleeve has no capital. Checker only. |
| `W_TEMPLATE_NUMBERS` | A sleeve's `inputs` equal its model's template, or a list or object inside them does. Those numbers are made up. Checker only. |
| `W_MISSING_FIGURE` | A sleeve with capital lacks a figure, from its model and from `assumed`. One warning per figure, naming the sleeves. Checker only. |
| `W_ASSUMED_NOT_USED` | A sleeve assumes a figure its model gives, so the assumed one isn't used. Checker only. |
| `W_LIMIT_OVER` | The book as written is over a named limit. The warning carries `limit`. Checker only. |
| `W_LIMIT_NOT_CHECKED` | A limit couldn't be checked because a figure is missing. The warning carries `limit`. Checker only. |

A clean check says a file can run. It says nothing about whether its numbers are right, and nothing about whether the book is within anything but the limits it names.

## 9. Running a book

**Refusing.** A run refuses, with a message, anything section 6 refuses, an unknown model, any sleeve its model refuses (the message names the sleeve), and a book whose counted work is over 300. A sleeve's work is `loans x sims / 2800` for `private_credit_tape` (`sims` defaults to 5000), `10 + 2 x assets` for `redemption_gates`, and otherwise `2 + values / 500`, where values counts every list, object and scalar in its inputs, itself included. This counts inputs because the code reads no clock.

**Figures.** Each sleeve's model returns a `sleeve` object per 100 of capital. For each of the six figures, a run uses the model's when it is a finite number, then `assumed`, and otherwise the figure is missing. A model's figure beats an assumed one. Each result says which was which. A missing figure is never treated as zero: a sleeve with capital above 0 that lacks one is listed by name under `missing`, and any total that needs it says it covers only the rest. A sleeve with no capital needs no figures.

**Adding up.** With `w_i = capital_pct_i / 100`, and summing only over sleeves that have the figure:

```
capital used   = sum of capital_pct_i
outside        = max(0, 100 - capital used)
gross          = sum of w_i x gross_pct_i
net            = sum of w_i x net_pct_i
carry          = sum of w_i x carry_pct_i
tail sum       = sum of w_i x max(0, tail_loss_pct_i)
a_i            = w_i x vol_pct_i, or 0 if the sleeve has no vol_pct
variance       = sum over i and j of rho_ij x a_i x a_j, floored at 0, with rho_ii = 1
volatility     = sqrt(variance)
one-year tail  = z x volatility, where z = 2.3263478740408408, the normal 99% point
one-day tail   = one-year tail / sqrt(252). This is var99_pct, and what max_var99_pct reads
risk share_i   = a_i x (sum over j of rho_ij x a_j) / variance x 100, or 0 if variance is 0
biggest source = the sleeve with the largest risk share among those with a_i above 0, the first on a tie, or "none" if variance is 0
reachable(d)   = outside + capital of sleeves with liquidity_days <= d, for d in 1, 7, 30, 90, 365
```

Both tails are normal with no mean return. The tail sum adds tails with no offsets, and each model sets its own horizon, so it is a rough undiversified total. On the ladder a sleeve counts in full from its `liquidity_days` and not at all before. Capital outside the sleeves counts as cash reachable within a day. Capital over 100 raises the flag `capital_over_100` and leaves the ladder uncapped.

**Limits.** A limit is compared with a tolerance of `1e-9 x max(1, |cap|)`, and a value at exactly its cap is within it. Each set limit read by a book gets a check with status `within`, `breach` or `not_checked`:

- A max limit is `breach` if the value is over it, with or without gaps. If not, it is `not_checked` when a sleeve lacks the figure, and otherwise `within`.
- A min liquidity limit is `within` if reachable is at or above it. If not, it is `not_checked` when counting every sleeve with no `liquidity_days` in full would reach it, and otherwise `breach`.

A `breach` raises the flag `limit_<name>`, and `not_checked` raises `limit_not_checked_<name>`. No limit read by a book raises `no_limits`. Each missing figure raises `missing_<figure>`. An assumed figure a model overrides raises `assumed_not_used`. A sleeve with capital whose inputs are still its template's, in whole or in part, raises `template_numbers`, and the summary says so. A sleeve with a breach flag of its own raises `sleeve_flag`.

**Flips.** When a limit is set and the figures it needs are complete, a run solves exactly for where it changes sides:

- With one pairwise correlation and two or more sleeves, for `max_vol_pct` and `max_var99_pct`: `rho = (L^2 - S1) / S2`, where `S1` is the sum of `a_i^2`, `S2` is `(sum of a_i)^2 - S1`, and `L` is the limit in volatility terms. Given if `S2` is above 0 and `rho` is valid for that many sleeves.
- For the same limits, the biggest source's capital: the larger root of `a^2 + 2 b a + D - L^2 = 0`, with `b` the sum over other sleeves of `rho x a_j` and `D` the variance of the others, divided by its vol and multiplied by 100.
- For `max_gross_pct`, capital in the sleeve adding most gross: `capital + (cap - gross) x 100 / its gross_pct`.

A capital flip is given when it is at least 0 and at most 100, or the sleeve's capital if that is larger.

**The result.** A run returns the same shape as a model, and more:

| Field | Meaning |
| --- | --- |
| `model`, `desk` | `book` and `fund`. |
| `name`, `currency`, `nav`, `updated`, `correlation` | From the book. |
| `book_id` | Section 7. |
| `summary` | Plain sentences beginning `On these numbers,`. Never advice. |
| `readings` | `[{ key, label, value, unit?, note?, flag? }]`. `flag` is `watch` or `breach`. |
| `tables` | `[{ title, columns, rows }]`. |
| `charts` | `[{ id, title, kind, x, series: [{ name, y }], x_label?, y_label? }]`. |
| `flags` | `[{ level, code, text }]`. `level` is `watch` or `breach`. |
| `flips` | `[{ input, at, means }]`. |
| `sleeve` | The book's own figures, where complete. `tail_loss_pct` is the tail sum, and `liquidity_days` is the slowest sleeve's. |
| `totals` | `capital_used_pct`, `outside_sleeves_pct`, `gross_pct`, `net_pct`, `vol_pct`, `var99_pct`, `var99_value`, `tail99_one_year_pct`, `tail_sum_pct`, `carry_pct`. |
| `liquidity_ladder` | `[{ days, reachable_pct, from_sleeves_pct, outside_sleeves_pct, no_figure_pct }]`. |
| `limit_checks` | `[{ limit, status, value, cap, unit, text, missing?, sleeve? }]`. |
| `limits_for_orders` | Limits set here that only `check_order` reads. |
| `biggest_risk` | `{ sleeve, share_pct, capital_pct }`. |
| `risk_shares` | `[{ sleeve, share_pct }]`, for sleeves with capital and a vol figure. |
| `missing`, `complete` | The figures missing, by figure, with the sleeves' names. |
| `assumed_figures` | `[{ sleeve, figure, value }]`. |
| `for_check_order` | The `gross_pct`, `net_pct` and `var99_pct` that `check_order`'s book takes. |
| `sleeves` | Each model's result, without `formula` or `not_modeled`, plus `id`, `capital_pct` and `figures`. `figures` gives each figure's `value` and its `source`: `model`, `assumed` or `missing`. |
| `formula`, `assumptions`, `not_modeled`, `hypothetical` | How to reproduce it, and what it leaves out. |
| `inputs_hash` | The first 16 hex of the same sha256 as the book id. |

Every number in a result is cut to 10 significant digits, by the rule in section 11, so it is the same on any engine. A model's result has the same core fields: `model`, `desk`, `summary`, `readings`, `tables`, `charts`, `flags`, `flips`, `sleeve`, `formula`, `assumptions`, `not_modeled`, `hypothetical`, `inputs_hash`. Its `inputs_hash` is the first 16 hex of the sha256 of the canonical JSON of its inputs.

## 10. Checking an order

`check_order` takes `{ order, book, limits }` and returns one of four outcomes. Only `within_limits` has `needs_yes: false`.

| Outcome | Means |
| --- | --- |
| `over_limit` | A named limit would be broken. |
| `needs_a_yes` | No limit would be broken, but the order is over the approval size, or some limit couldn't be checked because a number is missing. |
| `within_limits` | Every limit that was set got checked, and none would break. |
| `no_limits_checked` | No limit that applies to an order was sent. This says nothing about the order, and it needs a person's yes. |

It takes `order`: `notional_pct_nav` and `side` (`add` or `reduce`), with optional `sleeve`, `direction` (`long` or `short`), `adv_participation_pct`, `days_to_exit`, `tokenized`, `settle_days` and `var99_add_pct`. It takes `book`: optional `gross_pct`, `net_pct`, `var99_pct`, `drawdown_pct` and `liquidity_cover`. A null counts as not sent. An unknown field anywhere, and an unknown limit, is refused. `drawdown_pct` is positive: 12 means 12% down.

With `n` the size of the order, each set limit is compared in this way:

- `max_gross_pct`: gross after is `gross + n`, or `gross - n` on a reduce. A reduce can't break it: if the book is still over, the result says so under `still_over`. A reduce larger than gross is `not_checked`, since the numbers disagree or the position flips.
- `max_net_pct`: net after is `net + n` for long and add or short and reduce, and `net - n` for short and add or long and reduce. It is compared by size. An order that doesn't raise the size of net can't break it. With no `direction` both ways are worked out: both breaking is `over`, neither is within, and one is `not_checked`.
- `max_var99_pct`: tail after is `tail + var99_add_pct` floored at 0 when that is sent, and otherwise `tail x gross after / gross`, which assumes the order carries the book's average tail per unit of gross. An order that doesn't raise the tail can't break it. With no usable figure it is `not_checked`.
- `drawdown_stop_pct`: drawdown at or past the stop breaks it on an add. A reduce isn't caught, and `still_over` says so.
- `max_adv_participation_pct`, `max_settle_days`, `max_days_to_exit`: the order's own number, on either side. Not sent is `not_checked`.
- `min_liquidity_cover`: cover under it breaks it on an add. A reduce isn't caught, and `still_over` says so. Cover is read as it stands, since how much the order draws on it isn't known.
- `needs_yes_over_pct_nav`: an order larger than this needs a yes, on either side.

A book number needed by a limit and not sent is `not_checked`. A limit that only a book run reads is set aside with a note.

The result has `summary`, `outcome`, `needs_yes`, `needs_yes_because` (whenever `needs_yes` is true), `order`, `after`, `checked`, `not_checked`, `not_set`, `still_over`, `readings`, `tables`, `flags`, `notes`, `checks_only`, `formula`, `assumptions`, `not_modeled`, `hypothetical` and `inputs_hash`. Each entry of `checked` and `not_checked` has `citations`: `[{ path, value }]`, or `[{ path, missing: true }]`, naming the numbers it rests on. An entry of `checked` has `status`: `within`, `over` or, for the approval size, `needs_a_yes`.

The result never says approved, cleared or safe. It checks only what it was told, and the caller could be wrong.

## 11. Test vectors

`https://hedgefund.md/vectors.json` holds inputs and the exact outputs of this engine:

| Key | Holds |
| --- | --- |
| `format`, `engine` | The format version and the engine version that wrote them. |
| `hashing` | `sha256` of strings. |
| `canonical` | Canonical JSON of values. |
| `math` | `ln`, `exp`, `normCdf` and `normInv` at given points, to every bit. |
| `rng` | The first draws of the seeded generator. |
| `models` | Every model's template, its `inputs_hash`, and the sha256 of the canonical JSON of its result. |
| `books` | Each starter book, its id, the sha256 of its file, and the sha256 of the canonical JSON of its result. |
| `files` | Files and the codes a check gives: one for each code a file can trigger, and some good ones. `pad`, when present, means a text of that many spaces. |
| `orders` | Orders, the outcome of each, and the sha256 of the canonical JSON of its result. |

The result hashes hold on any engine because every number comes from `+ - * /`, `sqrt` and integer operations, which IEEE 754 fixes. `ln` and `exp` are fdlibm's, written out in the library, and every other function is built from them. Results are rounded by this rule, in decimal, before they are written:

```
sig(x, 10): with d the decimal exponent of |x|, found by dividing or multiplying by 10 in a loop,
  places = 9 - d. If places >= 0, round(x, places), else round to the nearest 10^-places.
round(x, p): v = x x 10^p, with 10^p built by repeated multiplication.
  r = floor(v + 0.5) for v >= 0, -floor(-v + 0.5) for v < 0. The result is r / 10^p.
```

The generator is xoshiro128\*\*, with its state set to the first four big-endian 32-bit words of the sha256 of `seed/stream`, and normals are the inverse normal CDF of its uniforms.

A second reader of the format needs sections 1 to 8 and the `files` vectors. A second implementation of a model needs that model's formula and assumptions, which every result states, and the library is the reference for the rest.

A hash that differs from a vector means an engine or an implementation disagrees. The library recomputes them all: `node hedgefund.mjs selftest vectors.json`, or `runVectors(vectors)` from any page.

## 12. Versioning

`hedgefund: 1` is this format. Within it, a code keeps its meaning, a limit keeps its name, and a book keeps its id. New limits, new warnings and new models may be added, so a reader meets `E_UNKNOWN_MODEL` or `E_UNKNOWN_LIMIT` for what it doesn't know yet, and a warning can appear on a file that had none. A change that would alter what an existing file means gets a new `hedgefund:` number.

The engine's version is separate. A model's numbers can change between engine versions when a method is corrected, and the changelog says so. A result hash covers every field, words included, so changed wording changes it too. The vectors name the engine that wrote them, and a result reproduces only against the same engine version.

## 13. A complete example

Every number here is made up. This file checks clean, and its id is `hf1_a8d03c7f15e6ed48f60b98ee`.

````markdown
---
hedgefund: 1
name: Example book
currency: USD
nav: 1000000
updated: 2026-11-01
---

# Example book

A made-up example. Every number is made up.

The numbers in this file are the author's own. A run describes only these numbers. It is not a forecast, an offer, a record of real trading or advice. It isn't a fund document, and it isn't for investors or clients. Format: https://hedgefund.md/spec.md Terms: https://hedgefund.md/terms

## Limits

```json
{ "max_gross_pct": 150, "max_vol_pct": 12, "min_liquid_30d_pct": 50 }
```

## Correlation

```json
0.3
```

## Sleeves

### sleeve_a

```json
{
  "model": "merger_spread",
  "capital_pct": 40,
  "assumed": { "vol_pct": 8, "liquidity_days": 10 },
  "inputs": {
    "deal_cash": 50,
    "target_price": 47,
    "break_price": 35,
    "days_to_close": 120,
    "financing_rate_pct": 5
  }
}
```

### sleeve_b

```json
{
  "model": "size_position",
  "capital_pct": 30,
  "assumed": { "tail_loss_pct": 5, "carry_pct": 2 },
  "inputs": {
    "nav": 1000000,
    "vol_pct": 25,
    "expected_return_pct": 8,
    "risk_budget_pct_nav": 1,
    "adv_value": 500000,
    "max_participation_pct": 10,
    "days_to_build": 5
  }
}
```
````

Running it gives these totals, each in percent of NAV except `var99_value`:

| Total | Value |
| --- | --- |
| `capital_used_pct` | 70 |
| `gross_pct` | 41.2 |
| `net_pct` | 41.2 |
| `vol_pct` | 3.302423353 |
| `var99_pct` | 0.4839573995 |
| `var99_value` | 4839.573995 |
| `tail_sum_pct` | 11.71276 |
| `carry_pct` | 6.36596 |
