@tradejs/cli 3.1.29-beta.266 → 3.1.29-beta.269
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/dist/cli.js +194 -13
- package/dist/lib/aiPocketSearch.js +13 -0
- package/dist/lib/aiPocketSearchCli.js +68 -1
- package/dist/lib/coreResearch.js +29 -8
- package/dist/scripts/aiPocketSearch.js +163 -4
- package/dist/scripts/coreResearch.js +29 -8
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -1565,7 +1565,7 @@ var init_aiExport2 = __esm({
|
|
|
1565
1565
|
});
|
|
1566
1566
|
|
|
1567
1567
|
// src/lib/aiPocketSearchCli.ts
|
|
1568
|
-
var DAY_MS, DEFAULT_MIN_SUPPORT, LOW_CADENCE_EVENT_LIMIT, LOW_CADENCE_MAX_EVENT_SHARE, countIndependentEvents, getPeriodDays, clampInt, resolveAiPocketCadenceProfile, AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS, splitAiPocketResearchRowsByTimestamp, sealAiPocketTestPartition, readAiPocketSearchCliOption;
|
|
1568
|
+
var DAY_MS, DEFAULT_MIN_SUPPORT, LOW_CADENCE_EVENT_LIMIT, LOW_CADENCE_MAX_EVENT_SHARE, countIndependentEvents, getPeriodDays, clampInt, resolveAiPocketCadenceProfile, AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS, parseAiPocketTestSince, classifyAiPocketLabelHorizon, splitAiPocketResearchRowsByTimestamp, sealAiPocketTestPartition, readAiPocketSearchCliOption;
|
|
1569
1569
|
var init_aiPocketSearchCli = __esm({
|
|
1570
1570
|
"src/lib/aiPocketSearchCli.ts"() {
|
|
1571
1571
|
"use strict";
|
|
@@ -1660,7 +1660,70 @@ var init_aiPocketSearchCli = __esm({
|
|
|
1660
1660
|
validationSplit: "0",
|
|
1661
1661
|
testSplit: "0.4"
|
|
1662
1662
|
};
|
|
1663
|
-
|
|
1663
|
+
parseAiPocketTestSince = (value) => {
|
|
1664
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
1665
|
+
const numeric = typeof value === "number" || /^\d+$/.test(text);
|
|
1666
|
+
const iso = text.match(
|
|
1667
|
+
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/
|
|
1668
|
+
);
|
|
1669
|
+
const validCalendar = iso != null && Number(iso[2]) >= 1 && Number(iso[2]) <= 12 && Number(iso[3]) >= 1 && Number(iso[3]) <= new Date(Date.UTC(Number(iso[1]), Number(iso[2]), 0)).getUTCDate() && Number(iso[4]) < 24 && Number(iso[5]) < 60 && Number(iso[6] ?? 0) < 60;
|
|
1670
|
+
const timestamp = numeric ? Number(value) : validCalendar ? Date.parse(text) : NaN;
|
|
1671
|
+
if (!Number.isSafeInteger(timestamp) || timestamp <= 0 || timestamp > 864e13) {
|
|
1672
|
+
throw new Error("--testSince requires a valid UTC timestamp or epoch ms");
|
|
1673
|
+
}
|
|
1674
|
+
return timestamp;
|
|
1675
|
+
};
|
|
1676
|
+
classifyAiPocketLabelHorizon = (decisionTimestamp, exitTimestamp, cutoff) => {
|
|
1677
|
+
if (decisionTimestamp >= cutoff) return null;
|
|
1678
|
+
if (typeof exitTimestamp !== "number" || !Number.isFinite(exitTimestamp)) {
|
|
1679
|
+
return "unknown";
|
|
1680
|
+
}
|
|
1681
|
+
if (exitTimestamp < decisionTimestamp) {
|
|
1682
|
+
throw new Error(
|
|
1683
|
+
"--testSince requires label exitTimestamp >= decision timestamp"
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
return exitTimestamp >= cutoff ? "crossing" : null;
|
|
1687
|
+
};
|
|
1688
|
+
splitAiPocketResearchRowsByTimestamp = (rows, validationSplit, testSplit = 0, testSince) => {
|
|
1689
|
+
if (testSince != null) {
|
|
1690
|
+
const cutoff = parseAiPocketTestSince(testSince);
|
|
1691
|
+
if (validationSplit !== 0) {
|
|
1692
|
+
throw new Error("--testSince requires --validationSplit 0");
|
|
1693
|
+
}
|
|
1694
|
+
const trainRows = [];
|
|
1695
|
+
const testRows = [];
|
|
1696
|
+
const labelHorizonPurge = {
|
|
1697
|
+
crossing: 0,
|
|
1698
|
+
unknown: 0
|
|
1699
|
+
};
|
|
1700
|
+
for (const row of rows) {
|
|
1701
|
+
if (typeof row.timestamp !== "number" || !Number.isFinite(row.timestamp)) {
|
|
1702
|
+
throw new Error("--testSince requires a finite timestamp on every row");
|
|
1703
|
+
}
|
|
1704
|
+
const purge = classifyAiPocketLabelHorizon(
|
|
1705
|
+
row.timestamp,
|
|
1706
|
+
row.exitTimestamp,
|
|
1707
|
+
cutoff
|
|
1708
|
+
);
|
|
1709
|
+
if (purge) {
|
|
1710
|
+
labelHorizonPurge[purge] += 1;
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
(row.timestamp < cutoff ? trainRows : testRows).push(row);
|
|
1714
|
+
}
|
|
1715
|
+
if (!trainRows.length || !testRows.length) {
|
|
1716
|
+
throw new Error(
|
|
1717
|
+
"--testSince requires non-empty development and test partitions"
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
return {
|
|
1721
|
+
trainRows,
|
|
1722
|
+
validationRows: [],
|
|
1723
|
+
testRows,
|
|
1724
|
+
labelHorizonPurge
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1664
1727
|
const timestamps = [
|
|
1665
1728
|
...new Set(
|
|
1666
1729
|
rows.map((row) => row.timestamp).filter((timestamp) => Number.isFinite(timestamp))
|
|
@@ -2043,6 +2106,12 @@ var init_commandOptions = __esm({
|
|
|
2043
2106
|
if (sealTest && testSplit <= 0) {
|
|
2044
2107
|
throw new Error("--sealTest requires a positive --testSplit");
|
|
2045
2108
|
}
|
|
2109
|
+
const explicitTestSince = hasCliOption2(argv2, "testSince");
|
|
2110
|
+
const testSinceInput = explicitTestSince ? readAiPocketSearchCliOption({ argv: argv2, longName: "testSince" }) : flags35.testSince;
|
|
2111
|
+
const testSince = explicitTestSince || testSinceInput != null && testSinceInput !== "" ? parseAiPocketTestSince(testSinceInput) : null;
|
|
2112
|
+
if (testSince != null && validationSplit !== 0) {
|
|
2113
|
+
throw new Error("--testSince requires --validationSplit 0");
|
|
2114
|
+
}
|
|
2046
2115
|
const maxEventCountShare = normalizeUnitRatio(
|
|
2047
2116
|
readAiPocketSearchCliOption({
|
|
2048
2117
|
argv: argv2,
|
|
@@ -2083,6 +2152,7 @@ var init_commandOptions = __esm({
|
|
|
2083
2152
|
maxCombinations: normalizePositiveInt(flags35.maxCombinations, 6e4),
|
|
2084
2153
|
validationSplit,
|
|
2085
2154
|
testSplit,
|
|
2155
|
+
testSince,
|
|
2086
2156
|
sealTest,
|
|
2087
2157
|
explicitMinValidationSupport: normalizeInt(flags35.minValidationSupport, 0),
|
|
2088
2158
|
explicitMinEvents: normalizeInt(flags35.minEvents, 0),
|
|
@@ -4079,6 +4149,19 @@ var init_markdownReport = __esm({
|
|
|
4079
4149
|
["test_rows", run.testRows],
|
|
4080
4150
|
["validation_split", formatMdPercent(run.validationSplit)],
|
|
4081
4151
|
["test_split", formatMdPercent(run.testSplit)],
|
|
4152
|
+
["partition_mode", run.partitionMode ?? "timestamp-ratio"],
|
|
4153
|
+
[
|
|
4154
|
+
"purged_development_crossing_labels",
|
|
4155
|
+
run.labelHorizonPurge?.crossing ?? 0
|
|
4156
|
+
],
|
|
4157
|
+
[
|
|
4158
|
+
"purged_development_unknown_labels",
|
|
4159
|
+
run.labelHorizonPurge?.unknown ?? 0
|
|
4160
|
+
],
|
|
4161
|
+
[
|
|
4162
|
+
"test_since",
|
|
4163
|
+
run.testSince == null ? "n/a" : new Date(run.testSince).toISOString()
|
|
4164
|
+
],
|
|
4082
4165
|
["test_sealed", run.sealedTest?.sealed ? "yes" : "no"],
|
|
4083
4166
|
["sealed_test_rows", run.sealedTest?.rows ?? run.testRows],
|
|
4084
4167
|
["sealed_test_events", run.sealedTest?.events ?? "n/a"],
|
|
@@ -4855,6 +4938,11 @@ var init_presenter = __esm({
|
|
|
4855
4938
|
["max_combinations", import_chalk4.default.magenta(String(maxCombinations))],
|
|
4856
4939
|
["validation_split", import_chalk4.default.magenta(formatRatio(validationSplit))],
|
|
4857
4940
|
["test_split", import_chalk4.default.magenta(formatRatio(testSplit))],
|
|
4941
|
+
["partition_mode", run.partitionMode ?? "timestamp-ratio"],
|
|
4942
|
+
[
|
|
4943
|
+
"test_since",
|
|
4944
|
+
run.testSince == null ? "n/a" : new Date(run.testSince).toISOString()
|
|
4945
|
+
],
|
|
4858
4946
|
[
|
|
4859
4947
|
"test_evidence",
|
|
4860
4948
|
sealedTest.sealed ? import_chalk4.default.green(
|
|
@@ -5248,6 +5336,7 @@ var init_dataset = __esm({
|
|
|
5248
5336
|
import_registry = require("@tradejs/node/registry");
|
|
5249
5337
|
init_aiPocketSearch();
|
|
5250
5338
|
init_aiTrainDataset();
|
|
5339
|
+
init_aiPocketSearchCli();
|
|
5251
5340
|
normalizeQuality = (analysis) => {
|
|
5252
5341
|
const quality = Number(analysis?.quality);
|
|
5253
5342
|
return Number.isFinite(quality) ? Math.round(quality) : null;
|
|
@@ -5412,6 +5501,10 @@ var init_dataset = __esm({
|
|
|
5412
5501
|
let failed = 0;
|
|
5413
5502
|
const errors = [];
|
|
5414
5503
|
const rows = [];
|
|
5504
|
+
const labelHorizonPurge = {
|
|
5505
|
+
crossing: 0,
|
|
5506
|
+
unknown: 0
|
|
5507
|
+
};
|
|
5415
5508
|
const excludedFeaturePaths = /* @__PURE__ */ new Map();
|
|
5416
5509
|
await (0, import_ai4.streamAiDatasetRows)({
|
|
5417
5510
|
filePaths: prepared.filePaths,
|
|
@@ -5422,12 +5515,43 @@ var init_dataset = __esm({
|
|
|
5422
5515
|
if (resolvedStrategyName === "unknown" && typeof row.strategyName === "string" && row.strategyName.trim()) {
|
|
5423
5516
|
resolvedStrategyName = row.strategyName.trim();
|
|
5424
5517
|
}
|
|
5518
|
+
if (options.testSince != null && (typeof row.timestamp !== "number" || !Number.isFinite(row.timestamp))) {
|
|
5519
|
+
throw new Error(
|
|
5520
|
+
"--testSince requires a finite numeric timestamp on every export row"
|
|
5521
|
+
);
|
|
5522
|
+
}
|
|
5425
5523
|
const timestamp = getDatasetRowTimestamp(row);
|
|
5426
5524
|
if (prepared.sinceTimestamp != null && (timestamp == null || timestamp < prepared.sinceTimestamp) || options.untilTimestamp != null && (timestamp == null || timestamp > options.untilTimestamp)) {
|
|
5427
5525
|
dateSkipped += 1;
|
|
5428
5526
|
onProgress?.({ symbol: row.symbol, status: "date-skip" });
|
|
5429
5527
|
return;
|
|
5430
5528
|
}
|
|
5529
|
+
if (options.testSince != null) {
|
|
5530
|
+
const purge = classifyAiPocketLabelHorizon(
|
|
5531
|
+
timestamp,
|
|
5532
|
+
row.tradeResult?.exitTimestamp,
|
|
5533
|
+
options.testSince
|
|
5534
|
+
);
|
|
5535
|
+
if (purge) {
|
|
5536
|
+
labelHorizonPurge[purge] += 1;
|
|
5537
|
+
return;
|
|
5538
|
+
}
|
|
5539
|
+
if (options.sealTest && timestamp >= options.testSince) {
|
|
5540
|
+
rows.push({
|
|
5541
|
+
signalId: row.signalId,
|
|
5542
|
+
strategy: row.strategyName,
|
|
5543
|
+
symbol: row.symbol,
|
|
5544
|
+
direction: row.direction,
|
|
5545
|
+
timestamp,
|
|
5546
|
+
profit: 0,
|
|
5547
|
+
profitableTrade: false,
|
|
5548
|
+
aiApproved: false,
|
|
5549
|
+
quality: null,
|
|
5550
|
+
features: {}
|
|
5551
|
+
});
|
|
5552
|
+
return;
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5431
5555
|
try {
|
|
5432
5556
|
const signal = extractSignalFromAiDatasetRow(row);
|
|
5433
5557
|
const payload = (0, import_ai5.buildAiPayload)(signal);
|
|
@@ -5457,6 +5581,7 @@ var init_dataset = __esm({
|
|
|
5457
5581
|
symbol: row.symbol,
|
|
5458
5582
|
direction: row.direction,
|
|
5459
5583
|
timestamp,
|
|
5584
|
+
...options.testSince != null ? { exitTimestamp: row.tradeResult?.exitTimestamp } : {},
|
|
5460
5585
|
profit,
|
|
5461
5586
|
profitableTrade: profit > 0,
|
|
5462
5587
|
aiApproved: isAiApproval(row, analysis, options.minQuality),
|
|
@@ -5479,6 +5604,7 @@ var init_dataset = __esm({
|
|
|
5479
5604
|
}
|
|
5480
5605
|
});
|
|
5481
5606
|
return {
|
|
5607
|
+
labelHorizonPurge,
|
|
5482
5608
|
rows,
|
|
5483
5609
|
resolvedStrategyName,
|
|
5484
5610
|
scanned,
|
|
@@ -5915,7 +6041,8 @@ var init_research = __esm({
|
|
|
5915
6041
|
runAiPocketSearchResearch = ({
|
|
5916
6042
|
rows,
|
|
5917
6043
|
options,
|
|
5918
|
-
onProgress
|
|
6044
|
+
onProgress,
|
|
6045
|
+
datasetLabelHorizonPurge
|
|
5919
6046
|
}) => {
|
|
5920
6047
|
const {
|
|
5921
6048
|
direction,
|
|
@@ -5923,6 +6050,7 @@ var init_research = __esm({
|
|
|
5923
6050
|
qualityThresholds,
|
|
5924
6051
|
validationSplit,
|
|
5925
6052
|
testSplit,
|
|
6053
|
+
testSince,
|
|
5926
6054
|
sealTest,
|
|
5927
6055
|
cadenceMode,
|
|
5928
6056
|
explicitMinSupport,
|
|
@@ -5947,14 +6075,30 @@ var init_research = __esm({
|
|
|
5947
6075
|
coverageMode
|
|
5948
6076
|
} = options;
|
|
5949
6077
|
const directionRows = direction ? rows.filter((row) => row.direction === direction) : rows;
|
|
5950
|
-
const
|
|
5951
|
-
|
|
6078
|
+
const calendarSplit = testSince != null ? splitAiPocketResearchRowsByTimestamp(
|
|
6079
|
+
rows,
|
|
6080
|
+
validationSplit,
|
|
6081
|
+
testSplit,
|
|
6082
|
+
testSince
|
|
6083
|
+
) : null;
|
|
6084
|
+
const inDirection = (row) => !direction || row.direction === direction;
|
|
6085
|
+
const fullSplit = calendarSplit ? {
|
|
6086
|
+
trainRows: calendarSplit.trainRows.filter(inDirection),
|
|
6087
|
+
validationRows: calendarSplit.validationRows.filter(inDirection),
|
|
6088
|
+
testRows: calendarSplit.testRows.filter(inDirection)
|
|
6089
|
+
} : splitAiPocketResearchRowsByTimestamp(
|
|
5952
6090
|
directionRows,
|
|
5953
6091
|
validationSplit,
|
|
5954
6092
|
testSplit
|
|
5955
6093
|
);
|
|
6094
|
+
if (testSince != null && (!fullSplit.trainRows.length || !fullSplit.testRows.length)) {
|
|
6095
|
+
throw new Error(
|
|
6096
|
+
"--testSince requires non-empty development and test partitions for the selected direction"
|
|
6097
|
+
);
|
|
6098
|
+
}
|
|
5956
6099
|
const sealedFullSplit = sealAiPocketTestPartition(fullSplit, sealTest);
|
|
5957
6100
|
const discoveryRows = sealedFullSplit.discoveryRows;
|
|
6101
|
+
const scopeRows = resolveScopeRows(discoveryRows, scope);
|
|
5958
6102
|
const trainRows = resolveScopeRows(fullSplit.trainRows, scope);
|
|
5959
6103
|
const validationRows = resolveScopeRows(fullSplit.validationRows, scope);
|
|
5960
6104
|
const testRows = resolveScopeRows(sealedFullSplit.searchTestRows, scope);
|
|
@@ -6107,6 +6251,12 @@ var init_research = __esm({
|
|
|
6107
6251
|
}
|
|
6108
6252
|
}
|
|
6109
6253
|
return {
|
|
6254
|
+
...calendarSplit ? {
|
|
6255
|
+
labelHorizonPurge: {
|
|
6256
|
+
crossing: (calendarSplit.labelHorizonPurge?.crossing ?? 0) + (datasetLabelHorizonPurge?.crossing ?? 0),
|
|
6257
|
+
unknown: (calendarSplit.labelHorizonPurge?.unknown ?? 0) + (datasetLabelHorizonPurge?.unknown ?? 0)
|
|
6258
|
+
}
|
|
6259
|
+
} : {},
|
|
6110
6260
|
scopeRows,
|
|
6111
6261
|
trainRows,
|
|
6112
6262
|
validationRows,
|
|
@@ -6154,6 +6304,7 @@ var init_command = __esm({
|
|
|
6154
6304
|
maxCombinations,
|
|
6155
6305
|
validationSplit,
|
|
6156
6306
|
testSplit,
|
|
6307
|
+
testSince,
|
|
6157
6308
|
maxBatch,
|
|
6158
6309
|
maxSymbolCountShare,
|
|
6159
6310
|
objective,
|
|
@@ -6201,6 +6352,7 @@ var init_command = __esm({
|
|
|
6201
6352
|
const research = runAiPocketSearchResearch({
|
|
6202
6353
|
rows,
|
|
6203
6354
|
options,
|
|
6355
|
+
datasetLabelHorizonPurge: evaluatedDataset.labelHorizonPurge,
|
|
6204
6356
|
onProgress: createAiPocketSearchResearchProgressPresenter()
|
|
6205
6357
|
});
|
|
6206
6358
|
const {
|
|
@@ -6274,6 +6426,9 @@ var init_command = __esm({
|
|
|
6274
6426
|
objective,
|
|
6275
6427
|
validationSplit,
|
|
6276
6428
|
testSplit,
|
|
6429
|
+
testSince,
|
|
6430
|
+
partitionMode: testSince == null ? "timestamp-ratio" : "calendar-cutoff",
|
|
6431
|
+
...research.labelHorizonPurge ? { labelHorizonPurge: research.labelHorizonPurge } : {},
|
|
6277
6432
|
sealedTest,
|
|
6278
6433
|
minValidationSupport: search.stats.cadence.minValidationSupport,
|
|
6279
6434
|
reportPath,
|
|
@@ -6423,6 +6578,11 @@ var init_aiPocketSearch2 = __esm({
|
|
|
6423
6578
|
"Trailing timestamp-grouped test share; default 0.4 (outer 60/40)",
|
|
6424
6579
|
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.testSplit
|
|
6425
6580
|
);
|
|
6581
|
+
import_args3.default.option(
|
|
6582
|
+
["z", "testSince"],
|
|
6583
|
+
"Fixed calendar test boundary (UTC ISO or epoch ms); overrides the split ratio and requires validationSplit 0",
|
|
6584
|
+
""
|
|
6585
|
+
);
|
|
6426
6586
|
import_args3.default.option(
|
|
6427
6587
|
["w", "sealTest"],
|
|
6428
6588
|
"Reserve test bounds without exposing its rows or economics to discovery",
|
|
@@ -28332,7 +28492,12 @@ var init_artifacts = __esm({
|
|
|
28332
28492
|
"controlPnl",
|
|
28333
28493
|
"candidatePnl",
|
|
28334
28494
|
"pnlDelta",
|
|
28335
|
-
"
|
|
28495
|
+
"controlExitReason",
|
|
28496
|
+
"candidateExitReason",
|
|
28497
|
+
"exitReasonChanged",
|
|
28498
|
+
"controlExitCode",
|
|
28499
|
+
"candidateExitCode",
|
|
28500
|
+
"exitCodeChanged"
|
|
28336
28501
|
].map(csv).join(",") + "\n"
|
|
28337
28502
|
);
|
|
28338
28503
|
for (const comparison of params.comparisons) {
|
|
@@ -28350,7 +28515,12 @@ var init_artifacts = __esm({
|
|
|
28350
28515
|
pair.control.netProfit,
|
|
28351
28516
|
pair.candidate.netProfit,
|
|
28352
28517
|
pair.pnlDelta,
|
|
28353
|
-
pair.
|
|
28518
|
+
pair.control.exitReason,
|
|
28519
|
+
pair.candidate.exitReason,
|
|
28520
|
+
pair.exitReasonChanged,
|
|
28521
|
+
pair.control.exitCode,
|
|
28522
|
+
pair.candidate.exitCode,
|
|
28523
|
+
pair.exitCodeChanged
|
|
28354
28524
|
].map(csv).join(",") + "\n"
|
|
28355
28525
|
);
|
|
28356
28526
|
}
|
|
@@ -28995,6 +29165,7 @@ var init_comparison = __esm({
|
|
|
28995
29165
|
candidate: candidateTrade,
|
|
28996
29166
|
pnlDelta: candidateTrade.netProfit - controlTrade.netProfit,
|
|
28997
29167
|
exitReasonChanged: candidateTrade.exitReason !== controlTrade.exitReason,
|
|
29168
|
+
exitCodeChanged: candidateTrade.exitCode !== controlTrade.exitCode,
|
|
28998
29169
|
entryTimestampDeltaMs: candidateTrade.entryTimestamp - controlTrade.entryTimestamp,
|
|
28999
29170
|
exitTimestampDeltaMs: candidateTrade.exitTimestamp - controlTrade.exitTimestamp
|
|
29000
29171
|
});
|
|
@@ -29212,6 +29383,7 @@ var init_dataset2 = __esm({
|
|
|
29212
29383
|
"totalSlippageCost"
|
|
29213
29384
|
),
|
|
29214
29385
|
exitReason: result.exitReason,
|
|
29386
|
+
...result.exitCode ? { exitCode: result.exitCode } : {},
|
|
29215
29387
|
regime: resolveCoreResearchRegime(row)
|
|
29216
29388
|
};
|
|
29217
29389
|
};
|
|
@@ -29255,7 +29427,7 @@ var init_dataset2 = __esm({
|
|
|
29255
29427
|
}
|
|
29256
29428
|
const primary = primaryRows[0];
|
|
29257
29429
|
if (cycle.some(
|
|
29258
|
-
(trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason
|
|
29430
|
+
(trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason || trade.exitCode !== primary.exitCode
|
|
29259
29431
|
)) {
|
|
29260
29432
|
throw new Error(
|
|
29261
29433
|
`Position cycle ${identity} contains inconsistent final exits`
|
|
@@ -29444,7 +29616,7 @@ var init_report = __esm({
|
|
|
29444
29616
|
)}</svg><div class="legend">${series.map((entry) => `<span><i style="background:${entry.color}"></i>${escapeHtml4(entry.id)}</span>`).join("")}</div>`;
|
|
29445
29617
|
};
|
|
29446
29618
|
comparisonCards = (comparisons) => comparisons.map(
|
|
29447
|
-
(comparison) => `<article class="card"><h3>${escapeHtml4(comparison.candidateId)} vs ${escapeHtml4(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format2(comparison.matchedIdentityPctOfControl)}%</dd><dt>Matched \u0394PnL</dt><dd>${format2(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format2(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format2(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format2(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format2(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394>0)</dt><dd>${format2(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format2(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml4(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
|
|
29619
|
+
(comparison) => `<article class="card"><h3>${escapeHtml4(comparison.candidateId)} vs ${escapeHtml4(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format2(comparison.matchedIdentityPctOfControl)}%</dd><dt>Exit reason changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitReasonChanged).length}</dd><dt>Exit code changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitCodeChanged).length}</dd><dt>Matched \u0394PnL</dt><dd>${format2(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format2(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format2(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format2(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format2(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394>0)</dt><dd>${format2(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format2(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml4(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
|
|
29448
29620
|
).join("");
|
|
29449
29621
|
regimeRows = (variants) => {
|
|
29450
29622
|
const keys = [
|
|
@@ -29458,7 +29630,7 @@ var init_report = __esm({
|
|
|
29458
29630
|
).join("");
|
|
29459
29631
|
};
|
|
29460
29632
|
traceRows = (variants) => variants.map(
|
|
29461
|
-
(variant) => `<tr><td>${escapeHtml4(variant.variant.label)}</td><td>${escapeHtml4(variant.reconciliation.status)}</td><td>${escapeHtml4(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml4(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
|
|
29633
|
+
(variant) => `<tr><td>${escapeHtml4(variant.variant.label)}</td><td>${escapeHtml4(variant.reconciliation.status)}</td><td>${escapeHtml4(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml4(JSON.stringify(variant.traceFunnel.exitCodes))}</td><td>${escapeHtml4(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
|
|
29462
29634
|
).join("");
|
|
29463
29635
|
buildCoreResearchHtml = (params) => {
|
|
29464
29636
|
const { spec, result, tradesByVariant } = params;
|
|
@@ -29484,7 +29656,7 @@ var init_report = __esm({
|
|
|
29484
29656
|
([key, value]) => `<span class="${value === "present" ? "positive" : "negative"}">${escapeHtml4(key)}: ${value}</span>`
|
|
29485
29657
|
).join(
|
|
29486
29658
|
""
|
|
29487
|
-
)}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort
|
|
29659
|
+
)}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort completed positions. ALL drawdown is aggregate portfolio; LONG/SHORT drawdowns are side-only.</p><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${allRows}</tbody></table></div><h2>Fold and monthly robustness</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${robustnessRows}</tbody></table></div><h2>Cost stress</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Extra round-trip bps</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>Realized DD</th></tr></thead><tbody>${costRows}</tbody></table></div><h2>Signal-time market regimes</h2><div class="scroll"><table><thead><tr><th>Trend | Volatility | Breadth | Derivatives</th><th>Variant</th><th>N</th><th>PnL</th><th>PF</th><th>WR</th></tr></thead><tbody>${regimeRows(result.variants)}</tbody></table></div><h2>Trace funnel and reconciliation</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Reconcile</th><th>Setup identity source</th><th>Events</th><th>Top skips</th><th>Exit codes</th><th>Latest signal-time regime</th></tr></thead><tbody>${traceRows(result.variants)}</tbody></table></div><h2>Multiple testing / overfitting</h2><pre>${escapeHtml4(JSON.stringify({ multipleTesting: result.multipleTesting, overfittingDiagnostics: result.overfittingDiagnostics }, null, 2))}</pre><h2>Lineage</h2><pre>${escapeHtml4(JSON.stringify({ stage: spec.stage, universeSha256: spec.universe.sha256, lineage: spec.lineage ?? null, artifactHashes: result.artifactHashes }, null, 2))}</pre></main></body></html>`;
|
|
29488
29660
|
};
|
|
29489
29661
|
}
|
|
29490
29662
|
});
|
|
@@ -29786,8 +29958,9 @@ var init_trace = __esm({
|
|
|
29786
29958
|
import_promises24 = __toESM(require("fs/promises"));
|
|
29787
29959
|
import_node_path18 = __toESM(require("path"));
|
|
29788
29960
|
summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
29789
|
-
const events =
|
|
29790
|
-
const skipCounts =
|
|
29961
|
+
const events = /* @__PURE__ */ Object.create(null);
|
|
29962
|
+
const skipCounts = /* @__PURE__ */ Object.create(null);
|
|
29963
|
+
const exitCodes = /* @__PURE__ */ Object.create(null);
|
|
29791
29964
|
for (const inputPath of filePaths) {
|
|
29792
29965
|
const filePath = import_node_path18.default.resolve(inputPath);
|
|
29793
29966
|
const text = await import_promises24.default.readFile(filePath, "utf8");
|
|
@@ -29807,6 +29980,9 @@ var init_trace = __esm({
|
|
|
29807
29980
|
skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
|
|
29808
29981
|
}
|
|
29809
29982
|
}
|
|
29983
|
+
if (event.event === "position_exited" && event.exitCode) {
|
|
29984
|
+
exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
|
|
29985
|
+
}
|
|
29810
29986
|
}
|
|
29811
29987
|
}
|
|
29812
29988
|
return {
|
|
@@ -29815,6 +29991,11 @@ var init_trace = __esm({
|
|
|
29815
29991
|
Object.entries(skipCounts).sort(
|
|
29816
29992
|
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
29817
29993
|
)
|
|
29994
|
+
),
|
|
29995
|
+
exitCodes: Object.fromEntries(
|
|
29996
|
+
Object.entries(exitCodes).sort(
|
|
29997
|
+
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
29998
|
+
)
|
|
29818
29999
|
)
|
|
29819
30000
|
};
|
|
29820
30001
|
};
|
|
@@ -1966,6 +1966,19 @@ var buildAiPocketMarkdownReport = ({
|
|
|
1966
1966
|
["test_rows", run.testRows],
|
|
1967
1967
|
["validation_split", formatMdPercent(run.validationSplit)],
|
|
1968
1968
|
["test_split", formatMdPercent(run.testSplit)],
|
|
1969
|
+
["partition_mode", run.partitionMode ?? "timestamp-ratio"],
|
|
1970
|
+
[
|
|
1971
|
+
"purged_development_crossing_labels",
|
|
1972
|
+
run.labelHorizonPurge?.crossing ?? 0
|
|
1973
|
+
],
|
|
1974
|
+
[
|
|
1975
|
+
"purged_development_unknown_labels",
|
|
1976
|
+
run.labelHorizonPurge?.unknown ?? 0
|
|
1977
|
+
],
|
|
1978
|
+
[
|
|
1979
|
+
"test_since",
|
|
1980
|
+
run.testSince == null ? "n/a" : new Date(run.testSince).toISOString()
|
|
1981
|
+
],
|
|
1969
1982
|
["test_sealed", run.sealedTest?.sealed ? "yes" : "no"],
|
|
1970
1983
|
["sealed_test_rows", run.sealedTest?.rows ?? run.testRows],
|
|
1971
1984
|
["sealed_test_events", run.sealedTest?.events ?? "n/a"],
|
|
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var aiPocketSearchCli_exports = {};
|
|
22
22
|
__export(aiPocketSearchCli_exports, {
|
|
23
23
|
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS: () => AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS,
|
|
24
|
+
classifyAiPocketLabelHorizon: () => classifyAiPocketLabelHorizon,
|
|
25
|
+
parseAiPocketTestSince: () => parseAiPocketTestSince,
|
|
24
26
|
readAiPocketSearchCliOption: () => readAiPocketSearchCliOption,
|
|
25
27
|
resolveAiPocketCadenceProfile: () => resolveAiPocketCadenceProfile,
|
|
26
28
|
sealAiPocketTestPartition: () => sealAiPocketTestPartition,
|
|
@@ -119,7 +121,70 @@ var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS = {
|
|
|
119
121
|
validationSplit: "0",
|
|
120
122
|
testSplit: "0.4"
|
|
121
123
|
};
|
|
122
|
-
var
|
|
124
|
+
var parseAiPocketTestSince = (value) => {
|
|
125
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
126
|
+
const numeric = typeof value === "number" || /^\d+$/.test(text);
|
|
127
|
+
const iso = text.match(
|
|
128
|
+
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/
|
|
129
|
+
);
|
|
130
|
+
const validCalendar = iso != null && Number(iso[2]) >= 1 && Number(iso[2]) <= 12 && Number(iso[3]) >= 1 && Number(iso[3]) <= new Date(Date.UTC(Number(iso[1]), Number(iso[2]), 0)).getUTCDate() && Number(iso[4]) < 24 && Number(iso[5]) < 60 && Number(iso[6] ?? 0) < 60;
|
|
131
|
+
const timestamp = numeric ? Number(value) : validCalendar ? Date.parse(text) : NaN;
|
|
132
|
+
if (!Number.isSafeInteger(timestamp) || timestamp <= 0 || timestamp > 864e13) {
|
|
133
|
+
throw new Error("--testSince requires a valid UTC timestamp or epoch ms");
|
|
134
|
+
}
|
|
135
|
+
return timestamp;
|
|
136
|
+
};
|
|
137
|
+
var classifyAiPocketLabelHorizon = (decisionTimestamp, exitTimestamp, cutoff) => {
|
|
138
|
+
if (decisionTimestamp >= cutoff) return null;
|
|
139
|
+
if (typeof exitTimestamp !== "number" || !Number.isFinite(exitTimestamp)) {
|
|
140
|
+
return "unknown";
|
|
141
|
+
}
|
|
142
|
+
if (exitTimestamp < decisionTimestamp) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
"--testSince requires label exitTimestamp >= decision timestamp"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return exitTimestamp >= cutoff ? "crossing" : null;
|
|
148
|
+
};
|
|
149
|
+
var splitAiPocketResearchRowsByTimestamp = (rows, validationSplit, testSplit = 0, testSince) => {
|
|
150
|
+
if (testSince != null) {
|
|
151
|
+
const cutoff = parseAiPocketTestSince(testSince);
|
|
152
|
+
if (validationSplit !== 0) {
|
|
153
|
+
throw new Error("--testSince requires --validationSplit 0");
|
|
154
|
+
}
|
|
155
|
+
const trainRows = [];
|
|
156
|
+
const testRows = [];
|
|
157
|
+
const labelHorizonPurge = {
|
|
158
|
+
crossing: 0,
|
|
159
|
+
unknown: 0
|
|
160
|
+
};
|
|
161
|
+
for (const row of rows) {
|
|
162
|
+
if (typeof row.timestamp !== "number" || !Number.isFinite(row.timestamp)) {
|
|
163
|
+
throw new Error("--testSince requires a finite timestamp on every row");
|
|
164
|
+
}
|
|
165
|
+
const purge = classifyAiPocketLabelHorizon(
|
|
166
|
+
row.timestamp,
|
|
167
|
+
row.exitTimestamp,
|
|
168
|
+
cutoff
|
|
169
|
+
);
|
|
170
|
+
if (purge) {
|
|
171
|
+
labelHorizonPurge[purge] += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
(row.timestamp < cutoff ? trainRows : testRows).push(row);
|
|
175
|
+
}
|
|
176
|
+
if (!trainRows.length || !testRows.length) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
"--testSince requires non-empty development and test partitions"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
trainRows,
|
|
183
|
+
validationRows: [],
|
|
184
|
+
testRows,
|
|
185
|
+
labelHorizonPurge
|
|
186
|
+
};
|
|
187
|
+
}
|
|
123
188
|
const timestamps = [
|
|
124
189
|
...new Set(
|
|
125
190
|
rows.map((row) => row.timestamp).filter((timestamp) => Number.isFinite(timestamp))
|
|
@@ -201,6 +266,8 @@ var readAiPocketSearchCliOption = ({
|
|
|
201
266
|
// Annotate the CommonJS export names for ESM import in node:
|
|
202
267
|
0 && (module.exports = {
|
|
203
268
|
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS,
|
|
269
|
+
classifyAiPocketLabelHorizon,
|
|
270
|
+
parseAiPocketTestSince,
|
|
204
271
|
readAiPocketSearchCliOption,
|
|
205
272
|
resolveAiPocketCadenceProfile,
|
|
206
273
|
sealAiPocketTestPartition,
|
package/dist/lib/coreResearch.js
CHANGED
|
@@ -691,7 +691,12 @@ var writeCoreResearchMatches = async (params) => {
|
|
|
691
691
|
"controlPnl",
|
|
692
692
|
"candidatePnl",
|
|
693
693
|
"pnlDelta",
|
|
694
|
-
"
|
|
694
|
+
"controlExitReason",
|
|
695
|
+
"candidateExitReason",
|
|
696
|
+
"exitReasonChanged",
|
|
697
|
+
"controlExitCode",
|
|
698
|
+
"candidateExitCode",
|
|
699
|
+
"exitCodeChanged"
|
|
695
700
|
].map(csv).join(",") + "\n"
|
|
696
701
|
);
|
|
697
702
|
for (const comparison of params.comparisons) {
|
|
@@ -709,7 +714,12 @@ var writeCoreResearchMatches = async (params) => {
|
|
|
709
714
|
pair.control.netProfit,
|
|
710
715
|
pair.candidate.netProfit,
|
|
711
716
|
pair.pnlDelta,
|
|
712
|
-
pair.
|
|
717
|
+
pair.control.exitReason,
|
|
718
|
+
pair.candidate.exitReason,
|
|
719
|
+
pair.exitReasonChanged,
|
|
720
|
+
pair.control.exitCode,
|
|
721
|
+
pair.candidate.exitCode,
|
|
722
|
+
pair.exitCodeChanged
|
|
713
723
|
].map(csv).join(",") + "\n"
|
|
714
724
|
);
|
|
715
725
|
}
|
|
@@ -1326,6 +1336,7 @@ var compareCoreResearchVariants = (params) => {
|
|
|
1326
1336
|
candidate: candidateTrade,
|
|
1327
1337
|
pnlDelta: candidateTrade.netProfit - controlTrade.netProfit,
|
|
1328
1338
|
exitReasonChanged: candidateTrade.exitReason !== controlTrade.exitReason,
|
|
1339
|
+
exitCodeChanged: candidateTrade.exitCode !== controlTrade.exitCode,
|
|
1329
1340
|
entryTimestampDeltaMs: candidateTrade.entryTimestamp - controlTrade.entryTimestamp,
|
|
1330
1341
|
exitTimestampDeltaMs: candidateTrade.exitTimestamp - controlTrade.exitTimestamp
|
|
1331
1342
|
});
|
|
@@ -1537,6 +1548,7 @@ var normalizeCompletedTrade = (params) => {
|
|
|
1537
1548
|
"totalSlippageCost"
|
|
1538
1549
|
),
|
|
1539
1550
|
exitReason: result.exitReason,
|
|
1551
|
+
...result.exitCode ? { exitCode: result.exitCode } : {},
|
|
1540
1552
|
regime: resolveCoreResearchRegime(row)
|
|
1541
1553
|
};
|
|
1542
1554
|
};
|
|
@@ -1580,7 +1592,7 @@ var aggregatePositionCycles = (trades) => {
|
|
|
1580
1592
|
}
|
|
1581
1593
|
const primary = primaryRows[0];
|
|
1582
1594
|
if (cycle.some(
|
|
1583
|
-
(trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason
|
|
1595
|
+
(trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason || trade.exitCode !== primary.exitCode
|
|
1584
1596
|
)) {
|
|
1585
1597
|
throw new Error(
|
|
1586
1598
|
`Position cycle ${identity} contains inconsistent final exits`
|
|
@@ -1762,7 +1774,7 @@ var buildCurveSvg = (tradesByVariant, cohort, curve) => {
|
|
|
1762
1774
|
)}</svg><div class="legend">${series.map((entry) => `<span><i style="background:${entry.color}"></i>${escapeHtml(entry.id)}</span>`).join("")}</div>`;
|
|
1763
1775
|
};
|
|
1764
1776
|
var comparisonCards = (comparisons) => comparisons.map(
|
|
1765
|
-
(comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Matched \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394>0)</dt><dd>${format(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
|
|
1777
|
+
(comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Exit reason changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitReasonChanged).length}</dd><dt>Exit code changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitCodeChanged).length}</dd><dt>Matched \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394>0)</dt><dd>${format(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
|
|
1766
1778
|
).join("");
|
|
1767
1779
|
var regimeRows = (variants) => {
|
|
1768
1780
|
const keys = [
|
|
@@ -1776,7 +1788,7 @@ var regimeRows = (variants) => {
|
|
|
1776
1788
|
).join("");
|
|
1777
1789
|
};
|
|
1778
1790
|
var traceRows = (variants) => variants.map(
|
|
1779
|
-
(variant) => `<tr><td>${escapeHtml(variant.variant.label)}</td><td>${escapeHtml(variant.reconciliation.status)}</td><td>${escapeHtml(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
|
|
1791
|
+
(variant) => `<tr><td>${escapeHtml(variant.variant.label)}</td><td>${escapeHtml(variant.reconciliation.status)}</td><td>${escapeHtml(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.exitCodes))}</td><td>${escapeHtml(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
|
|
1780
1792
|
).join("");
|
|
1781
1793
|
var buildCoreResearchHtml = (params) => {
|
|
1782
1794
|
const { spec, result, tradesByVariant } = params;
|
|
@@ -1802,7 +1814,7 @@ var buildCoreResearchHtml = (params) => {
|
|
|
1802
1814
|
([key, value]) => `<span class="${value === "present" ? "positive" : "negative"}">${escapeHtml(key)}: ${value}</span>`
|
|
1803
1815
|
).join(
|
|
1804
1816
|
""
|
|
1805
|
-
)}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort
|
|
1817
|
+
)}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort completed positions. ALL drawdown is aggregate portfolio; LONG/SHORT drawdowns are side-only.</p><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${allRows}</tbody></table></div><h2>Fold and monthly robustness</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${robustnessRows}</tbody></table></div><h2>Cost stress</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Extra round-trip bps</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>Realized DD</th></tr></thead><tbody>${costRows}</tbody></table></div><h2>Signal-time market regimes</h2><div class="scroll"><table><thead><tr><th>Trend | Volatility | Breadth | Derivatives</th><th>Variant</th><th>N</th><th>PnL</th><th>PF</th><th>WR</th></tr></thead><tbody>${regimeRows(result.variants)}</tbody></table></div><h2>Trace funnel and reconciliation</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Reconcile</th><th>Setup identity source</th><th>Events</th><th>Top skips</th><th>Exit codes</th><th>Latest signal-time regime</th></tr></thead><tbody>${traceRows(result.variants)}</tbody></table></div><h2>Multiple testing / overfitting</h2><pre>${escapeHtml(JSON.stringify({ multipleTesting: result.multipleTesting, overfittingDiagnostics: result.overfittingDiagnostics }, null, 2))}</pre><h2>Lineage</h2><pre>${escapeHtml(JSON.stringify({ stage: spec.stage, universeSha256: spec.universe.sha256, lineage: spec.lineage ?? null, artifactHashes: result.artifactHashes }, null, 2))}</pre></main></body></html>`;
|
|
1806
1818
|
};
|
|
1807
1819
|
|
|
1808
1820
|
// src/lib/backtest/checkpoint.ts
|
|
@@ -2108,8 +2120,9 @@ var applyCoreResearchRobustnessGuardrails = (params) => {
|
|
|
2108
2120
|
var import_promises3 = __toESM(require("fs/promises"));
|
|
2109
2121
|
var import_node_path4 = __toESM(require("path"));
|
|
2110
2122
|
var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
2111
|
-
const events =
|
|
2112
|
-
const skipCounts =
|
|
2123
|
+
const events = /* @__PURE__ */ Object.create(null);
|
|
2124
|
+
const skipCounts = /* @__PURE__ */ Object.create(null);
|
|
2125
|
+
const exitCodes = /* @__PURE__ */ Object.create(null);
|
|
2113
2126
|
for (const inputPath of filePaths) {
|
|
2114
2127
|
const filePath = import_node_path4.default.resolve(inputPath);
|
|
2115
2128
|
const text = await import_promises3.default.readFile(filePath, "utf8");
|
|
@@ -2129,6 +2142,9 @@ var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
|
2129
2142
|
skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
|
|
2130
2143
|
}
|
|
2131
2144
|
}
|
|
2145
|
+
if (event.event === "position_exited" && event.exitCode) {
|
|
2146
|
+
exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
|
|
2147
|
+
}
|
|
2132
2148
|
}
|
|
2133
2149
|
}
|
|
2134
2150
|
return {
|
|
@@ -2137,6 +2153,11 @@ var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
|
2137
2153
|
Object.entries(skipCounts).sort(
|
|
2138
2154
|
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
2139
2155
|
)
|
|
2156
|
+
),
|
|
2157
|
+
exitCodes: Object.fromEntries(
|
|
2158
|
+
Object.entries(exitCodes).sort(
|
|
2159
|
+
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
2160
|
+
)
|
|
2140
2161
|
)
|
|
2141
2162
|
};
|
|
2142
2163
|
};
|
|
@@ -127,7 +127,70 @@ var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS = {
|
|
|
127
127
|
validationSplit: "0",
|
|
128
128
|
testSplit: "0.4"
|
|
129
129
|
};
|
|
130
|
-
var
|
|
130
|
+
var parseAiPocketTestSince = (value) => {
|
|
131
|
+
const text = typeof value === "string" ? value.trim() : "";
|
|
132
|
+
const numeric = typeof value === "number" || /^\d+$/.test(text);
|
|
133
|
+
const iso = text.match(
|
|
134
|
+
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/
|
|
135
|
+
);
|
|
136
|
+
const validCalendar = iso != null && Number(iso[2]) >= 1 && Number(iso[2]) <= 12 && Number(iso[3]) >= 1 && Number(iso[3]) <= new Date(Date.UTC(Number(iso[1]), Number(iso[2]), 0)).getUTCDate() && Number(iso[4]) < 24 && Number(iso[5]) < 60 && Number(iso[6] ?? 0) < 60;
|
|
137
|
+
const timestamp = numeric ? Number(value) : validCalendar ? Date.parse(text) : NaN;
|
|
138
|
+
if (!Number.isSafeInteger(timestamp) || timestamp <= 0 || timestamp > 864e13) {
|
|
139
|
+
throw new Error("--testSince requires a valid UTC timestamp or epoch ms");
|
|
140
|
+
}
|
|
141
|
+
return timestamp;
|
|
142
|
+
};
|
|
143
|
+
var classifyAiPocketLabelHorizon = (decisionTimestamp, exitTimestamp, cutoff) => {
|
|
144
|
+
if (decisionTimestamp >= cutoff) return null;
|
|
145
|
+
if (typeof exitTimestamp !== "number" || !Number.isFinite(exitTimestamp)) {
|
|
146
|
+
return "unknown";
|
|
147
|
+
}
|
|
148
|
+
if (exitTimestamp < decisionTimestamp) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
"--testSince requires label exitTimestamp >= decision timestamp"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return exitTimestamp >= cutoff ? "crossing" : null;
|
|
154
|
+
};
|
|
155
|
+
var splitAiPocketResearchRowsByTimestamp = (rows, validationSplit, testSplit = 0, testSince) => {
|
|
156
|
+
if (testSince != null) {
|
|
157
|
+
const cutoff = parseAiPocketTestSince(testSince);
|
|
158
|
+
if (validationSplit !== 0) {
|
|
159
|
+
throw new Error("--testSince requires --validationSplit 0");
|
|
160
|
+
}
|
|
161
|
+
const trainRows = [];
|
|
162
|
+
const testRows = [];
|
|
163
|
+
const labelHorizonPurge = {
|
|
164
|
+
crossing: 0,
|
|
165
|
+
unknown: 0
|
|
166
|
+
};
|
|
167
|
+
for (const row of rows) {
|
|
168
|
+
if (typeof row.timestamp !== "number" || !Number.isFinite(row.timestamp)) {
|
|
169
|
+
throw new Error("--testSince requires a finite timestamp on every row");
|
|
170
|
+
}
|
|
171
|
+
const purge = classifyAiPocketLabelHorizon(
|
|
172
|
+
row.timestamp,
|
|
173
|
+
row.exitTimestamp,
|
|
174
|
+
cutoff
|
|
175
|
+
);
|
|
176
|
+
if (purge) {
|
|
177
|
+
labelHorizonPurge[purge] += 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
(row.timestamp < cutoff ? trainRows : testRows).push(row);
|
|
181
|
+
}
|
|
182
|
+
if (!trainRows.length || !testRows.length) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
"--testSince requires non-empty development and test partitions"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
trainRows,
|
|
189
|
+
validationRows: [],
|
|
190
|
+
testRows,
|
|
191
|
+
labelHorizonPurge
|
|
192
|
+
};
|
|
193
|
+
}
|
|
131
194
|
const timestamps = [
|
|
132
195
|
...new Set(
|
|
133
196
|
rows.map((row) => row.timestamp).filter((timestamp) => Number.isFinite(timestamp))
|
|
@@ -445,6 +508,12 @@ var resolveAiPocketSearchCommandOptions = ({
|
|
|
445
508
|
if (sealTest && testSplit <= 0) {
|
|
446
509
|
throw new Error("--sealTest requires a positive --testSplit");
|
|
447
510
|
}
|
|
511
|
+
const explicitTestSince = hasCliOption2(argv, "testSince");
|
|
512
|
+
const testSinceInput = explicitTestSince ? readAiPocketSearchCliOption({ argv, longName: "testSince" }) : flags2.testSince;
|
|
513
|
+
const testSince = explicitTestSince || testSinceInput != null && testSinceInput !== "" ? parseAiPocketTestSince(testSinceInput) : null;
|
|
514
|
+
if (testSince != null && validationSplit !== 0) {
|
|
515
|
+
throw new Error("--testSince requires --validationSplit 0");
|
|
516
|
+
}
|
|
448
517
|
const maxEventCountShare = normalizeUnitRatio(
|
|
449
518
|
readAiPocketSearchCliOption({
|
|
450
519
|
argv,
|
|
@@ -485,6 +554,7 @@ var resolveAiPocketSearchCommandOptions = ({
|
|
|
485
554
|
maxCombinations: normalizePositiveInt(flags2.maxCombinations, 6e4),
|
|
486
555
|
validationSplit,
|
|
487
556
|
testSplit,
|
|
557
|
+
testSince,
|
|
488
558
|
sealTest,
|
|
489
559
|
explicitMinValidationSupport: normalizeInt(flags2.minValidationSupport, 0),
|
|
490
560
|
explicitMinEvents: normalizeInt(flags2.minEvents, 0),
|
|
@@ -2452,6 +2522,19 @@ var buildAiPocketMarkdownReport = ({
|
|
|
2452
2522
|
["test_rows", run.testRows],
|
|
2453
2523
|
["validation_split", formatMdPercent(run.validationSplit)],
|
|
2454
2524
|
["test_split", formatMdPercent(run.testSplit)],
|
|
2525
|
+
["partition_mode", run.partitionMode ?? "timestamp-ratio"],
|
|
2526
|
+
[
|
|
2527
|
+
"purged_development_crossing_labels",
|
|
2528
|
+
run.labelHorizonPurge?.crossing ?? 0
|
|
2529
|
+
],
|
|
2530
|
+
[
|
|
2531
|
+
"purged_development_unknown_labels",
|
|
2532
|
+
run.labelHorizonPurge?.unknown ?? 0
|
|
2533
|
+
],
|
|
2534
|
+
[
|
|
2535
|
+
"test_since",
|
|
2536
|
+
run.testSince == null ? "n/a" : new Date(run.testSince).toISOString()
|
|
2537
|
+
],
|
|
2455
2538
|
["test_sealed", run.sealedTest?.sealed ? "yes" : "no"],
|
|
2456
2539
|
["sealed_test_rows", run.sealedTest?.rows ?? run.testRows],
|
|
2457
2540
|
["sealed_test_events", run.sealedTest?.events ?? "n/a"],
|
|
@@ -3204,6 +3287,11 @@ var presentAiPocketSearchResult = ({
|
|
|
3204
3287
|
["max_combinations", import_chalk.default.magenta(String(maxCombinations))],
|
|
3205
3288
|
["validation_split", import_chalk.default.magenta(formatRatio(validationSplit))],
|
|
3206
3289
|
["test_split", import_chalk.default.magenta(formatRatio(testSplit))],
|
|
3290
|
+
["partition_mode", run.partitionMode ?? "timestamp-ratio"],
|
|
3291
|
+
[
|
|
3292
|
+
"test_since",
|
|
3293
|
+
run.testSince == null ? "n/a" : new Date(run.testSince).toISOString()
|
|
3294
|
+
],
|
|
3207
3295
|
[
|
|
3208
3296
|
"test_evidence",
|
|
3209
3297
|
sealedTest.sealed ? import_chalk.default.green(
|
|
@@ -3749,6 +3837,10 @@ var evaluateAiPocketSearchDataset = async ({
|
|
|
3749
3837
|
let failed = 0;
|
|
3750
3838
|
const errors = [];
|
|
3751
3839
|
const rows = [];
|
|
3840
|
+
const labelHorizonPurge = {
|
|
3841
|
+
crossing: 0,
|
|
3842
|
+
unknown: 0
|
|
3843
|
+
};
|
|
3752
3844
|
const excludedFeaturePaths = /* @__PURE__ */ new Map();
|
|
3753
3845
|
await (0, import_ai2.streamAiDatasetRows)({
|
|
3754
3846
|
filePaths: prepared.filePaths,
|
|
@@ -3759,12 +3851,43 @@ var evaluateAiPocketSearchDataset = async ({
|
|
|
3759
3851
|
if (resolvedStrategyName === "unknown" && typeof row.strategyName === "string" && row.strategyName.trim()) {
|
|
3760
3852
|
resolvedStrategyName = row.strategyName.trim();
|
|
3761
3853
|
}
|
|
3854
|
+
if (options.testSince != null && (typeof row.timestamp !== "number" || !Number.isFinite(row.timestamp))) {
|
|
3855
|
+
throw new Error(
|
|
3856
|
+
"--testSince requires a finite numeric timestamp on every export row"
|
|
3857
|
+
);
|
|
3858
|
+
}
|
|
3762
3859
|
const timestamp = getDatasetRowTimestamp(row);
|
|
3763
3860
|
if (prepared.sinceTimestamp != null && (timestamp == null || timestamp < prepared.sinceTimestamp) || options.untilTimestamp != null && (timestamp == null || timestamp > options.untilTimestamp)) {
|
|
3764
3861
|
dateSkipped += 1;
|
|
3765
3862
|
onProgress?.({ symbol: row.symbol, status: "date-skip" });
|
|
3766
3863
|
return;
|
|
3767
3864
|
}
|
|
3865
|
+
if (options.testSince != null) {
|
|
3866
|
+
const purge = classifyAiPocketLabelHorizon(
|
|
3867
|
+
timestamp,
|
|
3868
|
+
row.tradeResult?.exitTimestamp,
|
|
3869
|
+
options.testSince
|
|
3870
|
+
);
|
|
3871
|
+
if (purge) {
|
|
3872
|
+
labelHorizonPurge[purge] += 1;
|
|
3873
|
+
return;
|
|
3874
|
+
}
|
|
3875
|
+
if (options.sealTest && timestamp >= options.testSince) {
|
|
3876
|
+
rows.push({
|
|
3877
|
+
signalId: row.signalId,
|
|
3878
|
+
strategy: row.strategyName,
|
|
3879
|
+
symbol: row.symbol,
|
|
3880
|
+
direction: row.direction,
|
|
3881
|
+
timestamp,
|
|
3882
|
+
profit: 0,
|
|
3883
|
+
profitableTrade: false,
|
|
3884
|
+
aiApproved: false,
|
|
3885
|
+
quality: null,
|
|
3886
|
+
features: {}
|
|
3887
|
+
});
|
|
3888
|
+
return;
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3768
3891
|
try {
|
|
3769
3892
|
const signal = extractSignalFromAiDatasetRow(row);
|
|
3770
3893
|
const payload = (0, import_ai3.buildAiPayload)(signal);
|
|
@@ -3794,6 +3917,7 @@ var evaluateAiPocketSearchDataset = async ({
|
|
|
3794
3917
|
symbol: row.symbol,
|
|
3795
3918
|
direction: row.direction,
|
|
3796
3919
|
timestamp,
|
|
3920
|
+
...options.testSince != null ? { exitTimestamp: row.tradeResult?.exitTimestamp } : {},
|
|
3797
3921
|
profit,
|
|
3798
3922
|
profitableTrade: profit > 0,
|
|
3799
3923
|
aiApproved: isAiApproval(row, analysis, options.minQuality),
|
|
@@ -3816,6 +3940,7 @@ var evaluateAiPocketSearchDataset = async ({
|
|
|
3816
3940
|
}
|
|
3817
3941
|
});
|
|
3818
3942
|
return {
|
|
3943
|
+
labelHorizonPurge,
|
|
3819
3944
|
rows,
|
|
3820
3945
|
resolvedStrategyName,
|
|
3821
3946
|
scanned,
|
|
@@ -4182,7 +4307,8 @@ var resolveScopeRows = (rows, scope) => {
|
|
|
4182
4307
|
var runAiPocketSearchResearch = ({
|
|
4183
4308
|
rows,
|
|
4184
4309
|
options,
|
|
4185
|
-
onProgress
|
|
4310
|
+
onProgress,
|
|
4311
|
+
datasetLabelHorizonPurge
|
|
4186
4312
|
}) => {
|
|
4187
4313
|
const {
|
|
4188
4314
|
direction,
|
|
@@ -4190,6 +4316,7 @@ var runAiPocketSearchResearch = ({
|
|
|
4190
4316
|
qualityThresholds,
|
|
4191
4317
|
validationSplit,
|
|
4192
4318
|
testSplit,
|
|
4319
|
+
testSince,
|
|
4193
4320
|
sealTest,
|
|
4194
4321
|
cadenceMode,
|
|
4195
4322
|
explicitMinSupport,
|
|
@@ -4214,14 +4341,30 @@ var runAiPocketSearchResearch = ({
|
|
|
4214
4341
|
coverageMode
|
|
4215
4342
|
} = options;
|
|
4216
4343
|
const directionRows = direction ? rows.filter((row) => row.direction === direction) : rows;
|
|
4217
|
-
const
|
|
4218
|
-
|
|
4344
|
+
const calendarSplit = testSince != null ? splitAiPocketResearchRowsByTimestamp(
|
|
4345
|
+
rows,
|
|
4346
|
+
validationSplit,
|
|
4347
|
+
testSplit,
|
|
4348
|
+
testSince
|
|
4349
|
+
) : null;
|
|
4350
|
+
const inDirection = (row) => !direction || row.direction === direction;
|
|
4351
|
+
const fullSplit = calendarSplit ? {
|
|
4352
|
+
trainRows: calendarSplit.trainRows.filter(inDirection),
|
|
4353
|
+
validationRows: calendarSplit.validationRows.filter(inDirection),
|
|
4354
|
+
testRows: calendarSplit.testRows.filter(inDirection)
|
|
4355
|
+
} : splitAiPocketResearchRowsByTimestamp(
|
|
4219
4356
|
directionRows,
|
|
4220
4357
|
validationSplit,
|
|
4221
4358
|
testSplit
|
|
4222
4359
|
);
|
|
4360
|
+
if (testSince != null && (!fullSplit.trainRows.length || !fullSplit.testRows.length)) {
|
|
4361
|
+
throw new Error(
|
|
4362
|
+
"--testSince requires non-empty development and test partitions for the selected direction"
|
|
4363
|
+
);
|
|
4364
|
+
}
|
|
4223
4365
|
const sealedFullSplit = sealAiPocketTestPartition(fullSplit, sealTest);
|
|
4224
4366
|
const discoveryRows = sealedFullSplit.discoveryRows;
|
|
4367
|
+
const scopeRows = resolveScopeRows(discoveryRows, scope);
|
|
4225
4368
|
const trainRows = resolveScopeRows(fullSplit.trainRows, scope);
|
|
4226
4369
|
const validationRows = resolveScopeRows(fullSplit.validationRows, scope);
|
|
4227
4370
|
const testRows = resolveScopeRows(sealedFullSplit.searchTestRows, scope);
|
|
@@ -4374,6 +4517,12 @@ var runAiPocketSearchResearch = ({
|
|
|
4374
4517
|
}
|
|
4375
4518
|
}
|
|
4376
4519
|
return {
|
|
4520
|
+
...calendarSplit ? {
|
|
4521
|
+
labelHorizonPurge: {
|
|
4522
|
+
crossing: (calendarSplit.labelHorizonPurge?.crossing ?? 0) + (datasetLabelHorizonPurge?.crossing ?? 0),
|
|
4523
|
+
unknown: (calendarSplit.labelHorizonPurge?.unknown ?? 0) + (datasetLabelHorizonPurge?.unknown ?? 0)
|
|
4524
|
+
}
|
|
4525
|
+
} : {},
|
|
4377
4526
|
scopeRows,
|
|
4378
4527
|
trainRows,
|
|
4379
4528
|
validationRows,
|
|
@@ -4411,6 +4560,7 @@ var runAiPocketSearchCommand = async ({
|
|
|
4411
4560
|
maxCombinations,
|
|
4412
4561
|
validationSplit,
|
|
4413
4562
|
testSplit,
|
|
4563
|
+
testSince,
|
|
4414
4564
|
maxBatch,
|
|
4415
4565
|
maxSymbolCountShare,
|
|
4416
4566
|
objective,
|
|
@@ -4458,6 +4608,7 @@ var runAiPocketSearchCommand = async ({
|
|
|
4458
4608
|
const research = runAiPocketSearchResearch({
|
|
4459
4609
|
rows,
|
|
4460
4610
|
options,
|
|
4611
|
+
datasetLabelHorizonPurge: evaluatedDataset.labelHorizonPurge,
|
|
4461
4612
|
onProgress: createAiPocketSearchResearchProgressPresenter()
|
|
4462
4613
|
});
|
|
4463
4614
|
const {
|
|
@@ -4531,6 +4682,9 @@ var runAiPocketSearchCommand = async ({
|
|
|
4531
4682
|
objective,
|
|
4532
4683
|
validationSplit,
|
|
4533
4684
|
testSplit,
|
|
4685
|
+
testSince,
|
|
4686
|
+
partitionMode: testSince == null ? "timestamp-ratio" : "calendar-cutoff",
|
|
4687
|
+
...research.labelHorizonPurge ? { labelHorizonPurge: research.labelHorizonPurge } : {},
|
|
4534
4688
|
sealedTest,
|
|
4535
4689
|
minValidationSupport: search.stats.cadence.minValidationSupport,
|
|
4536
4690
|
reportPath,
|
|
@@ -4667,6 +4821,11 @@ import_args.default.option(
|
|
|
4667
4821
|
"Trailing timestamp-grouped test share; default 0.4 (outer 60/40)",
|
|
4668
4822
|
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.testSplit
|
|
4669
4823
|
);
|
|
4824
|
+
import_args.default.option(
|
|
4825
|
+
["z", "testSince"],
|
|
4826
|
+
"Fixed calendar test boundary (UTC ISO or epoch ms); overrides the split ratio and requires validationSplit 0",
|
|
4827
|
+
""
|
|
4828
|
+
);
|
|
4670
4829
|
import_args.default.option(
|
|
4671
4830
|
["w", "sealTest"],
|
|
4672
4831
|
"Reserve test bounds without exposing its rows or economics to discovery",
|
|
@@ -682,7 +682,12 @@ var writeCoreResearchMatches = async (params) => {
|
|
|
682
682
|
"controlPnl",
|
|
683
683
|
"candidatePnl",
|
|
684
684
|
"pnlDelta",
|
|
685
|
-
"
|
|
685
|
+
"controlExitReason",
|
|
686
|
+
"candidateExitReason",
|
|
687
|
+
"exitReasonChanged",
|
|
688
|
+
"controlExitCode",
|
|
689
|
+
"candidateExitCode",
|
|
690
|
+
"exitCodeChanged"
|
|
686
691
|
].map(csv).join(",") + "\n"
|
|
687
692
|
);
|
|
688
693
|
for (const comparison of params.comparisons) {
|
|
@@ -700,7 +705,12 @@ var writeCoreResearchMatches = async (params) => {
|
|
|
700
705
|
pair.control.netProfit,
|
|
701
706
|
pair.candidate.netProfit,
|
|
702
707
|
pair.pnlDelta,
|
|
703
|
-
pair.
|
|
708
|
+
pair.control.exitReason,
|
|
709
|
+
pair.candidate.exitReason,
|
|
710
|
+
pair.exitReasonChanged,
|
|
711
|
+
pair.control.exitCode,
|
|
712
|
+
pair.candidate.exitCode,
|
|
713
|
+
pair.exitCodeChanged
|
|
704
714
|
].map(csv).join(",") + "\n"
|
|
705
715
|
);
|
|
706
716
|
}
|
|
@@ -1317,6 +1327,7 @@ var compareCoreResearchVariants = (params) => {
|
|
|
1317
1327
|
candidate: candidateTrade,
|
|
1318
1328
|
pnlDelta: candidateTrade.netProfit - controlTrade.netProfit,
|
|
1319
1329
|
exitReasonChanged: candidateTrade.exitReason !== controlTrade.exitReason,
|
|
1330
|
+
exitCodeChanged: candidateTrade.exitCode !== controlTrade.exitCode,
|
|
1320
1331
|
entryTimestampDeltaMs: candidateTrade.entryTimestamp - controlTrade.entryTimestamp,
|
|
1321
1332
|
exitTimestampDeltaMs: candidateTrade.exitTimestamp - controlTrade.exitTimestamp
|
|
1322
1333
|
});
|
|
@@ -1528,6 +1539,7 @@ var normalizeCompletedTrade = (params) => {
|
|
|
1528
1539
|
"totalSlippageCost"
|
|
1529
1540
|
),
|
|
1530
1541
|
exitReason: result.exitReason,
|
|
1542
|
+
...result.exitCode ? { exitCode: result.exitCode } : {},
|
|
1531
1543
|
regime: resolveCoreResearchRegime(row)
|
|
1532
1544
|
};
|
|
1533
1545
|
};
|
|
@@ -1571,7 +1583,7 @@ var aggregatePositionCycles = (trades) => {
|
|
|
1571
1583
|
}
|
|
1572
1584
|
const primary = primaryRows[0];
|
|
1573
1585
|
if (cycle.some(
|
|
1574
|
-
(trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason
|
|
1586
|
+
(trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason || trade.exitCode !== primary.exitCode
|
|
1575
1587
|
)) {
|
|
1576
1588
|
throw new Error(
|
|
1577
1589
|
`Position cycle ${identity} contains inconsistent final exits`
|
|
@@ -1753,7 +1765,7 @@ var buildCurveSvg = (tradesByVariant, cohort, curve) => {
|
|
|
1753
1765
|
)}</svg><div class="legend">${series.map((entry) => `<span><i style="background:${entry.color}"></i>${escapeHtml(entry.id)}</span>`).join("")}</div>`;
|
|
1754
1766
|
};
|
|
1755
1767
|
var comparisonCards = (comparisons) => comparisons.map(
|
|
1756
|
-
(comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Matched \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394>0)</dt><dd>${format(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
|
|
1768
|
+
(comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Exit reason changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitReasonChanged).length}</dd><dt>Exit code changes</dt><dd>${comparison.matchedPairs.filter((pair) => pair.exitCodeChanged).length}</dd><dt>Matched \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394>0)</dt><dd>${format(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
|
|
1757
1769
|
).join("");
|
|
1758
1770
|
var regimeRows = (variants) => {
|
|
1759
1771
|
const keys = [
|
|
@@ -1767,7 +1779,7 @@ var regimeRows = (variants) => {
|
|
|
1767
1779
|
).join("");
|
|
1768
1780
|
};
|
|
1769
1781
|
var traceRows = (variants) => variants.map(
|
|
1770
|
-
(variant) => `<tr><td>${escapeHtml(variant.variant.label)}</td><td>${escapeHtml(variant.reconciliation.status)}</td><td>${escapeHtml(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
|
|
1782
|
+
(variant) => `<tr><td>${escapeHtml(variant.variant.label)}</td><td>${escapeHtml(variant.reconciliation.status)}</td><td>${escapeHtml(JSON.stringify(variant.setupIdentitySources))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.events))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.skipCounts))}</td><td>${escapeHtml(JSON.stringify(variant.traceFunnel.exitCodes))}</td><td>${escapeHtml(variant.latestSignalTimeRegime?.key ?? "n/a")}</td></tr>`
|
|
1771
1783
|
).join("");
|
|
1772
1784
|
var buildCoreResearchHtml = (params) => {
|
|
1773
1785
|
const { spec, result, tradesByVariant } = params;
|
|
@@ -1793,7 +1805,7 @@ var buildCoreResearchHtml = (params) => {
|
|
|
1793
1805
|
([key, value]) => `<span class="${value === "present" ? "positive" : "negative"}">${escapeHtml(key)}: ${value}</span>`
|
|
1794
1806
|
).join(
|
|
1795
1807
|
""
|
|
1796
|
-
)}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort
|
|
1808
|
+
)}</section><h2>ALL equity</h2>${buildCurveSvg(tradesByVariant, "ALL", "equity")}<h2>ALL realized drawdown</h2>${buildCurveSvg(tradesByVariant, "ALL", "drawdown")}<h2>LONG equity</h2>${buildCurveSvg(tradesByVariant, "LONG", "equity")}<h2>LONG realized drawdown</h2>${buildCurveSvg(tradesByVariant, "LONG", "drawdown")}<h2>SHORT equity</h2>${buildCurveSvg(tradesByVariant, "SHORT", "equity")}<h2>SHORT realized drawdown</h2>${buildCurveSvg(tradesByVariant, "SHORT", "drawdown")}<h2>Fixed cohort metrics</h2><p class="muted">Avg PnL/trade = cohort PnL / cohort completed positions. ALL drawdown is aggregate portfolio; LONG/SHORT drawdowns are side-only.</p><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${allRows}</tbody></table></div><h2>Fold and monthly robustness</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Window</th><th>Cohort</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>WR</th><th>Realized DD</th><th>Cad/day</th><th>Avg win</th><th>Avg loss</th><th>Payoff</th><th>Median</th><th>P05</th><th>P95</th><th>Median hold h</th><th>Max loss streak</th></tr></thead><tbody>${robustnessRows}</tbody></table></div><h2>Cost stress</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Extra round-trip bps</th><th>N</th><th>PnL</th><th>Avg PnL/trade</th><th>PF</th><th>Realized DD</th></tr></thead><tbody>${costRows}</tbody></table></div><h2>Signal-time market regimes</h2><div class="scroll"><table><thead><tr><th>Trend | Volatility | Breadth | Derivatives</th><th>Variant</th><th>N</th><th>PnL</th><th>PF</th><th>WR</th></tr></thead><tbody>${regimeRows(result.variants)}</tbody></table></div><h2>Trace funnel and reconciliation</h2><div class="scroll"><table><thead><tr><th>Variant</th><th>Reconcile</th><th>Setup identity source</th><th>Events</th><th>Top skips</th><th>Exit codes</th><th>Latest signal-time regime</th></tr></thead><tbody>${traceRows(result.variants)}</tbody></table></div><h2>Multiple testing / overfitting</h2><pre>${escapeHtml(JSON.stringify({ multipleTesting: result.multipleTesting, overfittingDiagnostics: result.overfittingDiagnostics }, null, 2))}</pre><h2>Lineage</h2><pre>${escapeHtml(JSON.stringify({ stage: spec.stage, universeSha256: spec.universe.sha256, lineage: spec.lineage ?? null, artifactHashes: result.artifactHashes }, null, 2))}</pre></main></body></html>`;
|
|
1797
1809
|
};
|
|
1798
1810
|
|
|
1799
1811
|
// src/lib/backtest/checkpoint.ts
|
|
@@ -2099,8 +2111,9 @@ var applyCoreResearchRobustnessGuardrails = (params) => {
|
|
|
2099
2111
|
var import_promises3 = __toESM(require("fs/promises"));
|
|
2100
2112
|
var import_node_path4 = __toESM(require("path"));
|
|
2101
2113
|
var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
2102
|
-
const events =
|
|
2103
|
-
const skipCounts =
|
|
2114
|
+
const events = /* @__PURE__ */ Object.create(null);
|
|
2115
|
+
const skipCounts = /* @__PURE__ */ Object.create(null);
|
|
2116
|
+
const exitCodes = /* @__PURE__ */ Object.create(null);
|
|
2104
2117
|
for (const inputPath of filePaths) {
|
|
2105
2118
|
const filePath = import_node_path4.default.resolve(inputPath);
|
|
2106
2119
|
const text = await import_promises3.default.readFile(filePath, "utf8");
|
|
@@ -2120,6 +2133,9 @@ var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
|
2120
2133
|
skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
|
|
2121
2134
|
}
|
|
2122
2135
|
}
|
|
2136
|
+
if (event.event === "position_exited" && event.exitCode) {
|
|
2137
|
+
exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
|
|
2138
|
+
}
|
|
2123
2139
|
}
|
|
2124
2140
|
}
|
|
2125
2141
|
return {
|
|
@@ -2128,6 +2144,11 @@ var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
|
2128
2144
|
Object.entries(skipCounts).sort(
|
|
2129
2145
|
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
2130
2146
|
)
|
|
2147
|
+
),
|
|
2148
|
+
exitCodes: Object.fromEntries(
|
|
2149
|
+
Object.entries(exitCodes).sort(
|
|
2150
|
+
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
2151
|
+
)
|
|
2131
2152
|
)
|
|
2132
2153
|
};
|
|
2133
2154
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/cli",
|
|
3
|
-
"version": "3.1.29-beta.
|
|
3
|
+
"version": "3.1.29-beta.269",
|
|
4
4
|
"description": "Official CLI for the TradeJS TypeScript framework: infra setup, backtests, signals, bots, and ML workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tradejs",
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"tradejs": "dist/cli.js"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@tradejs/connectors": "^3.1.29-beta.
|
|
41
|
-
"@tradejs/core": "^3.1.29-beta.
|
|
42
|
-
"@tradejs/indicators": "^3.1.29-beta.
|
|
43
|
-
"@tradejs/infra": "^3.1.29-beta.
|
|
44
|
-
"@tradejs/node": "^3.1.29-beta.
|
|
40
|
+
"@tradejs/connectors": "^3.1.29-beta.269",
|
|
41
|
+
"@tradejs/core": "^3.1.29-beta.269",
|
|
42
|
+
"@tradejs/indicators": "^3.1.29-beta.269",
|
|
43
|
+
"@tradejs/infra": "^3.1.29-beta.269",
|
|
44
|
+
"@tradejs/node": "^3.1.29-beta.269",
|
|
45
45
|
"@tradejs/strategy-kit": "3.0.6",
|
|
46
|
-
"@tradejs/types": "^3.1.29-beta.
|
|
46
|
+
"@tradejs/types": "^3.1.29-beta.269",
|
|
47
47
|
"args": "^5.0.3",
|
|
48
48
|
"bcryptjs": "^2.4.3",
|
|
49
49
|
"chalk": "4.1.2",
|