@tradejs/node 3.1.8-beta.206 → 3.1.8-beta.210

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.
@@ -34,10 +34,8 @@ __export(runtimeDashboard_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(runtimeDashboard_exports);
36
36
  var import_time3 = require("@tradejs/core/time");
37
- var import_logger5 = require("@tradejs/infra/logger");
38
- var import_tradingAccounts2 = require("@tradejs/infra/tradingAccounts");
39
- var import_runtimeDeployments = require("@tradejs/infra/runtimeDeployments");
40
- var import_runtimeStrategyReleases = require("@tradejs/infra/runtimeStrategyReleases");
37
+ var import_logger6 = require("@tradejs/infra/logger");
38
+ var import_tradingAccounts3 = require("@tradejs/infra/tradingAccounts");
41
39
  var import_redis2 = require("@tradejs/infra/redis");
42
40
 
43
41
  // src/connectorsRegistry.ts
@@ -78,17 +76,19 @@ var normalizeConfig = (rawConfig) => {
78
76
  return {};
79
77
  }
80
78
  const config = rawConfig;
81
- const strategies = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
79
+ const strategies2 = Array.isArray(config.strategies) ? config.strategies.map((value) => String(value || "").trim()).filter(Boolean) : [];
82
80
  const indicators = Array.isArray(config.indicators) ? config.indicators.map((value) => String(value || "").trim()).filter(Boolean) : [];
83
81
  const connectors = Array.isArray(config.connectors) ? config.connectors.map((value) => String(value || "").trim()).filter(Boolean) : [];
84
82
  const hooks = (0, import_config.normalizeTradejsConfigHooks)(
85
83
  config.hooks
86
84
  );
85
+ const runtime = config.runtime && typeof config.runtime === "object" && !Array.isArray(config.runtime) ? config.runtime : void 0;
87
86
  return {
88
- strategies,
87
+ strategies: strategies2,
89
88
  indicators,
90
89
  connectors,
91
- ...hooks ? { hooks } : {}
90
+ ...hooks ? { hooks } : {},
91
+ ...runtime ? { runtime } : {}
92
92
  };
93
93
  };
