@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.
@@ -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) {
@@ -107,8 +109,9 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
107
109
  repairAllowed: false,
108
110
  targetDirection: input.recentFailure?.direction ?? null,
109
111
  maxLossValue: null,
112
+ requiresRuntimeBinding: false,
110
113
  blockers,
111
- summary: "The final composition does not have complete positive ALL/LONG/SHORT evidence on the frozen 3y/4y/max-window matrix."
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.`
112
115
  };
113
116
  }
114
117
  const repairAllowed = Boolean(
@@ -121,6 +124,7 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
121
124
  repairAllowed: true,
122
125
  targetDirection: input.recentFailure.direction,
123
126
  maxLossValue: null,
127
+ requiresRuntimeBinding: false,
124
128
  blockers: [],
125
129
  summary: "Spend the single terminal-direction repair round on the preregistered causal mechanism, then rebuild the full evidence matrix."
126
130
  };
@@ -139,6 +143,7 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
139
143
  repairAllowed: false,
140
144
  targetDirection: input.recentFailure?.direction ?? null,
141
145
  maxLossValue: null,
146
+ requiresRuntimeBinding: false,
142
147
  blockers,
143
148
  summary: "Complete the frozen candidate implementation and full-period chart before any micro-forward execution."
144
149
  };
@@ -150,6 +155,9 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
150
155
  repairAllowed: false,
151
156
  targetDirection: input.recentFailure?.direction ?? null,
152
157
  maxLossValue: 1,
158
+ requiresRuntimeBinding: !hasExactRuntimeTarget(
159
+ input.forwardTest.runtimeTarget
160
+ ),
153
161
  blockers: ["FORWARD_NOT_AUTHORIZED"],
154
162
  summary: "The candidate is ready for a separately authorized micro-forward test at MAX_LOSS_VALUE=1."
155
163
  };
@@ -157,12 +165,13 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
157
165
  if (!hasExactRuntimeTarget(input.forwardTest.runtimeTarget)) {
158
166
  return {
159
167
  strategy: input.strategy,
160
- action: "FORWARD_BLOCKED",
168
+ action: "MICRO_FORWARD_READY",
161
169
  repairAllowed: false,
162
170
  targetDirection: input.recentFailure?.direction ?? null,
163
171
  maxLossValue: 1,
164
- blockers: ["RUNTIME_TARGET_UNRESOLVED"],
165
- summary: "Forward testing is authorized, but an exact runtime deployment/account target must be resolved first."
172
+ requiresRuntimeBinding: true,
173
+ blockers: [],
174
+ 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."
166
175
  };
167
176
  }
168
177
  return {
@@ -171,6 +180,7 @@ var deriveStrategyReleaseResearchDecision = async (input) => {
171
180
  repairAllowed: false,
172
181
  targetDirection: input.recentFailure?.direction ?? null,
173
182
  maxLossValue: 1,
183
+ requiresRuntimeBinding: false,
174
184
  blockers: [],
175
185
  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."
176
186
  };
@@ -228,6 +238,7 @@ var deriveReleaseGates = (evidence) => {
228
238
  };
229
239
  };
230
240
  var verifyEvidenceComposition = (strategy, composition, evidence) => {
241
+ const directionPolicy = composition.directionPolicy ?? "both";
231
242
  const requireEqual = (actual, expected, label, artifactId) => assert(
232
243
  actual === expected,
233
244
  `evidence ${artifactId} ${label} does not match the frozen composition`
@@ -298,6 +309,14 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
298
309
  "gateContextFingerprint",
299
310
  reference.artifactId
300
311
  );
312
+ if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
313
+ requireEqual(
314
+ lineage?.directionPolicy,
315
+ directionPolicy,
316
+ "directionPolicy",
317
+ reference.artifactId
318
+ );
319
+ }
301
320
  assert(
302
321
  lineage?.sourceSha256s.includes(composition.coreExportSha256),
303
322
  `evidence ${reference.artifactId} does not use the frozen core export`
@@ -342,6 +361,14 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
342
361
  "MAX_LOSS_VALUE",
343
362
  reference.artifactId
344
363
  );
364
+ if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
365
+ requireEqual(
366
+ lineage?.directionPolicy,
367
+ directionPolicy,
368
+ "directionPolicy",
369
+ reference.artifactId
370
+ );
371
+ }
345
372
  continue;
346
373
  }
347
374
  if (reference.kind === "execution_calibration") {
@@ -382,6 +409,14 @@ var verifyEvidenceComposition = (strategy, composition, evidence) => {
382
409
  "MAX_LOSS_VALUE",
383
410
  reference.artifactId
384
411
  );
412
+ if (directionPolicy !== "both" || lineage?.directionPolicy != null) {
413
+ requireEqual(
414
+ lineage?.directionPolicy,
415
+ directionPolicy,
416
+ "directionPolicy",
417
+ reference.artifactId
418
+ );
419
+ }
385
420
  }
386
421
  }
387
422
  };
@@ -417,6 +452,12 @@ var createStrategyReleaseManifest = (input) => {
417
452
  input.composition.longEnabled && input.composition.shortEnabled,
418
453
  "LONG and SHORT must both remain enabled"
419
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
+ );
420
461
  assert(
421
462
  Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
422
463
  "monitoring.minimumProspectiveClosedTrades must be positive"
@@ -481,7 +522,8 @@ var createStrategyReleaseManifest = (input) => {
481
522
  gateFingerprint: input.composition.gateFingerprint,
482
523
  gateContextFingerprint: input.composition.gateContextFingerprint,
483
524
  runtimeContextFingerprint: input.composition.runtimeContextFingerprint,
484
- maxLossValue: input.composition.maxLossValue
525
+ maxLossValue: input.composition.maxLossValue,
526
+ ...input.composition.directionPolicy == null ? {} : { directionPolicy: input.composition.directionPolicy }
485
527
  };
486
528
  const compositionId = `${safeSegment(input.strategy, "strategy")}_${strategyReleaseSha256(compositionIdentity).slice(0, 16)}`;
487
529
  const reasons = releaseReasons({ ...input, gates: evidenceGates });
@@ -571,7 +613,8 @@ var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
571
613
  runtimeContextFingerprint: manifest.composition.runtimeContextFingerprint,
572
614
  maxLossValue: manifest.composition.maxLossValue,
573
615
  longEnabled: manifest.composition.longEnabled,
574
- shortEnabled: manifest.composition.shortEnabled
616
+ shortEnabled: manifest.composition.shortEnabled,
617
+ directionPolicy: manifest.composition.directionPolicy
575
618
  },
576
619
  marketWindow: manifest.marketWindow,
577
620
  researchBudget: manifest.researchBudget,
@@ -951,6 +994,7 @@ var finiteNumber = (value) => {
951
994
  const number = Number(value);
952
995
  return Number.isFinite(number) ? number : null;
953
996
  };
997
+ var finiteDirectionPolicy = (value) => ["both", "long_only", "short_only", "direction_aware"].includes(String(value)) ? value : null;
954
998
  var uniqueStrings = (values) => [
955
999
  ...new Set(
956
1000
  values.map(finiteString).filter((value) => value != null)
@@ -967,6 +1011,7 @@ var incompleteEvidenceLineage = () => ({
967
1011
  gateContextFingerprint: null,
968
1012
  runtimeContextFingerprint: null,
969
1013
  maxLossValue: null,
1014
+ directionPolicy: null,
970
1015
  sourceSha256s: []
971
1016
  });
972
1017
  var extractEvidenceLineage = ({
@@ -1029,6 +1074,7 @@ var extractEvidenceLineage = ({
1029
1074
  gateConfigIdsFingerprint: finiteString(lineage?.configIdsFingerprint),
1030
1075
  gateFingerprint: finiteString(lineage?.gateFingerprint),
1031
1076
  gateContextFingerprint: finiteString(lineage?.contextFingerprint),
1077
+ directionPolicy: finiteDirectionPolicy(lineage?.directionPolicy),
1032
1078
  sourceSha256s: uniqueStrings(array(lineage?.sourceSha256s))
1033
1079
  };
1034
1080
  }
@@ -1064,7 +1110,8 @@ var extractEvidenceLineage = ({
1064
1110
  gateFingerprint: one("gateFingerprint"),
1065
1111
  runtimeConfigFingerprint: one("configFingerprint"),
1066
1112
  runtimeContextFingerprint: one("contextFingerprint"),
1067
- maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null
1113
+ maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
1114
+ directionPolicy: finiteDirectionPolicy(one("directionPolicy"))
1068
1115
  };
1069
1116
  }
1070
1117
  if (reference.kind === "execution_calibration") {
@@ -1101,6 +1148,7 @@ var extractEvidenceLineage = ({
1101
1148
  runtimeConfigFingerprint: one("configFingerprint"),
1102
1149
  runtimeContextFingerprint: one("contextFingerprint"),
1103
1150
  maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
1151
+ directionPolicy: finiteDirectionPolicy(one("directionPolicy")),
1104
1152
  sourceSha256s: uniqueStrings([
1105
1153
  ...array(nested(artifact.sources)?.sha256s),
1106
1154
  ...array(artifact.sourceSha256s)
@@ -1237,9 +1285,10 @@ async function collectReleaseEvidenceReferences(references) {
1237
1285
  if (reference.kind === "ai_gate") {
1238
1286
  const outcome = nested(artifact.outcome);
1239
1287
  const approvedRisk = nested(outcome?.approvedRisk);
1240
- const terminalWindows = array(
1241
- nested(artifact.research)?.terminalWindows
1242
- ).map(nested).filter((entry) => entry != null);
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);
1243
1292
  const hasLongShortDirections = (value) => {
1244
1293
  const directions = new Map(
1245
1294
  array(value).map(nested).filter(
@@ -1253,6 +1302,22 @@ async function collectReleaseEvidenceReferences(references) {
1253
1302
  const directionalEvidenceComplete = hasLongShortDirections(artifact.byDirection) && terminalWindows.every(
1254
1303
  (window) => hasLongShortDirections(window.byDirection)
1255
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
+ ));
1256
1321
  const terminalsPass = terminalWindows.length > 0 && terminalWindows.every((window) => {
1257
1322
  const terminalOutcome = nested(window.outcome);
1258
1323
  return window.complete === true && Number(terminalOutcome?.approved ?? 0) > 0 && Number(
@@ -1260,7 +1325,7 @@ async function collectReleaseEvidenceReferences(references) {
1260
1325
  ) > 0;
1261
1326
  });
1262
1327
  return {
1263
- 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
1264
1329
  };
1265
1330
  }
1266
1331
  if (reference.kind === "runtime_parity") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "2.0.20",
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": "^2.0.20",
33
- "@tradejs/connectors": "^2.0.20",
34
- "@tradejs/core": "^2.0.20",
35
- "@tradejs/indicators": "^2.0.20",
36
- "@tradejs/infra": "^2.0.20",
37
- "@tradejs/node": "^2.0.20",
38
- "@tradejs/strategies": "^2.0.20",
39
- "@tradejs/types": "^2.0.20",
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",