@tradejs/node 2.0.6 → 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.
package/dist/backtest.mjs CHANGED
@@ -7,10 +7,10 @@ import {
7
7
  enrichSignalWithBinanceMarketContext,
8
8
  enrichSignalWithCoinMarketCapContext,
9
9
  enrichSignalWithDerivativesContext
10
- } from "./chunk-2DZCHRP6.mjs";
10
+ } from "./chunk-76TEMQKX.mjs";
11
11
  import {
12
12
  buildAiPayload
13
- } from "./chunk-IUZML4RK.mjs";
13
+ } from "./chunk-PD25CABK.mjs";
14
14
  import {
15
15
  getStrategyCreator
16
16
  } from "./chunk-QVSMINLG.mjs";
@@ -23,6 +23,9 @@ import "./chunk-UV2HVMKZ.mjs";
23
23
  export * from "@tradejs/core/backtest";
24
24
 
25
25
  // src/testing.ts
26
+ import {
27
+ BACKTEST_WARNING_CODES
28
+ } from "@tradejs/types";
26
29
  import { alignSortedCandlesByTimestamp } from "@tradejs/core/indicators";
27
30
  import {
28
31
  BACKTEST_EXECUTION_INTERVAL,
@@ -985,6 +988,16 @@ var resolveExecutionCosts = async (params) => {
985
988
  var isBacktestEntryDelayControlCode = (value) => typeof value === "string" && value.startsWith("BACKTEST_ENTRY_DELAY_");
986
989
  var CLOSED_RESULT_FLUSH_INTERVAL = 500;
987
990
  var DEFAULT_STRATEGY_CANDLE_TIMEOUT_MS = 6e4;
991
+ var createBacktestWarningCounts = () => ({
992
+ [BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY]: 0
993
+ });
994
+ var recordBacktestSignalWarning = (warningCounts, signal) => {
995
+ if (!signal || typeof signal === "string" || signal.orderStatus !== "failed" || signal.orderFailureReason !== BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
996
+ return;
997
+ }
998
+ const code = BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY;
999
+ warningCounts[code] = (warningCounts[code] ?? 0) + 1;
1000
+ };
988
1001
  var resolvePositiveInt = (value, fallback) => {
989
1002
  const parsed = parseInt(String(value ?? ""), 10);
990
1003
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
@@ -1818,6 +1831,7 @@ var testing = async ({
1818
1831
  );
1819
1832
  const pendingMlPayloadBySignalId = /* @__PURE__ */ new Map();
1820
1833
  const pendingAiRowBySignalId = /* @__PURE__ */ new Map();
1834
+ const warningCounts = createBacktestWarningCounts();
1821
1835
  const replaySignalEvaluations = collectReplaySignalEvaluations ? [] : null;
1822
1836
  const flushClosedResultsBatch = async () => {
1823
1837
  if (!ml && !ai) return;
@@ -1862,6 +1876,7 @@ var testing = async ({
1862
1876
  }
1863
1877
  };
1864
1878
  const processSignal = async (signal, candle) => {
1879
+ recordBacktestSignalWarning(warningCounts, signal);
1865
1880
  if (isBacktestEntryDelayControlCode(signal)) {
1866
1881
  return;
1867
1882
  }
@@ -1970,10 +1985,14 @@ var testing = async ({
1970
1985
  }
1971
1986
  await withTimeout("flush closed results", flushClosedResultsBatch());
1972
1987
  const result = await withTimeout("collect result", testConnector.getResult());
1973
- return replaySignalEvaluations ? {
1988
+ const resultWithWarnings = {
1974
1989
  ...result,
1990
+ warningCounts
1991
+ };
1992
+ return replaySignalEvaluations ? {
1993
+ ...resultWithWarnings,
1975
1994
  inlineReplaySignalEvaluations: replaySignalEvaluations
1976
- } : result;
1995
+ } : resultWithWarnings;
1977
1996
  };
1978
1997
  var testingGroupInSharedCandleLoop = async (tests) => {
1979
1998
  if (!canRunTestsInSharedCandleLoop(tests)) {
@@ -2227,6 +2246,7 @@ var testingGroupInSharedCandleLoop = async (tests) => {
2227
2246
  testConnector,
2228
2247
  pendingMlPayloadBySignalId: /* @__PURE__ */ new Map(),
2229
2248
  pendingAiRowBySignalId: /* @__PURE__ */ new Map(),
2249
+ warningCounts: createBacktestWarningCounts(),
2230
2250
  replaySignalEvaluations: collectReplaySignalEvaluations ? [] : null
2231
2251
  });
2232
2252
  }
@@ -2279,6 +2299,7 @@ var testingGroupInSharedCandleLoop = async (tests) => {
2279
2299
  }
2280
2300
  };
2281
2301
  const processRunnerSignal = async (runner, signal, candle) => {
2302
+ recordBacktestSignalWarning(runner.warningCounts, signal);
2282
2303
  if (isBacktestEntryDelayControlCode(signal)) {
2283
2304
  return;
2284
2305
  }
@@ -2420,8 +2441,12 @@ var testingGroupInSharedCandleLoop = async (tests) => {
2420
2441
  test: runner.test,
2421
2442
  result: runner.replaySignalEvaluations ? {
2422
2443
  ...result,
2444
+ warningCounts: runner.warningCounts,
2423
2445
  inlineReplaySignalEvaluations: runner.replaySignalEvaluations
2424
- } : result
2446
+ } : {
2447
+ ...result,
2448
+ warningCounts: runner.warningCounts
2449
+ }
2425
2450
  });
2426
2451
  }
2427
2452
  return results;
@@ -1,10 +1,315 @@
1
1
  import {
2
2
  getStrategyProfileMlAdapter
3
- } from "./chunk-IUZML4RK.mjs";
3
+ } from "./chunk-PD25CABK.mjs";
4
4
  import {
5
5
  getStrategyManifest
6
6
  } from "./chunk-QVSMINLG.mjs";
7
7
 
8
+ // src/binanceBreadthUniverses.ts
9
+ import { createHash } from "crypto";
10
+
11
+ // src/config/binanceBreadthUniverses.json
12
+ var binanceBreadthUniverses_default = {
13
+ schemaVersion: 1,
14
+ updatedAt: "2026-07-24T15:28:27.234Z",
15
+ source: "binance_spot_usdt_turnover24h",
16
+ fingerprint: "43df006e5510bb99",
17
+ universes: {
18
+ top5: {
19
+ size: 5,
20
+ fingerprint: "ba5b73456b18",
21
+ symbols: ["ETHUSDT", "USD1USDT", "SOLUSDT", "OPNUSDT", "VANAUSDT"]
22
+ },
23
+ top10: {
24
+ size: 10,
25
+ fingerprint: "370eb5af9778",
26
+ symbols: [
27
+ "ETHUSDT",
28
+ "USD1USDT",
29
+ "SOLUSDT",
30
+ "OPNUSDT",
31
+ "VANAUSDT",
32
+ "DEXEUSDT",
33
+ "BANKUSDT",
34
+ "ZECUSDT",
35
+ "XRPUSDT",
36
+ "RLUSDUSDT"
37
+ ]
38
+ },
39
+ top30: {
40
+ size: 30,
41
+ fingerprint: "685a7e7f5fce",
42
+ symbols: [
43
+ "ETHUSDT",
44
+ "USD1USDT",
45
+ "SOLUSDT",
46
+ "OPNUSDT",
47
+ "VANAUSDT",
48
+ "DEXEUSDT",
49
+ "BANKUSDT",
50
+ "ZECUSDT",
51
+ "XRPUSDT",
52
+ "RLUSDUSDT",
53
+ "BNBUSDT",
54
+ "DOGEUSDT",
55
+ "REUSDT",
56
+ "RIFUSDT",
57
+ "ZAMAUSDT",
58
+ "TRXUSDT",
59
+ "EURUSDT",
60
+ "AEROUSDT",
61
+ "UUSDT",
62
+ "SUIUSDT",
63
+ "XAUTUSDT",
64
+ "NEARUSDT",
65
+ "LTCUSDT",
66
+ "SPCXBUSDT",
67
+ "LAUSDT",
68
+ "AVAXUSDT",
69
+ "WLDUSDT",
70
+ "ONDOUSDT",
71
+ "XLMUSDT",
72
+ "UNIUSDT"
73
+ ]
74
+ },
75
+ top50: {
76
+ size: 50,
77
+ fingerprint: "cb9725e24141",
78
+ symbols: [
79
+ "ETHUSDT",
80
+ "USD1USDT",
81
+ "SOLUSDT",
82
+ "OPNUSDT",
83
+ "VANAUSDT",
84
+ "DEXEUSDT",
85
+ "BANKUSDT",
86
+ "ZECUSDT",
87
+ "XRPUSDT",
88
+ "RLUSDUSDT",
89
+ "BNBUSDT",
90
+ "DOGEUSDT",
91
+ "REUSDT",
92
+ "RIFUSDT",
93
+ "ZAMAUSDT",
94
+ "TRXUSDT",
95
+ "EURUSDT",
96
+ "AEROUSDT",
97
+ "UUSDT",
98
+ "SUIUSDT",
99
+ "XAUTUSDT",
100
+ "NEARUSDT",
101
+ "LTCUSDT",
102
+ "SPCXBUSDT",
103
+ "LAUSDT",
104
+ "AVAXUSDT",
105
+ "WLDUSDT",
106
+ "ONDOUSDT",
107
+ "XLMUSDT",
108
+ "UNIUSDT",
109
+ "ADAUSDT",
110
+ "SNDKBUSDT",
111
+ "KAITOUSDT",
112
+ "UTKUSDT",
113
+ "PEPEUSDT",
114
+ "WLFIUSDT",
115
+ "PAXGUSDT",
116
+ "ENAUSDT",
117
+ "TAOUSDT",
118
+ "AAVEUSDT",
119
+ "CRCLBUSDT",
120
+ "LINKUSDT",
121
+ "TONUSDT",
122
+ "ALLOUSDT",
123
+ "PUMPUSDT",
124
+ "INJUSDT",
125
+ "HOMEUSDT",
126
+ "MUBUSDT",
127
+ "TSLABUSDT",
128
+ "GRAMUSDT"
129
+ ]
130
+ },
131
+ top100: {
132
+ size: 100,
133
+ fingerprint: "f397883a81cf",
134
+ symbols: [
135
+ "ETHUSDT",
136
+ "USD1USDT",
137
+ "SOLUSDT",
138
+ "OPNUSDT",
139
+ "VANAUSDT",
140
+ "DEXEUSDT",
141
+ "BANKUSDT",
142
+ "ZECUSDT",
143
+ "XRPUSDT",
144
+ "RLUSDUSDT",
145
+ "BNBUSDT",
146
+ "DOGEUSDT",
147
+ "REUSDT",
148
+ "RIFUSDT",
149
+ "ZAMAUSDT",
150
+ "TRXUSDT",
151
+ "EURUSDT",
152
+ "AEROUSDT",
153
+ "UUSDT",
154
+ "SUIUSDT",
155
+ "XAUTUSDT",
156
+ "NEARUSDT",
157
+ "LTCUSDT",
158
+ "SPCXBUSDT",
159
+ "LAUSDT",
160
+ "AVAXUSDT",
161
+ "WLDUSDT",
162
+ "ONDOUSDT",
163
+ "XLMUSDT",
164
+ "UNIUSDT",
165
+ "ADAUSDT",
166
+ "SNDKBUSDT",
167
+ "KAITOUSDT",
168
+ "UTKUSDT",
169
+ "PEPEUSDT",
170
+ "WLFIUSDT",
171
+ "PAXGUSDT",
172
+ "ENAUSDT",
173
+ "TAOUSDT",
174
+ "AAVEUSDT",
175
+ "CRCLBUSDT",
176
+ "LINKUSDT",
177
+ "TONUSDT",
178
+ "ALLOUSDT",
179
+ "PUMPUSDT",
180
+ "INJUSDT",
181
+ "HOMEUSDT",
182
+ "MUBUSDT",
183
+ "TSLABUSDT",
184
+ "GRAMUSDT",
185
+ "\u5E01\u5B89\u4EBA\u751FUSDT",
186
+ "SYNUSDT",
187
+ "TRUMPUSDT",
188
+ "PROMUSDT",
189
+ "XPLUSDT",
190
+ "HBARUSDT",
191
+ "ERAUSDT",
192
+ "BCHUSDT",
193
+ "SOXLBUSDT",
194
+ "DOTUSDT",
195
+ "EPICUSDT",
196
+ "KITEUSDT",
197
+ "NFPUSDT",
198
+ "KGSTUSDT",
199
+ "FILUSDT",
200
+ "FETUSDT",
201
+ "KORUBUSDT",
202
+ "ARBUSDT",
203
+ "STXUSDT",
204
+ "HEIUSDT",
205
+ "BARDUSDT",
206
+ "DASHUSDT",
207
+ "ASTERUSDT",
208
+ "VANRYUSDT",
209
+ "INTCBUSDT",
210
+ "ETCUSDT",
211
+ "EIGENUSDT",
212
+ "PENGUUSDT",
213
+ "LRCUSDT",
214
+ "VIRTUALUSDT",
215
+ "POLUSDT",
216
+ "ICPUSDT",
217
+ "ZBTUSDT",
218
+ "SKHYBUSDT",
219
+ "SHIBUSDT",
220
+ "LDOUSDT",
221
+ "APTUSDT",
222
+ "JTOUSDT",
223
+ "NOMUSDT",
224
+ "ZROUSDT",
225
+ "OPUSDT",
226
+ "BONKUSDT",
227
+ "SKLUSDT",
228
+ "HEMIUSDT",
229
+ "TNSRUSDT",
230
+ "INTWBUSDT",
231
+ "BIOUSDT",
232
+ "TIAUSDT",
233
+ "ORDIUSDT",
234
+ "XUSDUSDT"
235
+ ]
236
+ }
237
+ }
238
+ };
239
+
240
+ // src/binanceBreadthUniverses.ts
241
+ var BINANCE_BREADTH_UNIVERSE_KEYS = [
242
+ "top5",
243
+ "top10",
244
+ "top30",
245
+ "top50",
246
+ "top100"
247
+ ];
248
+ var fingerprint = (value, length) => createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
249
+ var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
250
+ var buildBinanceBreadthUniverseSnapshot = ({
251
+ rankedSymbols,
252
+ updatedAt = (/* @__PURE__ */ new Date()).toISOString()
253
+ }) => {
254
+ const uniqueSymbols = [...new Set(normalizeSymbols(rankedSymbols))];
255
+ if (uniqueSymbols.length < 100) {
256
+ throw new Error(
257
+ `Binance breadth snapshot requires at least 100 symbols, got ${uniqueSymbols.length}`
258
+ );
259
+ }
260
+ const universes = Object.fromEntries(
261
+ BINANCE_BREADTH_UNIVERSE_KEYS.map((key) => {
262
+ const size = Number(key.slice(3));
263
+ const symbols = uniqueSymbols.slice(0, size);
264
+ return [
265
+ key,
266
+ {
267
+ size,
268
+ fingerprint: fingerprint(symbols, 12),
269
+ symbols
270
+ }
271
+ ];
272
+ })
273
+ );
274
+ return {
275
+ schemaVersion: 1,
276
+ updatedAt,
277
+ source: "binance_spot_usdt_turnover24h",
278
+ fingerprint: fingerprint(universes, 16),
279
+ universes
280
+ };
281
+ };
282
+ var validateSnapshot = (value) => {
283
+ const rebuilt = buildBinanceBreadthUniverseSnapshot({
284
+ rankedSymbols: value.universes.top100.symbols,
285
+ updatedAt: value.updatedAt
286
+ });
287
+ if (value.schemaVersion !== 1 || value.source !== rebuilt.source || value.fingerprint !== rebuilt.fingerprint) {
288
+ throw new Error("Invalid Binance breadth universe snapshot fingerprint");
289
+ }
290
+ for (const key of BINANCE_BREADTH_UNIVERSE_KEYS) {
291
+ const actual = value.universes[key];
292
+ const expected = rebuilt.universes[key];
293
+ if (actual.size !== expected.size || actual.fingerprint !== expected.fingerprint || JSON.stringify(actual.symbols) !== JSON.stringify(expected.symbols)) {
294
+ throw new Error(`Invalid Binance breadth universe snapshot: ${key}`);
295
+ }
296
+ }
297
+ return value;
298
+ };
299
+ var snapshot = validateSnapshot(binanceBreadthUniverses_default);
300
+ var getBinanceBreadthUniverseSnapshot = () => snapshot;
301
+ var getBinanceBreadthUniverses = () => BINANCE_BREADTH_UNIVERSE_KEYS.map((key) => {
302
+ const definition = snapshot.universes[key];
303
+ return {
304
+ key,
305
+ size: definition.size,
306
+ fingerprint: definition.fingerprint,
307
+ universe: `binance_${key}_usdt_${definition.fingerprint}`,
308
+ symbols: [...definition.symbols]
309
+ };
310
+ });
311
+ var getPrimaryBinanceBreadthUniverse = () => getBinanceBreadthUniverses().find(({ key }) => key === "top30");
312
+
8
313
  // src/strategyHelpers/binanceMarketContext.ts
9
314
  import {
10
315
  getLatestMarketBreadth,
@@ -43,7 +348,6 @@ var signalIntervalToMarketInterval = (value) => {
43
348
  return "15m";
44
349
  };
45
350
  var resolveMarketInterval = (signal, override) => override ?? signalIntervalToMarketInterval(signal.interval);
46
- var resolveBreadthUniverse = () => (process.env.BINANCE_MARKET_CONTEXT_BREADTH_UNIVERSE || "binance_top30_usdt").trim().toLowerCase();
47
351
  var getReferenceSymbols = () => {
48
352
  const symbols = (process.env.BINANCE_MARKET_CONTEXT_REFERENCE_SYMBOLS || "BTCUSDT,ETHUSDT").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
49
353
  return symbols.length ? [...new Set(symbols)] : ["BTCUSDT", "ETHUSDT"];
@@ -116,13 +420,30 @@ var getCachedBreadth = ({
116
420
  breadthCache.set(key, promise);
117
421
  return promise;
118
422
  };
423
+ var toMarketBreadthContext = (breadth, interval) => ({
424
+ source: "binance_klines",
425
+ universe: breadth.universe,
426
+ interval,
427
+ asOfTs: breadth.ts.getTime(),
428
+ ageMs: breadth.ageMs,
429
+ stale: breadth.stale,
430
+ symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
431
+ advancers: toFiniteNumberOrNull(breadth.advancers),
432
+ decliners: toFiniteNumberOrNull(breadth.decliners),
433
+ unchanged: toFiniteNumberOrNull(breadth.unchanged),
434
+ advanceDeclineRatio: toFiniteNumberOrNull(breadth.advanceDeclineRatio),
435
+ pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
436
+ pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
437
+ equalWeightedReturn: toFiniteNumberOrNull(breadth.equalWeightedReturn),
438
+ volumeWeightedReturn: toFiniteNumberOrNull(breadth.volumeWeightedReturn),
439
+ dispersion: toFiniteNumberOrNull(breadth.dispersion)
440
+ });
119
441
  var enrichSignalWithBinanceMarketContext = async (params) => {
120
442
  const {
121
443
  signal,
122
444
  env,
123
445
  enabled = isBinanceMarketContextEnabled(env),
124
446
  interval = resolveMarketInterval(signal, params.interval),
125
- breadthUniverse = resolveBreadthUniverse(),
126
447
  maxAgeMs = DEFAULT_MAX_AGE_BY_INTERVAL[interval]
127
448
  } = params;
128
449
  if (signal.universe === "tradfi" || !enabled || binanceMarketContextUnavailable || !hasBaseContext(signal)) {
@@ -131,26 +452,49 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
131
452
  try {
132
453
  const referenceSymbols = getReferenceSymbols();
133
454
  const primaryReferenceSymbol = resolvePrimaryReferenceSymbol(signal.symbol);
134
- const [referenceRows, breadth] = await Promise.all([
455
+ const breadthUniverses = params.breadthUniverse ? [
456
+ {
457
+ key: "top30",
458
+ universe: params.breadthUniverse
459
+ }
460
+ ] : getBinanceBreadthUniverses();
461
+ const [referenceRows, breadthRows] = await Promise.all([
135
462
  getCachedReferenceRows({
136
463
  referenceSymbols,
137
464
  interval,
138
465
  timestamp: signal.timestamp,
139
466
  maxAgeMs
140
467
  }),
141
- getCachedBreadth({
142
- breadthUniverse,
143
- interval,
144
- timestamp: signal.timestamp,
145
- maxAgeMs
146
- })
468
+ Promise.all(
469
+ breadthUniverses.map(async ({ key, universe }) => ({
470
+ key,
471
+ breadth: await getCachedBreadth({
472
+ breadthUniverse: universe,
473
+ interval,
474
+ timestamp: signal.timestamp,
475
+ maxAgeMs
476
+ })
477
+ }))
478
+ )
147
479
  ]);
480
+ const availableBreadths = breadthRows.filter(
481
+ (row) => row.breadth != null
482
+ );
483
+ const marketBreadths = Object.fromEntries(
484
+ availableBreadths.map(({ key, breadth }) => [
485
+ key,
486
+ toMarketBreadthContext(breadth, interval)
487
+ ])
488
+ );
489
+ const primaryBreadth = availableBreadths.find(
490
+ ({ key }) => key === "top30"
491
+ )?.breadth;
148
492
  const tradeFlowBySymbol = Object.fromEntries(
149
493
  referenceRows.filter((row) => row.tradeFlow).map((row) => [row.symbol, row.tradeFlow])
150
494
  );
151
495
  const targetReferenceSymbol = signal.symbol.trim().toUpperCase();
152
496
  const targetTradeFlow = tradeFlowBySymbol[targetReferenceSymbol];
153
- if (!Object.keys(tradeFlowBySymbol).length && !breadth) {
497
+ if (!Object.keys(tradeFlowBySymbol).length && !availableBreadths.length) {
154
498
  return false;
155
499
  }
156
500
  const baseContext = signal.additionalIndicators.baseContext;
@@ -177,76 +521,65 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
177
521
  tradeFlowBySymbol
178
522
  }
179
523
  } : {},
180
- ...breadth ? {
181
- marketBreadth: {
182
- source: "binance_klines",
183
- universe: breadth.universe,
184
- interval,
185
- asOfTs: breadth.ts.getTime(),
186
- ageMs: breadth.ageMs,
187
- stale: breadth.stale,
188
- symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
189
- advancers: toFiniteNumberOrNull(breadth.advancers),
190
- decliners: toFiniteNumberOrNull(breadth.decliners),
191
- unchanged: toFiniteNumberOrNull(breadth.unchanged),
192
- advanceDeclineRatio: toFiniteNumberOrNull(
193
- breadth.advanceDeclineRatio
194
- ),
195
- pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
196
- pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
197
- equalWeightedReturn: toFiniteNumberOrNull(
198
- breadth.equalWeightedReturn
199
- ),
200
- volumeWeightedReturn: toFiniteNumberOrNull(
201
- breadth.volumeWeightedReturn
524
+ ...availableBreadths.length ? {
525
+ marketBreadths,
526
+ ...primaryBreadth ? {
527
+ marketBreadth: toMarketBreadthContext(
528
+ primaryBreadth,
529
+ interval
202
530
  ),
203
- dispersion: toFiniteNumberOrNull(breadth.dispersion)
204
- },
205
- btcAltRegime: {
206
- source: "binance_klines",
207
- universe: breadth.universe,
208
- interval,
209
- asOfTs: breadth.ts.getTime(),
210
- ageMs: breadth.ageMs,
211
- stale: breadth.stale,
212
- btcReturn1h: toFiniteNumberOrNull(breadth.btcReturn1h),
213
- btcReturn4h: toFiniteNumberOrNull(breadth.btcReturn4h),
214
- btcReturn24h: toFiniteNumberOrNull(breadth.btcReturn24h),
215
- altBasketReturn1h: toFiniteNumberOrNull(
216
- breadth.altBasketReturn1h
217
- ),
218
- altBasketReturn4h: toFiniteNumberOrNull(
219
- breadth.altBasketReturn4h
220
- ),
221
- altBasketReturn24h: toFiniteNumberOrNull(
222
- breadth.altBasketReturn24h
223
- ),
224
- btcVsAltReturn1h: toFiniteNumberOrNull(
225
- breadth.btcVsAltReturn1h
226
- ),
227
- btcVsAltReturn4h: toFiniteNumberOrNull(
228
- breadth.btcVsAltReturn4h
229
- ),
230
- btcVsAltReturn24h: toFiniteNumberOrNull(
231
- breadth.btcVsAltReturn24h
232
- ),
233
- btcTurnoverShare1h: toFiniteNumberOrNull(
234
- breadth.btcTurnoverShare1h
235
- ),
236
- btcTurnoverShare24h: toFiniteNumberOrNull(
237
- breadth.btcTurnoverShare24h
238
- ),
239
- btcTurnoverShareChange24h: toFiniteNumberOrNull(
240
- breadth.btcTurnoverShareChange24h
241
- ),
242
- altVolToBtcVol24h: toFiniteNumberOrNull(
243
- breadth.altVolToBtcVol24h
244
- ),
245
- altDispersion24h: toFiniteNumberOrNull(
246
- breadth.altDispersion24h
247
- ),
248
- regime: breadth.btcAltRegime ?? "unknown"
249
- }
531
+ btcAltRegime: {
532
+ source: "binance_klines",
533
+ universe: primaryBreadth.universe,
534
+ interval,
535
+ asOfTs: primaryBreadth.ts.getTime(),
536
+ ageMs: primaryBreadth.ageMs,
537
+ stale: primaryBreadth.stale,
538
+ btcReturn1h: toFiniteNumberOrNull(
539
+ primaryBreadth.btcReturn1h
540
+ ),
541
+ btcReturn4h: toFiniteNumberOrNull(
542
+ primaryBreadth.btcReturn4h
543
+ ),
544
+ btcReturn24h: toFiniteNumberOrNull(
545
+ primaryBreadth.btcReturn24h
546
+ ),
547
+ altBasketReturn1h: toFiniteNumberOrNull(
548
+ primaryBreadth.altBasketReturn1h
549
+ ),
550
+ altBasketReturn4h: toFiniteNumberOrNull(
551
+ primaryBreadth.altBasketReturn4h
552
+ ),
553
+ altBasketReturn24h: toFiniteNumberOrNull(
554
+ primaryBreadth.altBasketReturn24h
555
+ ),
556
+ btcVsAltReturn1h: toFiniteNumberOrNull(
557
+ primaryBreadth.btcVsAltReturn1h
558
+ ),
559
+ btcVsAltReturn4h: toFiniteNumberOrNull(
560
+ primaryBreadth.btcVsAltReturn4h
561
+ ),
562
+ btcVsAltReturn24h: toFiniteNumberOrNull(
563
+ primaryBreadth.btcVsAltReturn24h
564
+ ),
565
+ btcTurnoverShare1h: toFiniteNumberOrNull(
566
+ primaryBreadth.btcTurnoverShare1h
567
+ ),
568
+ btcTurnoverShare24h: toFiniteNumberOrNull(
569
+ primaryBreadth.btcTurnoverShare24h
570
+ ),
571
+ btcTurnoverShareChange24h: toFiniteNumberOrNull(
572
+ primaryBreadth.btcTurnoverShareChange24h
573
+ ),
574
+ altVolToBtcVol24h: toFiniteNumberOrNull(
575
+ primaryBreadth.altVolToBtcVol24h
576
+ ),
577
+ altDispersion24h: toFiniteNumberOrNull(
578
+ primaryBreadth.altDispersion24h
579
+ ),
580
+ regime: primaryBreadth.btcAltRegime ?? "unknown"
581
+ }
582
+ } : {}
250
583
  } : {}
251
584
  }
252
585
  }
@@ -1041,6 +1374,11 @@ var buildMlPayload = (payload) => {
1041
1374
  };
1042
1375
 
1043
1376
  export {
1377
+ BINANCE_BREADTH_UNIVERSE_KEYS,
1378
+ buildBinanceBreadthUniverseSnapshot,
1379
+ getBinanceBreadthUniverseSnapshot,
1380
+ getBinanceBreadthUniverses,
1381
+ getPrimaryBinanceBreadthUniverse,
1044
1382
  enrichSignalWithBinanceMarketContext,
1045
1383
  enrichSignalWithCoinMarketCapContext,
1046
1384
  enrichSignalWithDerivativesContext,