@tradejs/cli 2.0.20 → 3.0.0
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 +267 -98
- package/dist/lib/aiTrainOptions.js +36 -0
- package/dist/lib/coreResearch.js +73 -8
- package/dist/lib/runtimeLineage.js +2 -14
- package/dist/lib/strategyRelease.js +82 -17
- package/dist/scripts/agentRun.js +22 -23
- package/dist/scripts/aiPocketSearch.js +2 -1
- package/dist/scripts/aiTrain.js +64 -13
- package/dist/scripts/coreResearch.js +73 -8
- package/dist/scripts/replay.js +2 -14
- package/dist/scripts/replayRunner.js +2 -14
- package/dist/scripts/runtimeEvidence.js +1 -0
- package/dist/scripts/runtimeScorecard.js +35 -2
- package/dist/scripts/signals.js +2 -14
- package/dist/scripts/signalsDaemon.js +2 -14
- package/dist/scripts/strategyRelease.js +82 -17
- package/package.json +9 -9
package/dist/cli.js
CHANGED
|
@@ -241,7 +241,7 @@ var agentRun_exports = {};
|
|
|
241
241
|
__export(agentRun_exports, {
|
|
242
242
|
main: () => main
|
|
243
243
|
});
|
|
244
|
-
var import_args, import_chalk, import_promises, import_node_os, import_node_path, import_node_child_process, import_constants, import_redis, import_userSettings, flags, projectRoot, AGENT_SYSTEM_PROMPT, trimTextTail, escapeHtml, isOpenRouterEndpoint, resolveChatCompletionsUrl, runCommand, runCommandOrThrow, listFilesRecursive, collectStrategyContextFiles, renderFileContext, buildPrompt, extractAssistantContent, requestAgentDiff, applyDiff, getChangedFilesFromGit, runValidationSuite, buildAgentTelegramReport, loadRun, saveAgentRun, resolveGithubRepository, createGithubPullRequest, createWorktree, cleanupWorktree, main;
|
|
244
|
+
var import_args, import_chalk, import_promises, import_node_os, import_node_path, import_node_child_process, import_constants, import_aiEndpoints, import_redis, import_userSettings, flags, projectRoot, AGENT_SYSTEM_PROMPT, trimTextTail, escapeHtml, isOpenRouterEndpoint, resolveChatCompletionsUrl, runCommand, runCommandOrThrow, listFilesRecursive, collectStrategyContextFiles, renderFileContext, buildPrompt, extractAssistantContent, requestAgentDiff, applyDiff, getChangedFilesFromGit, runValidationSuite, buildAgentTelegramReport, loadRun, saveAgentRun, resolveGithubRepository, createGithubPullRequest, createWorktree, cleanupWorktree, main;
|
|
245
245
|
var init_agentRun = __esm({
|
|
246
246
|
"src/scripts/agentRun.ts"() {
|
|
247
247
|
"use strict";
|
|
@@ -252,6 +252,7 @@ var init_agentRun = __esm({
|
|
|
252
252
|
import_node_path = __toESM(require("path"));
|
|
253
253
|
import_node_child_process = require("child_process");
|
|
254
254
|
import_constants = require("@tradejs/core/constants");
|
|
255
|
+
import_aiEndpoints = require("@tradejs/core/aiEndpoints");
|
|
255
256
|
import_redis = require("@tradejs/infra/redis");
|
|
256
257
|
import_userSettings = require("@tradejs/infra/userSettings");
|
|
257
258
|
init_telegramReports();
|
|
@@ -439,35 +440,33 @@ Rules:
|
|
|
439
440
|
};
|
|
440
441
|
requestAgentDiff = async (userName2, messages) => {
|
|
441
442
|
const settings = await (0, import_userSettings.getUserSettings)(userName2);
|
|
442
|
-
|
|
443
|
+
const aiApiEndpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings.AI_API_ENDPOINT);
|
|
444
|
+
if (!settings.AI_API_KEY || !aiApiEndpoint) {
|
|
443
445
|
throw new Error(`AI settings are incomplete for user ${userName2}`);
|
|
444
446
|
}
|
|
445
|
-
if (!isOpenRouterEndpoint(
|
|
447
|
+
if (!isOpenRouterEndpoint(aiApiEndpoint)) {
|
|
446
448
|
throw new Error(
|
|
447
|
-
`Research agent requires OpenRouter endpoint, got: ${
|
|
449
|
+
`Research agent requires OpenRouter endpoint, got: ${aiApiEndpoint}`
|
|
448
450
|
);
|
|
449
451
|
}
|
|
450
|
-
const response = await fetch(
|
|
451
|
-
|
|
452
|
-
{
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
452
|
+
const response = await fetch(resolveChatCompletionsUrl(aiApiEndpoint), {
|
|
453
|
+
method: "POST",
|
|
454
|
+
headers: {
|
|
455
|
+
Authorization: `Bearer ${settings.AI_API_KEY}`,
|
|
456
|
+
"Content-Type": "application/json",
|
|
457
|
+
"HTTP-Referer": "https://tradejs.dev",
|
|
458
|
+
"X-Title": "TradeJS Research Agent"
|
|
459
|
+
},
|
|
460
|
+
body: JSON.stringify({
|
|
461
|
+
model: RESEARCH_AGENT_MODEL,
|
|
462
|
+
messages,
|
|
463
|
+
reasoning: {
|
|
464
|
+
effort: RESEARCH_AGENT_REASONING_EFFORT,
|
|
465
|
+
exclude: true
|
|
459
466
|
},
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
reasoning: {
|
|
464
|
-
effort: RESEARCH_AGENT_REASONING_EFFORT,
|
|
465
|
-
exclude: true
|
|
466
|
-
},
|
|
467
|
-
temperature: 0.2
|
|
468
|
-
})
|
|
469
|
-
}
|
|
470
|
-
);
|
|
467
|
+
temperature: 0.2
|
|
468
|
+
})
|
|
469
|
+
});
|
|
471
470
|
if (!response.ok) {
|
|
472
471
|
throw new Error(
|
|
473
472
|
`OpenRouter request failed (${response.status}): ${await response.text()}`
|
|
@@ -1857,7 +1856,7 @@ var init_aiTrainMetrics = __esm({
|
|
|
1857
1856
|
});
|
|
1858
1857
|
|
|
1859
1858
|
// src/lib/aiTrainOptions.ts
|
|
1860
|
-
var parseTimestampFilter, parseTrailingPeriodMs, parseQualityThresholds, parseTerminalWindowDays, parseDumpFeatureMode, hasCliOption, resolveAiTrainRecentLimit;
|
|
1859
|
+
var parseTimestampFilter, parseTrailingPeriodMs, parseQualityThresholds, parseTerminalWindowDays, parseAiTrainDirectionPolicy, assertAiTrainDirectionPolicy, parseDumpFeatureMode, hasCliOption, resolveAiTrainRecentLimit;
|
|
1861
1860
|
var init_aiTrainOptions = __esm({
|
|
1862
1861
|
"src/lib/aiTrainOptions.ts"() {
|
|
1863
1862
|
"use strict";
|
|
@@ -1913,6 +1912,38 @@ var init_aiTrainOptions = __esm({
|
|
|
1913
1912
|
const raw = String(value ?? "90,30,7").split(",").map((part) => Number(part.trim())).filter((part) => Number.isFinite(part)).map((part) => Math.trunc(part)).filter((part) => part > 0);
|
|
1914
1913
|
return [...new Set(raw)].sort((left, right) => right - left);
|
|
1915
1914
|
};
|
|
1915
|
+
parseAiTrainDirectionPolicy = (value) => {
|
|
1916
|
+
const normalized = String(value ?? "both").trim().toLowerCase().replaceAll("-", "_");
|
|
1917
|
+
if (normalized === "both" || normalized === "long_only" || normalized === "short_only" || normalized === "direction_aware") {
|
|
1918
|
+
return normalized;
|
|
1919
|
+
}
|
|
1920
|
+
throw new Error(
|
|
1921
|
+
`Invalid --directionPolicy value "${String(value)}". Use both, long_only, short_only, or direction_aware.`
|
|
1922
|
+
);
|
|
1923
|
+
};
|
|
1924
|
+
assertAiTrainDirectionPolicy = ({
|
|
1925
|
+
directionPolicy,
|
|
1926
|
+
byDirection,
|
|
1927
|
+
terminalWindows
|
|
1928
|
+
}) => {
|
|
1929
|
+
const suppressedDirection = directionPolicy === "long_only" ? "SHORT" : directionPolicy === "short_only" ? "LONG" : null;
|
|
1930
|
+
if (!suppressedDirection) return;
|
|
1931
|
+
for (const scope of [{ label: "full", byDirection }, ...terminalWindows]) {
|
|
1932
|
+
const summary = scope.byDirection.find(
|
|
1933
|
+
(entry) => entry.direction === suppressedDirection
|
|
1934
|
+
);
|
|
1935
|
+
if (!summary) {
|
|
1936
|
+
throw new Error(
|
|
1937
|
+
`${scope.label} ${suppressedDirection} direction summary is required for ${directionPolicy}`
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
if (summary.summary.approved !== 0) {
|
|
1941
|
+
throw new Error(
|
|
1942
|
+
`${scope.label} has ${summary.summary.approved} ${suppressedDirection} approved rows under ${directionPolicy}`
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1916
1947
|
parseDumpFeatureMode = (value) => {
|
|
1917
1948
|
const text = String(value ?? "none").trim().toLowerCase();
|
|
1918
1949
|
if (!text || text === "none" || text === "false" || text === "0") {
|
|
@@ -4333,7 +4364,7 @@ var aiPocketSearch_exports = {};
|
|
|
4333
4364
|
__export(aiPocketSearch_exports, {
|
|
4334
4365
|
main: () => main3
|
|
4335
4366
|
});
|
|
4336
|
-
var import_args3, import_chalk4, import_promises4, import_path2, import_progress2, import_ai3, import_ai4, ListIt2, flags3, createListIt2, createTable2, formatRatio, formatProfit, formatNumber2, colorizeRatio, colorizeProfit, colorizeNumber, toReportTimestamp, markdownPlainTable, buildPendingMarkdownReport, normalizeInt, normalizePositiveInt, normalizeNonNegativeNumber, normalizeRatio, normalizeUnitRatio, normalizeFeatureProfile, normalizeFeaturePolicy, normalizeCoverageMode, normalizeCadenceMode, normalizeObjective, normalizeQuality, isAiApproval, listMergedFiles, deriveStrategyNameFromFile, getMergedGroupId, resolveMarkdownReportPath, sortDatasetPartPaths, resolveMergedDatasetFiles, getDatasetRowTimestamp, findMaxSelectedTimestamp, getDeterministicModelCandidate, resolveScopeRows, printSection, buildSummaryRows, buildPocketRows, buildQualityRows, tickProgressBarTo, searchPhaseLabels, hasCliOption2, main3;
|
|
4367
|
+
var import_args3, import_chalk4, import_promises4, import_path2, import_progress2, import_ai3, import_ai4, import_registry, ListIt2, flags3, createListIt2, createTable2, formatRatio, formatProfit, formatNumber2, colorizeRatio, colorizeProfit, colorizeNumber, toReportTimestamp, markdownPlainTable, buildPendingMarkdownReport, normalizeInt, normalizePositiveInt, normalizeNonNegativeNumber, normalizeRatio, normalizeUnitRatio, normalizeFeatureProfile, normalizeFeaturePolicy, normalizeCoverageMode, normalizeCadenceMode, normalizeObjective, normalizeQuality, isAiApproval, listMergedFiles, deriveStrategyNameFromFile, getMergedGroupId, resolveMarkdownReportPath, sortDatasetPartPaths, resolveMergedDatasetFiles, getDatasetRowTimestamp, findMaxSelectedTimestamp, getDeterministicModelCandidate, resolveScopeRows, printSection, buildSummaryRows, buildPocketRows, buildQualityRows, tickProgressBarTo, searchPhaseLabels, hasCliOption2, main3;
|
|
4337
4368
|
var init_aiPocketSearch2 = __esm({
|
|
4338
4369
|
"src/scripts/aiPocketSearch.ts"() {
|
|
4339
4370
|
"use strict";
|
|
@@ -4344,6 +4375,7 @@ var init_aiPocketSearch2 = __esm({
|
|
|
4344
4375
|
import_progress2 = __toESM(require("progress"));
|
|
4345
4376
|
import_ai3 = require("@tradejs/infra/ai");
|
|
4346
4377
|
import_ai4 = require("@tradejs/node/ai");
|
|
4378
|
+
import_registry = require("@tradejs/node/registry");
|
|
4347
4379
|
init_aiTrainDataset();
|
|
4348
4380
|
init_aiTrainMetrics();
|
|
4349
4381
|
init_aiTrainOptions();
|
|
@@ -5057,7 +5089,7 @@ var init_aiPocketSearch2 = __esm({
|
|
|
5057
5089
|
const explicitReportFile = String(flags3.reportFile || "").trim();
|
|
5058
5090
|
const generatedAt = Date.now();
|
|
5059
5091
|
const excludedFeaturePaths = /* @__PURE__ */ new Map();
|
|
5060
|
-
await (0,
|
|
5092
|
+
await (0, import_registry.ensureStrategyPluginsLoaded)();
|
|
5061
5093
|
const filePaths = await resolveMergedDatasetFiles({
|
|
5062
5094
|
outDir,
|
|
5063
5095
|
strategyName,
|
|
@@ -6856,7 +6888,7 @@ var aiTrain_exports = {};
|
|
|
6856
6888
|
__export(aiTrain_exports, {
|
|
6857
6889
|
main: () => main4
|
|
6858
6890
|
});
|
|
6859
|
-
var import_args4, import_chalk5, import_promises7, import_path3, import_progress3, import_constants3, import_ai5, import_strategyReleaseEvidence, import_redis3, import_ai6, import_constants4, ListIt3, flags4, createListIt3, createTable3, formatRatio3, formatProfit2, formatMetricNumber, colorizeRatio2, colorizePercent, colorizeProfit2, colorizeMetricNumber, colorizeQuality, normalizeInt2, normalizePositiveInt2, normalizeNonNegativeNumber2, normalizeQuality2, isProfitableTrade, isAiApproval2, listMergedFiles2, deriveStrategyNameFromFile2, getMergedGroupId2, sortDatasetPartPaths2, resolveMergedDatasetFiles2, resolvePromptRunContext, getDeterministicGateContext, resolveDeterministicGateEvaluation, summarizeDeterministicGateEvaluations, printSection2, persistAiChartSnapshot, resolveDatasetFiles, getDatasetRowTimestamp2, findMaxSelectedTimestamp2, main4;
|
|
6891
|
+
var import_args4, import_chalk5, import_promises7, import_path3, import_progress3, import_constants3, import_ai5, import_strategyReleaseEvidence, import_redis3, import_ai6, import_registry2, import_constants4, ListIt3, flags4, createListIt3, createTable3, formatRatio3, formatProfit2, formatMetricNumber, colorizeRatio2, colorizePercent, colorizeProfit2, colorizeMetricNumber, colorizeQuality, normalizeInt2, normalizePositiveInt2, normalizeNonNegativeNumber2, normalizeQuality2, isProfitableTrade, isAiApproval2, listMergedFiles2, deriveStrategyNameFromFile2, getMergedGroupId2, sortDatasetPartPaths2, resolveMergedDatasetFiles2, resolvePromptRunContext, getDeterministicGateContext, resolveDeterministicGateEvaluation, summarizeDeterministicGateEvaluations, printSection2, persistAiChartSnapshot, resolveDatasetFiles, getDatasetRowTimestamp2, findMaxSelectedTimestamp2, main4;
|
|
6860
6892
|
var init_aiTrain = __esm({
|
|
6861
6893
|
"src/scripts/aiTrain.ts"() {
|
|
6862
6894
|
"use strict";
|
|
@@ -6870,6 +6902,7 @@ var init_aiTrain = __esm({
|
|
|
6870
6902
|
import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
6871
6903
|
import_redis3 = require("@tradejs/infra/redis");
|
|
6872
6904
|
import_ai6 = require("@tradejs/node/ai");
|
|
6905
|
+
import_registry2 = require("@tradejs/node/registry");
|
|
6873
6906
|
import_constants4 = require("@tradejs/node/constants");
|
|
6874
6907
|
init_aiTrainCharts();
|
|
6875
6908
|
init_aiTrainMetrics();
|
|
@@ -6911,6 +6944,11 @@ var init_aiTrain = __esm({
|
|
|
6911
6944
|
"Minimum AI quality required to approve entry",
|
|
6912
6945
|
4
|
|
6913
6946
|
);
|
|
6947
|
+
import_args4.default.option(
|
|
6948
|
+
["Y", "directionPolicy"],
|
|
6949
|
+
"Evidence label for the enforced gate policy: both, long_only, short_only, or direction_aware",
|
|
6950
|
+
"both"
|
|
6951
|
+
);
|
|
6914
6952
|
import_args4.default.option(
|
|
6915
6953
|
["l", "localOnly"],
|
|
6916
6954
|
"Replay deterministic adapter gate without AI provider calls",
|
|
@@ -7301,6 +7339,7 @@ var init_aiTrain = __esm({
|
|
|
7301
7339
|
});
|
|
7302
7340
|
const qualityThresholds = parseQualityThresholds(flags4.qualityThresholds);
|
|
7303
7341
|
const terminalWindowDays = parseTerminalWindowDays(flags4.terminalWindows);
|
|
7342
|
+
const directionPolicy = parseAiTrainDirectionPolicy(flags4.directionPolicy);
|
|
7304
7343
|
const dumpEvaluationsPath = String(flags4.dumpEvaluations || "").trim();
|
|
7305
7344
|
const dumpFeatureMode = parseDumpFeatureMode(flags4.dumpFeatures);
|
|
7306
7345
|
if (dumpFeatureMode !== "none" && !dumpEvaluationsPath) {
|
|
@@ -7322,7 +7361,7 @@ var init_aiTrain = __esm({
|
|
|
7322
7361
|
const userName2 = String(flags4.user || "root").trim() || "root";
|
|
7323
7362
|
const model = String(flags4.model || import_ai6.DEFAULT_AI_MODEL).trim() || import_ai6.DEFAULT_AI_MODEL;
|
|
7324
7363
|
const parallel = normalizePositiveInt2(flags4.parallel, import_constants4.AI_CONCURRENCY_LIMIT);
|
|
7325
|
-
await (0,
|
|
7364
|
+
await (0, import_registry2.ensureStrategyPluginsLoaded)();
|
|
7326
7365
|
const filePaths = await resolveDatasetFiles();
|
|
7327
7366
|
const datasetId = getMergedGroupId2(filePaths[0] || "")?.mergeId;
|
|
7328
7367
|
const { totalRows, selectedRows } = await (0, import_ai5.countAiDatasetRows)({
|
|
@@ -7515,18 +7554,29 @@ var init_aiTrain = __esm({
|
|
|
7515
7554
|
finalEvaluations,
|
|
7516
7555
|
terminalWindowDays
|
|
7517
7556
|
);
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
configIds: finalEvaluations.map((evaluation) => evaluation.configId),
|
|
7523
|
-
sourceSha256s: await Promise.all(filePaths.map(import_strategyReleaseEvidence.strategyEvidenceFileSha256)),
|
|
7524
|
-
runContext: {
|
|
7525
|
-
mode: localOnly ? "local-deterministic" : "llm",
|
|
7526
|
-
model: localOnly ? "local-deterministic" : model,
|
|
7527
|
-
minQuality
|
|
7528
|
-
}
|
|
7557
|
+
assertAiTrainDirectionPolicy({
|
|
7558
|
+
directionPolicy,
|
|
7559
|
+
byDirection: directionSummaries,
|
|
7560
|
+
terminalWindows
|
|
7529
7561
|
});
|
|
7562
|
+
const topRejectReasons = summarizeAiTrainRejectReasons(finalEvaluations);
|
|
7563
|
+
const lineage = {
|
|
7564
|
+
...await buildAiTrainLineage({
|
|
7565
|
+
projectRoot: String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd(),
|
|
7566
|
+
strategyName,
|
|
7567
|
+
configIds: finalEvaluations.map((evaluation) => evaluation.configId),
|
|
7568
|
+
sourceSha256s: await Promise.all(
|
|
7569
|
+
filePaths.map(import_strategyReleaseEvidence.strategyEvidenceFileSha256)
|
|
7570
|
+
),
|
|
7571
|
+
runContext: {
|
|
7572
|
+
mode: localOnly ? "local-deterministic" : "llm",
|
|
7573
|
+
model: localOnly ? "local-deterministic" : model,
|
|
7574
|
+
minQuality,
|
|
7575
|
+
directionPolicy
|
|
7576
|
+
}
|
|
7577
|
+
}),
|
|
7578
|
+
directionPolicy
|
|
7579
|
+
};
|
|
7530
7580
|
const evaluated = summary.correct + summary.incorrect;
|
|
7531
7581
|
const result = {
|
|
7532
7582
|
run: {
|
|
@@ -7548,7 +7598,8 @@ var init_aiTrain = __esm({
|
|
|
7548
7598
|
model: localOnly ? "local-deterministic" : model,
|
|
7549
7599
|
parallel: concurrency,
|
|
7550
7600
|
dumpFeatures: dumpFeatureMode,
|
|
7551
|
-
terminalWindows: terminalWindowDays
|
|
7601
|
+
terminalWindows: terminalWindowDays,
|
|
7602
|
+
directionPolicy
|
|
7552
7603
|
},
|
|
7553
7604
|
outcome: summary,
|
|
7554
7605
|
byDirection: directionSummaries,
|
|
@@ -11495,13 +11546,13 @@ var init_hyperliquidWhaleBackfill = __esm({
|
|
|
11495
11546
|
});
|
|
11496
11547
|
|
|
11497
11548
|
// src/lib/marketContextPrepare.ts
|
|
11498
|
-
var import_chalk9, import_marketContext3,
|
|
11549
|
+
var import_chalk9, import_marketContext3, import_registry3, uniqueReasons, shouldPrepareDerivativesContextForRun, shouldPrepareBinanceMarketContextForRun, shouldPrepareCoinMarketCapContextForRun, shouldPrepareHyperliquidWhaleContextForRun, resolveCoreContextSources, resolveMarketContextRunRequirements, resolveCoinMarketCapBackfillForMode, buildCoinMarketCapBackfillParams, buildBinanceMarketBackfillParams, buildDerivativesBackfillParams, prepareMarketContextForRun;
|
|
11499
11550
|
var init_marketContextPrepare = __esm({
|
|
11500
11551
|
"src/lib/marketContextPrepare.ts"() {
|
|
11501
11552
|
"use strict";
|
|
11502
11553
|
import_chalk9 = __toESM(require("chalk"));
|
|
11503
11554
|
import_marketContext3 = require("@tradejs/infra/timescale/marketContext");
|
|
11504
|
-
|
|
11555
|
+
import_registry3 = require("@tradejs/node/registry");
|
|
11505
11556
|
init_binanceMarketContextBackfill();
|
|
11506
11557
|
init_derivativesContextBackfill();
|
|
11507
11558
|
init_coinMarketCapContextBackfill();
|
|
@@ -11579,11 +11630,11 @@ var init_marketContextPrepare = __esm({
|
|
|
11579
11630
|
resolveCoreContextSources = async (params) => {
|
|
11580
11631
|
const strategyNames = [...new Set(params.strategyNames ?? [])];
|
|
11581
11632
|
if (!strategyNames.length) return [];
|
|
11582
|
-
await (0,
|
|
11633
|
+
await (0, import_registry3.ensureStrategyPluginsLoaded)(params.projectRoot);
|
|
11583
11634
|
return [
|
|
11584
11635
|
...new Set(
|
|
11585
11636
|
strategyNames.flatMap(
|
|
11586
|
-
(strategyName) => (0,
|
|
11637
|
+
(strategyName) => (0, import_registry3.getStrategyManifest)(strategyName, params.projectRoot)?.contextRequirements?.core ?? []
|
|
11587
11638
|
)
|
|
11588
11639
|
)
|
|
11589
11640
|
];
|
|
@@ -21566,7 +21617,7 @@ var init_comparison = __esm({
|
|
|
21566
21617
|
});
|
|
21567
21618
|
|
|
21568
21619
|
// src/lib/coreResearch/dataset.ts
|
|
21569
|
-
var import_node_crypto8, import_node_fs4, import_node_path10, import_node_readline, asRecord3, finiteNumber4, normalizeDirection, findStrategySetupIdentity, classifyBreadth, classifyDerivatives, resolveCoreResearchRegime, normalizeCompletedTrade, completedTradeIdentity, compareText, compareTrades, readCoreResearchVariant;
|
|
21620
|
+
var import_node_crypto8, import_node_fs4, import_node_path10, import_node_readline, asRecord3, finiteNumber4, normalizeDirection, findStrategySetupIdentity, classifyBreadth, classifyDerivatives, resolveCoreResearchRegime, normalizeCompletedTrade, completedTradeIdentity, positionCycleIdentity, sumBy, weightedPrice, aggregatePositionCycles, compareText, compareTrades, readCoreResearchVariant;
|
|
21570
21621
|
var init_dataset = __esm({
|
|
21571
21622
|
"src/lib/coreResearch/dataset.ts"() {
|
|
21572
21623
|
"use strict";
|
|
@@ -21687,6 +21738,7 @@ var init_dataset = __esm({
|
|
|
21687
21738
|
runId: row.backtestRunId ?? null,
|
|
21688
21739
|
configId: row.configId?.trim() || "<missing-config-id>",
|
|
21689
21740
|
signalId,
|
|
21741
|
+
positionCycleId: typeof result.positionCycleId === "string" && result.positionCycleId.trim() ? result.positionCycleId.trim() : null,
|
|
21690
21742
|
setupIdentity,
|
|
21691
21743
|
setupIdentitySource: explicitIdentity ? "research.setupIdentity" : strategyIdentity ? "strategy-context" : "signal-time-fallback",
|
|
21692
21744
|
strategy: row.strategyName,
|
|
@@ -21710,6 +21762,69 @@ var init_dataset = __esm({
|
|
|
21710
21762
|
};
|
|
21711
21763
|
};
|
|
21712
21764
|
completedTradeIdentity = (trade) => [trade.runId ?? "", trade.configId, trade.signalId, trade.symbol].join("|");
|
|
21765
|
+
positionCycleIdentity = (trade) => trade.positionCycleId ? [
|
|
21766
|
+
trade.runId ?? "",
|
|
21767
|
+
trade.configId,
|
|
21768
|
+
trade.strategy,
|
|
21769
|
+
trade.symbol,
|
|
21770
|
+
trade.direction,
|
|
21771
|
+
trade.positionCycleId
|
|
21772
|
+
].join("|") : null;
|
|
21773
|
+
sumBy = (trades, select) => trades.reduce((sum3, trade) => sum3 + select(trade), 0);
|
|
21774
|
+
weightedPrice = (trades, select) => {
|
|
21775
|
+
const withPrice = trades.filter((trade) => select(trade) != null);
|
|
21776
|
+
const qty = sumBy(withPrice, (trade) => trade.qty);
|
|
21777
|
+
if (qty <= 0) return null;
|
|
21778
|
+
return sumBy(withPrice, (trade) => Number(select(trade)) * trade.qty) / qty;
|
|
21779
|
+
};
|
|
21780
|
+
aggregatePositionCycles = (trades) => {
|
|
21781
|
+
const standalone = [];
|
|
21782
|
+
const cycles = /* @__PURE__ */ new Map();
|
|
21783
|
+
for (const trade of trades) {
|
|
21784
|
+
const identity = positionCycleIdentity(trade);
|
|
21785
|
+
if (!identity) {
|
|
21786
|
+
standalone.push(trade);
|
|
21787
|
+
continue;
|
|
21788
|
+
}
|
|
21789
|
+
const cycle = cycles.get(identity) ?? [];
|
|
21790
|
+
cycle.push(trade);
|
|
21791
|
+
cycles.set(identity, cycle);
|
|
21792
|
+
}
|
|
21793
|
+
for (const [identity, cycle] of cycles) {
|
|
21794
|
+
const primaryRows = cycle.filter(
|
|
21795
|
+
(trade) => trade.signalId === trade.positionCycleId
|
|
21796
|
+
);
|
|
21797
|
+
if (primaryRows.length !== 1) {
|
|
21798
|
+
throw new Error(
|
|
21799
|
+
`Position cycle ${identity} must contain exactly one opening row; found ${primaryRows.length}`
|
|
21800
|
+
);
|
|
21801
|
+
}
|
|
21802
|
+
const primary = primaryRows[0];
|
|
21803
|
+
if (cycle.some(
|
|
21804
|
+
(trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason
|
|
21805
|
+
)) {
|
|
21806
|
+
throw new Error(
|
|
21807
|
+
`Position cycle ${identity} contains inconsistent final exits`
|
|
21808
|
+
);
|
|
21809
|
+
}
|
|
21810
|
+
if (cycle.length === 1) {
|
|
21811
|
+
standalone.push(primary);
|
|
21812
|
+
continue;
|
|
21813
|
+
}
|
|
21814
|
+
standalone.push({
|
|
21815
|
+
...primary,
|
|
21816
|
+
entryTimestamp: Math.min(...cycle.map((trade) => trade.entryTimestamp)),
|
|
21817
|
+
entryPrice: weightedPrice(cycle, (trade) => trade.entryPrice) ?? 0,
|
|
21818
|
+
exitPrice: weightedPrice(cycle, (trade) => trade.exitPrice),
|
|
21819
|
+
qty: sumBy(cycle, (trade) => trade.qty),
|
|
21820
|
+
netProfit: sumBy(cycle, (trade) => trade.netProfit),
|
|
21821
|
+
grossProfit: sumBy(cycle, (trade) => trade.grossProfit),
|
|
21822
|
+
totalFee: sumBy(cycle, (trade) => trade.totalFee),
|
|
21823
|
+
totalSlippageCost: sumBy(cycle, (trade) => trade.totalSlippageCost)
|
|
21824
|
+
});
|
|
21825
|
+
}
|
|
21826
|
+
return standalone;
|
|
21827
|
+
};
|
|
21713
21828
|
compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
21714
21829
|
compareTrades = (left, right) => left.exitTimestamp - right.exitTimestamp || compareText(left.symbol, right.symbol) || compareText(left.direction, right.direction) || compareText(left.signalId, right.signalId) || compareText(left.sourceFile, right.sourceFile) || left.sourceLine - right.sourceLine;
|
|
21715
21830
|
readCoreResearchVariant = async (variant) => {
|
|
@@ -21786,7 +21901,7 @@ var init_dataset = __esm({
|
|
|
21786
21901
|
variant,
|
|
21787
21902
|
files: sources,
|
|
21788
21903
|
duplicateRowsDropped,
|
|
21789
|
-
trades: trades.sort(compareTrades)
|
|
21904
|
+
trades: aggregatePositionCycles(trades).sort(compareTrades)
|
|
21790
21905
|
};
|
|
21791
21906
|
};
|
|
21792
21907
|
}
|
|
@@ -22293,13 +22408,17 @@ var init_orchestrator = __esm({
|
|
|
22293
22408
|
const loaded = await Promise.all(
|
|
22294
22409
|
analysisSpec.variants.map(readCoreResearchVariant)
|
|
22295
22410
|
);
|
|
22411
|
+
const periodDays = (spec.window.end - spec.window.start) / DAY_MS11;
|
|
22296
22412
|
const windowed = loaded.map((entry) => ({
|
|
22297
22413
|
...entry,
|
|
22414
|
+
reconciliationMetrics: summarizeCoreResearchCohorts(
|
|
22415
|
+
entry.trades,
|
|
22416
|
+
periodDays
|
|
22417
|
+
).ALL,
|
|
22298
22418
|
trades: entry.trades.filter(
|
|
22299
22419
|
(trade) => trade.exitTimestamp >= spec.window.start && trade.exitTimestamp < spec.window.end
|
|
22300
22420
|
)
|
|
22301
22421
|
}));
|
|
22302
|
-
const periodDays = (spec.window.end - spec.window.start) / DAY_MS11;
|
|
22303
22422
|
const analyses = await Promise.all(
|
|
22304
22423
|
windowed.map(async (entry) => ({
|
|
22305
22424
|
variant: entry.variant,
|
|
@@ -22357,12 +22476,9 @@ var init_orchestrator = __esm({
|
|
|
22357
22476
|
reconciliation: await reconcileCoreResearchVariant({
|
|
22358
22477
|
variant: entry.variant,
|
|
22359
22478
|
spec,
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
start: spec.window.start,
|
|
22364
|
-
end: spec.window.end
|
|
22365
|
-
}).cohorts.ALL
|
|
22479
|
+
// Redis result.stat includes the runner's terminal close at window.end.
|
|
22480
|
+
// Keep half-open research metrics, but reconcile the complete run export.
|
|
22481
|
+
exportMetrics: entry.reconciliationMetrics
|
|
22366
22482
|
}),
|
|
22367
22483
|
supplemental: {
|
|
22368
22484
|
coldStart: Object.fromEntries(
|
|
@@ -24654,7 +24770,7 @@ var init_runtimeEvidenceArtifacts = __esm({
|
|
|
24654
24770
|
});
|
|
24655
24771
|
|
|
24656
24772
|
// src/lib/runtimeLineage.ts
|
|
24657
|
-
var import_node_child_process6, import_node_crypto11, import_promises22, import_node_path16, import_types8, import_strategies11, RUNTIME_CONTEXT_ENV_KEYS, RUNTIME_CONTEXT_DATA_MODEL, normalizeForStableJson2,
|
|
24773
|
+
var import_node_child_process6, import_node_crypto11, import_promises22, import_node_path16, import_types8, import_strategies11, import_strategyReleaseEvidence2, RUNTIME_CONTEXT_ENV_KEYS, RUNTIME_CONTEXT_DATA_MODEL, normalizeForStableJson2, resolveRuntimeMaxLossValue, fingerprintRuntimeValue, gitLineageCache, gateFingerprintCache, binanceBreadthFingerprintCache, snapshotFingerprintCache, resolveGitLineage, readOptionalFile, resolveBinanceBreadthFingerprint, resolveSnapshotFingerprint, resolveRuntimeGateFingerprint, buildRuntimeLineage, runtimeLineageKey, runtimeLineagesMatch;
|
|
24658
24774
|
var init_runtimeLineage = __esm({
|
|
24659
24775
|
"src/lib/runtimeLineage.ts"() {
|
|
24660
24776
|
"use strict";
|
|
@@ -24664,6 +24780,7 @@ var init_runtimeLineage = __esm({
|
|
|
24664
24780
|
import_node_path16 = __toESM(require("path"));
|
|
24665
24781
|
import_types8 = require("@tradejs/types");
|
|
24666
24782
|
import_strategies11 = require("@tradejs/node/strategies");
|
|
24783
|
+
import_strategyReleaseEvidence2 = require("@tradejs/infra/strategyReleaseEvidence");
|
|
24667
24784
|
init_strategyGateFingerprint();
|
|
24668
24785
|
RUNTIME_CONTEXT_ENV_KEYS = [
|
|
24669
24786
|
"AI_MODE",
|
|
@@ -24714,17 +24831,6 @@ var init_runtimeLineage = __esm({
|
|
|
24714
24831
|
}
|
|
24715
24832
|
return value;
|
|
24716
24833
|
};
|
|
24717
|
-
normalizeRuntimeLogicConfig = (value) => {
|
|
24718
|
-
if (Array.isArray(value)) {
|
|
24719
|
-
return value.map(normalizeRuntimeLogicConfig);
|
|
24720
|
-
}
|
|
24721
|
-
if (value && typeof value === "object") {
|
|
24722
|
-
return Object.fromEntries(
|
|
24723
|
-
Object.entries(value).filter(([key]) => key !== "MAX_LOSS_VALUE").sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeRuntimeLogicConfig(entry)])
|
|
24724
|
-
);
|
|
24725
|
-
}
|
|
24726
|
-
return value;
|
|
24727
|
-
};
|
|
24728
24834
|
resolveRuntimeMaxLossValue = (config2) => {
|
|
24729
24835
|
if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) {
|
|
24730
24836
|
return null;
|
|
@@ -24888,9 +24994,7 @@ var init_runtimeLineage = __esm({
|
|
|
24888
24994
|
})).gateFingerprint,
|
|
24889
24995
|
// Position risk is tracked separately so changing trade size does not
|
|
24890
24996
|
// masquerade as a change to the core + gate decision logic.
|
|
24891
|
-
configFingerprint:
|
|
24892
|
-
normalizeRuntimeLogicConfig(config2)
|
|
24893
|
-
),
|
|
24997
|
+
configFingerprint: (0, import_strategyReleaseEvidence2.strategyLogicConfigFingerprint)(config2),
|
|
24894
24998
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
24895
24999
|
maxLossValue: resolveRuntimeMaxLossValue(config2)
|
|
24896
25000
|
};
|
|
@@ -25880,7 +25984,7 @@ function assert2(condition, message) {
|
|
|
25880
25984
|
async function collectReleaseEvidenceReferences(references) {
|
|
25881
25985
|
return Promise.all(
|
|
25882
25986
|
references.map(async (reference) => {
|
|
25883
|
-
const actualSha256 = await (0,
|
|
25987
|
+
const actualSha256 = await (0, import_strategyReleaseEvidence3.strategyEvidenceFileSha256)(reference.path);
|
|
25884
25988
|
assert2(
|
|
25885
25989
|
actualSha256 === reference.sha256,
|
|
25886
25990
|
`evidence ${reference.artifactId} checksum mismatch`
|
|
@@ -25960,9 +26064,10 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
25960
26064
|
if (reference.kind === "ai_gate") {
|
|
25961
26065
|
const outcome = nested(artifact.outcome);
|
|
25962
26066
|
const approvedRisk = nested(outcome?.approvedRisk);
|
|
25963
|
-
const
|
|
25964
|
-
|
|
25965
|
-
|
|
26067
|
+
const run = nested(artifact.run);
|
|
26068
|
+
const research = nested(artifact.research);
|
|
26069
|
+
const researchLineage = nested(research?.lineage);
|
|
26070
|
+
const terminalWindows = array(research?.terminalWindows).map(nested).filter((entry) => entry != null);
|
|
25966
26071
|
const hasLongShortDirections = (value) => {
|
|
25967
26072
|
const directions = new Map(
|
|
25968
26073
|
array(value).map(nested).filter(
|
|
@@ -25976,6 +26081,22 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
25976
26081
|
const directionalEvidenceComplete = hasLongShortDirections(artifact.byDirection) && terminalWindows.every(
|
|
25977
26082
|
(window) => hasLongShortDirections(window.byDirection)
|
|
25978
26083
|
);
|
|
26084
|
+
const directionPolicy = finiteDirectionPolicy(researchLineage?.directionPolicy) ?? "both";
|
|
26085
|
+
const runDirectionPolicy = finiteDirectionPolicy(run?.directionPolicy) ?? "both";
|
|
26086
|
+
const suppressedDirection = directionPolicy === "long_only" ? "SHORT" : directionPolicy === "short_only" ? "LONG" : null;
|
|
26087
|
+
const approvedForDirection = (value, direction) => {
|
|
26088
|
+
const entry = array(value).map(nested).find((candidate) => candidate?.direction === direction);
|
|
26089
|
+
return Number(nested(entry?.summary)?.approved ?? Number.NaN);
|
|
26090
|
+
};
|
|
26091
|
+
const directionPolicyValid = runDirectionPolicy === directionPolicy && (suppressedDirection == null || approvedForDirection(
|
|
26092
|
+
artifact.byDirection,
|
|
26093
|
+
suppressedDirection
|
|
26094
|
+
) === 0 && terminalWindows.every(
|
|
26095
|
+
(window) => approvedForDirection(
|
|
26096
|
+
window.byDirection,
|
|
26097
|
+
suppressedDirection
|
|
26098
|
+
) === 0
|
|
26099
|
+
));
|
|
25979
26100
|
const terminalsPass = terminalWindows.length > 0 && terminalWindows.every((window) => {
|
|
25980
26101
|
const terminalOutcome = nested(window.outcome);
|
|
25981
26102
|
return window.complete === true && Number(terminalOutcome?.approved ?? 0) > 0 && Number(
|
|
@@ -25983,7 +26104,7 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
25983
26104
|
) > 0;
|
|
25984
26105
|
});
|
|
25985
26106
|
return {
|
|
25986
|
-
aiGateAddsValue: Number(outcome?.expectancyDelta ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.totalProfit ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.profitFactor ?? Number.NEGATIVE_INFINITY) > 1 && directionalEvidenceComplete && terminalsPass
|
|
26107
|
+
aiGateAddsValue: Number(outcome?.expectancyDelta ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.totalProfit ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.profitFactor ?? Number.NEGATIVE_INFINITY) > 1 && directionalEvidenceComplete && directionPolicyValid && terminalsPass
|
|
25987
26108
|
};
|
|
25988
26109
|
}
|
|
25989
26110
|
if (reference.kind === "runtime_parity") {
|
|
@@ -26018,7 +26139,7 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
26018
26139
|
})
|
|
26019
26140
|
);
|
|
26020
26141
|
}
|
|
26021
|
-
var import_node_crypto12, import_promises25, import_node_path20, import_types9,
|
|
26142
|
+
var import_node_crypto12, import_promises25, import_node_path20, import_types9, import_strategyReleaseEvidence3, SHA256_RE2, LINEAGE_FINGERPRINT_RE, DAY_MS12, REQUIRED_LONG_WINDOWS, REQUIRED_STABLE_TERMINAL_WINDOWS, MIN_RECENT_DIRECTION_REPAIR_TRADES, safeSegment3, compactTimestamp2, verifyFullPeriodChartArtifact, hasExactRuntimeTarget, deriveStrategyReleaseResearchDecision, canonicalStrategyReleaseJson, strategyReleaseSha256, assertFingerprint, assertLineageFingerprint, releaseReasons, deriveReleaseGates, verifyEvidenceComposition, createStrategyReleaseManifest, createStrategyReleaseEnvelope, verifyStrategyReleaseEnvelope, verifyStrategyEvidenceMarkerEnvelope, releaseMarkers, writeJsonAtomic2, publishStrategyRelease, buildStrategyLiveDiagnosis, buildStrategyLiveDiagnosisFromScorecard, realizedDrawdown, lowerBoundExitTimestamp, percentile2, buildStrategyMonitoringProfile, publishStrategyLiveDiagnosis, planStrategyEvidenceRetention, finiteString3, finiteNumber6, finiteDirectionPolicy, uniqueStrings, incompleteEvidenceLineage, extractEvidenceLineage, verifyCompletedCoreResearchBundle;
|
|
26022
26143
|
var init_strategyRelease = __esm({
|
|
26023
26144
|
"src/lib/strategyRelease.ts"() {
|
|
26024
26145
|
"use strict";
|
|
@@ -26026,21 +26147,21 @@ var init_strategyRelease = __esm({
|
|
|
26026
26147
|
import_promises25 = __toESM(require("fs/promises"));
|
|
26027
26148
|
import_node_path20 = __toESM(require("path"));
|
|
26028
26149
|
import_types9 = require("@tradejs/types");
|
|
26029
|
-
|
|
26150
|
+
import_strategyReleaseEvidence3 = require("@tradejs/infra/strategyReleaseEvidence");
|
|
26030
26151
|
SHA256_RE2 = /^[a-f0-9]{64}$/;
|
|
26031
26152
|
LINEAGE_FINGERPRINT_RE = /^[a-f0-9]{16}$/;
|
|
26032
26153
|
DAY_MS12 = 864e5;
|
|
26033
26154
|
REQUIRED_LONG_WINDOWS = [1095, 1460, 1825];
|
|
26034
26155
|
REQUIRED_STABLE_TERMINAL_WINDOWS = [365, 180, 90];
|
|
26035
26156
|
MIN_RECENT_DIRECTION_REPAIR_TRADES = 20;
|
|
26036
|
-
safeSegment3 =
|
|
26037
|
-
compactTimestamp2 =
|
|
26157
|
+
safeSegment3 = import_strategyReleaseEvidence3.safeStrategyEvidenceSegment;
|
|
26158
|
+
compactTimestamp2 = import_strategyReleaseEvidence3.compactStrategyEvidenceTimestamp;
|
|
26038
26159
|
verifyFullPeriodChartArtifact = async (input) => {
|
|
26039
26160
|
const artifact = input.chartArtifact;
|
|
26040
26161
|
if (!artifact?.path || !SHA256_RE2.test(artifact.sha256)) return false;
|
|
26041
26162
|
try {
|
|
26042
26163
|
const artifactPath = import_node_path20.default.resolve(artifact.path);
|
|
26043
|
-
if (await (0,
|
|
26164
|
+
if (await (0, import_strategyReleaseEvidence3.strategyEvidenceFileSha256)(artifactPath) !== artifact.sha256) {
|
|
26044
26165
|
return false;
|
|
26045
26166
|
}
|
|
26046
26167
|
const report = JSON.parse(await import_promises25.default.readFile(artifactPath, "utf8"));
|
|
@@ -26056,6 +26177,12 @@ var init_strategyRelease = __esm({
|
|
|
26056
26177
|
);
|
|
26057
26178
|
deriveStrategyReleaseResearchDecision = async (input) => {
|
|
26058
26179
|
const blockers = [];
|
|
26180
|
+
const directionPolicy = input.directionPolicy ?? "both";
|
|
26181
|
+
const activeDirections = directionPolicy === "long_only" ? ["long"] : directionPolicy === "short_only" ? ["short"] : ["long", "short"];
|
|
26182
|
+
const suppressedDirection = directionPolicy === "long_only" ? "short" : directionPolicy === "short_only" ? "long" : null;
|
|
26183
|
+
const hasHistoricalEdge = (entry) => entry.pnl > 0 && entry.profitFactor >= 1 && activeDirections.every(
|
|
26184
|
+
(direction) => entry[direction].pnl > 0 && entry[direction].profitFactor >= 1
|
|
26185
|
+
) && (suppressedDirection == null || entry[suppressedDirection].trades === 0 && entry[suppressedDirection].pnl === 0);
|
|
26059
26186
|
const byDays = new Map(
|
|
26060
26187
|
input.historicalWindows.map((entry) => [entry.days, entry])
|
|
26061
26188
|
);
|
|
@@ -26068,17 +26195,13 @@ var init_strategyRelease = __esm({
|
|
|
26068
26195
|
const longWindows = REQUIRED_LONG_WINDOWS.map(
|
|
26069
26196
|
(days) => byDays.get(days)
|
|
26070
26197
|
).filter((entry) => Boolean(entry));
|
|
26071
|
-
if (longWindows.some(
|
|
26072
|
-
(entry) => entry.pnl <= 0 || entry.profitFactor < 1 || entry.long.pnl <= 0 || entry.long.profitFactor < 1 || entry.short.pnl <= 0 || entry.short.profitFactor < 1
|
|
26073
|
-
)) {
|
|
26198
|
+
if (longWindows.some((entry) => !hasHistoricalEdge(entry))) {
|
|
26074
26199
|
blockers.push("HISTORICAL_EDGE_FAILED");
|
|
26075
26200
|
}
|
|
26076
26201
|
const stableTerminalWindows = REQUIRED_STABLE_TERMINAL_WINDOWS.map(
|
|
26077
26202
|
(days) => byDays.get(days)
|
|
26078
26203
|
).filter((entry) => Boolean(entry));
|
|
26079
|
-
if (stableTerminalWindows.some(
|
|
26080
|
-
(entry) => entry.pnl <= 0 || entry.profitFactor < 1 || entry.long.pnl <= 0 || entry.long.profitFactor < 1 || entry.short.pnl <= 0 || entry.short.profitFactor < 1
|
|
26081
|
-
)) {
|
|
26204
|
+
if (stableTerminalWindows.some((entry) => !hasHistoricalEdge(entry))) {
|
|
26082
26205
|
blockers.push("HISTORICAL_EDGE_FAILED");
|
|
26083
26206
|
}
|
|
26084
26207
|
if (blockers.length) {
|
|
@@ -26088,8 +26211,9 @@ var init_strategyRelease = __esm({
|
|
|
26088
26211
|
repairAllowed: false,
|
|
26089
26212
|
targetDirection: input.recentFailure?.direction ?? null,
|
|
26090
26213
|
maxLossValue: null,
|
|
26214
|
+
requiresRuntimeBinding: false,
|
|
26091
26215
|
blockers,
|
|
26092
|
-
summary:
|
|
26216
|
+
summary: `The final ${directionPolicy} composition does not have complete positive evidence for ALL and every active direction on the frozen 3y/4y/max-window matrix.`
|
|
26093
26217
|
};
|
|
26094
26218
|
}
|
|
26095
26219
|
const repairAllowed = Boolean(
|
|
@@ -26102,6 +26226,7 @@ var init_strategyRelease = __esm({
|
|
|
26102
26226
|
repairAllowed: true,
|
|
26103
26227
|
targetDirection: input.recentFailure.direction,
|
|
26104
26228
|
maxLossValue: null,
|
|
26229
|
+
requiresRuntimeBinding: false,
|
|
26105
26230
|
blockers: [],
|
|
26106
26231
|
summary: "Spend the single terminal-direction repair round on the preregistered causal mechanism, then rebuild the full evidence matrix."
|
|
26107
26232
|
};
|
|
@@ -26120,6 +26245,7 @@ var init_strategyRelease = __esm({
|
|
|
26120
26245
|
repairAllowed: false,
|
|
26121
26246
|
targetDirection: input.recentFailure?.direction ?? null,
|
|
26122
26247
|
maxLossValue: null,
|
|
26248
|
+
requiresRuntimeBinding: false,
|
|
26123
26249
|
blockers,
|
|
26124
26250
|
summary: "Complete the frozen candidate implementation and full-period chart before any micro-forward execution."
|
|
26125
26251
|
};
|
|
@@ -26131,6 +26257,9 @@ var init_strategyRelease = __esm({
|
|
|
26131
26257
|
repairAllowed: false,
|
|
26132
26258
|
targetDirection: input.recentFailure?.direction ?? null,
|
|
26133
26259
|
maxLossValue: 1,
|
|
26260
|
+
requiresRuntimeBinding: !hasExactRuntimeTarget(
|
|
26261
|
+
input.forwardTest.runtimeTarget
|
|
26262
|
+
),
|
|
26134
26263
|
blockers: ["FORWARD_NOT_AUTHORIZED"],
|
|
26135
26264
|
summary: "The candidate is ready for a separately authorized micro-forward test at MAX_LOSS_VALUE=1."
|
|
26136
26265
|
};
|
|
@@ -26138,12 +26267,13 @@ var init_strategyRelease = __esm({
|
|
|
26138
26267
|
if (!hasExactRuntimeTarget(input.forwardTest.runtimeTarget)) {
|
|
26139
26268
|
return {
|
|
26140
26269
|
strategy: input.strategy,
|
|
26141
|
-
action: "
|
|
26270
|
+
action: "MICRO_FORWARD_READY",
|
|
26142
26271
|
repairAllowed: false,
|
|
26143
26272
|
targetDirection: input.recentFailure?.direction ?? null,
|
|
26144
26273
|
maxLossValue: 1,
|
|
26145
|
-
|
|
26146
|
-
|
|
26274
|
+
requiresRuntimeBinding: true,
|
|
26275
|
+
blockers: [],
|
|
26276
|
+
summary: "The portable micro-forward handoff is ready. Bind it on the runtime server to that server's deployment/account ids and verify canonical composition fingerprints there."
|
|
26147
26277
|
};
|
|
26148
26278
|
}
|
|
26149
26279
|
return {
|
|
@@ -26152,12 +26282,13 @@ var init_strategyRelease = __esm({
|
|
|
26152
26282
|
repairAllowed: false,
|
|
26153
26283
|
targetDirection: input.recentFailure?.direction ?? null,
|
|
26154
26284
|
maxLossValue: 1,
|
|
26285
|
+
requiresRuntimeBinding: false,
|
|
26155
26286
|
blockers: [],
|
|
26156
26287
|
summary: "Start the exact frozen composition as a micro-forward test at MAX_LOSS_VALUE=1; do not retune an underpowered or exposed recent tail."
|
|
26157
26288
|
};
|
|
26158
26289
|
};
|
|
26159
|
-
canonicalStrategyReleaseJson = (value) => (0,
|
|
26160
|
-
strategyReleaseSha256 = (value) => (0,
|
|
26290
|
+
canonicalStrategyReleaseJson = (value) => (0, import_strategyReleaseEvidence3.canonicalStrategyEvidenceJson)(value);
|
|
26291
|
+
strategyReleaseSha256 = (value) => (0, import_strategyReleaseEvidence3.strategyEvidenceSha256)(value);
|
|
26161
26292
|
assertFingerprint = (value, label) => assert2(SHA256_RE2.test(value), `${label} must be a lowercase SHA-256`);
|
|
26162
26293
|
assertLineageFingerprint = (value, label) => assert2(
|
|
26163
26294
|
LINEAGE_FINGERPRINT_RE.test(value),
|
|
@@ -26206,6 +26337,7 @@ var init_strategyRelease = __esm({
|
|
|
26206
26337
|
};
|
|
26207
26338
|
};
|
|
26208
26339
|
verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
26340
|
+
const directionPolicy = composition.directionPolicy ?? "both";
|
|
26209
26341
|
const requireEqual = (actual, expected, label, artifactId) => assert2(
|
|
26210
26342
|
actual === expected,
|
|
26211
26343
|
`evidence ${artifactId} ${label} does not match the frozen composition`
|
|
@@ -26276,6 +26408,14 @@ var init_strategyRelease = __esm({
|
|
|
26276
26408
|
"gateContextFingerprint",
|
|
26277
26409
|
reference.artifactId
|
|
26278
26410
|
);
|
|
26411
|
+
if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
|
|
26412
|
+
requireEqual(
|
|
26413
|
+
lineage?.directionPolicy,
|
|
26414
|
+
directionPolicy,
|
|
26415
|
+
"directionPolicy",
|
|
26416
|
+
reference.artifactId
|
|
26417
|
+
);
|
|
26418
|
+
}
|
|
26279
26419
|
assert2(
|
|
26280
26420
|
lineage?.sourceSha256s.includes(composition.coreExportSha256),
|
|
26281
26421
|
`evidence ${reference.artifactId} does not use the frozen core export`
|
|
@@ -26320,6 +26460,14 @@ var init_strategyRelease = __esm({
|
|
|
26320
26460
|
"MAX_LOSS_VALUE",
|
|
26321
26461
|
reference.artifactId
|
|
26322
26462
|
);
|
|
26463
|
+
if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
|
|
26464
|
+
requireEqual(
|
|
26465
|
+
lineage?.directionPolicy,
|
|
26466
|
+
directionPolicy,
|
|
26467
|
+
"directionPolicy",
|
|
26468
|
+
reference.artifactId
|
|
26469
|
+
);
|
|
26470
|
+
}
|
|
26323
26471
|
continue;
|
|
26324
26472
|
}
|
|
26325
26473
|
if (reference.kind === "execution_calibration") {
|
|
@@ -26360,6 +26508,14 @@ var init_strategyRelease = __esm({
|
|
|
26360
26508
|
"MAX_LOSS_VALUE",
|
|
26361
26509
|
reference.artifactId
|
|
26362
26510
|
);
|
|
26511
|
+
if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
|
|
26512
|
+
requireEqual(
|
|
26513
|
+
lineage?.directionPolicy,
|
|
26514
|
+
directionPolicy,
|
|
26515
|
+
"directionPolicy",
|
|
26516
|
+
reference.artifactId
|
|
26517
|
+
);
|
|
26518
|
+
}
|
|
26363
26519
|
}
|
|
26364
26520
|
}
|
|
26365
26521
|
};
|
|
@@ -26395,6 +26551,12 @@ var init_strategyRelease = __esm({
|
|
|
26395
26551
|
input.composition.longEnabled && input.composition.shortEnabled,
|
|
26396
26552
|
"LONG and SHORT must both remain enabled"
|
|
26397
26553
|
);
|
|
26554
|
+
assert2(
|
|
26555
|
+
input.composition.directionPolicy == null || ["both", "long_only", "short_only", "direction_aware"].includes(
|
|
26556
|
+
input.composition.directionPolicy
|
|
26557
|
+
),
|
|
26558
|
+
"composition.directionPolicy is invalid"
|
|
26559
|
+
);
|
|
26398
26560
|
assert2(
|
|
26399
26561
|
Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
|
|
26400
26562
|
"monitoring.minimumProspectiveClosedTrades must be positive"
|
|
@@ -26459,7 +26621,8 @@ var init_strategyRelease = __esm({
|
|
|
26459
26621
|
gateFingerprint: input.composition.gateFingerprint,
|
|
26460
26622
|
gateContextFingerprint: input.composition.gateContextFingerprint,
|
|
26461
26623
|
runtimeContextFingerprint: input.composition.runtimeContextFingerprint,
|
|
26462
|
-
maxLossValue: input.composition.maxLossValue
|
|
26624
|
+
maxLossValue: input.composition.maxLossValue,
|
|
26625
|
+
...input.composition.directionPolicy == null ? {} : { directionPolicy: input.composition.directionPolicy }
|
|
26463
26626
|
};
|
|
26464
26627
|
const compositionId = `${safeSegment3(input.strategy, "strategy")}_${strategyReleaseSha256(compositionIdentity).slice(0, 16)}`;
|
|
26465
26628
|
const reasons = releaseReasons({ ...input, gates: evidenceGates });
|
|
@@ -26549,7 +26712,8 @@ var init_strategyRelease = __esm({
|
|
|
26549
26712
|
runtimeContextFingerprint: manifest.composition.runtimeContextFingerprint,
|
|
26550
26713
|
maxLossValue: manifest.composition.maxLossValue,
|
|
26551
26714
|
longEnabled: manifest.composition.longEnabled,
|
|
26552
|
-
shortEnabled: manifest.composition.shortEnabled
|
|
26715
|
+
shortEnabled: manifest.composition.shortEnabled,
|
|
26716
|
+
directionPolicy: manifest.composition.directionPolicy
|
|
26553
26717
|
},
|
|
26554
26718
|
marketWindow: manifest.marketWindow,
|
|
26555
26719
|
researchBudget: manifest.researchBudget,
|
|
@@ -26567,7 +26731,7 @@ var init_strategyRelease = __esm({
|
|
|
26567
26731
|
};
|
|
26568
26732
|
verifyStrategyEvidenceMarkerEnvelope = async (valueOrPath) => {
|
|
26569
26733
|
const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises25.default.readFile(valueOrPath, "utf8")) : valueOrPath;
|
|
26570
|
-
return (0,
|
|
26734
|
+
return (0, import_strategyReleaseEvidence3.verifyStrategyEvidenceMarkerEnvelope)(value);
|
|
26571
26735
|
};
|
|
26572
26736
|
releaseMarkers = (manifest) => {
|
|
26573
26737
|
const common = {
|
|
@@ -26642,7 +26806,7 @@ var init_strategyRelease = __esm({
|
|
|
26642
26806
|
manifest
|
|
26643
26807
|
}) => {
|
|
26644
26808
|
const envelope = createStrategyReleaseEnvelope(manifest);
|
|
26645
|
-
const markerEnvelope = (0,
|
|
26809
|
+
const markerEnvelope = (0, import_strategyReleaseEvidence3.createStrategyEvidenceMarkerEnvelope)({
|
|
26646
26810
|
strategy: manifest.strategy,
|
|
26647
26811
|
createdAt: manifest.createdAt,
|
|
26648
26812
|
markers: releaseMarkers(manifest),
|
|
@@ -26917,7 +27081,7 @@ var init_strategyRelease = __esm({
|
|
|
26917
27081
|
severity: diagnosis.verdict === "EXPECTED_DRAWDOWN" ? "info" : "warning"
|
|
26918
27082
|
}
|
|
26919
27083
|
];
|
|
26920
|
-
const markerEnvelope = (0,
|
|
27084
|
+
const markerEnvelope = (0, import_strategyReleaseEvidence3.createStrategyEvidenceMarkerEnvelope)({
|
|
26921
27085
|
strategy: diagnosis.strategy,
|
|
26922
27086
|
createdAt: diagnosis.createdAt,
|
|
26923
27087
|
markers,
|
|
@@ -26973,6 +27137,7 @@ var init_strategyRelease = __esm({
|
|
|
26973
27137
|
const number = Number(value);
|
|
26974
27138
|
return Number.isFinite(number) ? number : null;
|
|
26975
27139
|
};
|
|
27140
|
+
finiteDirectionPolicy = (value) => ["both", "long_only", "short_only", "direction_aware"].includes(String(value)) ? value : null;
|
|
26976
27141
|
uniqueStrings = (values) => [
|
|
26977
27142
|
...new Set(
|
|
26978
27143
|
values.map(finiteString3).filter((value) => value != null)
|
|
@@ -26989,6 +27154,7 @@ var init_strategyRelease = __esm({
|
|
|
26989
27154
|
gateContextFingerprint: null,
|
|
26990
27155
|
runtimeContextFingerprint: null,
|
|
26991
27156
|
maxLossValue: null,
|
|
27157
|
+
directionPolicy: null,
|
|
26992
27158
|
sourceSha256s: []
|
|
26993
27159
|
});
|
|
26994
27160
|
extractEvidenceLineage = ({
|
|
@@ -27051,6 +27217,7 @@ var init_strategyRelease = __esm({
|
|
|
27051
27217
|
gateConfigIdsFingerprint: finiteString3(lineage?.configIdsFingerprint),
|
|
27052
27218
|
gateFingerprint: finiteString3(lineage?.gateFingerprint),
|
|
27053
27219
|
gateContextFingerprint: finiteString3(lineage?.contextFingerprint),
|
|
27220
|
+
directionPolicy: finiteDirectionPolicy(lineage?.directionPolicy),
|
|
27054
27221
|
sourceSha256s: uniqueStrings(array(lineage?.sourceSha256s))
|
|
27055
27222
|
};
|
|
27056
27223
|
}
|
|
@@ -27086,7 +27253,8 @@ var init_strategyRelease = __esm({
|
|
|
27086
27253
|
gateFingerprint: one("gateFingerprint"),
|
|
27087
27254
|
runtimeConfigFingerprint: one("configFingerprint"),
|
|
27088
27255
|
runtimeContextFingerprint: one("contextFingerprint"),
|
|
27089
|
-
maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null
|
|
27256
|
+
maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
|
|
27257
|
+
directionPolicy: finiteDirectionPolicy(one("directionPolicy"))
|
|
27090
27258
|
};
|
|
27091
27259
|
}
|
|
27092
27260
|
if (reference.kind === "execution_calibration") {
|
|
@@ -27123,6 +27291,7 @@ var init_strategyRelease = __esm({
|
|
|
27123
27291
|
runtimeConfigFingerprint: one("configFingerprint"),
|
|
27124
27292
|
runtimeContextFingerprint: one("contextFingerprint"),
|
|
27125
27293
|
maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
|
|
27294
|
+
directionPolicy: finiteDirectionPolicy(one("directionPolicy")),
|
|
27126
27295
|
sourceSha256s: uniqueStrings([
|
|
27127
27296
|
...array(nested(artifact.sources)?.sha256s),
|
|
27128
27297
|
...array(artifact.sourceSha256s)
|
|
@@ -27167,7 +27336,7 @@ var init_strategyRelease = __esm({
|
|
|
27167
27336
|
typeof expectedSha256 === "string" && SHA256_RE2.test(expectedSha256),
|
|
27168
27337
|
`core bundle artifact ${relativePath} has an invalid checksum`
|
|
27169
27338
|
);
|
|
27170
|
-
const actualSha256 = await (0,
|
|
27339
|
+
const actualSha256 = await (0, import_strategyReleaseEvidence3.strategyEvidenceFileSha256)(
|
|
27171
27340
|
import_node_path20.default.join(researchDir, relativePath)
|
|
27172
27341
|
);
|
|
27173
27342
|
assert2(
|