@tradejs/node 3.1.27-beta.250 → 3.1.28-beta.251

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.
@@ -1,6 +1,6 @@
1
1
  export * from '@tradejs/core/strategies';
2
2
  export { AiChatMessage, DEFAULT_AI_MODEL, InvokeAiChatOptions, MAX_AI_SERIES_POINTS, askAI, buildAiHumanPrompt, buildAiPayload, buildAiPrompts, buildAiSystemPrompt, buildCompactAiIndicatorsSnapshot, getDeterministicAiGateContext, getOpenRouterModelKwargs, invokeAiChat, resetAiRuntimeCache, runAiPrompt, runAiPromptLocal, trimSeriesDeep } from './ai.js';
3
- export { e as ensureIndicatorPluginsLoaded, a as ensureStrategyPluginsLoaded, g as getAvailableStrategyNames, b as getRegisteredManifests, c as getRegisteredStrategies, d as getStrategyCreator, f as getStrategyDefaults, h as getStrategyManifest, i as getStrategyPluginSource, j as isKnownStrategy, r as registerStrategyEntries, k as resetStrategyRegistryCache, s as strategies } from './registry-DHTLjQcr.js';
3
+ export { e as ensureIndicatorPluginsLoaded, a as ensureStrategyPluginsLoaded, g as getAvailableStrategyNames, b as getRegisteredManifests, c as getRegisteredStrategies, d as getStrategyCreator, f as getStrategyDefaults, h as getStrategyManifest, i as getStrategyPluginSource, j as isKnownStrategy, r as registerStrategyEntries, k as resetStrategyRegistryCache, s as strategies } from './registry-3TsO1Ff1.js';
4
4
  import { StrategyConfig, CreateStrategyCore, StrategyManifest, StrategyCreator, RuntimeStrategyConfigSnapshot, Signal, Direction, StrategyRuntimeMlOptions, StrategyRuntimeAiOptions, Connector, Tp, MarketFeatureInterval, StrategyEntrySignalContext } from '@tradejs/types';
5
5
  import { TradejsConfigOnBarHook, TradejsConfigBeforeSignalsHook } from '@tradejs/core/config';
6
6
 
@@ -79,6 +79,18 @@ declare const validateEntryProtectionAtArrival: ({ direction, signalPrice, bid,
79
79
  }) => void;
80
80
  declare const executeEntryOrder: ({ connector, userName, symbol, direction, qty, currentPrice, timestamp, takeProfits, stopLossPrice, positionIntent, signal, beforePlaceOrder, recordRuntimeTrade, leverage, }: ExecuteEntryOrderParams) => Promise<number>;
81
81
 