94
94
  var getNodeCreateRequire = () => {
@@ -835,210 +835,6 @@ var DEFAULT_CONNECTOR_NAME = BUILTIN_CONNECTOR_NAMES.ByBit;
835
835
  // src/runtimeDashboard.ts
836
836
  var import_runtimeTrades3 = require("@tradejs/core/runtimeTrades");
837
837
 
838
- // src/strategyEvidenceTimeline.ts
839
- var import_promises = __toESM(require("fs/promises"));
840
- var import_node_path = __toESM(require("path"));
841
- var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
842
- var DEFAULT_MARKER_DIRECTORY = "data/strategy-release/markers";
843
- var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
844
- var isNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0;
845
- var strategyEvidenceTimelineSelectorKey = (selector) => [
846
- selector.strategy,
847
- selector.releaseVersion ?? "",
848
- selector.compositionId ?? "",
849
- selector.gitSha ?? "",
850
- selector.gateFingerprint ?? "",
851
- selector.configFingerprint ?? "",
852
- selector.contextFingerprint ?? "",
853
- selector.requireCompleteLineage ? "exact" : "partial"
854
- ].join(":");
855
- var discoverJsonFiles = async (rootDir) => {
856
- const files = [];
857
- const visit = async (directory, depth) => {
858
- if (depth > 12) return;
859
- let entries;
860
- try {
861
- entries = await import_promises.default.readdir(directory, { withFileTypes: true });
862
- } catch (error) {
863
- if (error.code === "ENOENT") return;
864
- throw error;
865
- }
866
- await Promise.all(
867
- entries.map(async (entry) => {
868
- if (entry.name.startsWith(".") || entry.name.includes(".tmp-")) return;
869
- const entryPath = import_node_path.default.join(directory, entry.name);
870
- if (entry.isDirectory()) {
871
- await visit(entryPath, depth + 1);
872
- } else if (entry.isFile() && entry.name.endsWith(".json")) {
873
- files.push(entryPath);
874
- }
875
- })
876
- );
877
- };
878
- await visit(rootDir, 0);
879
- return files.sort();
880
- };
881
- var inferMatchingStrategies = ({
882
- filePath,
883
- rootDir,
884
- parsed,
885
- strategies
886
- }) => {
887
- const payload = asRecord(asRecord(parsed)?.payload);
888
- const declaredStrategy = isNonEmptyString(payload?.strategy) ? payload.strategy : null;
889
- const directoryStrategy = import_node_path.default.relative(rootDir, filePath).split(import_node_path.default.sep)[0];
890
- return strategies.filter(
891
- (strategy) => strategy === declaredStrategy || directoryStrategy === (0, import_strategyReleaseEvidence.safeStrategyEvidenceSegment)(strategy)
892
- );
893
- };
894
- var missingTimeline = () => ({
895
- status: "missing",
896
- observedFrom: null,
897
- markers: []
898
- });
899
- var notAttachedTimeline = () => ({
900
- status: "not_attached",
901
- observedFrom: null,
902
- markers: []
903
- });
904
- var invalidTimeline = () => ({
905
- status: "invalid",
906
- observedFrom: null,
907
- markers: []
908
- });
909
- var loadStrategyEvidenceTimelines = async ({
910
- projectRoot,
911
- markerDir,
912
- selectors: requestedSelectors,
913
- startTime,
914
- endTime
915
- }) => {
916
- const selectors = [...requestedSelectors].filter((selector) => selector.strategy.trim().length > 0).sort(
917
- (left, right) => strategyEvidenceTimelineSelectorKey(left).localeCompare(
918
- strategyEvidenceTimelineSelectorKey(right)
919
- )
920
- );
921
- const strategies = [...new Set(selectors.map(({ strategy }) => strategy))];
922
- const timelines = new Map(
923
- selectors.map((selector) => [
924
- strategyEvidenceTimelineSelectorKey(selector),
925
- selector.releaseVersion ? notAttachedTimeline() : missingTimeline()
926
- ])
927
- );
928
- if (!selectors.length) return timelines;
929
- const configuredDir = markerDir?.trim() || DEFAULT_MARKER_DIRECTORY;
930
- const rootDir = import_node_path.default.isAbsolute(configuredDir) ? configuredDir : import_node_path.default.resolve(projectRoot, configuredDir);
931
- let files;
932
- try {
933
- files = await discoverJsonFiles(rootDir);
934
- } catch {
935
- for (const selector of selectors) {
936
- timelines.set(
937
- strategyEvidenceTimelineSelectorKey(selector),
938
- invalidTimeline()
939
- );
940
- }
941
- return timelines;
942
- }
943
- const envelopesByStrategy = /* @__PURE__ */ new Map();
944
- const invalidStrategies = /* @__PURE__ */ new Set();
945
- for (const filePath of files) {
946
- let parsed = null;
947
- try {
948
- parsed = JSON.parse(await import_promises.default.readFile(filePath, "utf8"));
949
- } catch {
950
- for (const strategy of inferMatchingStrategies({
951
- filePath,
952
- rootDir,
953
- parsed,
954
- strategies
955
- })) {
956
- invalidStrategies.add(strategy);
957
- }
958
- continue;
959
- }
960
- const matchingStrategies = inferMatchingStrategies({
961
- filePath,
962
- rootDir,
963
- parsed,
964
- strategies
965
- });
966
- if (!matchingStrategies.length) continue;
967
- try {
968
- const envelope = (0, import_strategyReleaseEvidence.verifyStrategyEvidenceMarkerEnvelope)(parsed);
969
- for (const strategy of matchingStrategies) {
970
- if (strategy !== envelope.payload.strategy) {
971
- invalidStrategies.add(strategy);
972
- }
973
- }
974
- if (!strategies.includes(envelope.payload.strategy)) {
975
- continue;
976
- }
977
- const envelopes = envelopesByStrategy.get(envelope.payload.strategy) ?? [];
978
- envelopes.push(envelope);
979
- envelopesByStrategy.set(envelope.payload.strategy, envelopes);
980
- } catch {
981
- for (const strategy of matchingStrategies) {
982
- invalidStrategies.add(strategy);
983
- }
984
- }
985
- }
986
- for (const selector of selectors) {
987
- const strategy = selector.strategy;
988
- const selectorKey = strategyEvidenceTimelineSelectorKey(selector);
989
- if (invalidStrategies.has(strategy)) {
990
- timelines.set(selectorKey, invalidTimeline());
991
- continue;
992
- }
993
- const envelopes = envelopesByStrategy.get(strategy) ?? [];
994
- if (!envelopes.length) continue;
995
- const hasCompleteSelector = selector.releaseVersion != null || Boolean(selector.compositionId) && Boolean(selector.gitSha) && Boolean(selector.gateFingerprint) && Boolean(selector.configFingerprint) && Boolean(selector.contextFingerprint);
996
- if (selector.requireCompleteLineage && !hasCompleteSelector) continue;
997
- const markersById = /* @__PURE__ */ new Map();
998
- let hasConflict = false;
999
- for (const envelope of envelopes) {
1000
- for (const marker of envelope.payload.markers) {
1001
- const existing = markersById.get(marker.id);
1002
- if (existing && (0, import_strategyReleaseEvidence.canonicalStrategyEvidenceJson)(existing) !== (0, import_strategyReleaseEvidence.canonicalStrategyEvidenceJson)(marker)) {
1003
- hasConflict = true;
1004
- break;
1005
- }
1006
- markersById.set(marker.id, marker);
1007
- }
1008
- if (hasConflict) break;
1009
- }
1010
- if (hasConflict) {
1011
- timelines.set(selectorKey, invalidTimeline());
1012
- continue;
1013
- }
1014
- const matchingMarkers = [...markersById.values()].filter(
1015
- (marker) => (!selector.compositionId || marker.compositionId === selector.compositionId) && (!selector.releaseVersion || marker.releaseVersion === selector.releaseVersion) && (!selector.gitSha || marker.gitSha === selector.gitSha) && (!selector.gateFingerprint || marker.gateFingerprint === selector.gateFingerprint) && (!selector.configFingerprint || marker.configFingerprint === selector.configFingerprint) && (!selector.contextFingerprint || marker.contextFingerprint === selector.contextFingerprint) && marker.timestamp >= startTime && marker.timestamp < endTime
1016
- ).sort(
1017
- (left, right) => left.timestamp - right.timestamp || left.type.localeCompare(right.type) || left.id.localeCompare(right.id)
1018
- );
1019
- let lastLossValue;
1020
- let hasLastLossValue = false;
1021
- const markers = matchingMarkers.filter((marker) => {
1022
- if (marker.type !== "L") return true;
1023
- if (hasLastLossValue && marker.maxLossValue === lastLossValue) {
1024
- return false;
1025
- }
1026
- hasLastLossValue = true;
1027
- lastLossValue = marker.maxLossValue;
1028
- return true;
1029
- });
1030
- if (!markers.length && (selector.compositionId || selector.releaseVersion || selector.gitSha || selector.gateFingerprint || selector.configFingerprint || selector.contextFingerprint)) {
1031
- continue;
1032
- }
1033
- timelines.set(selectorKey, {
1034
- status: "verified",
1035
- observedFrom: markers.length ? Math.min(...markers.map((marker) => marker.timestamp)) : Math.min(...envelopes.map((envelope) => envelope.payload.createdAt)),
1036
- markers
1037
- });
1038
- }
1039
- return timelines;
1040
- };
1041
-
1042
838
  // src/runtimeTradeSync.ts
1043
839
  var import_constants2 = require("@tradejs/core/constants");
1044
840
  var import_time2 = require("@tradejs/core/time");
@@ -1532,6 +1328,499 @@ var syncRuntimeTrades = async ({
1532
1328
  return syncedTrades;
1533
1329
  };
1534
1330
 
1331
+ // src/runtimeStrategies.ts
1332
+ var import_promises = require("fs/promises");
1333
+ var import_node_path = __toESM(require("path"));
1334
+ var import_runtimeControls = require("@tradejs/infra/runtimeControls");
1335
+ var import_tradingAccounts2 = require("@tradejs/infra/tradingAccounts");
1336
+
1337
+ // src/strategy/manifests.ts
1338
+ var import_indicators = require("@tradejs/core/indicators");
1339
+ var import_logger5 = require("@tradejs/infra/logger");
1340
+ var SHARED_STRATEGY_REGISTRY_KEY = "__tradejsNodeSharedStrategyRegistryV1__";
1341
+ var sharedRegistryScope = globalThis;
1342
+ var sharedStrategyRegistry = sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] ?? (sharedRegistryScope[SHARED_STRATEGY_REGISTRY_KEY] = {
1343
+ registryStateByProjectRoot: /* @__PURE__ */ new Map()
1344
+ });
1345
+ var createStrategyRegistryState = () => ({
1346
+ strategyCreators: /* @__PURE__ */ new Map(),
1347
+ strategyManifestsMap: /* @__PURE__ */ new Map(),
1348
+ strategyEntriesMap: /* @__PURE__ */ new Map(),
1349
+ strategySourcesMap: /* @__PURE__ */ new Map(),
1350
+ pluginsLoadPromise: null
1351
+ });
1352
+ var registryStateByProjectRoot2 = sharedStrategyRegistry.registryStateByProjectRoot;
1353
+ var getStrategyRegistryState = (cwd = getTradejsProjectCwd()) => {
1354
+ const projectRoot = getTradejsProjectCwd(cwd);
1355
+ let state = registryStateByProjectRoot2.get(projectRoot);
1356
+ if (!state) {
1357
+ state = createStrategyRegistryState();
1358
+ registryStateByProjectRoot2.set(projectRoot, state);
1359
+ }
1360
+ return {
1361
+ projectRoot,
1362
+ state
1363
+ };
1364
+ };
1365
+ var toUniqueModules2 = (modules = []) => [
1366
+ ...new Set(modules.map((moduleName) => moduleName.trim()).filter(Boolean))
1367
+ ];
1368
+ var getConfiguredPluginModuleNames = async (cwd = getTradejsProjectCwd()) => {
1369
+ const config = await loadTradejsConfig(cwd);
1370
+ return {
1371
+ strategyModules: toUniqueModules2(config.strategies),
1372
+ indicatorModules: toUniqueModules2(config.indicators)
1373
+ };
1374
+ };
1375
+ var extractModuleEntries = (moduleExport, key) => {
1376
+ if (!moduleExport || typeof moduleExport !== "object") {
1377
+ return null;
1378
+ }
1379
+ const candidate = moduleExport;
1380
+ if (Array.isArray(candidate[key])) {
1381
+ return candidate[key];
1382
+ }
1383
+ const defaultExport = candidate.default;
1384
+ if (defaultExport && Array.isArray(defaultExport[key])) {
1385
+ return defaultExport[key];
1386
+ }
1387
+ return null;
1388
+ };
1389
+ var extractStrategyPluginDefinition = (moduleExport) => {
1390
+ const strategyEntries = extractModuleEntries(
1391
+ moduleExport,
1392
+ "strategyEntries"
1393
+ );
1394
+ return strategyEntries ? { strategyEntries } : null;
1395
+ };
1396
+ var extractIndicatorPluginDefinition = (moduleExport) => {
1397
+ const indicatorEntries = extractModuleEntries(
1398
+ moduleExport,
1399
+ "indicatorEntries"
1400
+ );
1401
+ return indicatorEntries ? { indicatorEntries } : null;
1402
+ };
1403
+ var registerEntries2 = (entries, source, state) => {
1404
+ for (const entry of entries) {
1405
+ const strategyName = entry.manifest?.name;
1406
+ if (!strategyName) {
1407
+ import_logger5.logger.warn("Skip strategy entry without name from %s", source);
1408
+ continue;
1409
+ }
1410
+ if (state.strategyCreators.has(strategyName)) {
1411
+ import_logger5.logger.warn(
1412
+ 'Skip duplicate strategy "%s" from %s: already registered',
1413
+ strategyName,
1414
+ source
1415
+ );
1416
+ continue;
1417
+ }
1418
+ state.strategyManifestsMap.set(strategyName, entry.manifest);
1419
+ state.strategyEntriesMap.set(strategyName, entry);
1420
+ state.strategySourcesMap.set(strategyName, source);
1421
+ materializeStrategyCreator(strategyName, state);
1422
+ }
1423
+ };
1424
+ var materializeStrategyCreator = (strategyName, state) => {
1425
+ if (state.strategyCreators.has(strategyName) || !sharedStrategyRegistry.strategyRuntimeFactory) {
1426
+ return;
1427
+ }
1428
+ const entry = state.strategyEntriesMap.get(strategyName);
1429
+ if (!entry) return;
1430
+ state.strategyCreators.set(
1431
+ strategyName,
1432
+ sharedStrategyRegistry.strategyRuntimeFactory({
1433
+ strategyName,
1434
+ defaults: entry.defaults,
1435
+ createCore: entry.createCore,
1436
+ manifest: entry.manifest,
1437
+ detectorKey: entry.detectorKey,
1438
+ detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
1439
+ resolveRegisteredManifest: (name) => state.strategyManifestsMap.get(name)
1440
+ })
1441
+ );
1442
+ };
1443
+ var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()) => {
1444
+ if (typeof importTradejsModule === "function") {
1445
+ return importTradejsModule(moduleName, cwd);
1446
+ }
1447
+ return import(
1448
+ /* webpackIgnore: true */
1449
+ moduleName
1450
+ );
1451
+ };
1452
+ var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
1453
+ const { projectRoot, state } = getStrategyRegistryState(cwd);
1454
+ if (!state.pluginsLoadPromise) {
1455
+ (0, import_indicators.resetIndicatorRegistryCache)(projectRoot);
1456
+ state.pluginsLoadPromise = (async () => {
1457
+ const { strategyModules, indicatorModules } = await getConfiguredPluginModuleNames(projectRoot);
1458
+ const strategySet = new Set(strategyModules);
1459
+ const indicatorSet = new Set(indicatorModules);
1460
+ const pluginModuleNames = [
1461
+ .../* @__PURE__ */ new Set([...strategyModules, ...indicatorModules])
1462
+ ];
1463
+ if (!pluginModuleNames.length) {
1464
+ return;
1465
+ }
1466
+ for (const moduleName of pluginModuleNames) {
1467
+ try {
1468
+ const resolvedModuleName = resolvePluginModuleSpecifier(
1469
+ moduleName,
1470
+ projectRoot
1471
+ );
1472
+ const moduleExport = await importStrategyPluginModule(
1473
+ resolvedModuleName,
1474
+ projectRoot
1475
+ );
1476
+ if (strategySet.has(moduleName)) {
1477
+ const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
1478
+ if (!pluginDefinition) {
1479
+ import_logger5.logger.warn(
1480
+ 'Skip strategy plugin "%s": export { strategyEntries } is missing',
1481
+ moduleName
1482
+ );
1483
+ } else {
1484
+ registerEntries2(
1485
+ pluginDefinition.strategyEntries,
1486
+ moduleName,
1487
+ state
1488
+ );
1489
+ }
1490
+ }
1491
+ if (indicatorSet.has(moduleName)) {
1492
+ const indicatorPluginDefinition = extractIndicatorPluginDefinition(moduleExport);
1493
+ if (!indicatorPluginDefinition) {
1494
+ import_logger5.logger.warn(
1495
+ 'Skip indicator plugin "%s": export { indicatorEntries } is missing',
1496
+ moduleName
1497
+ );
1498
+ } else {
1499
+ (0, import_indicators.registerIndicatorEntries)(
1500
+ indicatorPluginDefinition.indicatorEntries,
1501
+ moduleName,
1502
+ projectRoot
1503
+ );
1504
+ }
1505
+ }
1506
+ if (!strategySet.has(moduleName) && !indicatorSet.has(moduleName)) {
1507
+ import_logger5.logger.warn(
1508
+ 'Skip plugin "%s": no strategy/indicator sections requested in config',
1509
+ moduleName
1510
+ );
1511
+ }
1512
+ } catch (error) {
1513
+ import_logger5.logger.warn(
1514
+ 'Failed to load plugin "%s": %s',
1515
+ moduleName,
1516
+ String(error)
1517
+ );
1518
+ }
1519
+ }
1520
+ })();
1521
+ }
1522
+ await state.pluginsLoadPromise;
1523
+ };
1524
+ var getStrategyCreator = async (name, cwd = getTradejsProjectCwd()) => {
1525
+ await ensureStrategyPluginsLoaded(cwd);
1526
+ const { state } = getStrategyRegistryState(cwd);
1527
+ return state.strategyCreators.get(name);
1528
+ };
1529
+ var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd()) => {
1530
+ await ensureStrategyPluginsLoaded(cwd);
1531
+ const { state } = getStrategyRegistryState(cwd);
1532
+ return state.strategySourcesMap.get(name);
1533
+ };
1534
+ var strategies = new Proxy(
1535
+ {},
1536
+ {
1537
+ get: (_target, property) => {
1538
+ if (typeof property !== "string") {
1539
+ return void 0;
1540
+ }
1541
+ return getStrategyRegistryState().state.strategyCreators.get(property);
1542
+ },
1543
+ ownKeys: () => {
1544
+ return [...getStrategyRegistryState().state.strategyCreators.keys()];
1545
+ },
1546
+ getOwnPropertyDescriptor: () => ({
1547
+ enumerable: true,
1548
+ configurable: true
1549
+ })
1550
+ }
1551
+ );
1552
+
1553
+ // src/runtimeStrategies.ts
1554
+ var INTERVALS = /* @__PURE__ */ new Set([
1555
+ "1",
1556
+ "3",
1557
+ "5",
1558
+ "15",
1559
+ "30",
1560
+ "60",
1561
+ "120",
1562
+ "240",
1563
+ "360",
1564
+ "720",
1565
+ "D",
1566
+ "W",
1567
+ "M"
1568
+ ]);
1569
+ var RUNTIME_KEYS = /* @__PURE__ */ new Set(["deployments"]);
1570
+ var DEPLOYMENT_KEYS = /* @__PURE__ */ new Set([
1571
+ "label",
1572
+ "connectorName",
1573
+ "provider",
1574
+ "accountId",
1575
+ "enabled",
1576
+ "strategies",
1577
+ "assetClasses",
1578
+ "tickers"
1579
+ ]);
1580
+ var STRATEGY_KEYS = /* @__PURE__ */ new Set(["version", "enabled", "config"]);
1581
+ var FORBIDDEN_CONFIG_KEYS = /* @__PURE__ */ new Set([
1582
+ "ACCOUNT_ID",
1583
+ "DEPLOYMENT_ID",
1584
+ "CONNECTOR_NAME",
1585
+ "ENABLE"
1586
+ ]);
1587
+ var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
1588
+ var readPackageManifest = async (projectRoot) => {
1589
+ const candidates = [
1590
+ process.env.TRADEJS_RUNTIME_PACKAGE_MANIFEST,
1591
+ import_node_path.default.join(projectRoot, "runtime-package-manifest.json"),
1592
+ "/app/runtime-package-manifest.json"
1593
+ ].filter((candidate) => Boolean(candidate));
1594
+ for (const candidate of candidates) {
1595
+ try {
1596
+ return JSON.parse(
1597
+ await (0, import_promises.readFile)(candidate, "utf8")
1598
+ );
1599
+ } catch {
1600
+ }
1601
+ }
1602
+ return { packages: {} };
1603
+ };
1604
+ var resolveInstalledPackageVersion = async (projectRoot, packageName, manifest) => {
1605
+ if (!packageName || packageName === "runtime") return null;
1606
+ const manifestVersion = manifest.packages?.[packageName];
1607
+ if (manifestVersion) return manifestVersion;
1608
+ try {
1609
+ const packageJsonPath = import_node_path.default.join(
1610
+ projectRoot,
1611
+ "node_modules",
1612
+ ...packageName.split("/"),
1613
+ "package.json"
1614
+ );
1615
+ const packageJson = JSON.parse(await (0, import_promises.readFile)(packageJsonPath, "utf8"));
1616
+ return packageJson.version ?? null;
1617
+ } catch {
1618
+ return null;
1619
+ }
1620
+ };
1621
+ var resolveStrategyPackageName = async ({
1622
+ pluginSource,
1623
+ projectRoot
1624
+ }) => {
1625
+ if (!pluginSource) return null;
1626
+ if (!pluginSource.startsWith(".") && !import_node_path.default.isAbsolute(pluginSource)) {
1627
+ return pluginSource;
1628
+ }
1629
+ try {
1630
+ const packageJson = JSON.parse(
1631
+ await (0, import_promises.readFile)(import_node_path.default.join(projectRoot, "package.json"), "utf8")
1632
+ );
1633
+ return typeof packageJson.name === "string" && packageJson.name.trim() ? packageJson.name : null;
1634
+ } catch {
1635
+ return null;
1636
+ }
1637
+ };
1638
+ var verifyStringArray = (value) => value === void 0 || Array.isArray(value) && value.every((item) => typeof item === "string");
1639
+ var verifyDeploymentDeclaration = (deploymentId, value) => {
1640
+ if (!deploymentId.trim() || !isRecord(value) || Object.keys(value).some((key) => !DEPLOYMENT_KEYS.has(key)) || typeof value.connectorName !== "string" || !value.connectorName.trim() || typeof value.accountId !== "string" || !value.accountId.trim() || value.label !== void 0 && typeof value.label !== "string" || value.provider !== void 0 && typeof value.provider !== "string" || value.enabled !== void 0 && typeof value.enabled !== "boolean" || !verifyStringArray(value.assetClasses) || !verifyStringArray(value.tickers) || !isRecord(value.strategies) || !Object.keys(value.strategies).length) {
1641
+ throw new Error(`Invalid runtime deployment declaration: ${deploymentId}`);
1642
+ }
1643
+ for (const [strategyName, strategyValue] of Object.entries(
1644
+ value.strategies
1645
+ )) {
1646
+ if (!strategyName.trim() || !isRecord(strategyValue) || Object.keys(strategyValue).some((key) => !STRATEGY_KEYS.has(key)) || !Number.isSafeInteger(strategyValue.version) || Number(strategyValue.version) <= 0 || typeof strategyValue.enabled !== "boolean" || !isRecord(strategyValue.config) || Object.keys(strategyValue.config).some(
1647
+ (key) => FORBIDDEN_CONFIG_KEYS.has(key)
1648
+ ) || !INTERVALS.has(String(strategyValue.config.INTERVAL)) || !["crypto", "tradfi"].includes(String(strategyValue.config.UNIVERSE))) {
1649
+ throw new Error(
1650
+ `Invalid runtime strategy declaration: ${deploymentId}/${strategyName}`
1651
+ );
1652
+ }
1653
+ }
1654
+ return value;
1655
+ };
1656
+ var verifyRuntimeDeclaration = (value) => {
1657
+ if (!isRecord(value) || Object.keys(value).some((key) => !RUNTIME_KEYS.has(key)) || !isRecord(value.deployments) || !Object.keys(value.deployments).length) {
1658
+ throw new Error("Invalid runtime declaration");
1659
+ }
1660
+ for (const [deploymentId, deployment] of Object.entries(value.deployments)) {
1661
+ verifyDeploymentDeclaration(deploymentId, deployment);
1662
+ }
1663
+ return value;
1664
+ };
1665
+ var toRuntimeDeployment = ({
1666
+ id,
1667
+ declaration,
1668
+ controls
1669
+ }) => {
1670
+ const deploymentEnabled = declaration.enabled ?? true;
1671
+ return {
1672
+ id,
1673
+ label: declaration.label?.trim() || id,
1674
+ connectorName: declaration.connectorName.trim(),
1675
+ provider: (declaration.provider || declaration.connectorName).trim().toLowerCase(),
1676
+ accountId: declaration.accountId.trim(),
1677
+ enabled: deploymentEnabled,
1678
+ strategies: Object.entries(declaration.strategies).map(
1679
+ ([strategyName, strategy]) => ({
1680
+ strategyName,
1681
+ version: strategy.version,
1682
+ enabled: strategy.enabled,
1683
+ controlState: deploymentEnabled && strategy.enabled && !controls.deployments[id]?.[strategyName]?.entriesPaused ? "active" : "entries_paused"
1684
+ })
1685
+ ),
1686
+ ...declaration.assetClasses ? { assetClasses: declaration.assetClasses } : {},
1687
+ ...declaration.tickers ? { tickers: declaration.tickers } : {}
1688
+ };
1689
+ };
1690
+ var loadRuntimeDeclaration = async (projectRoot) => {
1691
+ const projectConfig = await loadTradejsConfig(projectRoot);
1692
+ if (!projectConfig.runtime) {
1693
+ throw new Error("Runtime declaration is required in tradejs.config.ts");
1694
+ }
1695
+ return verifyRuntimeDeclaration(projectConfig.runtime);
1696
+ };
1697
+ var listRuntimeDeployments = async ({
1698
+ userName,
1699
+ projectRoot
1700
+ }) => {
1701
+ const [runtime, controls] = await Promise.all([
1702
+ loadRuntimeDeclaration(projectRoot),
1703
+ (0, import_runtimeControls.getRuntimeControls)(userName)
1704
+ ]);
1705
+ return Object.entries(runtime.deployments).map(
1706
+ ([id, declaration]) => toRuntimeDeployment({ id, declaration, controls })
1707
+ ).sort((left, right) => left.label.localeCompare(right.label));
1708
+ };
1709
+ var resolveAccountId = async ({
1710
+ userName,
1711
+ deployment,
1712
+ universe
1713
+ }) => {
1714
+ const account = await (0, import_tradingAccounts2.resolveTradingAccount)({
1715
+ userName,
1716
+ accountId: deployment.accountId,
1717
+ provider: deployment.provider,
1718
+ universe
1719
+ });
1720
+ if (!account) {
1721
+ throw new Error(`Trading account not found: ${deployment.accountId}`);
1722
+ }
1723
+ return account.id;
1724
+ };
1725
+ var loadResolvedRuntimeStrategies = async ({
1726
+ userName,
1727
+ projectRoot,
1728
+ deploymentId,
1729
+ universe,
1730
+ accountId,
1731
+ interval
1732
+ }) => {
1733
+ const [runtime, controls, packageManifest] = await Promise.all([
1734
+ loadRuntimeDeclaration(projectRoot),
1735
+ (0, import_runtimeControls.getRuntimeControls)(userName),
1736
+ readPackageManifest(projectRoot)
1737
+ ]);
1738
+ const declaration = runtime.deployments[deploymentId];
1739
+ if (!declaration) {
1740
+ throw new Error(`Runtime deployment not found: ${deploymentId}`);
1741
+ }
1742
+ const deployment = toRuntimeDeployment({
1743
+ id: deploymentId,
1744
+ declaration,
1745
+ controls
1746
+ });
1747
+ const strategies2 = await Promise.all(
1748
+ Object.entries(declaration.strategies).map(
1749
+ async ([strategyName, strategyDeclaration]) => {
1750
+ const strategyCreator = await getStrategyCreator(
1751
+ strategyName,
1752
+ projectRoot
1753
+ );
1754
+ if (!strategyCreator) {
1755
+ throw new Error(`Unknown strategy: ${strategyName}`);
1756
+ }
1757
+ const pluginSource = await getStrategyPluginSource(strategyName, projectRoot) ?? null;
1758
+ const strategyPackage = await resolveStrategyPackageName({
1759
+ pluginSource,
1760
+ projectRoot
1761
+ });
1762
+ const [strategyPackageVersion, runtimePackageVersion] = await Promise.all([
1763
+ resolveInstalledPackageVersion(
1764
+ projectRoot,
1765
+ strategyPackage,
1766
+ packageManifest
1767
+ ),
1768
+ resolveInstalledPackageVersion(
1769
+ projectRoot,
1770
+ "@tradejs/node",
1771
+ packageManifest
1772
+ )
1773
+ ]);
1774
+ if (!strategyPackage || !strategyPackageVersion) {
1775
+ throw new Error(
1776
+ `Installed strategy package not found: ${strategyName}`
1777
+ );
1778
+ }
1779
+ if (!runtimePackageVersion) {
1780
+ throw new Error("Installed @tradejs/node package version not found");
1781
+ }
1782
+ const strategyView = deployment.strategies.find(
1783
+ (candidate) => candidate.strategyName === strategyName
1784
+ );
1785
+ const strategyConfig = strategyDeclaration.config;
1786
+ const strategyUniverse = strategyConfig.UNIVERSE;
1787
+ const resolvedAccountId = await resolveAccountId({
1788
+ userName,
1789
+ deployment,
1790
+ universe: strategyUniverse
1791
+ });
1792
+ return {
1793
+ strategyName,
1794
+ version: strategyDeclaration.version,
1795
+ enabled: strategyDeclaration.enabled,
1796
+ controlState: strategyView?.controlState ?? "entries_paused",
1797
+ interval: String(strategyConfig.INTERVAL),
1798
+ universe: strategyUniverse,
1799
+ accountId: resolvedAccountId,
1800
+ strategyPackage,
1801
+ strategyPackageVersion,
1802
+ runtimePackageVersion,
1803
+ strategyCreator,
1804
+ sourceStrategyConfig: strategyConfig,
1805
+ strategyConfig
1806
+ };
1807
+ }
1808
+ )
1809
+ );
1810
+ const filtered = strategies2.filter(
1811
+ (candidate) => (!universe || candidate.universe === universe) && (!interval || String(candidate.interval) === String(interval)) && (!accountId || candidate.accountId === accountId)
1812
+ );
1813
+ const identities = /* @__PURE__ */ new Set();
1814
+ for (const candidate of filtered) {
1815
+ const identity = `${candidate.strategyName}:${candidate.accountId ?? "default"}`;
1816
+ if (identities.has(identity)) {
1817
+ throw new Error(`Runtime strategy conflict: ${identity}`);
1818
+ }
1819
+ identities.add(identity);
1820
+ }
1821
+ return filtered;
1822
+ };
1823
+
1535
1824
  // src/runtimeDashboard.ts
