@tradejs/node 2.0.15 → 2.0.17

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/backtest.mjs CHANGED
@@ -4,14 +4,11 @@ import {
4
4
  } from "./chunk-V3YMKE4I.mjs";
5
5
  import {
6
6
  buildMlPayload,
7
- enrichSignalWithBinanceMarketContext,
8
- enrichSignalWithCoinMarketCapContext,
9
- enrichSignalWithDerivativesContext,
10
- enrichSignalWithHyperliquidWhaleContext
11
- } from "./chunk-FEQR4XGR.mjs";
7
+ enrichSignalWithMarketContextStages
8
+ } from "./chunk-3BP3ETAD.mjs";
12
9
  import {
13
10
  buildAiPayload
14
- } from "./chunk-DPHKRDD5.mjs";
11
+ } from "./chunk-WK5EUCX5.mjs";
15
12
  import {
16
13
  getStrategyCreator
17
14
  } from "./chunk-QVSMINLG.mjs";
@@ -2065,35 +2062,15 @@ var testing = async ({
2065
2062
  }
2066
2063
  const shouldCapturePayload = signal && typeof signal !== "string" && signal.signalId && (ml || ai);
2067
2064
  if (shouldCapturePayload) {
2068
- await withTimeout(
2069
- "binance market context",
2070
- enrichSignalWithBinanceMarketContext({
2071
- signal,
2072
- env: "BACKTEST"
2073
- })
2074
- );
2075
- await withTimeout(
2076
- "coinmarketcap context",
2077
- enrichSignalWithCoinMarketCapContext({
2078
- signal,
2079
- env: "BACKTEST",
2080
- enabled: Boolean(ml || ai)
2081
- })
2082
- );
2083
- await withTimeout(
2084
- "derivatives context",
2085
- enrichSignalWithDerivativesContext({
2086
- signal,
2087
- env: "BACKTEST"
2088
- })
2089
- );
2090
- await withTimeout(
2091
- "hyperliquid whale context",
2092
- enrichSignalWithHyperliquidWhaleContext({
2093
- signal,
2094
- env: "BACKTEST"
2095
- })
2096
- );
2065
+ await enrichSignalWithMarketContextStages({
2066
+ signal,
2067
+ env: "BACKTEST",
2068
+ coinMarketCapEnabled: true,
2069
+ onStageStart: (stage) => {
2070
+ activeStageStartedAt = Date.now();
2071
+ emitProgress(`${stage} context`, { force: true });
2072
+ }
2073
+ });
2097
2074
  }
2098
2075
  if (ml && signal && typeof signal !== "string" && signal.signalId) {
2099
2076
  const payload = buildMlPayload({
@@ -2497,35 +2474,15 @@ var testingGroupInSharedCandleLoop = async (tests) => {
2497
2474
  }
2498
2475
  const shouldCapturePayload = signal && typeof signal !== "string" && signal.signalId && (test.ml || test.ai);
2499
2476
  if (shouldCapturePayload) {
2500
- await withTimeout(
2501
- "binance market context",
2502
- enrichSignalWithBinanceMarketContext({
2503
- signal,
2504
- env: "BACKTEST"
2505
- })
2506
- );
2507
- await withTimeout(
2508
- "coinmarketcap context",
2509
- enrichSignalWithCoinMarketCapContext({
2510
- signal,
2511
- env: "BACKTEST",
2512
- enabled: Boolean(test.ml || test.ai)
2513
- })
2514
- );
2515
- await withTimeout(
2516
- "derivatives context",
2517
- enrichSignalWithDerivativesContext({
2518
- signal,
2519
- env: "BACKTEST"
2520
- })
2521
- );
2522
- await withTimeout(
2523
- "hyperliquid whale context",
2524
- enrichSignalWithHyperliquidWhaleContext({
2525
- signal,
2526
- env: "BACKTEST"
2527
- })
2528
- );
2477
+ await enrichSignalWithMarketContextStages({
2478
+ signal,
2479
+ env: "BACKTEST",
2480
+ coinMarketCapEnabled: true,
2481
+ onStageStart: (stage) => {
2482
+ activeStageStartedAt = Date.now();
2483
+ emitProgress(`${stage} context`, { force: true });
2484
+ }
2485
+ });
2529
2486
  }
2530
2487
  if (test.ml && signal && typeof signal !== "string" && signal.signalId) {
2531
2488
  const payload = buildMlPayload({
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getStrategyProfileMlAdapter
3
- } from "./chunk-DPHKRDD5.mjs";
3
+ } from "./chunk-WK5EUCX5.mjs";
4
4
  import {
5
5
  getStrategyManifest
6
6
  } from "./chunk-QVSMINLG.mjs";
@@ -317,6 +317,15 @@ import {
317
317
  } from "@tradejs/infra/timescale";
318
318
  import { logger } from "@tradejs/infra/logger";
319
319
  import { refreshSignalBaseContextGateFeatures } from "@tradejs/core/strategies";
320
+
321
+ // src/strategyHelpers/marketContextErrors.ts
322
+ var MARKET_CONTEXT_CANCELLATION_ERROR_NAMES = /* @__PURE__ */ new Set([
323
+ "AbortError",
324
+ "TimescaleQueryTimeoutError"
325
+ ]);
326
+ var isMarketContextCancellationError = (error, abortSignal) => abortSignal?.aborted === true || error instanceof Error && MARKET_CONTEXT_CANCELLATION_ERROR_NAMES.has(error.name);
327
+
328
+ // src/strategyHelpers/binanceMarketContext.ts
320
329
  var DEFAULT_MAX_AGE_BY_INTERVAL = {
321
330
  "1m": 3 * 6e4,
322
331
  "5m": 10 * 6e4,
@@ -380,7 +389,8 @@ var getCachedReferenceRows = ({
380
389
  referenceSymbols,
381
390
  interval,
382
391
  timestamp,
383
- maxAgeMs
392
+ maxAgeMs,
393
+ abortSignal
384
394
  }) => {
385
395
  const key = `${referenceSymbols.join(",")}:${interval}:${timestamp}:${maxAgeMs}`;
386
396
  const cached = referenceRowsCache.get(key);
@@ -391,7 +401,8 @@ var getCachedReferenceRows = ({
391
401
  symbol,
392
402
  interval,
393
403
  atMs: timestamp,
394
- maxAgeMs
404
+ maxAgeMs,
405
+ ...abortSignal ? { signal: abortSignal } : {}
395
406
  });
396
407
  return {
397
408
  symbol,
@@ -400,13 +411,15 @@ var getCachedReferenceRows = ({
400
411
  })
401
412
  );
402
413
  referenceRowsCache.set(key, promise);
414
+ void promise.catch(() => referenceRowsCache.delete(key));
403
415
  return promise;
404
416
  };
405
417
  var getCachedBreadth = ({
406
418
  breadthUniverse,
407
419
  interval,
408
420
  timestamp,
409
- maxAgeMs
421
+ maxAgeMs,
422
+ abortSignal
410
423
  }) => {
411
424
  const key = `${breadthUniverse}:${interval}:${timestamp}:${maxAgeMs}`;
412
425
  const cached = breadthCache.get(key);
@@ -415,9 +428,11 @@ var getCachedBreadth = ({
415
428
  universe: breadthUniverse,
416
429
  interval,
417
430
  atMs: timestamp,
418
- maxAgeMs
431
+ maxAgeMs,
432
+ ...abortSignal ? { signal: abortSignal } : {}
419
433
  });
420
434
  breadthCache.set(key, promise);
435
+ void promise.catch(() => breadthCache.delete(key));
421
436
  return promise;
422
437
  };
423
438
  var toMarketBreadthContext = (breadth, interval) => ({
@@ -463,7 +478,8 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
463
478
  referenceSymbols,
464
479
  interval,
465
480
  timestamp: signal.timestamp,
466
- maxAgeMs
481
+ maxAgeMs,
482
+ abortSignal: params.abortSignal
467
483
  }),
468
484
  Promise.all(
469
485
  breadthUniverses.map(async ({ key, universe }) => ({
@@ -472,7 +488,8 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
472
488
  breadthUniverse: universe,
473
489
  interval,
474
490
  timestamp: signal.timestamp,
475
- maxAgeMs
491
+ maxAgeMs,
492
+ abortSignal: params.abortSignal
476
493
  })
477
494
  }))
478
495
  )
@@ -587,6 +604,9 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
587
604
  refreshSignalBaseContextGateFeatures(signal);
588
605
  return true;
589
606
  } catch (error) {
607
+ if (isMarketContextCancellationError(error, params.abortSignal)) {
608
+ throw error;
609
+ }
590
610
  binanceMarketContextUnavailable = true;
591
611
  logger.warn(
592
612
  "Binance market context disabled after Timescale read failure: %s",
@@ -713,7 +733,8 @@ var toIndexRegime = ({
713
733
  };
714
734
  var getCachedGlobalContext = ({
715
735
  timestamp,
716
- maxAgeMs
736
+ maxAgeMs,
737
+ abortSignal
717
738
  }) => {
718
739
  const key = `${SOURCE_GLOBAL_DAILY}:${timestamp}:${maxAgeMs}`;
719
740
  const cached = globalContextCache.get(key);
@@ -721,14 +742,17 @@ var getCachedGlobalContext = ({
721
742
  const promise = getLatestMarketGlobalContext({
722
743
  source: SOURCE_GLOBAL_DAILY,
723
744
  atMs: timestamp,
724
- maxAgeMs
745
+ maxAgeMs,
746
+ ...abortSignal ? { signal: abortSignal } : {}
725
747
  });
726
748
  globalContextCache.set(key, promise);
749
+ void promise.catch(() => globalContextCache.delete(key));
727
750
  return promise;
728
751
  };
729
752
  var getCachedReferenceContexts = ({
730
753
  timestamp,
731
- maxAgeMs
754
+ maxAgeMs,
755
+ abortSignal
732
756
  }) => {
733
757
  const key = `${SOURCE_REFERENCE}:1d:${timestamp}:${maxAgeMs}`;
734
758
  const cached = referenceContextCache.get(key);
@@ -738,14 +762,17 @@ var getCachedReferenceContexts = ({
738
762
  symbols: ["BTCUSDT", "ETHUSDT"],
739
763
  interval: "1d",
740
764
  atMs: timestamp,
741
- maxAgeMs
765
+ maxAgeMs,
766
+ ...abortSignal ? { signal: abortSignal } : {}
742
767
  });
743
768
  referenceContextCache.set(key, promise);
769
+ void promise.catch(() => referenceContextCache.delete(key));
744
770
  return promise;
745
771
  };
746
772
  var getCachedExchangeLiquidityContext = ({
747
773
  timestamp,
748
- maxAgeMs
774
+ maxAgeMs,
775
+ abortSignal
749
776
  }) => {
750
777
  const key = `${SOURCE_EXCHANGE_LIQUIDITY}:1d:${timestamp}:${maxAgeMs}`;
751
778
  const cached = exchangeLiquidityContextCache.get(key);
@@ -754,14 +781,17 @@ var getCachedExchangeLiquidityContext = ({
754
781
  source: SOURCE_EXCHANGE_LIQUIDITY,
755
782
  interval: "1d",
756
783
  atMs: timestamp,
757
- maxAgeMs
784
+ maxAgeMs,
785
+ ...abortSignal ? { signal: abortSignal } : {}
758
786
  });
759
787
  exchangeLiquidityContextCache.set(key, promise);
788
+ void promise.catch(() => exchangeLiquidityContextCache.delete(key));
760
789
  return promise;
761
790
  };
762
791
  var getCachedFearGreedContext = ({
763
792
  timestamp,
764
- maxAgeMs
793
+ maxAgeMs,
794
+ abortSignal
765
795
  }) => {
766
796
  const key = `${SOURCE_FEAR_GREED}:1d:${timestamp}:${maxAgeMs}`;
767
797
  const cached = fearGreedContextCache.get(key);
@@ -770,14 +800,17 @@ var getCachedFearGreedContext = ({
770
800
  source: SOURCE_FEAR_GREED,
771
801
  interval: "1d",
772
802
  atMs: timestamp,
773
- maxAgeMs
803
+ maxAgeMs,
804
+ ...abortSignal ? { signal: abortSignal } : {}
774
805
  });
775
806
  fearGreedContextCache.set(key, promise);
807
+ void promise.catch(() => fearGreedContextCache.delete(key));
776
808
  return promise;
777
809
  };
778
810
  var getCachedIndexContexts = ({
779
811
  timestamp,
780
- maxAgeMs
812
+ maxAgeMs,
813
+ abortSignal
781
814
  }) => {
782
815
  const key = `${SOURCE_INDEX}:1d:${timestamp}:${maxAgeMs}`;
783
816
  const cached = indexContextCache.get(key);
@@ -787,9 +820,11 @@ var getCachedIndexContexts = ({
787
820
  indexSlugs: ["cmc100", "cmc20"],
788
821
  interval: "1d",
789
822
  atMs: timestamp,
790
- maxAgeMs
823
+ maxAgeMs,
824
+ ...abortSignal ? { signal: abortSignal } : {}
791
825
  });
792
826
  indexContextCache.set(key, promise);
827
+ void promise.catch(() => indexContextCache.delete(key));
793
828
  return promise;
794
829
  };
795
830
  var isCoinMarketCapContextEnabled = (env) => parseEnabledFlag2(process.env.COINMARKETCAP_CONTEXT_ENABLED, env);
@@ -814,27 +849,33 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
814
849
  ] = await Promise.all([
815
850
  getCachedGlobalContext({
816
851
  timestamp: signal.timestamp,
817
- maxAgeMs
852
+ maxAgeMs,
853
+ abortSignal: params.abortSignal
818
854
  }),
819
855
  getCachedReferenceContexts({
820
856
  timestamp: signal.timestamp,
821
- maxAgeMs
857
+ maxAgeMs,
858
+ abortSignal: params.abortSignal
822
859
  }),
823
860
  getCachedReferenceContexts({
824
861
  timestamp: signal.timestamp - DAY_MS,
825
- maxAgeMs: maxAgeMs + DAY_MS
862
+ maxAgeMs: maxAgeMs + DAY_MS,
863
+ abortSignal: params.abortSignal
826
864
  }),
827
865
  getCachedExchangeLiquidityContext({
828
866
  timestamp: signal.timestamp,
829
- maxAgeMs
867
+ maxAgeMs,
868
+ abortSignal: params.abortSignal
830
869
  }),
831
870
  getCachedFearGreedContext({
832
871
  timestamp: signal.timestamp,
833
- maxAgeMs
872
+ maxAgeMs,
873
+ abortSignal: params.abortSignal
834
874
  }),
835
875
  getCachedIndexContexts({
836
876
  timestamp: signal.timestamp,
837
- maxAgeMs
877
+ maxAgeMs,
878
+ abortSignal: params.abortSignal
838
879
  })
839
880
  ]);
840
881
  const globalRow = globalDailyRow;
@@ -1068,6 +1109,9 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
1068
1109
  refreshSignalBaseContextGateFeatures2(signal);
1069
1110
  return true;
1070
1111
  } catch (error) {
1112
+ if (isMarketContextCancellationError(error, params.abortSignal)) {
1113
+ throw error;
1114
+ }
1071
1115
  coinMarketCapContextUnavailable = true;
1072
1116
  logger2.warn(
1073
1117
  "CoinMarketCap context disabled after Timescale read failure: %s",
@@ -1307,7 +1351,7 @@ import { logger as logger3 } from "@tradejs/infra/logger";
1307
1351
  var MAX_CACHE_ENTRIES = 2048;
1308
1352
  var MAX_COVERAGE_SERIES_CACHE_ENTRIES = 20;
1309
1353
  var MAX_FLOW_SERIES_CACHE_ENTRIES = 64;
1310
- var SERIES_CHUNK_MS = 30 * 24 * 60 * 6e4;
1354
+ var SERIES_CHUNK_MS = 90 * 24 * 60 * 6e4;
1311
1355
  var MAX_SERIES_LOOKBACK_MS = 60 * 6e4;
1312
1356
  var DEFAULT_MIN_COVERAGE_PCT = 0.8;
1313
1357
  var INTERVAL_MS = {
@@ -1355,6 +1399,7 @@ var setBoundedCache = (key, value) => {
1355
1399
  if (oldestKey != null) contextCache.delete(oldestKey);
1356
1400
  }
1357
1401
  contextCache.set(key, value);
1402
+ void value.catch(() => contextCache.delete(key));
1358
1403
  };
1359
1404
  var setBoundedSeriesCache = (cache, maxEntries, key, value) => {
1360
1405
  if (cache.size >= maxEntries) {
@@ -1362,6 +1407,7 @@ var setBoundedSeriesCache = (cache, maxEntries, key, value) => {
1362
1407
  if (oldestKey != null) cache.delete(oldestKey);
1363
1408
  }
1364
1409
  cache.set(key, value);
1410
+ void value.catch(() => cache.delete(key));
1365
1411
  };
1366
1412
  var uniqueAddressCount = (rows, field) => {
1367
1413
  const addresses = /* @__PURE__ */ new Set();
@@ -1476,7 +1522,8 @@ var loadHyperliquidWhaleFlowAggregateFromSeries = async (params) => {
1476
1522
  fromMs,
1477
1523
  toMs,
1478
1524
  universeFingerprint: params.universeFingerprint,
1479
- whaleRegistryFingerprint: params.whaleRegistryFingerprint
1525
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint,
1526
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
1480
1527
  });
1481
1528
  setBoundedSeriesCache(
1482
1529
  coverageSeriesCache,
@@ -1493,7 +1540,8 @@ var loadHyperliquidWhaleFlowAggregateFromSeries = async (params) => {
1493
1540
  fromMs,
1494
1541
  toMs,
1495
1542
  universeFingerprint: params.universeFingerprint,
1496
- whaleRegistryFingerprint: params.whaleRegistryFingerprint
1543
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint,
1544
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
1497
1545
  });
1498
1546
  setBoundedSeriesCache(
1499
1547
  flowSeriesCache,
@@ -1577,7 +1625,8 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
1577
1625
  decisionTimeMs,
1578
1626
  maxAgeMs,
1579
1627
  universeFingerprint: universe.fingerprint,
1580
- whaleRegistryFingerprint: whales.fingerprint
1628
+ whaleRegistryFingerprint: whales.fingerprint,
1629
+ abortSignal: params.abortSignal
1581
1630
  });
1582
1631
  } else {
1583
1632
  let pending = contextCache.get(cacheKey);
@@ -1588,7 +1637,8 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
1588
1637
  decisionTimeMs,
1589
1638
  maxAgeMs,
1590
1639
  universeFingerprint: universe.fingerprint,
1591
- whaleRegistryFingerprint: whales.fingerprint
1640
+ whaleRegistryFingerprint: whales.fingerprint,
1641
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
1592
1642
  });
1593
1643
  setBoundedCache(cacheKey, pending);
1594
1644
  }
@@ -1597,6 +1647,9 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
1597
1647
  if (!row) return null;
1598
1648
  return toBaseHyperliquidWhaleFlowContext(row, minimumCoveragePct);
1599
1649
  } catch (error) {
1650
+ if (isMarketContextCancellationError(error, params.abortSignal)) {
1651
+ throw error;
1652
+ }
1600
1653
  hyperliquidWhaleContextUnavailable = true;
1601
1654
  logger3.warn(
1602
1655
  "Hyperliquid whale context disabled after Timescale read failure: %s",
@@ -1617,7 +1670,8 @@ var enrichSignalWithHyperliquidWhaleContext = async (params) => {
1617
1670
  env,
1618
1671
  enabled: params.enabled,
1619
1672
  marketInterval: params.interval,
1620
- maxAgeMs: params.maxAgeMs
1673
+ maxAgeMs: params.maxAgeMs,
1674
+ abortSignal: params.abortSignal
1621
1675
  });
1622
1676
  if (!hyperliquidWhales) return false;
1623
1677
  const baseContext = signal.additionalIndicators.baseContext;
@@ -1635,6 +1689,9 @@ var enrichSignalWithHyperliquidWhaleContext = async (params) => {
1635
1689
  return true;
1636
1690
  };
1637
1691
 
1692
+ // src/strategyHelpers/marketContextStages.ts
1693
+ import { logger as logger5 } from "@tradejs/infra/logger";
1694
+
1638
1695
  // src/strategyHelpers/derivativesContext.ts
1639
1696
  import {
1640
1697
  buildDerivativesContext,
@@ -1815,7 +1872,8 @@ var enrichSignalWithDerivativesContext = async (params) => {
1815
1872
  symbol,
1816
1873
  intervals: STORED_INTERVALS,
1817
1874
  endMs: derivativesEndMs,
1818
- lookbackMs
1875
+ lookbackMs,
1876
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
1819
1877
  });
1820
1878
  return [
1821
1879
  symbol,
@@ -1843,7 +1901,8 @@ var enrichSignalWithDerivativesContext = async (params) => {
1843
1901
  symbol: targetSymbol,
1844
1902
  intervals: STORED_INTERVALS,
1845
1903
  endMs: derivativesEndMs,
1846
- lookbackMs
1904
+ lookbackMs,
1905
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
1847
1906
  });
1848
1907
  const context = buildDerivativesContext({
1849
1908
  symbol: targetSymbol,
@@ -1874,6 +1933,9 @@ var enrichSignalWithDerivativesContext = async (params) => {
1874
1933
  refreshSignalBaseContextGateFeatures4(signal);
1875
1934
  return true;
1876
1935
  } catch (error) {
1936
+ if (isMarketContextCancellationError(error, params.abortSignal)) {
1937
+ throw error;
1938
+ }
1877
1939
  derivativesContextUnavailable = true;
1878
1940
  logger4.warn(
1879
1941
  "Derivatives context disabled after Timescale read failure: %s",
@@ -1883,6 +1945,125 @@ var enrichSignalWithDerivativesContext = async (params) => {
1883
1945
  }
1884
1946
  };
1885
1947
 
1948
+ // src/strategyHelpers/marketContextStages.ts
1949
+ var STAGE_ENV_KEYS = {
1950
+ binance: "BINANCE_MARKET_CONTEXT_STAGE_TIMEOUT_MS",
1951
+ coinmarketcap: "COINMARKETCAP_CONTEXT_STAGE_TIMEOUT_MS",
1952
+ derivatives: "DERIVATIVES_CONTEXT_STAGE_TIMEOUT_MS",
1953
+ hyperliquidWhales: "HYPERLIQUID_WHALE_CONTEXT_STAGE_TIMEOUT_MS"
1954
+ };
1955
+ var parsePositiveInt = (value) => {
1956
+ const parsed = Number.parseInt(String(value ?? ""), 10);
1957
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
1958
+ };
1959
+ var resolveMarketContextStageTimeoutMs = (stage) => parsePositiveInt(process.env[STAGE_ENV_KEYS[stage]]) ?? parsePositiveInt(process.env.MARKET_CONTEXT_STAGE_TIMEOUT_MS) ?? 35e3;
1960
+ var runMarketContextStage = async ({
1961
+ stage,
1962
+ parentSignal,
1963
+ operation,
1964
+ onStart,
1965
+ onComplete
1966
+ }) => {
1967
+ const controller = new AbortController();
1968
+ const timeoutMs = resolveMarketContextStageTimeoutMs(stage);
1969
+ const startedAt = Date.now();
1970
+ const onParentAbort = () => controller.abort(parentSignal?.reason);
1971
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
1972
+ if (parentSignal?.aborted) onParentAbort();
1973
+ onStart?.(stage);
1974
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1975
+ timer.unref?.();
1976
+ try {
1977
+ let available = false;
1978
+ try {
1979
+ available = await operation(controller.signal);
1980
+ } catch (error) {
1981
+ if (isMarketContextCancellationError(error)) {
1982
+ controller.abort(error);
1983
+ }
1984
+ if (!controller.signal.aborted) throw error;
1985
+ }
1986
+ const status = controller.signal.aborted ? "timed_out" : available ? "available" : "absent";
1987
+ const result = {
1988
+ stage,
1989
+ status,
1990
+ elapsedMs: Date.now() - startedAt
1991
+ };
1992
+ if (status === "timed_out") {
1993
+ logger5.warn(
1994
+ "Market context stage timed out: %s after %sms",
1995
+ stage,
1996
+ result.elapsedMs
1997
+ );
1998
+ }
1999
+ onComplete?.(result);
2000
+ return result;
2001
+ } finally {
2002
+ clearTimeout(timer);
2003
+ parentSignal?.removeEventListener("abort", onParentAbort);
2004
+ }
2005
+ };
2006
+ var enrichSignalWithMarketContextStages = async ({
2007
+ signal,
2008
+ env,
2009
+ coinMarketCapEnabled,
2010
+ includeHyperliquidWhales = true,
2011
+ abortSignal,
2012
+ onStageStart,
2013
+ onStageComplete
2014
+ }) => {
2015
+ const stages = [
2016
+ {
2017
+ stage: "binance",
2018
+ operation: (stageSignal) => enrichSignalWithBinanceMarketContext({
2019
+ signal,
2020
+ env,
2021
+ abortSignal: stageSignal
2022
+ })
2023
+ },
2024
+ {
2025
+ stage: "coinmarketcap",
2026
+ operation: (stageSignal) => enrichSignalWithCoinMarketCapContext({
2027
+ signal,
2028
+ env,
2029
+ enabled: coinMarketCapEnabled,
2030
+ abortSignal: stageSignal
2031
+ })
2032
+ },
2033
+ {
2034
+ stage: "derivatives",
2035
+ operation: (stageSignal) => enrichSignalWithDerivativesContext({
2036
+ signal,
2037
+ env,
2038
+ abortSignal: stageSignal
2039
+ })
2040
+ }
2041
+ ];
2042
+ if (includeHyperliquidWhales) {
2043
+ stages.push({
2044
+ stage: "hyperliquidWhales",
2045
+ operation: (stageSignal) => enrichSignalWithHyperliquidWhaleContext({
2046
+ signal,
2047
+ env,
2048
+ abortSignal: stageSignal
2049
+ })
2050
+ });
2051
+ }
2052
+ const results = [];
2053
+ for (const stage of stages) {
2054
+ if (abortSignal?.aborted) break;
2055
+ results.push(
2056
+ await runMarketContextStage({
2057
+ ...stage,
2058
+ parentSignal: abortSignal,
2059
+ onStart: onStageStart,
2060
+ onComplete: onStageComplete
2061
+ })
2062
+ );
2063
+ }
2064
+ return results;
2065
+ };
2066
+
1886
2067
  // src/strategyAdapters/ml.ts
1887
2068
  var defaultMlAdapter = {
1888
2069
  normalizeStrategyConfig: (strategyConfig) => strategyConfig
@@ -1939,7 +2120,6 @@ export {
1939
2120
  getPrimaryBinanceBreadthUniverse,
1940
2121
  enrichSignalWithBinanceMarketContext,
1941
2122
  enrichSignalWithCoinMarketCapContext,
1942
- enrichSignalWithDerivativesContext,
1943
2123
  getHyperliquidPerpUniverseSnapshot,
1944
2124
  getHyperliquidWhaleRegistrySnapshot,
1945
2125
  getHyperliquidPerpSymbols,
@@ -1949,5 +2129,6 @@ export {
1949
2129
  resolveHyperliquidPerpFromSignalSymbol,
1950
2130
  loadHyperliquidWhaleFlowContext,
1951
2131
  enrichSignalWithHyperliquidWhaleContext,
2132
+ enrichSignalWithMarketContextStages,
1952
2133
  buildMlPayload
1953
2134
  };
@@ -699,6 +699,15 @@ var postProcessAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloa
699
699
  payload,
700
700
  analysis
701
701
  }) ?? analysis;
702
+ var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiPayloadByStrategy(signal)) => {
703
+ const adapter = getSignalAiAdapter(signal);
704
+ const strategyAnalysis = adapter.postProcessAnalysis?.({ signal, payload, analysis }) ?? analysis;
705
+ return adapter.postProcessLocalAnalysis?.({
706
+ signal,
707
+ payload,
708
+ analysis: strategyAnalysis
709
+ }) ?? strategyAnalysis;
710
+ };
702
711
 
703
712
  // src/ai.ts
704
713
  var parseAIResponse = (input) => {
@@ -847,7 +856,7 @@ Input payload structure:
847
856
  \u2022 \`baseContext.relative\`: BTC/ETH relative-strength, benchmark MA bias context, Binance alt-basket breadth, and CoinMarketCap historical global/exchange/index context when available.
848
857
  \u2022 \`baseContext.derivatives\`: Coinalyze-aligned derivatives summary when available.
849
858
  \u2022 \`baseContext.mtf\`: compact multi-timeframe summary plus only the latest few candles for each timeframe.
850
- \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.
859
+ \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.
851
860
  Always inspect \`payload.additionalIndicators.marketContext\` when present:
852
861
  \u2022 \`marketContext.execution.binanceCoinbaseSpread\`: AI-friendly BTC spread view projected from \`payload.additionalIndicators.baseContext.relative.execution.venueSpread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
853
862
  \u2022 \`marketContext.participation.trueDelta\`: Binance taker buy/sell volume delta from kline payload when \`source=kline_taker_volume\`; otherwise absent/unavailable.
@@ -1129,7 +1138,7 @@ var runAiPromptLocal = async (signal, options = {}) => {
1129
1138
  const signalDirection = getSignalDirection(signal);
1130
1139
  const deterministicQuality = getDeterministicQuality(gateContext);
1131
1140
  const approvalAllowedNow = typeof gateContext?.approvalAllowedNow === "boolean" ? gateContext.approvalAllowedNow : deterministicQuality >= 4;
1132
- return postProcessAiAnalysisByStrategy(
1141
+ return postProcessLocalAiAnalysisByStrategy(
1133
1142
  signal,
1134
1143
  {
1135
1144
  direction: approvalAllowedNow ? signalDirection : null,
package/dist/cli.js CHANGED
@@ -6814,7 +6814,7 @@ Input payload structure:
6814
6814
  \u2022 \`baseContext.relative\`: BTC/ETH relative-strength, benchmark MA bias context, Binance alt-basket breadth, and CoinMarketCap historical global/exchange/index context when available.
6815
6815
  \u2022 \`baseContext.derivatives\`: Coinalyze-aligned derivatives summary when available.
6816
6816
  \u2022 \`baseContext.mtf\`: compact multi-timeframe summary plus only the latest few candles for each timeframe.
6817
- \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.
6817
+ \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.
6818
6818
  Always inspect \`payload.additionalIndicators.marketContext\` when present:
6819
6819
  \u2022 \`marketContext.execution.binanceCoinbaseSpread\`: AI-friendly BTC spread view projected from \`payload.additionalIndicators.baseContext.relative.execution.venueSpread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
6820
6820
  \u2022 \`marketContext.participation.trueDelta\`: Binance taker buy/sell volume delta from kline payload when \`source=kline_taker_volume\`; otherwise absent/unavailable.
package/dist/cli.mjs CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-GPR56UYQ.mjs";
9
9
  import {
10
10
  askAI
11
- } from "./chunk-DPHKRDD5.mjs";
11
+ } from "./chunk-WK5EUCX5.mjs";
12
12
  import "./chunk-QVSMINLG.mjs";
13
13
  import {
14
14
  getTradejsProjectCwd,