82
+ declare const preloadBinanceMarketContextForWindow: (params: {
83
+ startMs: number;
84
+ endMs: number;
85
+ interval: MarketFeatureInterval;
86
+ maxAgeMs?: number;
87
+ timeoutMs?: number;
88
+ chunkMs?: number;
89
+ abortSignal?: AbortSignal;
90
+ }) => Promise<{
91
+ tradeFlowRows: number;
92
+ breadthRows: number;
93
+ }>;
82
94
  declare const enrichSignalWithBinanceMarketContext: (params: {
83
95
  signal: Signal;
84
96
  env: string;
@@ -200,4 +212,4 @@ interface CreateCloseAllOnGlobalProfitBeforeSignalsHookParams {
200
212
  }
201
213
  declare const createCloseAllOnGlobalProfitBeforeSignalsHook: ({ getStrategyDefaultConfig, profitRiskMultiplier, }?: CreateCloseAllOnGlobalProfitBeforeSignalsHookParams) => TradejsConfigBeforeSignalsHook;
202
214
 
203
- export { BINANCE_BREADTH_UNIVERSE_KEYS, type BinanceBreadthUniverseDefinition, type BinanceBreadthUniverseKey, type BinanceBreadthUniverseSnapshot, type HyperliquidPerpUniverseSnapshot, type HyperliquidWhaleRegistrySnapshot, buildBinanceBreadthUniverseSnapshot, closeOppositePositionsBeforeOpen, createCloseAllOnGlobalProfitBeforeSignalsHook, createCloseOppositeBeforePlaceOrderHook, createMoveStopToBreakEvenAfterCoreDecisionHook, createMoveStopToBreakEvenOnBarHook, createStrategyRuntime, enrichSignalWithAi, enrichSignalWithBinanceMarketContext, enrichSignalWithCoinMarketCapContext, enrichSignalWithHyperliquidWhaleContext, enrichSignalWithMl, enrichSignalWithMlAi, executeEntryOrder, getBinanceBreadthUniverseSnapshot, getBinanceBreadthUniverses, getHyperliquidPerpSymbols, getHyperliquidPerpUniverseSnapshot, getHyperliquidWhaleAddresses, getHyperliquidWhaleRegistrySnapshot, getOrderArrivalSnapshot, getPrimaryBinanceBreadthUniverse, isTrackedHyperliquidPerp, isTrackedHyperliquidWhale, resolveHyperliquidPerpFromSignalSymbol, resolveStrategyConfig, validateEntryProtectionAtArrival };
215
+ export { BINANCE_BREADTH_UNIVERSE_KEYS, type BinanceBreadthUniverseDefinition, type BinanceBreadthUniverseKey, type BinanceBreadthUniverseSnapshot, type HyperliquidPerpUniverseSnapshot, type HyperliquidWhaleRegistrySnapshot, buildBinanceBreadthUniverseSnapshot, closeOppositePositionsBeforeOpen, createCloseAllOnGlobalProfitBeforeSignalsHook, createCloseOppositeBeforePlaceOrderHook, createMoveStopToBreakEvenAfterCoreDecisionHook, createMoveStopToBreakEvenOnBarHook, createStrategyRuntime, enrichSignalWithAi, enrichSignalWithBinanceMarketContext, enrichSignalWithCoinMarketCapContext, enrichSignalWithHyperliquidWhaleContext, enrichSignalWithMl, enrichSignalWithMlAi, executeEntryOrder, getBinanceBreadthUniverseSnapshot, getBinanceBreadthUniverses, getHyperliquidPerpSymbols, getHyperliquidPerpUniverseSnapshot, getHyperliquidWhaleAddresses, getHyperliquidWhaleRegistrySnapshot, getOrderArrivalSnapshot, getPrimaryBinanceBreadthUniverse, isTrackedHyperliquidPerp, isTrackedHyperliquidWhale, preloadBinanceMarketContextForWindow, resolveHyperliquidPerpFromSignalSymbol, resolveStrategyConfig, validateEntryProtectionAtArrival };
@@ -77,6 +77,7 @@ __export(strategies_exports, {
77
77
  isKnownStrategy: () => isKnownStrategy,
78
78
  isTrackedHyperliquidPerp: () => isTrackedHyperliquidPerp,
79
79
  isTrackedHyperliquidWhale: () => isTrackedHyperliquidWhale,
80
+ preloadBinanceMarketContextForWindow: () => preloadBinanceMarketContextForWindow,
80
81
  registerStrategyEntries: () => registerStrategyEntries,
81
82
  resetAiRuntimeCache: () => resetAiRuntimeCache,
82
83
  resetStrategyRegistryCache: () => resetStrategyRegistryCache,
@@ -968,6 +969,7 @@ var createStrategyRegistryState = () => ({
968
969
  strategyManifestsMap: /* @__PURE__ */ new Map(),
969
970
  strategyEntriesMap: /* @__PURE__ */ new Map(),
970
971
  strategySourcesMap: /* @__PURE__ */ new Map(),
972
+ strategyModuleResolutions: /* @__PURE__ */ new Map(),
971
973
  pluginsLoadPromise: null
972
974
  });
973
975
  var registryStateByProjectRoot = sharedStrategyRegistry.registryStateByProjectRoot;
@@ -1029,7 +1031,7 @@ var validateStrategyEntries = (moduleName, entries, state) => {
1029
1031
  const strategyName = entry?.manifest?.name;
1030
1032
  if (typeof strategyName !== "string" || !strategyName.trim()) {
1031
1033
  issues.push(`${entryPath}: manifest.name is required`);
1032
- } else if (names.has(strategyName) || state.strategyEntriesMap.has(strategyName)) {
1034
+ } else if (names.has(strategyName) || state?.strategyEntriesMap.has(strategyName)) {
1033
1035
  issues.push(`${entryPath}: duplicate strategy ${strategyName}`);
1034
1036
  } else {
1035
1037
  names.add(strategyName);
@@ -1091,6 +1093,72 @@ var importStrategyPluginModule = async (moduleName, cwd = getTradejsProjectCwd()
1091
1093
  moduleName
1092
1094
  );
1093
1095
  };
1096
+ var resolveStrategyModuleEntry = async ({
1097
+ strategyName,
1098
+ moduleName,
1099
+ cwd
1100
+ }) => {
1101
+ const { projectRoot, state } = getStrategyRegistryState(cwd);
1102
+ const source = moduleName.trim();
1103
+ const resolvedModuleName = resolvePluginModuleSpecifier(source, projectRoot);
1104
+ const cacheKey = `${resolvedModuleName}\0${strategyName}`;
1105
+ const cached = state.strategyModuleResolutions.get(cacheKey);
1106
+ if (cached) return cached;
1107
+ const resolution = (async () => {
1108
+ const moduleExport = await importStrategyPluginModule(
1109
+ resolvedModuleName,
1110
+ projectRoot
1111
+ );
1112
+ const pluginDefinition = extractStrategyPluginDefinition(moduleExport);
1113
+ if (!pluginDefinition) {
1114
+ throw new Error(`${source}: export { strategyEntries } is missing`);
1115
+ }
1116
+ const issues = validateStrategyEntries(
1117
+ source,
1118
+ pluginDefinition.strategyEntries
1119
+ );
1120
+ if (issues.length > 0) {
1121
+ throw new Error(
1122
+ ["Invalid TradeJS strategy module:", ...issues].join("\n")
1123
+ );
1124
+ }
1125
+ const entry = pluginDefinition.strategyEntries.find(
1126
+ (candidate) => candidate.manifest.name === strategyName
1127
+ );
1128
+ if (!entry) {
1129
+ throw new Error(`${source}: strategy ${strategyName} is missing`);
1130
+ }
1131
+ const factory = sharedStrategyRegistry.strategyRuntimeFactory;
1132
+ if (!factory) {
1133
+ throw new Error("Strategy runtime factory is not configured");
1134
+ }
1135
+ const manifestsByName = new Map(
1136
+ pluginDefinition.strategyEntries.map(
1137
+ (candidate) => [candidate.manifest.name, candidate.manifest]
1138
+ )
1139
+ );
1140
+ return {
1141
+ entry,
1142
+ creator: factory({
1143
+ strategyName,
1144
+ defaults: entry.defaults,
1145
+ createCore: entry.createCore,
1146
+ manifest: entry.manifest,
1147
+ detectorKey: entry.detectorKey,
1148
+ detectorNoSignalSkipReason: entry.detectorNoSignalSkipReason,
1149
+ resolveRegisteredManifest: (name) => manifestsByName.get(name) ?? state.strategyManifestsMap.get(name)
1150
+ }),
1151
+ source
1152
+ };
1153
+ })();
1154
+ state.strategyModuleResolutions.set(cacheKey, resolution);
1155
+ try {
1156
+ return await resolution;
1157
+ } catch (error) {
1158
+ state.strategyModuleResolutions.delete(cacheKey);
1159
+ throw error;
1160
+ }
1161
+ };
1094
1162
  var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
1095
1163
  const { projectRoot, state } = getStrategyRegistryState(cwd);
1096
1164
  if (!state.pluginsLoadPromise) {
@@ -1169,7 +1237,11 @@ var ensureStrategyPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
1169
1237
  await state.pluginsLoadPromise;
1170
1238
  };
1171
1239
  var ensureIndicatorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => ensureStrategyPluginsLoaded(cwd);
1172
- var getStrategyCreator = async (name, cwd = getTradejsProjectCwd()) => {
1240
+ var getStrategyCreator = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
1241
+ if (moduleName?.trim()) {
1242
+ await ensureStrategyPluginsLoaded(cwd);
1243
+ return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).creator;
1244
+ }
1173
1245
  await ensureStrategyPluginsLoaded(cwd);
1174
1246
  const { state } = getStrategyRegistryState(cwd);
1175
1247
  return state.strategyCreators.get(name);
@@ -1179,7 +1251,11 @@ var getStrategyDefaults = async (name, cwd = getTradejsProjectCwd()) => {
1179
1251
  const { state } = getStrategyRegistryState(cwd);
1180
1252
  return state.strategyEntriesMap.get(name)?.defaults;
1181
1253
  };
1182
- var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd()) => {
1254
+ var getStrategyPluginSource = async (name, cwd = getTradejsProjectCwd(), moduleName) => {
1255
+ if (moduleName?.trim()) {
1256
+ await ensureStrategyPluginsLoaded(cwd);
1257
+ return (await resolveStrategyModuleEntry({ strategyName: name, moduleName, cwd })).source;
1258
+ }
1183
1259
  await ensureStrategyPluginsLoaded(cwd);
1184
1260
  const { state } = getStrategyRegistryState(cwd);
1185
1261
  return state.strategySourcesMap.get(name);
@@ -2462,9 +2538,50 @@ var DEFAULT_MAX_AGE_BY_INTERVAL = {
2462
2538
  "15m": 30 * 6e4,
2463
2539
  "1h": 2 * 60 * 6e4
2464
2540
  };
2541
+ var MARKET_CONTEXT_PRELOAD_CHUNK_MS = 7 * 24 * 60 * 6e4;
2465
2542
  var binanceMarketContextUnavailable = false;
2466
2543
  var referenceRowsCache = /* @__PURE__ */ new Map();
2467
2544
  var breadthCache = /* @__PURE__ */ new Map();
2545
+ var TRADE_FLOW_NUMERIC_FIELDS = [
2546
+ "trades",
2547
+ "buyBaseVolume",
2548
+ "sellBaseVolume",
2549
+ "buyQuoteVolume",
2550
+ "sellQuoteVolume",
2551
+ "netBaseDelta",
2552
+ "netQuoteDelta",
2553
+ "buyPressurePct"
2554
+ ];
2555
+ var BREADTH_COMMON_NUMERIC_FIELDS = [
2556
+ "symbolsCount",
2557
+ "advancers",
2558
+ "decliners",
2559
+ "unchanged",
2560
+ "advanceDeclineRatio",
2561
+ "pctAboveMa20",
2562
+ "pctAboveMa50",
2563
+ "equalWeightedReturn",
2564
+ "volumeWeightedReturn",
2565
+ "dispersion"
2566
+ ];
2567
+ var BREADTH_REGIME_NUMERIC_FIELDS = [
2568
+ "btcReturn1h",
2569
+ "btcReturn4h",
2570
+ "btcReturn24h",
2571
+ "altBasketReturn1h",
2572
+ "altBasketReturn4h",
2573
+ "altBasketReturn24h",
2574
+ "btcVsAltReturn1h",
2575
+ "btcVsAltReturn4h",
2576
+ "btcVsAltReturn24h",
2577
+ "btcTurnoverShare1h",
2578
+ "btcTurnoverShare24h",
2579
+ "btcTurnoverShareChange24h",
2580
+ "altVolToBtcVol24h",
2581
+ "altDispersion24h"
2582
+ ];
2583
+ var preloadedTradeFlowBySymbol = null;
2584
+ var preloadedBreadthByUniverse = null;
2468
2585
  var parseEnabledFlag = (value, env) => {
2469
2586
  const normalized = String(value ?? "").trim().toLowerCase();
2470
2587
  if (!normalized)
@@ -2500,6 +2617,240 @@ var hasBaseContext = (signal) => Boolean(
2500
2617
  signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext)
2501
2618
  );
2502
2619
  var isBinanceMarketContextEnabled = (env) => parseEnabledFlag(process.env.BINANCE_MARKET_CONTEXT_ENABLED, env);
2620
+ var latestIndexAtOrBefore = (timestamps, timestamp) => {
2621
+ if (!timestamps?.length) return -1;
2622
+ let low = 0;
2623
+ let high = timestamps.length - 1;
2624
+ let match = -1;
2625
+ while (low <= high) {
2626
+ const middle = Math.floor((low + high) / 2);
2627
+ const rowTimestamp = timestamps[middle];
2628
+ if (rowTimestamp == null) break;
2629
+ if (rowTimestamp <= timestamp) {
2630
+ match = middle;
2631
+ low = middle + 1;
2632
+ } else {
2633
+ high = middle - 1;
2634
+ }
2635
+ }
2636
+ return match;
2637
+ };
2638
+ var latestChunkAtOrBefore = (chunks, timestamp) => {
2639
+ if (!chunks?.length) return null;
2640
+ let low = 0;
2641
+ let high = chunks.length - 1;
2642
+ let match = null;
2643
+ while (low <= high) {
2644
+ const middle = Math.floor((low + high) / 2);
2645
+ const chunk = chunks[middle];
2646
+ const firstTimestamp = chunk?.timestamps[0];
2647
+ if (!chunk || firstTimestamp == null) break;
2648
+ if (firstTimestamp <= timestamp) {
2649
+ match = chunk;
2650
+ low = middle + 1;
2651
+ } else {
2652
+ high = middle - 1;
2653
+ }
2654
+ }
2655
+ return match;
2656
+ };
2657
+ var createNumericColumns = (fields, length) => Object.fromEntries(
2658
+ fields.map((field) => [field, new Float64Array(length)])
2659
+ );
2660
+ var packedNumber = (value) => {
2661
+ const numeric = typeof value === "number" ? value : Number(value);
2662
+ return Number.isFinite(numeric) ? numeric : Number.NaN;
2663
+ };
2664
+ var unpackedNumber = (value) => value == null || Number.isNaN(value) ? null : value;
2665
+ var packTradeFlowRows = (rows) => {
2666
+ const counts = /* @__PURE__ */ new Map();
2667
+ for (const row of rows) {
2668
+ counts.set(row.symbol, (counts.get(row.symbol) ?? 0) + 1);
2669
+ }
2670
+ const packed = /* @__PURE__ */ new Map();
2671
+ for (const [symbol, length] of counts) {
2672
+ packed.set(symbol, {
2673
+ symbol,
2674
+ interval: rows.find((row) => row.symbol === symbol)?.interval ?? "15m",
2675
+ timestamps: new Float64Array(length),
2676
+ columns: createNumericColumns(TRADE_FLOW_NUMERIC_FIELDS, length)
2677
+ });
2678
+ }
2679
+ const offsets = /* @__PURE__ */ new Map();
2680
+ for (const row of rows) {
2681
+ const target = packed.get(row.symbol);
2682
+ if (!target) continue;
2683
+ const index = offsets.get(row.symbol) ?? 0;
2684
+ target.timestamps[index] = row.ts.getTime();
2685
+ for (const field of TRADE_FLOW_NUMERIC_FIELDS) {
2686
+ target.columns[field][index] = packedNumber(row[field]);
2687
+ }
2688
+ offsets.set(row.symbol, index + 1);
2689
+ }
2690
+ return packed;
2691
+ };
2692
+ var packBreadthRows = (rows, primaryUniverse) => {
2693
+ const counts = /* @__PURE__ */ new Map();
2694
+ for (const row of rows) {
2695
+ counts.set(row.universe, (counts.get(row.universe) ?? 0) + 1);
2696
+ }
2697
+ const packed = /* @__PURE__ */ new Map();
2698
+ for (const [universe, length] of counts) {
2699
+ packed.set(universe, {
2700
+ universe,
2701
+ interval: rows.find((row) => row.universe === universe)?.interval ?? "15m",
2702
+ timestamps: new Float64Array(length),
2703
+ commonColumns: createNumericColumns(
2704
+ BREADTH_COMMON_NUMERIC_FIELDS,
2705
+ length
2706
+ ),
2707
+ ...universe === primaryUniverse ? {
2708
+ regimeColumns: createNumericColumns(
2709
+ BREADTH_REGIME_NUMERIC_FIELDS,
2710
+ length
2711
+ ),
2712
+ regimes: new Array(length)
2713
+ } : {}
2714
+ });
2715
+ }
2716
+ const offsets = /* @__PURE__ */ new Map();
2717
+ for (const row of rows) {
2718
+ const target = packed.get(row.universe);
2719
+ if (!target) continue;
2720
+ const index = offsets.get(row.universe) ?? 0;
2721
+ target.timestamps[index] = row.ts.getTime();
2722
+ for (const field of BREADTH_COMMON_NUMERIC_FIELDS) {
2723
+ target.commonColumns[field][index] = packedNumber(row[field]);
2724
+ }
2725
+ if (target.regimeColumns) {
2726
+ for (const field of BREADTH_REGIME_NUMERIC_FIELDS) {
2727
+ target.regimeColumns[field][index] = packedNumber(row[field]);
2728
+ }
2729
+ }
2730
+ if (target.regimes) target.regimes[index] = row.btcAltRegime;
2731
+ offsets.set(row.universe, index + 1);
2732
+ }
2733
+ return packed;
2734
+ };
2735
+ var appendPackedChunks = (target, source) => {
2736
+ for (const [key, rows] of source) {
2737
+ const chunks = target.get(key) ?? [];
2738
+ chunks.push(rows);
2739
+ target.set(key, chunks);
2740
+ }
2741
+ };
2742
+ var unpackTradeFlowRow = (chunks, timestamp) => {
2743
+ const rows = latestChunkAtOrBefore(chunks, timestamp);
2744
+ const index = latestIndexAtOrBefore(rows?.timestamps, timestamp);
2745
+ if (!rows || index < 0) return null;
2746
+ return {
2747
+ symbol: rows.symbol,
2748
+ interval: rows.interval,
2749
+ ts: new Date(rows.timestamps[index]),
2750
+ trades: unpackedNumber(rows.columns.trades[index]) ?? 0,
2751
+ buyBaseVolume: unpackedNumber(rows.columns.buyBaseVolume[index]),
2752
+ sellBaseVolume: unpackedNumber(rows.columns.sellBaseVolume[index]),
2753
+ buyQuoteVolume: unpackedNumber(rows.columns.buyQuoteVolume[index]),
2754
+ sellQuoteVolume: unpackedNumber(rows.columns.sellQuoteVolume[index]),
2755
+ netBaseDelta: unpackedNumber(rows.columns.netBaseDelta[index]),
2756
+ netQuoteDelta: unpackedNumber(rows.columns.netQuoteDelta[index]),
2757
+ buyPressurePct: unpackedNumber(rows.columns.buyPressurePct[index])
2758
+ };
2759
+ };
2760
+ var unpackBreadthRow = (chunks, timestamp) => {
2761
+ const rows = latestChunkAtOrBefore(chunks, timestamp);
2762
+ const index = latestIndexAtOrBefore(rows?.timestamps, timestamp);
2763
+ if (!rows || index < 0) return null;
2764
+ const commonNumeric = Object.fromEntries(
2765
+ BREADTH_COMMON_NUMERIC_FIELDS.map((field) => [
2766
+ field,
2767
+ unpackedNumber(rows.commonColumns[field][index])
2768
+ ])
2769
+ );
2770
+ const regimeNumeric = Object.fromEntries(
2771
+ BREADTH_REGIME_NUMERIC_FIELDS.map((field) => [
2772
+ field,
2773
+ unpackedNumber(rows.regimeColumns?.[field][index])
2774
+ ])
2775
+ );
2776
+ return {
2777
+ universe: rows.universe,
2778
+ interval: rows.interval,
2779
+ ts: new Date(rows.timestamps[index]),
2780
+ ...commonNumeric,
2781
+ ...regimeNumeric,
2782
+ symbolsCount: commonNumeric.symbolsCount ?? 0,
2783
+ advancers: commonNumeric.advancers ?? 0,
2784
+ decliners: commonNumeric.decliners ?? 0,
2785
+ unchanged: commonNumeric.unchanged ?? 0,
2786
+ btcAltRegime: rows.regimes?.[index] ?? null
2787
+ };
2788
+ };
2789
+ var toAsOfRow = (row, timestamp, maxAgeMs) => {
2790
+ if (!row) return null;
2791
+ const ageMs = timestamp - row.ts.getTime();
2792
+ return {
2793
+ ...row,
2794
+ ageMs: Number.isFinite(ageMs) ? ageMs : null,
2795
+ stale: !Number.isFinite(ageMs) || ageMs < 0 || ageMs > maxAgeMs
2796
+ };
2797
+ };
2798
+ var preloadBinanceMarketContextForWindow = async (params) => {
2799
+ const maxAgeMs = params.maxAgeMs ?? DEFAULT_MAX_AGE_BY_INTERVAL[params.interval];
2800
+ const referenceSymbols = getReferenceSymbols();
2801
+ const breadthDefinitions = getBinanceBreadthUniverses();
2802
+ const breadthUniverses = breadthDefinitions.map(({ universe }) => universe);
2803
+ const primaryBreadthUniverse = breadthDefinitions.find(
2804
+ ({ key }) => key === "top30"
2805
+ ).universe;
2806
+ const timeoutMs = params.timeoutMs ?? 5 * 6e4;
2807
+ const chunkMs = params.chunkMs ?? MARKET_CONTEXT_PRELOAD_CHUNK_MS;
2808
+ if (!Number.isSafeInteger(chunkMs) || chunkMs <= 0) {
2809
+ throw new Error(`Invalid market context preload chunk: ${chunkMs}`);
2810
+ }
2811
+ const packedTradeFlow = /* @__PURE__ */ new Map();
2812
+ const packedBreadth = /* @__PURE__ */ new Map();
2813
+ let tradeFlowRowsCount = 0;
2814
+ let breadthRowsCount = 0;
2815
+ let fromMs = params.startMs - maxAgeMs;
2816
+ while (fromMs <= params.endMs) {
2817
+ const toMs = Math.min(params.endMs, fromMs + chunkMs - 1);
2818
+ const [tradeFlowRows, breadthRows] = await Promise.all([
2819
+ (0, import_marketContext.getMarketTradeFlowRows)({
2820
+ symbols: referenceSymbols,
2821
+ interval: params.interval,
2822
+ fromMs,
2823
+ toMs,
2824
+ timeoutMs,
2825
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
2826
+ }),
2827
+ (0, import_marketContext.getMarketBreadthRows)({
2828
+ universes: breadthUniverses,
2829
+ interval: params.interval,
2830
+ fromMs,
2831
+ toMs,
2832
+ timeoutMs,
2833
+ ...params.abortSignal ? { signal: params.abortSignal } : {}
2834
+ })
2835
+ ]);
2836
+ tradeFlowRowsCount += tradeFlowRows.length;
2837
+ breadthRowsCount += breadthRows.length;
2838
+ appendPackedChunks(packedTradeFlow, packTradeFlowRows(tradeFlowRows));
2839
+ appendPackedChunks(
2840
+ packedBreadth,
2841
+ packBreadthRows(breadthRows, primaryBreadthUniverse)
2842
+ );
2843
+ fromMs = toMs + 1;
2844
+ }
2845
+ preloadedTradeFlowBySymbol = packedTradeFlow;
2846
+ preloadedBreadthByUniverse = packedBreadth;
2847
+ referenceRowsCache.clear();
2848
+ breadthCache.clear();
2849
+ return {
2850
+ tradeFlowRows: tradeFlowRowsCount,
2851
+ breadthRows: breadthRowsCount
2852
+ };
2853
+ };
2503
2854
  var toTradeFlowContext = (row, interval) => row ? {
2504
2855
  source: "binance_agg_trades",
2505
2856
  interval,
@@ -2522,6 +2873,24 @@ var getCachedReferenceRows = ({
2522
2873
  maxAgeMs,
2523
2874
  abortSignal
2524
2875
  }) => {
2876
+ if (preloadedTradeFlowBySymbol) {
2877
+ return Promise.resolve(
2878
+ referenceSymbols.map((symbol) => ({
2879
+ symbol,
2880
+ tradeFlow: toTradeFlowContext(
2881
+ toAsOfRow(
2882
+ unpackTradeFlowRow(
2883
+ preloadedTradeFlowBySymbol?.get(symbol),
2884
+ timestamp
2885
+ ),
2886
+ timestamp,
2887
+ maxAgeMs
2888
+ ),
2889
+ interval
2890
+ )
2891
+ }))
2892
+ );
2893
+ }
2525
2894
  const key = `${referenceSymbols.join(",")}:${interval}:${timestamp}:${maxAgeMs}`;
2526
2895
  const cached = referenceRowsCache.get(key);
2527
2896
  if (cached) return cached;
@@ -2551,6 +2920,18 @@ var getCachedBreadth = ({
2551
2920
  maxAgeMs,
2552
2921
  abortSignal
2553
2922
  }) => {
2923
+ if (preloadedBreadthByUniverse) {
2924
+ return Promise.resolve(
2925
+ toAsOfRow(
2926
+ unpackBreadthRow(
2927
+ preloadedBreadthByUniverse.get(breadthUniverse),
2928
+ timestamp
2929
+ ),
2930
+ timestamp,
2931
+ maxAgeMs
2932
+ )
2933
+ );
2934
+ }
2554
2935
  const key = `${breadthUniverse}:${interval}:${timestamp}:${maxAgeMs}`;
2555
2936
  const cached = breadthCache.get(key);
2556
2937
  if (cached) return cached;
@@ -2757,12 +3138,20 @@ var SOURCE_EXCHANGE_LIQUIDITY = "coinmarketcap_exchange_liquidity";
2757
3138
  var SOURCE_FEAR_GREED = "coinmarketcap_fear_greed";
2758
3139
  var SOURCE_INDEX = "coinmarketcap_index";
2759
3140
  var DAY_MS = 864e5;
3141
+ var MAX_TIMESTAMP_CACHE_ENTRIES = 512;
2760
3142
  var coinMarketCapContextUnavailable = false;
2761
3143
  var globalContextCache = /* @__PURE__ */ new Map();
2762
3144
  var referenceContextCache = /* @__PURE__ */ new Map();
2763
3145
  var exchangeLiquidityContextCache = /* @__PURE__ */ new Map();
2764
3146
  var fearGreedContextCache = /* @__PURE__ */ new Map();
2765
3147
  var indexContextCache = /* @__PURE__ */ new Map();
3148
+ var setBoundedCache = (cache, key, value) => {
3149
+ if (cache.size >= MAX_TIMESTAMP_CACHE_ENTRIES) {
3150
+ const oldestKey = cache.keys().next().value;
3151
+ if (oldestKey != null) cache.delete(oldestKey);
3152
+ }
3153
+ cache.set(key, value);
3154
+ };
2766
3155
  var parseEnabledFlag2 = (value, env) => {
2767
3156
  const normalized = String(value ?? "").trim().toLowerCase();
2768
3157
  if (!normalized) {
@@ -2869,7 +3258,7 @@ var getCachedGlobalContext = ({
2869
3258
  maxAgeMs,
2870
3259
  ...abortSignal ? { signal: abortSignal } : {}
2871
3260
  });
2872
- globalContextCache.set(key, promise);
3261
+ setBoundedCache(globalContextCache, key, promise);
2873
3262
  void promise.catch(() => globalContextCache.delete(key));
2874
3263
  return promise;
2875
3264
  };
@@ -2889,7 +3278,7 @@ var getCachedReferenceContexts = ({
2889
3278
  maxAgeMs,
2890
3279
  ...abortSignal ? { signal: abortSignal } : {}
2891
3280
  });
2892
- referenceContextCache.set(key, promise);
3281
+ setBoundedCache(referenceContextCache, key, promise);
2893
3282
  void promise.catch(() => referenceContextCache.delete(key));
2894
3283
  return promise;
2895
3284
  };
@@ -2908,7 +3297,7 @@ var getCachedExchangeLiquidityContext = ({
2908
3297
  maxAgeMs,
2909
3298
  ...abortSignal ? { signal: abortSignal } : {}
2910
3299
  });
2911
- exchangeLiquidityContextCache.set(key, promise);
3300
+ setBoundedCache(exchangeLiquidityContextCache, key, promise);
2912
3301
  void promise.catch(() => exchangeLiquidityContextCache.delete(key));
2913
3302
  return promise;
2914
3303
  };
@@ -2927,7 +3316,7 @@ var getCachedFearGreedContext = ({
2927
3316
  maxAgeMs,
2928
3317
  ...abortSignal ? { signal: abortSignal } : {}
2929
3318
  });
2930
- fearGreedContextCache.set(key, promise);
3319
+ setBoundedCache(fearGreedContextCache, key, promise);
2931
3320
  void promise.catch(() => fearGreedContextCache.delete(key));
2932
3321
  return promise;
2933
3322
  };
@@ -2947,7 +3336,7 @@ var getCachedIndexContexts = ({
2947
3336
  maxAgeMs,
2948
3337
  ...abortSignal ? { signal: abortSignal } : {}
2949
3338
  });
2950
- indexContextCache.set(key, promise);
3339
+ setBoundedCache(indexContextCache, key, promise);
2951
3340
  void promise.catch(() => indexContextCache.delete(key));
2952
3341
  return promise;
2953
3342
  };
@@ -3761,7 +4150,7 @@ var signalIntervalToMarketInterval2 = (value) => {
3761
4150
  var hasBaseContext3 = (signal) => Boolean(
3762
4151
  signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext)
3763
4152
  );
3764
- var setBoundedCache = (key, value) => {
4153
+ var setBoundedCache2 = (key, value) => {
3765
4154
  if (contextCache.size >= MAX_CACHE_ENTRIES) {
3766
4155
  const oldestKey = contextCache.keys().next().value;
3767
4156
  if (oldestKey != null) contextCache.delete(oldestKey);
@@ -4008,7 +4397,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
4008
4397
  whaleRegistryFingerprint: whales.fingerprint,
4009
4398
  ...params.abortSignal ? { signal: params.abortSignal } : {}
4010
4399
  });
4011
- setBoundedCache(cacheKey, pending);
4400
+ setBoundedCache2(cacheKey, pending);
4012
4401
  }
4013
4402
  row = await pending;
4014
4403
  }
@@ -5156,20 +5545,23 @@ var buildHookCtx = ({
5156
5545
  strategyConfig,
5157
5546
  env,
5158
5547
  isConfigFromBacktest
5159
- }) => ({
5160
- connector,
5161
- strategyName,
5162
- userName,
5163
- symbol,
5164
- ...universe ? { universe } : {},
5165
- ...assetClass ? { assetClass } : {},
5166
- ...accountId ? { accountId } : {},
5167
- ...deploymentId ? { deploymentId } : {},
5168
- ...policyProfileId ? { policyProfileId } : {},
5169
- strategyConfig,
5170
- env,
5171
- isConfigFromBacktest
5172
- });
5548
+ }) => {
5549
+ const context = {
5550
+ connector,
5551
+ strategyName,
5552
+ userName,
5553
+ symbol
5554
+ };
5555
+ if (universe) context.universe = universe;
5556
+ if (assetClass) context.assetClass = assetClass;
5557
+ if (accountId) context.accountId = accountId;
5558
+ if (deploymentId) context.deploymentId = deploymentId;
5559
+ if (policyProfileId) context.policyProfileId = policyProfileId;
5560
+ context.strategyConfig = strategyConfig;
5561
+ context.env = env;
5562
+ context.isConfigFromBacktest = isConfigFromBacktest;
5563
+ return context;
5564
+ };
5173
5565
  var shouldRecordRuntimeJournal = ({
5174
5566
  env,
5175
5567
  config
@@ -6900,6 +7292,7 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
6900
7292
  isKnownStrategy,
6901
7293
  isTrackedHyperliquidPerp,
6902
7294
  isTrackedHyperliquidWhale,
7295
+ preloadBinanceMarketContextForWindow,
6903
7296
  registerStrategyEntries,
6904
7297
  resetAiRuntimeCache,
6905
7298
  resetStrategyRegistryCache,
@@ -19,10 +19,11 @@ import {
19
19
  getPrimaryBinanceBreadthUniverse,
20
20
  isTrackedHyperliquidPerp,
21
21
  isTrackedHyperliquidWhale,
22
+ preloadBinanceMarketContextForWindow,
22
23
  resolveHyperliquidPerpFromSignalSymbol,
23
24
  resolveStrategyConfig,
24
25
  validateEntryProtectionAtArrival
25
- } from "./chunk-XHNJ757K.mjs";
26
+ } from "./chunk-VQSGH7YA.mjs";
26
27
  import {
27
28
  DEFAULT_AI_MODEL,
28
29
  MAX_AI_SERIES_POINTS,
@@ -52,7 +53,7 @@ import {
52
53
  runAiPromptLocal,
53
54
  strategies,
54
55
  trimSeriesDeep
55
- } from "./chunk-IKRSNRP6.mjs";
56
+ } from "./chunk-UITD6LG6.mjs";
56
57
  import "./chunk-LZDXRXIU.mjs";
57
58
  import "./chunk-Y6FXYEAI.mjs";
58
59
 
@@ -472,6 +473,7 @@ export {
472
473
  isKnownStrategy,
473
474
  isTrackedHyperliquidPerp,
474
475
  isTrackedHyperliquidWhale,
476
+ preloadBinanceMarketContextForWindow,
475
477
  registerStrategyEntries,
476
478
  resetAiRuntimeCache,
477
479
  resetStrategyRegistryCache,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/node",
3
- "version": "3.1.27-beta.250",
3
+ "version": "3.1.28-beta.251",
4
4
  "description": "Node-only runtime for the TradeJS TypeScript framework: strategies, backtests, Pine strategy loading, and plugin registries.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -89,9 +89,9 @@
89
89
  "dependencies": {
90
90
  "@langchain/core": "^1.2.3",
91
91
  "@langchain/openai": "^1.5.5",
92
- "@tradejs/core": "^3.1.27-beta.250",
93
- "@tradejs/infra": "^3.1.27-beta.250",
94
- "@tradejs/types": "^3.1.27-beta.250",
92
+ "@tradejs/core": "^3.1.28-beta.251",
93
+ "@tradejs/infra": "^3.1.28-beta.251",
94
+ "@tradejs/types": "^3.1.28-beta.251",
95
95
  "@types/node": "^24",
96
96
  "chalk": "4.1.2",
97
97
  "ioredis": "5.11.1",
@@ -104,7 +104,7 @@
104
104
  "typescript": "^5.9"
105
105
  },
106
106
  "devDependencies": {
107
- "@tradejs/indicators": "^3.1.27-beta.250",
107
+ "@tradejs/indicators": "^3.1.28-beta.251",
108
108
  "@tradejs/strategy-adaptive-momentum-ribbon": "3.0.2",
109
109
  "@tradejs/strategy-hyperliquid-consensus": "3.0.2",
110
110
  "@tradejs/strategy-kit": "3.0.4",