@tradejs/node 2.0.15 → 2.0.16

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.
@@ -1252,7 +1252,7 @@ var require_lodash = __commonJS({
1252
1252
  if (typeof func != "function") {
1253
1253
  throw new TypeError2(FUNC_ERROR_TEXT);
1254
1254
  }
1255
- return setTimeout(function() {
1255
+ return setTimeout2(function() {
1256
1256
  func.apply(undefined2, args);
1257
1257
  }, wait);
1258
1258
  }
@@ -2045,7 +2045,7 @@ var require_lodash = __commonJS({
2045
2045
  end = end === undefined2 ? length : end;
2046
2046
  return !start && end >= length ? array : baseSlice(array, start, end);
2047
2047
  }
2048
- var clearTimeout = ctxClearTimeout || function(id) {
2048
+ var clearTimeout2 = ctxClearTimeout || function(id) {
2049
2049
  return root.clearTimeout(id);
2050
2050
  };
2051
2051
  function cloneBuffer(buffer, isDeep) {
@@ -3096,7 +3096,7 @@ var require_lodash = __commonJS({
3096
3096
  return object[key];
3097
3097
  }
3098
3098
  var setData3 = shortOut(baseSetData);
3099
- var setTimeout = ctxSetTimeout || function(func, wait) {
3099
+ var setTimeout2 = ctxSetTimeout || function(func, wait) {
3100
3100
  return root.setTimeout(func, wait);
3101
3101
  };
3102
3102
  var setToString = shortOut(baseSetToString);
@@ -3888,7 +3888,7 @@ var require_lodash = __commonJS({
3888
3888
  }
3889
3889
  function leadingEdge(time) {
3890
3890
  lastInvokeTime = time;
3891
- timerId = setTimeout(timerExpired, wait);
3891
+ timerId = setTimeout2(timerExpired, wait);
3892
3892
  return leading ? invokeFunc(time) : result2;
3893
3893
  }
3894
3894
  function remainingWait(time) {
@@ -3904,7 +3904,7 @@ var require_lodash = __commonJS({
3904
3904
  if (shouldInvoke(time)) {
3905
3905
  return trailingEdge(time);
3906
3906
  }
3907
- timerId = setTimeout(timerExpired, remainingWait(time));
3907
+ timerId = setTimeout2(timerExpired, remainingWait(time));
3908
3908
  }
3909
3909
  function trailingEdge(time) {
3910
3910
  timerId = undefined2;
@@ -3916,7 +3916,7 @@ var require_lodash = __commonJS({
3916
3916
  }
3917
3917
  function cancel() {
3918
3918
  if (timerId !== undefined2) {
3919
- clearTimeout(timerId);
3919
+ clearTimeout2(timerId);
3920
3920
  }
3921
3921
  lastInvokeTime = 0;
3922
3922
  lastArgs = lastCallTime = lastThis = timerId = undefined2;
@@ -3934,13 +3934,13 @@ var require_lodash = __commonJS({
3934
3934
  return leadingEdge(lastCallTime);
3935
3935
  }
3936
3936
  if (maxing) {
3937
- clearTimeout(timerId);
3938
- timerId = setTimeout(timerExpired, wait);
3937
+ clearTimeout2(timerId);
3938
+ timerId = setTimeout2(timerExpired, wait);
3939
3939
  return invokeFunc(lastCallTime);
3940
3940
  }
3941
3941
  }
3942
3942
  if (timerId === undefined2) {
3943
- timerId = setTimeout(timerExpired, wait);
3943
+ timerId = setTimeout2(timerExpired, wait);
3944
3944
  }
3945
3945
  return result2;
3946
3946
  }
@@ -6796,6 +6796,15 @@ var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloa
6796
6796
  payload,
6797
6797
  analysis
6798
6798
  }) ?? analysis;
6799
+ var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => {
6800
+ const adapter = getSignalAiAdapter(signal);
6801
+ const strategyAnalysis = adapter.postProcessAnalysis?.({ signal, payload, analysis }) ?? analysis;
6802
+ return adapter.postProcessLocalAnalysis?.({
6803
+ signal,
6804
+ payload,
6805
+ analysis: strategyAnalysis
6806
+ }) ?? strategyAnalysis;
6807
+ };
6799
6808
 
6800
6809
  // src/ai.ts
6801
6810
  var parseAIResponse = (input) => {
@@ -6944,7 +6953,7 @@ Input payload structure:
6944
6953
  \u2022 \`baseContext.relative\`: BTC/ETH relative-strength, benchmark MA bias context, Binance alt-basket breadth, and CoinMarketCap historical global/exchange/index context when available.
6945
6954
  \u2022 \`baseContext.derivatives\`: Coinalyze-aligned derivatives summary when available.
6946
6955
  \u2022 \`baseContext.mtf\`: compact multi-timeframe summary plus only the latest few candles for each timeframe.
6947
- \u2022 \`baseContext.gateFeatures\`: direction-aware, normalized fields derived from baseContext; prefer \`setup\`, \`scores\`, \`confirmations\`, \`conflicts\`, \`risk\`, and \`decisionHints\` for quick gate checks before inspecting raw nested context.
6956
+ \u2022 \`baseContext.gateFeatures\`: compact direction-aware fields derived from baseContext; prefer \`setup\`, \`scores\`, \`conflicts\`, \`risk\`, \`decisionHints\`, \`mtf\`, \`volatility\`, \`participation\`, and \`relative\` for quick gate checks before inspecting raw nested context.
6948
6957
  Always inspect \`payload.additionalIndicators.marketContext\` when present:
6949
6958
  \u2022 \`marketContext.execution.binanceCoinbaseSpread\`: AI-friendly BTC spread view projected from \`payload.additionalIndicators.baseContext.relative.execution.venueSpread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
6950
6959
  \u2022 \`marketContext.participation.trueDelta\`: Binance taker buy/sell volume delta from kline payload when \`source=kline_taker_volume\`; otherwise absent/unavailable.
@@ -7226,7 +7235,7 @@ var runAiPromptLocal = async (signal, options = {}) => {
7226
7235
  const signalDirection = getSignalDirection(signal);
7227
7236
  const deterministicQuality = getDeterministicQuality(gateContext);
7228
7237
  const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
7229
- return postProcessAiAnalysisByStrategy(
7238
+ return postProcessLocalAiAnalysisByStrategy(
7230
7239
  signal,
7231
7240
  {
7232
7241
  direction: approvalAllowedNow ? signalDirection : null,
@@ -7263,10 +7272,10 @@ var import_node_path2 = __toESM(require("path"));
7263
7272
  var import_constants4 = require("@tradejs/core/constants");
7264
7273
  var import_data3 = require("@tradejs/core/data");
7265
7274
  var import_strategies5 = require("@tradejs/core/strategies");
7266
- var import_logger9 = require("@tradejs/infra/logger");
7275
+ var import_logger10 = require("@tradejs/infra/logger");
7267
7276
 
7268
7277
  // src/strategyHelpers/runtime.ts
7269
- var import_logger8 = require("@tradejs/infra/logger");
7278
+ var import_logger9 = require("@tradejs/infra/logger");
7270
7279
  var import_constants3 = require("@tradejs/core/constants");
7271
7280
  var import_ml2 = require("@tradejs/infra/ml");
7272
7281
 
@@ -7529,6 +7538,7 @@ var markRuntimeTradeClosed = async (params) => {
7529
7538
  lastSyncedAt: now()
7530
7539
  };
7531
7540
  const dayKey = (0, import_time.getRuntimeStorageDayKey)(existing.entryTimestamp);
7541
+ const closeDayKey = (0, import_time.getRuntimeStorageDayKey)(next.exitTimestamp);
7532
7542
  try {
7533
7543
  await Promise.all([
7534
7544
  (0, import_redis2.setData)(import_redis2.redisKeys.runtimeTrade(userName, orderId), next, {
@@ -7540,6 +7550,12 @@ var markRuntimeTradeClosed = async (params) => {
7540
7550
  next,
7541
7551
  { expire: import_constants.TTL_1M }
7542
7552
  ),
7553
+ (0, import_redis2.setHashJsonField)(
7554
+ import_redis2.redisKeys.runtimeClosedTradeBucket(userName, closeDayKey),
7555
+ orderId,
7556
+ next,
7557
+ { expire: import_constants.TTL_1M }
7558
+ ),
7543
7559
  (0, import_redis2.delKey)(
7544
7560
  import_redis2.redisKeys.runtimeActiveTrade(
7545
7561
  userName,
@@ -7558,251 +7574,13 @@ var markRuntimeTradeClosed = async (params) => {
7558
7574
  return next;
7559
7575
  };
7560
7576
 
7561
- // src/strategyHelpers/derivativesContext.ts
7562
- var import_indicators2 = require("@tradejs/core/indicators");
7563
- var import_data = require("@tradejs/core/data");
7564
- var import_strategies = require("@tradejs/core/strategies");
7565
- var import_constants2 = require("@tradejs/core/constants");
7566
- var import_timescale = require("@tradejs/infra/timescale");
7567
- var import_logger4 = require("@tradejs/infra/logger");
7568
- var STORED_INTERVALS = ["15m", "1h"];
7569
- var CONTEXT_INTERVALS = ["15m", "1h"];
7570
- var DEFAULT_LOOKBACK_HOURS = 48;
7571
- var PRIMARY_DERIVATIVES_REFERENCE_SYMBOL = import_constants2.DERIVATIVES_CONTEXT_BASE_REFERENCE_SYMBOLS[0];
7572
- var SECONDARY_DERIVATIVES_REFERENCE_SYMBOL = import_constants2.DERIVATIVES_CONTEXT_BASE_REFERENCE_SYMBOLS[1];
7573
- var derivativesContextUnavailable = false;
7574
- var parseEnabledFlag = (value, env) => {
7575
- const normalized = String(value ?? "").trim().toLowerCase();
7576
- if (!normalized) return true;
7577
- if (["1", "true", "yes", "on"].includes(normalized)) return true;
7578
- if (normalized === "backtest") return env === "BACKTEST";
7579
- if (normalized === "live") return env !== "BACKTEST";
7580
- return false;
7581
- };
7582
- var parseBooleanFlag = (value, fallback = false) => {
7583
- const normalized = String(value ?? "").trim().toLowerCase();
7584
- if (!normalized) return fallback;
7585
- if (["1", "true", "yes", "on"].includes(normalized)) return true;
7586
- if (["0", "false", "no", "off"].includes(normalized)) return false;
7587
- return fallback;
7588
- };
7589
- var parseLookbackMs = () => {
7590
- const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
7591
- const normalizedHours = Number.isFinite(hours) && hours > 0 ? hours : DEFAULT_LOOKBACK_HOURS;
7592
- return normalizedHours * 60 * 60 * 1e3;
7593
- };
7594
- var withHourlyFallbackRows = (rowsByInterval) => ({
7595
- "15m": rowsByInterval["15m"] ?? [],
7596
- "1h": (0, import_indicators2.buildCoinalyzeHourlyRowsWithFallback)({
7597
- rows15m: rowsByInterval["15m"],
7598
- fallbackRows1h: rowsByInterval["1h"]
7599
- })
7600
- });
7601
- var getDerivativesContextReferenceSymbols = () => [
7602
- ...(0, import_constants2.resolveDerivativesContextReferenceSymbols)(
7603
- process.env.DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS
7604
- )
7605
- ];
7606
- var normalizeSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
7607
- var getSignalPriceChangePct1h = (signal) => {
7608
- const baseContext = signal.additionalIndicators?.baseContext;
7609
- if (!baseContext || typeof baseContext !== "object" || Array.isArray(baseContext)) {
7610
- return null;
7611
- }
7612
- const raw = typeof baseContext.raw === "object" && baseContext.raw && !Array.isArray(baseContext.raw) ? baseContext.raw : null;
7613
- const price = raw && typeof raw.price === "object" && raw.price && !Array.isArray(raw.price) ? raw.price : null;
7614
- const value = price?.price1hPct;
7615
- const numeric = typeof value === "number" ? value : Number(value);
7616
- return Number.isFinite(numeric) ? numeric : null;
7617
- };
7618
- var resolvePrimaryReferenceSymbol = () => PRIMARY_DERIVATIVES_REFERENCE_SYMBOL;
7619
- var resolveSecondaryReferenceSymbol = () => SECONDARY_DERIVATIVES_REFERENCE_SYMBOL;
7620
- var getPrimaryIntervalContext = (context) => context?.intervals["15m"] ?? context?.intervals["1h"] ?? null;
7621
- var hasDerivativesSymbolData = (context) => Object.keys(context.intervals).length > 0 && !context.summary.riskFlags.includes("missing_derivatives");
7622
- var toFiniteNumberOrNull = (value) => {
7623
- if (typeof value === "number" && Number.isFinite(value)) return value;
7624
- if (typeof value === "string" && value.trim()) {
7625
- const parsed = Number(value);
7626
- return Number.isFinite(parsed) ? parsed : null;
7627
- }
7628
- return null;
7629
- };
7630
- var roundNullable = (value, digits = 4) => {
7631
- if (value == null || !Number.isFinite(value)) return null;
7632
- const multiplier = 10 ** digits;
7633
- return Math.round(value * multiplier) / multiplier;
7634
- };
7635
- var deltaNullable = (targetValue, referenceValue) => {
7636
- const target = toFiniteNumberOrNull(targetValue);
7637
- const reference = toFiniteNumberOrNull(referenceValue);
7638
- return target == null || reference == null ? null : roundNullable(target - reference);
7639
- };
7640
- var buildTargetDerivedContext = (params) => {
7641
- const { targetContext, primaryReferenceContext } = params;
7642
- const targetPrimary = getPrimaryIntervalContext(targetContext);
7643
- const referencePrimary = getPrimaryIntervalContext(primaryReferenceContext);
7644
- const targetDirectionAligned = targetContext.summary.directionAligned;
7645
- const referenceDirectionAligned = primaryReferenceContext?.summary.directionAligned ?? null;
7646
- return {
7647
- available: hasDerivativesSymbolData(targetContext),
7648
- stale: targetContext.summary.riskFlags.includes("stale_derivatives") || targetPrimary?.stale === true ? true : targetPrimary == null ? null : false,
7649
- sourceSymbol: targetContext.symbol,
7650
- referenceSymbol: primaryReferenceContext?.symbol ?? null,
7651
- directionAligned: targetDirectionAligned,
7652
- referenceDirectionAligned,
7653
- pressure: targetContext.summary.pressure ?? null,
7654
- referencePressure: primaryReferenceContext?.summary.pressure ?? null,
7655
- riskFlags: targetContext.summary.riskFlags,
7656
- oiChangePct1h: targetPrimary?.oiChangePct1h ?? null,
7657
- oiAcceleration: targetContext.summary.oiAcceleration ?? null,
7658
- fundingRate: targetPrimary?.fundingRate ?? null,
7659
- fundingZScore: targetPrimary?.fundingZScore ?? null,
7660
- fundingChange1h: targetContext.summary.fundingChange1h ?? null,
7661
- liqSpikeRatio: targetPrimary?.liqSpikeRatio ?? null,
7662
- liqImbalance: targetPrimary?.liqImbalance ?? null,
7663
- targetVsPrimaryOiChangePct1hDelta: deltaNullable(
7664
- targetPrimary?.oiChangePct1h,
7665
- referencePrimary?.oiChangePct1h
7666
- ),
7667
- targetVsPrimaryFundingZScoreDelta: deltaNullable(
7668
- targetPrimary?.fundingZScore,
7669
- referencePrimary?.fundingZScore
7670
- ),
7671
- targetReferenceConflict: targetDirectionAligned == null || referenceDirectionAligned == null ? null : targetDirectionAligned !== referenceDirectionAligned
7672
- };
7673
- };
7674
- var buildReferenceDerivativesContext = (params) => {
7675
- const {
7676
- targetSymbol,
7677
- primaryReferenceSymbol,
7678
- secondaryReferenceSymbol,
7679
- referenceSymbols,
7680
- referenceContexts,
7681
- targetContext
7682
- } = params;
7683
- const primaryContext = referenceContexts[primaryReferenceSymbol] ?? referenceContexts[referenceSymbols[0]];
7684
- if (!primaryContext) {
7685
- throw new Error("No derivatives reference contexts built");
7686
- }
7687
- const referenceSymbolsMetadata = [
7688
- .../* @__PURE__ */ new Set([
7689
- primaryReferenceSymbol,
7690
- secondaryReferenceSymbol,
7691
- ...referenceSymbols,
7692
- ...Object.keys(referenceContexts)
7693
- ])
7694
- ];
7695
- const targetDerived = targetContext && hasDerivativesSymbolData(targetContext) ? buildTargetDerivedContext({
7696
- targetContext,
7697
- primaryReferenceContext: primaryContext
7698
- }) : void 0;
7699
- return {
7700
- ...primaryContext,
7701
- targetSymbol,
7702
- primaryReferenceSymbol: primaryContext.symbol,
7703
- secondaryReferenceSymbol: referenceContexts[secondaryReferenceSymbol]?.symbol ?? secondaryReferenceSymbol,
7704
- referenceSymbols: referenceSymbolsMetadata,
7705
- referenceContexts,
7706
- ...targetContext && targetDerived ? {
7707
- targetContext,
7708
- targetDerived
7709
- } : {}
7710
- };
7711
- };
7712
- var isDerivativesContextEnabled = (env) => parseEnabledFlag(process.env.DERIVATIVES_CONTEXT_ENABLED, env);
7713
- var isDerivativesTargetContextEnabled = () => parseBooleanFlag(process.env.DERIVATIVES_CONTEXT_TARGET_ENABLED, false);
7714
- var enrichSignalWithDerivativesContext = async (params) => {
7715
- const { signal, env, enabled = isDerivativesContextEnabled(env) } = params;
7716
- if (signal.universe === "tradfi" || !enabled || derivativesContextUnavailable) {
7717
- return false;
7718
- }
7719
- try {
7720
- const referenceSymbols = getDerivativesContextReferenceSymbols();
7721
- const targetSymbol = normalizeSymbol(signal.symbol);
7722
- const lookbackMs = parseLookbackMs();
7723
- const decisionTimeMs = signal.timestamp + (0, import_data.intervalToMs)(signal.interval);
7724
- const derivativesEndMs = (0, import_indicators2.getLastClosedDerivativesBarStartMs)(
7725
- decisionTimeMs,
7726
- "15m"
7727
- );
7728
- const contexts = await Promise.all(
7729
- referenceSymbols.map(async (symbol) => {
7730
- const rowsByInterval = await (0, import_timescale.getDerivativesWindow)({
7731
- symbol,
7732
- intervals: STORED_INTERVALS,
7733
- endMs: derivativesEndMs,
7734
- lookbackMs
7735
- });
7736
- return [
7737
- symbol,
7738
- (0, import_indicators2.buildDerivativesContext)({
7739
- symbol,
7740
- direction: signal.direction,
7741
- timestamp: derivativesEndMs,
7742
- rowsByInterval: withHourlyFallbackRows(rowsByInterval),
7743
- priceChangePct1h: getSignalPriceChangePct1h(signal),
7744
- intervals: CONTEXT_INTERVALS
7745
- })
7746
- ];
7747
- })
7748
- );
7749
- const referenceContexts = Object.fromEntries(contexts);
7750
- const primaryReferenceSymbol = resolvePrimaryReferenceSymbol();
7751
- const secondaryReferenceSymbol = resolveSecondaryReferenceSymbol();
7752
- const targetContextEnabled = isDerivativesTargetContextEnabled();
7753
- const referenceTargetContext = targetContextEnabled && targetSymbol !== primaryReferenceSymbol ? referenceContexts[targetSymbol] : void 0;
7754
- const shouldFetchTargetContext = targetContextEnabled && targetSymbol.length > 0 && !referenceSymbols.some(
7755
- (referenceSymbol) => referenceSymbol === targetSymbol
7756
- );
7757
- const fetchedTargetContext = shouldFetchTargetContext ? await (async () => {
7758
- const rowsByInterval = await (0, import_timescale.getDerivativesWindow)({
7759
- symbol: targetSymbol,
7760
- intervals: STORED_INTERVALS,
7761
- endMs: derivativesEndMs,
7762
- lookbackMs
7763
- });
7764
- const context = (0, import_indicators2.buildDerivativesContext)({
7765
- symbol: targetSymbol,
7766
- direction: signal.direction,
7767
- timestamp: derivativesEndMs,
7768
- rowsByInterval: withHourlyFallbackRows(rowsByInterval),
7769
- priceChangePct1h: getSignalPriceChangePct1h(signal),
7770
- intervals: CONTEXT_INTERVALS
7771
- });
7772
- return hasDerivativesSymbolData(context) ? context : void 0;
7773
- })() : void 0;
7774
- const targetContext = referenceTargetContext && hasDerivativesSymbolData(referenceTargetContext) ? referenceTargetContext : fetchedTargetContext;
7775
- const derivativesContext = buildReferenceDerivativesContext({
7776
- targetSymbol: targetSymbol || signal.symbol,
7777
- primaryReferenceSymbol,
7778
- secondaryReferenceSymbol,
7779
- referenceSymbols,
7780
- referenceContexts,
7781
- targetContext
7782
- });
7783
- signal.additionalIndicators = {
7784
- ...signal.additionalIndicators ?? {},
7785
- baseContext: signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext) ? {
7786
- ...signal.additionalIndicators.baseContext,
7787
- derivatives: derivativesContext
7788
- } : signal.additionalIndicators?.baseContext
7789
- };
7790
- (0, import_strategies.refreshSignalBaseContextGateFeatures)(signal);
7791
- return true;
7792
- } catch (error) {
7793
- derivativesContextUnavailable = true;
7794
- import_logger4.logger.warn(
7795
- "Derivatives context disabled after Timescale read failure: %s",
7796
- String(error)
7797
- );
7798
- return false;
7799
- }
7800
- };
7577
+ // src/strategyHelpers/marketContextStages.ts
7578
+ var import_logger8 = require("@tradejs/infra/logger");
7801
7579
 
7802
7580
  // src/strategyHelpers/binanceMarketContext.ts
7803
- var import_timescale2 = require("@tradejs/infra/timescale");
7804
- var import_logger5 = require("@tradejs/infra/logger");
7805
- var import_strategies2 = require("@tradejs/core/strategies");
7581
+ var import_timescale = require("@tradejs/infra/timescale");
7582
+ var import_logger4 = require("@tradejs/infra/logger");
7583
+ var import_strategies = require("@tradejs/core/strategies");
7806
7584
 
7807
7585
  // src/binanceBreadthUniverses.ts
7808
7586
  var import_node_crypto2 = require("crypto");
@@ -8109,6 +7887,13 @@ var getBinanceBreadthUniverses = () => BINANCE_BREADTH_UNIVERSE_KEYS.map((key) =
8109
7887
  });
8110
7888
  var getPrimaryBinanceBreadthUniverse = () => getBinanceBreadthUniverses().find(({ key }) => key === "top30");
8111
7889
 
7890
+ // src/strategyHelpers/marketContextErrors.ts
7891
+ var MARKET_CONTEXT_CANCELLATION_ERROR_NAMES = /* @__PURE__ */ new Set([
7892
+ "AbortError",
7893
+ "TimescaleQueryTimeoutError"
7894
+ ]);
7895
+ var isMarketContextCancellationError = (error, abortSignal) => abortSignal?.aborted === true || error instanceof Error && MARKET_CONTEXT_CANCELLATION_ERROR_NAMES.has(error.name);
7896
+
8112
7897
  // src/strategyHelpers/binanceMarketContext.ts
8113
7898
  var DEFAULT_MAX_AGE_BY_INTERVAL = {
8114
7899
  "1m": 3 * 6e4,
@@ -8119,7 +7904,7 @@ var DEFAULT_MAX_AGE_BY_INTERVAL = {
8119
7904
  var binanceMarketContextUnavailable = false;
8120
7905
  var referenceRowsCache = /* @__PURE__ */ new Map();
8121
7906
  var breadthCache = /* @__PURE__ */ new Map();
8122
- var parseEnabledFlag2 = (value, env) => {
7907
+ var parseEnabledFlag = (value, env) => {
8123
7908
  const normalized = String(value ?? "").trim().toLowerCase();
8124
7909
  if (!normalized)
8125
7910
  return env === "BACKTEST" || env === "CRON" || env === "PARITY";
@@ -8129,7 +7914,7 @@ var parseEnabledFlag2 = (value, env) => {
8129
7914
  if (normalized === "live") return env !== "BACKTEST";
8130
7915
  return false;
8131
7916
  };
8132
- var toFiniteNumberOrNull2 = (value) => {
7917
+ var toFiniteNumberOrNull = (value) => {
8133
7918
  const numeric = typeof value === "number" ? value : Number(value);
8134
7919
  return Number.isFinite(numeric) ? numeric : null;
8135
7920
  };
@@ -8145,7 +7930,7 @@ var getReferenceSymbols = () => {
8145
7930
  const symbols = (process.env.BINANCE_MARKET_CONTEXT_REFERENCE_SYMBOLS || "BTCUSDT,ETHUSDT").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
8146
7931
  return symbols.length ? [...new Set(symbols)] : ["BTCUSDT", "ETHUSDT"];
8147
7932
  };
8148
- var resolvePrimaryReferenceSymbol2 = (signalSymbol) => {
7933
+ var resolvePrimaryReferenceSymbol = (signalSymbol) => {
8149
7934
  const symbol = signalSymbol.trim().toUpperCase();
8150
7935
  const referenceSymbols = getReferenceSymbols();
8151
7936
  return referenceSymbols.includes(symbol) ? symbol : referenceSymbols[0];
@@ -8153,38 +7938,40 @@ var resolvePrimaryReferenceSymbol2 = (signalSymbol) => {
8153
7938
  var hasBaseContext = (signal) => Boolean(
8154
7939
  signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext)
8155
7940
  );
8156
- var isBinanceMarketContextEnabled = (env) => parseEnabledFlag2(process.env.BINANCE_MARKET_CONTEXT_ENABLED, env);
7941
+ var isBinanceMarketContextEnabled = (env) => parseEnabledFlag(process.env.BINANCE_MARKET_CONTEXT_ENABLED, env);
8157
7942
  var toTradeFlowContext = (row, interval) => row ? {
8158
7943
  source: "binance_agg_trades",
8159
7944
  interval,
8160
7945
  asOfTs: row.ts.getTime(),
8161
7946
  ageMs: row.ageMs,
8162
7947
  stale: row.stale,
8163
- trades: toFiniteNumberOrNull2(row.trades),
8164
- buyPressurePct: toFiniteNumberOrNull2(row.buyPressurePct),
8165
- buyBaseVolume: toFiniteNumberOrNull2(row.buyBaseVolume),
8166
- sellBaseVolume: toFiniteNumberOrNull2(row.sellBaseVolume),
8167
- buyQuoteVolume: toFiniteNumberOrNull2(row.buyQuoteVolume),
8168
- sellQuoteVolume: toFiniteNumberOrNull2(row.sellQuoteVolume),
8169
- netBaseDelta: toFiniteNumberOrNull2(row.netBaseDelta),
8170
- netQuoteDelta: toFiniteNumberOrNull2(row.netQuoteDelta)
7948
+ trades: toFiniteNumberOrNull(row.trades),
7949
+ buyPressurePct: toFiniteNumberOrNull(row.buyPressurePct),
7950
+ buyBaseVolume: toFiniteNumberOrNull(row.buyBaseVolume),
7951
+ sellBaseVolume: toFiniteNumberOrNull(row.sellBaseVolume),
7952
+ buyQuoteVolume: toFiniteNumberOrNull(row.buyQuoteVolume),
7953
+ sellQuoteVolume: toFiniteNumberOrNull(row.sellQuoteVolume),
7954
+ netBaseDelta: toFiniteNumberOrNull(row.netBaseDelta),
7955
+ netQuoteDelta: toFiniteNumberOrNull(row.netQuoteDelta)
8171
7956
  } : null;
8172
7957
  var getCachedReferenceRows = ({
8173
7958
  referenceSymbols,
8174
7959
  interval,
8175
7960
  timestamp,
8176
- maxAgeMs
7961
+ maxAgeMs,
7962
+ abortSignal
8177
7963
  }) => {
8178
7964
  const key = `${referenceSymbols.join(",")}:${interval}:${timestamp}:${maxAgeMs}`;
8179
7965
  const cached = referenceRowsCache.get(key);
8180
7966
  if (cached) return cached;
8181
7967
  const promise = Promise.all(
8182
7968
  referenceSymbols.map(async (symbol) => {
8183
- const tradeFlow = await (0, import_timescale2.getLatestMarketTradeFlow)({
7969
+ const tradeFlow = await (0, import_timescale.getLatestMarketTradeFlow)({
8184
7970
  symbol,
8185
7971
  interval,
8186
7972
  atMs: timestamp,
8187
- maxAgeMs
7973
+ maxAgeMs,
7974
+ ...abortSignal ? { signal: abortSignal } : {}
8188
7975
  });
8189
7976
  return {
8190
7977
  symbol,
@@ -8193,24 +7980,28 @@ var getCachedReferenceRows = ({
8193
7980
  })
8194
7981
  );
8195
7982
  referenceRowsCache.set(key, promise);
7983
+ void promise.catch(() => referenceRowsCache.delete(key));
8196
7984
  return promise;
8197
7985
  };
8198
7986
  var getCachedBreadth = ({
8199
7987
  breadthUniverse,
8200
7988
  interval,
8201
7989
  timestamp,
8202
- maxAgeMs
7990
+ maxAgeMs,
7991
+ abortSignal
8203
7992
  }) => {
8204
7993
  const key = `${breadthUniverse}:${interval}:${timestamp}:${maxAgeMs}`;
8205
7994
  const cached = breadthCache.get(key);
8206
7995
  if (cached) return cached;
8207
- const promise = (0, import_timescale2.getLatestMarketBreadth)({
7996
+ const promise = (0, import_timescale.getLatestMarketBreadth)({
8208
7997
  universe: breadthUniverse,
8209
7998
  interval,
8210
7999
  atMs: timestamp,
8211
- maxAgeMs
8000
+ maxAgeMs,
8001
+ ...abortSignal ? { signal: abortSignal } : {}
8212
8002
  });
8213
8003
  breadthCache.set(key, promise);
8004
+ void promise.catch(() => breadthCache.delete(key));
8214
8005
  return promise;
8215
8006
  };
8216
8007
  var toMarketBreadthContext = (breadth, interval) => ({
@@ -8220,16 +8011,16 @@ var toMarketBreadthContext = (breadth, interval) => ({
8220
8011
  asOfTs: breadth.ts.getTime(),
8221
8012
  ageMs: breadth.ageMs,
8222
8013
  stale: breadth.stale,
8223
- symbolsCount: toFiniteNumberOrNull2(breadth.symbolsCount),
8224
- advancers: toFiniteNumberOrNull2(breadth.advancers),
8225
- decliners: toFiniteNumberOrNull2(breadth.decliners),
8226
- unchanged: toFiniteNumberOrNull2(breadth.unchanged),
8227
- advanceDeclineRatio: toFiniteNumberOrNull2(breadth.advanceDeclineRatio),
8228
- pctAboveMa20: toFiniteNumberOrNull2(breadth.pctAboveMa20),
8229
- pctAboveMa50: toFiniteNumberOrNull2(breadth.pctAboveMa50),
8230
- equalWeightedReturn: toFiniteNumberOrNull2(breadth.equalWeightedReturn),
8231
- volumeWeightedReturn: toFiniteNumberOrNull2(breadth.volumeWeightedReturn),
8232
- dispersion: toFiniteNumberOrNull2(breadth.dispersion)
8014
+ symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
8015
+ advancers: toFiniteNumberOrNull(breadth.advancers),
8016
+ decliners: toFiniteNumberOrNull(breadth.decliners),
8017
+ unchanged: toFiniteNumberOrNull(breadth.unchanged),
8018
+ advanceDeclineRatio: toFiniteNumberOrNull(breadth.advanceDeclineRatio),
8019
+ pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
8020
+ pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
8021
+ equalWeightedReturn: toFiniteNumberOrNull(breadth.equalWeightedReturn),
8022
+ volumeWeightedReturn: toFiniteNumberOrNull(breadth.volumeWeightedReturn),
8023
+ dispersion: toFiniteNumberOrNull(breadth.dispersion)
8233
8024
  });
8234
8025
  var enrichSignalWithBinanceMarketContext = async (params) => {
8235
8026
  const {
@@ -8244,7 +8035,7 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
8244
8035
  }
8245
8036
  try {
8246
8037
  const referenceSymbols = getReferenceSymbols();
8247
- const primaryReferenceSymbol = resolvePrimaryReferenceSymbol2(signal.symbol);
8038
+ const primaryReferenceSymbol = resolvePrimaryReferenceSymbol(signal.symbol);
8248
8039
  const breadthUniverses = params.breadthUniverse ? [
8249
8040
  {
8250
8041
  key: "top30",
@@ -8256,7 +8047,8 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
8256
8047
  referenceSymbols,
8257
8048
  interval,
8258
8049
  timestamp: signal.timestamp,
8259
- maxAgeMs
8050
+ maxAgeMs,
8051
+ abortSignal: params.abortSignal
8260
8052
  }),
8261
8053
  Promise.all(
8262
8054
  breadthUniverses.map(async ({ key, universe }) => ({
@@ -8265,7 +8057,8 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
8265
8057
  breadthUniverse: universe,
8266
8058
  interval,
8267
8059
  timestamp: signal.timestamp,
8268
- maxAgeMs
8060
+ maxAgeMs,
8061
+ abortSignal: params.abortSignal
8269
8062
  })
8270
8063
  }))
8271
8064
  )
@@ -8328,46 +8121,46 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
8328
8121
  asOfTs: primaryBreadth.ts.getTime(),
8329
8122
  ageMs: primaryBreadth.ageMs,
8330
8123
  stale: primaryBreadth.stale,
8331
- btcReturn1h: toFiniteNumberOrNull2(
8124
+ btcReturn1h: toFiniteNumberOrNull(
8332
8125
  primaryBreadth.btcReturn1h
8333
8126
  ),
8334
- btcReturn4h: toFiniteNumberOrNull2(
8127
+ btcReturn4h: toFiniteNumberOrNull(
8335
8128
  primaryBreadth.btcReturn4h
8336
8129
  ),
8337
- btcReturn24h: toFiniteNumberOrNull2(
8130
+ btcReturn24h: toFiniteNumberOrNull(
8338
8131
  primaryBreadth.btcReturn24h
8339
8132
  ),
8340
- altBasketReturn1h: toFiniteNumberOrNull2(
8133
+ altBasketReturn1h: toFiniteNumberOrNull(
8341
8134
  primaryBreadth.altBasketReturn1h
8342
8135
  ),
8343
- altBasketReturn4h: toFiniteNumberOrNull2(
8136
+ altBasketReturn4h: toFiniteNumberOrNull(
8344
8137
  primaryBreadth.altBasketReturn4h
8345
8138
  ),
8346
- altBasketReturn24h: toFiniteNumberOrNull2(
8139
+ altBasketReturn24h: toFiniteNumberOrNull(
8347
8140
  primaryBreadth.altBasketReturn24h
8348
8141
  ),
8349
- btcVsAltReturn1h: toFiniteNumberOrNull2(
8142
+ btcVsAltReturn1h: toFiniteNumberOrNull(
8350
8143
  primaryBreadth.btcVsAltReturn1h
8351
8144
  ),
8352
- btcVsAltReturn4h: toFiniteNumberOrNull2(
8145
+ btcVsAltReturn4h: toFiniteNumberOrNull(
8353
8146
  primaryBreadth.btcVsAltReturn4h
8354
8147
  ),
8355
- btcVsAltReturn24h: toFiniteNumberOrNull2(
8148
+ btcVsAltReturn24h: toFiniteNumberOrNull(
8356
8149
  primaryBreadth.btcVsAltReturn24h
8357
8150
  ),
8358
- btcTurnoverShare1h: toFiniteNumberOrNull2(
8151
+ btcTurnoverShare1h: toFiniteNumberOrNull(
8359
8152
  primaryBreadth.btcTurnoverShare1h
8360
8153
  ),
8361
- btcTurnoverShare24h: toFiniteNumberOrNull2(
8154
+ btcTurnoverShare24h: toFiniteNumberOrNull(
8362
8155
  primaryBreadth.btcTurnoverShare24h
8363
8156
  ),
8364
- btcTurnoverShareChange24h: toFiniteNumberOrNull2(
8157
+ btcTurnoverShareChange24h: toFiniteNumberOrNull(
8365
8158
  primaryBreadth.btcTurnoverShareChange24h
8366
8159
  ),
8367
- altVolToBtcVol24h: toFiniteNumberOrNull2(
8160
+ altVolToBtcVol24h: toFiniteNumberOrNull(
8368
8161
  primaryBreadth.altVolToBtcVol24h
8369
8162
  ),
8370
- altDispersion24h: toFiniteNumberOrNull2(
8163
+ altDispersion24h: toFiniteNumberOrNull(
8371
8164
  primaryBreadth.altDispersion24h
8372
8165
  ),
8373
8166
  regime: primaryBreadth.btcAltRegime ?? "unknown"
@@ -8377,11 +8170,14 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
8377
8170
  }
8378
8171
  }
8379
8172
  };
8380
- (0, import_strategies2.refreshSignalBaseContextGateFeatures)(signal);
8173
+ (0, import_strategies.refreshSignalBaseContextGateFeatures)(signal);
8381
8174
  return true;
8382
8175
  } catch (error) {
8176
+ if (isMarketContextCancellationError(error, params.abortSignal)) {
8177
+ throw error;
8178
+ }
8383
8179
  binanceMarketContextUnavailable = true;
8384
- import_logger5.logger.warn(
8180
+ import_logger4.logger.warn(
8385
8181
  "Binance market context disabled after Timescale read failure: %s",
8386
8182
  String(error)
8387
8183
  );
@@ -8390,9 +8186,9 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
8390
8186
  };
8391
8187
 
8392
8188
  // src/strategyHelpers/coinMarketCapContext.ts
8393
- var import_strategies3 = require("@tradejs/core/strategies");
8394
- var import_logger6 = require("@tradejs/infra/logger");
8395
- var import_timescale3 = require("@tradejs/infra/timescale");
8189
+ var import_strategies2 = require("@tradejs/core/strategies");
8190
+ var import_logger5 = require("@tradejs/infra/logger");
8191
+ var import_timescale2 = require("@tradejs/infra/timescale");
8396
8192
  var DEFAULT_MAX_AGE_MS = 48 * 60 * 6e4;
8397
8193
  var SOURCE_GLOBAL_DAILY = "coinmarketcap_global";
8398
8194
  var SOURCE_REFERENCE = "coinmarketcap_reference_asset";
@@ -8406,7 +8202,7 @@ var referenceContextCache = /* @__PURE__ */ new Map();
8406
8202
  var exchangeLiquidityContextCache = /* @__PURE__ */ new Map();
8407
8203
  var fearGreedContextCache = /* @__PURE__ */ new Map();
8408
8204
  var indexContextCache = /* @__PURE__ */ new Map();
8409
- var parseEnabledFlag3 = (value, env) => {
8205
+ var parseEnabledFlag2 = (value, env) => {
8410
8206
  const normalized = String(value ?? "").trim().toLowerCase();
8411
8207
  if (!normalized) {
8412
8208
  return env === "BACKTEST" || env === "PARITY" || env === "CRON";
@@ -8421,7 +8217,7 @@ var asInt = (value, fallback) => {
8421
8217
  const parsed = Number.parseInt(String(value ?? ""), 10);
8422
8218
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
8423
8219
  };
8424
- var toFiniteNumberOrNull3 = (value) => {
8220
+ var toFiniteNumberOrNull2 = (value) => {
8425
8221
  const numeric = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
8426
8222
  return Number.isFinite(numeric) ? numeric : null;
8427
8223
  };
@@ -8500,86 +8296,101 @@ var toIndexRegime = ({
8500
8296
  };
8501
8297
  var getCachedGlobalContext = ({
8502
8298
  timestamp,
8503
- maxAgeMs
8299
+ maxAgeMs,
8300
+ abortSignal
8504
8301
  }) => {
8505
8302
  const key = `${SOURCE_GLOBAL_DAILY}:${timestamp}:${maxAgeMs}`;
8506
8303
  const cached = globalContextCache.get(key);
8507
8304
  if (cached) return cached;
8508
- const promise = (0, import_timescale3.getLatestMarketGlobalContext)({
8305
+ const promise = (0, import_timescale2.getLatestMarketGlobalContext)({
8509
8306
  source: SOURCE_GLOBAL_DAILY,
8510
8307
  atMs: timestamp,
8511
- maxAgeMs
8308
+ maxAgeMs,
8309
+ ...abortSignal ? { signal: abortSignal } : {}
8512
8310
  });
8513
8311
  globalContextCache.set(key, promise);
8312
+ void promise.catch(() => globalContextCache.delete(key));
8514
8313
  return promise;
8515
8314
  };
8516
8315
  var getCachedReferenceContexts = ({
8517
8316
  timestamp,
8518
- maxAgeMs
8317
+ maxAgeMs,
8318
+ abortSignal
8519
8319
  }) => {
8520
8320
  const key = `${SOURCE_REFERENCE}:1d:${timestamp}:${maxAgeMs}`;
8521
8321
  const cached = referenceContextCache.get(key);
8522
8322
  if (cached) return cached;
8523
- const promise = (0, import_timescale3.getLatestMarketReferenceAssetContexts)({
8323
+ const promise = (0, import_timescale2.getLatestMarketReferenceAssetContexts)({
8524
8324
  source: SOURCE_REFERENCE,
8525
8325
  symbols: ["BTCUSDT", "ETHUSDT"],
8526
8326
  interval: "1d",
8527
8327
  atMs: timestamp,
8528
- maxAgeMs
8328
+ maxAgeMs,
8329
+ ...abortSignal ? { signal: abortSignal } : {}
8529
8330
  });
8530
8331
  referenceContextCache.set(key, promise);
8332
+ void promise.catch(() => referenceContextCache.delete(key));
8531
8333
  return promise;
8532
8334
  };
8533
8335
  var getCachedExchangeLiquidityContext = ({
8534
8336
  timestamp,
8535
- maxAgeMs
8337
+ maxAgeMs,
8338
+ abortSignal
8536
8339
  }) => {
8537
8340
  const key = `${SOURCE_EXCHANGE_LIQUIDITY}:1d:${timestamp}:${maxAgeMs}`;
8538
8341
  const cached = exchangeLiquidityContextCache.get(key);
8539
8342
  if (cached) return cached;
8540
- const promise = (0, import_timescale3.getLatestMarketCmcExchangeLiquidityContext)({
8343
+ const promise = (0, import_timescale2.getLatestMarketCmcExchangeLiquidityContext)({
8541
8344
  source: SOURCE_EXCHANGE_LIQUIDITY,
8542
8345
  interval: "1d",
8543
8346
  atMs: timestamp,
8544
- maxAgeMs
8347
+ maxAgeMs,
8348
+ ...abortSignal ? { signal: abortSignal } : {}
8545
8349
  });
8546
8350
  exchangeLiquidityContextCache.set(key, promise);
8351
+ void promise.catch(() => exchangeLiquidityContextCache.delete(key));
8547
8352
  return promise;
8548
8353
  };
8549
8354
  var getCachedFearGreedContext = ({
8550
8355
  timestamp,
8551
- maxAgeMs
8356
+ maxAgeMs,
8357
+ abortSignal
8552
8358
  }) => {
8553
8359
  const key = `${SOURCE_FEAR_GREED}:1d:${timestamp}:${maxAgeMs}`;
8554
8360
  const cached = fearGreedContextCache.get(key);
8555
8361
  if (cached) return cached;
8556
- const promise = (0, import_timescale3.getLatestMarketCmcFearGreedContext)({
8362
+ const promise = (0, import_timescale2.getLatestMarketCmcFearGreedContext)({
8557
8363
  source: SOURCE_FEAR_GREED,
8558
8364
  interval: "1d",
8559
8365
  atMs: timestamp,
8560
- maxAgeMs
8366
+ maxAgeMs,
8367
+ ...abortSignal ? { signal: abortSignal } : {}
8561
8368
  });
8562
8369
  fearGreedContextCache.set(key, promise);
8370
+ void promise.catch(() => fearGreedContextCache.delete(key));
8563
8371
  return promise;
8564
8372
  };
8565
8373
  var getCachedIndexContexts = ({
8566
8374
  timestamp,
8567
- maxAgeMs
8375
+ maxAgeMs,
8376
+ abortSignal
8568
8377
  }) => {
8569
8378
  const key = `${SOURCE_INDEX}:1d:${timestamp}:${maxAgeMs}`;
8570
8379
  const cached = indexContextCache.get(key);
8571
8380
  if (cached) return cached;
8572
- const promise = (0, import_timescale3.getLatestMarketCmcIndexContexts)({
8381
+ const promise = (0, import_timescale2.getLatestMarketCmcIndexContexts)({
8573
8382
  source: SOURCE_INDEX,
8574
8383
  indexSlugs: ["cmc100", "cmc20"],
8575
8384
  interval: "1d",
8576
8385
  atMs: timestamp,
8577
- maxAgeMs
8386
+ maxAgeMs,
8387
+ ...abortSignal ? { signal: abortSignal } : {}
8578
8388
  });
8579
8389
  indexContextCache.set(key, promise);
8390
+ void promise.catch(() => indexContextCache.delete(key));
8580
8391
  return promise;
8581
8392
  };
8582
- var isCoinMarketCapContextEnabled = (env) => parseEnabledFlag3(process.env.COINMARKETCAP_CONTEXT_ENABLED, env);
8393
+ var isCoinMarketCapContextEnabled = (env) => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_ENABLED, env);
8583
8394
  var enrichSignalWithCoinMarketCapContext = async (params) => {
8584
8395
  const {
8585
8396
  signal,
@@ -8601,27 +8412,33 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
8601
8412
  ] = await Promise.all([
8602
8413
  getCachedGlobalContext({
8603
8414
  timestamp: signal.timestamp,
8604
- maxAgeMs
8415
+ maxAgeMs,
8416
+ abortSignal: params.abortSignal
8605
8417
  }),
8606
8418
  getCachedReferenceContexts({
8607
8419
  timestamp: signal.timestamp,
8608
- maxAgeMs
8420
+ maxAgeMs,
8421
+ abortSignal: params.abortSignal
8609
8422
  }),
8610
8423
  getCachedReferenceContexts({
8611
8424
  timestamp: signal.timestamp - DAY_MS,
8612
- maxAgeMs: maxAgeMs + DAY_MS
8425
+ maxAgeMs: maxAgeMs + DAY_MS,
8426
+ abortSignal: params.abortSignal
8613
8427
  }),
8614
8428
  getCachedExchangeLiquidityContext({
8615
8429
  timestamp: signal.timestamp,
8616
- maxAgeMs
8430
+ maxAgeMs,
8431
+ abortSignal: params.abortSignal
8617
8432
  }),
8618
8433
  getCachedFearGreedContext({
8619
8434
  timestamp: signal.timestamp,
8620
- maxAgeMs
8435
+ maxAgeMs,
8436
+ abortSignal: params.abortSignal
8621
8437
  }),
8622
8438
  getCachedIndexContexts({
8623
8439
  timestamp: signal.timestamp,
8624
- maxAgeMs
8440
+ maxAgeMs,
8441
+ abortSignal: params.abortSignal
8625
8442
  })
8626
8443
  ]);
8627
8444
  const globalRow = globalDailyRow;
@@ -8634,12 +8451,12 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
8634
8451
  const ethRow = references.get("ETHUSDT") ?? null;
8635
8452
  const previousBtcRow = previousReferences.get("BTCUSDT") ?? null;
8636
8453
  const previousEthRow = previousReferences.get("ETHUSDT") ?? null;
8637
- const btcMarketCapUsd = toFiniteNumberOrNull3(btcRow?.marketCapUsd);
8638
- const ethMarketCapUsd = toFiniteNumberOrNull3(ethRow?.marketCapUsd);
8639
- const previousBtcMarketCapUsd = toFiniteNumberOrNull3(
8454
+ const btcMarketCapUsd = toFiniteNumberOrNull2(btcRow?.marketCapUsd);
8455
+ const ethMarketCapUsd = toFiniteNumberOrNull2(ethRow?.marketCapUsd);
8456
+ const previousBtcMarketCapUsd = toFiniteNumberOrNull2(
8640
8457
  previousBtcRow?.marketCapUsd
8641
8458
  );
8642
- const previousEthMarketCapUsd = toFiniteNumberOrNull3(
8459
+ const previousEthMarketCapUsd = toFiniteNumberOrNull2(
8643
8460
  previousEthRow?.marketCapUsd
8644
8461
  );
8645
8462
  const ethBtcMarketCapRatio = safeDivide(ethMarketCapUsd, btcMarketCapUsd);
@@ -8648,16 +8465,16 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
8648
8465
  previousBtcMarketCapUsd
8649
8466
  );
8650
8467
  const ethBtcMarketCapRatioChange24hPct = ethBtcMarketCapRatio != null && previousEthBtcMarketCapRatio != null && previousEthBtcMarketCapRatio > 0 ? (ethBtcMarketCapRatio - previousEthBtcMarketCapRatio) / previousEthBtcMarketCapRatio : null;
8651
- const btcVolumeUsd = toFiniteNumberOrNull3(btcRow?.volumeUsd);
8652
- const ethVolumeUsd = toFiniteNumberOrNull3(ethRow?.volumeUsd);
8468
+ const btcVolumeUsd = toFiniteNumberOrNull2(btcRow?.volumeUsd);
8469
+ const ethVolumeUsd = toFiniteNumberOrNull2(ethRow?.volumeUsd);
8653
8470
  const referenceStale = btcRow?.stale === true || ethRow?.stale === true || !btcRow || !ethRow;
8654
- const btcDominanceChange24hPct = toFiniteNumberOrNull3(
8471
+ const btcDominanceChange24hPct = toFiniteNumberOrNull2(
8655
8472
  globalRow?.btcDominanceChange24hPct
8656
8473
  );
8657
- const altMarketCapChange24hPct = toFiniteNumberOrNull3(
8474
+ const altMarketCapChange24hPct = toFiniteNumberOrNull2(
8658
8475
  globalRow?.altMarketCapChange24hPct
8659
8476
  );
8660
- const altVolumeChange24hPct = toFiniteNumberOrNull3(
8477
+ const altVolumeChange24hPct = toFiniteNumberOrNull2(
8661
8478
  globalRow?.altVolumeChange24hPct
8662
8479
  );
8663
8480
  const altLiquidityRegime = globalRow ? toAltLiquidityRegime({
@@ -8668,196 +8485,445 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
8668
8485
  }) : "unknown";
8669
8486
  const exchangeLiquidityRegime = exchangeLiquidityRow ? toExchangeLiquidityRegime({
8670
8487
  stale: exchangeLiquidityRow.stale,
8671
- totalVolumeChange24hPct: toFiniteNumberOrNull3(
8488
+ totalVolumeChange24hPct: toFiniteNumberOrNull2(
8672
8489
  exchangeLiquidityRow.totalVolumeChange24hPct
8673
8490
  ),
8674
8491
  fallback: exchangeLiquidityRow.liquidityRegime ?? "unknown"
8675
8492
  }) : "unknown";
8676
8493
  const cmc100Row = indexRows.get("cmc100") ?? null;
8677
8494
  const cmc20Row = indexRows.get("cmc20") ?? null;
8678
- const cmc100Value = toFiniteNumberOrNull3(cmc100Row?.value);
8679
- const cmc20Value = toFiniteNumberOrNull3(cmc20Row?.value);
8680
- const cmc100Change24hPct = toFiniteNumberOrNull3(
8495
+ const cmc100Value = toFiniteNumberOrNull2(cmc100Row?.value);
8496
+ const cmc20Value = toFiniteNumberOrNull2(cmc20Row?.value);
8497
+ const cmc100Change24hPct = toFiniteNumberOrNull2(
8681
8498
  cmc100Row?.valueChange24hPct
8682
8499
  );
8683
- const cmc20Change24hPct = toFiniteNumberOrNull3(cmc20Row?.valueChange24hPct);
8684
- const cmc20ToCmc100Ratio = safeDivide(cmc20Value, cmc100Value);
8685
- const cmc20ToCmc100RatioChange24hPct = cmc20Change24hPct != null && cmc100Change24hPct != null ? (1 + cmc20Change24hPct) / (1 + cmc100Change24hPct) - 1 : null;
8686
- const indexStale = cmc100Row?.stale === true || cmc20Row?.stale === true || !cmc100Row || !cmc20Row;
8687
- const indexRegime = toIndexRegime({
8688
- stale: indexStale,
8689
- cmc100Change24hPct,
8690
- cmc20Change24hPct,
8691
- cmc20ToCmc100RatioChange24hPct
8500
+ const cmc20Change24hPct = toFiniteNumberOrNull2(cmc20Row?.valueChange24hPct);
8501
+ const cmc20ToCmc100Ratio = safeDivide(cmc20Value, cmc100Value);
8502
+ const cmc20ToCmc100RatioChange24hPct = cmc20Change24hPct != null && cmc100Change24hPct != null ? (1 + cmc20Change24hPct) / (1 + cmc100Change24hPct) - 1 : null;
8503
+ const indexStale = cmc100Row?.stale === true || cmc20Row?.stale === true || !cmc100Row || !cmc20Row;
8504
+ const indexRegime = toIndexRegime({
8505
+ stale: indexStale,
8506
+ cmc100Change24hPct,
8507
+ cmc20Change24hPct,
8508
+ cmc20ToCmc100RatioChange24hPct
8509
+ });
8510
+ const baseContext = signal.additionalIndicators.baseContext;
8511
+ signal.additionalIndicators = {
8512
+ ...signal.additionalIndicators,
8513
+ baseContext: {
8514
+ ...baseContext,
8515
+ relative: {
8516
+ ...baseContext.relative,
8517
+ ...globalRow ? {
8518
+ cmcGlobal: {
8519
+ source: globalRow.source,
8520
+ interval: "1d",
8521
+ asOfTs: globalRow.ts.getTime(),
8522
+ ageMs: globalRow.ageMs,
8523
+ stale: globalRow.stale,
8524
+ totalMarketCapUsd: toFiniteNumberOrNull2(
8525
+ globalRow.totalMarketCapUsd
8526
+ ),
8527
+ totalVolumeUsd: toFiniteNumberOrNull2(
8528
+ globalRow.totalVolumeUsd
8529
+ ),
8530
+ totalVolumeReportedUsd: toFiniteNumberOrNull2(
8531
+ globalRow.totalVolumeReportedUsd
8532
+ ),
8533
+ altMarketCapUsd: toFiniteNumberOrNull2(
8534
+ globalRow.altMarketCapUsd
8535
+ ),
8536
+ altVolumeUsd: toFiniteNumberOrNull2(globalRow.altVolumeUsd),
8537
+ altVolumeReportedUsd: toFiniteNumberOrNull2(
8538
+ globalRow.altVolumeReportedUsd
8539
+ ),
8540
+ btcDominancePct: toFiniteNumberOrNull2(
8541
+ globalRow.btcDominancePct
8542
+ ),
8543
+ ethDominancePct: toFiniteNumberOrNull2(
8544
+ globalRow.ethDominancePct
8545
+ ),
8546
+ btcDominanceChange24hPct,
8547
+ ethDominanceChange24hPct: toFiniteNumberOrNull2(
8548
+ globalRow.ethDominanceChange24hPct
8549
+ ),
8550
+ altMarketCapChange24hPct,
8551
+ altVolumeChange24hPct,
8552
+ activeCryptocurrencies: toFiniteNumberOrNull2(
8553
+ globalRow.activeCryptocurrencies
8554
+ ),
8555
+ activeExchanges: toFiniteNumberOrNull2(
8556
+ globalRow.activeExchanges
8557
+ ),
8558
+ activeMarketPairs: toFiniteNumberOrNull2(
8559
+ globalRow.activeMarketPairs
8560
+ ),
8561
+ altLiquidityRegime
8562
+ }
8563
+ } : {},
8564
+ ...btcRow || ethRow ? {
8565
+ cmcReferenceAssets: {
8566
+ source: SOURCE_REFERENCE,
8567
+ interval: "1d",
8568
+ asOfTs: Math.max(
8569
+ btcRow?.ts.getTime() ?? 0,
8570
+ ethRow?.ts.getTime() ?? 0
8571
+ ),
8572
+ ageMs: btcRow?.ageMs != null && ethRow?.ageMs != null ? Math.max(btcRow.ageMs, ethRow.ageMs) : btcRow?.ageMs ?? ethRow?.ageMs ?? null,
8573
+ stale: referenceStale,
8574
+ btcMarketCapUsd,
8575
+ ethMarketCapUsd,
8576
+ btcVolumeUsd,
8577
+ ethVolumeUsd,
8578
+ btcVolumeToMarketCap: safeDivide(
8579
+ btcVolumeUsd,
8580
+ btcMarketCapUsd
8581
+ ),
8582
+ ethVolumeToMarketCap: safeDivide(
8583
+ ethVolumeUsd,
8584
+ ethMarketCapUsd
8585
+ ),
8586
+ ethBtcMarketCapRatio,
8587
+ ethBtcMarketCapRatioChange24hPct,
8588
+ ethVsBtcVolumeRatio: safeDivide(ethVolumeUsd, btcVolumeUsd),
8589
+ referenceLiquidityRegime: toReferenceLiquidityRegime({
8590
+ stale: referenceStale,
8591
+ ethBtcMarketCapRatioChange24hPct,
8592
+ ethVsBtcVolumeRatio: safeDivide(ethVolumeUsd, btcVolumeUsd)
8593
+ })
8594
+ }
8595
+ } : {},
8596
+ ...exchangeLiquidityRow ? {
8597
+ cmcExchangeLiquidity: {
8598
+ source: SOURCE_EXCHANGE_LIQUIDITY,
8599
+ interval: exchangeLiquidityRow.interval,
8600
+ asOfTs: exchangeLiquidityRow.ts.getTime(),
8601
+ ageMs: exchangeLiquidityRow.ageMs,
8602
+ stale: exchangeLiquidityRow.stale,
8603
+ exchangesCount: toFiniteNumberOrNull2(
8604
+ exchangeLiquidityRow.exchangesCount
8605
+ ),
8606
+ totalVolumeUsd: toFiniteNumberOrNull2(
8607
+ exchangeLiquidityRow.totalVolumeUsd
8608
+ ),
8609
+ totalVolumeChange24hPct: toFiniteNumberOrNull2(
8610
+ exchangeLiquidityRow.totalVolumeChange24hPct
8611
+ ),
8612
+ binanceVolumeUsd: toFiniteNumberOrNull2(
8613
+ exchangeLiquidityRow.binanceVolumeUsd
8614
+ ),
8615
+ binanceVolumeShare: toFiniteNumberOrNull2(
8616
+ exchangeLiquidityRow.binanceVolumeShare
8617
+ ),
8618
+ topExchangeVolumeShare: toFiniteNumberOrNull2(
8619
+ exchangeLiquidityRow.topExchangeVolumeShare
8620
+ ),
8621
+ liquidityRegime: exchangeLiquidityRegime
8622
+ }
8623
+ } : {},
8624
+ ...fearGreedRow ? {
8625
+ cmcFearGreed: {
8626
+ source: SOURCE_FEAR_GREED,
8627
+ interval: "1d",
8628
+ asOfTs: fearGreedRow.ts.getTime(),
8629
+ ageMs: fearGreedRow.ageMs,
8630
+ stale: fearGreedRow.stale,
8631
+ value: toFiniteNumberOrNull2(fearGreedRow.value),
8632
+ valueChange24h: toFiniteNumberOrNull2(
8633
+ fearGreedRow.valueChange24h
8634
+ ),
8635
+ valueChange7d: toFiniteNumberOrNull2(
8636
+ fearGreedRow.valueChange7d
8637
+ ),
8638
+ classification: fearGreedRow.classification ?? "Unknown",
8639
+ sentimentRegime: fearGreedRow.sentimentRegime ?? "unknown"
8640
+ }
8641
+ } : {},
8642
+ ...cmc100Row || cmc20Row ? {
8643
+ cmcIndexes: {
8644
+ source: SOURCE_INDEX,
8645
+ interval: "1d",
8646
+ asOfTs: Math.max(
8647
+ cmc100Row?.ts.getTime() ?? 0,
8648
+ cmc20Row?.ts.getTime() ?? 0
8649
+ ),
8650
+ ageMs: cmc100Row?.ageMs != null && cmc20Row?.ageMs != null ? Math.max(cmc100Row.ageMs, cmc20Row.ageMs) : cmc100Row?.ageMs ?? cmc20Row?.ageMs ?? null,
8651
+ stale: indexStale,
8652
+ cmc100Value,
8653
+ cmc100Change24hPct,
8654
+ cmc100TopConstituentSymbol: cmc100Row?.topConstituentSymbol ?? null,
8655
+ cmc100TopConstituentWeightPct: toFiniteNumberOrNull2(
8656
+ cmc100Row?.topConstituentWeightPct
8657
+ ),
8658
+ cmc20Value,
8659
+ cmc20Change24hPct,
8660
+ cmc20TopConstituentSymbol: cmc20Row?.topConstituentSymbol ?? null,
8661
+ cmc20TopConstituentWeightPct: toFiniteNumberOrNull2(
8662
+ cmc20Row?.topConstituentWeightPct
8663
+ ),
8664
+ cmc20ToCmc100Ratio,
8665
+ cmc20ToCmc100RatioChange24hPct,
8666
+ indexRegime
8667
+ }
8668
+ } : {}
8669
+ }
8670
+ }
8671
+ };
8672
+ (0, import_strategies2.refreshSignalBaseContextGateFeatures)(signal);
8673
+ return true;
8674
+ } catch (error) {
8675
+ if (isMarketContextCancellationError(error, params.abortSignal)) {
8676
+ throw error;
8677
+ }
8678
+ coinMarketCapContextUnavailable = true;
8679
+ import_logger5.logger.warn(
8680
+ "CoinMarketCap context disabled after Timescale read failure: %s",
8681
+ String(error)
8682
+ );
8683
+ return false;
8684
+ }
8685
+ };
8686
+
8687
+ // src/strategyHelpers/derivativesContext.ts
8688
+ var import_indicators2 = require("@tradejs/core/indicators");
8689
+ var import_data = require("@tradejs/core/data");
8690
+ var import_strategies3 = require("@tradejs/core/strategies");
8691
+ var import_constants2 = require("@tradejs/core/constants");
8692
+ var import_timescale3 = require("@tradejs/infra/timescale");
8693
+ var import_logger6 = require("@tradejs/infra/logger");
8694
+ var STORED_INTERVALS = ["15m", "1h"];
8695
+ var CONTEXT_INTERVALS = ["15m", "1h"];
8696
+ var DEFAULT_LOOKBACK_HOURS = 48;
8697
+ var PRIMARY_DERIVATIVES_REFERENCE_SYMBOL = import_constants2.DERIVATIVES_CONTEXT_BASE_REFERENCE_SYMBOLS[0];
8698
+ var SECONDARY_DERIVATIVES_REFERENCE_SYMBOL = import_constants2.DERIVATIVES_CONTEXT_BASE_REFERENCE_SYMBOLS[1];
8699
+ var derivativesContextUnavailable = false;
8700
+ var parseEnabledFlag3 = (value, env) => {
8701
+ const normalized = String(value ?? "").trim().toLowerCase();
8702
+ if (!normalized) return true;
8703
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
8704
+ if (normalized === "backtest") return env === "BACKTEST";
8705
+ if (normalized === "live") return env !== "BACKTEST";
8706
+ return false;
8707
+ };
8708
+ var parseBooleanFlag = (value, fallback = false) => {
8709
+ const normalized = String(value ?? "").trim().toLowerCase();
8710
+ if (!normalized) return fallback;
8711
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
8712
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
8713
+ return fallback;
8714
+ };
8715
+ var parseLookbackMs = () => {
8716
+ const hours = Number(process.env.DERIVATIVES_CONTEXT_LOOKBACK_HOURS);
8717
+ const normalizedHours = Number.isFinite(hours) && hours > 0 ? hours : DEFAULT_LOOKBACK_HOURS;
8718
+ return normalizedHours * 60 * 60 * 1e3;
8719
+ };
8720
+ var withHourlyFallbackRows = (rowsByInterval) => ({
8721
+ "15m": rowsByInterval["15m"] ?? [],
8722
+ "1h": (0, import_indicators2.buildCoinalyzeHourlyRowsWithFallback)({
8723
+ rows15m: rowsByInterval["15m"],
8724
+ fallbackRows1h: rowsByInterval["1h"]
8725
+ })
8726
+ });
8727
+ var getDerivativesContextReferenceSymbols = () => [
8728
+ ...(0, import_constants2.resolveDerivativesContextReferenceSymbols)(
8729
+ process.env.DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS
8730
+ )
8731
+ ];
8732
+ var normalizeSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
8733
+ var getSignalPriceChangePct1h = (signal) => {
8734
+ const baseContext = signal.additionalIndicators?.baseContext;
8735
+ if (!baseContext || typeof baseContext !== "object" || Array.isArray(baseContext)) {
8736
+ return null;
8737
+ }
8738
+ const raw = typeof baseContext.raw === "object" && baseContext.raw && !Array.isArray(baseContext.raw) ? baseContext.raw : null;
8739
+ const price = raw && typeof raw.price === "object" && raw.price && !Array.isArray(raw.price) ? raw.price : null;
8740
+ const value = price?.price1hPct;
8741
+ const numeric = typeof value === "number" ? value : Number(value);
8742
+ return Number.isFinite(numeric) ? numeric : null;
8743
+ };
8744
+ var resolvePrimaryReferenceSymbol2 = () => PRIMARY_DERIVATIVES_REFERENCE_SYMBOL;
8745
+ var resolveSecondaryReferenceSymbol = () => SECONDARY_DERIVATIVES_REFERENCE_SYMBOL;
8746
+ var getPrimaryIntervalContext = (context) => context?.intervals["15m"] ?? context?.intervals["1h"] ?? null;
8747
+ var hasDerivativesSymbolData = (context) => Object.keys(context.intervals).length > 0 && !context.summary.riskFlags.includes("missing_derivatives");
8748
+ var toFiniteNumberOrNull3 = (value) => {
8749
+ if (typeof value === "number" && Number.isFinite(value)) return value;
8750
+ if (typeof value === "string" && value.trim()) {
8751
+ const parsed = Number(value);
8752
+ return Number.isFinite(parsed) ? parsed : null;
8753
+ }
8754
+ return null;
8755
+ };
8756
+ var roundNullable = (value, digits = 4) => {
8757
+ if (value == null || !Number.isFinite(value)) return null;
8758
+ const multiplier = 10 ** digits;
8759
+ return Math.round(value * multiplier) / multiplier;
8760
+ };
8761
+ var deltaNullable = (targetValue, referenceValue) => {
8762
+ const target = toFiniteNumberOrNull3(targetValue);
8763
+ const reference = toFiniteNumberOrNull3(referenceValue);
8764
+ return target == null || reference == null ? null : roundNullable(target - reference);
8765
+ };
8766
+ var buildTargetDerivedContext = (params) => {
8767
+ const { targetContext, primaryReferenceContext } = params;
8768
+ const targetPrimary = getPrimaryIntervalContext(targetContext);
8769
+ const referencePrimary = getPrimaryIntervalContext(primaryReferenceContext);
8770
+ const targetDirectionAligned = targetContext.summary.directionAligned;
8771
+ const referenceDirectionAligned = primaryReferenceContext?.summary.directionAligned ?? null;
8772
+ return {
8773
+ available: hasDerivativesSymbolData(targetContext),
8774
+ stale: targetContext.summary.riskFlags.includes("stale_derivatives") || targetPrimary?.stale === true ? true : targetPrimary == null ? null : false,
8775
+ sourceSymbol: targetContext.symbol,
8776
+ referenceSymbol: primaryReferenceContext?.symbol ?? null,
8777
+ directionAligned: targetDirectionAligned,
8778
+ referenceDirectionAligned,
8779
+ pressure: targetContext.summary.pressure ?? null,
8780
+ referencePressure: primaryReferenceContext?.summary.pressure ?? null,
8781
+ riskFlags: targetContext.summary.riskFlags,
8782
+ oiChangePct1h: targetPrimary?.oiChangePct1h ?? null,
8783
+ oiAcceleration: targetContext.summary.oiAcceleration ?? null,
8784
+ fundingRate: targetPrimary?.fundingRate ?? null,
8785
+ fundingZScore: targetPrimary?.fundingZScore ?? null,
8786
+ fundingChange1h: targetContext.summary.fundingChange1h ?? null,
8787
+ liqSpikeRatio: targetPrimary?.liqSpikeRatio ?? null,
8788
+ liqImbalance: targetPrimary?.liqImbalance ?? null,
8789
+ targetVsPrimaryOiChangePct1hDelta: deltaNullable(
8790
+ targetPrimary?.oiChangePct1h,
8791
+ referencePrimary?.oiChangePct1h
8792
+ ),
8793
+ targetVsPrimaryFundingZScoreDelta: deltaNullable(
8794
+ targetPrimary?.fundingZScore,
8795
+ referencePrimary?.fundingZScore
8796
+ ),
8797
+ targetReferenceConflict: targetDirectionAligned == null || referenceDirectionAligned == null ? null : targetDirectionAligned !== referenceDirectionAligned
8798
+ };
8799
+ };
8800
+ var buildReferenceDerivativesContext = (params) => {
8801
+ const {
8802
+ targetSymbol,
8803
+ primaryReferenceSymbol,
8804
+ secondaryReferenceSymbol,
8805
+ referenceSymbols,
8806
+ referenceContexts,
8807
+ targetContext
8808
+ } = params;
8809
+ const primaryContext = referenceContexts[primaryReferenceSymbol] ?? referenceContexts[referenceSymbols[0]];
8810
+ if (!primaryContext) {
8811
+ throw new Error("No derivatives reference contexts built");
8812
+ }
8813
+ const referenceSymbolsMetadata = [
8814
+ .../* @__PURE__ */ new Set([
8815
+ primaryReferenceSymbol,
8816
+ secondaryReferenceSymbol,
8817
+ ...referenceSymbols,
8818
+ ...Object.keys(referenceContexts)
8819
+ ])
8820
+ ];
8821
+ const targetDerived = targetContext && hasDerivativesSymbolData(targetContext) ? buildTargetDerivedContext({
8822
+ targetContext,
8823
+ primaryReferenceContext: primaryContext
8824
+ }) : void 0;
8825
+ return {
8826
+ ...primaryContext,
8827
+ targetSymbol,
8828
+ primaryReferenceSymbol: primaryContext.symbol,
8829
+ secondaryReferenceSymbol: referenceContexts[secondaryReferenceSymbol]?.symbol ?? secondaryReferenceSymbol,
8830
+ referenceSymbols: referenceSymbolsMetadata,
8831
+ referenceContexts,
8832
+ ...targetContext && targetDerived ? {
8833
+ targetContext,
8834
+ targetDerived
8835
+ } : {}
8836
+ };
8837
+ };
8838
+ var isDerivativesContextEnabled = (env) => parseEnabledFlag3(process.env.DERIVATIVES_CONTEXT_ENABLED, env);
8839
+ var isDerivativesTargetContextEnabled = () => parseBooleanFlag(process.env.DERIVATIVES_CONTEXT_TARGET_ENABLED, false);
8840
+ var enrichSignalWithDerivativesContext = async (params) => {
8841
+ const { signal, env, enabled = isDerivativesContextEnabled(env) } = params;
8842
+ if (signal.universe === "tradfi" || !enabled || derivativesContextUnavailable) {
8843
+ return false;
8844
+ }
8845
+ try {
8846
+ const referenceSymbols = getDerivativesContextReferenceSymbols();
8847
+ const targetSymbol = normalizeSymbol(signal.symbol);
8848
+ const lookbackMs = parseLookbackMs();
8849
+ const decisionTimeMs = signal.timestamp + (0, import_data.intervalToMs)(signal.interval);
8850
+ const derivativesEndMs = (0, import_indicators2.getLastClosedDerivativesBarStartMs)(
8851
+ decisionTimeMs,
8852
+ "15m"
8853
+ );
8854
+ const contexts = await Promise.all(
8855
+ referenceSymbols.map(async (symbol) => {
8856
+ const rowsByInterval = await (0, import_timescale3.getDerivativesWindow)({
8857
+ symbol,
8858
+ intervals: STORED_INTERVALS,
8859
+ endMs: derivativesEndMs,
8860
+ lookbackMs,
8861
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
8862
+ });
8863
+ return [
8864
+ symbol,
8865
+ (0, import_indicators2.buildDerivativesContext)({
8866
+ symbol,
8867
+ direction: signal.direction,
8868
+ timestamp: derivativesEndMs,
8869
+ rowsByInterval: withHourlyFallbackRows(rowsByInterval),
8870
+ priceChangePct1h: getSignalPriceChangePct1h(signal),
8871
+ intervals: CONTEXT_INTERVALS
8872
+ })
8873
+ ];
8874
+ })
8875
+ );
8876
+ const referenceContexts = Object.fromEntries(contexts);
8877
+ const primaryReferenceSymbol = resolvePrimaryReferenceSymbol2();
8878
+ const secondaryReferenceSymbol = resolveSecondaryReferenceSymbol();
8879
+ const targetContextEnabled = isDerivativesTargetContextEnabled();
8880
+ const referenceTargetContext = targetContextEnabled && targetSymbol !== primaryReferenceSymbol ? referenceContexts[targetSymbol] : void 0;
8881
+ const shouldFetchTargetContext = targetContextEnabled && targetSymbol.length > 0 && !referenceSymbols.some(
8882
+ (referenceSymbol) => referenceSymbol === targetSymbol
8883
+ );
8884
+ const fetchedTargetContext = shouldFetchTargetContext ? await (async () => {
8885
+ const rowsByInterval = await (0, import_timescale3.getDerivativesWindow)({
8886
+ symbol: targetSymbol,
8887
+ intervals: STORED_INTERVALS,
8888
+ endMs: derivativesEndMs,
8889
+ lookbackMs,
8890
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
8891
+ });
8892
+ const context = (0, import_indicators2.buildDerivativesContext)({
8893
+ symbol: targetSymbol,
8894
+ direction: signal.direction,
8895
+ timestamp: derivativesEndMs,
8896
+ rowsByInterval: withHourlyFallbackRows(rowsByInterval),
8897
+ priceChangePct1h: getSignalPriceChangePct1h(signal),
8898
+ intervals: CONTEXT_INTERVALS
8899
+ });
8900
+ return hasDerivativesSymbolData(context) ? context : void 0;
8901
+ })() : void 0;
8902
+ const targetContext = referenceTargetContext && hasDerivativesSymbolData(referenceTargetContext) ? referenceTargetContext : fetchedTargetContext;
8903
+ const derivativesContext = buildReferenceDerivativesContext({
8904
+ targetSymbol: targetSymbol || signal.symbol,
8905
+ primaryReferenceSymbol,
8906
+ secondaryReferenceSymbol,
8907
+ referenceSymbols,
8908
+ referenceContexts,
8909
+ targetContext
8692
8910
  });
8693
- const baseContext = signal.additionalIndicators.baseContext;
8694
8911
  signal.additionalIndicators = {
8695
- ...signal.additionalIndicators,
8696
- baseContext: {
8697
- ...baseContext,
8698
- relative: {
8699
- ...baseContext.relative,
8700
- ...globalRow ? {
8701
- cmcGlobal: {
8702
- source: globalRow.source,
8703
- interval: "1d",
8704
- asOfTs: globalRow.ts.getTime(),
8705
- ageMs: globalRow.ageMs,
8706
- stale: globalRow.stale,
8707
- totalMarketCapUsd: toFiniteNumberOrNull3(
8708
- globalRow.totalMarketCapUsd
8709
- ),
8710
- totalVolumeUsd: toFiniteNumberOrNull3(
8711
- globalRow.totalVolumeUsd
8712
- ),
8713
- totalVolumeReportedUsd: toFiniteNumberOrNull3(
8714
- globalRow.totalVolumeReportedUsd
8715
- ),
8716
- altMarketCapUsd: toFiniteNumberOrNull3(
8717
- globalRow.altMarketCapUsd
8718
- ),
8719
- altVolumeUsd: toFiniteNumberOrNull3(globalRow.altVolumeUsd),
8720
- altVolumeReportedUsd: toFiniteNumberOrNull3(
8721
- globalRow.altVolumeReportedUsd
8722
- ),
8723
- btcDominancePct: toFiniteNumberOrNull3(
8724
- globalRow.btcDominancePct
8725
- ),
8726
- ethDominancePct: toFiniteNumberOrNull3(
8727
- globalRow.ethDominancePct
8728
- ),
8729
- btcDominanceChange24hPct,
8730
- ethDominanceChange24hPct: toFiniteNumberOrNull3(
8731
- globalRow.ethDominanceChange24hPct
8732
- ),
8733
- altMarketCapChange24hPct,
8734
- altVolumeChange24hPct,
8735
- activeCryptocurrencies: toFiniteNumberOrNull3(
8736
- globalRow.activeCryptocurrencies
8737
- ),
8738
- activeExchanges: toFiniteNumberOrNull3(
8739
- globalRow.activeExchanges
8740
- ),
8741
- activeMarketPairs: toFiniteNumberOrNull3(
8742
- globalRow.activeMarketPairs
8743
- ),
8744
- altLiquidityRegime
8745
- }
8746
- } : {},
8747
- ...btcRow || ethRow ? {
8748
- cmcReferenceAssets: {
8749
- source: SOURCE_REFERENCE,
8750
- interval: "1d",
8751
- asOfTs: Math.max(
8752
- btcRow?.ts.getTime() ?? 0,
8753
- ethRow?.ts.getTime() ?? 0
8754
- ),
8755
- ageMs: btcRow?.ageMs != null && ethRow?.ageMs != null ? Math.max(btcRow.ageMs, ethRow.ageMs) : btcRow?.ageMs ?? ethRow?.ageMs ?? null,
8756
- stale: referenceStale,
8757
- btcMarketCapUsd,
8758
- ethMarketCapUsd,
8759
- btcVolumeUsd,
8760
- ethVolumeUsd,
8761
- btcVolumeToMarketCap: safeDivide(
8762
- btcVolumeUsd,
8763
- btcMarketCapUsd
8764
- ),
8765
- ethVolumeToMarketCap: safeDivide(
8766
- ethVolumeUsd,
8767
- ethMarketCapUsd
8768
- ),
8769
- ethBtcMarketCapRatio,
8770
- ethBtcMarketCapRatioChange24hPct,
8771
- ethVsBtcVolumeRatio: safeDivide(ethVolumeUsd, btcVolumeUsd),
8772
- referenceLiquidityRegime: toReferenceLiquidityRegime({
8773
- stale: referenceStale,
8774
- ethBtcMarketCapRatioChange24hPct,
8775
- ethVsBtcVolumeRatio: safeDivide(ethVolumeUsd, btcVolumeUsd)
8776
- })
8777
- }
8778
- } : {},
8779
- ...exchangeLiquidityRow ? {
8780
- cmcExchangeLiquidity: {
8781
- source: SOURCE_EXCHANGE_LIQUIDITY,
8782
- interval: exchangeLiquidityRow.interval,
8783
- asOfTs: exchangeLiquidityRow.ts.getTime(),
8784
- ageMs: exchangeLiquidityRow.ageMs,
8785
- stale: exchangeLiquidityRow.stale,
8786
- exchangesCount: toFiniteNumberOrNull3(
8787
- exchangeLiquidityRow.exchangesCount
8788
- ),
8789
- totalVolumeUsd: toFiniteNumberOrNull3(
8790
- exchangeLiquidityRow.totalVolumeUsd
8791
- ),
8792
- totalVolumeChange24hPct: toFiniteNumberOrNull3(
8793
- exchangeLiquidityRow.totalVolumeChange24hPct
8794
- ),
8795
- binanceVolumeUsd: toFiniteNumberOrNull3(
8796
- exchangeLiquidityRow.binanceVolumeUsd
8797
- ),
8798
- binanceVolumeShare: toFiniteNumberOrNull3(
8799
- exchangeLiquidityRow.binanceVolumeShare
8800
- ),
8801
- topExchangeVolumeShare: toFiniteNumberOrNull3(
8802
- exchangeLiquidityRow.topExchangeVolumeShare
8803
- ),
8804
- liquidityRegime: exchangeLiquidityRegime
8805
- }
8806
- } : {},
8807
- ...fearGreedRow ? {
8808
- cmcFearGreed: {
8809
- source: SOURCE_FEAR_GREED,
8810
- interval: "1d",
8811
- asOfTs: fearGreedRow.ts.getTime(),
8812
- ageMs: fearGreedRow.ageMs,
8813
- stale: fearGreedRow.stale,
8814
- value: toFiniteNumberOrNull3(fearGreedRow.value),
8815
- valueChange24h: toFiniteNumberOrNull3(
8816
- fearGreedRow.valueChange24h
8817
- ),
8818
- valueChange7d: toFiniteNumberOrNull3(
8819
- fearGreedRow.valueChange7d
8820
- ),
8821
- classification: fearGreedRow.classification ?? "Unknown",
8822
- sentimentRegime: fearGreedRow.sentimentRegime ?? "unknown"
8823
- }
8824
- } : {},
8825
- ...cmc100Row || cmc20Row ? {
8826
- cmcIndexes: {
8827
- source: SOURCE_INDEX,
8828
- interval: "1d",
8829
- asOfTs: Math.max(
8830
- cmc100Row?.ts.getTime() ?? 0,
8831
- cmc20Row?.ts.getTime() ?? 0
8832
- ),
8833
- ageMs: cmc100Row?.ageMs != null && cmc20Row?.ageMs != null ? Math.max(cmc100Row.ageMs, cmc20Row.ageMs) : cmc100Row?.ageMs ?? cmc20Row?.ageMs ?? null,
8834
- stale: indexStale,
8835
- cmc100Value,
8836
- cmc100Change24hPct,
8837
- cmc100TopConstituentSymbol: cmc100Row?.topConstituentSymbol ?? null,
8838
- cmc100TopConstituentWeightPct: toFiniteNumberOrNull3(
8839
- cmc100Row?.topConstituentWeightPct
8840
- ),
8841
- cmc20Value,
8842
- cmc20Change24hPct,
8843
- cmc20TopConstituentSymbol: cmc20Row?.topConstituentSymbol ?? null,
8844
- cmc20TopConstituentWeightPct: toFiniteNumberOrNull3(
8845
- cmc20Row?.topConstituentWeightPct
8846
- ),
8847
- cmc20ToCmc100Ratio,
8848
- cmc20ToCmc100RatioChange24hPct,
8849
- indexRegime
8850
- }
8851
- } : {}
8852
- }
8853
- }
8912
+ ...signal.additionalIndicators ?? {},
8913
+ baseContext: signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext) ? {
8914
+ ...signal.additionalIndicators.baseContext,
8915
+ derivatives: derivativesContext
8916
+ } : signal.additionalIndicators?.baseContext
8854
8917
  };
8855
8918
  (0, import_strategies3.refreshSignalBaseContextGateFeatures)(signal);
8856
8919
  return true;
8857
8920
  } catch (error) {
8858
- coinMarketCapContextUnavailable = true;
8921
+ if (isMarketContextCancellationError(error, params.abortSignal)) {
8922
+ throw error;
8923
+ }
8924
+ derivativesContextUnavailable = true;
8859
8925
  import_logger6.logger.warn(
8860
- "CoinMarketCap context disabled after Timescale read failure: %s",
8926
+ "Derivatives context disabled after Timescale read failure: %s",
8861
8927
  String(error)
8862
8928
  );
8863
8929
  return false;
@@ -9092,7 +9158,7 @@ var resolveHyperliquidPerpFromSignalSymbol = (symbol) => {
9092
9158
  var MAX_CACHE_ENTRIES = 2048;
9093
9159
  var MAX_COVERAGE_SERIES_CACHE_ENTRIES = 20;
9094
9160
  var MAX_FLOW_SERIES_CACHE_ENTRIES = 64;
9095
- var SERIES_CHUNK_MS = 30 * 24 * 60 * 6e4;
9161
+ var SERIES_CHUNK_MS = 90 * 24 * 60 * 6e4;
9096
9162
  var MAX_SERIES_LOOKBACK_MS = 60 * 6e4;
9097
9163
  var DEFAULT_MIN_COVERAGE_PCT = 0.8;
9098
9164
  var INTERVAL_MS = {
@@ -9140,6 +9206,7 @@ var setBoundedCache = (key, value) => {
9140
9206
  if (oldestKey != null) contextCache.delete(oldestKey);
9141
9207
  }
9142
9208
  contextCache.set(key, value);
9209
+ void value.catch(() => contextCache.delete(key));
9143
9210
  };
9144
9211
  var setBoundedSeriesCache = (cache, maxEntries, key, value) => {
9145
9212
  if (cache.size >= maxEntries) {
@@ -9147,6 +9214,7 @@ var setBoundedSeriesCache = (cache, maxEntries, key, value) => {
9147
9214
  if (oldestKey != null) cache.delete(oldestKey);
9148
9215
  }
9149
9216
  cache.set(key, value);
9217
+ void value.catch(() => cache.delete(key));
9150
9218
  };
9151
9219
  var uniqueAddressCount = (rows, field) => {
9152
9220
  const addresses = /* @__PURE__ */ new Set();
@@ -9261,7 +9329,8 @@ var loadHyperliquidWhaleFlowAggregateFromSeries = async (params) => {
9261
9329
  fromMs,
9262
9330
  toMs,
9263
9331
  universeFingerprint: params.universeFingerprint,
9264
- whaleRegistryFingerprint: params.whaleRegistryFingerprint
9332
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint,
9333
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
9265
9334
  });
9266
9335
  setBoundedSeriesCache(
9267
9336
  coverageSeriesCache,
@@ -9278,7 +9347,8 @@ var loadHyperliquidWhaleFlowAggregateFromSeries = async (params) => {
9278
9347
  fromMs,
9279
9348
  toMs,
9280
9349
  universeFingerprint: params.universeFingerprint,
9281
- whaleRegistryFingerprint: params.whaleRegistryFingerprint
9350
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint,
9351
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
9282
9352
  });
9283
9353
  setBoundedSeriesCache(
9284
9354
  flowSeriesCache,
@@ -9362,7 +9432,8 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
9362
9432
  decisionTimeMs,
9363
9433
  maxAgeMs,
9364
9434
  universeFingerprint: universe.fingerprint,
9365
- whaleRegistryFingerprint: whales.fingerprint
9435
+ whaleRegistryFingerprint: whales.fingerprint,
9436
+ abortSignal: params.abortSignal
9366
9437
  });
9367
9438
  } else {
9368
9439
  let pending = contextCache.get(cacheKey);
@@ -9373,7 +9444,8 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
9373
9444
  decisionTimeMs,
9374
9445
  maxAgeMs,
9375
9446
  universeFingerprint: universe.fingerprint,
9376
- whaleRegistryFingerprint: whales.fingerprint
9447
+ whaleRegistryFingerprint: whales.fingerprint,
9448
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
9377
9449
  });
9378
9450
  setBoundedCache(cacheKey, pending);
9379
9451
  }
@@ -9382,6 +9454,9 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
9382
9454
  if (!row) return null;
9383
9455
  return toBaseHyperliquidWhaleFlowContext(row, minimumCoveragePct);
9384
9456
  } catch (error) {
9457
+ if (isMarketContextCancellationError(error, params.abortSignal)) {
9458
+ throw error;
9459
+ }
9385
9460
  hyperliquidWhaleContextUnavailable = true;
9386
9461
  import_logger7.logger.warn(
9387
9462
  "Hyperliquid whale context disabled after Timescale read failure: %s",
@@ -9402,7 +9477,8 @@ var enrichSignalWithHyperliquidWhaleContext = async (params) => {
9402
9477
  env,
9403
9478
  enabled: params.enabled,
9404
9479
  marketInterval: params.interval,
9405
- maxAgeMs: params.maxAgeMs
9480
+ maxAgeMs: params.maxAgeMs,
9481
+ abortSignal: params.abortSignal
9406
9482
  });
9407
9483
  if (!hyperliquidWhales) return false;
9408
9484
  const baseContext = signal.additionalIndicators.baseContext;
@@ -9420,6 +9496,125 @@ var enrichSignalWithHyperliquidWhaleContext = async (params) => {
9420
9496
  return true;
9421
9497
  };
9422
9498
 
9499
+ // src/strategyHelpers/marketContextStages.ts
9500
+ var STAGE_ENV_KEYS = {
9501
+ binance: "BINANCE_MARKET_CONTEXT_STAGE_TIMEOUT_MS",
9502
+ coinmarketcap: "COINMARKETCAP_CONTEXT_STAGE_TIMEOUT_MS",
9503
+ derivatives: "DERIVATIVES_CONTEXT_STAGE_TIMEOUT_MS",
9504
+ hyperliquidWhales: "HYPERLIQUID_WHALE_CONTEXT_STAGE_TIMEOUT_MS"
9505
+ };
9506
+ var parsePositiveInt = (value) => {
9507
+ const parsed = Number.parseInt(String(value ?? ""), 10);
9508
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
9509
+ };
9510
+ var resolveMarketContextStageTimeoutMs = (stage) => parsePositiveInt(process.env[STAGE_ENV_KEYS[stage]]) ?? parsePositiveInt(process.env.MARKET_CONTEXT_STAGE_TIMEOUT_MS) ?? 35e3;
9511
+ var runMarketContextStage = async ({
9512
+ stage,
9513
+ parentSignal,
9514
+ operation,
9515
+ onStart,
9516
+ onComplete
9517
+ }) => {
9518
+ const controller = new AbortController();
9519
+ const timeoutMs = resolveMarketContextStageTimeoutMs(stage);
9520
+ const startedAt = Date.now();
9521
+ const onParentAbort = () => controller.abort(parentSignal?.reason);
9522
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
9523
+ if (parentSignal?.aborted) onParentAbort();
9524
+ onStart?.(stage);
9525
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
9526
+ timer.unref?.();
9527
+ try {
9528
+ let available = false;
9529
+ try {
9530
+ available = await operation(controller.signal);
9531
+ } catch (error) {
9532
+ if (isMarketContextCancellationError(error)) {
9533
+ controller.abort(error);
9534
+ }
9535
+ if (!controller.signal.aborted) throw error;
9536
+ }
9537
+ const status = controller.signal.aborted ? "timed_out" : available ? "available" : "absent";
9538
+ const result = {
9539
+ stage,
9540
+ status,
9541
+ elapsedMs: Date.now() - startedAt
9542
+ };
9543
+ if (status === "timed_out") {
9544
+ import_logger8.logger.warn(
9545
+ "Market context stage timed out: %s after %sms",
9546
+ stage,
9547
+ result.elapsedMs
9548
+ );
9549
+ }
9550
+ onComplete?.(result);
9551
+ return result;
9552
+ } finally {
9553
+ clearTimeout(timer);
9554
+ parentSignal?.removeEventListener("abort", onParentAbort);
9555
+ }
9556
+ };
9557
+ var enrichSignalWithMarketContextStages = async ({
9558
+ signal,
9559
+ env,
9560
+ coinMarketCapEnabled,
9561
+ includeHyperliquidWhales = true,
9562
+ abortSignal,
9563
+ onStageStart,
9564
+ onStageComplete
9565
+ }) => {
9566
+ const stages = [
9567
+ {
9568
+ stage: "binance",
9569
+ operation: (stageSignal) => enrichSignalWithBinanceMarketContext({
9570
+ signal,
9571
+ env,
9572
+ abortSignal: stageSignal
9573
+ })
9574
+ },
9575
+ {
9576
+ stage: "coinmarketcap",
9577
+ operation: (stageSignal) => enrichSignalWithCoinMarketCapContext({
9578
+ signal,
9579
+ env,
9580
+ enabled: coinMarketCapEnabled,
9581
+ abortSignal: stageSignal
9582
+ })
9583
+ },
9584
+ {
9585
+ stage: "derivatives",
9586
+ operation: (stageSignal) => enrichSignalWithDerivativesContext({
9587
+ signal,
9588
+ env,
9589
+ abortSignal: stageSignal
9590
+ })
9591
+ }
9592
+ ];
9593
+ if (includeHyperliquidWhales) {
9594
+ stages.push({
9595
+ stage: "hyperliquidWhales",
9596
+ operation: (stageSignal) => enrichSignalWithHyperliquidWhaleContext({
9597
+ signal,
9598
+ env,
9599
+ abortSignal: stageSignal
9600
+ })
9601
+ });
9602
+ }
9603
+ const results = [];
9604
+ for (const stage of stages) {
9605
+ if (abortSignal?.aborted) break;
9606
+ results.push(
9607
+ await runMarketContextStage({
9608
+ ...stage,
9609
+ parentSignal: abortSignal,
9610
+ onStart: onStageStart,
9611
+ onComplete: onStageComplete
9612
+ })
9613
+ );
9614
+ }
9615
+ return results;
9616
+ };
9617
+
9423
9618
  // src/strategyHelpers/runtime.ts
9424
9619
  var formatAiError = (err) => {
9425
9620
  const error = err;
@@ -9536,7 +9731,7 @@ var enrichSignalWithAi = async ({
9536
9731
  signal.aiAnalysis = analysis;
9537
9732
  return resolveAiQuality(analysis, direction);
9538
9733
  } catch (err) {
9539
- import_logger8.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
9734
+ import_logger9.logger.error("AI analysis error: %s %s", symbol, formatAiError(err));
9540
9735
  }
9541
9736
  return void 0;
9542
9737
  };
@@ -9549,10 +9744,7 @@ var enrichSignalWithMlAi = async ({
9549
9744
  ml,
9550
9745
  ai
9551
9746
  }) => {
9552
- await enrichSignalWithBinanceMarketContext({ signal, env });
9553
- await enrichSignalWithCoinMarketCapContext({ signal, env });
9554
- await enrichSignalWithDerivativesContext({ signal, env });
9555
- await enrichSignalWithHyperliquidWhaleContext({ signal, env });
9747
+ await enrichSignalWithMarketContextStages({ signal, env });
9556
9748
  await enrichSignalWithMl({ signal, env, ml });
9557
9749
  return enrichSignalWithAi({ signal, userName, symbol, direction, env, ai });
9558
9750
  };
@@ -9587,7 +9779,7 @@ var getOrderArrivalSnapshot = async ({
9587
9779
  spreadBps
9588
9780
  };
9589
9781
  } catch (error) {
9590
- import_logger8.logger.warn(
9782
+ import_logger9.logger.warn(
9591
9783
  "runtime order arrival snapshot failed: %s %s",
9592
9784
  symbol,
9593
9785
  error?.message || String(error)
@@ -9816,6 +10008,7 @@ var executeEntryOrder = async ({
9816
10008
  deploymentId: signal.deploymentId,
9817
10009
  policyProfileId: signal.policyProfileId,
9818
10010
  runtimeConfigId: signal.runtimeConfigId,
10011
+ runtimeLineage: signal.runtimeLineage,
9819
10012
  ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {}
9820
10013
  });
9821
10014
  }
@@ -9934,6 +10127,10 @@ var resolveStrategyConfig = async ({
9934
10127
 
9935
10128
  // src/strategyRuntime.ts
9936
10129
  var import_types = require("@tradejs/types");
10130
+ var cloneWithPropertyDescriptors = (value) => Object.create(
10131
+ Object.getPrototypeOf(value),
10132
+ Object.getOwnPropertyDescriptors(value)
10133
+ );
9937
10134
  var buildExitOrderSignal = ({
9938
10135
  strategyName,
9939
10136
  symbol,
@@ -10453,7 +10650,7 @@ var handleExitDecision = async ({
10453
10650
  deploymentId: connector.deploymentId
10454
10651
  });
10455
10652
  if (!activeTrade) {
10456
- import_logger9.logger.warn(
10653
+ import_logger10.logger.warn(
10457
10654
  "[%s] blocked closePosition for untracked runtime position: %s",
10458
10655
  strategyName ?? "unknown",
10459
10656
  symbol
@@ -10461,7 +10658,7 @@ var handleExitDecision = async ({
10461
10658
  return "CLOSE_BLOCKED_BY_UNTRACKED_POSITION";
10462
10659
  }
10463
10660
  if (!strategyName || activeTrade.strategy !== strategyName) {
10464
- import_logger9.logger.warn(
10661
+ import_logger10.logger.warn(
10465
10662
  "[%s] blocked closePosition for foreign runtime position: %s ownedBy=%s",
10466
10663
  strategyName ?? "unknown",
10467
10664
  symbol,
@@ -10513,7 +10710,7 @@ var handleExitDecision = async ({
10513
10710
  exitType: closedTrade?.exitType ?? "exit"
10514
10711
  });
10515
10712
  } catch (notificationError) {
10516
- import_logger9.logger.error(
10713
+ import_logger10.logger.error(
10517
10714
  "runtime close notification error: %s %s",
10518
10715
  symbol,
10519
10716
  notificationError
@@ -10527,7 +10724,7 @@ var handleExitDecision = async ({
10527
10724
  decision,
10528
10725
  market
10529
10726
  });
10530
- import_logger9.logger.error("close order error: %s %s", symbol, err);
10727
+ import_logger10.logger.error("close order error: %s %s", symbol, err);
10531
10728
  return "ORDER_ERROR";
10532
10729
  }
10533
10730
  return decision.code;
@@ -10554,7 +10751,7 @@ var handleProtectDecision = async ({
10554
10751
  decision,
10555
10752
  market
10556
10753
  });
10557
- import_logger9.logger.error("protect position error: %s %s", symbol, err);
10754
+ import_logger10.logger.error("protect position error: %s %s", symbol, err);
10558
10755
  return "ORDER_ERROR";
10559
10756
  }
10560
10757
  return decision.code;
@@ -10717,9 +10914,9 @@ var executeEntryDecision = async ({
10717
10914
  market
10718
10915
  });
10719
10916
  if (err?.message === import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
10720
- import_logger9.logger.warn("order warning: %s %s", symbol, err);
10917
+ import_logger10.logger.warn("order warning: %s %s", symbol, err);
10721
10918
  } else {
10722
- import_logger9.logger.error("order error: %s %s", symbol, err);
10919
+ import_logger10.logger.error("order error: %s %s", symbol, err);
10723
10920
  }
10724
10921
  return signal ?? "ORDER_ERROR";
10725
10922
  }
@@ -10874,7 +11071,7 @@ var createStrategyRuntime = ({
10874
11071
  try {
10875
11072
  await projectHook(errorParams);
10876
11073
  } catch (hookError) {
10877
- import_logger9.logger.error(
11074
+ import_logger10.logger.error(
10878
11075
  "project hook onRuntimeError failed: %s %s",
10879
11076
  strategyName,
10880
11077
  hookError
@@ -10888,7 +11085,7 @@ var createStrategyRuntime = ({
10888
11085
  try {
10889
11086
  await onRuntimeError(errorParams);
10890
11087
  } catch (hookError) {
10891
- import_logger9.logger.error(
11088
+ import_logger10.logger.error(
10892
11089
  "runtime hook onRuntimeError failed: %s %s",
10893
11090
  strategyName,
10894
11091
  hookError
@@ -10902,7 +11099,7 @@ var createStrategyRuntime = ({
10902
11099
  try {
10903
11100
  return await hook(params);
10904
11101
  } catch (error) {
10905
- import_logger9.logger.error(
11102
+ import_logger10.logger.error(
10906
11103
  'strategy hook "%s" failed for %s: %s',
10907
11104
  stage,
10908
11105
  strategyName,
@@ -11050,6 +11247,9 @@ var createStrategyRuntime = ({
11050
11247
  sharedReplayKey: indicatorSharedReplayKey,
11051
11248
  useBtcReference: universe === "crypto"
11052
11249
  });
11250
+ const coreContextRequirements = new Set(
11251
+ strategyManifest?.contextRequirements?.core ?? []
11252
+ );
11053
11253
  const strategyApi = (0, import_strategies5.createStrategyAPI)({
11054
11254
  strategy: strategyName,
11055
11255
  symbol,
@@ -11068,6 +11268,9 @@ var createStrategyRuntime = ({
11068
11268
  interval: decisionInterval
11069
11269
  }) => {
11070
11270
  if (!baseContext) return void 0;
11271
+ if (!coreContextRequirements.has("hyperliquidWhales")) {
11272
+ return baseContext;
11273
+ }
11071
11274
  const hyperliquidWhales = await loadHyperliquidWhaleFlowContext({
11072
11275
  symbol: decisionSymbol,
11073
11276
  interval: decisionInterval,
@@ -11076,13 +11279,23 @@ var createStrategyRuntime = ({
11076
11279
  useSeriesCache: env === "BACKTEST" || env === "PARITY"
11077
11280
  });
11078
11281
  if (!hyperliquidWhales) return baseContext;
11079
- return {
11080
- ...baseContext,
11081
- participation: {
11082
- ...baseContext.participation,
11083
- hyperliquidWhales
11084
- }
11085
- };
11282
+ const participation = cloneWithPropertyDescriptors(
11283
+ baseContext.participation
11284
+ );
11285
+ Object.defineProperty(participation, "hyperliquidWhales", {
11286
+ configurable: true,
11287
+ enumerable: true,
11288
+ value: hyperliquidWhales,
11289
+ writable: true
11290
+ });
11291
+ const enrichedBaseContext = cloneWithPropertyDescriptors(baseContext);
11292
+ Object.defineProperty(enrichedBaseContext, "participation", {
11293
+ configurable: true,
11294
+ enumerable: true,
11295
+ value: participation,
11296
+ writable: true
11297
+ });
11298
+ return enrichedBaseContext;
11086
11299
  }
11087
11300
  });
11088
11301
  const core = await createCore({
@@ -11473,17 +11686,10 @@ var createStrategyRuntime = ({
11473
11686
  let ai;
11474
11687
  if (signal) {
11475
11688
  try {
11476
- await enrichSignalWithBinanceMarketContext({
11689
+ await enrichSignalWithMarketContextStages({
11477
11690
  signal,
11478
- env
11479
- });
11480
- await enrichSignalWithCoinMarketCapContext({
11481
- signal,
11482
- env
11483
- });
11484
- await enrichSignalWithDerivativesContext({
11485
- signal,
11486
- env
11691
+ env,
11692
+ includeHyperliquidWhales: false
11487
11693
  });
11488
11694
  quality = await enrichSignalWithAi({
11489
11695
  signal,
@@ -11644,7 +11850,7 @@ var createStrategyRuntime = ({
11644
11850
 
11645
11851
  // src/strategyHooks/closeOppositePositionsBeforeOpen.ts
11646
11852
  var import_lodash2 = __toESM(require_lodash());
11647
- var import_logger10 = require("@tradejs/infra/logger");
11853
+ var import_logger11 = require("@tradejs/infra/logger");
11648
11854
  var closeOppositePositionsBeforeOpen = async ({
11649
11855
  connector,
11650
11856
  entryContext
@@ -11658,7 +11864,7 @@ var closeOppositePositionsBeforeOpen = async ({
11658
11864
  } = entryContext;
11659
11865
  const price = prices.currentPrice;
11660
11866
  try {
11661
- import_logger10.logger.log(
11867
+ import_logger11.logger.log(
11662
11868
  "info",
11663
11869
  "[%s] checking open positions before open: %s %s",
11664
11870
  strategyName,
@@ -11669,7 +11875,7 @@ var closeOppositePositionsBeforeOpen = async ({
11669
11875
  const openPositions = (positions || []).filter(
11670
11876
  (item) => item && Number(item.qty) > 0
11671
11877
  );
11672
- import_logger10.logger.log(
11878
+ import_logger11.logger.log(
11673
11879
  "info",
11674
11880
  "[%s] open positions found: %s",
11675
11881
  strategyName,
@@ -11679,7 +11885,7 @@ var closeOppositePositionsBeforeOpen = async ({
11679
11885
  (item) => item.symbol !== currentSymbol && item.direction !== currentDirection
11680
11886
  );
11681
11887
  if (import_lodash2.default.isEmpty(oppositePositions)) {
11682
- import_logger10.logger.log(
11888
+ import_logger11.logger.log(
11683
11889
  "info",
11684
11890
  "[%s] no opposite positions to close before open: %s",
11685
11891
  strategyName,
@@ -11688,7 +11894,7 @@ var closeOppositePositionsBeforeOpen = async ({
11688
11894
  return;
11689
11895
  }
11690
11896
  for (const position of oppositePositions) {
11691
- import_logger10.logger.log(
11897
+ import_logger11.logger.log(
11692
11898
  "info",
11693
11899
  "[%s] closing opposite position: %s %s qty=%s",
11694
11900
  strategyName,
@@ -11703,14 +11909,14 @@ var closeOppositePositionsBeforeOpen = async ({
11703
11909
  timestamp,
11704
11910
  direction: position.direction
11705
11911
  });
11706
- import_logger10.logger.log(
11912
+ import_logger11.logger.log(
11707
11913
  "info",
11708
11914
  "[%s] opposite position closed: %s",
11709
11915
  strategyName,
11710
11916
  position.symbol
11711
11917
  );
11712
11918
  } catch (err) {
11713
- import_logger10.logger.log(
11919
+ import_logger11.logger.log(
11714
11920
  "error",
11715
11921
  "[%s] failed to close opposite position: %s %s",
11716
11922
  strategyName,
@@ -11720,7 +11926,7 @@ var closeOppositePositionsBeforeOpen = async ({
11720
11926
  }
11721
11927
  }
11722
11928
  } catch (err) {
11723
- import_logger10.logger.log(
11929
+ import_logger11.logger.log(
11724
11930
  "error",
11725
11931
  "[%s] failed to load open positions before open: %s %s",
11726
11932
  strategyName,
@@ -11932,7 +12138,7 @@ var createMoveStopToBreakEvenOnBarHook = ({
11932
12138
  var createMoveStopToBreakEvenAfterCoreDecisionHook = createMoveStopToBreakEvenOnBarHook;
11933
12139
 
11934
12140
  // src/signalsHooks/closeAllPositionsOnGlobalProfitBeforeSignals.ts
11935
- var import_logger11 = require("@tradejs/infra/logger");
12141
+ var import_logger12 = require("@tradejs/infra/logger");
11936
12142
  var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
11937
12143
  getStrategyDefaultConfig = () => void 0,
11938
12144
  profitRiskMultiplier = DEFAULT_GLOBAL_UNREALIZED_PNL_TRIGGER_RISK_MULTIPLIER
@@ -11971,7 +12177,7 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
11971
12177
  if (!Number.isFinite(unrealizedPnlThreshold) || unrealizedPnlThreshold <= 0 || totalUnrealizedPnl < unrealizedPnlThreshold) {
11972
12178
  return;
11973
12179
  }
11974
- import_logger11.logger.info(
12180
+ import_logger12.logger.info(
11975
12181
  "closing all positions before signals by global unrealized pnl threshold: totalPnl=%s threshold=%s positions=%s",
11976
12182
  totalUnrealizedPnl,
11977
12183
  unrealizedPnlThreshold,
@@ -11997,7 +12203,7 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
11997
12203
  ];
11998
12204
  });
11999
12205
  if (failedClosures.length) {
12000
- import_logger11.logger.warn(
12206
+ import_logger12.logger.warn(
12001
12207
  "close-all before signals hook could not confirm closures for %s",
12002
12208
  failedClosures.join(", ")
12003
12209
  );