@tradejs/cli 2.0.8 → 2.0.10

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
@@ -4868,6 +4868,37 @@ var init_aiPocketSearch = __esm({
4868
4868
  }
4869
4869
  });
4870
4870
 
4871
+ // src/lib/aiPocketSearchCli.ts
4872
+ var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS, readAiPocketSearchCliOption;
4873
+ var init_aiPocketSearchCli = __esm({
4874
+ "src/lib/aiPocketSearchCli.ts"() {
4875
+ "use strict";
4876
+ AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS = {
4877
+ minProfitFactor: "1.2",
4878
+ minWinRate: "0",
4879
+ minTotalProfit: "0",
4880
+ validationSplit: "0.25"
4881
+ };
4882
+ readAiPocketSearchCliOption = ({
4883
+ argv,
4884
+ longName,
4885
+ shortName
4886
+ }) => {
4887
+ const longPrefix = `--${longName}=`;
4888
+ for (let index = 0; index < argv.length; index += 1) {
4889
+ const arg = argv[index];
4890
+ if (arg.startsWith(longPrefix)) {
4891
+ return arg.slice(longPrefix.length);
4892
+ }
4893
+ if (arg === `--${longName}` || shortName != null && arg === `-${shortName}`) {
4894
+ return argv[index + 1];
4895
+ }
4896
+ }
4897
+ return void 0;
4898
+ };
4899
+ }
4900
+ });
4901
+
4871
4902
  // src/scripts/aiPocketSearch.ts
4872
4903
  var aiPocketSearch_exports = {};
4873
4904
  __export(aiPocketSearch_exports, {
@@ -4888,6 +4919,7 @@ var init_aiPocketSearch2 = __esm({
4888
4919
  init_aiTrainMetrics();
4889
4920
  init_aiTrainOptions();
4890
4921
  init_aiPocketSearch();
4922
+ init_aiPocketSearchCli();
4891
4923
  ListIt2 = require_list_it();
4892
4924
  import_args3.default.example(
4893
4925
  "yarn ai-pocket-search --strategy LiquidityZones -n 0 --maxDepth 2 --minSupport 25",
@@ -4941,17 +4973,17 @@ var init_aiPocketSearch2 = __esm({
4941
4973
  import_args3.default.option(
4942
4974
  ["F", "minProfitFactor"],
4943
4975
  "Minimum profit factor required for positive pockets",
4944
- 1.2
4976
+ AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.minProfitFactor
4945
4977
  );
4946
4978
  import_args3.default.option(
4947
4979
  ["W", "minWinRate"],
4948
4980
  "Minimum win rate required for positive pockets",
4949
- 0
4981
+ AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.minWinRate
4950
4982
  );
4951
4983
  import_args3.default.option(
4952
4984
  ["R", "minTotalProfit"],
4953
4985
  "Minimum total PnL required for positive pockets",
4954
- 0
4986
+ AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.minTotalProfit
4955
4987
  );
4956
4988
  import_args3.default.option(
4957
4989
  ["a", "maxAtomicPredicates"],
@@ -4966,7 +4998,7 @@ var init_aiPocketSearch2 = __esm({
4966
4998
  import_args3.default.option(
4967
4999
  ["V", "validationSplit"],
4968
5000
  "Trailing time-ordered scope share reserved for validation (0 disables)",
4969
- 0.25
5001
+ AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.validationSplit
4970
5002
  );
4971
5003
  import_args3.default.option(
4972
5004
  ["N", "minValidationSupport"],
@@ -5383,17 +5415,40 @@ var init_aiPocketSearch2 = __esm({
5383
5415
  const maxDepth = normalizePositiveInt(flags3.maxDepth, 2);
5384
5416
  const minSupport = normalizePositiveInt(flags3.minSupport, 20);
5385
5417
  const minProfitFactor = normalizeNonNegativeNumber(
5386
- flags3.minProfitFactor,
5418
+ readAiPocketSearchCliOption({
5419
+ argv: process.argv,
5420
+ longName: "minProfitFactor",
5421
+ shortName: "F"
5422
+ }) ?? flags3.minProfitFactor,
5387
5423
  1.2
5388
5424
  );
5389
- const minWinRate = normalizeNonNegativeNumber(flags3.minWinRate, 0);
5390
- const minTotalProfit = Number.isFinite(Number(flags3.minTotalProfit)) ? Number(flags3.minTotalProfit) : 0;
5425
+ const minWinRate = normalizeNonNegativeNumber(
5426
+ readAiPocketSearchCliOption({
5427
+ argv: process.argv,
5428
+ longName: "minWinRate",
5429
+ shortName: "W"
5430
+ }) ?? flags3.minWinRate,
5431
+ 0
5432
+ );
5433
+ const minTotalProfitValue = readAiPocketSearchCliOption({
5434
+ argv: process.argv,
5435
+ longName: "minTotalProfit",
5436
+ shortName: "R"
5437
+ }) ?? flags3.minTotalProfit;
5438
+ const minTotalProfit = Number.isFinite(Number(minTotalProfitValue)) ? Number(minTotalProfitValue) : 0;
5391
5439
  const maxAtomicPredicates = normalizePositiveInt(
5392
5440
  flags3.maxAtomicPredicates,
5393
5441
  180
5394
5442
  );
5395
5443
  const maxCombinations = normalizePositiveInt(flags3.maxCombinations, 6e4);
5396
- const validationSplit = hasCliOption2("validationSplit", "V") ? normalizeRatio(flags3.validationSplit, 0.25) : 0.25;
5444
+ const validationSplit = hasCliOption2("validationSplit", "V") ? normalizeRatio(
5445
+ readAiPocketSearchCliOption({
5446
+ argv: process.argv,
5447
+ longName: "validationSplit",
5448
+ shortName: "V"
5449
+ }) ?? flags3.validationSplit,
5450
+ 0.25
5451
+ ) : 0.25;
5397
5452
  const explicitMinValidationSupport = normalizeInt(
5398
5453
  flags3.minValidationSupport,
5399
5454
  0
@@ -10947,7 +11002,7 @@ var init_marketContextPrepare = __esm({
10947
11002
  userName: params.userName,
10948
11003
  startMs: params.startMs,
10949
11004
  endMs: params.endMs,
10950
- preloadStartMs: params.preloadStartMs
11005
+ preloadStartMs: params.mode === "signals" ? void 0 : params.preloadStartMs
10951
11006
  });
10952
11007
  buildBinanceMarketBackfillParams = (params) => ({
10953
11008
  userName: params.userName,
@@ -10956,7 +11011,7 @@ var init_marketContextPrepare = __esm({
10956
11011
  interval: params.interval,
10957
11012
  startMs: params.startMs,
10958
11013
  endMs: params.endMs,
10959
- preloadStartMs: params.preloadStartMs
11014
+ preloadStartMs: params.mode === "signals" ? void 0 : params.preloadStartMs
10960
11015
  });
10961
11016
  buildDerivativesBackfillParams = (params) => ({
10962
11017
  userName: params.userName,
@@ -11105,7 +11160,7 @@ var init_timeWindow = __esm({
11105
11160
  });
11106
11161
 
11107
11162
  // src/lib/runtimeRedis.ts
11108
- var import_redis4, import_time, RESERVED_STRATEGY_NAMES, RESERVED_CONFIG_IDS, resolveStrategyConfigIdentityByKey, isRuntimeStrategyEnabled, isRuntimeTradeRecord, resolveStrategyNameByConfigKey, getRuntimeStrategyConfigKeys, loadRuntimeStrategyNames, loadRuntimeStrategyConfigs, loadRuntimeTrades, loadStrategyResultSymbols;
11163
+ var import_redis4, import_time, RESERVED_STRATEGY_NAMES, RESERVED_CONFIG_IDS, resolveStrategyConfigIdentityByKey, isRuntimeStrategyEnabled, isRuntimeTradeRecord, resolveStrategyNameByConfigKey, getRuntimeStrategyConfigKeys, loadRuntimeStrategyNames, loadRuntimeStrategyConfigs, loadRuntimeTrades, loadRuntimeActiveTradeOrderIds, loadStrategyResultSymbols;
11109
11164
  var init_runtimeRedis = __esm({
11110
11165
  "src/lib/runtimeRedis.ts"() {
11111
11166
  "use strict";
@@ -11209,6 +11264,15 @@ var init_runtimeRedis = __esm({
11209
11264
  const trades = await Promise.all(keys.map((key) => (0, import_redis4.getData)(key, null)));
11210
11265
  return trades.filter(isRuntimeTradeRecord).filter(filterByWindow).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
11211
11266
  };
11267
+ loadRuntimeActiveTradeOrderIds = async (userName2) => {
11268
+ const keys = await (0, import_redis4.getKeys)(import_redis4.redisKeys.runtimeActiveTrades(userName2));
11269
+ const refs = await Promise.all(keys.map((key) => (0, import_redis4.getData)(key, null)));
11270
+ return new Set(
11271
+ refs.map(
11272
+ (ref) => ref && typeof ref === "object" && "orderId" in ref && typeof ref.orderId === "string" && ref.orderId.trim() ? ref.orderId.trim() : null
11273
+ ).filter((orderId) => orderId != null)
11274
+ );
11275
+ };
11212
11276
  loadStrategyResultSymbols = async ({
11213
11277
  userName: userName2,
11214
11278
  strategy
@@ -24837,7 +24901,8 @@ var init_runtimeDebugEvidence = __esm({
24837
24901
  );
24838
24902
  const activeTradeKey = import_redis14.redisKeys.runtimeActiveTrade(
24839
24903
  userName2,
24840
- trade.symbol
24904
+ trade.symbol,
24905
+ trade.deploymentId ?? trade.accountId
24841
24906
  );
24842
24907
  const signalKey = trade.signalId != null ? import_redis14.redisKeys.storeSignal(trade.symbol, trade.signalId) : null;
24843
24908
  const evaluationBucketKey = import_redis14.redisKeys.runtimeSignalEvaluationBucket(
@@ -28353,6 +28418,7 @@ var init_runtimeStrategies = __esm({
28353
28418
  universe: configUniverse,
28354
28419
  accountId: effectiveAccountId,
28355
28420
  strategyCreator,
28421
+ sourceStrategyConfig: strategyConfig,
28356
28422
  strategyConfig: {
28357
28423
  ...strategyConfig,
28358
28424
  ...deploymentStrategy?.config,
@@ -28652,7 +28718,7 @@ var init_runtimeLineage = __esm({
28652
28718
  });
28653
28719
 
28654
28720
  // src/lib/signals/evaluations.ts
28655
- var import_redis19, runtimeLineageScopeCache, runtimeLineageScopeCacheDayKey, buildRuntimeSignalEvaluationId, saveRuntimeSignalEvaluation;
28721
+ var import_redis19, runtimeLineageScopeCache, runtimeLineageScopeCacheDayKey, runtimeLineageScopeCacheLoaded, buildRuntimeSignalEvaluationId, buildLineageField, flushRuntimeSignalEvaluations, createRuntimeSignalEvaluationBuffer;
28656
28722
  var init_evaluations = __esm({
28657
28723
  "src/lib/signals/evaluations.ts"() {
28658
28724
  "use strict";
@@ -28661,79 +28727,140 @@ var init_evaluations = __esm({
28661
28727
  init_runtimeLineage();
28662
28728
  runtimeLineageScopeCache = /* @__PURE__ */ new Map();
28663
28729
  runtimeLineageScopeCacheDayKey = "";
28730
+ runtimeLineageScopeCacheLoaded = false;
28664
28731
  buildRuntimeSignalEvaluationId = ({
28665
28732
  strategyName,
28666
28733
  symbol,
28667
28734
  timestamp,
28668
28735
  runtimeConfigId
28669
28736
  }) => runtimeConfigId && runtimeConfigId !== "config" ? [strategyName, runtimeConfigId, symbol, timestamp].join(":") : [strategyName, symbol, timestamp].join(":");
28670
- saveRuntimeSignalEvaluation = async (evaluation) => {
28671
- const dayKey = (0, import_time6.getRuntimeStorageDayKey)(evaluation.timestamp);
28672
- if (runtimeLineageScopeCacheDayKey !== dayKey) {
28673
- runtimeLineageScopeCache.clear();
28674
- runtimeLineageScopeCacheDayKey = dayKey;
28737
+ buildLineageField = (scope) => [
28738
+ scope.strategy,
28739
+ scope.runtimeConfigId ?? "config",
28740
+ scope.symbol,
28741
+ runtimeLineageKey(scope.lineage)
28742
+ ].join(":");
28743
+ flushRuntimeSignalEvaluations = async (evaluations) => {
28744
+ if (evaluations.length === 0) return;
28745
+ const evaluationsByDay = /* @__PURE__ */ new Map();
28746
+ for (const evaluation of evaluations) {
28747
+ const dayKey = (0, import_time6.getRuntimeStorageDayKey)(evaluation.timestamp);
28748
+ const dayEvaluations = evaluationsByDay.get(dayKey) ?? [];
28749
+ dayEvaluations.push(evaluation);
28750
+ evaluationsByDay.set(dayKey, dayEvaluations);
28675
28751
  }
28676
28752
  const runtimeSignalRetentionTtl = getRuntimeSignalRetentionTtlSeconds();
28677
- if (evaluation.runtimeLineage) {
28678
- const lineageField = [
28679
- evaluation.strategy,
28680
- evaluation.runtimeConfigId ?? "config",
28681
- evaluation.symbol,
28682
- runtimeLineageKey(evaluation.runtimeLineage)
28683
- ].join(":");
28753
+ for (const [dayKey, dayEvaluations] of evaluationsByDay) {
28754
+ if (runtimeLineageScopeCacheDayKey !== dayKey) {
28755
+ runtimeLineageScopeCache.clear();
28756
+ runtimeLineageScopeCacheDayKey = dayKey;
28757
+ runtimeLineageScopeCacheLoaded = false;
28758
+ }
28684
28759
  const lineageBucket = import_redis19.redisKeys.runtimeLineageScopeBucket(
28685
- evaluation.userName,
28760
+ dayEvaluations[0].userName,
28686
28761
  dayKey
28687
28762
  );
28688
- const cacheKey = `${lineageBucket}:${lineageField}`;
28689
- const existing = runtimeLineageScopeCache.get(cacheKey) ?? await (0, import_redis19.getHashJsonField)(
28690
- lineageBucket,
28691
- lineageField
28763
+ const lineageEvaluations = dayEvaluations.filter(
28764
+ (evaluation) => evaluation.runtimeLineage != null
28692
28765
  );
28693
- const scope = {
28694
- strategy: evaluation.strategy,
28695
- symbol: evaluation.symbol,
28696
- runtimeConfigId: evaluation.runtimeConfigId,
28697
- lineage: evaluation.runtimeLineage,
28698
- firstTimestamp: Math.min(
28699
- existing?.firstTimestamp ?? evaluation.timestamp,
28700
- evaluation.timestamp
28701
- ),
28702
- lastTimestamp: Math.max(
28703
- existing?.lastTimestamp ?? evaluation.timestamp,
28704
- evaluation.timestamp
28705
- )
28706
- };
28707
- runtimeLineageScopeCache.set(cacheKey, scope);
28708
- await (0, import_redis19.setHashJsonField)(lineageBucket, lineageField, scope, {
28709
- expire: RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS
28710
- });
28711
- }
28712
- if (shouldStoreDetailedRuntimeSignalEvaluation(evaluation)) {
28713
- await (0, import_redis19.setHashJsonField)(
28714
- import_redis19.redisKeys.runtimeSignalEvaluationBucket(
28766
+ if (lineageEvaluations.length > 0 && !runtimeLineageScopeCacheLoaded) {
28767
+ const storedScopes = await (0, import_redis19.getHashJsonValues)(lineageBucket);
28768
+ for (const scope of storedScopes) {
28769
+ const lineageField = buildLineageField(scope);
28770
+ runtimeLineageScopeCache.set(`${lineageBucket}:${lineageField}`, scope);
28771
+ }
28772
+ runtimeLineageScopeCacheLoaded = true;
28773
+ }
28774
+ const lineageWrites = /* @__PURE__ */ new Map();
28775
+ for (const evaluation of lineageEvaluations) {
28776
+ const lineageField = buildLineageField({
28777
+ strategy: evaluation.strategy,
28778
+ runtimeConfigId: evaluation.runtimeConfigId,
28779
+ symbol: evaluation.symbol,
28780
+ lineage: evaluation.runtimeLineage
28781
+ });
28782
+ const cacheKey = `${lineageBucket}:${lineageField}`;
28783
+ const existing = runtimeLineageScopeCache.get(cacheKey);
28784
+ const scope = {
28785
+ strategy: evaluation.strategy,
28786
+ symbol: evaluation.symbol,
28787
+ runtimeConfigId: evaluation.runtimeConfigId,
28788
+ lineage: evaluation.runtimeLineage,
28789
+ firstTimestamp: Math.min(
28790
+ existing?.firstTimestamp ?? evaluation.timestamp,
28791
+ evaluation.timestamp
28792
+ ),
28793
+ lastTimestamp: Math.max(
28794
+ existing?.lastTimestamp ?? evaluation.timestamp,
28795
+ evaluation.timestamp
28796
+ )
28797
+ };
28798
+ runtimeLineageScopeCache.set(cacheKey, scope);
28799
+ lineageWrites.set(lineageField, scope);
28800
+ }
28801
+ if (lineageWrites.size > 0) {
28802
+ await (0, import_redis19.setHashJsonFields)(
28803
+ lineageBucket,
28804
+ [...lineageWrites].map(([field, data]) => ({ field, data })),
28805
+ {
28806
+ expire: RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS
28807
+ }
28808
+ );
28809
+ }
28810
+ const detailedWrites = /* @__PURE__ */ new Map();
28811
+ const statsIncrements = /* @__PURE__ */ new Map();
28812
+ for (const evaluation of dayEvaluations) {
28813
+ if (shouldStoreDetailedRuntimeSignalEvaluation(evaluation)) {
28814
+ const bucket = import_redis19.redisKeys.runtimeSignalEvaluationBucket(
28815
+ evaluation.userName,
28816
+ dayKey,
28817
+ evaluation.strategy
28818
+ );
28819
+ const entries = detailedWrites.get(bucket) ?? [];
28820
+ entries.push({
28821
+ field: evaluation.evaluationId,
28822
+ data: evaluation
28823
+ });
28824
+ detailedWrites.set(bucket, entries);
28825
+ }
28826
+ const statsBucket = import_redis19.redisKeys.runtimeSignalEvaluationStatsBucket(
28715
28827
  evaluation.userName,
28716
28828
  dayKey,
28717
28829
  evaluation.strategy
28718
- ),
28719
- evaluation.evaluationId,
28720
- evaluation,
28721
- {
28722
- expire: runtimeSignalRetentionTtl
28830
+ );
28831
+ const increments = statsIncrements.get(statsBucket) ?? {};
28832
+ for (const [field, increment] of Object.entries(
28833
+ buildRuntimeSignalStatsIncrements(evaluation)
28834
+ )) {
28835
+ increments[field] = (increments[field] ?? 0) + increment;
28723
28836
  }
28724
- );
28837
+ statsIncrements.set(statsBucket, increments);
28838
+ }
28839
+ await Promise.all([
28840
+ ...[...detailedWrites].map(
28841
+ ([bucket, entries]) => (0, import_redis19.setHashJsonFields)(bucket, entries, {
28842
+ expire: runtimeSignalRetentionTtl
28843
+ })
28844
+ ),
28845
+ ...[...statsIncrements].map(
28846
+ ([bucket, increments]) => (0, import_redis19.incrHashFields)(bucket, increments, {
28847
+ expire: runtimeSignalRetentionTtl
28848
+ })
28849
+ )
28850
+ ]);
28725
28851
  }
28726
- await (0, import_redis19.incrHashFields)(
28727
- import_redis19.redisKeys.runtimeSignalEvaluationStatsBucket(
28728
- evaluation.userName,
28729
- dayKey,
28730
- evaluation.strategy
28731
- ),
28732
- buildRuntimeSignalStatsIncrements(evaluation),
28733
- {
28734
- expire: runtimeSignalRetentionTtl
28852
+ };
28853
+ createRuntimeSignalEvaluationBuffer = () => {
28854
+ const evaluations = [];
28855
+ return {
28856
+ save: async (evaluation) => {
28857
+ evaluations.push(evaluation);
28858
+ },
28859
+ flush: async () => {
28860
+ const pending = evaluations.splice(0);
28861
+ await flushRuntimeSignalEvaluations(pending);
28735
28862
  }
28736
- );
28863
+ };
28737
28864
  };
28738
28865
  }
28739
28866
  });
@@ -28840,8 +28967,8 @@ var init_runtimeLifecycle = __esm({
28840
28967
  }
28841
28968
  try {
28842
28969
  const strategy = await create({
28843
- btcBinanceData: [...btcBinanceData ?? []],
28844
- btcCoinbaseData: [...btcCoinbaseData ?? []],
28970
+ btcBinanceData: btcBinanceData ?? [],
28971
+ btcCoinbaseData: btcCoinbaseData ?? [],
28845
28972
  onRuntimeClose
28846
28973
  });
28847
28974
  strategy.__tradejsUpdateReferenceData?.({
@@ -29295,7 +29422,7 @@ var init_signals = __esm({
29295
29422
  lifecycle
29296
29423
  };
29297
29424
  };
29298
- findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCoinbaseData, runtimeStrategies, strategyStats, runtimeCloseNotifications, lifecycle, preloadStart, currentTimestamp, persistStrategyState, universe, interval5, intervalMs2, accountId, deploymentId, instrument, runtimeScopeUniverse) => {
29425
+ findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCoinbaseData, primaryBtcClosedData, primaryEthClosedData, primaryEthByTimestamp, runtimeStrategies, strategyStats, runtimeCloseNotifications, lifecycle, preloadStart, currentTimestamp, persistStrategyState, universe, interval5, intervalMs2, accountId, deploymentId, instrument, runtimeScopeUniverse, saveRuntimeSignalEvaluation) => {
29299
29426
  const strategySignals = [];
29300
29427
  const loadCached = (cachedSymbol) => connector.kline({
29301
29428
  symbol: cachedSymbol,
@@ -29304,36 +29431,21 @@ var init_signals = __esm({
29304
29431
  cacheOnly: true,
29305
29432
  interval: interval5
29306
29433
  });
29307
- const [cachedData, btcCachedData, ethCachedData] = universe === "crypto" ? await Promise.all([
29308
- loadCached(symbol),
29309
- loadCached("BTCUSDT"),
29310
- loadCached("ETHUSDT")
29311
- ]) : [await loadCached(symbol), [], []];
29434
+ const cachedData = universe === "crypto" && symbol === "BTCUSDT" ? primaryBtcClosedData : universe === "crypto" && symbol === "ETHUSDT" ? primaryEthClosedData : await loadCached(symbol);
29312
29435
  const closedData = getClosedCandlesForInterval(
29313
29436
  cachedData,
29314
29437
  currentTimestamp,
29315
29438
  intervalMs2
29316
29439
  );
29317
- const closedBtcData = getClosedCandlesForInterval(
29318
- btcCachedData,
29319
- currentTimestamp,
29320
- intervalMs2
29321
- );
29322
- const closedEthData = getClosedCandlesForInterval(
29323
- ethCachedData,
29324
- currentTimestamp,
29325
- intervalMs2
29326
- );
29440
+ const closedBtcData = primaryBtcClosedData;
29441
+ const closedEthData = primaryEthClosedData;
29327
29442
  const { alignedCoinCandles, alignedBtcCandles } = universe === "crypto" ? alignSymbolWithBtcReference(closedData, closedBtcData) : { alignedCoinCandles: closedData, alignedBtcCandles: closedData };
29328
- const ethByTimestamp = new Map(
29329
- closedEthData.map((candle) => [candle.timestamp, candle])
29330
- );
29331
- const alignedEthCandles = alignedCoinCandles.map((candle) => ethByTimestamp.get(candle.timestamp)).filter(
29443
+ const alignedEthCandles = alignedCoinCandles.map((candle) => primaryEthByTimestamp.get(candle.timestamp)).filter(
29332
29444
  (candle) => Boolean(candle)
29333
29445
  );
29334
29446
  const lastCandle = alignedCoinCandles.at(-1);
29335
29447
  const btcLastCandle = alignedBtcCandles.at(-1);
29336
- const ethLastCandle = lastCandle == null ? void 0 : ethByTimestamp.get(lastCandle.timestamp);
29448
+ const ethLastCandle = lastCandle == null ? void 0 : primaryEthByTimestamp.get(lastCandle.timestamp);
29337
29449
  if (!lastCandle || !btcLastCandle) {
29338
29450
  return strategySignals;
29339
29451
  }
@@ -29347,6 +29459,7 @@ var init_signals = __esm({
29347
29459
  strategyName,
29348
29460
  configId,
29349
29461
  strategyCreator,
29462
+ sourceStrategyConfig,
29350
29463
  strategyConfig,
29351
29464
  strategyResults
29352
29465
  } = runtimeStrategy;
@@ -29381,46 +29494,61 @@ var init_signals = __esm({
29381
29494
  configId
29382
29495
  });
29383
29496
  const stats = strategyStats.get(strategyName);
29384
- const evaluation = await lifecycle.evaluate({
29385
- key: lifecycleKey,
29386
- timestamp: lastCandle.timestamp,
29387
- config: {
29388
- runtimeConfig,
29389
- symbolResultConfig: strategyResults?.[symbol]?.config ?? null
29390
- },
29391
- btcBinanceData,
29392
- btcCoinbaseData,
29393
- onRuntimeClose: (event) => {
29394
- runtimeCloseNotifications.push(event);
29395
- },
29396
- create: async ({
29397
- btcBinanceData: lifecycleBtcBinanceData,
29398
- btcCoinbaseData: lifecycleBtcCoinbaseData,
29399
- onRuntimeClose
29400
- }) => strategyCreator({
29401
- userName: flags26.user,
29402
- connectorName,
29403
- runtimeConfigId: configId,
29404
- connector,
29405
- symbol,
29406
- ...runtimeScopeUniverse ? {
29407
- universe,
29408
- assetClass: instrument?.assetClass,
29409
- instrument,
29410
- accountId,
29411
- deploymentId
29412
- } : {},
29413
- data: [...previousData],
29414
- btcData: universe === "crypto" ? [...previousBtcData] : [],
29415
- ethData: universe === "crypto" ? [...previousEthData] : [],
29416
- btcBinanceData: lifecycleBtcBinanceData,
29417
- btcCoinbaseData: lifecycleBtcCoinbaseData,
29418
- config: runtimeConfig,
29419
- ...persistStrategyState ? { sharedStrategyStateKey: lifecycleKey } : {},
29420
- onRuntimeClose
29421
- }),
29422
- run: (strategy) => strategy(lastCandle, btcLastCandle, ethLastCandle)
29423
- });
29497
+ const initialDataLength = previousData.length;
29498
+ const initialBtcDataLength = previousBtcData.length;
29499
+ const initialEthDataLength = previousEthData.length;
29500
+ let evaluation;
29501
+ try {
29502
+ evaluation = await lifecycle.evaluate({
29503
+ key: lifecycleKey,
29504
+ timestamp: lastCandle.timestamp,
29505
+ config: {
29506
+ runtimeConfig,
29507
+ sourceStrategyConfig,
29508
+ symbolResultConfig: strategyResults?.[symbol]?.config ?? null
29509
+ },
29510
+ btcBinanceData,
29511
+ btcCoinbaseData,
29512
+ onRuntimeClose: (event) => {
29513
+ runtimeCloseNotifications.push(event);
29514
+ },
29515
+ create: async ({
29516
+ btcBinanceData: lifecycleBtcBinanceData,
29517
+ btcCoinbaseData: lifecycleBtcCoinbaseData,
29518
+ onRuntimeClose
29519
+ }) => strategyCreator({
29520
+ userName: flags26.user,
29521
+ connectorName,
29522
+ runtimeConfigId: configId,
29523
+ runtimeConfigSnapshot: {
29524
+ userConfig: sourceStrategyConfig,
29525
+ symbolResultConfig: strategyResults?.[symbol]?.config ?? null
29526
+ },
29527
+ connector,
29528
+ symbol,
29529
+ ...runtimeScopeUniverse ? {
29530
+ universe,
29531
+ assetClass: instrument?.assetClass,
29532
+ instrument,
29533
+ accountId,
29534
+ deploymentId
29535
+ } : {},
29536
+ data: previousData,
29537
+ btcData: universe === "crypto" ? previousBtcData : [],
29538
+ ethData: universe === "crypto" ? previousEthData : [],
29539
+ btcBinanceData: lifecycleBtcBinanceData,
29540
+ btcCoinbaseData: lifecycleBtcCoinbaseData,
29541
+ config: runtimeConfig,
29542
+ ...persistStrategyState ? { sharedStrategyStateKey: lifecycleKey } : {},
29543
+ onRuntimeClose
29544
+ }),
29545
+ run: (strategy) => strategy(lastCandle, btcLastCandle, ethLastCandle)
29546
+ });
29547
+ } finally {
29548
+ previousData.length = initialDataLength;
29549
+ previousBtcData.length = initialBtcDataLength;
29550
+ previousEthData.length = initialEthDataLength;
29551
+ }
29424
29552
  if (evaluation.action === "duplicate" || evaluation.action === "stale") {
29425
29553
  continue;
29426
29554
  }
@@ -29441,7 +29569,7 @@ var init_signals = __esm({
29441
29569
  if (typeof signal === "string") {
29442
29570
  recordStrategyReason(strategyStats, strategyName, signal, "core");
29443
29571
  }
29444
- await saveRuntimeSignalEvaluation({
29572
+ await saveRuntimeSignalEvaluation?.({
29445
29573
  evaluationId: buildRuntimeSignalEvaluationId({
29446
29574
  strategyName,
29447
29575
  symbol,
@@ -29507,7 +29635,7 @@ var init_signals = __esm({
29507
29635
  expire: runtimeSignalRetentionTtl
29508
29636
  }
29509
29637
  );
29510
- await saveRuntimeSignalEvaluation({
29638
+ await saveRuntimeSignalEvaluation?.({
29511
29639
  evaluationId: buildRuntimeSignalEvaluationId({
29512
29640
  strategyName,
29513
29641
  symbol,
@@ -29549,6 +29677,7 @@ var init_signals = __esm({
29549
29677
  let activeSession;
29550
29678
  let projectHooks;
29551
29679
  let afterSignalsHookContext = null;
29680
+ const runtimeSignalEvaluations = createRuntimeSignalEvaluationBuffer();
29552
29681
  try {
29553
29682
  const session = options.session ?? await createSignalsSession(createDefaultSignalsLifecycle());
29554
29683
  activeSession = session;
@@ -29660,7 +29789,12 @@ var init_signals = __esm({
29660
29789
  if (flags26.updateOnly) {
29661
29790
  return;
29662
29791
  }
29663
- const [btcBinanceData, btcCoinbaseData] = universe === "crypto" ? await timeOperation3(
29792
+ const [
29793
+ btcBinanceData,
29794
+ btcCoinbaseData,
29795
+ primaryBtcCachedData,
29796
+ primaryEthCachedData
29797
+ ] = universe === "crypto" ? await timeOperation3(
29664
29798
  "reference candles load",
29665
29799
  () => Promise.all([
29666
29800
  btcReferences.binance.kline({
@@ -29676,9 +29810,36 @@ var init_signals = __esm({
29676
29810
  end: currentTimestamp,
29677
29811
  cacheOnly: true,
29678
29812
  interval: interval5
29813
+ }),
29814
+ marketConnector.kline({
29815
+ symbol: "BTCUSDT",
29816
+ start: preloadStart,
29817
+ end: currentTimestamp,
29818
+ cacheOnly: true,
29819
+ interval: interval5
29820
+ }),
29821
+ marketConnector.kline({
29822
+ symbol: "ETHUSDT",
29823
+ start: preloadStart,
29824
+ end: currentTimestamp,
29825
+ cacheOnly: true,
29826
+ interval: interval5
29679
29827
  })
29680
29828
  ])
29681
- ) : [[], []];
29829
+ ) : [[], [], [], []];
29830
+ const primaryBtcClosedData = getClosedCandlesForInterval(
29831
+ primaryBtcCachedData,
29832
+ currentTimestamp,
29833
+ intervalMs2
29834
+ );
29835
+ const primaryEthClosedData = getClosedCandlesForInterval(
29836
+ primaryEthCachedData,
29837
+ currentTimestamp,
29838
+ intervalMs2
29839
+ );
29840
+ const primaryEthByTimestamp = new Map(
29841
+ primaryEthClosedData.map((candle) => [candle.timestamp, candle])
29842
+ );
29682
29843
  const runtimeStrategies = await loadRuntimeStrategies({
29683
29844
  userName: flags26.user,
29684
29845
  projectRoot: projectRoot12,
@@ -29762,6 +29923,9 @@ var init_signals = __esm({
29762
29923
  marketConnector,
29763
29924
  btcBinanceData,
29764
29925
  btcCoinbaseData,
29926
+ primaryBtcClosedData,
29927
+ primaryEthClosedData,
29928
+ primaryEthByTimestamp,
29765
29929
  runtimeStrategies,
29766
29930
  strategyStats,
29767
29931
  runtimeCloseNotifications,
@@ -29775,7 +29939,8 @@ var init_signals = __esm({
29775
29939
  accountId,
29776
29940
  deployment?.id,
29777
29941
  instrumentsBySymbol.get(symbol),
29778
- sessionUniverse
29942
+ sessionUniverse,
29943
+ runtimeSignalEvaluations.save
29779
29944
  );
29780
29945
  if (strategySignals.length > 0) {
29781
29946
  signals2.push(...strategySignals);
@@ -29816,6 +29981,14 @@ var init_signals = __esm({
29816
29981
  );
29817
29982
  throw error;
29818
29983
  } finally {
29984
+ try {
29985
+ await runtimeSignalEvaluations.flush();
29986
+ } catch (error) {
29987
+ import_logger10.logger.error(
29988
+ "runtime signal evaluation flush failed: %s",
29989
+ error?.message || String(error)
29990
+ );
29991
+ }
29819
29992
  const durationMs = Date.now() - startedAt;
29820
29993
  if (activeSession?.deployment) {
29821
29994
  await (0, import_tradingAccounts3.saveRuntimeDeploymentHeartbeat)(flags26.user, {
@@ -33812,7 +33985,7 @@ __export(signalsSummary_exports, {
33812
33985
  main: () => main36,
33813
33986
  signalsSummary: () => signalsSummary
33814
33987
  });
33815
- 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;
33988
+ 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;
33816
33989
  var init_signalsSummary = __esm({
33817
33990
  "src/scripts/signalsSummary.ts"() {
33818
33991
  "use strict";
@@ -33826,6 +33999,7 @@ var init_signalsSummary = __esm({
33826
33999
  init_runtimeSignalsLoader();
33827
34000
  init_runtimeSignalsStorage();
33828
34001
  init_runtimeDebugEvidence();
34002
+ import_tradingAccounts4 = require("@tradejs/infra/tradingAccounts");
33829
34003
  import_args29.default.option(["u", "user"], "Use user config", "root");
33830
34004
  import_args29.default.option(
33831
34005
  "connector",
@@ -33938,6 +34112,108 @@ var init_signalsSummary = __esm({
33938
34112
  );
33939
34113
  return import_connectors18.DEFAULT_CONNECTOR_NAME;
33940
34114
  };
34115
+ groupRuntimeTradesByConnectorScope = ({
34116
+ trades,
34117
+ deployments,
34118
+ fallbackConnectorName
34119
+ }) => {
34120
+ const deploymentById = new Map(
34121
+ deployments.map((deployment) => [deployment.id, deployment])
34122
+ );
34123
+ const scopes = /* @__PURE__ */ new Map();
34124
+ for (const trade of trades) {
34125
+ const deployment = trade.deploymentId ? deploymentById.get(trade.deploymentId) : void 0;
34126
+ const connectorName = deployment?.connectorName ?? fallbackConnectorName;
34127
+ const universe = trade.universe ?? deployment?.universe ?? "crypto";
34128
+ const accountId = trade.accountId ?? deployment?.accountId;
34129
+ const deploymentId = trade.deploymentId;
34130
+ const scopeKey = JSON.stringify([
34131
+ connectorName,
34132
+ universe,
34133
+ accountId ?? null,
34134
+ deploymentId ?? null
34135
+ ]);
34136
+ const scope = scopes.get(scopeKey) ?? {
34137
+ connectorName,
34138
+ universe,
34139
+ ...accountId ? { accountId } : {},
34140
+ ...deploymentId ? { deploymentId } : {},
34141
+ trades: []
34142
+ };
34143
+ scope.trades.push(trade);
34144
+ scopes.set(scopeKey, scope);
34145
+ }
34146
+ return [...scopes.values()];
34147
+ };
34148
+ syncRuntimeTradeScopes = async ({
34149
+ userName: userName2,
34150
+ startTime,
34151
+ endTime,
34152
+ fallbackConnectorName,
34153
+ trades,
34154
+ deployments
34155
+ }) => {
34156
+ const scopes = groupRuntimeTradesByConnectorScope({
34157
+ trades,
34158
+ deployments,
34159
+ fallbackConnectorName
34160
+ });
34161
+ const syncedTrades = [];
34162
+ const connectorNames = /* @__PURE__ */ new Set();
34163
+ for (const scope of scopes) {
34164
+ const connectorName = await resolveSummaryConnectorName(
34165
+ scope.connectorName
34166
+ );
34167
+ const connectorFactory = await (0, import_connectors18.getConnectorCreatorByName)(
34168
+ connectorName,
34169
+ projectRoot13
34170
+ );
34171
+ if (!connectorFactory) {
34172
+ throw new Error(`Connector "${connectorName}" is not registered`);
34173
+ }
34174
+ const connector = await connectorFactory({
34175
+ userName: userName2,
34176
+ universe: scope.universe,
34177
+ accountId: scope.accountId,
34178
+ deploymentId: scope.deploymentId
34179
+ });
34180
+ connectorNames.add(connectorName);
34181
+ syncedTrades.push(
34182
+ ...await syncRuntimeTrades({
34183
+ userName: userName2,
34184
+ connector,
34185
+ trades: scope.trades,
34186
+ startTime,
34187
+ endTime,
34188
+ openPositionCallbacks: {
34189
+ onError: (error) => {
34190
+ import_logger12.logger.warn(
34191
+ "signals summary: getOpenPositionPnl failed for scope account=%s deployment=%s: %s",
34192
+ scope.accountId ?? "default",
34193
+ scope.deploymentId ?? "default",
34194
+ formatRuntimeTradeSyncError(error)
34195
+ );
34196
+ }
34197
+ },
34198
+ closedPnlCallbacks: {
34199
+ onError: (error) => {
34200
+ import_logger12.logger.warn(
34201
+ "signals summary: getClosedPnl failed for scope account=%s deployment=%s: %s",
34202
+ scope.accountId ?? "default",
34203
+ scope.deploymentId ?? "default",
34204
+ formatRuntimeTradeSyncError(error)
34205
+ );
34206
+ }
34207
+ }
34208
+ })
34209
+ );
34210
+ }
34211
+ return {
34212
+ trades: syncedTrades,
34213
+ connectorNames: [...connectorNames].sort(),
34214
+ scopesCount: scopes.length
34215
+ };
34216
+ };
33941
34217
  createRuntimeSignalStats = () => ({
33942
34218
  evaluated: 0,
33943
34219
  signals: 0,
@@ -34247,46 +34523,35 @@ none`
34247
34523
  );
34248
34524
  const endTime = Date.now();
34249
34525
  const startTime = endTime - hours * 60 * 60 * 1e3;
34250
- const connectorName = await resolveSummaryConnectorName(flags27.connector);
34251
- const connectorFactory = await (0, import_connectors18.getConnectorCreatorByName)(
34252
- connectorName,
34253
- projectRoot13
34526
+ const fallbackConnectorName = await resolveSummaryConnectorName(
34527
+ flags27.connector
34254
34528
  );
34255
- if (!connectorFactory) {
34256
- throw new Error(`Connector "${connectorName}" is not registered`);
34257
- }
34258
- const connector = await connectorFactory({
34259
- userName: flags27.user
34260
- });
34261
- const [configuredStrategyNames, signals2, evaluationStatsBuckets, trades] = await Promise.all([
34529
+ const [
34530
+ configuredStrategyNames,
34531
+ signals2,
34532
+ evaluationStatsBuckets,
34533
+ trades,
34534
+ runtimeDeployments
34535
+ ] = await Promise.all([
34262
34536
  loadRuntimeStrategyNames(flags27.user),
34263
34537
  loadRuntimeSignals(flags27.user, { startTime, endTime }),
34264
34538
  loadRuntimeSignalEvaluationStatsBuckets(flags27.user),
34265
- loadRuntimeTrades(flags27.user)
34539
+ loadRuntimeTrades(flags27.user),
34540
+ (0, import_tradingAccounts4.listRuntimeDeployments)(flags27.user)
34266
34541
  ]);
34267
- const syncedTrades = await syncRuntimeTrades({
34542
+ const {
34543
+ trades: syncedTrades,
34544
+ connectorNames,
34545
+ scopesCount
34546
+ } = await syncRuntimeTradeScopes({
34268
34547
  userName: flags27.user,
34269
- connector,
34270
- trades,
34271
34548
  startTime,
34272
34549
  endTime,
34273
- openPositionCallbacks: {
34274
- onError: (error) => {
34275
- import_logger12.logger.warn(
34276
- "signals summary: getOpenPositionPnl failed: %s",
34277
- formatRuntimeTradeSyncError(error)
34278
- );
34279
- }
34280
- },
34281
- closedPnlCallbacks: {
34282
- onError: (error) => {
34283
- import_logger12.logger.warn(
34284
- "signals summary: getClosedPnl failed: %s",
34285
- formatRuntimeTradeSyncError(error)
34286
- );
34287
- }
34288
- }
34550
+ fallbackConnectorName,
34551
+ trades,
34552
+ deployments: runtimeDeployments
34289
34553
  });
34554
+ const activeTradeOrderIds = await loadRuntimeActiveTradeOrderIds(flags27.user);
34290
34555
  const windowSignals = signals2.filter(
34291
34556
  (signal) => signal.timestamp >= startTime && signal.timestamp < endTime
34292
34557
  );
@@ -34304,7 +34569,7 @@ none`
34304
34569
  (trade) => isRuntimeTradeClosedInWindow(trade, startTime, endTime)
34305
34570
  );
34306
34571
  const openSnapshotTrades = syncedTrades.filter(
34307
- (trade) => trade.status === "active" && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp) && trade.entryTimestamp < endTime
34572
+ (trade) => trade.status === "active" && activeTradeOrderIds.has(trade.orderId) && typeof trade.entryTimestamp === "number" && Number.isFinite(trade.entryTimestamp) && trade.entryTimestamp < endTime
34308
34573
  );
34309
34574
  const windowTrades = [...windowClosedTrades, ...openSnapshotTrades];
34310
34575
  const windowTradeSignalIds = new Set(
@@ -34327,12 +34592,13 @@ none`
34327
34592
  0
34328
34593
  );
34329
34594
  import_logger12.logger.info(
34330
- "signals summary window=%sh signals=%s evaluations=%s trades=%s connector=%s user=%s",
34595
+ "signals summary window=%sh signals=%s evaluations=%s trades=%s connectors=%s scopes=%s user=%s",
34331
34596
  hours,
34332
34597
  windowSignals.length,
34333
34598
  windowEvaluationsCount,
34334
34599
  windowClosedTrades.length,
34335
- connectorName,
34600
+ connectorNames.join(",") || fallbackConnectorName,
34601
+ scopesCount,
34336
34602
  flags27.user
34337
34603
  );
34338
34604
  const debugAttachment = shouldAttachDebugReport(flags27.debugAttachment) ? await buildRuntimeDebugReportAttachment({