@tradejs/cli 2.0.9 → 2.0.11

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 CHANGED
@@ -4728,10 +4728,13 @@ var init_aiPocketSearch = __esm({
4728
4728
  ["selected_rows", run.selectedRows],
4729
4729
  ["evaluated_rows", run.evaluatedRows],
4730
4730
  ["scope", run.scope],
4731
+ ["direction", run.direction ?? "all"],
4731
4732
  ["scope_rows", run.scopeRows],
4732
4733
  ["train_rows", run.trainRows],
4733
4734
  ["validation_rows", run.validationRows],
4735
+ ["test_rows", run.testRows],
4734
4736
  ["validation_split", formatMdPercent(run.validationSplit)],
4737
+ ["test_split", formatMdPercent(run.testSplit)],
4735
4738
  ["min_validation_support", run.minValidationSupport],
4736
4739
  ["failed", run.failed],
4737
4740
  ["recent", run.recent === 0 ? "all" : run.recent],
@@ -4869,7 +4872,7 @@ var init_aiPocketSearch = __esm({
4869
4872
  });
4870
4873
 
4871
4874
  // src/lib/aiPocketSearchCli.ts
4872
- var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS, readAiPocketSearchCliOption;
4875
+ var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS, splitAiPocketResearchRowsByTimestamp, readAiPocketSearchCliOption;
4873
4876
  var init_aiPocketSearchCli = __esm({
4874
4877
  "src/lib/aiPocketSearchCli.ts"() {
4875
4878
  "use strict";
@@ -4877,7 +4880,47 @@ var init_aiPocketSearchCli = __esm({
4877
4880
  minProfitFactor: "1.2",
4878
4881
  minWinRate: "0",
4879
4882
  minTotalProfit: "0",
4880
- validationSplit: "0.25"
4883
+ validationSplit: "0.25",
4884
+ testSplit: "0"
4885
+ };
4886
+ splitAiPocketResearchRowsByTimestamp = (rows, validationSplit, testSplit = 0) => {
4887
+ const timestamps = [
4888
+ ...new Set(
4889
+ rows.map((row) => row.timestamp).filter((timestamp) => Number.isFinite(timestamp))
4890
+ )
4891
+ ].sort((left, right) => left - right);
4892
+ if (timestamps.length < 2) {
4893
+ return { trainRows: rows, validationRows: [], testRows: [] };
4894
+ }
4895
+ const getSplitCount = (ratio) => ratio > 0 ? Math.max(1, Math.floor(timestamps.length * ratio)) : 0;
4896
+ let testEvents = getSplitCount(testSplit);
4897
+ let validationEvents = getSplitCount(validationSplit);
4898
+ const maximumHeldOut = timestamps.length - 1;
4899
+ if (testEvents + validationEvents > maximumHeldOut) {
4900
+ validationEvents = Math.max(
4901
+ 0,
4902
+ validationEvents - (testEvents + validationEvents - maximumHeldOut)
4903
+ );
4904
+ }
4905
+ if (testEvents + validationEvents > maximumHeldOut) {
4906
+ testEvents = Math.max(0, maximumHeldOut - validationEvents);
4907
+ }
4908
+ const testStart = timestamps.length - testEvents;
4909
+ const validationStart = testStart - validationEvents;
4910
+ const validationTimestamps = new Set(
4911
+ timestamps.slice(validationStart, testStart)
4912
+ );
4913
+ const testTimestamps = new Set(timestamps.slice(testStart));
4914
+ const isIn = (timestampsSet, timestamp) => typeof timestamp === "number" && timestampsSet.has(timestamp);
4915
+ return {
4916
+ trainRows: rows.filter(
4917
+ (row) => !isIn(validationTimestamps, row.timestamp) && !isIn(testTimestamps, row.timestamp)
4918
+ ),
4919
+ validationRows: rows.filter(
4920
+ (row) => isIn(validationTimestamps, row.timestamp)
4921
+ ),
4922
+ testRows: rows.filter((row) => isIn(testTimestamps, row.timestamp))
4923
+ };
4881
4924
  };
4882
4925
  readAiPocketSearchCliOption = ({
4883
4926
  argv,
@@ -4904,7 +4947,7 @@ var aiPocketSearch_exports = {};
4904
4947
  __export(aiPocketSearch_exports, {
4905
4948
  main: () => main3
4906
4949
  });
4907
- var import_args3, import_chalk4, import_promises4, import_path2, import_progress2, import_ai3, import_ai4, ListIt2, flags3, createListIt2, createTable2, formatRatio, formatProfit, formatNumber2, colorizeRatio, colorizeProfit, colorizeNumber, toReportTimestamp, markdownPlainTable, buildPendingMarkdownReport, normalizeInt, normalizePositiveInt, normalizeNonNegativeNumber, normalizeRatio, normalizeFeatureProfile, normalizeQuality, isAiApproval, listMergedFiles, deriveStrategyNameFromFile, getMergedGroupId, resolveMarkdownReportPath, sortDatasetPartPaths, resolveMergedDatasetFiles, getDatasetRowTimestamp, findMaxSelectedTimestamp, getDeterministicModelCandidate, resolveScopeRows, splitValidationRows, printSection, buildSummaryRows, buildPocketRows, buildQualityRows, tickProgressBarTo, searchPhaseLabels, hasCliOption2, main3;
4950
+ var import_args3, import_chalk4, import_promises4, import_path2, import_progress2, import_ai3, import_ai4, ListIt2, flags3, createListIt2, createTable2, formatRatio, formatProfit, formatNumber2, colorizeRatio, colorizeProfit, colorizeNumber, toReportTimestamp, markdownPlainTable, buildPendingMarkdownReport, normalizeInt, normalizePositiveInt, normalizeNonNegativeNumber, normalizeRatio, normalizeFeatureProfile, normalizeQuality, isAiApproval, listMergedFiles, deriveStrategyNameFromFile, getMergedGroupId, resolveMarkdownReportPath, sortDatasetPartPaths, resolveMergedDatasetFiles, getDatasetRowTimestamp, findMaxSelectedTimestamp, getDeterministicModelCandidate, resolveScopeRows, printSection, buildSummaryRows, buildPocketRows, buildQualityRows, tickProgressBarTo, searchPhaseLabels, hasCliOption2, main3;
4908
4951
  var init_aiPocketSearch2 = __esm({
4909
4952
  "src/scripts/aiPocketSearch.ts"() {
4910
4953
  "use strict";
@@ -4968,6 +5011,11 @@ var init_aiPocketSearch2 = __esm({
4968
5011
  "Search scope: all, approved, rejected, or candidates",
4969
5012
  "all"
4970
5013
  );
5014
+ import_args3.default.option(
5015
+ ["I", "direction"],
5016
+ "Only search rows with this signal direction: LONG or SHORT",
5017
+ ""
5018
+ );
4971
5019
  import_args3.default.option(["d", "maxDepth"], "Maximum predicate-combination depth", 2);
4972
5020
  import_args3.default.option(["m", "minSupport"], "Minimum rows required for a pocket", 20);
4973
5021
  import_args3.default.option(
@@ -5000,6 +5048,11 @@ var init_aiPocketSearch2 = __esm({
5000
5048
  "Trailing time-ordered scope share reserved for validation (0 disables)",
5001
5049
  AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.validationSplit
5002
5050
  );
5051
+ import_args3.default.option(
5052
+ ["T", "testSplit"],
5053
+ "Trailing timestamp-grouped scope share withheld as untouched test",
5054
+ AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.testSplit
5055
+ );
5003
5056
  import_args3.default.option(
5004
5057
  ["N", "minValidationSupport"],
5005
5058
  "Minimum validation rows required for positive pockets (0 = auto)",
@@ -5288,31 +5341,6 @@ var init_aiPocketSearch2 = __esm({
5288
5341
  }
5289
5342
  return rows;
5290
5343
  };
5291
- splitValidationRows = (rows, validationSplit) => {
5292
- if (validationSplit <= 0 || rows.length < 2) {
5293
- return {
5294
- trainRows: rows,
5295
- validationRows: []
5296
- };
5297
- }
5298
- const sortedRows = [...rows].sort((left, right) => {
5299
- const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
5300
- const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
5301
- return leftTimestamp - rightTimestamp;
5302
- });
5303
- const validationCount = Math.max(
5304
- 1,
5305
- Math.min(
5306
- sortedRows.length - 1,
5307
- Math.floor(sortedRows.length * validationSplit)
5308
- )
5309
- );
5310
- const splitIndex = sortedRows.length - validationCount;
5311
- return {
5312
- trainRows: sortedRows.slice(0, splitIndex),
5313
- validationRows: sortedRows.slice(splitIndex)
5314
- };
5315
- };
5316
5344
  printSection = (title, table) => {
5317
5345
  console.log(import_chalk4.default.gray(`${title}:`));
5318
5346
  console.log(table);
@@ -5412,6 +5440,12 @@ var init_aiPocketSearch2 = __esm({
5412
5440
  `Unsupported --scope "${scope}". Use all, approved, rejected, or candidates.`
5413
5441
  );
5414
5442
  }
5443
+ const direction = String(flags3.direction || "").trim().toUpperCase();
5444
+ if (direction && !["LONG", "SHORT"].includes(direction)) {
5445
+ throw new Error(
5446
+ `Unsupported --direction "${direction}". Use LONG or SHORT.`
5447
+ );
5448
+ }
5415
5449
  const maxDepth = normalizePositiveInt(flags3.maxDepth, 2);
5416
5450
  const minSupport = normalizePositiveInt(flags3.minSupport, 20);
5417
5451
  const minProfitFactor = normalizeNonNegativeNumber(
@@ -5449,6 +5483,14 @@ var init_aiPocketSearch2 = __esm({
5449
5483
  }) ?? flags3.validationSplit,
5450
5484
  0.25
5451
5485
  ) : 0.25;
5486
+ const testSplit = hasCliOption2("testSplit", "T") ? normalizeRatio(
5487
+ readAiPocketSearchCliOption({
5488
+ argv: process.argv,
5489
+ longName: "testSplit",
5490
+ shortName: "T"
5491
+ }) ?? flags3.testSplit,
5492
+ 0
5493
+ ) : 0;
5452
5494
  const explicitMinValidationSupport = normalizeInt(
5453
5495
  flags3.minValidationSupport,
5454
5496
  0
@@ -5588,11 +5630,9 @@ var init_aiPocketSearch2 = __esm({
5588
5630
  }
5589
5631
  }
5590
5632
  });
5591
- const scopeRows = resolveScopeRows(rows, scope);
5592
- const { trainRows, validationRows } = splitValidationRows(
5593
- scopeRows,
5594
- validationSplit
5595
- );
5633
+ const scopedRows = resolveScopeRows(rows, scope);
5634
+ const scopeRows = direction ? scopedRows.filter((row) => row.direction === direction) : scopedRows;
5635
+ const { trainRows, validationRows, testRows } = splitAiPocketResearchRowsByTimestamp(scopeRows, validationSplit, testSplit);
5596
5636
  const minValidationSupport = explicitMinValidationSupport > 0 ? explicitMinValidationSupport : validationRows.length ? Math.max(3, Math.ceil(minSupport * validationSplit * 0.5)) : 0;
5597
5637
  const currentGateSummary = summarizeAiTrainEvaluations(rows);
5598
5638
  const currentGateQualityThresholds = summarizeAiTrainEvaluationsByQualityThreshold(rows, qualityThresholds);
@@ -5655,9 +5695,11 @@ var init_aiPocketSearch2 = __esm({
5655
5695
  selectedRows,
5656
5696
  evaluatedRows: rows.length,
5657
5697
  scope,
5698
+ direction: direction || null,
5658
5699
  scopeRows: scopeRows.length,
5659
5700
  trainRows: trainRows.length,
5660
5701
  validationRows: validationRows.length,
5702
+ testRows: testRows.length,
5661
5703
  scanned,
5662
5704
  dateSkipped,
5663
5705
  failed,
@@ -5672,6 +5714,7 @@ var init_aiPocketSearch2 = __esm({
5672
5714
  includeGateContext,
5673
5715
  featureProfile,
5674
5716
  validationSplit,
5717
+ testSplit,
5675
5718
  minValidationSupport,
5676
5719
  reportPath,
5677
5720
  search: {
@@ -5683,6 +5726,7 @@ var init_aiPocketSearch2 = __esm({
5683
5726
  maxAtomicPredicates,
5684
5727
  maxCombinations,
5685
5728
  validationSplit,
5729
+ testSplit,
5686
5730
  minValidationSupport,
5687
5731
  dedupeEquivalentSelections,
5688
5732
  top
@@ -5724,9 +5768,11 @@ var init_aiPocketSearch2 = __esm({
5724
5768
  ["selected", import_chalk4.default.blue(String(selectedRows))],
5725
5769
  ["evaluated", import_chalk4.default.blue(String(rows.length))],
5726
5770
  ["scope", import_chalk4.default.yellow(scope)],
5771
+ ["direction", direction ? import_chalk4.default.yellow(direction) : import_chalk4.default.gray("all")],
5727
5772
  ["scope_rows", import_chalk4.default.blue(String(scopeRows.length))],
5728
5773
  ["train_rows", import_chalk4.default.blue(String(trainRows.length))],
5729
5774
  ["validation_rows", import_chalk4.default.blue(String(validationRows.length))],
5775
+ ["test_rows", import_chalk4.default.blue(String(testRows.length))],
5730
5776
  ["source_rows", import_chalk4.default.blue(String(totalRows))],
5731
5777
  ["date_skipped", import_chalk4.default.blue(String(dateSkipped))],
5732
5778
  [
@@ -5753,6 +5799,7 @@ var init_aiPocketSearch2 = __esm({
5753
5799
  ["max_atomic_predicates", import_chalk4.default.magenta(String(maxAtomicPredicates))],
5754
5800
  ["max_combinations", import_chalk4.default.magenta(String(maxCombinations))],
5755
5801
  ["validation_split", import_chalk4.default.magenta(formatRatio(validationSplit))],
5802
+ ["test_split", import_chalk4.default.magenta(formatRatio(testSplit))],
5756
5803
  ["min_validation_support", import_chalk4.default.magenta(String(minValidationSupport))],
5757
5804
  [
5758
5805
  "dedupe_equivalent",
@@ -11160,7 +11207,7 @@ var init_timeWindow = __esm({
11160
11207
  });
11161
11208
 
11162
11209
  // src/lib/runtimeRedis.ts
11163
- var import_redis4, import_time, RESERVED_STRATEGY_NAMES, RESERVED_CONFIG_IDS, resolveStrategyConfigIdentityByKey, isRuntimeStrategyEnabled, isRuntimeTradeRecord, resolveStrategyNameByConfigKey, getRuntimeStrategyConfigKeys, loadRuntimeStrategyNames, loadRuntimeStrategyConfigs, loadRuntimeTrades, loadStrategyResultSymbols;
11210
+ var import_redis4, import_time, RESERVED_STRATEGY_NAMES, RESERVED_CONFIG_IDS, resolveStrategyConfigIdentityByKey, isRuntimeStrategyEnabled, isRuntimeTradeRecord, resolveStrategyNameByConfigKey, getRuntimeStrategyConfigKeys, loadRuntimeStrategyNames, loadRuntimeStrategyConfigs, loadRuntimeTrades, loadRuntimeActiveTradeOrderIds, loadStrategyResultSymbols;
11164
11211
  var init_runtimeRedis = __esm({
11165
11212
  "src/lib/runtimeRedis.ts"() {
11166
11213
  "use strict";
@@ -11264,6 +11311,15 @@ var init_runtimeRedis = __esm({
11264
11311
  const trades = await Promise.all(keys.map((key) => (0, import_redis4.getData)(key, null)));
11265
11312
  return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
11266
11313
  };
11314
+ loadRuntimeActiveTradeOrderIds = async (userName2) => {
11315
+ const keys = await (0, import_redis4.getKeys)(import_redis4.redisKeys.runtimeActiveTrades(userName2));
11316
+ const refs = await Promise.all(keys.map((key) => (0, import_redis4.getData)(key, null)));
11317
+ return new Set(
11318
+ refs.map(
11319
+ (ref) => ref && typeof ref === "object" && "orderId" in ref && typeof ref.orderId === "string" && ref.orderId.trim() ? ref.orderId.trim() : null
11320
+ ).filter((orderId) => orderId != null)
11321
+ );
11322
+ };
11267
11323
  loadStrategyResultSymbols = async ({
11268
11324
  userName: userName2,
11269
11325
  strategy
@@ -24892,7 +24948,8 @@ var init_runtimeDebugEvidence = __esm({
24892
24948
  );
24893
24949
  const activeTradeKey = import_redis14.redisKeys.runtimeActiveTrade(
24894
24950
  userName2,
24895
- trade.symbol
24951
+ trade.symbol,
24952
+ trade.deploymentId ?? trade.accountId
24896
24953
  );
24897
24954
  const signalKey = trade.signalId != null ? import_redis14.redisKeys.storeSignal(trade.symbol, trade.signalId) : null;
24898
24955
  const evaluationBucketKey = import_redis14.redisKeys.runtimeSignalEvaluationBucket(
@@ -33975,7 +34032,7 @@ __export(signalsSummary_exports, {
33975
34032
  main: () => main36,
33976
34033
  signalsSummary: () => signalsSummary
33977
34034
  });
33978
- var import_config11, import_args29, import_logger12, import_connectors18, flags27, projectRoot13, SUMMARY_TIMEZONE2, SUMMARY_TIMEZONE_LABEL2, escapeHtml5, formatMskDateTime3, formatSigned3, formatPnlMoneyText, formatWinRateText, resolveSummaryTitle, normalizeStatus, shouldAttachDebugReport, getSummaryDateParts, formatSummaryIsoDayKey, unescapeHtml, stripTelegramHtml, buildSignalsSummaryAttachment, appendReportAttachmentSummary, resolveSummaryConnectorName, createRuntimeSignalStats, mergeRuntimeSignalStats, buildSummaryPrelude, resolveTradeStatusEmoji, isTimestampInWindow, isRuntimeTradeClosedInWindow, buildSummaryMessages, signalsSummary, main36;
34035
+ var import_config11, import_args29, import_logger12, import_connectors18, import_tradingAccounts4, flags27, projectRoot13, SUMMARY_TIMEZONE2, SUMMARY_TIMEZONE_LABEL2, escapeHtml5, formatMskDateTime3, formatSigned3, formatPnlMoneyText, formatWinRateText, resolveSummaryTitle, normalizeStatus, shouldAttachDebugReport, getSummaryDateParts, formatSummaryIsoDayKey, unescapeHtml, stripTelegramHtml, buildSignalsSummaryAttachment, appendReportAttachmentSummary, resolveSummaryConnectorName, groupRuntimeTradesByConnectorScope, syncRuntimeTradeScopes, createRuntimeSignalStats, mergeRuntimeSignalStats, buildSummaryPrelude, resolveTradeStatusEmoji, isTimestampInWindow, isRuntimeTradeClosedInWindow, buildSummaryMessages, signalsSummary, main36;
33979
34036
  var init_signalsSummary = __esm({
33980
34037
  "src/scripts/signalsSummary.ts"() {
33981
34038
  "use strict";
@@ -33989,6 +34046,7 @@ var init_signalsSummary = __esm({
33989
34046
  init_runtimeSignalsLoader();
33990
34047
  init_runtimeSignalsStorage();
33991
34048
  init_runtimeDebugEvidence();
34049
+ import_tradingAccounts4 = require("@tradejs/infra/tradingAccounts");
33992
34050
  import_args29.default.option(["u", "user"], "Use user config", "root");
33993
34051
  import_args29.default.option(
33994
34052
  "connector",
@@ -34101,6 +34159,108 @@ var init_signalsSummary = __esm({
34101
34159
  );
34102
34160
  return import_connectors18.DEFAULT_CONNECTOR_NAME;
34103
34161
  };
34162
+ groupRuntimeTradesByConnectorScope = ({
34163
+ trades,
34164
+ deployments,
34165
+ fallbackConnectorName
34166
+ }) => {
34167
+ const deploymentById = new Map(
34168
+ deployments.map((deployment) => [deployment.id, deployment])
34169
+ );
34170
+ const scopes = /* @__PURE__ */ new Map();
34171
+ for (const trade of trades) {
34172
+ const deployment = trade.deploymentId ? deploymentById.get(trade.deploymentId) : void 0;
34173
+ const connectorName = deployment?.connectorName ?? fallbackConnectorName;
34174
+ const universe = trade.universe ?? deployment?.universe ?? "crypto";
34175
+ const accountId = trade.accountId ?? deployment?.accountId;
34176
+ const deploymentId = trade.deploymentId;
34177
+ const scopeKey = JSON.stringify([
34178
+ connectorName,
34179
+ universe,
34180
+ accountId ?? null,
34181
+ deploymentId ?? null
34182
+ ]);
34183
+ const scope = scopes.get(scopeKey) ?? {
34184
+ connectorName,
34185
+ universe,
34186
+ ...accountId ? { accountId } : {},
34187
+ ...deploymentId ? { deploymentId } : {},
34188
+ trades: []
34189
+ };
34190
+ scope.trades.push(trade);
34191
+ scopes.set(scopeKey, scope);
34192
+ }
34193
+ return [...scopes.values()];
34194
+ };
34195
+ syncRuntimeTradeScopes = async ({
34196
+ userName: userName2,
34197
+ startTime,
34198
+ endTime,
34199
+ fallbackConnectorName,
34200
+ trades,
34201
+ deployments
34202
+ }) => {
34203
+ const scopes = groupRuntimeTradesByConnectorScope({
34204
+ trades,
34205
+ deployments,
34206
+ fallbackConnectorName
34207
+ });
34208
+ const syncedTrades = [];
34209
+ const connectorNames = /* @__PURE__ */ new Set();
34210
+ for (const scope of scopes) {
34211
+ const connectorName = await resolveSummaryConnectorName(
34212
+ scope.connectorName
34213
+ );
34214
+ const connectorFactory = await (0, import_connectors18.getConnectorCreatorByName)(
34215
+ connectorName,
34216
+ projectRoot13
34217
+ );
34218
+ if (!connectorFactory) {
34219
+ throw new Error(`Connector "${connectorName}" is not registered`);
34220
+ }
34221
+ const connector = await connectorFactory({
34222
+ userName: userName2,
34223
+ universe: scope.universe,
34224
+ accountId: scope.accountId,
34225
+ deploymentId: scope.deploymentId
34226
+ });
34227
+ connectorNames.add(connectorName);
34228
+ syncedTrades.push(
34229
+ ...await syncRuntimeTrades({
34230
+ userName: userName2,
34231
+ connector,
34232
+ trades: scope.trades,
34233
+ startTime,
34234
+ endTime,
34235
+ openPositionCallbacks: {
34236
+ onError: (error) => {
34237
+ import_logger12.logger.warn(
34238
+ "signals summary: getOpenPositionPnl failed for scope account=%s deployment=%s: %s",
34239
+ scope.accountId ?? "default",
34240
+ scope.deploymentId ?? "default",
34241
+ formatRuntimeTradeSyncError(error)
34242
+ );
34243
+ }
34244
+ },
34245
+ closedPnlCallbacks: {
34246
+ onError: (error) => {
34247
+ import_logger12.logger.warn(
34248
+ "signals summary: getClosedPnl failed for scope account=%s deployment=%s: %s",
34249
+ scope.accountId ?? "default",
34250
+ scope.deploymentId ?? "default",
34251
+ formatRuntimeTradeSyncError(error)
34252
+ );
34253
+ }
34254
+ }
34255
+ })
34256
+ );
34257
+ }
34258
+ return {
34259
+ trades: syncedTrades,
34260
+ connectorNames: [...connectorNames].sort(),
34261
+ scopesCount: scopes.length
34262
+ };
34263
+ };
34104
34264
  createRuntimeSignalStats = () => ({
34105
34265
  evaluated: 0,
34106
34266
  signals: 0,
@@ -34410,46 +34570,35 @@ none`
34410
34570
  );
34411
34571
  const endTime = Date.now();
34412
34572
  const startTime = endTime - hours * 60 * 60 * 1e3;
34413
- const connectorName = await resolveSummaryConnectorName(flags27.connector);
34414
- const connectorFactory = await (0, import_connectors18.getConnectorCreatorByName)(
34415
- connectorName,
34416
- projectRoot13
34573
+ const fallbackConnectorName = await resolveSummaryConnectorName(
34574
+ flags27.connector
34417
34575
  );
34418
- if (!connectorFactory) {
34419
- throw new Error(`Connector "${connectorName}" is not registered`);
34420
- }
34421
- const connector = await connectorFactory({
34422
- userName: flags27.user
34423
- });
34424
- const [configuredStrategyNames, signals2, evaluationStatsBuckets, trades] = await Promise.all([
34576
+ const [
34577
+ configuredStrategyNames,
34578
+ signals2,
34579
+ evaluationStatsBuckets,
34580
+ trades,
34581
+ runtimeDeployments
34582
+ ] = await Promise.all([
34425
34583
  loadRuntimeStrategyNames(flags27.user),
34426
34584
  loadRuntimeSignals(flags27.user, { startTime, endTime }),
34427
34585
  loadRuntimeSignalEvaluationStatsBuckets(flags27.user),
34428
- loadRuntimeTrades(flags27.user)
34586
+ loadRuntimeTrades(flags27.user),
34587
+ (0, import_tradingAccounts4.listRuntimeDeployments)(flags27.user)
34429
34588
  ]);
34430
- const syncedTrades = await syncRuntimeTrades({
34589
+ const {
34590
+ trades: syncedTrades,
34591
+ connectorNames,
34592
+ scopesCount
34593
+ } = await syncRuntimeTradeScopes({
34431
34594
  userName: flags27.user,
34432
- connector,
34433
- trades,
34434
34595
  startTime,
34435
34596
  endTime,
34436
- openPositionCallbacks: {
34437
- onError: (error) => {
34438
- import_logger12.logger.warn(
34439
- "signals summary: getOpenPositionPnl failed: %s",
34440
- formatRuntimeTradeSyncError(error)
34441
- );
34442
- }
34443
- },
34444
- closedPnlCallbacks: {
34445
- onError: (error) => {
34446
- import_logger12.logger.warn(
34447
- "signals summary: getClosedPnl failed: %s",
34448
- formatRuntimeTradeSyncError(error)
34449
- );
34450
- }
34451
- }
34597
+ fallbackConnectorName,
34598
+ trades,
34599
+ deployments: runtimeDeployments
34452
34600
  });
34601
+ const activeTradeOrderIds = await loadRuntimeActiveTradeOrderIds(flags27.user);
34453
34602
  const windowSignals = signals2.filter(
34454
34603
  (signal) => signal.timestamp >= startTime && signal.timestamp < endTime
34455
34604
  );
@@ -34467,7 +34616,7 @@ none`
34467
34616
  (trade) => isRuntimeTradeClosedInWindow(trade, startTime, endTime)
34468
34617
  );
34469
34618
  const openSnapshotTrades = syncedTrades.filter(
34470
- (trade) => trade.status === "active" && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp) && trade.entryTimestamp < endTime
34619
+ (trade) => trade.status === "active" && activeTradeOrderIds.has(trade.orderId) && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp) && trade.entryTimestamp < endTime
34471
34620
  );
34472
34621
  const windowTrades = [...windowClosedTrades, ...openSnapshotTrades];
34473
34622
  const windowTradeSignalIds = new Set(
@@ -34490,12 +34639,13 @@ none`
34490
34639
  0
34491
34640
  );
34492
34641
  import_logger12.logger.info(
34493
- "signals summary window=%sh signals=%s evaluations=%s trades=%s connector=%s user=%s",
34642
+ "signals summary window=%sh signals=%s evaluations=%s trades=%s connectors=%s scopes=%s user=%s",
34494
34643
  hours,
34495
34644
  windowSignals.length,
34496
34645
  windowEvaluationsCount,
34497
34646
  windowClosedTrades.length,
34498
- connectorName,
34647
+ connectorNames.join(",") || fallbackConnectorName,
34648
+ scopesCount,
34499
34649
  flags27.user
34500
34650
  );
34501
34651
  const debugAttachment = shouldAttachDebugReport(flags27.debugAttachment) ? await buildRuntimeDebugReportAttachment({
@@ -1187,10 +1187,13 @@ var buildAiPocketMarkdownReport = ({
1187
1187
  ["selected_rows", run.selectedRows],
1188
1188
  ["evaluated_rows", run.evaluatedRows],
1189
1189
  ["scope", run.scope],
1190
+ ["direction", run.direction ?? "all"],
1190
1191
  ["scope_rows", run.scopeRows],
1191
1192
  ["train_rows", run.trainRows],
1192
1193
  ["validation_rows", run.validationRows],
1194
+ ["test_rows", run.testRows],
1193
1195
  ["validation_split", formatMdPercent(run.validationSplit)],
1196
+ ["test_split", formatMdPercent(run.testSplit)],
1194
1197
  ["min_validation_support", run.minValidationSupport],
1195
1198
  ["failed", run.failed],
1196
1199
  ["recent", run.recent === 0 ? "all" : run.recent],
@@ -21,14 +21,55 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var aiPocketSearchCli_exports = {};
22
22
  __export(aiPocketSearchCli_exports, {
23
23
  AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS: () => AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS,
24
- readAiPocketSearchCliOption: () => readAiPocketSearchCliOption
24
+ readAiPocketSearchCliOption: () => readAiPocketSearchCliOption,
25
+ splitAiPocketResearchRowsByTimestamp: () => splitAiPocketResearchRowsByTimestamp
25
26
  });
26
27
  module.exports = __toCommonJS(aiPocketSearchCli_exports);
27
28
  var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS = {
28
29
  minProfitFactor: "1.2",
29
30
  minWinRate: "0",
30
31
  minTotalProfit: "0",
31
- validationSplit: "0.25"
32
+ validationSplit: "0.25",
33
+ testSplit: "0"
34
+ };
35
+ var splitAiPocketResearchRowsByTimestamp = (rows, validationSplit, testSplit = 0) => {
36
+ const timestamps = [
37
+ ...new Set(
38
+ rows.map((row) => row.timestamp).filter((timestamp) => Number.isFinite(timestamp))
39
+ )
40
+ ].sort((left, right) => left - right);
41
+ if (timestamps.length < 2) {
42
+ return { trainRows: rows, validationRows: [], testRows: [] };
43
+ }
44
+ const getSplitCount = (ratio) => ratio > 0 ? Math.max(1, Math.floor(timestamps.length * ratio)) : 0;
45
+ let testEvents = getSplitCount(testSplit);
46
+ let validationEvents = getSplitCount(validationSplit);
47
+ const maximumHeldOut = timestamps.length - 1;
48
+ if (testEvents + validationEvents > maximumHeldOut) {
49
+ validationEvents = Math.max(
50
+ 0,
51
+ validationEvents - (testEvents + validationEvents - maximumHeldOut)
52
+ );
53
+ }
54
+ if (testEvents + validationEvents > maximumHeldOut) {
55
+ testEvents = Math.max(0, maximumHeldOut - validationEvents);
56
+ }
57
+ const testStart = timestamps.length - testEvents;
58
+ const validationStart = testStart - validationEvents;
59
+ const validationTimestamps = new Set(
60
+ timestamps.slice(validationStart, testStart)
61
+ );
62
+ const testTimestamps = new Set(timestamps.slice(testStart));
63
+ const isIn = (timestampsSet, timestamp) => typeof timestamp === "number" && timestampsSet.has(timestamp);
64
+ return {
65
+ trainRows: rows.filter(
66
+ (row) => !isIn(validationTimestamps, row.timestamp) && !isIn(testTimestamps, row.timestamp)
67
+ ),
68
+ validationRows: rows.filter(
69
+ (row) => isIn(validationTimestamps, row.timestamp)
70
+ ),
71
+ testRows: rows.filter((row) => isIn(testTimestamps, row.timestamp))
72
+ };
32
73
  };
33
74
  var readAiPocketSearchCliOption = ({
34
75
  argv,
@@ -50,5 +91,6 @@ var readAiPocketSearchCliOption = ({
50
91
  // Annotate the CommonJS export names for ESM import in node:
51
92
  0 && (module.exports = {
52
93
  AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS,
53
- readAiPocketSearchCliOption
94
+ readAiPocketSearchCliOption,
95
+ splitAiPocketResearchRowsByTimestamp
54
96
  });
@@ -382,7 +382,8 @@ var buildRuntimeDebugReportPayload = async ({
382
382
  );
383
383
  const activeTradeKey = import_redis3.redisKeys.runtimeActiveTrade(
384
384
  userName,
385
- trade.symbol
385
+ trade.symbol,
386
+ trade.deploymentId ?? trade.accountId
386
387
  );
387
388
  const signalKey = trade.signalId != null ? import_redis3.redisKeys.storeSignal(trade.symbol, trade.signalId) : null;
388
389
  const evaluationBucketKey = import_redis3.redisKeys.runtimeSignalEvaluationBucket(
@@ -23,6 +23,7 @@ __export(runtimeRedis_exports, {
23
23
  getRuntimeStrategyConfigKeys: () => getRuntimeStrategyConfigKeys,
24
24
  isRuntimeStrategyEnabled: () => isRuntimeStrategyEnabled,
25
25
  isRuntimeTradeRecord: () => isRuntimeTradeRecord,
26
+ loadRuntimeActiveTradeOrderIds: () => loadRuntimeActiveTradeOrderIds,
26
27
  loadRuntimeStrategyConfigs: () => loadRuntimeStrategyConfigs,
27
28
  loadRuntimeStrategyNames: () => loadRuntimeStrategyNames,
28
29
  loadRuntimeTrades: () => loadRuntimeTrades,
@@ -131,6 +132,15 @@ var loadRuntimeTrades = async (userName, {
131
132
  const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
132
133
  return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
133
134
  };
135
+ var loadRuntimeActiveTradeOrderIds = async (userName) => {
136
+ const keys = await (0, import_redis.getKeys)(import_redis.redisKeys.runtimeActiveTrades(userName));
137
+ const refs = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
138
+ return new Set(
139
+ refs.map(
140
+ (ref) => ref && typeof ref === "object" && "orderId" in ref && typeof ref.orderId === "string" && ref.orderId.trim() ? ref.orderId.trim() : null
141
+ ).filter((orderId) => orderId != null)
142
+ );
143
+ };
134
144
  var loadStrategyResultSymbols = async ({
135
145
  userName,
136
146
  strategy
@@ -148,6 +158,7 @@ var loadStrategyResultSymbols = async ({
148
158
  getRuntimeStrategyConfigKeys,
149
159
  isRuntimeStrategyEnabled,
150
160
  isRuntimeTradeRecord,
161
+ loadRuntimeActiveTradeOrderIds,
151
162
  loadRuntimeStrategyConfigs,
152
163
  loadRuntimeStrategyNames,
153
164
  loadRuntimeTrades,
@@ -3176,10 +3176,13 @@ var buildAiPocketMarkdownReport = ({
3176
3176
  ["selected_rows", run.selectedRows],
3177
3177
  ["evaluated_rows", run.evaluatedRows],
3178
3178
  ["scope", run.scope],
3179
+ ["direction", run.direction ?? "all"],
3179
3180
  ["scope_rows", run.scopeRows],
3180
3181
  ["train_rows", run.trainRows],
3181
3182
  ["validation_rows", run.validationRows],
3183
+ ["test_rows", run.testRows],
3182
3184
  ["validation_split", formatMdPercent(run.validationSplit)],
3185
+ ["test_split", formatMdPercent(run.testSplit)],
3183
3186
  ["min_validation_support", run.minValidationSupport],
3184
3187
  ["failed", run.failed],
3185
3188
  ["recent", run.recent === 0 ? "all" : run.recent],
@@ -3319,7 +3322,47 @@ var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS = {
3319
3322
  minProfitFactor: "1.2",
3320
3323
  minWinRate: "0",
3321
3324
  minTotalProfit: "0",
3322
- validationSplit: "0.25"
3325
+ validationSplit: "0.25",
3326
+ testSplit: "0"
3327
+ };
3328
+ var splitAiPocketResearchRowsByTimestamp = (rows, validationSplit, testSplit = 0) => {
3329
+ const timestamps = [
3330
+ ...new Set(
3331
+ rows.map((row) => row.timestamp).filter((timestamp) => Number.isFinite(timestamp))
3332
+ )
3333
+ ].sort((left, right) => left - right);
3334
+ if (timestamps.length < 2) {
3335
+ return { trainRows: rows, validationRows: [], testRows: [] };
3336
+ }
3337
+ const getSplitCount = (ratio) => ratio > 0 ? Math.max(1, Math.floor(timestamps.length * ratio)) : 0;
3338
+ let testEvents = getSplitCount(testSplit);
3339
+ let validationEvents = getSplitCount(validationSplit);
3340
+ const maximumHeldOut = timestamps.length - 1;
3341
+ if (testEvents + validationEvents > maximumHeldOut) {
3342
+ validationEvents = Math.max(
3343
+ 0,
3344
+ validationEvents - (testEvents + validationEvents - maximumHeldOut)
3345
+ );
3346
+ }
3347
+ if (testEvents + validationEvents > maximumHeldOut) {
3348
+ testEvents = Math.max(0, maximumHeldOut - validationEvents);
3349
+ }
3350
+ const testStart = timestamps.length - testEvents;
3351
+ const validationStart = testStart - validationEvents;
3352
+ const validationTimestamps = new Set(
3353
+ timestamps.slice(validationStart, testStart)
3354
+ );
3355
+ const testTimestamps = new Set(timestamps.slice(testStart));
3356
+ const isIn = (timestampsSet, timestamp) => typeof timestamp === "number" && timestampsSet.has(timestamp);
3357
+ return {
3358
+ trainRows: rows.filter(
3359
+ (row) => !isIn(validationTimestamps, row.timestamp) && !isIn(testTimestamps, row.timestamp)
3360
+ ),
3361
+ validationRows: rows.filter(
3362
+ (row) => isIn(validationTimestamps, row.timestamp)
3363
+ ),
3364
+ testRows: rows.filter((row) => isIn(testTimestamps, row.timestamp))
3365
+ };
3323
3366
  };
3324
3367
  var readAiPocketSearchCliOption = ({
3325
3368
  argv,
@@ -3388,6 +3431,11 @@ import_args.default.option(
3388
3431
  "Search scope: all, approved, rejected, or candidates",
3389
3432
  "all"
3390
3433
  );
3434
+ import_args.default.option(
3435
+ ["I", "direction"],
3436
+ "Only search rows with this signal direction: LONG or SHORT",
3437
+ ""
3438
+ );
3391
3439
  import_args.default.option(["d", "maxDepth"], "Maximum predicate-combination depth", 2);
3392
3440
  import_args.default.option(["m", "minSupport"], "Minimum rows required for a pocket", 20);
3393
3441
  import_args.default.option(
@@ -3420,6 +3468,11 @@ import_args.default.option(
3420
3468
  "Trailing time-ordered scope share reserved for validation (0 disables)",
3421
3469
  AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.validationSplit
3422
3470
  );
3471
+ import_args.default.option(
3472
+ ["T", "testSplit"],
3473
+ "Trailing timestamp-grouped scope share withheld as untouched test",
3474
+ AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.testSplit
3475
+ );
3423
3476
  import_args.default.option(
3424
3477
  ["N", "minValidationSupport"],
3425
3478
  "Minimum validation rows required for positive pockets (0 = auto)",
@@ -3708,31 +3761,6 @@ var resolveScopeRows = (rows, scope) => {
3708
3761
  }
3709
3762
  return rows;
3710
3763
  };
3711
- var splitValidationRows = (rows, validationSplit) => {
3712
- if (validationSplit <= 0 || rows.length < 2) {
3713
- return {
3714
- trainRows: rows,
3715
- validationRows: []
3716
- };
3717
- }
3718
- const sortedRows = [...rows].sort((left, right) => {
3719
- const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
3720
- const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
3721
- return leftTimestamp - rightTimestamp;
3722
- });
3723
- const validationCount = Math.max(
3724
- 1,
3725
- Math.min(
3726
- sortedRows.length - 1,
3727
- Math.floor(sortedRows.length * validationSplit)
3728
- )
3729
- );
3730
- const splitIndex = sortedRows.length - validationCount;
3731
- return {
3732
- trainRows: sortedRows.slice(0, splitIndex),
3733
- validationRows: sortedRows.slice(splitIndex)
3734
- };
3735
- };
3736
3764
  var printSection = (title, table) => {
3737
3765
  console.log(import_chalk.default.gray(`${title}:`));
3738
3766
  console.log(table);
@@ -3832,6 +3860,12 @@ var main = async () => {
3832
3860
  `Unsupported --scope "${scope}". Use all, approved, rejected, or candidates.`
3833
3861
  );
3834
3862
  }
3863
+ const direction = String(flags.direction || "").trim().toUpperCase();
3864
+ if (direction && !["LONG", "SHORT"].includes(direction)) {
3865
+ throw new Error(
3866
+ `Unsupported --direction "${direction}". Use LONG or SHORT.`
3867
+ );
3868
+ }
3835
3869
  const maxDepth = normalizePositiveInt(flags.maxDepth, 2);
3836
3870
  const minSupport = normalizePositiveInt(flags.minSupport, 20);
3837
3871
  const minProfitFactor = normalizeNonNegativeNumber(
@@ -3869,6 +3903,14 @@ var main = async () => {
3869
3903
  }) ?? flags.validationSplit,
3870
3904
  0.25
3871
3905
  ) : 0.25;
3906
+ const testSplit = hasCliOption2("testSplit", "T") ? normalizeRatio(
3907
+ readAiPocketSearchCliOption({
3908
+ argv: process.argv,
3909
+ longName: "testSplit",
3910
+ shortName: "T"
3911
+ }) ?? flags.testSplit,
3912
+ 0
3913
+ ) : 0;
3872
3914
  const explicitMinValidationSupport = normalizeInt(
3873
3915
  flags.minValidationSupport,
3874
3916
  0
@@ -4008,11 +4050,9 @@ var main = async () => {
4008
4050
  }
4009
4051
  }
4010
4052
  });
4011
- const scopeRows = resolveScopeRows(rows, scope);
4012
- const { trainRows, validationRows } = splitValidationRows(
4013
- scopeRows,
4014
- validationSplit
4015
- );
4053
+ const scopedRows = resolveScopeRows(rows, scope);
4054
+ const scopeRows = direction ? scopedRows.filter((row) => row.direction === direction) : scopedRows;
4055
+ const { trainRows, validationRows, testRows } = splitAiPocketResearchRowsByTimestamp(scopeRows, validationSplit, testSplit);
4016
4056
  const minValidationSupport = explicitMinValidationSupport > 0 ? explicitMinValidationSupport : validationRows.length ? Math.max(3, Math.ceil(minSupport * validationSplit * 0.5)) : 0;
4017
4057
  const currentGateSummary = summarizeAiTrainEvaluations(rows);
4018
4058
  const currentGateQualityThresholds = summarizeAiTrainEvaluationsByQualityThreshold(rows, qualityThresholds);
@@ -4075,9 +4115,11 @@ var main = async () => {
4075
4115
  selectedRows,
4076
4116
  evaluatedRows: rows.length,
4077
4117
  scope,
4118
+ direction: direction || null,
4078
4119
  scopeRows: scopeRows.length,
4079
4120
  trainRows: trainRows.length,
4080
4121
  validationRows: validationRows.length,
4122
+ testRows: testRows.length,
4081
4123
  scanned,
4082
4124
  dateSkipped,
4083
4125
  failed,
@@ -4092,6 +4134,7 @@ var main = async () => {
4092
4134
  includeGateContext,
4093
4135
  featureProfile,
4094
4136
  validationSplit,
4137
+ testSplit,
4095
4138
  minValidationSupport,
4096
4139
  reportPath,
4097
4140
  search: {
@@ -4103,6 +4146,7 @@ var main = async () => {
4103
4146
  maxAtomicPredicates,
4104
4147
  maxCombinations,
4105
4148
  validationSplit,
4149
+ testSplit,
4106
4150
  minValidationSupport,
4107
4151
  dedupeEquivalentSelections,
4108
4152
  top
@@ -4144,9 +4188,11 @@ var main = async () => {
4144
4188
  ["selected", import_chalk.default.blue(String(selectedRows))],
4145
4189
  ["evaluated", import_chalk.default.blue(String(rows.length))],
4146
4190
  ["scope", import_chalk.default.yellow(scope)],
4191
+ ["direction", direction ? import_chalk.default.yellow(direction) : import_chalk.default.gray("all")],
4147
4192
  ["scope_rows", import_chalk.default.blue(String(scopeRows.length))],
4148
4193
  ["train_rows", import_chalk.default.blue(String(trainRows.length))],
4149
4194
  ["validation_rows", import_chalk.default.blue(String(validationRows.length))],
4195
+ ["test_rows", import_chalk.default.blue(String(testRows.length))],
4150
4196
  ["source_rows", import_chalk.default.blue(String(totalRows))],
4151
4197
  ["date_skipped", import_chalk.default.blue(String(dateSkipped))],
4152
4198
  [
@@ -4173,6 +4219,7 @@ var main = async () => {
4173
4219
  ["max_atomic_predicates", import_chalk.default.magenta(String(maxAtomicPredicates))],
4174
4220
  ["max_combinations", import_chalk.default.magenta(String(maxCombinations))],
4175
4221
  ["validation_split", import_chalk.default.magenta(formatRatio(validationSplit))],
4222
+ ["test_split", import_chalk.default.magenta(formatRatio(testSplit))],
4176
4223
  ["min_validation_support", import_chalk.default.magenta(String(minValidationSupport))],
4177
4224
  [
4178
4225
  "dedupe_equivalent",
@@ -382,7 +382,8 @@ var buildRuntimeDebugReportPayload = async ({
382
382
  );
383
383
  const activeTradeKey = import_redis3.redisKeys.runtimeActiveTrade(
384
384
  userName,
385
- trade.symbol
385
+ trade.symbol,
386
+ trade.deploymentId ?? trade.accountId
386
387
  );
387
388
  const signalKey = trade.signalId != null ? import_redis3.redisKeys.storeSignal(trade.symbol, trade.signalId) : null;
388
389
  const evaluationBucketKey = import_redis3.redisKeys.runtimeSignalEvaluationBucket(
@@ -381,7 +381,8 @@ var buildRuntimeDebugReportPayload = async ({
381
381
  );
382
382
  const activeTradeKey = import_redis3.redisKeys.runtimeActiveTrade(
383
383
  userName,
384
- trade.symbol
384
+ trade.symbol,
385
+ trade.deploymentId ?? trade.accountId
385
386
  );
386
387
  const signalKey = trade.signalId != null ? import_redis3.redisKeys.storeSignal(trade.symbol, trade.signalId) : null;
387
388
  const evaluationBucketKey = import_redis3.redisKeys.runtimeSignalEvaluationBucket(
@@ -107,6 +107,15 @@ var loadRuntimeTrades = async (userName, {
107
107
  const trades = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
108
108
  return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
109
109
  };
110
+ var loadRuntimeActiveTradeOrderIds = async (userName) => {
111
+ const keys = await (0, import_redis.getKeys)(import_redis.redisKeys.runtimeActiveTrades(userName));
112
+ const refs = await Promise.all(keys.map((key) => (0, import_redis.getData)(key, null)));
113
+ return new Set(
114
+ refs.map(
115
+ (ref) => ref && typeof ref === "object" && "orderId" in ref && typeof ref.orderId === "string" && ref.orderId.trim() ? ref.orderId.trim() : null
116
+ ).filter((orderId) => orderId != null)
117
+ );
118
+ };
110
119
 
111
120
  // src/lib/runtimeTradeSync.ts
112
121
  var import_constants = require("@tradejs/core/constants");
@@ -636,7 +645,8 @@ var buildRuntimeDebugReportPayload = async ({
636
645
  );
637
646
  const activeTradeKey = import_redis4.redisKeys.runtimeActiveTrade(
638
647
  userName,
639
- trade.symbol
648
+ trade.symbol,
649
+ trade.deploymentId ?? trade.accountId
640
650
  );
641
651
  const signalKey = trade.signalId != null ? import_redis4.redisKeys.storeSignal(trade.symbol, trade.signalId) : null;
642
652
  const evaluationBucketKey = import_redis4.redisKeys.runtimeSignalEvaluationBucket(
@@ -771,6 +781,7 @@ var buildRuntimeDebugReportAttachment = async (params) => {
771
781
  };
772
782
 
773
783
  // src/scripts/signalsSummary.ts
784
+ var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
774
785
  import_args.default.option(["u", "user"], "Use user config", "root");
775
786
  import_args.default.option(
776
787
  "connector",
@@ -883,6 +894,108 @@ var resolveSummaryConnectorName = async (value) => {
883
894
  );
884
895
  return import_connectors.DEFAULT_CONNECTOR_NAME;
885
896
  };
897
+ var groupRuntimeTradesByConnectorScope = ({
898
+ trades,
899
+ deployments,
900
+ fallbackConnectorName
901
+ }) => {
902
+ const deploymentById = new Map(
903
+ deployments.map((deployment) => [deployment.id, deployment])
904
+ );
905
+ const scopes = /* @__PURE__ */ new Map();
906
+ for (const trade of trades) {
907
+ const deployment = trade.deploymentId ? deploymentById.get(trade.deploymentId) : void 0;
908
+ const connectorName = deployment?.connectorName ?? fallbackConnectorName;
909
+ const universe = trade.universe ?? deployment?.universe ?? "crypto";
910
+ const accountId = trade.accountId ?? deployment?.accountId;
911
+ const deploymentId = trade.deploymentId;
912
+ const scopeKey = JSON.stringify([
913
+ connectorName,
914
+ universe,
915
+ accountId ?? null,
916
+ deploymentId ?? null
917
+ ]);
918
+ const scope = scopes.get(scopeKey) ?? {
919
+ connectorName,
920
+ universe,
921
+ ...accountId ? { accountId } : {},
922
+ ...deploymentId ? { deploymentId } : {},
923
+ trades: []
924
+ };
925
+ scope.trades.push(trade);
926
+ scopes.set(scopeKey, scope);
927
+ }
928
+ return [...scopes.values()];
929
+ };
930
+ var syncRuntimeTradeScopes = async ({
931
+ userName,
932
+ startTime,
933
+ endTime,
934
+ fallbackConnectorName,
935
+ trades,
936
+ deployments
937
+ }) => {
938
+ const scopes = groupRuntimeTradesByConnectorScope({
939
+ trades,
940
+ deployments,
941
+ fallbackConnectorName
942
+ });
943
+ const syncedTrades = [];
944
+ const connectorNames = /* @__PURE__ */ new Set();
945
+ for (const scope of scopes) {
946
+ const connectorName = await resolveSummaryConnectorName(
947
+ scope.connectorName
948
+ );
949
+ const connectorFactory = await (0, import_connectors.getConnectorCreatorByName)(
950
+ connectorName,
951
+ projectRoot
952
+ );
953
+ if (!connectorFactory) {
954
+ throw new Error(`Connector "${connectorName}" is not registered`);
955
+ }
956
+ const connector = await connectorFactory({
957
+ userName,
958
+ universe: scope.universe,
959
+ accountId: scope.accountId,
960
+ deploymentId: scope.deploymentId
961
+ });
962
+ connectorNames.add(connectorName);
963
+ syncedTrades.push(
964
+ ...await syncRuntimeTrades({
965
+ userName,
966
+ connector,
967
+ trades: scope.trades,
968
+ startTime,
969
+ endTime,
970
+ openPositionCallbacks: {
971
+ onError: (error) => {
972
+ import_logger.logger.warn(
973
+ "signals summary: getOpenPositionPnl failed for scope account=%s deployment=%s: %s",
974
+ scope.accountId ?? "default",
975
+ scope.deploymentId ?? "default",
976
+ formatRuntimeTradeSyncError(error)
977
+ );
978
+ }
979
+ },
980
+ closedPnlCallbacks: {
981
+ onError: (error) => {
982
+ import_logger.logger.warn(
983
+ "signals summary: getClosedPnl failed for scope account=%s deployment=%s: %s",
984
+ scope.accountId ?? "default",
985
+ scope.deploymentId ?? "default",
986
+ formatRuntimeTradeSyncError(error)
987
+ );
988
+ }
989
+ }
990
+ })
991
+ );
992
+ }
993
+ return {
994
+ trades: syncedTrades,
995
+ connectorNames: [...connectorNames].sort(),
996
+ scopesCount: scopes.length
997
+ };
998
+ };
886
999
  var createRuntimeSignalStats = () => ({
887
1000
  evaluated: 0,
888
1001
  signals: 0,
@@ -1192,46 +1305,35 @@ var signalsSummary = async () => {
1192
1305
  );
1193
1306
  const endTime = Date.now();
1194
1307
  const startTime = endTime - hours * 60 * 60 * 1e3;
1195
- const connectorName = await resolveSummaryConnectorName(flags.connector);
1196
- const connectorFactory = await (0, import_connectors.getConnectorCreatorByName)(
1197
- connectorName,
1198
- projectRoot
1308
+ const fallbackConnectorName = await resolveSummaryConnectorName(
1309
+ flags.connector
1199
1310
  );
1200
- if (!connectorFactory) {
1201
- throw new Error(`Connector "${connectorName}" is not registered`);
1202
- }
1203
- const connector = await connectorFactory({
1204
- userName: flags.user
1205
- });
1206
- const [configuredStrategyNames, signals, evaluationStatsBuckets, trades] = await Promise.all([
1311
+ const [
1312
+ configuredStrategyNames,
1313
+ signals,
1314
+ evaluationStatsBuckets,
1315
+ trades,
1316
+ runtimeDeployments
1317
+ ] = await Promise.all([
1207
1318
  loadRuntimeStrategyNames(flags.user),
1208
1319
  loadRuntimeSignals(flags.user, { startTime, endTime }),
1209
1320
  loadRuntimeSignalEvaluationStatsBuckets(flags.user),
1210
- loadRuntimeTrades(flags.user)
1321
+ loadRuntimeTrades(flags.user),
1322
+ (0, import_tradingAccounts.listRuntimeDeployments)(flags.user)
1211
1323
  ]);
1212
- const syncedTrades = await syncRuntimeTrades({
1324
+ const {
1325
+ trades: syncedTrades,
1326
+ connectorNames,
1327
+ scopesCount
1328
+ } = await syncRuntimeTradeScopes({
1213
1329
  userName: flags.user,
1214
- connector,
1215
- trades,
1216
1330
  startTime,
1217
1331
  endTime,
1218
- openPositionCallbacks: {
1219
- onError: (error) => {
1220
- import_logger.logger.warn(
1221
- "signals summary: getOpenPositionPnl failed: %s",
1222
- formatRuntimeTradeSyncError(error)
1223
- );
1224
- }
1225
- },
1226
- closedPnlCallbacks: {
1227
- onError: (error) => {
1228
- import_logger.logger.warn(
1229
- "signals summary: getClosedPnl failed: %s",
1230
- formatRuntimeTradeSyncError(error)
1231
- );
1232
- }
1233
- }
1332
+ fallbackConnectorName,
1333
+ trades,
1334
+ deployments: runtimeDeployments
1234
1335
  });
1336
+ const activeTradeOrderIds = await loadRuntimeActiveTradeOrderIds(flags.user);
1235
1337
  const windowSignals = signals.filter(
1236
1338
  (signal) => signal.timestamp >= startTime && signal.timestamp < endTime
1237
1339
  );
@@ -1249,7 +1351,7 @@ var signalsSummary = async () => {
1249
1351
  (trade) => isRuntimeTradeClosedInWindow(trade, startTime, endTime)
1250
1352
  );
1251
1353
  const openSnapshotTrades = syncedTrades.filter(
1252
- (trade) => trade.status === "active" && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp) && trade.entryTimestamp < endTime
1354
+ (trade) => trade.status === "active" && activeTradeOrderIds.has(trade.orderId) && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp) && trade.entryTimestamp < endTime
1253
1355
  );
1254
1356
  const windowTrades = [...windowClosedTrades, ...openSnapshotTrades];
1255
1357
  const windowTradeSignalIds = new Set(
@@ -1272,12 +1374,13 @@ var signalsSummary = async () => {
1272
1374
  0
1273
1375
  );
1274
1376
  import_logger.logger.info(
1275
- "signals summary window=%sh signals=%s evaluations=%s trades=%s connector=%s user=%s",
1377
+ "signals summary window=%sh signals=%s evaluations=%s trades=%s connectors=%s scopes=%s user=%s",
1276
1378
  hours,
1277
1379
  windowSignals.length,
1278
1380
  windowEvaluationsCount,
1279
1381
  windowClosedTrades.length,
1280
- connectorName,
1382
+ connectorNames.join(",") || fallbackConnectorName,
1383
+ scopesCount,
1281
1384
  flags.user
1282
1385
  );
1283
1386
  const debugAttachment = shouldAttachDebugReport(flags.debugAttachment) ? await buildRuntimeDebugReportAttachment({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "2.0.9",
3
+ "version": "2.0.11",
4
4
  "description": "Official CLI for the TradeJS TypeScript framework: infra setup, backtests, signals, bots, and ML workflows.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -29,14 +29,14 @@
29
29
  "tradejs": "dist/cli.js"
30
30
  },
31
31
  "dependencies": {
32
- "@tradejs/base": "^2.0.9",
33
- "@tradejs/connectors": "^2.0.9",
34
- "@tradejs/core": "^2.0.9",
35
- "@tradejs/indicators": "^2.0.9",
36
- "@tradejs/infra": "^2.0.9",
37
- "@tradejs/node": "^2.0.9",
38
- "@tradejs/strategies": "^2.0.9",
39
- "@tradejs/types": "^2.0.9",
32
+ "@tradejs/base": "^2.0.11",
33
+ "@tradejs/connectors": "^2.0.11",
34
+ "@tradejs/core": "^2.0.11",
35
+ "@tradejs/indicators": "^2.0.11",
36
+ "@tradejs/infra": "^2.0.11",
37
+ "@tradejs/node": "^2.0.11",
38
+ "@tradejs/strategies": "^2.0.11",
39
+ "@tradejs/types": "^2.0.11",
40
40
  "args": "^5.0.3",
41
41
  "bcryptjs": "^2.4.3",
42
42
  "chalk": "4.1.2",