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