Read this firstConventions
These are the mistakes that produce a plausible number rather than an error. Every one of them has been made.
Units
Rates and volatilities are decimal fractions
20% is 0.2, not 20. So is a 20% return. A volatility
of 20 is not refused, because it describes a market that moves 2000%
a year — which is a market, so the server prices it.
Time is a year fraction
Thirty days is about 0.082. One year is 1.0. Nothing
here takes a count of days or a date for an option's life.
Losses are positive
A value at risk of 0.023 is a 2.3% loss. An expected shortfall is
always at least as large as the value at risk at the same confidence; if it comes
back smaller, the arguments are not what you think they are.
A Sharpe ratio is per period
The figure people quote is annualised. An annualised 1.0 on daily data is
about 0.063 per period, and everything in the validation group takes
the per-period one. Pass periodsPerYear to get both back.
periodsPerYear is required, not assumed
252 for daily equity data, 52 for weekly, 12 for monthly. Annualising weekly data with 252 overstates volatility by a factor of about 2.2, which does not look wrong enough to notice.
Weights
Weights are not normalised for you. Weights summing to 0.98 are either a 2% cash position or a typo, and the two want opposite treatment; normalising quietly would turn the second into a plausible answer. The sum is reported on every result that takes weights, and a sum far from one is refused with the total named.
Handles
Three tools mint handles, and the three kinds are not interchangeable. Each
payload carries a kind tag, so presenting the wrong one is refused
rather than misread as an empty one of the right kind.
| Minted by | Carries | Cannot be used for |
|---|---|---|
| open_position_book | option and underlying legs, the spot, the rate | anything expecting a covariance estimate |
| estimate_return_moments | a mean vector and a covariance matrix — not the returns | the historical and drawdown tools, which read the path |
| bootstrap_discount_curve | the pillar times and their quotes | either of the above |
A handle caps at 8192 characters, which is why the moments handle
carries second moments rather than the matrix. A year of daily returns
on four assets encodes to roughly 6000 characters and five years on ten assets to
about 69,000, so a handle carrying the matrix would refuse almost every portfolio
worth asking about. A mean vector and a covariance matrix are n + n²
numbers however long the history is. A curve does fit — five pillars is 264
characters — so the curve handle carries its pillars and quotes.
What that buys: the parametric estimators run from a handle, so a matrix is sent once and then reweighted and decomposed across as many calls as you like. What it costs: the historical estimators and every drawdown statistic read the return path, and a second-moment summary has thrown it away. Those tools require the matrix and say so when handed a handle.
Failures
The specification draws a line between two kinds of failure and this server draws it in the same place, because the two have different audiences.
Protocol errors are JSON-RPC errors. They say the request was not a thing the server could act on — an unknown tool, a malformed body — and a model is unlikely to recover, because the fault is in the plumbing rather than in the arguments.
Execution errors come back as ordinary results with
isError set. They say the call arrived intact and the server
declined it, and they are addressed to the model: which field, what was wrong,
what would be accepted instead. A schema violation is one of these, not a
protocol error — a volatility given as 20 instead of
0.2 is a fixable mistake, and saying so is more useful than refusing
the request.
Absent numbers
An absent number comes back as null, and means something
specific.
- A zero rate at a curve's own reference date, because the discount factor there is one whatever the rate is, so no rate is implied.
- The half-life of a risk-neutral execution schedule, because an equal slice each period never decays and the half-life is infinite.
- A degradation slope where the in-sample metric did not vary across partitions, so there is no regression to report.
Infinity and NaN are never sent. Neither is JSON, and
a strict parser rejects the whole message over one of them — so a single
unbounded quantity would destroy every number beside it.