@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/scripts/aiTrain.js
CHANGED
|
@@ -43,6 +43,7 @@ var import_ai = require("@tradejs/infra/ai");
|
|
|
43
43
|
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
44
44
|
var import_redis = require("@tradejs/infra/redis");
|
|
45
45
|
var import_ai2 = require("@tradejs/node/ai");
|
|
46
|
+
var import_registry = require("@tradejs/node/registry");
|
|
46
47
|
var import_constants2 = require("@tradejs/node/constants");
|
|
47
48
|
|
|
48
49
|
// src/lib/aiTrainMetrics.ts
|
|
@@ -920,6 +921,38 @@ var parseTerminalWindowDays = (value) => {
|
|
|
920
921
|
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);
|
|
921
922
|
return [...new Set(raw)].sort((left, right) => right - left);
|
|
922
923
|
};
|
|
924
|
+
var parseAiTrainDirectionPolicy = (value) => {
|
|
925
|
+
const normalized = String(value ?? "both").trim().toLowerCase().replaceAll("-", "_");
|
|
926
|
+
if (normalized === "both" || normalized === "long_only" || normalized === "short_only" || normalized === "direction_aware") {
|
|
927
|
+
return normalized;
|
|
928
|
+
}
|
|
929
|
+
throw new Error(
|
|
930
|
+
`Invalid --directionPolicy value "${String(value)}". Use both, long_only, short_only, or direction_aware.`
|
|
931
|
+
);
|
|
932
|
+
};
|
|
933
|
+
var assertAiTrainDirectionPolicy = ({
|
|
934
|
+
directionPolicy,
|
|
935
|
+
byDirection,
|
|
936
|
+
terminalWindows
|
|
937
|
+
}) => {
|
|
938
|
+
const suppressedDirection = directionPolicy === "long_only" ? "SHORT" : directionPolicy === "short_only" ? "LONG" : null;
|
|
939
|
+
if (!suppressedDirection) return;
|
|
940
|
+
for (const scope of [{ label: "full", byDirection }, ...terminalWindows]) {
|
|
941
|
+
const summary = scope.byDirection.find(
|
|
942
|
+
(entry) => entry.direction === suppressedDirection
|
|
943
|
+
);
|
|
944
|
+
if (!summary) {
|
|
945
|
+
throw new Error(
|
|
946
|
+
`${scope.label} ${suppressedDirection} direction summary is required for ${directionPolicy}`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
if (summary.summary.approved !== 0) {
|
|
950
|
+
throw new Error(
|
|
951
|
+
`${scope.label} has ${summary.summary.approved} ${suppressedDirection} approved rows under ${directionPolicy}`
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
};
|
|
923
956
|
var parseDumpFeatureMode = (value) => {
|
|
924
957
|
const text = String(value ?? "none").trim().toLowerCase();
|
|
925
958
|
if (!text || text === "none" || text === "false" || text === "0") {
|
|
@@ -1416,6 +1449,11 @@ import_args.default.option(
|
|
|
1416
1449
|
"Minimum AI quality required to approve entry",
|
|
1417
1450
|
4
|
|
1418
1451
|
);
|
|
1452
|
+
import_args.default.option(
|
|
1453
|
+
["Y", "directionPolicy"],
|
|
1454
|
+
"Evidence label for the enforced gate policy: both, long_only, short_only, or direction_aware",
|
|
1455
|
+
"both"
|
|
1456
|
+
);
|
|
1419
1457
|
import_args.default.option(
|
|
1420
1458
|
["l", "localOnly"],
|
|
1421
1459
|
"Replay deterministic adapter gate without AI provider calls",
|
|
@@ -1806,6 +1844,7 @@ var main = async () => {
|
|
|
1806
1844
|
});
|
|
1807
1845
|
const qualityThresholds = parseQualityThresholds(flags.qualityThresholds);
|
|
1808
1846
|
const terminalWindowDays = parseTerminalWindowDays(flags.terminalWindows);
|
|
1847
|
+
const directionPolicy = parseAiTrainDirectionPolicy(flags.directionPolicy);
|
|
1809
1848
|
const dumpEvaluationsPath = String(flags.dumpEvaluations || "").trim();
|
|
1810
1849
|
const dumpFeatureMode = parseDumpFeatureMode(flags.dumpFeatures);
|
|
1811
1850
|
if (dumpFeatureMode !== "none" && !dumpEvaluationsPath) {
|
|
@@ -1827,7 +1866,7 @@ var main = async () => {
|
|
|
1827
1866
|
const userName = String(flags.user || "root").trim() || "root";
|
|
1828
1867
|
const model = String(flags.model || import_ai2.DEFAULT_AI_MODEL).trim() || import_ai2.DEFAULT_AI_MODEL;
|
|
1829
1868
|
const parallel = normalizePositiveInt(flags.parallel, import_constants2.AI_CONCURRENCY_LIMIT);
|
|
1830
|
-
await (0,
|
|
1869
|
+
await (0, import_registry.ensureStrategyPluginsLoaded)();
|
|
1831
1870
|
const filePaths = await resolveDatasetFiles();
|
|
1832
1871
|
const datasetId = getMergedGroupId(filePaths[0] || "")?.mergeId;
|
|
1833
1872
|
const { totalRows, selectedRows } = await (0, import_ai.countAiDatasetRows)({
|
|
@@ -2020,18 +2059,29 @@ var main = async () => {
|
|
|
2020
2059
|
finalEvaluations,
|
|
2021
2060
|
terminalWindowDays
|
|
2022
2061
|
);
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
configIds: finalEvaluations.map((evaluation) => evaluation.configId),
|
|
2028
|
-
sourceSha256s: await Promise.all(filePaths.map(import_strategyReleaseEvidence.strategyEvidenceFileSha256)),
|
|
2029
|
-
runContext: {
|
|
2030
|
-
mode: localOnly ? "local-deterministic" : "llm",
|
|
2031
|
-
model: localOnly ? "local-deterministic" : model,
|
|
2032
|
-
minQuality
|
|
2033
|
-
}
|
|
2062
|
+
assertAiTrainDirectionPolicy({
|
|
2063
|
+
directionPolicy,
|
|
2064
|
+
byDirection: directionSummaries,
|
|
2065
|
+
terminalWindows
|
|
2034
2066
|
});
|
|
2067
|
+
const topRejectReasons = summarizeAiTrainRejectReasons(finalEvaluations);
|
|
2068
|
+
const lineage = {
|
|
2069
|
+
...await buildAiTrainLineage({
|
|
2070
|
+
projectRoot: String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd(),
|
|
2071
|
+
strategyName,
|
|
2072
|
+
configIds: finalEvaluations.map((evaluation) => evaluation.configId),
|
|
2073
|
+
sourceSha256s: await Promise.all(
|
|
2074
|
+
filePaths.map(import_strategyReleaseEvidence.strategyEvidenceFileSha256)
|
|
2075
|
+
),
|
|
2076
|
+
runContext: {
|
|
2077
|
+
mode: localOnly ? "local-deterministic" : "llm",
|
|
2078
|
+
model: localOnly ? "local-deterministic" : model,
|
|
2079
|
+
minQuality,
|
|
2080
|
+
directionPolicy
|
|
2081
|
+
}
|
|
2082
|
+
}),
|
|
2083
|
+
directionPolicy
|
|
2084
|
+
};
|
|
2035
2085
|
const evaluated = summary.correct + summary.incorrect;
|
|
2036
2086
|
const result = {
|
|
2037
2087
|
run: {
|
|
@@ -2053,7 +2103,8 @@ var main = async () => {
|
|
|
2053
2103
|
model: localOnly ? "local-deterministic" : model,
|
|
2054
2104
|
parallel: concurrency,
|
|
2055
2105
|
dumpFeatures: dumpFeatureMode,
|
|
2056
|
-
terminalWindows: terminalWindowDays
|
|
2106
|
+
terminalWindows: terminalWindowDays,
|
|
2107
|
+
directionPolicy
|
|
2057
2108
|
},
|
|
2058
2109
|
outcome: summary,
|
|
2059
2110
|
byDirection: directionSummaries,
|
|
@@ -1504,6 +1504,7 @@ var normalizeCompletedTrade = (params) => {
|
|
|
1504
1504
|
runId: row.backtestRunId ?? null,
|
|
1505
1505
|
configId: row.configId?.trim() || "<missing-config-id>",
|
|
1506
1506
|
signalId,
|
|
1507
|
+
positionCycleId: typeof result.positionCycleId === "string" && result.positionCycleId.trim() ? result.positionCycleId.trim() : null,
|
|
1507
1508
|
setupIdentity,
|
|
1508
1509
|
setupIdentitySource: explicitIdentity ? "research.setupIdentity" : strategyIdentity ? "strategy-context" : "signal-time-fallback",
|
|
1509
1510
|
strategy: row.strategyName,
|
|
@@ -1527,6 +1528,69 @@ var normalizeCompletedTrade = (params) => {
|
|
|
1527
1528
|
};
|
|
1528
1529
|
};
|
|
1529
1530
|
var completedTradeIdentity = (trade) => [trade.runId ?? "", trade.configId, trade.signalId, trade.symbol].join("|");
|
|
1531
|
+
var positionCycleIdentity = (trade) => trade.positionCycleId ? [
|
|
1532
|
+
trade.runId ?? "",
|
|
1533
|
+
trade.configId,
|
|
1534
|
+
trade.strategy,
|
|
1535
|
+
trade.symbol,
|
|
1536
|
+
trade.direction,
|
|
1537
|
+
trade.positionCycleId
|
|
1538
|
+
].join("|") : null;
|
|
1539
|
+
var sumBy = (trades, select) => trades.reduce((sum, trade) => sum + select(trade), 0);
|
|
1540
|
+
var weightedPrice = (trades, select) => {
|
|
1541
|
+
const withPrice = trades.filter((trade) => select(trade) != null);
|
|
1542
|
+
const qty = sumBy(withPrice, (trade) => trade.qty);
|
|
1543
|
+
if (qty <= 0) return null;
|
|
1544
|
+
return sumBy(withPrice, (trade) => Number(select(trade)) * trade.qty) / qty;
|
|
1545
|
+
};
|
|
1546
|
+
var aggregatePositionCycles = (trades) => {
|
|
1547
|
+
const standalone = [];
|
|
1548
|
+
const cycles = /* @__PURE__ */ new Map();
|
|
1549
|
+
for (const trade of trades) {
|
|
1550
|
+
const identity = positionCycleIdentity(trade);
|
|
1551
|
+
if (!identity) {
|
|
1552
|
+
standalone.push(trade);
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
const cycle = cycles.get(identity) ?? [];
|
|
1556
|
+
cycle.push(trade);
|
|
1557
|
+
cycles.set(identity, cycle);
|
|
1558
|
+
}
|
|
1559
|
+
for (const [identity, cycle] of cycles) {
|
|
1560
|
+
const primaryRows = cycle.filter(
|
|
1561
|
+
(trade) => trade.signalId === trade.positionCycleId
|
|
1562
|
+
);
|
|
1563
|
+
if (primaryRows.length !== 1) {
|
|
1564
|
+
throw new Error(
|
|
1565
|
+
`Position cycle ${identity} must contain exactly one opening row; found ${primaryRows.length}`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
const primary = primaryRows[0];
|
|
1569
|
+
if (cycle.some(
|
|
1570
|
+
(trade) => trade.exitTimestamp !== primary.exitTimestamp || trade.exitReason !== primary.exitReason
|
|
1571
|
+
)) {
|
|
1572
|
+
throw new Error(
|
|
1573
|
+
`Position cycle ${identity} contains inconsistent final exits`
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
if (cycle.length === 1) {
|
|
1577
|
+
standalone.push(primary);
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
standalone.push({
|
|
1581
|
+
...primary,
|
|
1582
|
+
entryTimestamp: Math.min(...cycle.map((trade) => trade.entryTimestamp)),
|
|
1583
|
+
entryPrice: weightedPrice(cycle, (trade) => trade.entryPrice) ?? 0,
|
|
1584
|
+
exitPrice: weightedPrice(cycle, (trade) => trade.exitPrice),
|
|
1585
|
+
qty: sumBy(cycle, (trade) => trade.qty),
|
|
1586
|
+
netProfit: sumBy(cycle, (trade) => trade.netProfit),
|
|
1587
|
+
grossProfit: sumBy(cycle, (trade) => trade.grossProfit),
|
|
1588
|
+
totalFee: sumBy(cycle, (trade) => trade.totalFee),
|
|
1589
|
+
totalSlippageCost: sumBy(cycle, (trade) => trade.totalSlippageCost)
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
return standalone;
|
|
1593
|
+
};
|
|
1530
1594
|
var compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
1531
1595
|
var 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;
|
|
1532
1596
|
var readCoreResearchVariant = async (variant) => {
|
|
@@ -1603,7 +1667,7 @@ var readCoreResearchVariant = async (variant) => {
|
|
|
1603
1667
|
variant,
|
|
1604
1668
|
files: sources,
|
|
1605
1669
|
duplicateRowsDropped,
|
|
1606
|
-
trades: trades.sort(compareTrades)
|
|
1670
|
+
trades: aggregatePositionCycles(trades).sort(compareTrades)
|
|
1607
1671
|
};
|
|
1608
1672
|
};
|
|
1609
1673
|
|
|
@@ -2084,13 +2148,17 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
|
2084
2148
|
const loaded = await Promise.all(
|
|
2085
2149
|
analysisSpec.variants.map(readCoreResearchVariant)
|
|
2086
2150
|
);
|
|
2151
|
+
const periodDays = (spec.window.end - spec.window.start) / DAY_MS;
|
|
2087
2152
|
const windowed = loaded.map((entry) => ({
|
|
2088
2153
|
...entry,
|
|
2154
|
+
reconciliationMetrics: summarizeCoreResearchCohorts(
|
|
2155
|
+
entry.trades,
|
|
2156
|
+
periodDays
|
|
2157
|
+
).ALL,
|
|
2089
2158
|
trades: entry.trades.filter(
|
|
2090
2159
|
(trade) => trade.exitTimestamp >= spec.window.start && trade.exitTimestamp < spec.window.end
|
|
2091
2160
|
)
|
|
2092
2161
|
}));
|
|
2093
|
-
const periodDays = (spec.window.end - spec.window.start) / DAY_MS;
|
|
2094
2162
|
const analyses = await Promise.all(
|
|
2095
2163
|
windowed.map(async (entry) => ({
|
|
2096
2164
|
variant: entry.variant,
|
|
@@ -2148,12 +2216,9 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
|
2148
2216
|
reconciliation: await reconcileCoreResearchVariant({
|
|
2149
2217
|
variant: entry.variant,
|
|
2150
2218
|
spec,
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
start: spec.window.start,
|
|
2155
|
-
end: spec.window.end
|
|
2156
|
-
}).cohorts.ALL
|
|
2219
|
+
// Redis result.stat includes the runner's terminal close at window.end.
|
|
2220
|
+
// Keep half-open research metrics, but reconcile the complete run export.
|
|
2221
|
+
exportMetrics: entry.reconciliationMetrics
|
|
2157
2222
|
}),
|
|
2158
2223
|
supplemental: {
|
|
2159
2224
|
coldStart: Object.fromEntries(
|
package/dist/scripts/replay.js
CHANGED
|
@@ -4906,6 +4906,7 @@ var import_promises2 = __toESM(require("fs/promises"));
|
|
|
4906
4906
|
var import_node_path2 = __toESM(require("path"));
|
|
4907
4907
|
var import_types2 = require("@tradejs/types");
|
|
4908
4908
|
var import_strategies3 = require("@tradejs/node/strategies");
|
|
4909
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
4909
4910
|
|
|
4910
4911
|
// src/lib/strategyGateFingerprint.ts
|
|
4911
4912
|
var import_node_crypto = require("crypto");
|
|
@@ -5000,17 +5001,6 @@ var normalizeForStableJson = (value) => {
|
|
|
5000
5001
|
}
|
|
5001
5002
|
return value;
|
|
5002
5003
|
};
|
|
5003
|
-
var normalizeRuntimeLogicConfig = (value) => {
|
|
5004
|
-
if (Array.isArray(value)) {
|
|
5005
|
-
return value.map(normalizeRuntimeLogicConfig);
|
|
5006
|
-
}
|
|
5007
|
-
if (value && typeof value === "object") {
|
|
5008
|
-
return Object.fromEntries(
|
|
5009
|
-
Object.entries(value).filter(([key]) => key !== "MAX_LOSS_VALUE").sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeRuntimeLogicConfig(entry)])
|
|
5010
|
-
);
|
|
5011
|
-
}
|
|
5012
|
-
return value;
|
|
5013
|
-
};
|
|
5014
5004
|
var resolveRuntimeMaxLossValue = (config) => {
|
|
5015
5005
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
5016
5006
|
return null;
|
|
@@ -5174,9 +5164,7 @@ var buildRuntimeLineage = async ({
|
|
|
5174
5164
|
})).gateFingerprint,
|
|
5175
5165
|
// Position risk is tracked separately so changing trade size does not
|
|
5176
5166
|
// masquerade as a change to the core + gate decision logic.
|
|
5177
|
-
configFingerprint:
|
|
5178
|
-
normalizeRuntimeLogicConfig(config)
|
|
5179
|
-
),
|
|
5167
|
+
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config),
|
|
5180
5168
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
5181
5169
|
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
5182
5170
|
};
|
|
@@ -4909,6 +4909,7 @@ var import_promises2 = __toESM(require("fs/promises"));
|
|
|
4909
4909
|
var import_node_path2 = __toESM(require("path"));
|
|
4910
4910
|
var import_types2 = require("@tradejs/types");
|
|
4911
4911
|
var import_strategies3 = require("@tradejs/node/strategies");
|
|
4912
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
4912
4913
|
|
|
4913
4914
|
// src/lib/strategyGateFingerprint.ts
|
|
4914
4915
|
var import_node_crypto = require("crypto");
|
|
@@ -5003,17 +5004,6 @@ var normalizeForStableJson = (value) => {
|
|
|
5003
5004
|
}
|
|
5004
5005
|
return value;
|
|
5005
5006
|
};
|
|
5006
|
-
var normalizeRuntimeLogicConfig = (value) => {
|
|
5007
|
-
if (Array.isArray(value)) {
|
|
5008
|
-
return value.map(normalizeRuntimeLogicConfig);
|
|
5009
|
-
}
|
|
5010
|
-
if (value && typeof value === "object") {
|
|
5011
|
-
return Object.fromEntries(
|
|
5012
|
-
Object.entries(value).filter(([key]) => key !== "MAX_LOSS_VALUE").sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeRuntimeLogicConfig(entry)])
|
|
5013
|
-
);
|
|
5014
|
-
}
|
|
5015
|
-
return value;
|
|
5016
|
-
};
|
|
5017
5007
|
var resolveRuntimeMaxLossValue = (config) => {
|
|
5018
5008
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
5019
5009
|
return null;
|
|
@@ -5177,9 +5167,7 @@ var buildRuntimeLineage = async ({
|
|
|
5177
5167
|
})).gateFingerprint,
|
|
5178
5168
|
// Position risk is tracked separately so changing trade size does not
|
|
5179
5169
|
// masquerade as a change to the core + gate decision logic.
|
|
5180
|
-
configFingerprint:
|
|
5181
|
-
normalizeRuntimeLogicConfig(config)
|
|
5182
|
-
),
|
|
5170
|
+
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config),
|
|
5183
5171
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
5184
5172
|
maxLossValue: resolveRuntimeMaxLossValue(config)
|
|
5185
5173
|
};
|
|
@@ -564,6 +564,7 @@ var import_promises3 = __toESM(require("fs/promises"));
|
|
|
564
564
|
var import_node_path3 = __toESM(require("path"));
|
|
565
565
|
var import_types = require("@tradejs/types");
|
|
566
566
|
var import_strategies = require("@tradejs/node/strategies");
|
|
567
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
567
568
|
|
|
568
569
|
// src/lib/strategyGateFingerprint.ts
|
|
569
570
|
var import_node_crypto2 = require("crypto");
|
|
@@ -811,6 +811,7 @@ var deriveReleaseGates = (evidence) => {
|
|
|
811
811
|
};
|
|
812
812
|
};
|
|
813
813
|
var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
814
|
+
const directionPolicy = composition.directionPolicy ?? "both";
|
|
814
815
|
const requireEqual = (actual, expected, label, artifactId) => assert(
|
|
815
816
|
actual === expected,
|
|
816
817
|
`evidence ${artifactId} ${label} does not match the frozen composition`
|
|
@@ -881,6 +882,14 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
|
881
882
|
"gateContextFingerprint",
|
|
882
883
|
reference.artifactId
|
|
883
884
|
);
|
|
885
|
+
if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
|
|
886
|
+
requireEqual(
|
|
887
|
+
lineage?.directionPolicy,
|
|
888
|
+
directionPolicy,
|
|
889
|
+
"directionPolicy",
|
|
890
|
+
reference.artifactId
|
|
891
|
+
);
|
|
892
|
+
}
|
|
884
893
|
assert(
|
|
885
894
|
lineage?.sourceSha256s.includes(composition.coreExportSha256),
|
|
886
895
|
`evidence ${reference.artifactId} does not use the frozen core export`
|
|
@@ -925,6 +934,14 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
|
925
934
|
"MAX_LOSS_VALUE",
|
|
926
935
|
reference.artifactId
|
|
927
936
|
);
|
|
937
|
+
if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
|
|
938
|
+
requireEqual(
|
|
939
|
+
lineage?.directionPolicy,
|
|
940
|
+
directionPolicy,
|
|
941
|
+
"directionPolicy",
|
|
942
|
+
reference.artifactId
|
|
943
|
+
);
|
|
944
|
+
}
|
|
928
945
|
continue;
|
|
929
946
|
}
|
|
930
947
|
if (reference.kind === "execution_calibration") {
|
|
@@ -965,6 +982,14 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
|
965
982
|
"MAX_LOSS_VALUE",
|
|
966
983
|
reference.artifactId
|
|
967
984
|
);
|
|
985
|
+
if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
|
|
986
|
+
requireEqual(
|
|
987
|
+
lineage?.directionPolicy,
|
|
988
|
+
directionPolicy,
|
|
989
|
+
"directionPolicy",
|
|
990
|
+
reference.artifactId
|
|
991
|
+
);
|
|
992
|
+
}
|
|
968
993
|
}
|
|
969
994
|
}
|
|
970
995
|
};
|
|
@@ -1000,6 +1025,12 @@ var createStrategyReleaseManifest = (input) => {
|
|
|
1000
1025
|
input.composition.longEnabled && input.composition.shortEnabled,
|
|
1001
1026
|
"LONG and SHORT must both remain enabled"
|
|
1002
1027
|
);
|
|
1028
|
+
assert(
|
|
1029
|
+
input.composition.directionPolicy == null || ["both", "long_only", "short_only", "direction_aware"].includes(
|
|
1030
|
+
input.composition.directionPolicy
|
|
1031
|
+
),
|
|
1032
|
+
"composition.directionPolicy is invalid"
|
|
1033
|
+
);
|
|
1003
1034
|
assert(
|
|
1004
1035
|
Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
|
|
1005
1036
|
"monitoring.minimumProspectiveClosedTrades must be positive"
|
|
@@ -1064,7 +1095,8 @@ var createStrategyReleaseManifest = (input) => {
|
|
|
1064
1095
|
gateFingerprint: input.composition.gateFingerprint,
|
|
1065
1096
|
gateContextFingerprint: input.composition.gateContextFingerprint,
|
|
1066
1097
|
runtimeContextFingerprint: input.composition.runtimeContextFingerprint,
|
|
1067
|
-
maxLossValue: input.composition.maxLossValue
|
|
1098
|
+
maxLossValue: input.composition.maxLossValue,
|
|
1099
|
+
...input.composition.directionPolicy == null ? {} : { directionPolicy: input.composition.directionPolicy }
|
|
1068
1100
|
};
|
|
1069
1101
|
const compositionId = `${safeSegment2(input.strategy, "strategy")}_${strategyReleaseSha256(compositionIdentity).slice(0, 16)}`;
|
|
1070
1102
|
const reasons = releaseReasons({ ...input, gates: evidenceGates });
|
|
@@ -1148,7 +1180,8 @@ var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
|
|
|
1148
1180
|
runtimeContextFingerprint: manifest.composition.runtimeContextFingerprint,
|
|
1149
1181
|
maxLossValue: manifest.composition.maxLossValue,
|
|
1150
1182
|
longEnabled: manifest.composition.longEnabled,
|
|
1151
|
-
shortEnabled: manifest.composition.shortEnabled
|
|
1183
|
+
shortEnabled: manifest.composition.shortEnabled,
|
|
1184
|
+
directionPolicy: manifest.composition.directionPolicy
|
|
1152
1185
|
},
|
|
1153
1186
|
marketWindow: manifest.marketWindow,
|
|
1154
1187
|
researchBudget: manifest.researchBudget,
|
package/dist/scripts/signals.js
CHANGED
|
@@ -4003,6 +4003,7 @@ var import_promises2 = __toESM(require("fs/promises"));
|
|
|
4003
4003
|
var import_node_path2 = __toESM(require("path"));
|
|
4004
4004
|
var import_types = require("@tradejs/types");
|
|
4005
4005
|
var import_strategies4 = require("@tradejs/node/strategies");
|
|
4006
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
4006
4007
|
|
|
4007
4008
|
// src/lib/strategyGateFingerprint.ts
|
|
4008
4009
|
var import_node_crypto = require("crypto");
|
|
@@ -4097,17 +4098,6 @@ var normalizeForStableJson = (value) => {
|
|
|
4097
4098
|
}
|
|
4098
4099
|
return value;
|
|
4099
4100
|
};
|
|
4100
|
-
var normalizeRuntimeLogicConfig = (value) => {
|
|
4101
|
-
if (Array.isArray(value)) {
|
|
4102
|
-
return value.map(normalizeRuntimeLogicConfig);
|
|
4103
|
-
}
|
|
4104
|
-
if (value && typeof value === "object") {
|
|
4105
|
-
return Object.fromEntries(
|
|
4106
|
-
Object.entries(value).filter(([key]) => key !== "MAX_LOSS_VALUE").sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeRuntimeLogicConfig(entry)])
|
|
4107
|
-
);
|
|
4108
|
-
}
|
|
4109
|
-
return value;
|
|
4110
|
-
};
|
|
4111
4101
|
var resolveRuntimeMaxLossValue = (config2) => {
|
|
4112
4102
|
if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) {
|
|
4113
4103
|
return null;
|
|
@@ -4271,9 +4261,7 @@ var buildRuntimeLineage = async ({
|
|
|
4271
4261
|
})).gateFingerprint,
|
|
4272
4262
|
// Position risk is tracked separately so changing trade size does not
|
|
4273
4263
|
// masquerade as a change to the core + gate decision logic.
|
|
4274
|
-
configFingerprint:
|
|
4275
|
-
normalizeRuntimeLogicConfig(config2)
|
|
4276
|
-
),
|
|
4264
|
+
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config2),
|
|
4277
4265
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
4278
4266
|
maxLossValue: resolveRuntimeMaxLossValue(config2)
|
|
4279
4267
|
};
|
|
@@ -4001,6 +4001,7 @@ var import_promises2 = __toESM(require("fs/promises"));
|
|
|
4001
4001
|
var import_node_path2 = __toESM(require("path"));
|
|
4002
4002
|
var import_types = require("@tradejs/types");
|
|
4003
4003
|
var import_strategies4 = require("@tradejs/node/strategies");
|
|
4004
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
4004
4005
|
|
|
4005
4006
|
// src/lib/strategyGateFingerprint.ts
|
|
4006
4007
|
var import_node_crypto = require("crypto");
|
|
@@ -4095,17 +4096,6 @@ var normalizeForStableJson = (value) => {
|
|
|
4095
4096
|
}
|
|
4096
4097
|
return value;
|
|
4097
4098
|
};
|
|
4098
|
-
var normalizeRuntimeLogicConfig = (value) => {
|
|
4099
|
-
if (Array.isArray(value)) {
|
|
4100
|
-
return value.map(normalizeRuntimeLogicConfig);
|
|
4101
|
-
}
|
|
4102
|
-
if (value && typeof value === "object") {
|
|
4103
|
-
return Object.fromEntries(
|
|
4104
|
-
Object.entries(value).filter(([key]) => key !== "MAX_LOSS_VALUE").sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeRuntimeLogicConfig(entry)])
|
|
4105
|
-
);
|
|
4106
|
-
}
|
|
4107
|
-
return value;
|
|
4108
|
-
};
|
|
4109
4099
|
var resolveRuntimeMaxLossValue = (config2) => {
|
|
4110
4100
|
if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) {
|
|
4111
4101
|
return null;
|
|
@@ -4269,9 +4259,7 @@ var buildRuntimeLineage = async ({
|
|
|
4269
4259
|
})).gateFingerprint,
|
|
4270
4260
|
// Position risk is tracked separately so changing trade size does not
|
|
4271
4261
|
// masquerade as a change to the core + gate decision logic.
|
|
4272
|
-
configFingerprint:
|
|
4273
|
-
normalizeRuntimeLogicConfig(config2)
|
|
4274
|
-
),
|
|
4262
|
+
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config2),
|
|
4275
4263
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
4276
4264
|
maxLossValue: resolveRuntimeMaxLossValue(config2)
|
|
4277
4265
|
};
|