Unpublished source build: registry install examples below apply after publication. For now, build the checkout; see versions and local setup.
Results & the .explain() envelope
The four public roles (one grammar per role)
Every runtime operation in TotalFinance is exactly one of four roles, and each role has ONE result grammar (alignment spec Law 2 — settled by both PR-303 reviews):
| Role | Who it's for | Call shape → result |
|---|---|---|
| Facade | most users | one input object (or series + options) → plain value; the same call's .explain() → Computed<T> |
| Analysis | results whose diagnostics are inseparable (calibration, optimization, exposure, backtests, crypto carry) | one input object → Computed<T> directly — no plain form, nothing to forget |
| Artifact | reusable calibrated/stateful values (surfaces, curves, positions, streams) | factory config → immutable object with methods + versioned toJSON/fromJSON |
| Kernel | experts & internal composition | one named object for confusable finance inputs on expert subpaths; direct numeric result |
After learning one domain you can predict every other: probabilityInTheMoney({...}) is a facade (plain
number, .explain() when the risk-neutral caveat matters); calibrateSvi(...) is an analysis
(envelope always); a VolatilitySurface is an artifact; blackScholesPrice({ type, spot, strike, timeToExpiryYears, riskFreeRate, dividendYield, volatility }) is a kernel behind @insiderfinance/totalfinance/options/black-scholes.
TotalFinance has one rule for the shape of every result, and it never varies:
-
Plain call → plain value.
blackScholes.call({ … })→ a number.sharpe(returns)→ a number.rsi(closes)→ a number array. No wrapper, no ceremony — the value you asked for. -
.explain()→ the envelope. The same call with.explain()returns the richComputed<T>envelope:{ value, assumptions, diagnostics }.assumptionsechoes every convention that was applied — so a defaultedperiodsPerYear: 252orriskFreeRate: 0is disclosed, never hidden;diagnostics.warningscarries any caveats. For example:sharpe(returns); // => 1.23 sharpe.explain(returns).assumptions; // => { conventionsVersion: '0.0.1', periodsPerYear: 252, riskFreeRate: 0 } blackScholes.call.explain({ spot: 100, strike: 100, timeToExpiryYears: 0.5, riskFreeRate: 0.05, volatility: 0.2, }).diagnostics.warnings; // => [] -
Pro API → the envelope always.
option.price({ contract: contract, market: market, engine: engine }),exposure(chain, market, config),calibrateSsvi(surface)and the other analysis-rolef(subject, market, config?)functions returnComputed<T>directly — there is no plain form, because a professional result must always carry the assumptions it was computed under.
One envelope type. Computed<T> is used for every rich result across every package, so a
serialized result is self-interpreting and MCP / serialization consumers treat all results the same
way. (Warnings always live under diagnostics.warnings — never hoisted to a top-level warnings.)
Technical indicators fit the same envelope. @insiderfinance/totalfinance/technical-analysis's .explain() returns the ordinary
{ value, assumptions, diagnostics } envelope too — there is no separate SeriesResult type. Its
value is the aligned, NaN-padded series (each output lined up with its input bar), and the length of
the leading NaN warmup is reported as diagnostics.warmup — the index of the first finite bar, exactly
what a chart needs to line an indicator up with price:
const rsi = technicalAnalysis.rsi.explain(closes, { period: 14 });
rsi.value; // number[] aligned to closes; the first `rsi.diagnostics.warmup` entries are NaN
rsi.diagnostics.warmup; // => 14 (RSI-14: the first finite value sits at index 14)