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.
- package/README.md +3 -12
- package/dist/{chunk-C6LL746C.js → chunk-2I3S2ZLT.js} +259 -1
- package/dist/chunk-2I3S2ZLT.js.map +1 -0
- package/dist/{chunk-UBUC5A66.js → chunk-4HZLVUOZ.js} +81 -76
- package/dist/chunk-4HZLVUOZ.js.map +1 -0
- package/dist/{chunk-VDU25Z6X.js → chunk-NWNSKALN.js} +2 -2
- package/dist/{chunk-AP7IHUUR.js → chunk-WNTYA7ER.js} +2 -2
- package/dist/{chunk-6S37CXUA.js → chunk-YZA4RS76.js} +14 -6
- package/dist/chunk-YZA4RS76.js.map +1 -0
- package/dist/{chunk-SEUZYQGQ.js → chunk-ZTJXC3LS.js} +2 -2
- package/dist/daily-log-processor-MZW2XBZY.js +9 -0
- package/dist/market-provider-6U33TQUT.js +16 -0
- package/dist/{sync-EVLKB3ZJ.js → sync-3UBC37VW.js} +6 -6
- package/dist/test-exports.js +560 -271
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-DKDCWGKD.js +9 -0
- package/package.json +2 -6
- package/server/{chunk-YUCOAJ4Z.js → chunk-536NSFED.js} +2 -2
- package/server/{chunk-GH2552SE.js → chunk-A3UKD64A.js} +2 -2
- package/server/{chunk-ZBJCF4ZG.js → chunk-OMUDNJBJ.js} +2 -2
- package/server/{chunk-BOPHW5M6.js → chunk-RJDJHZ5Y.js} +414 -101
- package/server/chunk-RJDJHZ5Y.js.map +1 -0
- package/server/{chunk-4P7D7YZP.js → chunk-RM4B2VKS.js} +72 -71
- package/server/chunk-RM4B2VKS.js.map +1 -0
- package/server/{chunk-OBYKFW2B.js → chunk-SY6GDNVI.js} +14 -6
- package/server/chunk-SY6GDNVI.js.map +1 -0
- package/server/daily-log-processor-B2VPU2FQ.js +10 -0
- package/server/index.js +787 -552
- package/server/index.js.map +1 -1
- package/server/market-provider-L56RFHAC.js +17 -0
- package/server/{sync-V25UQJA3.js → sync-CENKDN53.js} +6 -6
- package/server/trade-processor-3PI4LHZI.js +10 -0
- package/src/db/backtest-schemas.ts +26 -4
- package/src/db/data-root.ts +22 -0
- package/src/test-exports.ts +12 -0
- package/src/tools/blocks/index.ts +2 -0
- package/src/tools/blocks/paired-comparison.ts +425 -0
- package/src/tools/imports.ts +2 -2
- package/src/utils/block-loader.ts +1 -1
- package/src/utils/calibration-probe.ts +2 -2
- package/dist/chunk-6S37CXUA.js.map +0 -1
- package/dist/chunk-C6LL746C.js.map +0 -1
- package/dist/chunk-UBUC5A66.js.map +0 -1
- package/dist/daily-log-processor-BY3ISY6K.js +0 -9
- package/dist/market-provider-H3ARIVZ4.js +0 -16
- package/dist/trade-processor-EYA3I3XB.js +0 -9
- package/manifest.json +0 -133
- package/server/chunk-4P7D7YZP.js.map +0 -1
- package/server/chunk-BOPHW5M6.js.map +0 -1
- package/server/chunk-OBYKFW2B.js.map +0 -1
- package/server/daily-log-processor-ENEUT22A.js +0 -10
- package/server/market-provider-B437HKLW.js +0 -17
- package/server/trade-processor-L3PIQ5TG.js +0 -10
- /package/dist/{chunk-VDU25Z6X.js.map → chunk-NWNSKALN.js.map} +0 -0
- /package/dist/{chunk-AP7IHUUR.js.map → chunk-WNTYA7ER.js.map} +0 -0
- /package/dist/{chunk-SEUZYQGQ.js.map → chunk-ZTJXC3LS.js.map} +0 -0
- /package/dist/{daily-log-processor-BY3ISY6K.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
- /package/dist/{market-provider-H3ARIVZ4.js.map → market-provider-6U33TQUT.js.map} +0 -0
- /package/dist/{sync-EVLKB3ZJ.js.map → sync-3UBC37VW.js.map} +0 -0
- /package/dist/{trade-processor-EYA3I3XB.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
- /package/server/{chunk-YUCOAJ4Z.js.map → chunk-536NSFED.js.map} +0 -0
- /package/server/{chunk-GH2552SE.js.map → chunk-A3UKD64A.js.map} +0 -0
- /package/server/{chunk-ZBJCF4ZG.js.map → chunk-OMUDNJBJ.js.map} +0 -0
- /package/server/{daily-log-processor-ENEUT22A.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
- /package/server/{market-provider-B437HKLW.js.map → market-provider-L56RFHAC.js.map} +0 -0
- /package/server/{sync-V25UQJA3.js.map → sync-CENKDN53.js.map} +0 -0
- /package/server/{trade-processor-L3PIQ5TG.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tradeblocks-mcp",
|
|
3
|
-
"version": "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-
|
|
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-
|
|
407
|
+
//# sourceMappingURL=chunk-536NSFED.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
dailyLogEntrySchema,
|
|
5
5
|
findMissingHeaders,
|
|
6
6
|
rawDailyLogDataSchema
|
|
7
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
23998
|
-
if (
|
|
23999
|
-
|
|
24000
|
-
|
|
24001
|
-
|
|
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
|
|
24060
|
-
|
|
24061
|
-
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
|
|
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
|
|
24069
|
-
|
|
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
|
|
24114
|
-
|
|
24115
|
-
|
|
24116
|
-
|
|
24117
|
-
|
|
24118
|
-
|
|
24119
|
-
|
|
24120
|
-
|
|
24121
|
-
|
|
24122
|
-
|
|
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
|
|
24126
|
-
const strat =
|
|
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
|
|
24130
|
-
const
|
|
24131
|
-
|
|
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-
|
|
26011
|
+
//# sourceMappingURL=chunk-RJDJHZ5Y.js.map
|