@tradejs/cli 2.0.19 → 2.0.20

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.
@@ -2117,27 +2117,27 @@ var createCoinMarketCapFetchStats = () => ({
2117
2117
  totalCredits: 0,
2118
2118
  byPath: /* @__PURE__ */ new Map()
2119
2119
  });
2120
- var getCoinMarketCapPathLabel = (path2) => {
2121
- if (path2.includes("global-metrics")) return "global";
2122
- if (path2.includes("cryptocurrency/quotes")) return "reference";
2123
- if (path2.includes("exchange/quotes")) return "exchangeLiquidity";
2124
- if (path2.includes("fear-and-greed")) return "fearGreed";
2125
- if (path2.includes("cmc100")) return "cmc100";
2126
- if (path2.includes("cmc20")) return "cmc20";
2127
- return path2;
2128
- };
2129
- var recordCoinMarketCapFetchStats = (stats, path2, creditCount) => {
2120
+ var getCoinMarketCapPathLabel = (path3) => {
2121
+ if (path3.includes("global-metrics")) return "global";
2122
+ if (path3.includes("cryptocurrency/quotes")) return "reference";
2123
+ if (path3.includes("exchange/quotes")) return "exchangeLiquidity";
2124
+ if (path3.includes("fear-and-greed")) return "fearGreed";
2125
+ if (path3.includes("cmc100")) return "cmc100";
2126
+ if (path3.includes("cmc20")) return "cmc20";
2127
+ return path3;
2128
+ };
2129
+ var recordCoinMarketCapFetchStats = (stats, path3, creditCount) => {
2130
2130
  if (!stats) return;
2131
- const existing = stats.byPath.get(path2) ?? { requests: 0, credits: 0 };
2131
+ const existing = stats.byPath.get(path3) ?? { requests: 0, credits: 0 };
2132
2132
  existing.requests += 1;
2133
2133
  existing.credits += creditCount ?? 0;
2134
2134
  stats.requests += 1;
2135
2135
  stats.totalCredits += creditCount ?? 0;
2136
- stats.byPath.set(path2, existing);
2136
+ stats.byPath.set(path3, existing);
2137
2137
  };
2138
2138
  var formatCoinMarketCapFetchStats = (stats) => {
2139
2139
  const byPath = [...stats.byPath.entries()].map(
2140
- ([path2, item]) => `${getCoinMarketCapPathLabel(path2)}=${item.requests}req/${item.credits}cr`
2140
+ ([path3, item]) => `${getCoinMarketCapPathLabel(path3)}=${item.requests}req/${item.credits}cr`
2141
2141
  ).join(" ");
2142
2142
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
2143
2143
  };
@@ -3807,6 +3807,7 @@ var loadRuntimeStrategies = async ({
3807
3807
  interval: configInterval,
3808
3808
  universe: configUniverse,
3809
3809
  accountId: effectiveAccountId,
3810
+ releaseCompositionId: deploymentStrategy?.releaseCompositionId,
3810
3811
  strategyCreator,
3811
3812
  sourceStrategyConfig: strategyConfig,
3812
3813
  strategyConfig: {
@@ -3997,11 +3998,56 @@ var import_redis3 = require("@tradejs/infra/redis");
3997
3998
 
3998
3999
  // src/lib/runtimeLineage.ts
3999
4000
  var import_node_child_process = require("child_process");
4001
+ var import_node_crypto2 = require("crypto");
4002
+ var import_promises2 = __toESM(require("fs/promises"));
4003
+ var import_node_path2 = __toESM(require("path"));
4004
+ var import_types = require("@tradejs/types");
4005
+ var import_strategies4 = require("@tradejs/node/strategies");
4006
+
4007
+ // src/lib/strategyGateFingerprint.ts
4000
4008
  var import_node_crypto = require("crypto");
4001
4009
  var import_promises = __toESM(require("fs/promises"));
4002
4010
  var import_node_path = __toESM(require("path"));
4003
- var import_types = require("@tradejs/types");
4004
- var import_strategies4 = require("@tradejs/node/strategies");
4011
+ var gateFingerprintRelativePaths = (strategyName) => [
4012
+ `packages/strategies/src/${strategyName}/adapters/ai.ts`,
4013
+ `packages/strategies/src/${strategyName}/guardrails.ts`,
4014
+ `packages/strategies/src/${strategyName}/pockets.ts`,
4015
+ `packages/strategies/src/${strategyName}/config.ts`,
4016
+ "packages/strategies/src/shared/localAiGate.ts",
4017
+ "packages/node/src/ai.ts",
4018
+ "packages/core/src/utils/strategyHelpers/signalBuilders.ts"
4019
+ ];
4020
+ var resolveStrategyGateFingerprint = async ({
4021
+ projectRoot,
4022
+ strategyName,
4023
+ gitSha
4024
+ }) => {
4025
+ const candidates = await Promise.all(
4026
+ gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
4027
+ const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
4028
+ return content == null ? null : { relativePath, content };
4029
+ })
4030
+ );
4031
+ const sourceEntries = candidates.filter(
4032
+ (entry) => entry != null
4033
+ );
4034
+ const hash = (0, import_node_crypto.createHash)("sha256");
4035
+ hash.update(strategyName);
4036
+ if (sourceEntries.length) {
4037
+ for (const entry of sourceEntries) {
4038
+ hash.update(entry.relativePath);
4039
+ hash.update(entry.content);
4040
+ }
4041
+ } else {
4042
+ hash.update(gitSha ?? "unknown-git-sha");
4043
+ }
4044
+ return {
4045
+ gateFingerprint: hash.digest("hex").slice(0, 16),
4046
+ gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
4047
+ };
4048
+ };
4049
+
4050
+ // src/lib/runtimeLineage.ts
4005
4051
  var RUNTIME_CONTEXT_ENV_KEYS = [
4006
4052
  "AI_MODE",
4007
4053
  "MIN_AI_QUALITY",
@@ -4051,6 +4097,17 @@ var normalizeForStableJson = (value) => {
4051
4097
  }
4052
4098
  return value;
4053
4099
  };
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
+ };
4054
4111
  var resolveRuntimeMaxLossValue = (config2) => {
4055
4112
  if (!config2 || typeof config2 !== "object" || Array.isArray(config2)) {
4056
4113
  return null;
@@ -4064,7 +4121,7 @@ var resolveRuntimeMaxLossValue = (config2) => {
4064
4121
  );
4065
4122
  return Number.isFinite(maxLossValue) ? maxLossValue : null;
4066
4123
  };
4067
- var fingerprintRuntimeValue = (value) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
4124
+ var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
4068
4125
  var gitLineageCache = /* @__PURE__ */ new Map();
4069
4126
  var gateFingerprintCache = /* @__PURE__ */ new Map();
4070
4127
  var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
@@ -4115,7 +4172,7 @@ var resolveGitLineage = (projectRoot) => {
4115
4172
  };
4116
4173
  var readOptionalFile = async (filePath) => {
4117
4174
  try {
4118
- return await import_promises.default.readFile(filePath);
4175
+ return await import_promises2.default.readFile(filePath);
4119
4176
  } catch {
4120
4177
  return null;
4121
4178
  }
@@ -4125,7 +4182,7 @@ var resolveBinanceBreadthFingerprint = async (projectRoot) => {
4125
4182
  return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
4126
4183
  }
4127
4184
  const content = await readOptionalFile(
4128
- import_node_path.default.join(
4185
+ import_node_path2.default.join(
4129
4186
  projectRoot,
4130
4187
  "packages/node/src/config/binanceBreadthUniverses.json"
4131
4188
  )
@@ -4147,7 +4204,7 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
4147
4204
  if (snapshotFingerprintCache.has(cacheKey)) {
4148
4205
  return snapshotFingerprintCache.get(cacheKey) ?? null;
4149
4206
  }
4150
- const content = await readOptionalFile(import_node_path.default.join(projectRoot, relativePath));
4207
+ const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
4151
4208
  let result = null;
4152
4209
  if (content) {
4153
4210
  try {
@@ -4160,55 +4217,26 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
4160
4217
  snapshotFingerprintCache.set(cacheKey, result);
4161
4218
  return result;
4162
4219
  };
4163
- var resolveGateFingerprint = async ({
4220
+ var resolveRuntimeGateFingerprint = async ({
4164
4221
  projectRoot,
4165
4222
  strategyName,
4166
4223
  gitSha
4167
4224
  }) => {
4168
4225
  const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
4169
4226
  const cached = gateFingerprintCache.get(cacheKey);
4170
- if (cached) {
4171
- return cached;
4172
- }
4173
- const relativeCandidates = [
4174
- `packages/strategies/src/${strategyName}/adapters/ai.ts`,
4175
- `packages/strategies/src/${strategyName}/guardrails.ts`,
4176
- `packages/strategies/src/${strategyName}/pockets.ts`,
4177
- `packages/strategies/src/${strategyName}/config.ts`,
4178
- "packages/node/src/ai.ts",
4179
- "packages/core/src/utils/strategyHelpers/signalBuilders.ts"
4180
- ];
4181
- const optionalEntries = await Promise.all(
4182
- relativeCandidates.map(async (relativePath) => {
4183
- const content = await readOptionalFile(
4184
- import_node_path.default.join(projectRoot, relativePath)
4185
- );
4186
- return content == null ? null : { relativePath, content };
4187
- })
4188
- );
4189
- const sourceEntries = [];
4190
- for (const entry of optionalEntries) {
4191
- if (entry != null) {
4192
- sourceEntries.push(entry);
4193
- }
4194
- }
4195
- const hash = (0, import_node_crypto.createHash)("sha256");
4196
- hash.update(strategyName);
4197
- if (sourceEntries.length) {
4198
- for (const entry of sourceEntries) {
4199
- hash.update(entry.relativePath);
4200
- hash.update(entry.content);
4201
- }
4202
- } else {
4203
- hash.update(gitSha ?? "unknown-git-sha");
4204
- }
4205
- const fingerprint = hash.digest("hex").slice(0, 16);
4206
- gateFingerprintCache.set(cacheKey, fingerprint);
4207
- return fingerprint;
4227
+ if (cached) return cached;
4228
+ const result = await resolveStrategyGateFingerprint({
4229
+ projectRoot,
4230
+ strategyName,
4231
+ gitSha
4232
+ });
4233
+ gateFingerprintCache.set(cacheKey, result);
4234
+ return result;
4208
4235
  };
4209
4236
  var buildRuntimeLineage = async ({
4210
4237
  projectRoot,
4211
4238
  strategyName,
4239
+ compositionId,
4212
4240
  config: config2,
4213
4241
  runContext,
4214
4242
  env = process.env
@@ -4234,19 +4262,25 @@ var buildRuntimeLineage = async ({
4234
4262
  };
4235
4263
  return {
4236
4264
  schemaVersion: 1,
4265
+ compositionId: compositionId?.trim() || null,
4237
4266
  ...git,
4238
- gateFingerprint: await resolveGateFingerprint({
4267
+ gateFingerprint: (await resolveRuntimeGateFingerprint({
4239
4268
  projectRoot,
4240
4269
  strategyName,
4241
4270
  gitSha: git.gitSha
4242
- }),
4243
- configFingerprint: fingerprintRuntimeValue(config2),
4271
+ })).gateFingerprint,
4272
+ // Position risk is tracked separately so changing trade size does not
4273
+ // masquerade as a change to the core + gate decision logic.
4274
+ configFingerprint: fingerprintRuntimeValue(
4275
+ normalizeRuntimeLogicConfig(config2)
4276
+ ),
4244
4277
  contextFingerprint: fingerprintRuntimeValue(context),
4245
4278
  maxLossValue: resolveRuntimeMaxLossValue(config2)
4246
4279
  };
4247
4280
  };
4248
4281
  var runtimeLineageKey = (lineage) => [
4249
4282
  lineage.schemaVersion,
4283
+ lineage.compositionId ?? "unbound-composition",
4250
4284
  lineage.gitSha ?? "unknown",
4251
4285
  lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
4252
4286
  lineage.gateFingerprint,
@@ -4383,6 +4417,39 @@ var flushRuntimeSignalEvaluations = async (evaluations) => {
4383
4417
  var createRuntimeSignalEvaluationBuffer = () => {
4384
4418
  const evaluations = [];
4385
4419
  const signals2 = [];
4420
+ const flushSignals = async () => {
4421
+ const pendingSignals = signals2.splice(0);
4422
+ if (pendingSignals.length === 0) return;
4423
+ const runtimeSignalRetentionTtl = getRuntimeSignalRetentionTtlSeconds();
4424
+ const signalRefsByBucket = /* @__PURE__ */ new Map();
4425
+ for (const { userName, signal } of pendingSignals) {
4426
+ const bucket = import_redis3.redisKeys.runtimeSignalBucket(
4427
+ userName,
4428
+ (0, import_time2.getRuntimeStorageDayKey)(signal.timestamp),
4429
+ signal.strategy
4430
+ );
4431
+ const entries = signalRefsByBucket.get(bucket) ?? [];
4432
+ entries.push({
4433
+ field: signal.signalId,
4434
+ data: toRuntimeSignalBucketRef(signal)
4435
+ });
4436
+ signalRefsByBucket.set(bucket, entries);
4437
+ }
4438
+ await Promise.all([
4439
+ ...pendingSignals.map(
4440
+ ({ signal }) => (0, import_redis3.setData)(
4441
+ import_redis3.redisKeys.storeSignal(signal.symbol, signal.signalId),
4442
+ toStoredRuntimeSignal(signal),
4443
+ { expire: runtimeSignalRetentionTtl }
4444
+ )
4445
+ ),
4446
+ ...[...signalRefsByBucket].map(
4447
+ ([bucket, entries]) => (0, import_redis3.setHashJsonFields)(bucket, entries, {
4448
+ expire: runtimeSignalRetentionTtl
4449
+ })
4450
+ )
4451
+ ]);
4452
+ };
4386
4453
  return {
4387
4454
  save: async (evaluation) => {
4388
4455
  evaluations.push(evaluation);
@@ -4390,38 +4457,12 @@ var createRuntimeSignalEvaluationBuffer = () => {
4390
4457
  saveSignal: (userName, signal) => {
4391
4458
  signals2.push({ userName, signal });
4392
4459
  },
4460
+ flushSignals,
4393
4461
  flush: async () => {
4394
4462
  const pending = evaluations.splice(0);
4395
- const pendingSignals = signals2.splice(0);
4396
- const runtimeSignalRetentionTtl = getRuntimeSignalRetentionTtlSeconds();
4397
- const signalRefsByBucket = /* @__PURE__ */ new Map();
4398
- for (const { userName, signal } of pendingSignals) {
4399
- const bucket = import_redis3.redisKeys.runtimeSignalBucket(
4400
- userName,
4401
- (0, import_time2.getRuntimeStorageDayKey)(signal.timestamp),
4402
- signal.strategy
4403
- );
4404
- const entries = signalRefsByBucket.get(bucket) ?? [];
4405
- entries.push({
4406
- field: signal.signalId,
4407
- data: toRuntimeSignalBucketRef(signal)
4408
- });
4409
- signalRefsByBucket.set(bucket, entries);
4410
- }
4411
4463
  await Promise.all([
4412
4464
  flushRuntimeSignalEvaluations(pending),
4413
- ...pendingSignals.map(
4414
- ({ signal }) => (0, import_redis3.setData)(
4415
- import_redis3.redisKeys.storeSignal(signal.symbol, signal.signalId),
4416
- toStoredRuntimeSignal(signal),
4417
- { expire: runtimeSignalRetentionTtl }
4418
- )
4419
- ),
4420
- ...[...signalRefsByBucket].map(
4421
- ([bucket, entries]) => (0, import_redis3.setHashJsonFields)(bucket, entries, {
4422
- expire: runtimeSignalRetentionTtl
4423
- })
4424
- )
4465
+ flushSignals()
4425
4466
  ]);
4426
4467
  }
4427
4468
  };
@@ -4848,6 +4889,7 @@ var createSignalsTickerEvaluator = ({
4848
4889
  strategyName,
4849
4890
  configId,
4850
4891
  strategyCreator,
4892
+ releaseCompositionId,
4851
4893
  sourceStrategyConfig,
4852
4894
  strategyConfig,
4853
4895
  strategyResults
@@ -4862,6 +4904,7 @@ var createSignalsTickerEvaluator = ({
4862
4904
  const runtimeLineage = await buildRuntimeLineage({
4863
4905
  projectRoot,
4864
4906
  strategyName,
4907
+ compositionId: releaseCompositionId,
4865
4908
  config: { configId, strategyConfig, symbolResultConfig },
4866
4909
  runContext: {
4867
4910
  connectorName: connectorName.toLowerCase(),
@@ -5441,6 +5484,7 @@ var createSignalsRunner = (config2) => {
5441
5484
  if (config2.notify) {
5442
5485
  const telegramSignals = getTelegramDeliverableSignals(signals3);
5443
5486
  if (!config2.skipScreenshots && telegramSignals.length > 0) {
5487
+ await runtimeSignalEvaluations.flushSignals();
5444
5488
  await (0, import_cli2.makeScreenshots)(telegramSignals, interval2, config2.userName);
5445
5489
  }
5446
5490
  await (0, import_cli2.sendToTG)(telegramSignals, interval2, config2.userName);