1536
1825
  var DEFAULT_PROVIDER = "bybit";
1537
1826
  var DEFAULT_HOURS = 168;
@@ -1550,7 +1839,7 @@ var resolveConnectorCreatorByProvider = async (provider, projectRoot) => await g
1550
1839
  var resolveConnectorAccountId = async ({
1551
1840
  userName,
1552
1841
  provider
1553
- }) => (await (0, import_tradingAccounts2.resolveTradingAccount)({
1842
+ }) => (await (0, import_tradingAccounts3.resolveTradingAccount)({
1554
1843
  userName,
1555
1844
  provider,
1556
1845
  universe: "crypto"
@@ -1616,7 +1905,7 @@ var loadExchangeRange = async ({
1616
1905
  } catch (error) {
1617
1906
  const message = error?.message || String(error);
1618
1907
  errors?.push(`${label}: ${message}`);
1619
- import_logger5.logger.warn("strategies runtime: %s failed: %s", label, message);
1908
+ import_logger6.logger.warn("strategies runtime: %s failed: %s", label, message);
1620
1909
  return [];
1621
1910
  }
1622
1911
  };
@@ -1656,7 +1945,7 @@ var loadClosedPnlRows = async ({
1656
1945
  } catch (error) {
1657
1946
  const message = error?.message || String(error);
1658
1947
  errors?.push(`getClosedPnl: ${message}`);
1659
- import_logger5.logger.warn("strategies runtime: getClosedPnl failed: %s", message);
1948
+ import_logger6.logger.warn("strategies runtime: getClosedPnl failed: %s", message);
1660
1949
  return [];
1661
1950
  }
1662
1951
  };
@@ -1689,7 +1978,7 @@ var loadExchangeEntryRows = async ({
1689
1978
  } catch (error) {
1690
1979
  const message = error?.message || String(error);
1691
1980
  errors?.push(`getEntryExecutions: ${message}`);
1692
- import_logger5.logger.warn("strategies runtime: getEntryExecutions failed: %s", message);
1981
+ import_logger6.logger.warn("strategies runtime: getEntryExecutions failed: %s", message);
1693
1982
  return [];
1694
1983
  }
1695
1984
  };
@@ -1705,7 +1994,7 @@ var loadOpenPositions = async (connector, errors) => {
1705
1994
  } catch (error) {
1706
1995
  const message = error?.message || String(error);
1707
1996
  errors?.push(`getOpenPositionPnl: ${message}`);
1708
- import_logger5.logger.warn("strategies runtime: getOpenPositionPnl failed: %s", message);
1997
+ import_logger6.logger.warn("strategies runtime: getOpenPositionPnl failed: %s", message);
1709
1998
  return { positions: [], reliable: false };
1710
1999
  }
1711
2000
  };
@@ -1762,8 +2051,8 @@ var loadRuntimeDashboard = async ({
1762
2051
  errors: exchangeErrors
1763
2052
  }),
1764
2053
  loadOpenPositions(connector, exchangeErrors),
1765
- (0, import_runtimeDeployments.listRuntimeDeployments)(userName),
1766
- (0, import_tradingAccounts2.listTradingAccounts)(userName)
2054
+ listRuntimeDeployments({ userName, projectRoot }),
2055
+ (0, import_tradingAccounts3.listTradingAccounts)(userName)
1767
2056
  ]);
1768
2057
  const relevantTrades = (0, import_runtimeTrades3.selectTradesForWindow)(
1769
2058
  runtimeTrades,
@@ -1807,9 +2096,23 @@ var loadRuntimeDashboard = async ({
1807
2096
  const accountsById = new Map(
1808
2097
  tradingAccounts.map((account) => [account.id, account])
1809
2098
  );
2099
+ const resolvedStrategiesByDeployment = new Map(
2100
+ await Promise.all(
2101
+ runtimeDeployments.map(
2102
+ async (deployment) => [
2103
+ deployment.id,
2104
+ await loadResolvedRuntimeStrategies({
2105
+ userName,
2106
+ projectRoot,
2107
+ deploymentId: deployment.id
2108
+ })
2109
+ ]
2110
+ )
2111
+ )
2112
+ );
1810
2113
  const runtimeIdentityKey = (trade) => (0, import_runtimeTrades3.buildRuntimeStrategyIdentityKey)({
1811
2114
  strategyName: trade.strategy,
1812
- configId: trade.runtimeReleaseVersion ? `v${trade.runtimeReleaseVersion}` : void 0,
2115
+ configId: trade.runtimeVersion ? `v${trade.runtimeVersion}` : void 0,
1813
2116
  universe: trade.universe,
1814
2117
  accountId: trade.accountId,
1815
2118
  deploymentId: trade.deploymentId,
@@ -1817,42 +2120,34 @@ var loadRuntimeDashboard = async ({
1817
2120
  });
1818
2121
  const identityByKey = /* @__PURE__ */ new Map();
1819
2122
  for (const deployment of runtimeDeployments) {
1820
- for (const deploymentStrategy of deployment.strategies) {
1821
- const release = await (0, import_runtimeStrategyReleases.getRuntimeStrategyRelease)(
1822
- userName,
1823
- deploymentStrategy.strategyName,
1824
- deploymentStrategy.releaseVersion
1825
- );
1826
- if (!release) {
1827
- throw new Error(
1828
- `Runtime release not found: ${deploymentStrategy.strategyName} v${deploymentStrategy.releaseVersion}`
1829
- );
1830
- }
1831
- const releaseUniverse = release.config.UNIVERSE === "tradfi" ? "tradfi" : "crypto";
1832
- const releaseInterval = String(release.config.INTERVAL);
1833
- const releaseConfigId = `v${deploymentStrategy.releaseVersion}`;
1834
- const releasePolicyProfileId = typeof release.config.POLICY_PROFILE_ID === "string" ? release.config.POLICY_PROFILE_ID : void 0;
2123
+ for (const resolvedStrategy of resolvedStrategiesByDeployment.get(
2124
+ deployment.id
2125
+ ) ?? []) {
2126
+ const strategyUniverse = resolvedStrategy.universe;
2127
+ const strategyInterval = resolvedStrategy.interval;
2128
+ const strategyConfigId = `v${resolvedStrategy.version}`;
2129
+ const strategyPolicyProfileId = typeof resolvedStrategy.strategyConfig.POLICY_PROFILE_ID === "string" ? resolvedStrategy.strategyConfig.POLICY_PROFILE_ID : void 0;
1835
2130
  const runtimeKey = (0, import_runtimeTrades3.buildRuntimeStrategyIdentityKey)({
1836
- strategyName: deploymentStrategy.strategyName,
1837
- configId: releaseConfigId,
1838
- universe: releaseUniverse,
2131
+ strategyName: resolvedStrategy.strategyName,
2132
+ configId: strategyConfigId,
2133
+ universe: strategyUniverse,
1839
2134
  accountId: deployment.accountId,
1840
2135
  deploymentId: deployment.id,
1841
- policyProfileId: releasePolicyProfileId
2136
+ policyProfileId: strategyPolicyProfileId
1842
2137
  });
1843
2138
  identityByKey.set(runtimeKey, {
1844
- strategyName: deploymentStrategy.strategyName,
1845
- configId: releaseConfigId,
1846
- releaseVersion: deploymentStrategy.releaseVersion,
1847
- controlState: deploymentStrategy.controlState,
1848
- interval: releaseInterval,
1849
- universe: releaseUniverse,
2139
+ strategyName: resolvedStrategy.strategyName,
2140
+ configId: strategyConfigId,
2141
+ version: resolvedStrategy.version,
2142
+ controlState: resolvedStrategy.controlState,
2143
+ interval: strategyInterval,
2144
+ universe: strategyUniverse,
1850
2145
  accountId: deployment.accountId,
1851
2146
  accountLabel: accountsById.get(deployment.accountId)?.label,
1852
2147
  deploymentId: deployment.id,
1853
- policyProfileId: releasePolicyProfileId,
1854
- enabled: deployment.enabled && deploymentStrategy.controlState !== "entries_paused",
1855
- config: release.config,
2148
+ policyProfileId: strategyPolicyProfileId,
2149
+ enabled: resolvedStrategy.controlState !== "entries_paused",
2150
+ config: resolvedStrategy.strategyConfig,
1856
2151
  connected: deployment.enabled
1857
2152
  });
1858
2153
  }
@@ -1862,8 +2157,8 @@ var loadRuntimeDashboard = async ({
1862
2157
  const key = runtimeIdentityKey(trade);
1863
2158
  const configuredIdentity = identityByKey.get(key);
1864
2159
  if (!configuredIdentity) continue;
1865
- const releaseVersion = trade.runtimeReleaseVersion ?? (trade.runtimeLineage?.schemaVersion === 2 ? trade.runtimeLineage.releaseVersion : void 0);
1866
- if (releaseVersion !== configuredIdentity.releaseVersion) continue;
2160
+ const version = trade.runtimeVersion ?? (trade.runtimeLineage?.schemaVersion === 2 ? trade.runtimeLineage.version : void 0);
2161
+ if (version !== configuredIdentity.version) continue;
1867
2162
  identityByKey.set(key, {
1868
2163
  ...configuredIdentity,
1869
2164
  interval: String(
@@ -1871,18 +2166,7 @@ var loadRuntimeDashboard = async ({
1871
2166
  )
1872
2167
  });
1873
2168
  }
1874
- const evidenceTimelines = await loadStrategyEvidenceTimelines({
1875
- projectRoot,
1876
- markerDir: process.env.STRATEGY_RELEASE_MARKER_DIR,
1877
- selectors: [...identityByKey.values()].map((identity) => ({
1878
- strategy: identity.strategyName,
1879
- releaseVersion: identity.releaseVersion,
1880
- requireCompleteLineage: true
1881
- })),
1882
- startTime,
1883
- endTime
1884
- });
1885
- const strategies = await Promise.all(
2169
+ const strategies2 = await Promise.all(
1886
2170
  [...identityByKey.entries()].map(async ([runtimeKey, identity]) => {
1887
2171
  const { strategyName } = identity;
1888
2172
  const strategyTrades = accountScopedTrades.filter((trade) => runtimeIdentityKey(trade) === runtimeKey).sort((left, right) => right.entryTimestamp - left.entryTimestamp);
@@ -1901,7 +2185,7 @@ var loadRuntimeDashboard = async ({
1901
2185
  runtimeKey,
1902
2186
  strategyName,
1903
2187
  configId: identity.configId,
1904
- releaseVersion: identity.releaseVersion,
2188
+ version: identity.version,
1905
2189
  controlState: identity.controlState,
1906
2190
  interval: identity.interval,
1907
2191
  universe: identity.universe,
@@ -1916,23 +2200,12 @@ var loadRuntimeDashboard = async ({
1916
2200
  stat: analytics.stat,
1917
2201
  summary: analytics.summary,
1918
2202
  orderLog: analytics.orderLog,
1919
- evidenceTimeline: evidenceTimelines.get(
1920
- strategyEvidenceTimelineSelectorKey({
1921
- strategy: strategyName,
1922
- releaseVersion: identity.releaseVersion,
1923
- requireCompleteLineage: true
1924
- })
1925
- ) ?? {
1926
- status: "not_attached",
1927
- observedFrom: null,
1928
- markers: []
1929
- },
1930
2203
  recentTrades: strategyTrades.slice(0, 8).map((trade) => (0, import_runtimeTrades3.toRuntimeTradeView)(trade, endTime)),
1931
2204
  orders
1932
2205
  };
1933
2206
  })
1934
2207
  );
1935
- strategies.sort((left, right) => {
2208
+ strategies2.sort((left, right) => {
1936
2209
  if (left.stat.netProfit !== right.stat.netProfit) {
1937
2210
  return right.stat.netProfit - left.stat.netProfit;
1938
2211
  }
@@ -1953,7 +2226,7 @@ var loadRuntimeDashboard = async ({
1953
2226
  exchangeFallbackTrades: fallbackTrades.length,
1954
2227
  exchangeErrors: [...new Set(exchangeErrors)].sort()
1955
2228
  },
1956
- strategies
2229
+ strategies: strategies2
1957
2230
  };
1958
2231
  return response;
1959
2232
  };