@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.js CHANGED
@@ -43,6 +43,7 @@ module.exports = __toCommonJS(backtest_exports);
43
43
  __reExport(backtest_exports, require("@tradejs/core/backtest"), module.exports);
44
44
 
45
45
  // src/testing.ts
46
+ var import_types = require("@tradejs/types");
46
47
  var import_indicators3 = require("@tradejs/core/indicators");
47
48
  var import_constants4 = require("@tradejs/core/constants");
48
49
  var import_strategies4 = require("@tradejs/core/strategies");
@@ -234,6 +235,51 @@ var buildMarketBreadthContextFromSignal = (signal) => {
234
235
  dispersion: toFiniteNumber(breadth.dispersion)
235
236
  };
236
237
  };
238
+ var buildMarketBreadthsContextFromSignal = (signal) => {
239
+ const baseContext = toRecord(signal.additionalIndicators?.baseContext);
240
+ const relative = toRecord(baseContext?.relative);
241
+ const breadths = toRecord(relative?.marketBreadths);
242
+ const build = (key) => {
243
+ const breadth = toRecord(breadths?.[key]);
244
+ if (!breadth) {
245
+ return {
246
+ source: null,
247
+ available: false,
248
+ universe: null,
249
+ interval: null,
250
+ stale: null,
251
+ symbolsCount: null,
252
+ advanceDeclineRatio: null,
253
+ pctAboveMa20: null,
254
+ pctAboveMa50: null,
255
+ equalWeightedReturn: null,
256
+ volumeWeightedReturn: null,
257
+ dispersion: null
258
+ };
259
+ }
260
+ return {
261
+ source: String(breadth.source ?? ""),
262
+ available: true,
263
+ universe: String(breadth.universe ?? ""),
264
+ interval: String(breadth.interval ?? ""),
265
+ stale: typeof breadth.stale === "boolean" ? breadth.stale : null,
266
+ symbolsCount: toFiniteNumber(breadth.symbolsCount),
267
+ advanceDeclineRatio: toFiniteNumber(breadth.advanceDeclineRatio),
268
+ pctAboveMa20: toFiniteNumber(breadth.pctAboveMa20),
269
+ pctAboveMa50: toFiniteNumber(breadth.pctAboveMa50),
270
+ equalWeightedReturn: toFiniteNumber(breadth.equalWeightedReturn),
271
+ volumeWeightedReturn: toFiniteNumber(breadth.volumeWeightedReturn),
272
+ dispersion: toFiniteNumber(breadth.dispersion)
273
+ };
274
+ };
275
+ return {
276
+ top5: build("top5"),
277
+ top10: build("top10"),
278
+ top30: build("top30"),
279
+ top50: build("top50"),
280
+ top100: build("top100")
281
+ };
282
+ };
237
283
  var buildTargetVsBtcContextFromSignal = (signal) => {
238
284
  const baseContext = toRecord(signal.additionalIndicators?.baseContext);
239
285
  const relative = toRecord(baseContext?.relative);
@@ -571,6 +617,7 @@ var buildAiMarketContext = (signal) => ({
571
617
  },
572
618
  relative: {
573
619
  marketBreadth: buildMarketBreadthContextFromSignal(signal),
620
+ marketBreadths: buildMarketBreadthsContextFromSignal(signal),
574
621
  targetVsBtc: buildTargetVsBtcContextFromSignal(signal),
575
622
  btcAltRegime: buildBtcAltRegimeContextFromSignal(signal),
576
623
  cmcGlobal: buildCmcGlobalContextFromSignal(signal),
@@ -1110,6 +1157,311 @@ var buildAiPayload = (signal) => buildAiPayloadByStrategy(signal);
1110
1157
  var import_timescale = require("@tradejs/infra/timescale");
1111
1158
  var import_logger3 = require("@tradejs/infra/logger");
1112
1159
  var import_strategies = require("@tradejs/core/strategies");
1160
+
1161
+ // src/binanceBreadthUniverses.ts
1162
+ var import_node_crypto = require("crypto");
1163
+
1164
+ // src/config/binanceBreadthUniverses.json
1165
+ var binanceBreadthUniverses_default = {
1166
+ schemaVersion: 1,
1167
+ updatedAt: "2026-07-24T15:28:27.234Z",
1168
+ source: "binance_spot_usdt_turnover24h",
1169
+ fingerprint: "43df006e5510bb99",
1170
+ universes: {
1171
+ top5: {
1172
+ size: 5,
1173
+ fingerprint: "ba5b73456b18",
1174
+ symbols: ["ETHUSDT", "USD1USDT", "SOLUSDT", "OPNUSDT", "VANAUSDT"]
1175
+ },
1176
+ top10: {
1177
+ size: 10,
1178
+ fingerprint: "370eb5af9778",
1179
+ symbols: [
1180
+ "ETHUSDT",
1181
+ "USD1USDT",
1182
+ "SOLUSDT",
1183
+ "OPNUSDT",
1184
+ "VANAUSDT",
1185
+ "DEXEUSDT",
1186
+ "BANKUSDT",
1187
+ "ZECUSDT",
1188
+ "XRPUSDT",
1189
+ "RLUSDUSDT"
1190
+ ]
1191
+ },
1192
+ top30: {
1193
+ size: 30,
1194
+ fingerprint: "685a7e7f5fce",
1195
+ symbols: [
1196
+ "ETHUSDT",
1197
+ "USD1USDT",
1198
+ "SOLUSDT",
1199
+ "OPNUSDT",
1200
+ "VANAUSDT",
1201
+ "DEXEUSDT",
1202
+ "BANKUSDT",
1203
+ "ZECUSDT",
1204
+ "XRPUSDT",
1205
+ "RLUSDUSDT",
1206
+ "BNBUSDT",
1207
+ "DOGEUSDT",
1208
+ "REUSDT",
1209
+ "RIFUSDT",
1210
+ "ZAMAUSDT",
1211
+ "TRXUSDT",
1212
+ "EURUSDT",
1213
+ "AEROUSDT",
1214
+ "UUSDT",
1215
+ "SUIUSDT",
1216
+ "XAUTUSDT",
1217
+ "NEARUSDT",
1218
+ "LTCUSDT",
1219
+ "SPCXBUSDT",
1220
+ "LAUSDT",
1221
+ "AVAXUSDT",
1222
+ "WLDUSDT",
1223
+ "ONDOUSDT",
1224
+ "XLMUSDT",
1225
+ "UNIUSDT"
1226
+ ]
1227
+ },
1228
+ top50: {
1229
+ size: 50,
1230
+ fingerprint: "cb9725e24141",
1231
+ symbols: [
1232
+ "ETHUSDT",
1233
+ "USD1USDT",
1234
+ "SOLUSDT",
1235
+ "OPNUSDT",
1236
+ "VANAUSDT",
1237
+ "DEXEUSDT",
1238
+ "BANKUSDT",
1239
+ "ZECUSDT",
1240
+ "XRPUSDT",
1241
+ "RLUSDUSDT",
1242
+ "BNBUSDT",
1243
+ "DOGEUSDT",
1244
+ "REUSDT",
1245
+ "RIFUSDT",
1246
+ "ZAMAUSDT",
1247
+ "TRXUSDT",
1248
+ "EURUSDT",
1249
+ "AEROUSDT",
1250
+ "UUSDT",
1251
+ "SUIUSDT",
1252
+ "XAUTUSDT",
1253
+ "NEARUSDT",
1254
+ "LTCUSDT",
1255
+ "SPCXBUSDT",
1256
+ "LAUSDT",
1257
+ "AVAXUSDT",
1258
+ "WLDUSDT",
1259
+ "ONDOUSDT",
1260
+ "XLMUSDT",
1261
+ "UNIUSDT",
1262
+ "ADAUSDT",
1263
+ "SNDKBUSDT",
1264
+ "KAITOUSDT",
1265
+ "UTKUSDT",
1266
+ "PEPEUSDT",
1267
+ "WLFIUSDT",
1268
+ "PAXGUSDT",
1269
+ "ENAUSDT",
1270
+ "TAOUSDT",
1271
+ "AAVEUSDT",
1272
+ "CRCLBUSDT",
1273
+ "LINKUSDT",
1274
+ "TONUSDT",
1275
+ "ALLOUSDT",
1276
+ "PUMPUSDT",
1277
+ "INJUSDT",
1278
+ "HOMEUSDT",
1279
+ "MUBUSDT",
1280
+ "TSLABUSDT",
1281
+ "GRAMUSDT"
1282
+ ]
1283
+ },
1284
+ top100: {
1285
+ size: 100,
1286
+ fingerprint: "f397883a81cf",
1287
+ symbols: [
1288
+ "ETHUSDT",
1289
+ "USD1USDT",
1290
+ "SOLUSDT",
1291
+ "OPNUSDT",
1292
+ "VANAUSDT",
1293
+ "DEXEUSDT",
1294
+ "BANKUSDT",
1295
+ "ZECUSDT",
1296
+ "XRPUSDT",
1297
+ "RLUSDUSDT",
1298
+ "BNBUSDT",
1299
+ "DOGEUSDT",
1300
+ "REUSDT",
1301
+ "RIFUSDT",
1302
+ "ZAMAUSDT",
1303
+ "TRXUSDT",
1304
+ "EURUSDT",
1305
+ "AEROUSDT",
1306
+ "UUSDT",
1307
+ "SUIUSDT",
1308
+ "XAUTUSDT",
1309
+ "NEARUSDT",
1310
+ "LTCUSDT",
1311
+ "SPCXBUSDT",
1312
+ "LAUSDT",
1313
+ "AVAXUSDT",
1314
+ "WLDUSDT",
1315
+ "ONDOUSDT",
1316
+ "XLMUSDT",
1317
+ "UNIUSDT",
1318
+ "ADAUSDT",
1319
+ "SNDKBUSDT",
1320
+ "KAITOUSDT",
1321
+ "UTKUSDT",
1322
+ "PEPEUSDT",
1323
+ "WLFIUSDT",
1324
+ "PAXGUSDT",
1325
+ "ENAUSDT",
1326
+ "TAOUSDT",
1327
+ "AAVEUSDT",
1328
+ "CRCLBUSDT",
1329
+ "LINKUSDT",
1330
+ "TONUSDT",
1331
+ "ALLOUSDT",
1332
+ "PUMPUSDT",
1333
+ "INJUSDT",
1334
+ "HOMEUSDT",
1335
+ "MUBUSDT",
1336
+ "TSLABUSDT",
1337
+ "GRAMUSDT",
1338
+ "\u5E01\u5B89\u4EBA\u751FUSDT",
1339
+ "SYNUSDT",
1340
+ "TRUMPUSDT",
1341
+ "PROMUSDT",
1342
+ "XPLUSDT",
1343
+ "HBARUSDT",
1344
+ "ERAUSDT",
1345
+ "BCHUSDT",
1346
+ "SOXLBUSDT",
1347
+ "DOTUSDT",
1348
+ "EPICUSDT",
1349
+ "KITEUSDT",
1350
+ "NFPUSDT",
1351
+ "KGSTUSDT",
1352
+ "FILUSDT",
1353
+ "FETUSDT",
1354
+ "KORUBUSDT",
1355
+ "ARBUSDT",
1356
+ "STXUSDT",
1357
+ "HEIUSDT",
1358
+ "BARDUSDT",
1359
+ "DASHUSDT",
1360
+ "ASTERUSDT",
1361
+ "VANRYUSDT",
1362
+ "INTCBUSDT",
1363
+ "ETCUSDT",
1364
+ "EIGENUSDT",
1365
+ "PENGUUSDT",
1366
+ "LRCUSDT",
1367
+ "VIRTUALUSDT",
1368
+ "POLUSDT",
1369
+ "ICPUSDT",
1370
+ "ZBTUSDT",
1371
+ "SKHYBUSDT",
1372
+ "SHIBUSDT",
1373
+ "LDOUSDT",
1374
+ "APTUSDT",
1375
+ "JTOUSDT",
1376
+ "NOMUSDT",
1377
+ "ZROUSDT",
1378
+ "OPUSDT",
1379
+ "BONKUSDT",
1380
+ "SKLUSDT",
1381
+ "HEMIUSDT",
1382
+ "TNSRUSDT",
1383
+ "INTWBUSDT",
1384
+ "BIOUSDT",
1385
+ "TIAUSDT",
1386
+ "ORDIUSDT",
1387
+ "XUSDUSDT"
1388
+ ]
1389
+ }
1390
+ }
1391
+ };
1392
+
1393
+ // src/binanceBreadthUniverses.ts
1394
+ var BINANCE_BREADTH_UNIVERSE_KEYS = [
1395
+ "top5",
1396
+ "top10",
1397
+ "top30",
1398
+ "top50",
1399
+ "top100"
1400
+ ];
1401
+ var fingerprint = (value, length) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
1402
+ var normalizeSymbols = (symbols) => symbols.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean);
1403
+ var buildBinanceBreadthUniverseSnapshot = ({
1404
+ rankedSymbols,
1405
+ updatedAt = (/* @__PURE__ */ new Date()).toISOString()
1406
+ }) => {
1407
+ const uniqueSymbols = [...new Set(normalizeSymbols(rankedSymbols))];
1408
+ if (uniqueSymbols.length < 100) {
1409
+ throw new Error(
1410
+ `Binance breadth snapshot requires at least 100 symbols, got ${uniqueSymbols.length}`
1411
+ );
1412
+ }
1413
+ const universes = Object.fromEntries(
1414
+ BINANCE_BREADTH_UNIVERSE_KEYS.map((key) => {
1415
+ const size = Number(key.slice(3));
1416
+ const symbols = uniqueSymbols.slice(0, size);
1417
+ return [
1418
+ key,
1419
+ {
1420
+ size,
1421
+ fingerprint: fingerprint(symbols, 12),
1422
+ symbols
1423
+ }
1424
+ ];
1425
+ })
1426
+ );
1427
+ return {
1428
+ schemaVersion: 1,
1429
+ updatedAt,
1430
+ source: "binance_spot_usdt_turnover24h",
1431
+ fingerprint: fingerprint(universes, 16),
1432
+ universes
1433
+ };
1434
+ };
1435
+ var validateSnapshot = (value) => {
1436
+ const rebuilt = buildBinanceBreadthUniverseSnapshot({
1437
+ rankedSymbols: value.universes.top100.symbols,
1438
+ updatedAt: value.updatedAt
1439
+ });
1440
+ if (value.schemaVersion !== 1 || value.source !== rebuilt.source || value.fingerprint !== rebuilt.fingerprint) {
1441
+ throw new Error("Invalid Binance breadth universe snapshot fingerprint");
1442
+ }
1443
+ for (const key of BINANCE_BREADTH_UNIVERSE_KEYS) {
1444
+ const actual = value.universes[key];
1445
+ const expected = rebuilt.universes[key];
1446
+ if (actual.size !== expected.size || actual.fingerprint !== expected.fingerprint || JSON.stringify(actual.symbols) !== JSON.stringify(expected.symbols)) {
1447
+ throw new Error(`Invalid Binance breadth universe snapshot: ${key}`);
1448
+ }
1449
+ }
1450
+ return value;
1451
+ };
1452
+ var snapshot = validateSnapshot(binanceBreadthUniverses_default);
1453
+ var getBinanceBreadthUniverses = () => BINANCE_BREADTH_UNIVERSE_KEYS.map((key) => {
1454
+ const definition = snapshot.universes[key];
1455
+ return {
1456
+ key,
1457
+ size: definition.size,
1458
+ fingerprint: definition.fingerprint,
1459
+ universe: `binance_${key}_usdt_${definition.fingerprint}`,
1460
+ symbols: [...definition.symbols]
1461
+ };
1462
+ });
1463
+
1464
+ // src/strategyHelpers/binanceMarketContext.ts
1113
1465
  var DEFAULT_MAX_AGE_BY_INTERVAL = {
1114
1466
  "1m": 3 * 6e4,
1115
1467
  "5m": 10 * 6e4,
@@ -1141,7 +1493,6 @@ var signalIntervalToMarketInterval = (value) => {
1141
1493
  return "15m";
1142
1494
  };
1143
1495
  var resolveMarketInterval = (signal, override) => override ?? signalIntervalToMarketInterval(signal.interval);
1144
- var resolveBreadthUniverse = () => (process.env.BINANCE_MARKET_CONTEXT_BREADTH_UNIVERSE || "binance_top30_usdt").trim().toLowerCase();
1145
1496
  var getReferenceSymbols = () => {
1146
1497
  const symbols = (process.env.BINANCE_MARKET_CONTEXT_REFERENCE_SYMBOLS || "BTCUSDT,ETHUSDT").split(",").map((item) => item.trim().toUpperCase()).filter(Boolean);
1147
1498
  return symbols.length ? [...new Set(symbols)] : ["BTCUSDT", "ETHUSDT"];
@@ -1214,13 +1565,30 @@ var getCachedBreadth = ({
1214
1565
  breadthCache.set(key, promise);
1215
1566
  return promise;
1216
1567
  };
1568
+ var toMarketBreadthContext = (breadth, interval) => ({
1569
+ source: "binance_klines",
1570
+ universe: breadth.universe,
1571
+ interval,
1572
+ asOfTs: breadth.ts.getTime(),
1573
+ ageMs: breadth.ageMs,
1574
+ stale: breadth.stale,
1575
+ symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
1576
+ advancers: toFiniteNumberOrNull(breadth.advancers),
1577
+ decliners: toFiniteNumberOrNull(breadth.decliners),
1578
+ unchanged: toFiniteNumberOrNull(breadth.unchanged),
1579
+ advanceDeclineRatio: toFiniteNumberOrNull(breadth.advanceDeclineRatio),
1580
+ pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
1581
+ pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
1582
+ equalWeightedReturn: toFiniteNumberOrNull(breadth.equalWeightedReturn),
1583
+ volumeWeightedReturn: toFiniteNumberOrNull(breadth.volumeWeightedReturn),
1584
+ dispersion: toFiniteNumberOrNull(breadth.dispersion)
1585
+ });
1217
1586
  var enrichSignalWithBinanceMarketContext = async (params) => {
1218
1587
  const {
1219
1588
  signal,
1220
1589
  env,
1221
1590
  enabled = isBinanceMarketContextEnabled(env),
1222
1591
  interval = resolveMarketInterval(signal, params.interval),
1223
- breadthUniverse = resolveBreadthUniverse(),
1224
1592
  maxAgeMs = DEFAULT_MAX_AGE_BY_INTERVAL[interval]
1225
1593
  } = params;
1226
1594
  if (signal.universe === "tradfi" || !enabled || binanceMarketContextUnavailable || !hasBaseContext(signal)) {
@@ -1229,26 +1597,49 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
1229
1597
  try {
1230
1598
  const referenceSymbols = getReferenceSymbols();
1231
1599
  const primaryReferenceSymbol = resolvePrimaryReferenceSymbol(signal.symbol);
1232
- const [referenceRows, breadth] = await Promise.all([
1600
+ const breadthUniverses = params.breadthUniverse ? [
1601
+ {
1602
+ key: "top30",
1603
+ universe: params.breadthUniverse
1604
+ }
1605
+ ] : getBinanceBreadthUniverses();
1606
+ const [referenceRows, breadthRows] = await Promise.all([
1233
1607
  getCachedReferenceRows({
1234
1608
  referenceSymbols,
1235
1609
  interval,
1236
1610
  timestamp: signal.timestamp,
1237
1611
  maxAgeMs
1238
1612
  }),
1239
- getCachedBreadth({
1240
- breadthUniverse,
1241
- interval,
1242
- timestamp: signal.timestamp,
1243
- maxAgeMs
1244
- })
1613
+ Promise.all(
1614
+ breadthUniverses.map(async ({ key, universe }) => ({
1615
+ key,
1616
+ breadth: await getCachedBreadth({
1617
+ breadthUniverse: universe,
1618
+ interval,
1619
+ timestamp: signal.timestamp,
1620
+ maxAgeMs
1621
+ })
1622
+ }))
1623
+ )
1245
1624
  ]);
1625
+ const availableBreadths = breadthRows.filter(
1626
+ (row) => row.breadth != null
1627
+ );
1628
+ const marketBreadths = Object.fromEntries(
1629
+ availableBreadths.map(({ key, breadth }) => [
1630
+ key,
1631
+ toMarketBreadthContext(breadth, interval)
1632
+ ])
1633
+ );
1634
+ const primaryBreadth = availableBreadths.find(
1635
+ ({ key }) => key === "top30"
1636
+ )?.breadth;
1246
1637
  const tradeFlowBySymbol = Object.fromEntries(
1247
1638
  referenceRows.filter((row) => row.tradeFlow).map((row) => [row.symbol, row.tradeFlow])
1248
1639
  );
1249
1640
  const targetReferenceSymbol = signal.symbol.trim().toUpperCase();
1250
1641
  const targetTradeFlow = tradeFlowBySymbol[targetReferenceSymbol];
1251
- if (!Object.keys(tradeFlowBySymbol).length && !breadth) {
1642
+ if (!Object.keys(tradeFlowBySymbol).length && !availableBreadths.length) {
1252
1643
  return false;
1253
1644
  }
1254
1645
  const baseContext = signal.additionalIndicators.baseContext;
@@ -1275,76 +1666,65 @@ var enrichSignalWithBinanceMarketContext = async (params) => {
1275
1666
  tradeFlowBySymbol
1276
1667
  }
1277
1668
  } : {},
1278
- ...breadth ? {
1279
- marketBreadth: {
1280
- source: "binance_klines",
1281
- universe: breadth.universe,
1282
- interval,
1283
- asOfTs: breadth.ts.getTime(),
1284
- ageMs: breadth.ageMs,
1285
- stale: breadth.stale,
1286
- symbolsCount: toFiniteNumberOrNull(breadth.symbolsCount),
1287
- advancers: toFiniteNumberOrNull(breadth.advancers),
1288
- decliners: toFiniteNumberOrNull(breadth.decliners),
1289
- unchanged: toFiniteNumberOrNull(breadth.unchanged),
1290
- advanceDeclineRatio: toFiniteNumberOrNull(
1291
- breadth.advanceDeclineRatio
1292
- ),
1293
- pctAboveMa20: toFiniteNumberOrNull(breadth.pctAboveMa20),
1294
- pctAboveMa50: toFiniteNumberOrNull(breadth.pctAboveMa50),
1295
- equalWeightedReturn: toFiniteNumberOrNull(
1296
- breadth.equalWeightedReturn
1297
- ),
1298
- volumeWeightedReturn: toFiniteNumberOrNull(
1299
- breadth.volumeWeightedReturn
1300
- ),
1301
- dispersion: toFiniteNumberOrNull(breadth.dispersion)
1302
- },
1303
- btcAltRegime: {
1304
- source: "binance_klines",
1305
- universe: breadth.universe,
1306
- interval,
1307
- asOfTs: breadth.ts.getTime(),
1308
- ageMs: breadth.ageMs,
1309
- stale: breadth.stale,
1310
- btcReturn1h: toFiniteNumberOrNull(breadth.btcReturn1h),
1311
- btcReturn4h: toFiniteNumberOrNull(breadth.btcReturn4h),
1312
- btcReturn24h: toFiniteNumberOrNull(breadth.btcReturn24h),
1313
- altBasketReturn1h: toFiniteNumberOrNull(
1314
- breadth.altBasketReturn1h
1315
- ),
1316
- altBasketReturn4h: toFiniteNumberOrNull(
1317
- breadth.altBasketReturn4h
1318
- ),
1319
- altBasketReturn24h: toFiniteNumberOrNull(
1320
- breadth.altBasketReturn24h
1669
+ ...availableBreadths.length ? {
1670
+ marketBreadths,
1671
+ ...primaryBreadth ? {
1672
+ marketBreadth: toMarketBreadthContext(
1673
+ primaryBreadth,
1674
+ interval
1321
1675
  ),
1322
- btcVsAltReturn1h: toFiniteNumberOrNull(
1323
- breadth.btcVsAltReturn1h
1324
- ),
1325
- btcVsAltReturn4h: toFiniteNumberOrNull(
1326
- breadth.btcVsAltReturn4h
1327
- ),
1328
- btcVsAltReturn24h: toFiniteNumberOrNull(
1329
- breadth.btcVsAltReturn24h
1330
- ),
1331
- btcTurnoverShare1h: toFiniteNumberOrNull(
1332
- breadth.btcTurnoverShare1h
1333
- ),
1334
- btcTurnoverShare24h: toFiniteNumberOrNull(
1335
- breadth.btcTurnoverShare24h
1336
- ),
1337
- btcTurnoverShareChange24h: toFiniteNumberOrNull(
1338
- breadth.btcTurnoverShareChange24h
1339
- ),
1340
- altVolToBtcVol24h: toFiniteNumberOrNull(
1341
- breadth.altVolToBtcVol24h
1342
- ),
1343
- altDispersion24h: toFiniteNumberOrNull(
1344
- breadth.altDispersion24h
1345
- ),
1346
- regime: breadth.btcAltRegime ?? "unknown"
1347
- }
1676
+ btcAltRegime: {
1677
+ source: "binance_klines",
1678
+ universe: primaryBreadth.universe,
1679
+ interval,
1680
+ asOfTs: primaryBreadth.ts.getTime(),
1681
+ ageMs: primaryBreadth.ageMs,
1682
+ stale: primaryBreadth.stale,
1683
+ btcReturn1h: toFiniteNumberOrNull(
1684
+ primaryBreadth.btcReturn1h
1685
+ ),
1686
+ btcReturn4h: toFiniteNumberOrNull(
1687
+ primaryBreadth.btcReturn4h
1688
+ ),
1689
+ btcReturn24h: toFiniteNumberOrNull(
1690
+ primaryBreadth.btcReturn24h
1691
+ ),
1692
+ altBasketReturn1h: toFiniteNumberOrNull(
1693
+ primaryBreadth.altBasketReturn1h
1694
+ ),
1695
+ altBasketReturn4h: toFiniteNumberOrNull(
1696
+ primaryBreadth.altBasketReturn4h
1697
+ ),
1698
+ altBasketReturn24h: toFiniteNumberOrNull(
1699
+ primaryBreadth.altBasketReturn24h
1700
+ ),
1701
+ btcVsAltReturn1h: toFiniteNumberOrNull(
1702
+ primaryBreadth.btcVsAltReturn1h
1703
+ ),
1704
+ btcVsAltReturn4h: toFiniteNumberOrNull(
1705
+ primaryBreadth.btcVsAltReturn4h
1706
+ ),
1707
+ btcVsAltReturn24h: toFiniteNumberOrNull(
1708
+ primaryBreadth.btcVsAltReturn24h
1709
+ ),
1710
+ btcTurnoverShare1h: toFiniteNumberOrNull(
1711
+ primaryBreadth.btcTurnoverShare1h
1712
+ ),
1713
+ btcTurnoverShare24h: toFiniteNumberOrNull(
1714
+ primaryBreadth.btcTurnoverShare24h
1715
+ ),
1716
+ btcTurnoverShareChange24h: toFiniteNumberOrNull(
1717
+ primaryBreadth.btcTurnoverShareChange24h
1718
+ ),
1719
+ altVolToBtcVol24h: toFiniteNumberOrNull(
1720
+ primaryBreadth.altVolToBtcVol24h
1721
+ ),
1722
+ altDispersion24h: toFiniteNumberOrNull(
1723
+ primaryBreadth.altDispersion24h
1724
+ ),
1725
+ regime: primaryBreadth.btcAltRegime ?? "unknown"
1726
+ }
1727
+ } : {}
1348
1728
  } : {}
1349
1729
  }
1350
1730
  }
@@ -2318,7 +2698,7 @@ var getConnectorCreatorByName = async (connectorName, cwd = getTradejsProjectCwd
2318
2698
  var DEFAULT_CONNECTOR_NAME = BUILTIN_CONNECTOR_NAMES.ByBit;
2319
2699
 
2320
2700
  // src/testConnector.ts
2321
- var import_node_crypto = require("crypto");
2701
+ var import_node_crypto2 = require("crypto");
2322
2702
  var import_constants2 = require("@tradejs/core/constants");
2323
2703
  var import_backtest = require("@tradejs/core/backtest");
2324
2704
  var import_trade = require("@tradejs/core/trade");
@@ -2629,7 +3009,7 @@ var createTestConnector = (connector, context) => {
2629
3009
  },
2630
3010
  kline: async (options) => connector.kline(options),
2631
3011
  getResult: async () => {
2632
- const orderLogId = (0, import_node_crypto.randomUUID)().slice(-12);
3012
+ const orderLogId = (0, import_node_crypto2.randomUUID)().slice(-12);
2633
3013
  const fullStat = fastMode ? (0, import_backtest.calculateStatsFull)(positionLog) : null;
2634
3014
  return {
2635
3015
  stat: fullStat ? {
@@ -3249,6 +3629,16 @@ var resolveExecutionCosts = async (params) => {
3249
3629
  var isBacktestEntryDelayControlCode = (value) => typeof value === "string" && value.startsWith("BACKTEST_ENTRY_DELAY_");
3250
3630
  var CLOSED_RESULT_FLUSH_INTERVAL = 500;
3251
3631
  var DEFAULT_STRATEGY_CANDLE_TIMEOUT_MS = 6e4;
3632
+ var createBacktestWarningCounts = () => ({
3633
+ [import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY]: 0
3634
+ });
3635
+ var recordBacktestSignalWarning = (warningCounts, signal) => {
3636
+ if (!signal || typeof signal === "string" || signal.orderStatus !== "failed" || signal.orderFailureReason !== import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY) {
3637
+ return;
3638
+ }
3639
+ const code = import_types.BACKTEST_WARNING_CODES.TAKE_PROFIT_CROSSED_BEFORE_ENTRY;
3640
+ warningCounts[code] = (warningCounts[code] ?? 0) + 1;
3641
+ };
3252
3642
  var resolvePositiveInt = (value, fallback) => {
3253
3643
  const parsed = parseInt(String(value ?? ""), 10);
3254
3644
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
@@ -4082,6 +4472,7 @@ var testing = async ({
4082
4472
  );
4083
4473
  const pendingMlPayloadBySignalId = /* @__PURE__ */ new Map();
4084
4474
  const pendingAiRowBySignalId = /* @__PURE__ */ new Map();
4475
+ const warningCounts = createBacktestWarningCounts();
4085
4476
  const replaySignalEvaluations = collectReplaySignalEvaluations ? [] : null;
4086
4477
  const flushClosedResultsBatch = async () => {
4087
4478
  if (!ml && !ai) return;
@@ -4126,6 +4517,7 @@ var testing = async ({
4126
4517
  }
4127
4518
  };
4128
4519
  const processSignal = async (signal, candle) => {
4520
+ recordBacktestSignalWarning(warningCounts, signal);
4129
4521
  if (isBacktestEntryDelayControlCode(signal)) {
4130
4522
  return;
4131
4523
  }
@@ -4234,10 +4626,14 @@ var testing = async ({
4234
4626
  }
4235
4627
  await withTimeout("flush closed results", flushClosedResultsBatch());
4236
4628
  const result = await withTimeout("collect result", testConnector.getResult());
4237
- return replaySignalEvaluations ? {
4629
+ const resultWithWarnings = {
4238
4630
  ...result,
4631
+ warningCounts
4632
+ };
4633
+ return replaySignalEvaluations ? {
4634
+ ...resultWithWarnings,
4239
4635
  inlineReplaySignalEvaluations: replaySignalEvaluations
4240
- } : result;
4636
+ } : resultWithWarnings;
4241
4637
  };
4242
4638
  var testingGroupInSharedCandleLoop = async (tests) => {
4243
4639
  if (!canRunTestsInSharedCandleLoop(tests)) {
@@ -4491,6 +4887,7 @@ var testingGroupInSharedCandleLoop = async (tests) => {
4491
4887
  testConnector,
4492
4888
  pendingMlPayloadBySignalId: /* @__PURE__ */ new Map(),
4493
4889
  pendingAiRowBySignalId: /* @__PURE__ */ new Map(),
4890
+ warningCounts: createBacktestWarningCounts(),
4494
4891
  replaySignalEvaluations: collectReplaySignalEvaluations ? [] : null
4495
4892
  });
4496
4893
  }
@@ -4543,6 +4940,7 @@ var testingGroupInSharedCandleLoop = async (tests) => {
4543
4940
  }
4544
4941
  };
4545
4942
  const processRunnerSignal = async (runner, signal, candle) => {
4943
+ recordBacktestSignalWarning(runner.warningCounts, signal);
4546
4944
  if (isBacktestEntryDelayControlCode(signal)) {
4547
4945
  return;
4548
4946
  }
@@ -4684,8 +5082,12 @@ var testingGroupInSharedCandleLoop = async (tests) => {
4684
5082
  test: runner.test,
4685
5083
  result: runner.replaySignalEvaluations ? {
4686
5084
  ...result,
5085
+ warningCounts: runner.warningCounts,
4687
5086
  inlineReplaySignalEvaluations: runner.replaySignalEvaluations
4688
- } : result
5087
+ } : {
5088
+ ...result,
5089
+ warningCounts: runner.warningCounts
5090
+ }
4689
5091
  });
4690
5092
  }
4691
5093
  return results;