@tradejs/node 2.0.13 → 2.0.14

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
@@ -46,11 +46,11 @@ __reExport(backtest_exports, require("@tradejs/core/backtest"), module.exports);
46
46
  var import_types = require("@tradejs/types");
47
47
  var import_indicators3 = require("@tradejs/core/indicators");
48
48
  var import_constants4 = require("@tradejs/core/constants");
49
- var import_strategies4 = require("@tradejs/core/strategies");
49
+ var import_strategies5 = require("@tradejs/core/strategies");
50
50
  var import_time = require("@tradejs/core/time");
51
51
  var import_ai2 = require("@tradejs/infra/ai");
52
52
  var import_ml2 = require("@tradejs/infra/ml");
53
- var import_logger7 = require("@tradejs/infra/logger");
53
+ var import_logger8 = require("@tradejs/infra/logger");
54
54
 
55
55
  // src/ai.ts
56
56
  var import_aiLanguages = require("@tradejs/infra/aiLanguages");
@@ -2457,6 +2457,558 @@ var enrichSignalWithDerivativesContext = async (params) => {
2457
2457
  }
2458
2458
  };
2459
2459
 
2460
+ // src/strategyHelpers/hyperliquidWhaleContext.ts
2461
+ var import_data2 = require("@tradejs/core/data");
2462
+ var import_strategies4 = require("@tradejs/core/strategies");
2463
+ var import_timescale4 = require("@tradejs/infra/timescale");
2464
+ var import_logger6 = require("@tradejs/infra/logger");
2465
+
2466
+ // src/hyperliquidWhaleUniverse.ts
2467
+ var import_node_crypto2 = require("crypto");
2468
+
2469
+ // src/config/hyperliquidPerpUniverse.json
2470
+ var hyperliquidPerpUniverse_default = {
2471
+ schemaVersion: 1,
2472
+ updatedAt: "2026-08-04T11:46:43.476Z",
2473
+ source: "hyperliquid_main_perp_day_notional_volume",
2474
+ fingerprint: "58f7e024b19158dc",
2475
+ size: 30,
2476
+ symbols: [
2477
+ "BTC",
2478
+ "ETH",
2479
+ "HYPE",
2480
+ "SOL",
2481
+ "ZEC",
2482
+ "PUMP",
2483
+ "XRP",
2484
+ "CASHCAT",
2485
+ "WLD",
2486
+ "GRAM",
2487
+ "UNI",
2488
+ "ADA",
2489
+ "LIT",
2490
+ "ENA",
2491
+ "NEAR",
2492
+ "kPEPE",
2493
+ "FARTCOIN",
2494
+ "KAITO",
2495
+ "AAVE",
2496
+ "SUI",
2497
+ "ONDO",
2498
+ "AVAX",
2499
+ "DOGE",
2500
+ "VVV",
2501
+ "BNB",
2502
+ "ALGO",
2503
+ "XMR",
2504
+ "MEGA",
2505
+ "LINK",
2506
+ "TAO"
2507
+ ]
2508
+ };
2509
+
2510
+ // src/config/hyperliquidWhales.json
2511
+ var hyperliquidWhales_default = {
2512
+ schemaVersion: 2,
2513
+ updatedAt: "2026-08-04T15:10:21.316Z",
2514
+ source: "hyperliquid_structural_fills_snapshot",
2515
+ selection: {
2516
+ calibrationFrom: "2026-07-28T00:00:00.000Z",
2517
+ calibrationTo: "2026-08-04T00:00:00.000Z",
2518
+ effectiveFrom: "2026-08-04T00:00:00.000Z",
2519
+ candidateLimit: 400,
2520
+ minimumAccountValueUsd: 5e5,
2521
+ minimumActiveDays: 1,
2522
+ maximumRawFillsPerDay: 20,
2523
+ maximumDirectionalExecutionsPerDay: 10,
2524
+ minimumMedianNotionalUsd: 0,
2525
+ minimumMedianInterExecutionMinutes: 0,
2526
+ minimumTop30NotionalShare: 1e-3,
2527
+ minimumTurnoverToEquity: 0.05,
2528
+ maximumTurnoverToEquity: 5,
2529
+ score: "log1p(accountValue)*log1p(medianExecutionNotional)*log1p(max(1,medianInterExecutionMinutes))*sqrt(activeDays)*top30Share/sqrt(1+rawFillsPerDay)",
2530
+ forbiddenSelectionMetrics: ["pnl", "roi", "winRate", "closedPnl"]
2531
+ },
2532
+ fingerprint: "77a6e9d41222efdb",
2533
+ size: 100,
2534
+ addresses: [
2535
+ "0xacb66d1996501bcc82a0e7819c9e041599f5efbd",
2536
+ "0x90dbb196cacf24bd212b377a0dd62eaad5e8151b",
2537
+ "0xde5b8c8e713f7c29ec72c22a326d61e5ac76e402",
2538
+ "0x22edc38749b59c31c9319eae7fc62143ac2fcc6c",
2539
+ "0x4bc9a771cd2c96cb2a4501f2c049402b4e7e11b3",
2540
+ "0x9a0825ca6c4c577a1202a8fae3b8f044c1d5711d",
2541
+ "0xb6ca5d1ed0ca4bf8444c9dee2d068a9f4c5e2e92",
2542
+ "0xc3610d02548cdb1a30ede87c5dc0d2ffe5b9b99f",
2543
+ "0x48c6aaa647ad5197e85797f8eb95d7b9ce1ec5b6",
2544
+ "0x8c1ab015da7e5319649afca4a551924f89eff883",
2545
+ "0x64bddab5f13a99576fca6747f0aa205422c5f708",
2546
+ "0x98785f2a43fe32f972476b86ed5a5cfaf5240f00",
2547
+ "0x9836e286fbf4f71998827d4bd6ea3b470c26457e",
2548
+ "0x8f8d2d2565bfb10608a7ce64b48e2aea7875a344",
2549
+ "0xe6df4ed7a6ccb8426cbc87bdcdd3cc6d0e6c0690",
2550
+ "0xab04d040dec929a204298d44701476968b238b69",
2551
+ "0x870f718a84843abc3e22412a9b4dc2924466d448",
2552
+ "0x12c206d3e2f0d0059420c54d33fe604ebbecc230",
2553
+ "0x7d9e91a8a47eb553cbfd323e7633c0c213327124",
2554
+ "0x2057d4f2b6ea957b63371980775b0a5505a438f0",
2555
+ "0xead5b7d86c681c036c59cd00a0390541061c69f2",
2556
+ "0x3005fade4c0df5e1cd187d7062da359416f0eb8e",
2557
+ "0xe83b5e3644b50e802a25b34b38b91a061a0d3ca8",
2558
+ "0x525232cb6ed5030d2b052b36bfc0baec96986ee3",
2559
+ "0xa251520154ca342f0b1d702bf5a56f78c982405b",
2560
+ "0xa9184bb57fd75c7c63e4c074d6319eceac6be53b",
2561
+ "0xa20fb0c9e04063eec5be286e9269028d966646fa",
2562
+ "0x169ba96d963e77a87dbe58922ecdcdd0f0cf3315",
2563
+ "0x115849ce84370f25cadcf0d348510d73837e1aa5",
2564
+ "0x37b81ab9e3ab04b9e3738e4891621205aaa31fd5",
2565
+ "0x90f57d90a7dd3114312d4c31803ad5732baf5935",
2566
+ "0xad227f63d34e7251c1d0ab65e64eeea07aee4e44",
2567
+ "0xd38e2b0b3c85ae138532f9fe1a1d5f5455866b82",
2568
+ "0xf5f4c3aa62a0f903a8529905031b1536d1cf16f8",
2569
+ "0x75632aaf83120eae9b0618f8f50773ce347de2ad",
2570
+ "0x3927af597c09774f91ef84bf765ded4109e9f8ea",
2571
+ "0xf1863bd6d60a8f4b5f583ec54009af6a044877bf",
2572
+ "0xe7b34f28c54f85533896f8827edc92edac28b17f",
2573
+ "0x1cda20f9f6a23c209d54940af3ef63b96c9b2c31",
2574
+ "0x9e55b0e43b7f7f3909af841d326b2dadfda9392e",
2575
+ "0x610b8dd6b392d010c2b6ad8d121dfc09524cb613",
2576
+ "0x1d639a688b78f3d7e57166e59d9a8886b1bfb5a5",
2577
+ "0x34d3a17095ef7def39a489d63c7c916e673c9ca2",
2578
+ "0x5d0bde4017530ed43370e332a5d30194941f73c6",
2579
+ "0xaa7afc250041e2c56a06d3b7a5295bd01929e11b",
2580
+ "0x325edd95bb016c36027ce3b9f7595af7094a9564",
2581
+ "0x47f5a6397b79deaaca5c456503e7c1a49357b942",
2582
+ "0xf5e7ffe523d0491709958490b4877e1d9de6c6f7",
2583
+ "0x64702807a1f0f9888ea22b858099d7f481227b55",
2584
+ "0xd08a4557671ca465e7bba41110a93148c90f9931",
2585
+ "0x0423b4b2acb4f219ae4a270abd5dde2360e455bb",
2586
+ "0x7b29f8c0ea709b96b72214b3de5d85c3d9570794",
2587
+ "0xeb8ebad162a9ce4cb7e2325c92b960b9df63365c",
2588
+ "0x939f95036d2e7b6d7419ec072bf9d967352204d2",
2589
+ "0x2490aa2b48022e857d44e738911787c8e2296aed",
2590
+ "0x7622f628135378f1b8d1876952de9bab80245f1c",
2591
+ "0x9d677028e0e592d48fc8a8ddc910693301a4a450",
2592
+ "0x85e410e4c535cb1709dbd36468f132037a5b2087",
2593
+ "0xd6f52e2ab58a678eced4a4d1693757b0677631c8",
2594
+ "0x24d8969e2bd6aabb6c4dfe042ef8b2cd8085217e",
2595
+ "0xda51323fe9800c8365646ad5c7ade0dd17fdc167",
2596
+ "0x53f8f390fd4f70941c5d160a964f6893c8dbceff",
2597
+ "0x72dae3560e7f4c59a305e2e739d6bd6777f738b5",
2598
+ "0x36f5e448c9cf0032551e58919cf4eabefcd742c1",
2599
+ "0x5b43b7162694de766c164202df0f0c716e4292ab",
2600
+ "0xae551d73161bac3315c5ade0e2d499a44ebe2236",
2601
+ "0x4eca8d62e07b3f3e78a8ae8725d4666da324c5a8",
2602
+ "0x40a632026febd007b0e4431a29170507f7aa3896",
2603
+ "0xd453e91f692eb5437cf345e09545827576e3225d",
2604
+ "0xbe2c69eb3d7b8ee35cbe3a28293472be31179d75",
2605
+ "0xfdf891f2b214a4c9374d26595ec6d4080262e381",
2606
+ "0xfd97600ac44b3c4e20ac1a5f23e3b18d10fa5912",
2607
+ "0x98e073b579fd483eac8f10d5bd0b32c8c3bbd7e0",
2608
+ "0xb56f19e917546a60d658110a611cf62a6877b517",
2609
+ "0xbd334a55db91617545e8d1b09941e036ad8e4200",
2610
+ "0x9f1920d0cbb63ed03376a1e09fd2851d601234c8",
2611
+ "0xe9cef9fd872a03cedf9d3e6e90309782b3724ea6",
2612
+ "0xa43682a1f129e4faf0152175cc03c44e4957e8cf",
2613
+ "0x6fbb33de5f39c690d2decb95b9ab46ed0c83e3bc",
2614
+ "0x81d4163289cf61a0eaaac4862cb88ef0f4215c29",
2615
+ "0xd248d2f09bfbe04e67fc7fea08828d6ad6d95b6d",
2616
+ "0x01ffcc7285866e5cee14c8e807cb2e8d5f61079f",
2617
+ "0xc69ae428f6049e78d445f053d2c1df879c59b34c",
2618
+ "0xaab4dfe6d735c4ac46217216fe883a39fbfe8284",
2619
+ "0xe912cac1a6641004a8803687ee7699227fdb0550",
2620
+ "0x5fdbf83c3d95d525704f61401db68cbec1b2a775",
2621
+ "0xebd7c4b3677a4480fb5f69ac2f51c51d66a19990",
2622
+ "0xe4d1fa7e6141f9131345117bdb8b5701bca67662",
2623
+ "0x685feceec46dd4e5c9b5b726f5d7550fd0eda526",
2624
+ "0x3ea8b09da72972e953541500721803a187526da6",
2625
+ "0xdbe0780a5c5a0e3b345101bbc01ac2218196d6d5",
2626
+ "0x18cd4597e06b7fe0a8cd33dda499121b3a145a8b",
2627
+ "0xb8243756956e722bff5c763971b562ac551bfbd2",
2628
+ "0xe0bf8c96795d2df0b61b3b17e7cfd7d3ba13d4be",
2629
+ "0x218a65e21eddeece7a9df38c6bbdd89f692b7da2",
2630
+ "0x53b63a30a688beb53b5dc7bd731c661d678c555c",
2631
+ "0xfce053a5e461683454bf37ad66d20344c0e3f4c0",
2632
+ "0x3e516cf3c9d4f29fae6c1324c2414dc872fc9c09",
2633
+ "0xe5f1486dc84706d2509885fe6bbc46d21ba81286",
2634
+ "0x38042d713af713f0275b00640a83654db91ea543"
2635
+ ]
2636
+ };
2637
+
2638
+ // src/hyperliquidWhaleUniverse.ts
2639
+ var fingerprint2 = (value) => (0, import_node_crypto2.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
2640
+ var validateUnique = (values, label) => {
2641
+ if (new Set(values).size !== values.length) {
2642
+ throw new Error(`Hyperliquid ${label} snapshot contains duplicates`);
2643
+ }
2644
+ };
2645
+ var validatePerpSnapshot = (value) => {
2646
+ const symbols = value.symbols.map((symbol) => symbol.trim()).filter(Boolean);
2647
+ validateUnique(symbols, "perp universe");
2648
+ if (value.schemaVersion !== 1 || value.source !== "hyperliquid_main_perp_day_notional_volume" || value.size !== 30 || symbols.length !== 30 || value.fingerprint !== fingerprint2(symbols)) {
2649
+ throw new Error("Invalid Hyperliquid perp universe snapshot");
2650
+ }
2651
+ return value;
2652
+ };
2653
+ var validateWhaleSnapshot = (value) => {
2654
+ const addresses = value.addresses.map((address) => address.toLowerCase());
2655
+ validateUnique(addresses, "whale registry");
2656
+ if (value.schemaVersion !== 2 || value.source !== "hyperliquid_structural_fills_snapshot" || value.size !== 100 || addresses.length !== 100 || addresses.some((address) => !/^0x[0-9a-f]{40}$/.test(address)) || value.fingerprint !== fingerprint2(addresses)) {
2657
+ throw new Error("Invalid Hyperliquid whale registry snapshot");
2658
+ }
2659
+ return value;
2660
+ };
2661
+ var perpSnapshot = validatePerpSnapshot(hyperliquidPerpUniverse_default);
2662
+ var whaleSnapshot = validateWhaleSnapshot(hyperliquidWhales_default);
2663
+ var perpSymbolSet = new Set(perpSnapshot.symbols);
2664
+ var whaleAddressSet = new Set(whaleSnapshot.addresses);
2665
+ var getHyperliquidPerpUniverseSnapshot = () => perpSnapshot;
2666
+ var getHyperliquidWhaleRegistrySnapshot = () => whaleSnapshot;
2667
+ var SIGNAL_TO_HYPERLIQUID_ALIASES = {
2668
+ PEPE: "kPEPE",
2669
+ "1000PEPE": "kPEPE",
2670
+ PUMPFUN: "PUMP",
2671
+ SHIB: "kSHIB",
2672
+ BONK: "kBONK"
2673
+ };
2674
+ var resolveHyperliquidPerpFromSignalSymbol = (symbol) => {
2675
+ const normalized = symbol.trim().toUpperCase().replace(/(?:USDT|USDC)$/, "");
2676
+ const candidate = SIGNAL_TO_HYPERLIQUID_ALIASES[normalized] ?? normalized;
2677
+ return perpSymbolSet.has(candidate) ? candidate : null;
2678
+ };
2679
+
2680
+ // src/strategyHelpers/hyperliquidWhaleContext.ts
2681
+ var MAX_CACHE_ENTRIES = 2048;
2682
+ var MAX_COVERAGE_SERIES_CACHE_ENTRIES = 20;
2683
+ var MAX_FLOW_SERIES_CACHE_ENTRIES = 64;
2684
+ var SERIES_CHUNK_MS = 30 * 24 * 60 * 6e4;
2685
+ var MAX_SERIES_LOOKBACK_MS = 60 * 6e4;
2686
+ var DEFAULT_MIN_COVERAGE_PCT = 0.8;
2687
+ var INTERVAL_MS = {
2688
+ "1m": 6e4,
2689
+ "5m": 5 * 6e4,
2690
+ "15m": 15 * 6e4,
2691
+ "1h": 60 * 6e4
2692
+ };
2693
+ var DEFAULT_MAX_AGE_BY_INTERVAL2 = {
2694
+ "1m": 2 * 6e4,
2695
+ "5m": 10 * 6e4,
2696
+ "15m": 30 * 6e4,
2697
+ "1h": 2 * 60 * 6e4
2698
+ };
2699
+ var hyperliquidWhaleContextUnavailable = false;
2700
+ var contextCache = /* @__PURE__ */ new Map();
2701
+ var coverageSeriesCache = /* @__PURE__ */ new Map();
2702
+ var flowSeriesCache = /* @__PURE__ */ new Map();
2703
+ var parseEnabledFlag4 = (value, env) => {
2704
+ const normalized = String(value ?? "").trim().toLowerCase();
2705
+ if (!normalized) return env !== "TEST";
2706
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
2707
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
2708
+ if (normalized === "backtest") return env === "BACKTEST";
2709
+ if (normalized === "live") return env !== "BACKTEST";
2710
+ return false;
2711
+ };
2712
+ var getMinimumCoveragePct = () => {
2713
+ const parsed = Number(process.env.HYPERLIQUID_WHALE_MIN_COVERAGE_PCT);
2714
+ return Number.isFinite(parsed) ? Math.max(0, Math.min(1, parsed)) : DEFAULT_MIN_COVERAGE_PCT;
2715
+ };
2716
+ var signalIntervalToMarketInterval2 = (value) => {
2717
+ const normalized = String(value).trim().toLowerCase();
2718
+ if (normalized === "1" || normalized === "1m") return "1m";
2719
+ if (normalized === "5" || normalized === "5m") return "5m";
2720
+ if (normalized === "60" || normalized === "1h") return "1h";
2721
+ return "15m";
2722
+ };
2723
+ var hasBaseContext3 = (signal) => Boolean(
2724
+ signal.additionalIndicators?.baseContext && typeof signal.additionalIndicators.baseContext === "object" && !Array.isArray(signal.additionalIndicators.baseContext)
2725
+ );
2726
+ var setBoundedCache = (key, value) => {
2727
+ if (contextCache.size >= MAX_CACHE_ENTRIES) {
2728
+ const oldestKey = contextCache.keys().next().value;
2729
+ if (oldestKey != null) contextCache.delete(oldestKey);
2730
+ }
2731
+ contextCache.set(key, value);
2732
+ };
2733
+ var setBoundedSeriesCache = (cache, maxEntries, key, value) => {
2734
+ if (cache.size >= maxEntries) {
2735
+ const oldestKey = cache.keys().next().value;
2736
+ if (oldestKey != null) cache.delete(oldestKey);
2737
+ }
2738
+ cache.set(key, value);
2739
+ };
2740
+ var uniqueAddressCount = (rows, field) => {
2741
+ const addresses = /* @__PURE__ */ new Set();
2742
+ for (const row of rows) {
2743
+ for (const address of row[field]) addresses.add(address);
2744
+ }
2745
+ return addresses.size;
2746
+ };
2747
+ var sumFlowField = (rows, field) => rows.reduce((sum, row) => sum + row[field], 0);
2748
+ var sliceSeriesWindow = (rows, fromMs, toMs) => {
2749
+ const lowerBound = (timestamp) => {
2750
+ let low = 0;
2751
+ let high = rows.length;
2752
+ while (low < high) {
2753
+ const middle = low + Math.floor((high - low) / 2);
2754
+ if (rows[middle].ts.getTime() < timestamp) low = middle + 1;
2755
+ else high = middle;
2756
+ }
2757
+ return low;
2758
+ };
2759
+ return rows.slice(lowerBound(fromMs), lowerBound(toMs));
2760
+ };
2761
+ var aggregateHyperliquidWhaleFlowSeries = (params) => {
2762
+ const intervalMs = INTERVAL_MS[params.interval];
2763
+ const windowStartMs = params.decisionTimeMs - intervalMs;
2764
+ const coverageRows = sliceSeriesWindow(
2765
+ params.coverageRows,
2766
+ windowStartMs,
2767
+ params.decisionTimeMs
2768
+ );
2769
+ const expectedBuckets = Math.ceil(intervalMs / 6e4);
2770
+ if (coverageRows.length !== expectedBuckets || coverageRows.some((row) => row.coveredWhales <= 0)) {
2771
+ return null;
2772
+ }
2773
+ const flowRows = sliceSeriesWindow(
2774
+ params.flowRows,
2775
+ windowStartMs,
2776
+ params.decisionTimeMs
2777
+ );
2778
+ const asOfTs = new Date(
2779
+ Math.max(...coverageRows.map((row) => row.ts.getTime()))
2780
+ );
2781
+ const coveredWhales = Math.min(
2782
+ ...coverageRows.map((row) => row.coveredWhales)
2783
+ );
2784
+ const expectedWhales = Math.max(
2785
+ ...coverageRows.map((row) => row.expectedWhales)
2786
+ );
2787
+ const coveragePct = Math.min(...coverageRows.map((row) => row.coveragePct));
2788
+ const trades = sumFlowField(flowRows, "trades");
2789
+ const whaleSides = sumFlowField(flowRows, "whaleSides");
2790
+ const buyNotionalUsd = sumFlowField(flowRows, "buyNotionalUsd");
2791
+ const sellNotionalUsd = sumFlowField(flowRows, "sellNotionalUsd");
2792
+ const positionAwareWhaleSides = sumFlowField(
2793
+ flowRows,
2794
+ "positionAwareWhaleSides"
2795
+ );
2796
+ const longEntryNotionalUsd = sumFlowField(flowRows, "longEntryNotionalUsd");
2797
+ const shortEntryNotionalUsd = sumFlowField(flowRows, "shortEntryNotionalUsd");
2798
+ const longExitNotionalUsd = sumFlowField(flowRows, "longExitNotionalUsd");
2799
+ const shortExitNotionalUsd = sumFlowField(flowRows, "shortExitNotionalUsd");
2800
+ const totalNotionalUsd = buyNotionalUsd + sellNotionalUsd;
2801
+ const totalEntryNotionalUsd = longEntryNotionalUsd + shortEntryNotionalUsd;
2802
+ const ageMs = params.decisionTimeMs - (asOfTs.getTime() + 6e4);
2803
+ return {
2804
+ symbol: params.symbol,
2805
+ interval: params.interval,
2806
+ asOfTs,
2807
+ windowEndTs: new Date(params.decisionTimeMs),
2808
+ trades,
2809
+ whaleSides,
2810
+ uniqueWhales: uniqueAddressCount(flowRows, "whaleAddresses"),
2811
+ coveredWhales,
2812
+ expectedWhales,
2813
+ coveragePct,
2814
+ buyNotionalUsd,
2815
+ sellNotionalUsd,
2816
+ netNotionalUsd: buyNotionalUsd - sellNotionalUsd,
2817
+ buySharePct: totalNotionalUsd > 0 ? buyNotionalUsd / totalNotionalUsd : null,
2818
+ positionAwareWhaleSides,
2819
+ positionAwarePct: whaleSides > 0 ? positionAwareWhaleSides / whaleSides : 0,
2820
+ longEntryWhales: uniqueAddressCount(flowRows, "longEntryWhaleAddresses"),
2821
+ shortEntryWhales: uniqueAddressCount(flowRows, "shortEntryWhaleAddresses"),
2822
+ longExitWhales: uniqueAddressCount(flowRows, "longExitWhaleAddresses"),
2823
+ shortExitWhales: uniqueAddressCount(flowRows, "shortExitWhaleAddresses"),
2824
+ longEntryNotionalUsd,
2825
+ shortEntryNotionalUsd,
2826
+ longExitNotionalUsd,
2827
+ shortExitNotionalUsd,
2828
+ entryNetNotionalUsd: longEntryNotionalUsd - shortEntryNotionalUsd,
2829
+ entryLongSharePct: totalEntryNotionalUsd > 0 ? longEntryNotionalUsd / totalEntryNotionalUsd : null,
2830
+ universeFingerprint: params.universeFingerprint,
2831
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint,
2832
+ source: positionAwareWhaleSides > 0 ? "hyperliquid_user_fills" : null,
2833
+ ageMs,
2834
+ stale: ageMs < 0 || (params.maxAgeMs != null && Number.isFinite(params.maxAgeMs) ? ageMs > params.maxAgeMs : false)
2835
+ };
2836
+ };
2837
+ var loadHyperliquidWhaleFlowAggregateFromSeries = async (params) => {
2838
+ const chunkStartMs = Math.floor(params.decisionTimeMs / SERIES_CHUNK_MS) * SERIES_CHUNK_MS;
2839
+ const fromMs = chunkStartMs - MAX_SERIES_LOOKBACK_MS;
2840
+ const toMs = chunkStartMs + SERIES_CHUNK_MS;
2841
+ const sharedKey = [
2842
+ fromMs,
2843
+ toMs,
2844
+ params.universeFingerprint,
2845
+ params.whaleRegistryFingerprint
2846
+ ].join(":");
2847
+ let pendingCoverage = coverageSeriesCache.get(sharedKey);
2848
+ if (!pendingCoverage) {
2849
+ pendingCoverage = (0, import_timescale4.getHyperliquidWhaleCoverageSeriesRows)({
2850
+ fromMs,
2851
+ toMs,
2852
+ universeFingerprint: params.universeFingerprint,
2853
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint
2854
+ });
2855
+ setBoundedSeriesCache(
2856
+ coverageSeriesCache,
2857
+ MAX_COVERAGE_SERIES_CACHE_ENTRIES,
2858
+ sharedKey,
2859
+ pendingCoverage
2860
+ );
2861
+ }
2862
+ const flowKey = `${params.symbol}:${sharedKey}`;
2863
+ let pendingFlow = flowSeriesCache.get(flowKey);
2864
+ if (!pendingFlow) {
2865
+ pendingFlow = (0, import_timescale4.getHyperliquidWhaleFlowSeriesRows)({
2866
+ symbol: params.symbol,
2867
+ fromMs,
2868
+ toMs,
2869
+ universeFingerprint: params.universeFingerprint,
2870
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint
2871
+ });
2872
+ setBoundedSeriesCache(
2873
+ flowSeriesCache,
2874
+ MAX_FLOW_SERIES_CACHE_ENTRIES,
2875
+ flowKey,
2876
+ pendingFlow
2877
+ );
2878
+ }
2879
+ const [coverageRows, flowRows] = await Promise.all([
2880
+ pendingCoverage,
2881
+ pendingFlow
2882
+ ]);
2883
+ return aggregateHyperliquidWhaleFlowSeries({
2884
+ ...params,
2885
+ coverageRows,
2886
+ flowRows
2887
+ });
2888
+ };
2889
+ var toBaseHyperliquidWhaleFlowContext = (row, minimumCoveragePct) => ({
2890
+ source: row.positionAwareWhaleSides > 0 ? "hyperliquid_user_fills" : "hyperliquid_trades",
2891
+ interval: row.interval,
2892
+ asOfTs: row.asOfTs.getTime(),
2893
+ windowEndTs: row.windowEndTs.getTime(),
2894
+ ageMs: row.ageMs,
2895
+ stale: row.stale,
2896
+ symbol: row.symbol,
2897
+ trades: row.trades,
2898
+ whaleSides: row.whaleSides,
2899
+ uniqueWhales: row.uniqueWhales,
2900
+ coveredWhales: row.coveredWhales,
2901
+ expectedWhales: row.expectedWhales,
2902
+ coveragePct: row.coveragePct,
2903
+ coverageSufficient: row.coveragePct >= minimumCoveragePct,
2904
+ buyNotionalUsd: row.buyNotionalUsd,
2905
+ sellNotionalUsd: row.sellNotionalUsd,
2906
+ netNotionalUsd: row.netNotionalUsd,
2907
+ buySharePct: row.buySharePct,
2908
+ positionAwareWhaleSides: row.positionAwareWhaleSides,
2909
+ positionAwarePct: row.positionAwarePct,
2910
+ longEntryWhales: row.longEntryWhales,
2911
+ shortEntryWhales: row.shortEntryWhales,
2912
+ longExitWhales: row.longExitWhales,
2913
+ shortExitWhales: row.shortExitWhales,
2914
+ longEntryNotionalUsd: row.longEntryNotionalUsd,
2915
+ shortEntryNotionalUsd: row.shortEntryNotionalUsd,
2916
+ longExitNotionalUsd: row.longExitNotionalUsd,
2917
+ shortExitNotionalUsd: row.shortExitNotionalUsd,
2918
+ entryNetNotionalUsd: row.entryNetNotionalUsd,
2919
+ entryLongSharePct: row.entryLongSharePct,
2920
+ universeFingerprint: row.universeFingerprint,
2921
+ whaleRegistryFingerprint: row.whaleRegistryFingerprint
2922
+ });
2923
+ var isHyperliquidWhaleContextEnabled = (env) => parseEnabledFlag4(process.env.HYPERLIQUID_WHALE_CONTEXT_ENABLED, env);
2924
+ var loadHyperliquidWhaleFlowContext = async (params) => {
2925
+ if (!(params.enabled ?? isHyperliquidWhaleContextEnabled(params.env)) || hyperliquidWhaleContextUnavailable || params.interval == null) {
2926
+ return null;
2927
+ }
2928
+ const symbol = resolveHyperliquidPerpFromSignalSymbol(params.symbol);
2929
+ if (!symbol) return null;
2930
+ const interval = params.marketInterval ?? signalIntervalToMarketInterval2(params.interval);
2931
+ const decisionTimeMs = params.timestamp + (0, import_data2.intervalToMs)(params.interval);
2932
+ const maxAgeMs = params.maxAgeMs ?? DEFAULT_MAX_AGE_BY_INTERVAL2[interval];
2933
+ const universe = getHyperliquidPerpUniverseSnapshot();
2934
+ const whales = getHyperliquidWhaleRegistrySnapshot();
2935
+ const minimumCoveragePct = getMinimumCoveragePct();
2936
+ const cacheKey = [
2937
+ symbol,
2938
+ interval,
2939
+ decisionTimeMs,
2940
+ maxAgeMs,
2941
+ universe.fingerprint,
2942
+ whales.fingerprint,
2943
+ minimumCoveragePct
2944
+ ].join(":");
2945
+ try {
2946
+ let row;
2947
+ if (params.useSeriesCache) {
2948
+ row = await loadHyperliquidWhaleFlowAggregateFromSeries({
2949
+ symbol,
2950
+ interval,
2951
+ decisionTimeMs,
2952
+ maxAgeMs,
2953
+ universeFingerprint: universe.fingerprint,
2954
+ whaleRegistryFingerprint: whales.fingerprint
2955
+ });
2956
+ } else {
2957
+ let pending = contextCache.get(cacheKey);
2958
+ if (!pending) {
2959
+ pending = (0, import_timescale4.getHyperliquidWhaleFlowAggregate)({
2960
+ symbol,
2961
+ interval,
2962
+ decisionTimeMs,
2963
+ maxAgeMs,
2964
+ universeFingerprint: universe.fingerprint,
2965
+ whaleRegistryFingerprint: whales.fingerprint
2966
+ });
2967
+ setBoundedCache(cacheKey, pending);
2968
+ }
2969
+ row = await pending;
2970
+ }
2971
+ if (!row) return null;
2972
+ return toBaseHyperliquidWhaleFlowContext(row, minimumCoveragePct);
2973
+ } catch (error) {
2974
+ hyperliquidWhaleContextUnavailable = true;
2975
+ import_logger6.logger.warn(
2976
+ "Hyperliquid whale context disabled after Timescale read failure: %s",
2977
+ String(error)
2978
+ );
2979
+ return null;
2980
+ }
2981
+ };
2982
+ var enrichSignalWithHyperliquidWhaleContext = async (params) => {
2983
+ const { signal, env } = params;
2984
+ if (signal.universe === "tradfi" || signal.interval == null || !hasBaseContext3(signal)) {
2985
+ return false;
2986
+ }
2987
+ const hyperliquidWhales = await loadHyperliquidWhaleFlowContext({
2988
+ symbol: signal.symbol,
2989
+ interval: signal.interval,
2990
+ timestamp: signal.timestamp,
2991
+ env,
2992
+ enabled: params.enabled,
2993
+ marketInterval: params.interval,
2994
+ maxAgeMs: params.maxAgeMs
2995
+ });
2996
+ if (!hyperliquidWhales) return false;
2997
+ const baseContext = signal.additionalIndicators.baseContext;
2998
+ signal.additionalIndicators = {
2999
+ ...signal.additionalIndicators,
3000
+ baseContext: {
3001
+ ...baseContext,
3002
+ participation: {
3003
+ ...baseContext.participation,
3004
+ hyperliquidWhales
3005
+ }
3006
+ }
3007
+ };
3008
+ (0, import_strategies4.refreshSignalBaseContextGateFeatures)(signal);
3009
+ return true;
3010
+ };
3011
+
2460
3012
  // src/strategyAdapters/ml.ts
2461
3013
  var defaultMlAdapter = {
2462
3014
  normalizeStrategyConfig: (strategyConfig) => strategyConfig
@@ -2506,7 +3058,7 @@ var buildMlPayload = (payload) => {
2506
3058
  };
2507
3059
 
2508
3060
  // src/connectorsRegistry.ts
2509
- var import_logger6 = require("@tradejs/infra/logger");
3061
+ var import_logger7 = require("@tradejs/infra/logger");
2510
3062
  var createConnectorRegistryState = () => ({
2511
3063
  connectorCreators: /* @__PURE__ */ new Map(),
2512
3064
  providerToConnectorName: /* @__PURE__ */ new Map(),
@@ -2558,7 +3110,7 @@ var normalizeProviders = (providers, connectorName) => {
2558
3110
  var registerProvider = (provider, connectorName, source, providerToConnectorName) => {
2559
3111
  const existing = providerToConnectorName.get(provider);
2560
3112
  if (existing && existing !== connectorName) {
2561
- import_logger6.logger.warn(
3113
+ import_logger7.logger.warn(
2562
3114
  'Skip duplicate connector provider "%s" from %s: already mapped to %s',
2563
3115
  provider,
2564
3116
  source,
@@ -2571,11 +3123,11 @@ var registerProvider = (provider, connectorName, source, providerToConnectorName
2571
3123
  var registerEntry = (entry, source, state) => {
2572
3124
  const connectorName = String(entry?.name ?? "").trim();
2573
3125
  if (!connectorName) {
2574
- import_logger6.logger.warn("Skip connector entry without name from %s", source);
3126
+ import_logger7.logger.warn("Skip connector entry without name from %s", source);
2575
3127
  return;
2576
3128
  }
2577
3129
  if (typeof entry.creator !== "function") {
2578
- import_logger6.logger.warn(
3130
+ import_logger7.logger.warn(
2579
3131
  'Skip connector entry "%s" from %s: creator must be a function',
2580
3132
  connectorName,
2581
3133
  source
@@ -2587,7 +3139,7 @@ var registerEntry = (entry, source, state) => {
2587
3139
  state.connectorCreators
2588
3140
  );
2589
3141
  if (existingByName) {
2590
- import_logger6.logger.warn(
3142
+ import_logger7.logger.warn(
2591
3143
  'Skip duplicate connector "%s" from %s: already registered as %s',
2592
3144
  connectorName,
2593
3145
  source,
@@ -2659,7 +3211,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
2659
3211
  );
2660
3212
  const pluginDefinition = extractConnectorPluginDefinition(moduleExport);
2661
3213
  if (!pluginDefinition) {
2662
- import_logger6.logger.warn(
3214
+ import_logger7.logger.warn(
2663
3215
  'Skip connector plugin "%s": export { connectorEntries } is missing',
2664
3216
  moduleName
2665
3217
  );
@@ -2667,7 +3219,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
2667
3219
  }
2668
3220
  registerEntries2(pluginDefinition.connectorEntries, moduleName, state);
2669
3221
  } catch (error) {
2670
- import_logger6.logger.warn(
3222
+ import_logger7.logger.warn(
2671
3223
  'Failed to load connector plugin "%s": %s',
2672
3224
  moduleName,
2673
3225
  String(error)
@@ -2698,7 +3250,7 @@ var getConnectorCreatorByName = async (connectorName, cwd = getTradejsProjectCwd
2698
3250
  var DEFAULT_CONNECTOR_NAME = BUILTIN_CONNECTOR_NAMES.ByBit;
2699
3251
 
2700
3252
  // src/testConnector.ts
2701
- var import_node_crypto2 = require("crypto");
3253
+ var import_node_crypto3 = require("crypto");
2702
3254
  var import_constants2 = require("@tradejs/core/constants");
2703
3255
  var import_backtest = require("@tradejs/core/backtest");
2704
3256
  var import_trade = require("@tradejs/core/trade");
@@ -3166,7 +3718,7 @@ var createTestConnector = (connector, context) => {
3166
3718
  },
3167
3719
  kline: async (options) => connector.kline(options),
3168
3720
  getResult: async () => {
3169
- const orderLogId = (0, import_node_crypto2.randomUUID)().slice(-12);
3721
+ const orderLogId = (0, import_node_crypto3.randomUUID)().slice(-12);
3170
3722
  const fullStat = fastMode ? (0, import_backtest.calculateStatsFull)(positionLog) : null;
3171
3723
  return {
3172
3724
  stat: fullStat ? {
@@ -4140,7 +4692,7 @@ var prepareTestingData = async (params) => {
4140
4692
  throw new Error(`Unknown connector: ${connectorName}`);
4141
4693
  }
4142
4694
  if (!binanceConnector || !coinbaseConnector) {
4143
- import_logger7.logger.warn(
4695
+ import_logger8.logger.warn(
4144
4696
  "Binance/Coinbase connectors are unavailable. Reusing %s for BTC references.",
4145
4697
  connectorName
4146
4698
  );
@@ -4727,6 +5279,13 @@ var testing = async ({
4727
5279
  env: "BACKTEST"
4728
5280
  })
4729
5281
  );
5282
+ await withTimeout(
5283
+ "hyperliquid whale context",
5284
+ enrichSignalWithHyperliquidWhaleContext({
5285
+ signal,
5286
+ env: "BACKTEST"
5287
+ })
5288
+ );
4730
5289
  }
4731
5290
  if (ml && signal && typeof signal !== "string" && signal.signalId) {
4732
5291
  const payload = buildMlPayload({
@@ -5152,6 +5711,13 @@ var testingGroupInSharedCandleLoop = async (tests) => {
5152
5711
  env: "BACKTEST"
5153
5712
  })
5154
5713
  );
5714
+ await withTimeout(
5715
+ "hyperliquid whale context",
5716
+ enrichSignalWithHyperliquidWhaleContext({
5717
+ signal,
5718
+ env: "BACKTEST"
5719
+ })
5720
+ );
5155
5721
  }
5156
5722
  if (test.ml && signal && typeof signal !== "string" && signal.signalId) {
5157
5723
  const payload = buildMlPayload({
@@ -5262,8 +5828,8 @@ var testingGroupInSharedCandleLoop = async (tests) => {
5262
5828
  }
5263
5829
  return results;
5264
5830
  } finally {
5265
- (0, import_strategies4.releaseStrategyIndicatorsReplayCache)(sharedIndicatorsReplayKey);
5266
- (0, import_strategies4.releaseStrategyReplayCache)(sharedIndicatorsReplayKey);
5831
+ (0, import_strategies5.releaseStrategyIndicatorsReplayCache)(sharedIndicatorsReplayKey);
5832
+ (0, import_strategies5.releaseStrategyReplayCache)(sharedIndicatorsReplayKey);
5267
5833
  }
5268
5834
  };
5269
5835
  // Annotate the CommonJS export names for ESM import in node: