@tradejs/cli 2.0.8 → 2.0.9

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,
@@ -28353,6 +28408,7 @@ var init_runtimeStrategies = __esm({
28353
28408
  universe: configUniverse,
28354
28409
  accountId: effectiveAccountId,
28355
28410
  strategyCreator,
28411
+ sourceStrategyConfig: strategyConfig,
28356
28412
  strategyConfig: {
28357
28413
  ...strategyConfig,
28358
28414
  ...deploymentStrategy?.config,
@@ -28652,7 +28708,7 @@ var init_runtimeLineage = __esm({
28652
28708
  });
28653
28709
 
28654
28710
  // src/lib/signals/evaluations.ts
28655
- var import_redis19, runtimeLineageScopeCache, runtimeLineageScopeCacheDayKey, buildRuntimeSignalEvaluationId, saveRuntimeSignalEvaluation;
28711
+ var import_redis19, runtimeLineageScopeCache, runtimeLineageScopeCacheDayKey, runtimeLineageScopeCacheLoaded, buildRuntimeSignalEvaluationId, buildLineageField, flushRuntimeSignalEvaluations, createRuntimeSignalEvaluationBuffer;
28656
28712
  var init_evaluations = __esm({
28657
28713
  "src/lib/signals/evaluations.ts"() {
28658
28714
  "use strict";
@@ -28661,79 +28717,140 @@ var init_evaluations = __esm({
28661
28717
  init_runtimeLineage();
28662
28718
  runtimeLineageScopeCache = /* @__PURE__ */ new Map();
28663
28719
  runtimeLineageScopeCacheDayKey = "";
28720
+ runtimeLineageScopeCacheLoaded = false;
28664
28721
  buildRuntimeSignalEvaluationId = ({
28665
28722
  strategyName,
28666
28723
  symbol,
28667
28724
  timestamp,
28668
28725
  runtimeConfigId
28669
28726
  }) => 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;
28727
+ buildLineageField = (scope) => [
28728
+ scope.strategy,
28729
+ scope.runtimeConfigId ?? "config",
28730
+ scope.symbol,
28731
+ runtimeLineageKey(scope.lineage)
28732
+ ].join(":");
28733
+ flushRuntimeSignalEvaluations = async (evaluations) => {
28734
+ if (evaluations.length === 0) return;
28735
+ const evaluationsByDay = /* @__PURE__ */ new Map();
28736
+ for (const evaluation of evaluations) {
28737
+ const dayKey = (0, import_time6.getRuntimeStorageDayKey)(evaluation.timestamp);
28738
+ const dayEvaluations = evaluationsByDay.get(dayKey) ?? [];
28739
+ dayEvaluations.push(evaluation);
28740
+ evaluationsByDay.set(dayKey, dayEvaluations);
28675
28741
  }
28676
28742
  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(":");
28743
+ for (const [dayKey, dayEvaluations] of evaluationsByDay) {
28744
+ if (runtimeLineageScopeCacheDayKey !== dayKey) {
28745
+ runtimeLineageScopeCache.clear();
28746
+ runtimeLineageScopeCacheDayKey = dayKey;
28747
+ runtimeLineageScopeCacheLoaded = false;
28748
+ }
28684
28749
  const lineageBucket = import_redis19.redisKeys.runtimeLineageScopeBucket(
28685
- evaluation.userName,
28750
+ dayEvaluations[0].userName,
28686
28751
  dayKey
28687
28752
  );
28688
- const cacheKey = `${lineageBucket}:${lineageField}`;
28689
- const existing = runtimeLineageScopeCache.get(cacheKey) ?? await (0, import_redis19.getHashJsonField)(
28690
- lineageBucket,
28691
- lineageField
28753
+ const lineageEvaluations = dayEvaluations.filter(
28754
+ (evaluation) => evaluation.runtimeLineage != null
28692
28755
  );
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(
28756
+ if (lineageEvaluations.length > 0 && !runtimeLineageScopeCacheLoaded) {
28757
+ const storedScopes = await (0, import_redis19.getHashJsonValues)(lineageBucket);
28758
+ for (const scope of storedScopes) {
28759
+ const lineageField = buildLineageField(scope);
28760
+ runtimeLineageScopeCache.set(`${lineageBucket}:${lineageField}`, scope);
28761
+ }
28762
+ runtimeLineageScopeCacheLoaded = true;
28763
+ }
28764
+ const lineageWrites = /* @__PURE__ */ new Map();
28765
+ for (const evaluation of lineageEvaluations) {
28766
+ const lineageField = buildLineageField({
28767
+ strategy: evaluation.strategy,
28768
+ runtimeConfigId: evaluation.runtimeConfigId,
28769
+ symbol: evaluation.symbol,
28770
+ lineage: evaluation.runtimeLineage
28771
+ });
28772
+ const cacheKey = `${lineageBucket}:${lineageField}`;
28773
+ const existing = runtimeLineageScopeCache.get(cacheKey);
28774
+ const scope = {
28775
+ strategy: evaluation.strategy,
28776
+ symbol: evaluation.symbol,
28777
+ runtimeConfigId: evaluation.runtimeConfigId,
28778
+ lineage: evaluation.runtimeLineage,
28779
+ firstTimestamp: Math.min(
28780
+ existing?.firstTimestamp ?? evaluation.timestamp,
28781
+ evaluation.timestamp
28782
+ ),
28783
+ lastTimestamp: Math.max(
28784
+ existing?.lastTimestamp ?? evaluation.timestamp,
28785
+ evaluation.timestamp
28786
+ )
28787
+ };
28788
+ runtimeLineageScopeCache.set(cacheKey, scope);
28789
+ lineageWrites.set(lineageField, scope);
28790
+ }
28791
+ if (lineageWrites.size > 0) {
28792
+ await (0, import_redis19.setHashJsonFields)(
28793
+ lineageBucket,
28794
+ [...lineageWrites].map(([field, data]) => ({ field, data })),
28795
+ {
28796
+ expire: RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS
28797
+ }
28798
+ );
28799
+ }
28800
+ const detailedWrites = /* @__PURE__ */ new Map();
28801
+ const statsIncrements = /* @__PURE__ */ new Map();
28802
+ for (const evaluation of dayEvaluations) {
28803
+ if (shouldStoreDetailedRuntimeSignalEvaluation(evaluation)) {
28804
+ const bucket = import_redis19.redisKeys.runtimeSignalEvaluationBucket(
28805
+ evaluation.userName,
28806
+ dayKey,
28807
+ evaluation.strategy
28808
+ );
28809
+ const entries = detailedWrites.get(bucket) ?? [];
28810
+ entries.push({
28811
+ field: evaluation.evaluationId,
28812
+ data: evaluation
28813
+ });
28814
+ detailedWrites.set(bucket, entries);
28815
+ }
28816
+ const statsBucket = import_redis19.redisKeys.runtimeSignalEvaluationStatsBucket(
28715
28817
  evaluation.userName,
28716
28818
  dayKey,
28717
28819
  evaluation.strategy
28718
- ),
28719
- evaluation.evaluationId,
28720
- evaluation,
28721
- {
28722
- expire: runtimeSignalRetentionTtl
28820
+ );
28821
+ const increments = statsIncrements.get(statsBucket) ?? {};
28822
+ for (const [field, increment] of Object.entries(
28823
+ buildRuntimeSignalStatsIncrements(evaluation)
28824
+ )) {
28825
+ increments[field] = (increments[field] ?? 0) + increment;
28723
28826
  }
28724
- );
28827
+ statsIncrements.set(statsBucket, increments);
28828
+ }
28829
+ await Promise.all([
28830
+ ...[...detailedWrites].map(
28831
+ ([bucket, entries]) => (0, import_redis19.setHashJsonFields)(bucket, entries, {
28832
+ expire: runtimeSignalRetentionTtl
28833
+ })
28834
+ ),
28835
+ ...[...statsIncrements].map(
28836
+ ([bucket, increments]) => (0, import_redis19.incrHashFields)(bucket, increments, {
28837
+ expire: runtimeSignalRetentionTtl
28838
+ })
28839
+ )
28840
+ ]);
28725
28841
  }
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
28842
+ };
28843
+ createRuntimeSignalEvaluationBuffer = () => {
28844
+ const evaluations = [];
28845
+ return {
28846
+ save: async (evaluation) => {
28847
+ evaluations.push(evaluation);
28848
+ },
28849
+ flush: async () => {
28850
+ const pending = evaluations.splice(0);
28851
+ await flushRuntimeSignalEvaluations(pending);
28735
28852
  }
28736
- );
28853
+ };
28737
28854
  };
28738
28855
  }
28739
28856
  });
@@ -28840,8 +28957,8 @@ var init_runtimeLifecycle = __esm({
28840
28957
  }
28841
28958
  try {
28842
28959
  const strategy = await create({
28843
- btcBinanceData: [...btcBinanceData ?? []],
28844
- btcCoinbaseData: [...btcCoinbaseData ?? []],
28960
+ btcBinanceData: btcBinanceData ?? [],
28961
+ btcCoinbaseData: btcCoinbaseData ?? [],
28845
28962
  onRuntimeClose
28846
28963
  });
28847
28964
  strategy.__tradejsUpdateReferenceData?.({
@@ -29295,7 +29412,7 @@ var init_signals = __esm({
29295
29412
  lifecycle
29296
29413
  };
29297
29414
  };
29298
- findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCoinbaseData, runtimeStrategies, strategyStats, runtimeCloseNotifications, lifecycle, preloadStart, currentTimestamp, persistStrategyState, universe, interval5, intervalMs2, accountId, deploymentId, instrument, runtimeScopeUniverse) => {
29415
+ 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
29416
  const strategySignals = [];
29300
29417
  const loadCached = (cachedSymbol) => connector.kline({
29301
29418
  symbol: cachedSymbol,
@@ -29304,36 +29421,21 @@ var init_signals = __esm({
29304
29421
  cacheOnly: true,
29305
29422
  interval: interval5
29306
29423
  });
29307
- const [cachedData, btcCachedData, ethCachedData] = universe === "crypto" ? await Promise.all([
29308
- loadCached(symbol),
29309
- loadCached("BTCUSDT"),
29310
- loadCached("ETHUSDT")
29311
- ]) : [await loadCached(symbol), [], []];
29424
+ const cachedData = universe === "crypto" && symbol === "BTCUSDT" ? primaryBtcClosedData : universe === "crypto" && symbol === "ETHUSDT" ? primaryEthClosedData : await loadCached(symbol);
29312
29425
  const closedData = getClosedCandlesForInterval(
29313
29426
  cachedData,
29314
29427
  currentTimestamp,
29315
29428
  intervalMs2
29316
29429
  );
29317
- const closedBtcData = getClosedCandlesForInterval(
29318
- btcCachedData,
29319
- currentTimestamp,
29320
- intervalMs2
29321
- );
29322
- const closedEthData = getClosedCandlesForInterval(
29323
- ethCachedData,
29324
- currentTimestamp,
29325
- intervalMs2
29326
- );
29430
+ const closedBtcData = primaryBtcClosedData;
29431
+ const closedEthData = primaryEthClosedData;
29327
29432
  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(
29433
+ const alignedEthCandles = alignedCoinCandles.map((candle) => primaryEthByTimestamp.get(candle.timestamp)).filter(
29332
29434
  (candle) => Boolean(candle)
29333
29435
  );
29334
29436
  const lastCandle = alignedCoinCandles.at(-1);
29335
29437
  const btcLastCandle = alignedBtcCandles.at(-1);
29336
- const ethLastCandle = lastCandle == null ? void 0 : ethByTimestamp.get(lastCandle.timestamp);
29438
+ const ethLastCandle = lastCandle == null ? void 0 : primaryEthByTimestamp.get(lastCandle.timestamp);
29337
29439
  if (!lastCandle || !btcLastCandle) {
29338
29440
  return strategySignals;
29339
29441
  }
@@ -29347,6 +29449,7 @@ var init_signals = __esm({
29347
29449
  strategyName,
29348
29450
  configId,
29349
29451
  strategyCreator,
29452
+ sourceStrategyConfig,
29350
29453
  strategyConfig,
29351
29454
  strategyResults
29352
29455
  } = runtimeStrategy;
@@ -29381,46 +29484,61 @@ var init_signals = __esm({
29381
29484
  configId
29382
29485
  });
29383
29486
  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
- });
29487
+ const initialDataLength = previousData.length;
29488
+ const initialBtcDataLength = previousBtcData.length;
29489
+ const initialEthDataLength = previousEthData.length;
29490
+ let evaluation;
29491
+ try {
29492
+ evaluation = await lifecycle.evaluate({
29493
+ key: lifecycleKey,
29494
+ timestamp: lastCandle.timestamp,
29495
+ config: {
29496
+ runtimeConfig,
29497
+ sourceStrategyConfig,
29498
+ symbolResultConfig: strategyResults?.[symbol]?.config ?? null
29499
+ },
29500
+ btcBinanceData,
29501
+ btcCoinbaseData,
29502
+ onRuntimeClose: (event) => {
29503
+ runtimeCloseNotifications.push(event);
29504
+ },
29505
+ create: async ({
29506
+ btcBinanceData: lifecycleBtcBinanceData,
29507
+ btcCoinbaseData: lifecycleBtcCoinbaseData,
29508
+ onRuntimeClose
29509
+ }) => strategyCreator({
29510
+ userName: flags26.user,
29511
+ connectorName,
29512
+ runtimeConfigId: configId,
29513
+ runtimeConfigSnapshot: {
29514
+ userConfig: sourceStrategyConfig,
29515
+ symbolResultConfig: strategyResults?.[symbol]?.config ?? null
29516
+ },
29517
+ connector,
29518
+ symbol,
29519
+ ...runtimeScopeUniverse ? {
29520
+ universe,
29521
+ assetClass: instrument?.assetClass,
29522
+ instrument,
29523
+ accountId,
29524
+ deploymentId
29525
+ } : {},
29526
+ data: previousData,
29527
+ btcData: universe === "crypto" ? previousBtcData : [],
29528
+ ethData: universe === "crypto" ? previousEthData : [],
29529
+ btcBinanceData: lifecycleBtcBinanceData,
29530
+ btcCoinbaseData: lifecycleBtcCoinbaseData,
29531
+ config: runtimeConfig,
29532
+ ...persistStrategyState ? { sharedStrategyStateKey: lifecycleKey } : {},
29533
+ onRuntimeClose
29534
+ }),
29535
+ run: (strategy) => strategy(lastCandle, btcLastCandle, ethLastCandle)
29536
+ });
29537
+ } finally {
29538
+ previousData.length = initialDataLength;
29539
+ previousBtcData.length = initialBtcDataLength;
29540
+ previousEthData.length = initialEthDataLength;
29541
+ }
29424
29542
  if (evaluation.action === "duplicate" || evaluation.action === "stale") {
29425
29543
  continue;
29426
29544
  }
@@ -29441,7 +29559,7 @@ var init_signals = __esm({
29441
29559
  if (typeof signal === "string") {
29442
29560
  recordStrategyReason(strategyStats, strategyName, signal, "core");
29443
29561
  }
29444
- await saveRuntimeSignalEvaluation({
29562
+ await saveRuntimeSignalEvaluation?.({
29445
29563
  evaluationId: buildRuntimeSignalEvaluationId({
29446
29564
  strategyName,
29447
29565
  symbol,
@@ -29507,7 +29625,7 @@ var init_signals = __esm({
29507
29625
  expire: runtimeSignalRetentionTtl
29508
29626
  }
29509
29627
  );
29510
- await saveRuntimeSignalEvaluation({
29628
+ await saveRuntimeSignalEvaluation?.({
29511
29629
  evaluationId: buildRuntimeSignalEvaluationId({
29512
29630
  strategyName,
29513
29631
  symbol,
@@ -29549,6 +29667,7 @@ var init_signals = __esm({
29549
29667
  let activeSession;
29550
29668
  let projectHooks;
29551
29669
  let afterSignalsHookContext = null;
29670
+ const runtimeSignalEvaluations = createRuntimeSignalEvaluationBuffer();
29552
29671
  try {
29553
29672
  const session = options.session ?? await createSignalsSession(createDefaultSignalsLifecycle());
29554
29673
  activeSession = session;
@@ -29660,7 +29779,12 @@ var init_signals = __esm({
29660
29779
  if (flags26.updateOnly) {
29661
29780
  return;
29662
29781
  }
29663
- const [btcBinanceData, btcCoinbaseData] = universe === "crypto" ? await timeOperation3(
29782
+ const [
29783
+ btcBinanceData,
29784
+ btcCoinbaseData,
29785
+ primaryBtcCachedData,
29786
+ primaryEthCachedData
29787
+ ] = universe === "crypto" ? await timeOperation3(
29664
29788
  "reference candles load",
29665
29789
  () => Promise.all([
29666
29790
  btcReferences.binance.kline({
@@ -29676,9 +29800,36 @@ var init_signals = __esm({
29676
29800
  end: currentTimestamp,
29677
29801
  cacheOnly: true,
29678
29802
  interval: interval5
29803
+ }),
29804
+ marketConnector.kline({
29805
+ symbol: "BTCUSDT",
29806
+ start: preloadStart,
29807
+ end: currentTimestamp,
29808
+ cacheOnly: true,
29809
+ interval: interval5
29810
+ }),
29811
+ marketConnector.kline({
29812
+ symbol: "ETHUSDT",
29813
+ start: preloadStart,
29814
+ end: currentTimestamp,
29815
+ cacheOnly: true,
29816
+ interval: interval5
29679
29817
  })
29680
29818
  ])
29681
- ) : [[], []];
29819
+ ) : [[], [], [], []];
29820
+ const primaryBtcClosedData = getClosedCandlesForInterval(
29821
+ primaryBtcCachedData,
29822
+ currentTimestamp,
29823
+ intervalMs2
29824
+ );
29825
+ const primaryEthClosedData = getClosedCandlesForInterval(
29826
+ primaryEthCachedData,
29827
+ currentTimestamp,
29828
+ intervalMs2
29829
+ );
29830
+ const primaryEthByTimestamp = new Map(
29831
+ primaryEthClosedData.map((candle) => [candle.timestamp, candle])
29832
+ );
29682
29833
  const runtimeStrategies = await loadRuntimeStrategies({
29683
29834
  userName: flags26.user,
29684
29835
  projectRoot: projectRoot12,
@@ -29762,6 +29913,9 @@ var init_signals = __esm({
29762
29913
  marketConnector,
29763
29914
  btcBinanceData,
29764
29915
  btcCoinbaseData,
29916
+ primaryBtcClosedData,
29917
+ primaryEthClosedData,
29918
+ primaryEthByTimestamp,
29765
29919
  runtimeStrategies,
29766
29920
  strategyStats,
29767
29921
  runtimeCloseNotifications,
@@ -29775,7 +29929,8 @@ var init_signals = __esm({
29775
29929
  accountId,
29776
29930
  deployment?.id,
29777
29931
  instrumentsBySymbol.get(symbol),
29778
- sessionUniverse
29932
+ sessionUniverse,
29933
+ runtimeSignalEvaluations.save
29779
29934
  );
29780
29935
  if (strategySignals.length > 0) {
29781
29936
  signals2.push(...strategySignals);
@@ -29816,6 +29971,14 @@ var init_signals = __esm({
29816
29971
  );
29817
29972
  throw error;
29818
29973
  } finally {
29974
+ try {
29975
+ await runtimeSignalEvaluations.flush();
29976
+ } catch (error) {
29977
+ import_logger10.logger.error(
29978
+ "runtime signal evaluation flush failed: %s",
29979
+ error?.message || String(error)
29980
+ );
29981
+ }
29819
29982
  const durationMs = Date.now() - startedAt;
29820
29983
  if (activeSession?.deployment) {
29821
29984
  await (0, import_tradingAccounts3.saveRuntimeDeploymentHeartbeat)(flags26.user, {