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.
|
|
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
|
-
/**
|
|
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
|
|
64
|
-
|
|
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
|
-
/**
|
|
68
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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:
|
|
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
|
|
17
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
}
|