@coinrithm/mcp-trading 0.7.15 → 0.7.17
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/CHANGELOG.md +885 -692
- package/README.md +486 -400
- package/dist/agent/bench/bench.d.ts +59 -0
- package/dist/agent/bench/bench.js +319 -0
- package/dist/agent/bench/cassette.d.ts +97 -0
- package/dist/agent/bench/cassette.js +181 -0
- package/dist/agent/bench/costs.d.ts +28 -0
- package/dist/agent/bench/costs.js +60 -0
- package/dist/agent/bench/labelBuilder.d.ts +123 -0
- package/dist/agent/bench/labelBuilder.js +282 -0
- package/dist/agent/bench/labels.d.ts +75 -0
- package/dist/agent/bench/labels.js +259 -0
- package/dist/agent/bench/recordingClient.d.ts +51 -0
- package/dist/agent/bench/recordingClient.js +193 -0
- package/dist/agent/bench/replayClient.d.ts +41 -0
- package/dist/agent/bench/replayClient.js +262 -0
- package/dist/agent/bench/report.d.ts +115 -0
- package/dist/agent/bench/report.js +435 -0
- package/dist/agent/capitalSizing.d.ts +10 -1
- package/dist/agent/capitalSizing.js +28 -4
- package/dist/agent/cli.d.ts +25 -0
- package/dist/agent/cli.js +300 -2
- package/dist/agent/client.d.ts +14 -0
- package/dist/agent/client.js +23 -6
- package/dist/agent/configurationWarnings.js +11 -0
- package/dist/agent/decision.d.ts +24 -16
- package/dist/agent/decision.js +92 -3
- package/dist/agent/decisionValidator.d.ts +31 -0
- package/dist/agent/decisionValidator.js +148 -3
- package/dist/agent/engine.d.ts +5 -3
- package/dist/agent/engine.js +5 -3
- package/dist/agent/futuresCloseOutcome.d.ts +19 -0
- package/dist/agent/futuresCloseOutcome.js +54 -0
- package/dist/agent/futuresEligibility.d.ts +13 -0
- package/dist/agent/futuresEligibility.js +43 -5
- package/dist/agent/observe.d.ts +28 -2
- package/dist/agent/observe.js +530 -90
- package/dist/agent/pmPaperCandidates.d.ts +17 -0
- package/dist/agent/pmPaperCandidates.js +125 -0
- package/dist/agent/pmPaperCycle.d.ts +16 -0
- package/dist/agent/pmPaperCycle.js +372 -0
- package/dist/agent/pmPaperDecision.d.ts +187 -0
- package/dist/agent/pmPaperDecision.js +101 -0
- package/dist/agent/pmPaperPolicy.d.ts +71 -0
- package/dist/agent/pmPaperPolicy.js +46 -0
- package/dist/agent/prompt.d.ts +15 -1
- package/dist/agent/prompt.js +187 -9
- package/dist/agent/promptTables.d.ts +1 -0
- package/dist/agent/promptTables.js +33 -0
- package/dist/agent/providerCapabilities.d.ts +7 -0
- package/dist/agent/providerCapabilities.js +27 -1
- package/dist/agent/providers.d.ts +26 -7
- package/dist/agent/providers.js +193 -48
- package/dist/agent/resolve.js +3 -0
- package/dist/agent/runner.d.ts +17 -1
- package/dist/agent/runner.js +210 -38
- package/dist/agent/setups.d.ts +2 -1
- package/dist/agent/setups.js +17 -16
- package/dist/agent/signals.d.ts +33 -0
- package/dist/agent/signals.js +89 -0
- package/dist/agent/skill.js +46 -0
- package/dist/agent/skillValidator.js +56 -0
- package/dist/agent/strictLint.js +30 -0
- package/dist/agent/templates.js +30 -30
- package/dist/agent/types.d.ts +180 -12
- package/dist/agent/universe.d.ts +78 -0
- package/dist/agent/universe.js +215 -0
- package/dist/agent/version.d.ts +1 -1
- package/dist/agent/version.js +1 -1
- package/dist/client.d.ts +14 -0
- package/dist/client.js +31 -1
- package/dist/http.js +1 -1
- package/dist/pmPaperV2.d.ts +81 -0
- package/dist/pmPaperV2.js +53 -0
- package/dist/tools.js +84 -6
- package/package.json +100 -97
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Replay side of the bench: a real CoinRithmClient whose transport serves a
|
|
2
|
+
// cassette instead of the network.
|
|
3
|
+
//
|
|
4
|
+
// Reads are served byte-for-byte from the cassette by canonical request key.
|
|
5
|
+
// A read that is NOT in the cassette is never filled in: it answers 599
|
|
6
|
+
// not_recorded and is counted as a missing input, so a variant that needs
|
|
7
|
+
// data the recording never fetched (a different watchlist, a capability the
|
|
8
|
+
// recorded agent lacked) shows up in the report instead of silently seeing
|
|
9
|
+
// an empty market.
|
|
10
|
+
//
|
|
11
|
+
// Quotes are the one thing a cassette cannot hold, because they depend on the
|
|
12
|
+
// action the variant proposes. They are synthesized deterministically from
|
|
13
|
+
// recorded evidence (costs.ts) and every one is counted. Any other non-GET is
|
|
14
|
+
// refused with 599 bench_write_refused: replay runs dry-run, so a write
|
|
15
|
+
// attempt means a runner change the bench must surface, not forward.
|
|
16
|
+
import { CoinRithmClient } from "../client.js";
|
|
17
|
+
import { asArr, asNum, asObj, asStr } from "../extract.js";
|
|
18
|
+
import { freshnessOf } from "../pmContext.js";
|
|
19
|
+
import { canonicalRequest } from "./cassette.js";
|
|
20
|
+
import { FUTURES_SYNTHETIC_FEE_BPS, SPOT_SYNTHETIC_FEE_BPS, synthesizePmQuote, } from "./costs.js";
|
|
21
|
+
import { jsonResponse, urlOf, writeRefusedResponse, } from "./recordingClient.js";
|
|
22
|
+
export const REPLAY_BASE_URL = "https://bench-replay.invalid";
|
|
23
|
+
export const NOT_RECORDED_STATUS = 599;
|
|
24
|
+
const NULL_BODY_STATUS = new Set([204, 205, 304]);
|
|
25
|
+
// Each client gets its own credential, so observe()'s per-credential
|
|
26
|
+
// calibration cache can never hand one cassette's read to another.
|
|
27
|
+
let replayNonce = 0;
|
|
28
|
+
/**
|
|
29
|
+
* The chosen PM outcome as the recording's discover reads reported it. Every
|
|
30
|
+
* recorded discover page is searched in key order (deterministic); the first
|
|
31
|
+
* match wins. Mirrors observe()'s expandPmMarkets field fallbacks.
|
|
32
|
+
*/
|
|
33
|
+
export function findRecordedPmOutcome(cassette, source, slug, outcomeExternalMarketId) {
|
|
34
|
+
for (const r of cassette.responses) {
|
|
35
|
+
if (r.path !== "/api/agent/pm/discover" || !r.ok)
|
|
36
|
+
continue;
|
|
37
|
+
const page = asObj(r.data);
|
|
38
|
+
for (const event of asArr(page.data ?? page.markets ?? page.results)) {
|
|
39
|
+
const ev = asObj(event);
|
|
40
|
+
if ((asStr(ev.source) ?? "").toLowerCase() !== source)
|
|
41
|
+
continue;
|
|
42
|
+
if ((asStr(ev.slug) ?? "").toLowerCase() !== slug)
|
|
43
|
+
continue;
|
|
44
|
+
const outcomes = Object.hasOwn(ev, "outcomes")
|
|
45
|
+
? asArr(ev.outcomes)
|
|
46
|
+
: [ev];
|
|
47
|
+
for (const outcome of outcomes) {
|
|
48
|
+
const o = asObj(outcome);
|
|
49
|
+
const id = asStr(o.externalMarketId) ?? asStr(o.outcomeExternalMarketId);
|
|
50
|
+
if (id !== outcomeExternalMarketId)
|
|
51
|
+
continue;
|
|
52
|
+
const p = asNum(o.probability);
|
|
53
|
+
return {
|
|
54
|
+
probability: p !== undefined && p >= 0 && p <= 100 ? p : undefined,
|
|
55
|
+
freshness: freshnessOf(ev)?.status ?? "fresh",
|
|
56
|
+
eligible: ev.eligible !== false && o.eligible !== false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
/** The recorded /market price and freshness for a coin, if it was read. */
|
|
64
|
+
export function findRecordedPrice(cassette, coinId) {
|
|
65
|
+
const path = canonicalRequest("GET", `${REPLAY_BASE_URL}/api/agent/market/${encodeURIComponent(coinId)}`).path;
|
|
66
|
+
const hit = cassette.responses.find((r) => r.path === path && r.ok);
|
|
67
|
+
if (!hit)
|
|
68
|
+
return undefined;
|
|
69
|
+
const m = asObj(hit.data);
|
|
70
|
+
const price = asNum(asObj(m.price).usd);
|
|
71
|
+
return {
|
|
72
|
+
priceUsd: price !== undefined && price > 0 ? price : undefined,
|
|
73
|
+
freshness: freshnessOf(asObj(m.observation))?.status ?? "fresh",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function ineligible(venue, subject, reason, freshness = "fresh") {
|
|
77
|
+
return {
|
|
78
|
+
response: {
|
|
79
|
+
eligible: false,
|
|
80
|
+
blockReasons: [reason],
|
|
81
|
+
observation: { freshness: { status: freshness } },
|
|
82
|
+
synthetic: true,
|
|
83
|
+
},
|
|
84
|
+
evidence: { venue, subject, eligible: false, basis: reason },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function synthesizePm(cassette, body) {
|
|
88
|
+
const source = (asStr(body.source) ?? "").toLowerCase();
|
|
89
|
+
const slug = (asStr(body.slug) ?? "").toLowerCase();
|
|
90
|
+
const outcome = asStr(body.outcomeExternalMarketId) ?? "";
|
|
91
|
+
const subject = `${source}/${slug}/${outcome}`;
|
|
92
|
+
const found = findRecordedPmOutcome(cassette, source, slug, outcome);
|
|
93
|
+
if (!found || found.probability === undefined)
|
|
94
|
+
return ineligible("pm", subject, "entry_price_unavailable");
|
|
95
|
+
if (!found.eligible)
|
|
96
|
+
return ineligible("pm", subject, "recorded_outcome_ineligible");
|
|
97
|
+
// NOTE: the server also applies minEntryProbabilityPct inside the quote
|
|
98
|
+
// (blockReasons entry_below_floor). The synthesized quote does not, so the
|
|
99
|
+
// runner's own validator applies the same floor and reports
|
|
100
|
+
// pm_entry_below_floor where production reports quote_ineligible. Both
|
|
101
|
+
// reject the same action.
|
|
102
|
+
const quote = synthesizePmQuote(found.probability, asNum(body.stakeMusd) ?? 0);
|
|
103
|
+
if (!quote)
|
|
104
|
+
return ineligible("pm", subject, "synthetic_unfillable", found.freshness);
|
|
105
|
+
const freshness = { status: found.freshness };
|
|
106
|
+
return {
|
|
107
|
+
response: {
|
|
108
|
+
eligible: true,
|
|
109
|
+
blockReasons: [],
|
|
110
|
+
fillBasis: "outcome_probability",
|
|
111
|
+
side: "yes",
|
|
112
|
+
entryProbability: quote.entryProbability,
|
|
113
|
+
stakeMusd: quote.stakeMusd,
|
|
114
|
+
sharesEstimate: quote.sharesEstimate,
|
|
115
|
+
maxPayout: quote.sharesEstimate,
|
|
116
|
+
minStake: 10,
|
|
117
|
+
freshness,
|
|
118
|
+
observation: { freshness },
|
|
119
|
+
openBlocked: false,
|
|
120
|
+
openBlockReasons: [],
|
|
121
|
+
synthetic: true,
|
|
122
|
+
},
|
|
123
|
+
evidence: {
|
|
124
|
+
venue: "pm",
|
|
125
|
+
subject,
|
|
126
|
+
eligible: true,
|
|
127
|
+
basis: "recorded_discover_probability",
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function synthesizeCoin(venue, cassette, body) {
|
|
132
|
+
const coinId = asStr(body.coinId) ?? "";
|
|
133
|
+
const found = findRecordedPrice(cassette, coinId);
|
|
134
|
+
if (!found || found.priceUsd === undefined)
|
|
135
|
+
return ineligible(venue, coinId, "entry_price_unavailable");
|
|
136
|
+
const freshness = { status: found.freshness };
|
|
137
|
+
const evidence = {
|
|
138
|
+
venue,
|
|
139
|
+
subject: coinId,
|
|
140
|
+
eligible: true,
|
|
141
|
+
basis: "recorded_market_price",
|
|
142
|
+
};
|
|
143
|
+
const price = found.priceUsd;
|
|
144
|
+
if (venue === "spot") {
|
|
145
|
+
const quantity = asNum(body.quantity);
|
|
146
|
+
if (quantity === undefined || quantity <= 0)
|
|
147
|
+
return ineligible(venue, coinId, "invalid_size", found.freshness);
|
|
148
|
+
const cost = price * quantity;
|
|
149
|
+
return {
|
|
150
|
+
response: {
|
|
151
|
+
eligible: true,
|
|
152
|
+
blockReasons: [],
|
|
153
|
+
executionPrice: price,
|
|
154
|
+
estimatedCostMusd: cost,
|
|
155
|
+
estimatedFeeMusd: (cost * SPOT_SYNTHETIC_FEE_BPS) / 10_000,
|
|
156
|
+
observation: { freshness },
|
|
157
|
+
synthetic: true,
|
|
158
|
+
},
|
|
159
|
+
evidence,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const margin = asNum(body.marginMusd);
|
|
163
|
+
const leverage = asNum(body.leverage);
|
|
164
|
+
if (margin === undefined ||
|
|
165
|
+
leverage === undefined ||
|
|
166
|
+
margin <= 0 ||
|
|
167
|
+
leverage <= 0)
|
|
168
|
+
return ineligible(venue, coinId, "invalid_size", found.freshness);
|
|
169
|
+
const entryFee = (margin * leverage * FUTURES_SYNTHETIC_FEE_BPS) / 10_000;
|
|
170
|
+
return {
|
|
171
|
+
response: {
|
|
172
|
+
eligible: true,
|
|
173
|
+
blockReasons: [],
|
|
174
|
+
entryPrice: price,
|
|
175
|
+
executionModel: {
|
|
176
|
+
version: "bench_synthetic_v1",
|
|
177
|
+
feeBps: FUTURES_SYNTHETIC_FEE_BPS,
|
|
178
|
+
estimatedEntryFeeMusd: entryFee,
|
|
179
|
+
},
|
|
180
|
+
cashRequiredMusd: margin + entryFee,
|
|
181
|
+
observation: { freshness },
|
|
182
|
+
synthetic: true,
|
|
183
|
+
},
|
|
184
|
+
evidence,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function bodyOf(init) {
|
|
188
|
+
const body = init?.body;
|
|
189
|
+
if (typeof body !== "string")
|
|
190
|
+
return {};
|
|
191
|
+
try {
|
|
192
|
+
return asObj(JSON.parse(body));
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return {};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function replayResponse(hit) {
|
|
199
|
+
const text = typeof hit.data === "string" ? hit.data : JSON.stringify(hit.data);
|
|
200
|
+
return new Response(NULL_BODY_STATUS.has(hit.status) ? null : text, {
|
|
201
|
+
status: hit.status,
|
|
202
|
+
headers: { "content-type": "application/json" },
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/** A production CoinRithmClient that answers from one cassette. */
|
|
206
|
+
export class ReplayClient extends CoinRithmClient {
|
|
207
|
+
stats;
|
|
208
|
+
constructor(cassette) {
|
|
209
|
+
const stats = {
|
|
210
|
+
served: 0,
|
|
211
|
+
missing: [],
|
|
212
|
+
synthesizedQuotes: [],
|
|
213
|
+
refusedWrites: [],
|
|
214
|
+
};
|
|
215
|
+
const index = new Map(cassette.responses.map((r) => [r.key, r]));
|
|
216
|
+
const replay = async (input, init) => {
|
|
217
|
+
const req = canonicalRequest(init?.method ?? "GET", urlOf(input));
|
|
218
|
+
if (req.method === "GET") {
|
|
219
|
+
const hit = index.get(req.key);
|
|
220
|
+
if (!hit) {
|
|
221
|
+
stats.missing.push(req.key);
|
|
222
|
+
return jsonResponse(NOT_RECORDED_STATUS, {
|
|
223
|
+
error: "not_recorded",
|
|
224
|
+
key: req.key,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
// Reproduce a recorded transport failure as one (the client maps it
|
|
228
|
+
// to status 0 network_error, exactly as it did while recording).
|
|
229
|
+
if (hit.transportError)
|
|
230
|
+
throw new TypeError("recorded transport error");
|
|
231
|
+
stats.served += 1;
|
|
232
|
+
return replayResponse(hit);
|
|
233
|
+
}
|
|
234
|
+
const venue = req.path === "/api/agent/pm/quote"
|
|
235
|
+
? "pm"
|
|
236
|
+
: req.path === "/api/agent/futures/quote"
|
|
237
|
+
? "futures"
|
|
238
|
+
: req.path === "/api/agent/spot/quote"
|
|
239
|
+
? "spot"
|
|
240
|
+
: undefined;
|
|
241
|
+
if (!venue) {
|
|
242
|
+
stats.refusedWrites.push(req.key);
|
|
243
|
+
return writeRefusedResponse();
|
|
244
|
+
}
|
|
245
|
+
const synthesized = venue === "pm"
|
|
246
|
+
? synthesizePm(cassette, bodyOf(init))
|
|
247
|
+
: synthesizeCoin(venue, cassette, bodyOf(init));
|
|
248
|
+
stats.synthesizedQuotes.push(synthesized.evidence);
|
|
249
|
+
return jsonResponse(200, synthesized.response);
|
|
250
|
+
};
|
|
251
|
+
replayNonce += 1;
|
|
252
|
+
super({
|
|
253
|
+
apiKey: `bench-replay-${replayNonce}`,
|
|
254
|
+
baseUrl: REPLAY_BASE_URL,
|
|
255
|
+
fetchFn: replay,
|
|
256
|
+
// A recorded 429 is replayed once, never retried with real waits.
|
|
257
|
+
maxRetries: 0,
|
|
258
|
+
sleepFn: async () => { },
|
|
259
|
+
});
|
|
260
|
+
this.stats = stats;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ActionScore } from "./labels.js";
|
|
2
|
+
import type { DecideRouteAttempt } from "../providers.js";
|
|
3
|
+
export declare const REPORT_SCHEMA = "coinrithm.bench.report.v1";
|
|
4
|
+
export declare const TUNE_FRACTION = 0.7;
|
|
5
|
+
export declare const NULL_CALIBRATION_RESAMPLINGS = 200;
|
|
6
|
+
export declare const NULL_CALIBRATION_BOOTSTRAP = 500;
|
|
7
|
+
export declare const NULL_CALIBRATION_TARGET = 0.05;
|
|
8
|
+
export interface ActionRow {
|
|
9
|
+
/** Identity of the move without its size (see actionKey). */
|
|
10
|
+
key: string;
|
|
11
|
+
type: string;
|
|
12
|
+
accepted: boolean;
|
|
13
|
+
code?: string;
|
|
14
|
+
/** Present when the action's quote was synthesized by the replay client. */
|
|
15
|
+
quote?: "synthesized";
|
|
16
|
+
forecastProbability?: number;
|
|
17
|
+
entryProbability?: number;
|
|
18
|
+
sizeMusd?: number;
|
|
19
|
+
score?: ActionScore;
|
|
20
|
+
}
|
|
21
|
+
export interface CycleRow {
|
|
22
|
+
cassetteId: string;
|
|
23
|
+
asOf: string;
|
|
24
|
+
variant: string;
|
|
25
|
+
repeat: number;
|
|
26
|
+
decision: "act" | "skip";
|
|
27
|
+
decisionType: string;
|
|
28
|
+
modelFailed: boolean;
|
|
29
|
+
llmCallMade: boolean;
|
|
30
|
+
/** Safe router provenance, at most two attempts. Omitted on legacy rows or
|
|
31
|
+
* providers without route metadata; no inference from its absence. Error
|
|
32
|
+
* text, latency, prompts and model output are deliberately not copied. */
|
|
33
|
+
routeAttempts?: Array<Pick<DecideRouteAttempt, "provider" | "model" | "outcome" | "failureClass" | "status" | "responseSource" | "actionsStringDiagnostic" | "admissionReasons">>;
|
|
34
|
+
/** A nonstandard provider exceeded the shipped two-entry route contract. */
|
|
35
|
+
routeAttemptsTruncated?: true;
|
|
36
|
+
/**
|
|
37
|
+
* Gate trigger codes for the cycle. Tells a real setup (PRICE_BREAKOUT,
|
|
38
|
+
* MOMENTUM_TREND, ...) apart from the periodic PM wake (PM_PERIODIC). Each
|
|
39
|
+
* cycle starts from a fresh state, so a cassette with PM markets and no
|
|
40
|
+
* setup or open position CAN wake periodically.
|
|
41
|
+
*/
|
|
42
|
+
triggerCodes?: string[];
|
|
43
|
+
skipReason?: string;
|
|
44
|
+
runtimeError?: string;
|
|
45
|
+
actions: ActionRow[];
|
|
46
|
+
missingInputs: string[];
|
|
47
|
+
synthesizedQuotes: number;
|
|
48
|
+
refusedWrites: string[];
|
|
49
|
+
}
|
|
50
|
+
export interface VariantInfo {
|
|
51
|
+
name: string;
|
|
52
|
+
kind: "variant" | "baseline";
|
|
53
|
+
definitionHash?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface CiResult {
|
|
56
|
+
n: number;
|
|
57
|
+
meanA: number | null;
|
|
58
|
+
meanB: number | null;
|
|
59
|
+
meanDiff: number | null;
|
|
60
|
+
ci95: [number, number] | null;
|
|
61
|
+
excludesZero: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** Deterministic 32-bit PRNG (mulberry32). Same seed, same sequence. */
|
|
64
|
+
export declare function mulberry32(seed: number): () => number;
|
|
65
|
+
/**
|
|
66
|
+
* Percentile bootstrap CI of the mean of `diffs`. Deterministic for a given
|
|
67
|
+
* seed. n = 0 gives null; n = 1 gives a degenerate [d, d] interval.
|
|
68
|
+
*/
|
|
69
|
+
export declare function bootstrapMeanCi(diffs: number[], seed: number, resamples: number): [number, number] | null;
|
|
70
|
+
export interface NullCalibration {
|
|
71
|
+
resamplings: number;
|
|
72
|
+
/** Cassettes with at least two repeat values for this metric. */
|
|
73
|
+
usableCassettes: number;
|
|
74
|
+
falsePositives: number;
|
|
75
|
+
/** Share of seeded A/A resamplings whose 95% CI excluded 0. */
|
|
76
|
+
falsePositiveRate: number | null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Calibrated-null check of the paired bootstrap test. `repeatValues` holds,
|
|
80
|
+
* per cassette, one metric value per repeat of the SAME variant. Each of the
|
|
81
|
+
* `resamplings` draws two different repeats per cassette (seeded), treats them
|
|
82
|
+
* as A and B, and runs the same 95% CI test the comparisons use. Under this
|
|
83
|
+
* construction there is no true difference, so the share of draws whose CI
|
|
84
|
+
* excludes 0 estimates the test's false-positive rate. A draw where every
|
|
85
|
+
* difference is exactly 0 has CI [0, 0] and counts as a non-rejection.
|
|
86
|
+
*/
|
|
87
|
+
export declare function calibrateNull(repeatValues: number[][], seed: number, resamplings?: number, bootstrapResamples?: number): NullCalibration;
|
|
88
|
+
/** Jaccard similarity of two action-key sets; two empty sets count as 1. */
|
|
89
|
+
export declare function jaccard(a: string[], b: string[]): number;
|
|
90
|
+
/** Round every finite number in a JSON-like value (deterministic output). */
|
|
91
|
+
export declare function roundDeep(value: unknown, digits?: number): unknown;
|
|
92
|
+
export interface BuildReportInput {
|
|
93
|
+
cassettes: Array<{
|
|
94
|
+
id: string;
|
|
95
|
+
asOf: string;
|
|
96
|
+
}>;
|
|
97
|
+
rows: CycleRow[];
|
|
98
|
+
variants: VariantInfo[];
|
|
99
|
+
repeats: number;
|
|
100
|
+
seed: number;
|
|
101
|
+
bootstrapResamples: number;
|
|
102
|
+
fidelity: Record<string, unknown>;
|
|
103
|
+
assumptions: Record<string, unknown>;
|
|
104
|
+
}
|
|
105
|
+
export interface BenchReport {
|
|
106
|
+
schema: typeof REPORT_SCHEMA;
|
|
107
|
+
contentHash: string;
|
|
108
|
+
[key: string]: unknown;
|
|
109
|
+
}
|
|
110
|
+
/** Chronological order: asOf (parsed when possible), then id. */
|
|
111
|
+
export declare function chronological<T extends {
|
|
112
|
+
id: string;
|
|
113
|
+
asOf: string;
|
|
114
|
+
}>(cassettes: T[]): T[];
|
|
115
|
+
export declare function buildReport(input: BuildReportInput): BenchReport;
|