@tradejs/node 2.0.5 → 2.0.7

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.
@@ -5537,6 +5537,7 @@ var require_lodash = __commonJS({
5537
5537
  // src/strategies.ts
5538
5538
  var strategies_exports = {};
5539
5539
  __export(strategies_exports, {
5540
+ BINANCE_BREADTH_UNIVERSE_KEYS: () => BINANCE_BREADTH_UNIVERSE_KEYS,
5540
5541
  DEFAULT_AI_MODEL: () => DEFAULT_AI_MODEL,
5541
5542
  MAX_AI_SERIES_POINTS: () => MAX_AI_SERIES_POINTS,
5542
5543
  askAI: () => askAI,
@@ -5544,6 +5545,7 @@ __export(strategies_exports, {
5544
5545
  buildAiPayload: () => buildAiPayload,
5545
5546
  buildAiPrompts: () => buildAiPrompts,
5546
5547
  buildAiSystemPrompt: () => buildAiSystemPrompt,
5548
+ buildBinanceBreadthUniverseSnapshot: () => buildBinanceBreadthUniverseSnapshot,
5547
5549
  buildCompactAiIndicatorsSnapshot: () => buildCompactAiIndicatorsSnapshot,
5548
5550
  closeOppositePositionsBeforeOpen: () => closeOppositePositionsBeforeOpen,
5549
5551
  createCloseAllOnGlobalProfitBeforeSignalsHook: () => createCloseAllOnGlobalProfitBeforeSignalsHook,
@@ -5561,8 +5563,12 @@ __export(strategies_exports, {
5561
5563
  ensureStrategyPluginsLoaded: () => ensureStrategyPluginsLoaded,
5562
5564
  executeEntryOrder: () => executeEntryOrder,
5563
5565
  getAvailableStrategyNames: () => getAvailableStrategyNames,
5566
+ getBinanceBreadthUniverseSnapshot: () => getBinanceBreadthUniverseSnapshot,
5567
+ getBinanceBreadthUniverses: () => getBinanceBreadthUniverses,
5564
5568
  getDeterministicAiGateContext: () => getDeterministicAiGateContext,
5565
5569
  getOpenRouterModelKwargs: () => getOpenRouterModelKwargs,
5570
+ getOrderArrivalSnapshot: () => getOrderArrivalSnapshot,
5571
+ getPrimaryBinanceBreadthUniverse: () => getPrimaryBinanceBreadthUniverse,
5566
5572
  getRegisteredManifests: () => getRegisteredManifests,
5567
5573
  getRegisteredStrategies: () => getRegisteredStrategies,
5568
5574
  getStrategyCreator: () => getStrategyCreator,
@@ -5575,7 +5581,8 @@ __export(strategies_exports, {
5575
5581
  runAiPrompt: () => runAiPrompt,
5576
5582
  runAiPromptLocal: () => runAiPromptLocal,
5577
5583
  strategies: () => strategies,
5578
- trimSeriesDeep: () => trimSeriesDeep
5584
+ trimSeriesDeep: () => trimSeriesDeep,
5585
+ validateEntryProtectionAtArrival: () => validateEntryProtectionAtArrival
5579
5586
  });
5580
5587
  module.exports = __toCommonJS(strategies_exports);
5581
5588
  __reExport(strategies_exports, require("@tradejs/core/strategies"), module.exports);
@@ -5763,6 +5770,51 @@ var buildMarketBreadthContextFromSignal = (signal) => {
5763
5770
  dispersion: toFiniteNumber(breadth.dispersion)
5764
5771
  };
5765
5772
  };
5773
+ var buildMarketBreadthsContextFromSignal = (signal) => {
5774
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5775
+ const relative = toRecord(baseContext?.relative);
5776
+ const breadths = toRecord(relative?.marketBreadths);
5777
+ const build = (key) => {
5778
+ const breadth = toRecord(breadths?.[key]);
5779
+ if (!breadth) {
5780
+ return {
5781
+ source: null,
5782
+ available: false,
5783
+ universe: null,
5784
+ interval: null,
5785
+ stale: null,
5786
+ symbolsCount: null,
5787
+ advanceDeclineRatio: null,
5788
+ pctAboveMa20: null,
5789
+ pctAboveMa50: null,
5790
+ equalWeightedReturn: null,
5791
+ volumeWeightedReturn: null,
5792
+ dispersion: null
5793
+ };
5794
+ }
5795
+ return {
5796
+ source: String(breadth.source ?? ""),
5797
+ available: true,
5798
+ universe: String(breadth.universe ?? ""),
5799
+ interval: String(breadth.interval ?? ""),
5800
+ stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
5801
+ symbolsCount: toFiniteNumber(breadth.symbolsCount),
5802
+ advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
5803
+ pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
5804
+ pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
5805
+ equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
5806
+ volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
5807
+ dispersion: toFiniteNumber(breadth.dispersion)
5808
+ };
5809
+ };
5810
+ return {
5811
+ top5: build("top5"),
5812
+ top10: build("top10"),
5813
+ top30: build("top30"),
5814
+ top50: build("top50"),
5815
+ top100: build("top100")
5816
+ };
5817
+ };
5766
5818
  var buildTargetVsBtcContextFromSignal = (signal) => {
5767
5819
  const baseContext = toRecord(signal.additionalIndicators?.baseContext);
5768
5820
  const relative = toRecord(baseContext?.relative);
@@ -6100,6 +6152,7 @@ var buildAiMarketContext = (signal) => ({
6100
6152
  },
6101
6153
  relative: {
6102
6154
  marketBreadth: buildMarketBreadthContextFromSignal(signal),
6155
+ marketBreadths: buildMarketBreadthsContextFromSignal(signal),
6103
6156
  targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
6104
6157
  btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
6105
6158
  cmcGlobal: buildCmcGlobalContextFromSignal(signal),
@@ -6888,7 +6941,7 @@ Input payload structure:
6888
6941
  \u2022 \`marketContext.execution.binanceCoinbaseSpread\`: AI-friendly BTC spread view projected from \`payload.additionalIndicators.baseContext.relative.execution.venueSpread\`; \`value=(Coinbase-Binance)/Binance\`, \`bps=value*10000\`.
6889
6942
  \u2022 \`marketContext.participation.trueDelta\`: Binance taker buy/sell volume delta from kline payload when \`source=kline_taker_volume\`; otherwise absent/unavailable.
6890
6943
  \u2022 \`marketContext.participation.tradeFlow\`: Binance aggTrades buy/sell pressure buckets when available.
6891
- \u2022 \`marketContext.relative.marketBreadth\`: equal/volume-weighted alt-basket return, advance/decline ratio, and MA breadth for the configured Binance breadth universe.
6944
+ \u2022 \`marketContext.relative.marketBreadths.top5|top10|top30|top50|top100\`: equal/volume-weighted alt-basket return, advance/decline ratio, and MA breadth for the five versioned Binance breadth universes. \`marketBreadth\` remains the top30 primary view used by existing gates.
6892
6945
  \u2022 \`marketContext.relative.targetVsBtc\`: target/BTC ratio returns, alpha, beta, and short-window correlation; use it to decide whether the target is leading or lagging BTC in the signal direction.
6893
6946
  \u2022 \`marketContext.relative.btcAltRegime\`: Binance-derived BTC-vs-alt basket regime, BTC/alt 24h returns, BTC turnover share, and alt dispersion; use it as a broad alt-market risk pocket.
6894
6947
  \u2022 \`marketContext.relative.cmcGlobal\`: historical CoinMarketCap global market metrics: total/alt market cap, total/alt volume, BTC/ETH dominance and 24h changes, active markets, \`interval\`, and \`altLiquidityRegime\`.
@@ -7413,7 +7466,7 @@ var getActiveRuntimeTrade = async (params) => {
7413
7466
  import_redis2.redisKeys.runtimeTrade(userName, orderId),
7414
7467
  null
7415
7468
  );
7416
- if (!existing) {
7469
+ if (!existing || existing.status !== "active") {
7417
7470
  await (0, import_redis2.delKey)(
7418
7471
  import_redis2.redisKeys.runtimeActiveTrade(userName, symbol, deploymentId ?? accountId)
7419
7472
  );
@@ -7742,6 +7795,313 @@ var enrichSignalWithDerivativesContext = async (params) => {
7742
7795
  var import_timescale2 = require("@tradejs/infra/timescale");
7743
7796
  var import_logger5 = require("@tradejs/infra/logger");
7744
7797
  var import_strategies2 = require("@tradejs/core/strategies");
7798
+
7799
+ // src/binanceBreadthUniverses.ts
7800
+ var import_node_crypto2 = require("crypto");
7801
+
7802
+ // src/config/binanceBreadthUniverses.json
7803
+ var binanceBreadthUniverses_default = {
7804
+ schemaVersion: 1,
7805
+ updatedAt: "2026-07-24T15:28:27.234Z",
7806
+ source: "binance_spot_usdt_turnover24h",
7807
+ fingerprint: "43df006e5510bb99",
7808
+ universes: {
7809
+ top5: {
7810
+ size: 5,
7811
+ fingerprint: "ba5b73456b18",
7812
+ symbols: ["ETHUSDT", "USD1USDT", "SOLUSDT", "OPNUSDT", "VANAUSDT"]
7813
+ },
7814
+ top10: {
7815
+ size: 10,
7816
+ fingerprint: "370eb5af9778",
7817
+ symbols: [
7818
+ "ETHUSDT",
7819
+ "USD1USDT",
7820
+ "SOLUSDT",
7821
+ "OPNUSDT",
7822
+ "VANAUSDT",
7823
+ "DEXEUSDT",
7824
+ "BANKUSDT",
7825
+ "ZECUSDT",
7826
+ "XRPUSDT",
7827
+ "RLUSDUSDT"
7828
+ ]
7829
+ },
7830
+ top30: {
7831
+ size: 30,
7832
+ fingerprint: "685a7e7f5fce",
7833
+ symbols: [
7834
+ "ETHUSDT",
7835
+ "USD1USDT",
7836
+ "SOLUSDT",
7837
+ "OPNUSDT",
7838
+ "VANAUSDT",
7839
+ "DEXEUSDT",
7840
+ "BANKUSDT",
7841
+ "ZECUSDT",
7842
+ "XRPUSDT",
7843
+ "RLUSDUSDT",
7844
+ "BNBUSDT",
7845
+ "DOGEUSDT",
7846
+ "REUSDT",
7847
+ "RIFUSDT",
7848
+ "ZAMAUSDT",
7849
+ "TRXUSDT",
7850
+ "EURUSDT",
7851
+ "AEROUSDT",
7852
+ "UUSDT",
7853
+ "SUIUSDT",
7854
+ "XAUTUSDT",
7855
+ "NEARUSDT",
7856
+ "LTCUSDT",
7857
+ "SPCXBUSDT",
7858
+ "LAUSDT",
7859
+ "AVAXUSDT",
7860
+ "WLDUSDT",
7861
+ "ONDOUSDT",
7862
+ "XLMUSDT",
7863
+ "UNIUSDT"
7864
+ ]
7865
+ },
7866
+ top50: {
7867
+ size: 50,
7868
+ fingerprint: "cb9725e24141",
7869
+ symbols: [
7870
+ "ETHUSDT",
7871
+ "USD1USDT",
7872
+ "SOLUSDT",
7873
+ "OPNUSDT",
7874
+ "VANAUSDT",
7875
+ "DEXEUSDT",
7876
+ "BANKUSDT",
7877
+ "ZECUSDT",
7878
+ "XRPUSDT",
7879
+ "RLUSDUSDT",
7880
+ "BNBUSDT",
7881
+ "DOGEUSDT",
7882
+ "REUSDT",
7883
+ "RIFUSDT",
7884
+ "ZAMAUSDT",
7885
+ "TRXUSDT",
7886
+ "EURUSDT",
7887
+ "AEROUSDT",
7888
+ "UUSDT",
7889
+ "SUIUSDT",
7890
+ "XAUTUSDT",
7891
+ "NEARUSDT",
7892
+ "LTCUSDT",
7893
+ "SPCXBUSDT",
7894
+ "LAUSDT",
7895
+ "AVAXUSDT",
7896
+ "WLDUSDT",
7897
+ "ONDOUSDT",
7898
+ "XLMUSDT",
7899
+ "UNIUSDT",
7900
+ "ADAUSDT",
7901
+ "SNDKBUSDT",
7902
+ "KAITOUSDT",
7903
+ "UTKUSDT",
7904
+ "PEPEUSDT",
7905
+ "WLFIUSDT",
7906
+ "PAXGUSDT",
7907
+ "ENAUSDT",
7908
+ "TAOUSDT",
7909
+ "AAVEUSDT",
7910
+ "CRCLBUSDT",
7911
+ "LINKUSDT",
7912
+ "TONUSDT",
7913
+ "ALLOUSDT",
7914
+ "PUMPUSDT",
7915
+ "INJUSDT",
7916
+ "HOMEUSDT",
7917
+ "MUBUSDT",
7918
+ "TSLABUSDT",
7919
+ "GRAMUSDT"
7920
+ ]
7921
+ },
7922
+ top100: {
7923
+ size: 100,
7924
+ fingerprint: "f397883a81cf",
7925
+ symbols: [
7926
+ "ETHUSDT",
7927
+ "USD1USDT",
7928
+ "SOLUSDT",
7929
+ "OPNUSDT",
7930
+ "VANAUSDT",
7931
+ "DEXEUSDT",
7932
+ "BANKUSDT",
7933
+ "ZECUSDT",
7934
+ "XRPUSDT",
7935
+ "RLUSDUSDT",
7936
+ "BNBUSDT",
7937
+ "DOGEUSDT",
7938
+ "REUSDT",
7939
+ "RIFUSDT",
7940
+ "ZAMAUSDT",
7941
+ "TRXUSDT",
7942
+ "EURUSDT",
7943
+ "AEROUSDT",
7944
+ "UUSDT",
7945
+ "SUIUSDT",
7946
+ "XAUTUSDT",
7947
+ "NEARUSDT",
7948
+ "LTCUSDT",
7949
+ "SPCXBUSDT",
7950
+ "LAUSDT",
7951
+ "AVAXUSDT",
7952
+ "WLDUSDT",
7953
+ "ONDOUSDT",
7954
+ "XLMUSDT",
7955
+ "UNIUSDT",
7956
+ "ADAUSDT",
7957
+ "SNDKBUSDT",
7958
+ "KAITOUSDT",
7959
+ "UTKUSDT",
7960
+ "PEPEUSDT",
7961
+ "WLFIUSDT",
7962
+ "PAXGUSDT",
7963
+ "ENAUSDT",
7964
+ "TAOUSDT",
7965
+ "AAVEUSDT",
7966
+ "CRCLBUSDT",
7967
+ "LINKUSDT",
7968
+ "TONUSDT",
7969
+ "ALLOUSDT",
7970
+ "PUMPUSDT",
7971
+ "INJUSDT",
7972
+ "HOMEUSDT",
7973
+ "MUBUSDT",
7974
+ "TSLABUSDT",
7975
+ "GRAMUSDT",
7976
+ "\u5E01\u5B89\u4EBA\u751FUSDT",
7977
+ "SYNUSDT",
7978
+ "TRUMPUSDT",
7979
+ "PROMUSDT",
7980
+ "XPLUSDT",
7981
+ "HBARUSDT",
7982
+ "ERAUSDT",
7983
+ "BCHUSDT",
7984
+ "SOXLBUSDT",
7985
+ "DOTUSDT",
7986
+ "EPICUSDT",
7987
+ "KITEUSDT",
7988
+ "NFPUSDT",
7989
+ "KGSTUSDT",
7990
+ "FILUSDT",
7991
+ "FETUSDT",
7992
+ "KORUBUSDT",
7993
+ "ARBUSDT",
7994
+ "STXUSDT",
7995
+ "HEIUSDT",
7996
+ "BARDUSDT",
7997
+ "DASHUSDT",
7998
+ "ASTERUSDT",
7999
+ "VANRYUSDT",
8000
+ "INTCBUSDT",
8001
+ "ETCUSDT",
8002
+ "EIGENUSDT",
8003
+ "PENGUUSDT",
8004
+ "LRCUSDT",
8005
+ "VIRTUALUSDT",
8006
+ "POLUSDT",
8007
+ "ICPUSDT",
8008
+ "ZBTUSDT",
8009
+ "SKHYBUSDT",
8010
+ "SHIBUSDT",
8011
+ "LDOUSDT",
8012
+ "APTUSDT",
8013
+ "JTOUSDT",
8014
+ "NOMUSDT",
8015
+ "ZROUSDT",
8016
+ "OPUSDT",
8017
+ "BONKUSDT",
8018
+ "SKLUSDT",
8019
+ "HEMIUSDT",
8020
+ "TNSRUSDT",
8021
+ "INTWBUSDT",
8022
+ "BIOUSDT",
8023
+ "TIAUSDT",
8024
+ "ORDIUSDT",
8025
+ "XUSDUSDT"
8026
+ ]
8027
+ }
8028
+ }
8029
+ };
8030
+
8031
+ // src/binanceBreadthUniverses.ts
8032
+ var BINANCE_BREADTH_UNIVERSE_KEYS = [
8033
+ "top5",
8034
+ "top10",
8035
+ "top30",
8036
+ "top50",
8037
+ "top100"
8038
+ ];
8039
+ var fingerprint = (value, length) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
8040
+ var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
8041
+ var buildBinanceBreadthUniverseSnapshot = ({
8042
+ rankedSymbols,
8043
+ updatedAt = (/* @__PURE__ */ new Date()).toISOString()
8044
+ }) => {
8045
+ const uniqueSymbols = [...new Set(normalizeSymbols(rankedSymbols))];
8046
+ if (uniqueSymbols.length < 100) {
8047
+ throw new Error(
8048
+ `Binance breadth snapshot requires at least 100 symbols, got ${uniqueSymbols.length}`
8049
+ );
8050
+ }
8051
+ const universes = Object.fromEntries(
8052
+ BINANCE_BREADTH_UNIVERSE_KEYS.map((key) => {
8053
+ const size = Number(key.slice(3));
8054
+ const symbols = uniqueSymbols.slice(0, size);
8055
+ return [
8056
+ key,
8057
+ {
8058
+ size,
8059
+ fingerprint: fingerprint(symbols, 12),
8060
+ symbols
8061
+ }
8062
+ ];
8063
+ })
8064
+ );
8065
+ return {
8066
+ schemaVersion: 1,
8067
+ updatedAt,
8068
+ source: "binance_spot_usdt_turnover24h",
8069
+ fingerprint: fingerprint(universes, 16),
8070
+ universes
8071
+ };
8072
+ };
8073
+ var validateSnapshot = (value) => {
8074
+ const rebuilt = buildBinanceBreadthUniverseSnapshot({
8075
+ rankedSymbols: value.universes.top100.symbols,
8076
+ updatedAt: value.updatedAt
8077
+ });
8078
+ if (value.schemaVersion !== 1 || value.source !== rebuilt.source || value.fingerprint !== rebuilt.fingerprint) {
8079
+ throw new Error("Invalid Binance breadth universe snapshot fingerprint");
8080
+ }
8081
+ for (const key of BINANCE_BREADTH_UNIVERSE_KEYS) {
8082
+ const actual = value.universes[key];
8083
+ const expected = rebuilt.universes[key];
8084
+ if (actual.size !== expected.size || actual.fingerprint !== expected.fingerprint || JSON.stringify(actual.symbols) !== JSON.stringify(expected.symbols)) {
8085
+ throw new Error(`Invalid Binance breadth universe snapshot: ${key}`);
8086
+ }
8087
+ }
8088
+ return value;
8089
+ };
8090
+ var snapshot = validateSnapshot(binanceBreadthUniverses_default);
8091
+ var getBinanceBreadthUniverseSnapshot = () => snapshot;
8092
+ var getBinanceBreadthUniverses = () => BINANCE_BREADTH_UNIVERSE_KEYS.map((key) => {
8093
+ const definition = snapshot.universes[key];
8094
+ return {
8095
+ key,
8096
+ size: definition.size,
8097
+ fingerprint: definition.fingerprint,
8098
+ universe: `binance_${key}_usdt_${definition.fingerprint}`,
8099
+ symbols: [...definition.symbols]
8100
+ };
8101
+ });
8102
+ var getPrimaryBinanceBreadthUniverse = () => getBinanceBreadthUniverses().find(({ key }) => key === "top30");
8103
+
8104
+ // src/strategyHelpers/binanceMarketContext.ts
7745
8105
  var DEFAULT_MAX_AGE_BY_INTERVAL = {
7746
8106
  "1m": 3 * 6e4,
7747
8107
  "5m": 10 * 6e4,
@@ -7773,7 +8133,6 @@ var signalIntervalToMarketInterval = (value) => {
7773
8133
  return "15m";
7774
8134
  };
7775
8135
  var resolveMarketInterval = (signal, override) => override ?? signalIntervalToMarketInterval(signal.interval);
7776
- var resolveBreadthUniverse = () => (process.env.BINANCE_MARKET_CONTEXT_BREADTH_UNIVERSE || "binance_top30_usdt").trim().toLowerCase();
7777
8136
  var getReferenceSymbols = () => {
7778
8137
  const symbols = (process.env.BINANCE_MARKET_CONTEXT_REFERENCE_SYMBOLS || "BTCUSDT,ETHUSDT").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
7779
8138
  return symbols.length ? [...new Set(symbols)] : ["BTCUSDT", "ETHUSDT"];
@@ -7846,13 +8205,30 @@ var getCachedBreadth = ({
7846
8205
  breadthCache.set(key, promise);
7847
8206
  return promise;
7848
8207
  };
8208
+ var toMarketBreadthContext = (breadth, interval) => ({
8209
+ source: "binance_klines",
8210
+ universe: breadth.universe,
8211
+ interval,
8212
+ asOfTs: breadth.ts.getTime(),
8213
+ ageMs: breadth.ageMs,
8214
+ stale: breadth.stale,
8215
+ symbolsCount: toFiniteNumberOrNull2(breadth.symbolsCount),
8216
+ advancers: toFiniteNumberOrNull2(breadth.advancers),
8217
+ decliners: toFiniteNumberOrNull2(breadth.decliners),
8218
+ unchanged: toFiniteNumberOrNull2(breadth.unchanged),
8219
+ advanceDeclineRatio: toFiniteNumberOrNull2(breadth.advanceDeclineRatio),
8220
+ pctAboveMa20: toFiniteNumberOrNull2(breadth.pctAboveMa20),
8221
+ pctAboveMa50: toFiniteNumberOrNull2(breadth.pctAboveMa50),
8222
+ equalWeightedReturn: toFiniteNumberOrNull2(breadth.equalWeightedReturn),
8223
+ volumeWeightedReturn: toFiniteNumberOrNull2(breadth.volumeWeightedReturn),
8224
+ dispersion: toFiniteNumberOrNull2(breadth.dispersion)
8225
+ });
7849
8226
  var enrichSignalWithBinanceMarketContext = async (params) => {
7850
8227
  const {
7851
8228
  signal,
7852
8229
  env,
7853
8230
  enabled = isBinanceMarketContextEnabled(env),
7854
8231
  interval = resolveMarketInterval(signal, params.interval),
7855
- breadthUniverse = resolveBreadthUniverse(),
7856
8232
  maxAgeMs = DEFAULT_MAX_AGE_BY_INTERVAL[interval]
7857
8233
  } = params;
7858
8234
  if (signal.universe === "tradfi" || !enabled || binanceMarketContextUnavailable || !hasBaseContext(signal)) {
@@ -7861,26 +8237,49 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
7861
8237
  try {
7862
8238
  const referenceSymbols = getReferenceSymbols();
7863
8239
  const primaryReferenceSymbol = resolvePrimaryReferenceSymbol2(signal.symbol);
7864
- const [referenceRows, breadth] = await Promise.all([
8240
+ const breadthUniverses = params.breadthUniverse ? [
8241
+ {
8242
+ key: "top30",
8243
+ universe: params.breadthUniverse
8244
+ }
8245
+ ] : getBinanceBreadthUniverses();
8246
+ const [referenceRows, breadthRows] = await Promise.all([
7865
8247
  getCachedReferenceRows({
7866
8248
  referenceSymbols,
7867
8249
  interval,
7868
8250
  timestamp: signal.timestamp,
7869
8251
  maxAgeMs
7870
8252
  }),
7871
- getCachedBreadth({
7872
- breadthUniverse,
7873
- interval,
7874
- timestamp: signal.timestamp,
7875
- maxAgeMs
7876
- })
8253
+ Promise.all(
8254
+ breadthUniverses.map(async ({ key, universe }) => ({
8255
+ key,
8256
+ breadth: await getCachedBreadth({
8257
+ breadthUniverse: universe,
8258
+ interval,
8259
+ timestamp: signal.timestamp,
8260
+ maxAgeMs
8261
+ })
8262
+ }))
8263
+ )
7877
8264
  ]);
8265
+ const availableBreadths = breadthRows.filter(
8266
+ (row) => row.breadth != null
8267
+ );
8268
+ const marketBreadths = Object.fromEntries(
8269
+ availableBreadths.map(({ key, breadth }) => [
8270
+ key,
8271
+ toMarketBreadthContext(breadth, interval)
8272
+ ])
8273
+ );
8274
+ const primaryBreadth = availableBreadths.find(
8275
+ ({ key }) => key === "top30"
8276
+ )?.breadth;
7878
8277
  const tradeFlowBySymbol = Object.fromEntries(
7879
8278
  referenceRows.filter((row) => row.tradeFlow).map((row) => [row.symbol, row.tradeFlow])
7880
8279
  );
7881
8280
  const targetReferenceSymbol = signal.symbol.trim().toUpperCase();
7882
8281
  const targetTradeFlow = tradeFlowBySymbol[targetReferenceSymbol];
7883
- if (!Object.keys(tradeFlowBySymbol).length && !breadth) {
8282
+ if (!Object.keys(tradeFlowBySymbol).length && !availableBreadths.length) {
7884
8283
  return false;
7885
8284
  }
7886
8285
  const baseContext = signal.additionalIndicators.baseContext;
@@ -7907,76 +8306,65 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
7907
8306
  tradeFlowBySymbol
7908
8307
  }
7909
8308
  } : {},
7910
- ...breadth ? {
7911
- marketBreadth: {
7912
- source: "binance_klines",
7913
- universe: breadth.universe,
7914
- interval,
7915
- asOfTs: breadth.ts.getTime(),
7916
- ageMs: breadth.ageMs,
7917
- stale: breadth.stale,
7918
- symbolsCount: toFiniteNumberOrNull2(breadth.symbolsCount),
7919
- advancers: toFiniteNumberOrNull2(breadth.advancers),
7920
- decliners: toFiniteNumberOrNull2(breadth.decliners),
7921
- unchanged: toFiniteNumberOrNull2(breadth.unchanged),
7922
- advanceDeclineRatio: toFiniteNumberOrNull2(
7923
- breadth.advanceDeclineRatio
7924
- ),
7925
- pctAboveMa20: toFiniteNumberOrNull2(breadth.pctAboveMa20),
7926
- pctAboveMa50: toFiniteNumberOrNull2(breadth.pctAboveMa50),
7927
- equalWeightedReturn: toFiniteNumberOrNull2(
7928
- breadth.equalWeightedReturn
7929
- ),
7930
- volumeWeightedReturn: toFiniteNumberOrNull2(
7931
- breadth.volumeWeightedReturn
7932
- ),
7933
- dispersion: toFiniteNumberOrNull2(breadth.dispersion)
7934
- },
7935
- btcAltRegime: {
7936
- source: "binance_klines",
7937
- universe: breadth.universe,
7938
- interval,
7939
- asOfTs: breadth.ts.getTime(),
7940
- ageMs: breadth.ageMs,
7941
- stale: breadth.stale,
7942
- btcReturn1h: toFiniteNumberOrNull2(breadth.btcReturn1h),
7943
- btcReturn4h: toFiniteNumberOrNull2(breadth.btcReturn4h),
7944
- btcReturn24h: toFiniteNumberOrNull2(breadth.btcReturn24h),
7945
- altBasketReturn1h: toFiniteNumberOrNull2(
7946
- breadth.altBasketReturn1h
8309
+ ...availableBreadths.length ? {
8310
+ marketBreadths,
8311
+ ...primaryBreadth ? {
8312
+ marketBreadth: toMarketBreadthContext(
8313
+ primaryBreadth,
8314
+ interval
7947
8315
  ),
7948
- altBasketReturn4h: toFiniteNumberOrNull2(
7949
- breadth.altBasketReturn4h
7950
- ),
7951
- altBasketReturn24h: toFiniteNumberOrNull2(
7952
- breadth.altBasketReturn24h
7953
- ),
7954
- btcVsAltReturn1h: toFiniteNumberOrNull2(
7955
- breadth.btcVsAltReturn1h
7956
- ),
7957
- btcVsAltReturn4h: toFiniteNumberOrNull2(
7958
- breadth.btcVsAltReturn4h
7959
- ),
7960
- btcVsAltReturn24h: toFiniteNumberOrNull2(
7961
- breadth.btcVsAltReturn24h
7962
- ),
7963
- btcTurnoverShare1h: toFiniteNumberOrNull2(
7964
- breadth.btcTurnoverShare1h
7965
- ),
7966
- btcTurnoverShare24h: toFiniteNumberOrNull2(
7967
- breadth.btcTurnoverShare24h
7968
- ),
7969
- btcTurnoverShareChange24h: toFiniteNumberOrNull2(
7970
- breadth.btcTurnoverShareChange24h
7971
- ),
7972
- altVolToBtcVol24h: toFiniteNumberOrNull2(
7973
- breadth.altVolToBtcVol24h
7974
- ),
7975
- altDispersion24h: toFiniteNumberOrNull2(
7976
- breadth.altDispersion24h
7977
- ),
7978
- regime: breadth.btcAltRegime ?? "unknown"
7979
- }
8316
+ btcAltRegime: {
8317
+ source: "binance_klines",
8318
+ universe: primaryBreadth.universe,
8319
+ interval,
8320
+ asOfTs: primaryBreadth.ts.getTime(),
8321
+ ageMs: primaryBreadth.ageMs,
8322
+ stale: primaryBreadth.stale,
8323
+ btcReturn1h: toFiniteNumberOrNull2(
8324
+ primaryBreadth.btcReturn1h
8325
+ ),
8326
+ btcReturn4h: toFiniteNumberOrNull2(
8327
+ primaryBreadth.btcReturn4h
8328
+ ),
8329
+ btcReturn24h: toFiniteNumberOrNull2(
8330
+ primaryBreadth.btcReturn24h
8331
+ ),
8332
+ altBasketReturn1h: toFiniteNumberOrNull2(
8333
+ primaryBreadth.altBasketReturn1h
8334
+ ),
8335
+ altBasketReturn4h: toFiniteNumberOrNull2(
8336
+ primaryBreadth.altBasketReturn4h
8337
+ ),
8338
+ altBasketReturn24h: toFiniteNumberOrNull2(
8339
+ primaryBreadth.altBasketReturn24h
8340
+ ),
8341
+ btcVsAltReturn1h: toFiniteNumberOrNull2(
8342
+ primaryBreadth.btcVsAltReturn1h
8343
+ ),
8344
+ btcVsAltReturn4h: toFiniteNumberOrNull2(
8345
+ primaryBreadth.btcVsAltReturn4h
8346
+ ),
8347
+ btcVsAltReturn24h: toFiniteNumberOrNull2(
8348
+ primaryBreadth.btcVsAltReturn24h
8349
+ ),
8350
+ btcTurnoverShare1h: toFiniteNumberOrNull2(
8351
+ primaryBreadth.btcTurnoverShare1h
8352
+ ),
8353
+ btcTurnoverShare24h: toFiniteNumberOrNull2(
8354
+ primaryBreadth.btcTurnoverShare24h
8355
+ ),
8356
+ btcTurnoverShareChange24h: toFiniteNumberOrNull2(
8357
+ primaryBreadth.btcTurnoverShareChange24h
8358
+ ),
8359
+ altVolToBtcVol24h: toFiniteNumberOrNull2(
8360
+ primaryBreadth.altVolToBtcVol24h
8361
+ ),
8362
+ altDispersion24h: toFiniteNumberOrNull2(
8363
+ primaryBreadth.altDispersion24h
8364
+ ),
8365
+ regime: primaryBreadth.btcAltRegime ?? "unknown"
8366
+ }
8367
+ } : {}
7980
8368
  } : {}
7981
8369
  }
7982
8370
  }
@@ -8505,18 +8893,18 @@ var findReplayAiAnalysis = ({
8505
8893
  return void 0;
8506
8894
  }
8507
8895
  let best = null;
8508
- for (const snapshot of snapshots) {
8509
- if (snapshot.symbol !== signal.symbol || snapshot.direction !== direction || snapshot.strategy && snapshot.strategy !== signal.strategy) {
8896
+ for (const snapshot2 of snapshots) {
8897
+ if (snapshot2.symbol !== signal.symbol || snapshot2.direction !== direction || snapshot2.strategy && snapshot2.strategy !== signal.strategy) {
8510
8898
  continue;
8511
8899
  }
8512
- const toleranceMs = Math.max(0, Number(snapshot.toleranceMs ?? 0));
8513
- const diff = Math.abs(snapshot.timestamp - signal.timestamp);
8900
+ const toleranceMs = Math.max(0, Number(snapshot2.toleranceMs ?? 0));
8901
+ const diff = Math.abs(snapshot2.timestamp - signal.timestamp);
8514
8902
  if (diff > toleranceMs || best && diff >= best.diff) {
8515
8903
  continue;
8516
8904
  }
8517
8905
  best = {
8518
8906
  diff,
8519
- analysis: snapshot.analysis
8907
+ analysis: snapshot2.analysis
8520
8908
  };
8521
8909
  }
8522
8910
  return best?.analysis;
@@ -8604,7 +8992,7 @@ var enrichSignalWithMlAi = async ({
8604
8992
  return enrichSignalWithAi({ signal, userName, symbol, direction, env, ai });
8605
8993
  };
8606
8994
  var toFiniteNumberOrNull4 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
8607
- var getArrivalSnapshot = async ({
8995
+ var getOrderArrivalSnapshot = async ({
8608
8996
  connector,
8609
8997
  symbol
8610
8998
  }) => {
@@ -8649,6 +9037,29 @@ var getArrivalSnapshot = async ({
8649
9037
  };
8650
9038
  }
8651
9039
  };
9040
+ var validateEntryProtectionAtArrival = ({
9041
+ direction,
9042
+ signalPrice,
9043
+ bid,
9044
+ ask,
9045
+ arrivalMid,
9046
+ takeProfits,
9047
+ stopLossPrice
9048
+ }) => {
9049
+ const entryReference = direction === "LONG" ? ask ?? arrivalMid ?? signalPrice : bid ?? arrivalMid ?? signalPrice;
9050
+ const stopReference = direction === "LONG" ? bid ?? arrivalMid ?? signalPrice : ask ?? arrivalMid ?? signalPrice;
9051
+ if (!Number.isFinite(entryReference) || entryReference <= 0 || !Number.isFinite(stopReference) || stopReference <= 0) {
9052
+ throw new Error("INVALID_ENTRY_REFERENCE_AT_ARRIVAL");
9053
+ }
9054
+ for (const takeProfit of takeProfits) {
9055
+ if (!Number.isFinite(takeProfit.price) || takeProfit.price <= 0 || (direction === "LONG" ? takeProfit.price <= entryReference : takeProfit.price >= entryReference)) {
9056
+ throw new Error("TAKE_PROFIT_CROSSED_BEFORE_ENTRY");
9057
+ }
9058
+ }
9059
+ if (stopLossPrice != null && (!Number.isFinite(stopLossPrice) || stopLossPrice <= 0 || (direction === "LONG" ? stopLossPrice >= stopReference : stopLossPrice <= stopReference))) {
9060
+ throw new Error("STOP_LOSS_CROSSED_BEFORE_ENTRY");
9061
+ }
9062
+ };
8652
9063
  var resolveRuntimeTelemetryQuality = ({
8653
9064
  signalClosePrice,
8654
9065
  arrivalMid,
@@ -8723,7 +9134,22 @@ var executeEntryOrder = async ({
8723
9134
  signal.orderQty = qty;
8724
9135
  signal.orderValue = qty * currentPrice;
8725
9136
  signal.orderFailureReason = void 0;
8726
- const arrivalSnapshot = await getArrivalSnapshot({ connector, symbol });
9137
+ const arrivalSnapshot = await getOrderArrivalSnapshot({ connector, symbol });
9138
+ try {
9139
+ validateEntryProtectionAtArrival({
9140
+ direction,
9141
+ signalPrice: currentPrice,
9142
+ bid: arrivalSnapshot.bid,
9143
+ ask: arrivalSnapshot.ask,
9144
+ arrivalMid: arrivalSnapshot.arrivalMid,
9145
+ takeProfits,
9146
+ stopLossPrice
9147
+ });
9148
+ } catch (error) {
9149
+ signal.orderStatus = "failed";
9150
+ signal.orderFailureReason = error.message;
9151
+ throw error;
9152
+ }
8727
9153
  const orderSubmitTime = Date.now();
8728
9154
  const orderPlaced = await connector.placeOrder({
8729
9155
  symbol,
@@ -8938,6 +9364,7 @@ var resolveStrategyConfig = async ({
8938
9364
  };
8939
9365
 
8940
9366
  // src/strategyRuntime.ts
9367
+ var import_types = require("@tradejs/types");
8941
9368
  var buildExitOrderSignal = ({
8942
9369
  strategyName,
8943
9370
  symbol,
@@ -9646,6 +10073,19 @@ var executeEntryDecision = async ({
9646
10073
  return signal;
9647
10074
  }
9648
10075
  await beforePlaceOrder();
10076
+ const arrivalSnapshot = await getOrderArrivalSnapshot({
10077
+ connector,
10078
+ symbol
10079
+ });
10080
+ validateEntryProtectionAtArrival({
10081
+ direction: decision.entryContext.direction,
10082
+ signalPrice: decision.entryContext.prices.currentPrice,
10083
+ bid: arrivalSnapshot.bid,
10084
+ ask: arrivalSnapshot.ask,
10085
+ arrivalMid: arrivalSnapshot.arrivalMid,
10086
+ takeProfits: decision.orderPlan.takeProfits,
10087
+ stopLossPrice: decision.orderPlan.stopLossPrice
10088
+ });
9649
10089
  const orderPlaced = await connector.placeOrder({
9650
10090
  symbol,
9651
10091
  qty: decision.orderPlan.qty,
@@ -9707,7 +10147,11 @@ var executeEntryDecision = async ({
9707
10147
  entry,
9708
10148
  market
9709
10149
  });
9710
- import_logger8.logger.error("order error: %s %s", symbol, err);
10150
+ if (err?.message === import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
10151
+ import_logger8.logger.warn("order warning: %s %s", symbol, err);
10152
+ } else {
10153
+ import_logger8.logger.error("order error: %s %s", symbol, err);
10154
+ }
9711
10155
  return signal ?? "ORDER_ERROR";
9712
10156
  }
9713
10157
  return signal ?? decision.code;
@@ -10972,6 +11416,7 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
10972
11416
  };
10973
11417
  // Annotate the CommonJS export names for ESM import in node:
10974
11418
  0 && (module.exports = {
11419
+ BINANCE_BREADTH_UNIVERSE_KEYS,
10975
11420
  DEFAULT_AI_MODEL,
10976
11421
  MAX_AI_SERIES_POINTS,
10977
11422
  askAI,
@@ -10979,6 +11424,7 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
10979
11424
  buildAiPayload,
10980
11425
  buildAiPrompts,
10981
11426
  buildAiSystemPrompt,
11427
+ buildBinanceBreadthUniverseSnapshot,
10982
11428
  buildCompactAiIndicatorsSnapshot,
10983
11429
  closeOppositePositionsBeforeOpen,
10984
11430
  createCloseAllOnGlobalProfitBeforeSignalsHook,
@@ -10996,8 +11442,12 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
10996
11442
  ensureStrategyPluginsLoaded,
10997
11443
  executeEntryOrder,
10998
11444
  getAvailableStrategyNames,
11445
+ getBinanceBreadthUniverseSnapshot,
11446
+ getBinanceBreadthUniverses,
10999
11447
  getDeterministicAiGateContext,
11000
11448
  getOpenRouterModelKwargs,
11449
+ getOrderArrivalSnapshot,
11450
+ getPrimaryBinanceBreadthUniverse,
11001
11451
  getRegisteredManifests,
11002
11452
  getRegisteredStrategies,
11003
11453
  getStrategyCreator,
@@ -11011,6 +11461,7 @@ var createCloseAllOnGlobalProfitBeforeSignalsHook = ({
11011
11461
  runAiPromptLocal,
11012
11462
  strategies,
11013
11463
  trimSeriesDeep,
11464
+ validateEntryProtectionAtArrival,
11014
11465
  ...require("@tradejs/core/strategies")
11015
11466
  });
11016
11467
  /*! Bundled license information: