@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.
@@ -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
- exportMetrics: summarizeCoreResearchWindow({
2152
- trades: entry.trades,
2153
- label: "full",
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: "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.`
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 terminalWindows = array(
1249
- nested(artifact.research)?.terminalWindows
1250
- ).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);
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": "2.0.21",
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.21",
33
- "@tradejs/connectors": "^2.0.21",
34
- "@tradejs/core": "^2.0.21",
35
- "@tradejs/indicators": "^2.0.21",
36
- "@tradejs/infra": "^2.0.21",
37
- "@tradejs/node": "^2.0.21",
38
- "@tradejs/strategies": "^2.0.21",
39
- "@tradejs/types": "^2.0.21",
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",