@tradejs/cli 2.0.21 → 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 +242 -69
- package/dist/lib/aiTrainOptions.js +36 -0
- package/dist/lib/coreResearch.js +73 -8
- package/dist/lib/strategyRelease.js +71 -14
- 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/runtimeScorecard.js +35 -2
- package/dist/scripts/strategyRelease.js +71 -14
- package/package.json +9 -9
|
@@ -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(
|
|
@@ -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,
|
|
@@ -75,6 +75,12 @@ var hasExactRuntimeTarget = (target) => Boolean(
|
|
|
75
75
|
);
|
|
76
76
|
var deriveStrategyReleaseResearchDecision = async (input) => {
|
|
77
77
|
const blockers = [];
|
|
78
|
+
const directionPolicy = input.directionPolicy ?? "both";
|
|
79
|
+
const activeDirections = directionPolicy === "long_only" ? ["long"] : directionPolicy === "short_only" ? ["short"] : ["long", "short"];
|
|
80
|
+
const suppressedDirection = directionPolicy === "long_only" ? "short" : directionPolicy === "short_only" ? "long" : null;
|
|
81
|
+
const hasHistoricalEdge = (entry) => entry.pnl > 0 && entry.profitFactor >= 1 && activeDirections.every(
|
|
82
|
+
(direction) => entry[direction].pnl > 0 && entry[direction].profitFactor >= 1
|
|
83
|
+
) && (suppressedDirection == null || entry[suppressedDirection].trades === 0 && entry[suppressedDirection].pnl === 0);
|
|
78
84
|
const byDays = new Map(
|
|
79
85
|
input.historicalWindows.map((entry) => [entry.days, entry])
|
|
80
86
|
);
|
|
@@ -87,17 +93,13 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
|
|
|
87
93
|
const longWindows = REQUIRED_LONG_WINDOWS.map(
|
|
88
94
|
(days) => byDays.get(days)
|
|
89
95
|
).filter((entry) => Boolean(entry));
|
|
90
|
-
if (longWindows.some(
|
|
91
|
-
(entry) => entry.pnl <= 0 || entry.profitFactor < 1 || entry.long.pnl <= 0 || entry.long.profitFactor < 1 || entry.short.pnl <= 0 || entry.short.profitFactor < 1
|
|
92
|
-
)) {
|
|
96
|
+
if (longWindows.some((entry) => !hasHistoricalEdge(entry))) {
|
|
93
97
|
blockers.push("HISTORICAL_EDGE_FAILED");
|
|
94
98
|
}
|
|
95
99
|
const stableTerminalWindows = REQUIRED_STABLE_TERMINAL_WINDOWS.map(
|
|
96
100
|
(days) => byDays.get(days)
|
|
97
101
|
).filter((entry) => Boolean(entry));
|
|
98
|
-
if (stableTerminalWindows.some(
|
|
99
|
-
(entry) => entry.pnl <= 0 || entry.profitFactor < 1 || entry.long.pnl <= 0 || entry.long.profitFactor < 1 || entry.short.pnl <= 0 || entry.short.profitFactor < 1
|
|
100
|
-
)) {
|
|
102
|
+
if (stableTerminalWindows.some((entry) => !hasHistoricalEdge(entry))) {
|
|
101
103
|
blockers.push("HISTORICAL_EDGE_FAILED");
|
|
102
104
|
}
|
|
103
105
|
if (blockers.length) {
|
|
@@ -109,7 +111,7 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
|
|
|
109
111
|
maxLossValue: null,
|
|
110
112
|
requiresRuntimeBinding: false,
|
|
111
113
|
blockers,
|
|
112
|
-
summary:
|
|
114
|
+
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.`
|
|
113
115
|
};
|
|
114
116
|
}
|
|
115
117
|
const repairAllowed = Boolean(
|
|
@@ -236,6 +238,7 @@ var deriveReleaseGates = (evidence) => {
|
|
|
236
238
|
};
|
|
237
239
|
};
|
|
238
240
|
var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
241
|
+
const directionPolicy = composition.directionPolicy ?? "both";
|
|
239
242
|
const requireEqual = (actual, expected, label, artifactId) => assert(
|
|
240
243
|
actual === expected,
|
|
241
244
|
`evidence ${artifactId} ${label} does not match the frozen composition`
|
|
@@ -306,6 +309,14 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
|
306
309
|
"gateContextFingerprint",
|
|
307
310
|
reference.artifactId
|
|
308
311
|
);
|
|
312
|
+
if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
|
|
313
|
+
requireEqual(
|
|
314
|
+
lineage?.directionPolicy,
|
|
315
|
+
directionPolicy,
|
|
316
|
+
"directionPolicy",
|
|
317
|
+
reference.artifactId
|
|
318
|
+
);
|
|
319
|
+
}
|
|
309
320
|
assert(
|
|
310
321
|
lineage?.sourceSha256s.includes(composition.coreExportSha256),
|
|
311
322
|
`evidence ${reference.artifactId} does not use the frozen core export`
|
|
@@ -350,6 +361,14 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
|
350
361
|
"MAX_LOSS_VALUE",
|
|
351
362
|
reference.artifactId
|
|
352
363
|
);
|
|
364
|
+
if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
|
|
365
|
+
requireEqual(
|
|
366
|
+
lineage?.directionPolicy,
|
|
367
|
+
directionPolicy,
|
|
368
|
+
"directionPolicy",
|
|
369
|
+
reference.artifactId
|
|
370
|
+
);
|
|
371
|
+
}
|
|
353
372
|
continue;
|
|
354
373
|
}
|
|
355
374
|
if (reference.kind === "execution_calibration") {
|
|
@@ -390,6 +409,14 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
|
390
409
|
"MAX_LOSS_VALUE",
|
|
391
410
|
reference.artifactId
|
|
392
411
|
);
|
|
412
|
+
if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
|
|
413
|
+
requireEqual(
|
|
414
|
+
lineage?.directionPolicy,
|
|
415
|
+
directionPolicy,
|
|
416
|
+
"directionPolicy",
|
|
417
|
+
reference.artifactId
|
|
418
|
+
);
|
|
419
|
+
}
|
|
393
420
|
}
|
|
394
421
|
}
|
|
395
422
|
};
|
|
@@ -425,6 +452,12 @@ var createStrategyReleaseManifest = (input) => {
|
|
|
425
452
|
input.composition.longEnabled && input.composition.shortEnabled,
|
|
426
453
|
"LONG and SHORT must both remain enabled"
|
|
427
454
|
);
|
|
455
|
+
assert(
|
|
456
|
+
input.composition.directionPolicy == null || ["both", "long_only", "short_only", "direction_aware"].includes(
|
|
457
|
+
input.composition.directionPolicy
|
|
458
|
+
),
|
|
459
|
+
"composition.directionPolicy is invalid"
|
|
460
|
+
);
|
|
428
461
|
assert(
|
|
429
462
|
Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
|
|
430
463
|
"monitoring.minimumProspectiveClosedTrades must be positive"
|
|
@@ -489,7 +522,8 @@ var createStrategyReleaseManifest = (input) => {
|
|
|
489
522
|
gateFingerprint: input.composition.gateFingerprint,
|
|
490
523
|
gateContextFingerprint: input.composition.gateContextFingerprint,
|
|
491
524
|
runtimeContextFingerprint: input.composition.runtimeContextFingerprint,
|
|
492
|
-
maxLossValue: input.composition.maxLossValue
|
|
525
|
+
maxLossValue: input.composition.maxLossValue,
|
|
526
|
+
...input.composition.directionPolicy == null ? {} : { directionPolicy: input.composition.directionPolicy }
|
|
493
527
|
};
|
|
494
528
|
const compositionId = `${safeSegment(input.strategy, "strategy")}_${strategyReleaseSha256(compositionIdentity).slice(0, 16)}`;
|
|
495
529
|
const reasons = releaseReasons({ ...input, gates: evidenceGates });
|
|
@@ -579,7 +613,8 @@ var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
|
|
|
579
613
|
runtimeContextFingerprint: manifest.composition.runtimeContextFingerprint,
|
|
580
614
|
maxLossValue: manifest.composition.maxLossValue,
|
|
581
615
|
longEnabled: manifest.composition.longEnabled,
|
|
582
|
-
shortEnabled: manifest.composition.shortEnabled
|
|
616
|
+
shortEnabled: manifest.composition.shortEnabled,
|
|
617
|
+
directionPolicy: manifest.composition.directionPolicy
|
|
583
618
|
},
|
|
584
619
|
marketWindow: manifest.marketWindow,
|
|
585
620
|
researchBudget: manifest.researchBudget,
|
|
@@ -959,6 +994,7 @@ var finiteNumber = (value) => {
|
|
|
959
994
|
const number = Number(value);
|
|
960
995
|
return Number.isFinite(number) ? number : null;
|
|
961
996
|
};
|
|
997
|
+
var finiteDirectionPolicy = (value) => ["both", "long_only", "short_only", "direction_aware"].includes(String(value)) ? value : null;
|
|
962
998
|
var uniqueStrings = (values) => [
|
|
963
999
|
...new Set(
|
|
964
1000
|
values.map(finiteString).filter((value) => value != null)
|
|
@@ -975,6 +1011,7 @@ var incompleteEvidenceLineage = () => ({
|
|
|
975
1011
|
gateContextFingerprint: null,
|
|
976
1012
|
runtimeContextFingerprint: null,
|
|
977
1013
|
maxLossValue: null,
|
|
1014
|
+
directionPolicy: null,
|
|
978
1015
|
sourceSha256s: []
|
|
979
1016
|
});
|
|
980
1017
|
var extractEvidenceLineage = ({
|
|
@@ -1037,6 +1074,7 @@ var extractEvidenceLineage = ({
|
|
|
1037
1074
|
gateConfigIdsFingerprint: finiteString(lineage?.configIdsFingerprint),
|
|
1038
1075
|
gateFingerprint: finiteString(lineage?.gateFingerprint),
|
|
1039
1076
|
gateContextFingerprint: finiteString(lineage?.contextFingerprint),
|
|
1077
|
+
directionPolicy: finiteDirectionPolicy(lineage?.directionPolicy),
|
|
1040
1078
|
sourceSha256s: uniqueStrings(array(lineage?.sourceSha256s))
|
|
1041
1079
|
};
|
|
1042
1080
|
}
|
|
@@ -1072,7 +1110,8 @@ var extractEvidenceLineage = ({
|
|
|
1072
1110
|
gateFingerprint: one("gateFingerprint"),
|
|
1073
1111
|
runtimeConfigFingerprint: one("configFingerprint"),
|
|
1074
1112
|
runtimeContextFingerprint: one("contextFingerprint"),
|
|
1075
|
-
maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null
|
|
1113
|
+
maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
|
|
1114
|
+
directionPolicy: finiteDirectionPolicy(one("directionPolicy"))
|
|
1076
1115
|
};
|
|
1077
1116
|
}
|
|
1078
1117
|
if (reference.kind === "execution_calibration") {
|
|
@@ -1109,6 +1148,7 @@ var extractEvidenceLineage = ({
|
|
|
1109
1148
|
runtimeConfigFingerprint: one("configFingerprint"),
|
|
1110
1149
|
runtimeContextFingerprint: one("contextFingerprint"),
|
|
1111
1150
|
maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
|
|
1151
|
+
directionPolicy: finiteDirectionPolicy(one("directionPolicy")),
|
|
1112
1152
|
sourceSha256s: uniqueStrings([
|
|
1113
1153
|
...array(nested(artifact.sources)?.sha256s),
|
|
1114
1154
|
...array(artifact.sourceSha256s)
|
|
@@ -1245,9 +1285,10 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
1245
1285
|
if (reference.kind === "ai_gate") {
|
|
1246
1286
|
const outcome = nested(artifact.outcome);
|
|
1247
1287
|
const approvedRisk = nested(outcome?.approvedRisk);
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1288
|
+
const run = nested(artifact.run);
|
|
1289
|
+
const research = nested(artifact.research);
|
|
1290
|
+
const researchLineage = nested(research?.lineage);
|
|
1291
|
+
const terminalWindows = array(research?.terminalWindows).map(nested).filter((entry) => entry != null);
|
|
1251
1292
|
const hasLongShortDirections = (value) => {
|
|
1252
1293
|
const directions = new Map(
|
|
1253
1294
|
array(value).map(nested).filter(
|
|
@@ -1261,6 +1302,22 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
1261
1302
|
const directionalEvidenceComplete = hasLongShortDirections(artifact.byDirection) && terminalWindows.every(
|
|
1262
1303
|
(window) => hasLongShortDirections(window.byDirection)
|
|
1263
1304
|
);
|
|
1305
|
+
const directionPolicy = finiteDirectionPolicy(researchLineage?.directionPolicy) ?? "both";
|
|
1306
|
+
const runDirectionPolicy = finiteDirectionPolicy(run?.directionPolicy) ?? "both";
|
|
1307
|
+
const suppressedDirection = directionPolicy === "long_only" ? "SHORT" : directionPolicy === "short_only" ? "LONG" : null;
|
|
1308
|
+
const approvedForDirection = (value, direction) => {
|
|
1309
|
+
const entry = array(value).map(nested).find((candidate) => candidate?.direction === direction);
|
|
1310
|
+
return Number(nested(entry?.summary)?.approved ?? Number.NaN);
|
|
1311
|
+
};
|
|
1312
|
+
const directionPolicyValid = runDirectionPolicy === directionPolicy && (suppressedDirection == null || approvedForDirection(
|
|
1313
|
+
artifact.byDirection,
|
|
1314
|
+
suppressedDirection
|
|
1315
|
+
) === 0 && terminalWindows.every(
|
|
1316
|
+
(window) => approvedForDirection(
|
|
1317
|
+
window.byDirection,
|
|
1318
|
+
suppressedDirection
|
|
1319
|
+
) === 0
|
|
1320
|
+
));
|
|
1264
1321
|
const terminalsPass = terminalWindows.length > 0 && terminalWindows.every((window) => {
|
|
1265
1322
|
const terminalOutcome = nested(window.outcome);
|
|
1266
1323
|
return window.complete === true && Number(terminalOutcome?.approved ?? 0) > 0 && Number(
|
|
@@ -1268,7 +1325,7 @@ async function collectReleaseEvidenceReferences(references) {
|
|
|
1268
1325
|
) > 0;
|
|
1269
1326
|
});
|
|
1270
1327
|
return {
|
|
1271
|
-
aiGateAddsValue: Number(outcome?.expectancyDelta ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.totalProfit ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.profitFactor ?? Number.NEGATIVE_INFINITY) > 1 && directionalEvidenceComplete && terminalsPass
|
|
1328
|
+
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
|
|
1272
1329
|
};
|
|
1273
1330
|
}
|
|
1274
1331
|
if (reference.kind === "runtime_parity") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Official CLI for the TradeJS TypeScript framework: infra setup, backtests, signals, bots, and ML workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tradejs",
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
"tradejs": "dist/cli.js"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@tradejs/base": "^
|
|
33
|
-
"@tradejs/connectors": "^
|
|
34
|
-
"@tradejs/core": "^
|
|
35
|
-
"@tradejs/indicators": "^
|
|
36
|
-
"@tradejs/infra": "^
|
|
37
|
-
"@tradejs/node": "^
|
|
38
|
-
"@tradejs/strategies": "^
|
|
39
|
-
"@tradejs/types": "^
|
|
32
|
+
"@tradejs/base": "^3.0.0",
|
|
33
|
+
"@tradejs/connectors": "^3.0.0",
|
|
34
|
+
"@tradejs/core": "^3.0.0",
|
|
35
|
+
"@tradejs/indicators": "^3.0.0",
|
|
36
|
+
"@tradejs/infra": "^3.0.0",
|
|
37
|
+
"@tradejs/node": "^3.0.0",
|
|
38
|
+
"@tradejs/strategies": "^3.0.0",
|
|
39
|
+
"@tradejs/types": "^3.0.0",
|
|
40
40
|
"args": "^5.0.3",
|
|
41
41
|
"bcryptjs": "^2.4.3",
|
|
42
42
|
"chalk": "4.1.2",
|