canli-validation-mcp 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canli-validation-mcp",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Check whether a backtest is real: deflated Sharpe, CSCV probability of backtest overfitting, minimum track record and backtest length, haircut Sharpe and luck-equivalent trials, as an MCP server. Local mode, hosted endpoint, signed receipts.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -58,22 +58,60 @@ export function bookSharpe({ sleeveSharpe, sleeves, correlation }) {
58
58
  return sleeveSharpe * Math.sqrt(sleeves / denominator);
59
59
  }
60
60
 
61
- /** The value no amount of breadth can exceed. Infinite only when rho <= 0. */
61
+ /**
62
+ * The most sleeves a shared correlation admits: `1 + (N-1)*rho` must stay positive, so a negative
63
+ * rho allows only N < 1 - 1/rho (rho = -0.3 allows 4). Infinite when rho >= 0, and when rho is so
64
+ * close to zero (above about -1e-16) that the cap passes 2^53 sleeves.
65
+ */
66
+ export function maxSleeves(correlation) {
67
+ if (!(correlation < 0)) return Number.POSITIVE_INFINITY;
68
+ const bound = 1 - 1 / correlation;
69
+ if (!(bound < Number.MAX_SAFE_INTEGER)) return Number.POSITIVE_INFINITY;
70
+ let n = Math.max(1, Math.ceil(bound) - 1);
71
+ // The closed form, then one step either way against floating-point rounding at the boundary.
72
+ while (n > 1 && 1 + (n - 1) * correlation <= 0) n -= 1;
73
+ while (1 + n * correlation > 0) n += 1;
74
+ return n;
75
+ }
76
+
77
+ /**
78
+ * The most a book of these sleeves can be worth. For rho > 0 it is the limit s / sqrt(rho), which
79
+ * no finite book reaches. For rho < 0 it is the book at maxSleeves, which is reached: a negative
80
+ * shared correlation caps the count, so it caps the Sharpe too. Infinite when rho is 0 (and, in
81
+ * practice, within about 1e-16 below it).
82
+ */
62
83
  export function breadthCeiling({ sleeveSharpe, correlation }) {
63
- if (correlation <= 0) return Number.POSITIVE_INFINITY;
64
- return sleeveSharpe / Math.sqrt(correlation);
84
+ if (correlation > 0) return sleeveSharpe / Math.sqrt(correlation);
85
+ const cap = maxSleeves(correlation);
86
+ if (!Number.isFinite(cap)) return Number.POSITIVE_INFINITY;
87
+ return bookSharpe({ sleeveSharpe, sleeves: cap, correlation });
65
88
  }
66
89
 
67
- /** The smallest N reaching `target`, or null when the ceiling forbids it. */
68
- export function sleevesRequired({ sleeveSharpe, correlation, target, maxSleeves = 500 }) {
90
+ /**
91
+ * The smallest N reaching `target`, or null when no admissible N does. Exact at any size: from
92
+ * s * sqrt(N / (1 + (N-1)*rho)) >= T, with k = (T/s)^2, N * (1 - k*rho) >= k * (1 - rho). A target
93
+ * of 50 from sleeves of Sharpe 1 at rho = 0.0001 needs 3,333 sleeves; a search that stopped at 500
94
+ * called it unreachable.
95
+ */
96
+ export function sleevesRequired({ sleeveSharpe, correlation, target }) {
69
97
  const ceiling = breadthCeiling({ sleeveSharpe, correlation });
70
- if (target > ceiling) return { sleeves: null, ceiling, reachable: false };
71
- for (let n = 1; n <= maxSleeves; n += 1) {
72
- if (bookSharpe({ sleeveSharpe, sleeves: n, correlation }) >= target) {
73
- return { sleeves: n, ceiling, reachable: true };
74
- }
98
+ const unreachable = { sleeves: null, ceiling, reachable: false };
99
+ if (!(sleeveSharpe > 0) || !(target > 0)) return unreachable;
100
+ if (target <= sleeveSharpe) return { sleeves: 1, ceiling, reachable: true };
101
+ // For rho > 0 the ceiling is a limit no book attains; for rho < 0 it is attained at maxSleeves.
102
+ if (correlation > 0 ? target >= ceiling : target > ceiling) return unreachable;
103
+ const k = (target / sleeveSharpe) ** 2;
104
+ const book = (n) => bookSharpe({ sleeveSharpe, sleeves: n, correlation });
105
+ let n = Math.max(1, Math.ceil((k * (1 - correlation)) / (1 - k * correlation)));
106
+ if (!Number.isSafeInteger(n)) return unreachable;
107
+ const cap = maxSleeves(correlation);
108
+ if (n > cap) n = cap;
109
+ while (n > 1 && book(n - 1) >= target) n -= 1;
110
+ while (book(n) < target) {
111
+ if (n >= cap || !Number.isSafeInteger(n + 1)) return unreachable;
112
+ n += 1;
75
113
  }
76
- return { sleeves: null, ceiling, reachable: false };
114
+ return { sleeves: n, ceiling, reachable: true };
77
115
  }
78
116
 
79
117
  /** The curve of book Sharpe against N, for plotting. */
@@ -1,7 +1,7 @@
1
1
  // js/validate/breadth.js
2
2
  // The pure computation behind POST /api/v1/validate/breadth, shared by the API route and the
3
3
  // MCP package's local mode (mcp/src/local mirrors it byte for byte), so the two cannot disagree.
4
- import { bookSharpe, breadthCeiling, ceilingCaptured, sleevesRequired } from "../breadth-core.js";
4
+ import { bookSharpe, breadthCeiling, ceilingCaptured, maxSleeves, sleevesRequired } from "../breadth-core.js";
5
5
 
6
6
  export function compute(body) {
7
7
  const sleeveSharpe = Number(body.sleeve_sharpe);
@@ -13,12 +13,40 @@ export function compute(body) {
13
13
  const target = body.target === undefined ? null : Number(body.target);
14
14
  if (target !== null && !(target > 0)) throw new RangeError("target must be a positive Sharpe");
15
15
  const ceiling = breadthCeiling({ sleeveSharpe, correlation });
16
- const out = { identity: "S_book = s_bar * sqrt(N / (1 + (N - 1) * rho_bar)); ceiling as N grows is s_bar / sqrt(rho_bar)", ceiling: Number.isFinite(ceiling) ? ceiling : null, ceiling_is_unbounded: !Number.isFinite(ceiling) };
17
- if (sleeves !== null) out.book = { sleeves, book_sharpe: bookSharpe({ sleeveSharpe, sleeves, correlation }), ceiling_captured: Number.isFinite(ceiling) ? ceilingCaptured({ sleeveSharpe, sleeves, correlation }) : null };
16
+ const cap = maxSleeves(correlation);
17
+ const bounded = Number.isFinite(ceiling);
18
+ // A negative shared correlation admits only so many sleeves, so the ceiling is the book at that
19
+ // count and is reached; a positive one gives a limit no finite book reaches; zero gives none.
20
+ const kind = !bounded ? "unbounded" : correlation > 0 ? "limit" : "maximum";
21
+ const out = {
22
+ identity: "S_book = s_bar * sqrt(N / (1 + (N - 1) * rho_bar)); for rho_bar > 0 the ceiling is the limit s_bar / sqrt(rho_bar); for rho_bar < 0 only N < 1 - 1 / rho_bar is possible, and the ceiling is the book at the largest such N",
23
+ ceiling: bounded ? ceiling : null,
24
+ ceiling_is_unbounded: !bounded,
25
+ ceiling_kind: kind,
26
+ ...(Number.isFinite(cap) ? { max_sleeves: cap } : {}),
27
+ };
28
+ if (sleeves !== null) {
29
+ if (sleeves > cap) throw new RangeError(`a shared correlation of ${correlation} admits at most ${cap} sleeves: with more, 1 + (N - 1) * rho is not positive and no real set of returns has that correlation`);
30
+ out.book = { sleeves, book_sharpe: bookSharpe({ sleeveSharpe, sleeves, correlation }), ceiling_captured: bounded ? ceilingCaptured({ sleeveSharpe, sleeves, correlation }) : null };
31
+ }
18
32
  if (target !== null) {
19
33
  const req = sleevesRequired({ sleeveSharpe, correlation, target });
20
- out.target = { target, reachable: req.reachable, sleeves_required: req.sleeves, note: req.reachable ? "At this per-sleeve quality and correlation the target is reachable with the stated sleeve count." : "No number of sleeves of this quality at this correlation reaches the target; raise per-sleeve quality or lower correlation." };
34
+ const n = req.sleeves;
35
+ out.target = {
36
+ target,
37
+ reachable: req.reachable,
38
+ sleeves_required: n,
39
+ note: req.reachable
40
+ ? `${n} sleeve${n === 1 ? "" : "s"} of this quality at this correlation reach a book Sharpe of ${target}; ${n === 1 ? "one sleeve already does" : `${n - 1} fall short`}.`
41
+ : kind === "maximum"
42
+ ? `At most ${cap} sleeves can share this correlation, and ${cap} of them reach only ${ceiling.toFixed(3)}; raise per-sleeve quality.`
43
+ : "No number of sleeves of this quality at this correlation reaches the target; raise per-sleeve quality or lower correlation.",
44
+ };
21
45
  }
22
- out.plain_reading = out.ceiling_is_unbounded ? "With average pairwise correlation at or below zero the book Sharpe has no ceiling from breadth alone; correlation this low is rare and should be checked in stress." : `Adding sleeves of this quality can never take the book above a Sharpe of ${ceiling.toFixed(3)}. Quality and correlation set the ceiling; count only approaches it.`;
46
+ out.plain_reading = kind === "unbounded"
47
+ ? "With average pairwise correlation of zero the book Sharpe grows with the square root of the sleeve count and has no ceiling from breadth alone; independence this clean is rare and should be checked in stress."
48
+ : kind === "maximum"
49
+ ? `A shared correlation of ${correlation} is possible for at most ${cap} sleeves, and ${cap} of them are worth a book Sharpe of ${ceiling.toFixed(3)}; a negative average correlation this strong is rare and should be checked in stress.`
50
+ : `Adding sleeves of this quality can never take the book above a Sharpe of ${ceiling.toFixed(3)}. Quality and correlation set the ceiling; count only approaches it.`;
23
51
  return out;
24
52
  }