@tradejs/cli 2.0.19 → 2.0.21
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 +8783 -3214
- package/dist/lib/aiExport.js +14 -1
- package/dist/lib/aiPocketSearch.js +499 -67
- package/dist/lib/aiPocketSearchCli.js +113 -0
- package/dist/lib/aiTrainCharts.js +14 -1
- package/dist/lib/aiTrainMetrics.js +4 -1
- package/dist/lib/aiTrainResearch.js +88 -54
- package/dist/lib/coreResearch.js +2738 -0
- package/dist/lib/executionCalibration.js +6 -3
- package/dist/lib/runtimeLineage.js +69 -48
- package/dist/lib/runtimeScorecard.js +153 -10
- package/dist/lib/strategyGateFingerprint.js +80 -0
- package/dist/lib/strategyRelease.js +1373 -0
- package/dist/scripts/aiExport.js +16 -3
- package/dist/scripts/aiPocketSearch.js +1069 -163
- package/dist/scripts/aiTrain.js +91 -65
- package/dist/scripts/backtest.js +10 -1
- package/dist/scripts/coreResearch.js +2830 -0
- package/dist/scripts/executionCalibration.js +6 -3
- package/dist/scripts/replay.js +94 -73
- package/dist/scripts/replayRunner.js +94 -73
- package/dist/scripts/replayRuntimeEvidence.js +27 -14
- package/dist/scripts/runtimeEvidence.js +17 -8
- package/dist/scripts/runtimeScorecard.js +883 -29
- package/dist/scripts/signals.js +121 -89
- package/dist/scripts/signalsDaemon.js +121 -89
- package/dist/scripts/strategyRelease.js +1477 -0
- package/package.json +11 -9
package/dist/scripts/signals.js
CHANGED
|
@@ -2117,27 +2117,27 @@ var createCoinMarketCapFetchStats = () => ({
|
|
|
2117
2117
|
totalCredits: 0,
|
|
2118
2118
|
byPath: /* @__PURE__ */ new Map()
|
|
2119
2119
|
});
|
|
2120
|
-
var getCoinMarketCapPathLabel = (
|
|
2121
|
-
if (
|
|
2122
|
-
if (
|
|
2123
|
-
if (
|
|
2124
|
-
if (
|
|
2125
|
-
if (
|
|
2126
|
-
if (
|
|
2127
|
-
return
|
|
2128
|
-
};
|
|
2129
|
-
var recordCoinMarketCapFetchStats = (stats,
|
|
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(
|
|
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(
|
|
2136
|
+
stats.byPath.set(path3, existing);
|
|
2137
2137
|
};
|
|
2138
2138
|
var formatCoinMarketCapFetchStats = (stats) => {
|
|
2139
2139
|
const byPath = [...stats.byPath.entries()].map(
|
|
2140
|
-
([
|
|
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,57 @@ 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
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
4007
|
+
|
|
4008
|
+
// src/lib/strategyGateFingerprint.ts
|
|
4000
4009
|
var import_node_crypto = require("crypto");
|
|
4001
4010
|
var import_promises = __toESM(require("fs/promises"));
|
|
4002
4011
|
var import_node_path = __toESM(require("path"));
|
|
4003
|
-
var
|
|
4004
|
-
|
|
4012
|
+
var gateFingerprintRelativePaths = (strategyName) => [
|
|
4013
|
+
`packages/strategies/src/${strategyName}/adapters/ai.ts`,
|
|
4014
|
+
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
4015
|
+
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
4016
|
+
`packages/strategies/src/${strategyName}/config.ts`,
|
|
4017
|
+
"packages/strategies/src/shared/localAiGate.ts",
|
|
4018
|
+
"packages/node/src/ai.ts",
|
|
4019
|
+
"packages/core/src/utils/strategyHelpers/signalBuilders.ts"
|
|
4020
|
+
];
|
|
4021
|
+
var resolveStrategyGateFingerprint = async ({
|
|
4022
|
+
projectRoot,
|
|
4023
|
+
strategyName,
|
|
4024
|
+
gitSha
|
|
4025
|
+
}) => {
|
|
4026
|
+
const candidates = await Promise.all(
|
|
4027
|
+
gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
|
|
4028
|
+
const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
|
|
4029
|
+
return content == null ? null : { relativePath, content };
|
|
4030
|
+
})
|
|
4031
|
+
);
|
|
4032
|
+
const sourceEntries = candidates.filter(
|
|
4033
|
+
(entry) => entry != null
|
|
4034
|
+
);
|
|
4035
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
4036
|
+
hash.update(strategyName);
|
|
4037
|
+
if (sourceEntries.length) {
|
|
4038
|
+
for (const entry of sourceEntries) {
|
|
4039
|
+
hash.update(entry.relativePath);
|
|
4040
|
+
hash.update(entry.content);
|
|
4041
|
+
}
|
|
4042
|
+
} else {
|
|
4043
|
+
hash.update(gitSha ?? "unknown-git-sha");
|
|
4044
|
+
}
|
|
4045
|
+
return {
|
|
4046
|
+
gateFingerprint: hash.digest("hex").slice(0, 16),
|
|
4047
|
+
gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
|
|
4048
|
+
};
|
|
4049
|
+
};
|
|
4050
|
+
|
|
4051
|
+
// src/lib/runtimeLineage.ts
|
|
4005
4052
|
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
4006
4053
|
"AI_MODE",
|
|
4007
4054
|
"MIN_AI_QUALITY",
|
|
@@ -4064,7 +4111,7 @@ var resolveRuntimeMaxLossValue = (config2) => {
|
|
|
4064
4111
|
);
|
|
4065
4112
|
return Number.isFinite(maxLossValue) ? maxLossValue : null;
|
|
4066
4113
|
};
|
|
4067
|
-
var fingerprintRuntimeValue = (value) => (0,
|
|
4114
|
+
var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
4068
4115
|
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
4069
4116
|
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
4070
4117
|
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
@@ -4115,7 +4162,7 @@ var resolveGitLineage = (projectRoot) => {
|
|
|
4115
4162
|
};
|
|
4116
4163
|
var readOptionalFile = async (filePath) => {
|
|
4117
4164
|
try {
|
|
4118
|
-
return await
|
|
4165
|
+
return await import_promises2.default.readFile(filePath);
|
|
4119
4166
|
} catch {
|
|
4120
4167
|
return null;
|
|
4121
4168
|
}
|
|
@@ -4125,7 +4172,7 @@ var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
|
4125
4172
|
return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
|
|
4126
4173
|
}
|
|
4127
4174
|
const content = await readOptionalFile(
|
|
4128
|
-
|
|
4175
|
+
import_node_path2.default.join(
|
|
4129
4176
|
projectRoot,
|
|
4130
4177
|
"packages/node/src/config/binanceBreadthUniverses.json"
|
|
4131
4178
|
)
|
|
@@ -4147,7 +4194,7 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
4147
4194
|
if (snapshotFingerprintCache.has(cacheKey)) {
|
|
4148
4195
|
return snapshotFingerprintCache.get(cacheKey) ?? null;
|
|
4149
4196
|
}
|
|
4150
|
-
const content = await readOptionalFile(
|
|
4197
|
+
const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
|
|
4151
4198
|
let result = null;
|
|
4152
4199
|
if (content) {
|
|
4153
4200
|
try {
|
|
@@ -4160,55 +4207,26 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
4160
4207
|
snapshotFingerprintCache.set(cacheKey, result);
|
|
4161
4208
|
return result;
|
|
4162
4209
|
};
|
|
4163
|
-
var
|
|
4210
|
+
var resolveRuntimeGateFingerprint = async ({
|
|
4164
4211
|
projectRoot,
|
|
4165
4212
|
strategyName,
|
|
4166
4213
|
gitSha
|
|
4167
4214
|
}) => {
|
|
4168
4215
|
const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
|
|
4169
4216
|
const cached = gateFingerprintCache.get(cacheKey);
|
|
4170
|
-
if (cached)
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
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;
|
|
4217
|
+
if (cached) return cached;
|
|
4218
|
+
const result = await resolveStrategyGateFingerprint({
|
|
4219
|
+
projectRoot,
|
|
4220
|
+
strategyName,
|
|
4221
|
+
gitSha
|
|
4222
|
+
});
|
|
4223
|
+
gateFingerprintCache.set(cacheKey, result);
|
|
4224
|
+
return result;
|
|
4208
4225
|
};
|
|
4209
4226
|
var buildRuntimeLineage = async ({
|
|
4210
4227
|
projectRoot,
|
|
4211
4228
|
strategyName,
|
|
4229
|
+
compositionId,
|
|
4212
4230
|
config: config2,
|
|
4213
4231
|
runContext,
|
|
4214
4232
|
env = process.env
|
|
@@ -4234,19 +4252,23 @@ var buildRuntimeLineage = async ({
|
|
|
4234
4252
|
};
|
|
4235
4253
|
return {
|
|
4236
4254
|
schemaVersion: 1,
|
|
4255
|
+
compositionId: compositionId?.trim() || null,
|
|
4237
4256
|
...git,
|
|
4238
|
-
gateFingerprint: await
|
|
4257
|
+
gateFingerprint: (await resolveRuntimeGateFingerprint({
|
|
4239
4258
|
projectRoot,
|
|
4240
4259
|
strategyName,
|
|
4241
4260
|
gitSha: git.gitSha
|
|
4242
|
-
}),
|
|
4243
|
-
|
|
4261
|
+
})).gateFingerprint,
|
|
4262
|
+
// Position risk is tracked separately so changing trade size does not
|
|
4263
|
+
// masquerade as a change to the core + gate decision logic.
|
|
4264
|
+
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config2),
|
|
4244
4265
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
4245
4266
|
maxLossValue: resolveRuntimeMaxLossValue(config2)
|
|
4246
4267
|
};
|
|
4247
4268
|
};
|
|
4248
4269
|
var runtimeLineageKey = (lineage) => [
|
|
4249
4270
|
lineage.schemaVersion,
|
|
4271
|
+
lineage.compositionId ?? "unbound-composition",
|
|
4250
4272
|
lineage.gitSha ?? "unknown",
|
|
4251
4273
|
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
4252
4274
|
lineage.gateFingerprint,
|
|
@@ -4383,6 +4405,39 @@ var flushRuntimeSignalEvaluations = async (evaluations) => {
|
|
|
4383
4405
|
var createRuntimeSignalEvaluationBuffer = () => {
|
|
4384
4406
|
const evaluations = [];
|
|
4385
4407
|
const signals2 = [];
|
|
4408
|
+
const flushSignals = async () => {
|
|
4409
|
+
const pendingSignals = signals2.splice(0);
|
|
4410
|
+
if (pendingSignals.length === 0) return;
|
|
4411
|
+
const runtimeSignalRetentionTtl = getRuntimeSignalRetentionTtlSeconds();
|
|
4412
|
+
const signalRefsByBucket = /* @__PURE__ */ new Map();
|
|
4413
|
+
for (const { userName, signal } of pendingSignals) {
|
|
4414
|
+
const bucket = import_redis3.redisKeys.runtimeSignalBucket(
|
|
4415
|
+
userName,
|
|
4416
|
+
(0, import_time2.getRuntimeStorageDayKey)(signal.timestamp),
|
|
4417
|
+
signal.strategy
|
|
4418
|
+
);
|
|
4419
|
+
const entries = signalRefsByBucket.get(bucket) ?? [];
|
|
4420
|
+
entries.push({
|
|
4421
|
+
field: signal.signalId,
|
|
4422
|
+
data: toRuntimeSignalBucketRef(signal)
|
|
4423
|
+
});
|
|
4424
|
+
signalRefsByBucket.set(bucket, entries);
|
|
4425
|
+
}
|
|
4426
|
+
await Promise.all([
|
|
4427
|
+
...pendingSignals.map(
|
|
4428
|
+
({ signal }) => (0, import_redis3.setData)(
|
|
4429
|
+
import_redis3.redisKeys.storeSignal(signal.symbol, signal.signalId),
|
|
4430
|
+
toStoredRuntimeSignal(signal),
|
|
4431
|
+
{ expire: runtimeSignalRetentionTtl }
|
|
4432
|
+
)
|
|
4433
|
+
),
|
|
4434
|
+
...[...signalRefsByBucket].map(
|
|
4435
|
+
([bucket, entries]) => (0, import_redis3.setHashJsonFields)(bucket, entries, {
|
|
4436
|
+
expire: runtimeSignalRetentionTtl
|
|
4437
|
+
})
|
|
4438
|
+
)
|
|
4439
|
+
]);
|
|
4440
|
+
};
|
|
4386
4441
|
return {
|
|
4387
4442
|
save: async (evaluation) => {
|
|
4388
4443
|
evaluations.push(evaluation);
|
|
@@ -4390,38 +4445,12 @@ var createRuntimeSignalEvaluationBuffer = () => {
|
|
|
4390
4445
|
saveSignal: (userName, signal) => {
|
|
4391
4446
|
signals2.push({ userName, signal });
|
|
4392
4447
|
},
|
|
4448
|
+
flushSignals,
|
|
4393
4449
|
flush: async () => {
|
|
4394
4450
|
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
4451
|
await Promise.all([
|
|
4412
4452
|
flushRuntimeSignalEvaluations(pending),
|
|
4413
|
-
|
|
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
|
-
)
|
|
4453
|
+
flushSignals()
|
|
4425
4454
|
]);
|
|
4426
4455
|
}
|
|
4427
4456
|
};
|
|
@@ -4848,6 +4877,7 @@ var createSignalsTickerEvaluator = ({
|
|
|
4848
4877
|
strategyName,
|
|
4849
4878
|
configId,
|
|
4850
4879
|
strategyCreator,
|
|
4880
|
+
releaseCompositionId,
|
|
4851
4881
|
sourceStrategyConfig,
|
|
4852
4882
|
strategyConfig,
|
|
4853
4883
|
strategyResults
|
|
@@ -4862,6 +4892,7 @@ var createSignalsTickerEvaluator = ({
|
|
|
4862
4892
|
const runtimeLineage = await buildRuntimeLineage({
|
|
4863
4893
|
projectRoot,
|
|
4864
4894
|
strategyName,
|
|
4895
|
+
compositionId: releaseCompositionId,
|
|
4865
4896
|
config: { configId, strategyConfig, symbolResultConfig },
|
|
4866
4897
|
runContext: {
|
|
4867
4898
|
connectorName: connectorName.toLowerCase(),
|
|
@@ -5441,6 +5472,7 @@ var createSignalsRunner = (config2) => {
|
|
|
5441
5472
|
if (config2.notify) {
|
|
5442
5473
|
const telegramSignals = getTelegramDeliverableSignals(signals3);
|
|
5443
5474
|
if (!config2.skipScreenshots && telegramSignals.length > 0) {
|
|
5475
|
+
await runtimeSignalEvaluations.flushSignals();
|
|
5444
5476
|
await (0, import_cli2.makeScreenshots)(telegramSignals, interval2, config2.userName);
|
|
5445
5477
|
}
|
|
5446
5478
|
await (0, import_cli2.sendToTG)(telegramSignals, interval2, config2.userName);
|
|
@@ -2115,27 +2115,27 @@ var createCoinMarketCapFetchStats = () => ({
|
|
|
2115
2115
|
totalCredits: 0,
|
|
2116
2116
|
byPath: /* @__PURE__ */ new Map()
|
|
2117
2117
|
});
|
|
2118
|
-
var getCoinMarketCapPathLabel = (
|
|
2119
|
-
if (
|
|
2120
|
-
if (
|
|
2121
|
-
if (
|
|
2122
|
-
if (
|
|
2123
|
-
if (
|
|
2124
|
-
if (
|
|
2125
|
-
return
|
|
2126
|
-
};
|
|
2127
|
-
var recordCoinMarketCapFetchStats = (stats,
|
|
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(
|
|
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(
|
|
2134
|
+
stats.byPath.set(path3, existing);
|
|
2135
2135
|
};
|
|
2136
2136
|
var formatCoinMarketCapFetchStats = (stats) => {
|
|
2137
2137
|
const byPath = [...stats.byPath.entries()].map(
|
|
2138
|
-
([
|
|
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,57 @@ 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
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
4005
|
+
|
|
4006
|
+
// src/lib/strategyGateFingerprint.ts
|
|
3998
4007
|
var import_node_crypto = require("crypto");
|
|
3999
4008
|
var import_promises = __toESM(require("fs/promises"));
|
|
4000
4009
|
var import_node_path = __toESM(require("path"));
|
|
4001
|
-
var
|
|
4002
|
-
|
|
4010
|
+
var gateFingerprintRelativePaths = (strategyName) => [
|
|
4011
|
+
`packages/strategies/src/${strategyName}/adapters/ai.ts`,
|
|
4012
|
+
`packages/strategies/src/${strategyName}/guardrails.ts`,
|
|
4013
|
+
`packages/strategies/src/${strategyName}/pockets.ts`,
|
|
4014
|
+
`packages/strategies/src/${strategyName}/config.ts`,
|
|
4015
|
+
"packages/strategies/src/shared/localAiGate.ts",
|
|
4016
|
+
"packages/node/src/ai.ts",
|
|
4017
|
+
"packages/core/src/utils/strategyHelpers/signalBuilders.ts"
|
|
4018
|
+
];
|
|
4019
|
+
var resolveStrategyGateFingerprint = async ({
|
|
4020
|
+
projectRoot,
|
|
4021
|
+
strategyName,
|
|
4022
|
+
gitSha
|
|
4023
|
+
}) => {
|
|
4024
|
+
const candidates = await Promise.all(
|
|
4025
|
+
gateFingerprintRelativePaths(strategyName).map(async (relativePath) => {
|
|
4026
|
+
const content = await import_promises.default.readFile(import_node_path.default.join(projectRoot, relativePath)).catch(() => null);
|
|
4027
|
+
return content == null ? null : { relativePath, content };
|
|
4028
|
+
})
|
|
4029
|
+
);
|
|
4030
|
+
const sourceEntries = candidates.filter(
|
|
4031
|
+
(entry) => entry != null
|
|
4032
|
+
);
|
|
4033
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
4034
|
+
hash.update(strategyName);
|
|
4035
|
+
if (sourceEntries.length) {
|
|
4036
|
+
for (const entry of sourceEntries) {
|
|
4037
|
+
hash.update(entry.relativePath);
|
|
4038
|
+
hash.update(entry.content);
|
|
4039
|
+
}
|
|
4040
|
+
} else {
|
|
4041
|
+
hash.update(gitSha ?? "unknown-git-sha");
|
|
4042
|
+
}
|
|
4043
|
+
return {
|
|
4044
|
+
gateFingerprint: hash.digest("hex").slice(0, 16),
|
|
4045
|
+
gateFingerprintFiles: sourceEntries.map((entry) => entry.relativePath)
|
|
4046
|
+
};
|
|
4047
|
+
};
|
|
4048
|
+
|
|
4049
|
+
// src/lib/runtimeLineage.ts
|
|
4003
4050
|
var RUNTIME_CONTEXT_ENV_KEYS = [
|
|
4004
4051
|
"AI_MODE",
|
|
4005
4052
|
"MIN_AI_QUALITY",
|
|
@@ -4062,7 +4109,7 @@ var resolveRuntimeMaxLossValue = (config2) => {
|
|
|
4062
4109
|
);
|
|
4063
4110
|
return Number.isFinite(maxLossValue) ? maxLossValue : null;
|
|
4064
4111
|
};
|
|
4065
|
-
var fingerprintRuntimeValue = (value) => (0,
|
|
4112
|
+
var fingerprintRuntimeValue = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
4066
4113
|
var gitLineageCache = /* @__PURE__ */ new Map();
|
|
4067
4114
|
var gateFingerprintCache = /* @__PURE__ */ new Map();
|
|
4068
4115
|
var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
|
|
@@ -4113,7 +4160,7 @@ var resolveGitLineage = (projectRoot) => {
|
|
|
4113
4160
|
};
|
|
4114
4161
|
var readOptionalFile = async (filePath) => {
|
|
4115
4162
|
try {
|
|
4116
|
-
return await
|
|
4163
|
+
return await import_promises2.default.readFile(filePath);
|
|
4117
4164
|
} catch {
|
|
4118
4165
|
return null;
|
|
4119
4166
|
}
|
|
@@ -4123,7 +4170,7 @@ var resolveBinanceBreadthFingerprint = async (projectRoot) => {
|
|
|
4123
4170
|
return binanceBreadthFingerprintCache.get(projectRoot) ?? null;
|
|
4124
4171
|
}
|
|
4125
4172
|
const content = await readOptionalFile(
|
|
4126
|
-
|
|
4173
|
+
import_node_path2.default.join(
|
|
4127
4174
|
projectRoot,
|
|
4128
4175
|
"packages/node/src/config/binanceBreadthUniverses.json"
|
|
4129
4176
|
)
|
|
@@ -4145,7 +4192,7 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
4145
4192
|
if (snapshotFingerprintCache.has(cacheKey)) {
|
|
4146
4193
|
return snapshotFingerprintCache.get(cacheKey) ?? null;
|
|
4147
4194
|
}
|
|
4148
|
-
const content = await readOptionalFile(
|
|
4195
|
+
const content = await readOptionalFile(import_node_path2.default.join(projectRoot, relativePath));
|
|
4149
4196
|
let result = null;
|
|
4150
4197
|
if (content) {
|
|
4151
4198
|
try {
|
|
@@ -4158,55 +4205,26 @@ var resolveSnapshotFingerprint = async (projectRoot, relativePath) => {
|
|
|
4158
4205
|
snapshotFingerprintCache.set(cacheKey, result);
|
|
4159
4206
|
return result;
|
|
4160
4207
|
};
|
|
4161
|
-
var
|
|
4208
|
+
var resolveRuntimeGateFingerprint = async ({
|
|
4162
4209
|
projectRoot,
|
|
4163
4210
|
strategyName,
|
|
4164
4211
|
gitSha
|
|
4165
4212
|
}) => {
|
|
4166
4213
|
const cacheKey = `${projectRoot}:${strategyName}:${gitSha ?? "unknown"}`;
|
|
4167
4214
|
const cached = gateFingerprintCache.get(cacheKey);
|
|
4168
|
-
if (cached)
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
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;
|
|
4215
|
+
if (cached) return cached;
|
|
4216
|
+
const result = await resolveStrategyGateFingerprint({
|
|
4217
|
+
projectRoot,
|
|
4218
|
+
strategyName,
|
|
4219
|
+
gitSha
|
|
4220
|
+
});
|
|
4221
|
+
gateFingerprintCache.set(cacheKey, result);
|
|
4222
|
+
return result;
|
|
4206
4223
|
};
|
|
4207
4224
|
var buildRuntimeLineage = async ({
|
|
4208
4225
|
projectRoot,
|
|
4209
4226
|
strategyName,
|
|
4227
|
+
compositionId,
|
|
4210
4228
|
config: config2,
|
|
4211
4229
|
runContext,
|
|
4212
4230
|
env = process.env
|
|
@@ -4232,19 +4250,23 @@ var buildRuntimeLineage = async ({
|
|
|
4232
4250
|
};
|
|
4233
4251
|
return {
|
|
4234
4252
|
schemaVersion: 1,
|
|
4253
|
+
compositionId: compositionId?.trim() || null,
|
|
4235
4254
|
...git,
|
|
4236
|
-
gateFingerprint: await
|
|
4255
|
+
gateFingerprint: (await resolveRuntimeGateFingerprint({
|
|
4237
4256
|
projectRoot,
|
|
4238
4257
|
strategyName,
|
|
4239
4258
|
gitSha: git.gitSha
|
|
4240
|
-
}),
|
|
4241
|
-
|
|
4259
|
+
})).gateFingerprint,
|
|
4260
|
+
// Position risk is tracked separately so changing trade size does not
|
|
4261
|
+
// masquerade as a change to the core + gate decision logic.
|
|
4262
|
+
configFingerprint: (0, import_strategyReleaseEvidence.strategyLogicConfigFingerprint)(config2),
|
|
4242
4263
|
contextFingerprint: fingerprintRuntimeValue(context),
|
|
4243
4264
|
maxLossValue: resolveRuntimeMaxLossValue(config2)
|
|
4244
4265
|
};
|
|
4245
4266
|
};
|
|
4246
4267
|
var runtimeLineageKey = (lineage) => [
|
|
4247
4268
|
lineage.schemaVersion,
|
|
4269
|
+
lineage.compositionId ?? "unbound-composition",
|
|
4248
4270
|
lineage.gitSha ?? "unknown",
|
|
4249
4271
|
lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
|
|
4250
4272
|
lineage.gateFingerprint,
|
|
@@ -4381,6 +4403,39 @@ var flushRuntimeSignalEvaluations = async (evaluations) => {
|
|
|
4381
4403
|
var createRuntimeSignalEvaluationBuffer = () => {
|
|
4382
4404
|
const evaluations = [];
|
|
4383
4405
|
const signals2 = [];
|
|
4406
|
+
const flushSignals = async () => {
|
|
4407
|
+
const pendingSignals = signals2.splice(0);
|
|
4408
|
+
if (pendingSignals.length === 0) return;
|
|
4409
|
+
const runtimeSignalRetentionTtl = getRuntimeSignalRetentionTtlSeconds();
|
|
4410
|
+
const signalRefsByBucket = /* @__PURE__ */ new Map();
|
|
4411
|
+
for (const { userName, signal } of pendingSignals) {
|
|
4412
|
+
const bucket = import_redis3.redisKeys.runtimeSignalBucket(
|
|
4413
|
+
userName,
|
|
4414
|
+
(0, import_time2.getRuntimeStorageDayKey)(signal.timestamp),
|
|
4415
|
+
signal.strategy
|
|
4416
|
+
);
|
|
4417
|
+
const entries = signalRefsByBucket.get(bucket) ?? [];
|
|
4418
|
+
entries.push({
|
|
4419
|
+
field: signal.signalId,
|
|
4420
|
+
data: toRuntimeSignalBucketRef(signal)
|
|
4421
|
+
});
|
|
4422
|
+
signalRefsByBucket.set(bucket, entries);
|
|
4423
|
+
}
|
|
4424
|
+
await Promise.all([
|
|
4425
|
+
...pendingSignals.map(
|
|
4426
|
+
({ signal }) => (0, import_redis3.setData)(
|
|
4427
|
+
import_redis3.redisKeys.storeSignal(signal.symbol, signal.signalId),
|
|
4428
|
+
toStoredRuntimeSignal(signal),
|
|
4429
|
+
{ expire: runtimeSignalRetentionTtl }
|
|
4430
|
+
)
|
|
4431
|
+
),
|
|
4432
|
+
...[...signalRefsByBucket].map(
|
|
4433
|
+
([bucket, entries]) => (0, import_redis3.setHashJsonFields)(bucket, entries, {
|
|
4434
|
+
expire: runtimeSignalRetentionTtl
|
|
4435
|
+
})
|
|
4436
|
+
)
|
|
4437
|
+
]);
|
|
4438
|
+
};
|
|
4384
4439
|
return {
|
|
4385
4440
|
save: async (evaluation) => {
|
|
4386
4441
|
evaluations.push(evaluation);
|
|
@@ -4388,38 +4443,12 @@ var createRuntimeSignalEvaluationBuffer = () => {
|
|
|
4388
4443
|
saveSignal: (userName, signal) => {
|
|
4389
4444
|
signals2.push({ userName, signal });
|
|
4390
4445
|
},
|
|
4446
|
+
flushSignals,
|
|
4391
4447
|
flush: async () => {
|
|
4392
4448
|
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
4449
|
await Promise.all([
|
|
4410
4450
|
flushRuntimeSignalEvaluations(pending),
|
|
4411
|
-
|
|
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
|
-
)
|
|
4451
|
+
flushSignals()
|
|
4423
4452
|
]);
|
|
4424
4453
|
}
|
|
4425
4454
|
};
|
|
@@ -4846,6 +4875,7 @@ var createSignalsTickerEvaluator = ({
|
|
|
4846
4875
|
strategyName,
|
|
4847
4876
|
configId,
|
|
4848
4877
|
strategyCreator,
|
|
4878
|
+
releaseCompositionId,
|
|
4849
4879
|
sourceStrategyConfig,
|
|
4850
4880
|
strategyConfig,
|
|
4851
4881
|
strategyResults
|
|
@@ -4860,6 +4890,7 @@ var createSignalsTickerEvaluator = ({
|
|
|
4860
4890
|
const runtimeLineage = await buildRuntimeLineage({
|
|
4861
4891
|
projectRoot,
|
|
4862
4892
|
strategyName,
|
|
4893
|
+
compositionId: releaseCompositionId,
|
|
4863
4894
|
config: { configId, strategyConfig, symbolResultConfig },
|
|
4864
4895
|
runContext: {
|
|
4865
4896
|
connectorName: connectorName.toLowerCase(),
|
|
@@ -5439,6 +5470,7 @@ var createSignalsRunner = (config2) => {
|
|
|
5439
5470
|
if (config2.notify) {
|
|
5440
5471
|
const telegramSignals = getTelegramDeliverableSignals(signals3);
|
|
5441
5472
|
if (!config2.skipScreenshots && telegramSignals.length > 0) {
|
|
5473
|
+
await runtimeSignalEvaluations.flushSignals();
|
|
5442
5474
|
await (0, import_cli2.makeScreenshots)(telegramSignals, interval2, config2.userName);
|
|
5443
5475
|
}
|
|
5444
5476
|
await (0, import_cli2.sendToTG)(telegramSignals, interval2, config2.userName);
|