tradeblocks-mcp 3.0.3 → 3.3.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.
Files changed (67) hide show
  1. package/README.md +3 -12
  2. package/dist/{chunk-C6LL746C.js → chunk-2I3S2ZLT.js} +259 -1
  3. package/dist/chunk-2I3S2ZLT.js.map +1 -0
  4. package/dist/{chunk-UBUC5A66.js → chunk-4HZLVUOZ.js} +81 -76
  5. package/dist/chunk-4HZLVUOZ.js.map +1 -0
  6. package/dist/{chunk-VDU25Z6X.js → chunk-NWNSKALN.js} +2 -2
  7. package/dist/{chunk-AP7IHUUR.js → chunk-WNTYA7ER.js} +2 -2
  8. package/dist/{chunk-6S37CXUA.js → chunk-YZA4RS76.js} +14 -6
  9. package/dist/chunk-YZA4RS76.js.map +1 -0
  10. package/dist/{chunk-SEUZYQGQ.js → chunk-ZTJXC3LS.js} +2 -2
  11. package/dist/daily-log-processor-MZW2XBZY.js +9 -0
  12. package/dist/market-provider-6U33TQUT.js +16 -0
  13. package/dist/{sync-EVLKB3ZJ.js → sync-3UBC37VW.js} +6 -6
  14. package/dist/test-exports.js +560 -271
  15. package/dist/test-exports.js.map +1 -1
  16. package/dist/trade-processor-DKDCWGKD.js +9 -0
  17. package/package.json +2 -6
  18. package/server/{chunk-YUCOAJ4Z.js → chunk-536NSFED.js} +2 -2
  19. package/server/{chunk-GH2552SE.js → chunk-A3UKD64A.js} +2 -2
  20. package/server/{chunk-ZBJCF4ZG.js → chunk-OMUDNJBJ.js} +2 -2
  21. package/server/{chunk-BOPHW5M6.js → chunk-RJDJHZ5Y.js} +414 -101
  22. package/server/chunk-RJDJHZ5Y.js.map +1 -0
  23. package/server/{chunk-4P7D7YZP.js → chunk-RM4B2VKS.js} +72 -71
  24. package/server/chunk-RM4B2VKS.js.map +1 -0
  25. package/server/{chunk-OBYKFW2B.js → chunk-SY6GDNVI.js} +14 -6
  26. package/server/chunk-SY6GDNVI.js.map +1 -0
  27. package/server/daily-log-processor-B2VPU2FQ.js +10 -0
  28. package/server/index.js +787 -552
  29. package/server/index.js.map +1 -1
  30. package/server/market-provider-L56RFHAC.js +17 -0
  31. package/server/{sync-V25UQJA3.js → sync-CENKDN53.js} +6 -6
  32. package/server/trade-processor-3PI4LHZI.js +10 -0
  33. package/src/db/backtest-schemas.ts +26 -4
  34. package/src/db/data-root.ts +22 -0
  35. package/src/test-exports.ts +12 -0
  36. package/src/tools/blocks/index.ts +2 -0
  37. package/src/tools/blocks/paired-comparison.ts +425 -0
  38. package/src/tools/imports.ts +2 -2
  39. package/src/utils/block-loader.ts +1 -1
  40. package/src/utils/calibration-probe.ts +2 -2
  41. package/dist/chunk-6S37CXUA.js.map +0 -1
  42. package/dist/chunk-C6LL746C.js.map +0 -1
  43. package/dist/chunk-UBUC5A66.js.map +0 -1
  44. package/dist/daily-log-processor-BY3ISY6K.js +0 -9
  45. package/dist/market-provider-H3ARIVZ4.js +0 -16
  46. package/dist/trade-processor-EYA3I3XB.js +0 -9
  47. package/manifest.json +0 -133
  48. package/server/chunk-4P7D7YZP.js.map +0 -1
  49. package/server/chunk-BOPHW5M6.js.map +0 -1
  50. package/server/chunk-OBYKFW2B.js.map +0 -1
  51. package/server/daily-log-processor-ENEUT22A.js +0 -10
  52. package/server/market-provider-B437HKLW.js +0 -17
  53. package/server/trade-processor-L3PIQ5TG.js +0 -10
  54. /package/dist/{chunk-VDU25Z6X.js.map → chunk-NWNSKALN.js.map} +0 -0
  55. /package/dist/{chunk-AP7IHUUR.js.map → chunk-WNTYA7ER.js.map} +0 -0
  56. /package/dist/{chunk-SEUZYQGQ.js.map → chunk-ZTJXC3LS.js.map} +0 -0
  57. /package/dist/{daily-log-processor-BY3ISY6K.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
  58. /package/dist/{market-provider-H3ARIVZ4.js.map → market-provider-6U33TQUT.js.map} +0 -0
  59. /package/dist/{sync-EVLKB3ZJ.js.map → sync-3UBC37VW.js.map} +0 -0
  60. /package/dist/{trade-processor-EYA3I3XB.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
  61. /package/server/{chunk-YUCOAJ4Z.js.map → chunk-536NSFED.js.map} +0 -0
  62. /package/server/{chunk-GH2552SE.js.map → chunk-A3UKD64A.js.map} +0 -0
  63. /package/server/{chunk-ZBJCF4ZG.js.map → chunk-OMUDNJBJ.js.map} +0 -0
  64. /package/server/{daily-log-processor-ENEUT22A.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
  65. /package/server/{market-provider-B437HKLW.js.map → market-provider-L56RFHAC.js.map} +0 -0
  66. /package/server/{sync-V25UQJA3.js.map → sync-CENKDN53.js.map} +0 -0
  67. /package/server/{trade-processor-L3PIQ5TG.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
@@ -0,0 +1,9 @@
1
+ import {
2
+ TradeProcessor
3
+ } from "./chunk-WNTYA7ER.js";
4
+ import "./chunk-YZA4RS76.js";
5
+ import "./chunk-5WRI5ZAA.js";
6
+ export {
7
+ TradeProcessor
8
+ };
9
+ //# sourceMappingURL=trade-processor-DKDCWGKD.js.map
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "tradeblocks-mcp",
3
- "version": "3.0.3",
3
+ "version": "3.3.1",
4
4
  "description": "MCP server for options trade analysis",
5
5
  "author": "David Romeo <davidmromeo@gmail.com>",
6
6
  "type": "module",
7
- "main": "dist/skill-installer.js",
8
7
  "bin": {
9
8
  "tradeblocks-mcp": "server/index.js"
10
9
  },
@@ -130,7 +129,6 @@
130
129
  "dev": "tsup --watch",
131
130
  "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
132
131
  "verify:exports": "node scripts/verify-exports-surface.mjs",
133
- "mcpb:pack": "npm run build && node scripts/pack-mcpb.js",
134
132
  "pack:beta": "npm run build && npm pack --pack-destination .",
135
133
  "prepublishOnly": "npm run build"
136
134
  },
@@ -151,7 +149,6 @@
151
149
  "@types/express": "^5.0.6",
152
150
  "@types/jest": "^30.0.0",
153
151
  "@types/node": "^25.9.3",
154
- "archiver": "^7.0.1",
155
152
  "esbuild": "^0.27.2",
156
153
  "jest": "^30.3.0",
157
154
  "ts-jest": "^29.4.9",
@@ -165,8 +162,7 @@
165
162
  "server",
166
163
  "dist",
167
164
  "src",
168
- "agent-skills",
169
- "manifest.json"
165
+ "agent-skills"
170
166
  ],
171
167
  "keywords": [
172
168
  "mcp",
@@ -5,7 +5,7 @@ import {
5
5
  normalizeHeaders,
6
6
  rawTradeDataSchema,
7
7
  tradeSchema
8
- } from "./chunk-OBYKFW2B.js";
8
+ } from "./chunk-SY6GDNVI.js";
9
9
 
10
10
  // ../lib/models/trade.ts
11
11
  var TRADE_COLUMN_MAPPING = {
@@ -404,4 +404,4 @@ var TradeProcessor = class {
404
404
  export {
405
405
  TradeProcessor
406
406
  };
407
- //# sourceMappingURL=chunk-YUCOAJ4Z.js.map
407
+ //# sourceMappingURL=chunk-536NSFED.js.map
@@ -4,7 +4,7 @@ import {
4
4
  dailyLogEntrySchema,
5
5
  findMissingHeaders,
6
6
  rawDailyLogDataSchema
7
- } from "./chunk-OBYKFW2B.js";
7
+ } from "./chunk-SY6GDNVI.js";
8
8
 
9
9
  // ../lib/models/daily-log.ts
10
10
  var DAILY_LOG_COLUMN_MAPPING = {
@@ -327,4 +327,4 @@ var DailyLogProcessor = class {
327
327
  export {
328
328
  DailyLogProcessor
329
329
  };
330
- //# sourceMappingURL=chunk-GH2552SE.js.map
330
+ //# sourceMappingURL=chunk-A3UKD64A.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getSofrRateByKey
4
- } from "./chunk-BOPHW5M6.js";
4
+ } from "./chunk-RJDJHZ5Y.js";
5
5
  import {
6
6
  loadMddsProtoRoot
7
7
  } from "./chunk-T66KH2XH.js";
@@ -3041,4 +3041,4 @@ export {
3041
3041
  getProvider,
3042
3042
  _resetProvider
3043
3043
  };
3044
- //# sourceMappingURL=chunk-ZBJCF4ZG.js.map
3044
+ //# sourceMappingURL=chunk-OMUDNJBJ.js.map
@@ -23985,6 +23985,69 @@ function analyzeWalkForwardDegradation(trades, options) {
23985
23985
  };
23986
23986
  }
23987
23987
 
23988
+ // ../lib/calculations/trade-set-alignment.ts
23989
+ var KEY_SEPARATOR = " ";
23990
+ var REASON_EMPTY_STRATEGY = "empty or non-string strategy";
23991
+ var REASON_INVALID_DATE = "invalid dateOpened";
23992
+ function computeMatchKey(strategy, dateOpened, time) {
23993
+ if (typeof strategy !== "string" || strategy.length === 0) {
23994
+ return { key: null, reason: REASON_EMPTY_STRATEGY };
23995
+ }
23996
+ const dateKey = formatDateKey2(new Date(dateOpened));
23997
+ if (dateKey.includes("NaN")) {
23998
+ return { key: null, reason: REASON_INVALID_DATE };
23999
+ }
24000
+ const timeKey = truncateTimeToMinute(time);
24001
+ return { key: `${dateKey}${KEY_SEPARATOR}${strategy}${KEY_SEPARATOR}${timeKey}`, reason: null };
24002
+ }
24003
+ function matchTradeSets(backtestTrades, actualTrades) {
24004
+ const actualByKey = /* @__PURE__ */ new Map();
24005
+ const unusableActual = [];
24006
+ actualTrades.forEach((trade, index) => {
24007
+ const { key, reason } = computeMatchKey(
24008
+ trade.strategy,
24009
+ trade.dateOpened,
24010
+ trade.rawTimeOpened ?? trade.timeOpened
24011
+ );
24012
+ if (key === null) {
24013
+ unusableActual.push({ index, reason });
24014
+ return;
24015
+ }
24016
+ const queue = actualByKey.get(key);
24017
+ if (queue) queue.push(index);
24018
+ else actualByKey.set(key, [index]);
24019
+ });
24020
+ const matched = [];
24021
+ const unmatchedBacktestIndices = [];
24022
+ const unusableBacktest = [];
24023
+ backtestTrades.forEach((trade, index) => {
24024
+ const { key, reason } = computeMatchKey(trade.strategy, trade.dateOpened, trade.timeOpened);
24025
+ if (key === null) {
24026
+ unusableBacktest.push({ index, reason });
24027
+ return;
24028
+ }
24029
+ const queue = actualByKey.get(key);
24030
+ if (queue && queue.length > 0) {
24031
+ const actualIndex = queue.shift();
24032
+ matched.push({ backtestIndex: index, actualIndex });
24033
+ } else {
24034
+ unmatchedBacktestIndices.push(index);
24035
+ }
24036
+ });
24037
+ const unmatchedActualIndices = [];
24038
+ for (const queue of actualByKey.values()) {
24039
+ for (const idx of queue) unmatchedActualIndices.push(idx);
24040
+ }
24041
+ unmatchedActualIndices.sort((a, b) => a - b);
24042
+ return {
24043
+ matched,
24044
+ unmatchedBacktestIndices,
24045
+ unmatchedActualIndices,
24046
+ unusableBacktest,
24047
+ unusableActual
24048
+ };
24049
+ }
24050
+
23988
24051
  // ../lib/calculations/trade-matching.ts
23989
24052
  function formatDateKey2(d) {
23990
24053
  const year = d.getFullYear();
@@ -23994,11 +24057,17 @@ function formatDateKey2(d) {
23994
24057
  }
23995
24058
  function truncateTimeToMinute(time) {
23996
24059
  if (!time) return "00:00";
23997
- const parts = time.split(":");
23998
- if (parts.length >= 2) {
23999
- return `${parts[0].padStart(2, "0")}:${parts[1].padStart(2, "0")}`;
24000
- }
24001
- return "00:00";
24060
+ const match = time.trim().match(/^(\d{1,2}):(\d{2})(?::\d{2}(?:\.\d+)?)?\s*([AP]M)?$/i);
24061
+ if (!match) return "00:00";
24062
+ let hour = Number(match[1]);
24063
+ const period = match[3]?.toUpperCase();
24064
+ if (period) {
24065
+ if (hour < 1 || hour > 12) return "00:00";
24066
+ hour = hour % 12 + (period === "PM" ? 12 : 0);
24067
+ } else if (hour > 23) {
24068
+ return "00:00";
24069
+ }
24070
+ return `${String(hour).padStart(2, "0")}:${match[2]}`;
24002
24071
  }
24003
24072
  function parseHourFromTime(timeOpened) {
24004
24073
  if (!timeOpened || typeof timeOpened !== "string") return null;
@@ -24056,111 +24125,88 @@ function calculateScaledPl(btPl, actualPl, btContracts, actualContracts, scaling
24056
24125
  return { scaledBtPl, scaledActualPl };
24057
24126
  }
24058
24127
  function matchTrades(backtestTrades, actualTrades, scaling) {
24059
- const actualByKey = /* @__PURE__ */ new Map();
24060
- actualTrades.forEach((trade) => {
24061
- const dateKey = formatDateKey2(new Date(trade.dateOpened));
24062
- const timeKey = truncateTimeToMinute(trade.timeOpened);
24063
- const key = `${dateKey}|${trade.strategy}|${timeKey}`;
24064
- const existing = actualByKey.get(key) || [];
24065
- existing.push(trade);
24066
- actualByKey.set(key, existing);
24128
+ const {
24129
+ matched,
24130
+ unmatchedBacktestIndices,
24131
+ unmatchedActualIndices,
24132
+ unusableBacktest,
24133
+ unusableActual
24134
+ } = matchTradeSets(backtestTrades, actualTrades);
24135
+ const matchedTrades = matched.map(({ backtestIndex, actualIndex }) => {
24136
+ const btTrade = backtestTrades[backtestIndex];
24137
+ const actualTrade = actualTrades[actualIndex];
24138
+ const { scaledBtPl, scaledActualPl } = calculateScaledPl(
24139
+ btTrade.pl,
24140
+ actualTrade.pl,
24141
+ btTrade.numContracts,
24142
+ actualTrade.numContracts,
24143
+ scaling
24144
+ );
24145
+ return {
24146
+ date: formatDateKey2(new Date(btTrade.dateOpened)),
24147
+ strategy: btTrade.strategy,
24148
+ timeOpened: truncateTimeToMinute(btTrade.timeOpened),
24149
+ // Total slippage = actual P/L - backtest P/L (after scaling)
24150
+ totalSlippage: scaledActualPl - scaledBtPl,
24151
+ openingVix: btTrade.openingVix,
24152
+ closingVix: btTrade.closingVix,
24153
+ gap: btTrade.gap,
24154
+ movement: btTrade.movement,
24155
+ hourOfDay: parseHourFromTime(btTrade.timeOpened),
24156
+ contracts: actualTrade.numContracts
24157
+ };
24067
24158
  });
24068
- const matchedTrades = [];
24069
- let unmatchedBacktestCount = 0;
24070
- let unmatchedActualCount = actualTrades.length;
24071
- for (const btTrade of backtestTrades) {
24072
- const dateKey = formatDateKey2(new Date(btTrade.dateOpened));
24073
- const timeKey = truncateTimeToMinute(btTrade.timeOpened);
24074
- const key = `${dateKey}|${btTrade.strategy}|${timeKey}`;
24075
- const actualMatches = actualByKey.get(key);
24076
- const actualTrade = actualMatches?.[0];
24077
- if (actualTrade) {
24078
- unmatchedActualCount--;
24079
- if (actualMatches && actualMatches.length > 1) {
24080
- actualByKey.set(key, actualMatches.slice(1));
24081
- } else {
24082
- actualByKey.delete(key);
24083
- }
24084
- const { scaledBtPl, scaledActualPl } = calculateScaledPl(
24085
- btTrade.pl,
24086
- actualTrade.pl,
24087
- btTrade.numContracts,
24088
- actualTrade.numContracts,
24089
- scaling
24090
- );
24091
- const totalSlippage = scaledActualPl - scaledBtPl;
24092
- matchedTrades.push({
24093
- date: dateKey,
24094
- strategy: btTrade.strategy,
24095
- timeOpened: timeKey,
24096
- totalSlippage,
24097
- openingVix: btTrade.openingVix,
24098
- closingVix: btTrade.closingVix,
24099
- gap: btTrade.gap,
24100
- movement: btTrade.movement,
24101
- hourOfDay: parseHourFromTime(btTrade.timeOpened),
24102
- contracts: actualTrade.numContracts
24103
- });
24104
- } else {
24105
- unmatchedBacktestCount++;
24106
- }
24107
- }
24159
+ const unmatchedBacktestCount = unmatchedBacktestIndices.length + unusableBacktest.length;
24160
+ const unmatchedActualCount = unmatchedActualIndices.length + unusableActual.length;
24108
24161
  return { matchedTrades, unmatchedBacktestCount, unmatchedActualCount };
24109
24162
  }
24110
24163
 
24111
24164
  // ../lib/calculations/live-alignment.ts
24112
24165
  function matchTradesWithScaledPl(backtestTrades, actualTrades, scaling) {
24113
- const actualByKey = /* @__PURE__ */ new Map();
24114
- for (const trade of actualTrades) {
24115
- const dateKey = formatDateKey2(new Date(trade.dateOpened));
24116
- const timeKey = truncateTimeToMinute(trade.timeOpened);
24117
- const key = `${dateKey} ${trade.strategy} ${timeKey}`;
24118
- const existing = actualByKey.get(key) || [];
24119
- existing.push(trade);
24120
- actualByKey.set(key, existing);
24121
- }
24122
- const pairs = [];
24166
+ const {
24167
+ matched,
24168
+ unmatchedBacktestIndices,
24169
+ unmatchedActualIndices,
24170
+ unusableBacktest,
24171
+ unusableActual
24172
+ } = matchTradeSets(backtestTrades, actualTrades);
24173
+ const pairs = matched.map(({ backtestIndex, actualIndex }) => {
24174
+ const btTrade = backtestTrades[backtestIndex];
24175
+ const actualTrade = actualTrades[actualIndex];
24176
+ const { scaledBtPl, scaledActualPl } = calculateScaledPl(
24177
+ btTrade.pl,
24178
+ actualTrade.pl,
24179
+ btTrade.numContracts,
24180
+ actualTrade.numContracts,
24181
+ scaling
24182
+ );
24183
+ return {
24184
+ date: formatDateKey2(new Date(btTrade.dateOpened)),
24185
+ strategy: btTrade.strategy,
24186
+ scaledBtPl,
24187
+ scaledActualPl,
24188
+ slippage: scaledActualPl - scaledBtPl,
24189
+ btContracts: btTrade.numContracts,
24190
+ actualContracts: actualTrade.numContracts
24191
+ };
24192
+ });
24123
24193
  const unmatchedBacktestByStrategy = /* @__PURE__ */ new Map();
24194
+ for (const index of unmatchedBacktestIndices) {
24195
+ const strat = backtestTrades[index].strategy;
24196
+ unmatchedBacktestByStrategy.set(strat, (unmatchedBacktestByStrategy.get(strat) || 0) + 1);
24197
+ }
24198
+ for (const { index } of unusableBacktest) {
24199
+ const strat = backtestTrades[index].strategy;
24200
+ unmatchedBacktestByStrategy.set(strat, (unmatchedBacktestByStrategy.get(strat) || 0) + 1);
24201
+ }
24124
24202
  const unmatchedActualByStrategy = /* @__PURE__ */ new Map();
24125
- for (const trade of actualTrades) {
24126
- const strat = trade.strategy;
24203
+ for (const index of unmatchedActualIndices) {
24204
+ const strat = actualTrades[index].strategy;
24127
24205
  unmatchedActualByStrategy.set(strat, (unmatchedActualByStrategy.get(strat) || 0) + 1);
24128
24206
  }
24129
- for (const btTrade of backtestTrades) {
24130
- const dateKey = formatDateKey2(new Date(btTrade.dateOpened));
24131
- const timeKey = truncateTimeToMinute(btTrade.timeOpened);
24132
- const key = `${dateKey} ${btTrade.strategy} ${timeKey}`;
24133
- const actualMatches = actualByKey.get(key);
24134
- const actualTrade = actualMatches?.[0];
24135
- if (actualTrade) {
24136
- const strat = actualTrade.strategy;
24137
- const remaining = (unmatchedActualByStrategy.get(strat) || 1) - 1;
24138
- unmatchedActualByStrategy.set(strat, remaining);
24139
- if (actualMatches && actualMatches.length > 1) {
24140
- actualByKey.set(key, actualMatches.slice(1));
24141
- } else {
24142
- actualByKey.delete(key);
24143
- }
24144
- const { scaledBtPl, scaledActualPl } = calculateScaledPl(
24145
- btTrade.pl,
24146
- actualTrade.pl,
24147
- btTrade.numContracts,
24148
- actualTrade.numContracts,
24149
- scaling
24150
- );
24151
- pairs.push({
24152
- date: dateKey,
24153
- strategy: btTrade.strategy,
24154
- scaledBtPl,
24155
- scaledActualPl,
24156
- slippage: scaledActualPl - scaledBtPl,
24157
- btContracts: btTrade.numContracts,
24158
- actualContracts: actualTrade.numContracts
24159
- });
24160
- } else {
24161
- const strat = btTrade.strategy;
24162
- unmatchedBacktestByStrategy.set(strat, (unmatchedBacktestByStrategy.get(strat) || 0) + 1);
24163
- }
24207
+ for (const { index } of unusableActual) {
24208
+ const strat = actualTrades[index].strategy;
24209
+ unmatchedActualByStrategy.set(strat, (unmatchedActualByStrategy.get(strat) || 0) + 1);
24164
24210
  }
24165
24211
  return { pairs, unmatchedBacktestByStrategy, unmatchedActualByStrategy };
24166
24212
  }
@@ -24210,6 +24256,13 @@ function analyzeLiveAlignment(backtestTrades, actualTrades, options) {
24210
24256
  filteredBacktest = [];
24211
24257
  filteredActual = [];
24212
24258
  }
24259
+ const outsideOverlapBacktestCount = backtestTrades.length - filteredBacktest.length;
24260
+ const outsideOverlapActualCount = actualTrades.length - filteredActual.length;
24261
+ if (overlapRange && (outsideOverlapBacktestCount > 0 || outsideOverlapActualCount > 0)) {
24262
+ warnings.push(
24263
+ `${outsideOverlapBacktestCount} backtest trade(s) and ${outsideOverlapActualCount} actual trade(s) fall outside the shared overlap window and are excluded from alignment metrics`
24264
+ );
24265
+ }
24213
24266
  if (backtestTrades.length === 0) {
24214
24267
  warnings.push("No backtest trades provided");
24215
24268
  }
@@ -24373,6 +24426,8 @@ function analyzeLiveAlignment(backtestTrades, actualTrades, options) {
24373
24426
  const dataQuality = {
24374
24427
  backtestTradeCount: backtestTrades.length,
24375
24428
  actualTradeCount: actualTrades.length,
24429
+ outsideOverlapBacktestCount,
24430
+ outsideOverlapActualCount,
24376
24431
  matchedTradeCount: pairs.length,
24377
24432
  matchRate,
24378
24433
  overlapMonths: monthlySeries.length,
@@ -25431,6 +25486,262 @@ var REPORT_FIELDS = [
25431
25486
  }
25432
25487
  ];
25433
25488
 
25489
+ // ../lib/calculations/paired-block-bootstrap.ts
25490
+ function mulberry32(seed) {
25491
+ let state = seed >>> 0;
25492
+ return function() {
25493
+ state = state + 1831565813 >>> 0;
25494
+ let t = state;
25495
+ t = Math.imul(t ^ t >>> 15, t | 1);
25496
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
25497
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
25498
+ };
25499
+ }
25500
+ function deriveSeed(seed, blockDays) {
25501
+ return seed + Math.imul(blockDays, 2654435761) >>> 0;
25502
+ }
25503
+ function percentileOf(sorted, p) {
25504
+ const n = sorted.length;
25505
+ if (n === 1) return sorted[0];
25506
+ const rank = p * (n - 1);
25507
+ const lo = Math.floor(rank);
25508
+ const hi = Math.ceil(rank);
25509
+ if (lo === hi) return sorted[lo];
25510
+ const weight = rank - lo;
25511
+ return sorted[lo] * (1 - weight) + sorted[hi] * weight;
25512
+ }
25513
+ function observedValueMap(arm) {
25514
+ const map2 = /* @__PURE__ */ new Map();
25515
+ for (let i = 0; i < arm.index.length; i++) {
25516
+ if (arm.observedMask[i]) {
25517
+ map2.set(arm.index[i], arm.values[i]);
25518
+ }
25519
+ }
25520
+ return map2;
25521
+ }
25522
+ function isConstantArm(arm) {
25523
+ return arm !== void 0 && "constant" in arm;
25524
+ }
25525
+ function unionDays(a, b) {
25526
+ const seen = /* @__PURE__ */ new Set();
25527
+ for (const d of a) seen.add(d);
25528
+ for (const d of b) seen.add(d);
25529
+ return Array.from(seen).sort();
25530
+ }
25531
+ function buildOverlap(ambient, isObserved, deltaOf) {
25532
+ const days = [];
25533
+ const delta = [];
25534
+ const runs = [];
25535
+ let runStart = -1;
25536
+ for (const day of ambient) {
25537
+ if (isObserved(day)) {
25538
+ if (runStart === -1) runStart = days.length;
25539
+ days.push(day);
25540
+ delta.push(deltaOf(day));
25541
+ } else if (runStart !== -1) {
25542
+ runs.push({ start: runStart, length: days.length - runStart });
25543
+ runStart = -1;
25544
+ }
25545
+ }
25546
+ if (runStart !== -1) {
25547
+ runs.push({ start: runStart, length: days.length - runStart });
25548
+ }
25549
+ return { days, delta, runs };
25550
+ }
25551
+ function buildComparisonOverlap(armA, armB) {
25552
+ const mapA = observedValueMap(armA);
25553
+ if (armB === void 0 || isConstantArm(armB)) {
25554
+ const c = armB === void 0 ? 0 : armB.constant;
25555
+ return buildOverlap(
25556
+ armA.index,
25557
+ (day) => mapA.has(day),
25558
+ (day) => mapA.get(day) - c
25559
+ );
25560
+ }
25561
+ const mapB = observedValueMap(armB);
25562
+ const ambient = unionDays(armA.index, armB.index);
25563
+ return buildOverlap(
25564
+ ambient,
25565
+ (day) => mapA.has(day) && mapB.has(day),
25566
+ (day) => mapA.get(day) - mapB.get(day)
25567
+ );
25568
+ }
25569
+ function enumerateBlockStarts(runs, blockDays) {
25570
+ const starts = [];
25571
+ for (const run of runs) {
25572
+ const lastStart = run.length - blockDays;
25573
+ for (let offset = 0; offset <= lastStart; offset++) {
25574
+ starts.push(run.start + offset);
25575
+ }
25576
+ }
25577
+ return starts;
25578
+ }
25579
+ function drawResampleIndices(rng, blockStarts, blockDays, n) {
25580
+ const indices = [];
25581
+ while (indices.length < n) {
25582
+ const start = blockStarts[Math.floor(rng() * blockStarts.length)];
25583
+ for (let k = 0; k < blockDays && indices.length < n; k++) {
25584
+ indices.push(start + k);
25585
+ }
25586
+ }
25587
+ return indices;
25588
+ }
25589
+ function computeCore(params) {
25590
+ const {
25591
+ overlapDays,
25592
+ runs,
25593
+ deltaSeries,
25594
+ memberDeltas,
25595
+ extremum,
25596
+ blockDays,
25597
+ resamples,
25598
+ seed,
25599
+ ciLevel,
25600
+ statistic,
25601
+ effectiveNFloorBlocks
25602
+ } = params;
25603
+ const n = overlapDays.length;
25604
+ const effectiveN = n / blockDays;
25605
+ const takeExtremum = (values) => extremum === "min" ? Math.min(...values) : Math.max(...values);
25606
+ let point;
25607
+ if (n === 0) {
25608
+ point = null;
25609
+ } else if (memberDeltas) {
25610
+ point = takeExtremum(memberDeltas.map((d) => statistic(d)));
25611
+ } else {
25612
+ point = statistic(deltaSeries);
25613
+ }
25614
+ const blockStarts = enumerateBlockStarts(runs, blockDays);
25615
+ if (effectiveNFloorBlocks !== void 0 && effectiveN < effectiveNFloorBlocks) {
25616
+ return { point, low: null, high: null, effectiveN, status: "notComparable" };
25617
+ }
25618
+ if (blockStarts.length < 2) {
25619
+ return { point, low: null, high: null, effectiveN, status: "underpowered" };
25620
+ }
25621
+ const rng = mulberry32(deriveSeed(seed, blockDays));
25622
+ const distribution = new Array(resamples);
25623
+ for (let r = 0; r < resamples; r++) {
25624
+ const idx = drawResampleIndices(rng, blockStarts, blockDays, n);
25625
+ if (memberDeltas) {
25626
+ const stats = memberDeltas.map((d) => statistic(idx.map((i) => d[i])));
25627
+ distribution[r] = takeExtremum(stats);
25628
+ } else {
25629
+ distribution[r] = statistic(idx.map((i) => deltaSeries[i]));
25630
+ }
25631
+ }
25632
+ distribution.sort((a, b) => a - b);
25633
+ const alpha = 1 - ciLevel;
25634
+ const low = percentileOf(distribution, alpha / 2);
25635
+ const high = percentileOf(distribution, 1 - alpha / 2);
25636
+ return { point, low, high, effectiveN, status: "resolved" };
25637
+ }
25638
+ function buildSelectionOverlap(members) {
25639
+ const memberMaps = members.map((m) => {
25640
+ const overlap2 = buildComparisonOverlap(m.armA, m.armB);
25641
+ const map2 = /* @__PURE__ */ new Map();
25642
+ for (let i = 0; i < overlap2.days.length; i++) {
25643
+ map2.set(overlap2.days[i], overlap2.delta[i]);
25644
+ }
25645
+ return map2;
25646
+ });
25647
+ let ambient = [];
25648
+ for (const m of members) {
25649
+ const armBDays = isConstantArm(m.armB) || m.armB === void 0 ? [] : m.armB.index;
25650
+ ambient = unionDays(ambient, unionDays(m.armA.index, armBDays));
25651
+ }
25652
+ const overlap = buildOverlap(
25653
+ ambient,
25654
+ (day) => memberMaps.every((map2) => map2.has(day)),
25655
+ () => 0
25656
+ );
25657
+ const memberDeltas = memberMaps.map((map2) => overlap.days.map((day) => map2.get(day)));
25658
+ return { days: overlap.days, runs: overlap.runs, memberDeltas };
25659
+ }
25660
+ function holdingPeriodBlockDays(holdingPeriodsTradingDays, percentile2 = 0.95) {
25661
+ if (holdingPeriodsTradingDays.length === 0) return 1;
25662
+ const sorted = [...holdingPeriodsTradingDays].sort((a, b) => a - b);
25663
+ return Math.max(1, Math.round(percentileOf(sorted, percentile2)));
25664
+ }
25665
+ function pairedBlockBootstrap(input) {
25666
+ const blockDays = Math.max(1, Math.round(input.holdingRule.blockDays));
25667
+ const { ciLevel, resamples, seed, statistic, effectiveNFloorBlocks } = input;
25668
+ let overlapDays;
25669
+ let runs;
25670
+ let deltaSeries;
25671
+ let memberDeltas;
25672
+ let extremum;
25673
+ let selection = null;
25674
+ if (input.selectionSet) {
25675
+ const sel = buildSelectionOverlap(input.selectionSet.members);
25676
+ overlapDays = sel.days;
25677
+ runs = sel.runs;
25678
+ memberDeltas = sel.memberDeltas;
25679
+ extremum = input.selectionSet.extremum;
25680
+ deltaSeries = [];
25681
+ const memberPoints = sel.memberDeltas.map((d) => statistic(d));
25682
+ const kConsidered = memberPoints.length;
25683
+ const centroid = kConsidered > 0 ? memberPoints.reduce((s, v) => s + v, 0) / kConsidered : 0;
25684
+ let maxGap = 0;
25685
+ if (kConsidered >= 2) {
25686
+ const ordered = [...memberPoints].sort((a, b) => a - b);
25687
+ maxGap = extremum === "min" ? ordered[1] - ordered[0] : ordered[kConsidered - 1] - ordered[kConsidered - 2];
25688
+ }
25689
+ selection = { centroid, maxGap, kConsidered };
25690
+ } else {
25691
+ const overlap = buildComparisonOverlap(input.armA, input.armB);
25692
+ overlapDays = overlap.days;
25693
+ runs = overlap.runs;
25694
+ deltaSeries = overlap.delta;
25695
+ }
25696
+ const overlapWindow = overlapDays.length > 0 ? { start: overlapDays[0], end: overlapDays[overlapDays.length - 1] } : null;
25697
+ const primary = computeCore({
25698
+ overlapDays,
25699
+ runs,
25700
+ deltaSeries,
25701
+ memberDeltas,
25702
+ extremum,
25703
+ blockDays,
25704
+ resamples,
25705
+ seed,
25706
+ ciLevel,
25707
+ statistic,
25708
+ effectiveNFloorBlocks
25709
+ });
25710
+ const sensitivity = [];
25711
+ for (const multiplier of input.holdingRule.sensitivity ?? []) {
25712
+ const sensBlockDays = Math.max(1, Math.round(multiplier * blockDays));
25713
+ const sens = computeCore({
25714
+ overlapDays,
25715
+ runs,
25716
+ deltaSeries,
25717
+ memberDeltas,
25718
+ extremum,
25719
+ blockDays: sensBlockDays,
25720
+ resamples,
25721
+ seed,
25722
+ ciLevel,
25723
+ statistic,
25724
+ effectiveNFloorBlocks
25725
+ });
25726
+ sensitivity.push({
25727
+ blockDays: sensBlockDays,
25728
+ ci: { low: sens.low, high: sens.high },
25729
+ status: sens.status
25730
+ });
25731
+ }
25732
+ return {
25733
+ point: primary.point,
25734
+ ci: { level: ciLevel, low: primary.low, high: primary.high, method: "paired-day-block" },
25735
+ effectiveN: primary.effectiveN,
25736
+ overlapWindow,
25737
+ blockDays,
25738
+ sensitivity,
25739
+ selection,
25740
+ status: primary.status,
25741
+ seed
25742
+ };
25743
+ }
25744
+
25434
25745
  // ../lib/calculations/index.ts
25435
25746
  function generateDataHash(trades, dailyLogs) {
25436
25747
  const data = {
@@ -25679,6 +25990,8 @@ export {
25679
25990
  matchTrades,
25680
25991
  analyzeLiveAlignment,
25681
25992
  synthesizeEdgeDecay,
25993
+ holdingPeriodBlockDays,
25994
+ pairedBlockBootstrap,
25682
25995
  REPORTING_TRADE_COLUMN_ALIASES,
25683
25996
  isTatFormat,
25684
25997
  convertTatRowToReportingTrade,
@@ -25695,4 +26008,4 @@ decimal.js/decimal.mjs:
25695
26008
  * MIT Licence
25696
26009
  *)
25697
26010
  */
25698
- //# sourceMappingURL=chunk-BOPHW5M6.js.map
26011
+ //# sourceMappingURL=chunk-RJDJHZ5Y.js.map