@tradejs/infra 2.0.13 → 2.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,8 @@
1
1
  // src/timescale.ts
2
2
  import { Pool } from "pg";
3
+ import {
4
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
5
+ } from "@tradejs/types";
3
6
  var getPool = () => {
4
7
  if (!global.__pgPool__) {
5
8
  const host = process.env.PG_HOST || "127.0.0.1";
@@ -28,10 +31,12 @@ var candlesSchemaReady = false;
28
31
  var derivativesSchemaReady = false;
29
32
  var spreadSchemaReady = false;
30
33
  var binanceMarketSchemaReady = false;
34
+ var hyperliquidWhaleSchemaReady = false;
31
35
  var candlesSchemaReadyPromise = null;
32
36
  var derivativesSchemaReadyPromise = null;
33
37
  var spreadSchemaReadyPromise = null;
34
38
  var binanceMarketSchemaReadyPromise = null;
39
+ var hyperliquidWhaleSchemaReadyPromise = null;
35
40
  var closeTimescalePool = async () => {
36
41
  const pool = global.__pgPool__;
37
42
  if (!pool) {
@@ -42,16 +47,19 @@ var closeTimescalePool = async () => {
42
47
  derivativesSchemaReady = false;
43
48
  spreadSchemaReady = false;
44
49
  binanceMarketSchemaReady = false;
50
+ hyperliquidWhaleSchemaReady = false;
45
51
  candlesSchemaReadyPromise = null;
46
52
  derivativesSchemaReadyPromise = null;
47
53
  spreadSchemaReadyPromise = null;
48
54
  binanceMarketSchemaReadyPromise = null;
55
+ hyperliquidWhaleSchemaReadyPromise = null;
49
56
  await pool.end();
50
57
  };
51
58
  var CANDLES_SCHEMA_LOCK_KEY = 61e4;
52
59
  var DERIVATIVES_SCHEMA_LOCK_KEY = 610001;
53
60
  var SPREAD_SCHEMA_LOCK_KEY = 610002;
54
61
  var BINANCE_MARKET_SCHEMA_LOCK_KEY = 610003;
62
+ var HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY = 610004;
55
63
  var PG_SAFE_MAX_BIND_PARAMS = 3e4;
56
64
  var normalizeCandleProvider = (provider) => String(provider || "").trim().toLowerCase();
57
65
  var normalizeCandleSymbol = (symbol) => String(symbol || "").trim().toUpperCase();
@@ -629,6 +637,234 @@ var ensureBinanceMarketSchema = async () => {
629
637
  });
630
638
  await binanceMarketSchemaReadyPromise;
631
639
  };
640
+ var ensureHyperliquidWhaleSchema = async () => {
641
+ if (hyperliquidWhaleSchemaReady) return;
642
+ if (hyperliquidWhaleSchemaReadyPromise) {
643
+ await hyperliquidWhaleSchemaReadyPromise;
644
+ return;
645
+ }
646
+ hyperliquidWhaleSchemaReadyPromise = withSchemaLock(
647
+ HYPERLIQUID_WHALE_SCHEMA_LOCK_KEY,
648
+ async () => {
649
+ if (hyperliquidWhaleSchemaReady) return;
650
+ const pool = getPool();
651
+ await pool.query("CREATE EXTENSION IF NOT EXISTS timescaledb");
652
+ await pool.query(`
653
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_trade_events (
654
+ symbol text NOT NULL,
655
+ ts timestamptz NOT NULL,
656
+ tid text NOT NULL,
657
+ price double precision NOT NULL,
658
+ size double precision NOT NULL,
659
+ notional_usd double precision NOT NULL,
660
+ buyer_address text,
661
+ seller_address text,
662
+ buyer_tracked boolean NOT NULL,
663
+ seller_tracked boolean NOT NULL,
664
+ buyer_start_position double precision,
665
+ buyer_end_position double precision,
666
+ buyer_position_action text,
667
+ buyer_closed_pnl double precision,
668
+ buyer_liquidation boolean,
669
+ seller_start_position double precision,
670
+ seller_end_position double precision,
671
+ seller_position_action text,
672
+ seller_closed_pnl double precision,
673
+ seller_liquidation boolean,
674
+ universe_fingerprint text NOT NULL,
675
+ whale_registry_fingerprint text NOT NULL,
676
+ source text,
677
+ ingested_at timestamptz NOT NULL DEFAULT now(),
678
+ PRIMARY KEY (
679
+ universe_fingerprint,
680
+ whale_registry_fingerprint,
681
+ symbol,
682
+ ts,
683
+ tid
684
+ )
685
+ )
686
+ `);
687
+ await pool.query(`
688
+ ALTER TABLE hyperliquid_whale_trade_events
689
+ ADD COLUMN IF NOT EXISTS buyer_start_position double precision,
690
+ ADD COLUMN IF NOT EXISTS buyer_end_position double precision,
691
+ ADD COLUMN IF NOT EXISTS buyer_position_action text,
692
+ ADD COLUMN IF NOT EXISTS buyer_closed_pnl double precision,
693
+ ADD COLUMN IF NOT EXISTS buyer_liquidation boolean,
694
+ ADD COLUMN IF NOT EXISTS seller_start_position double precision,
695
+ ADD COLUMN IF NOT EXISTS seller_end_position double precision,
696
+ ADD COLUMN IF NOT EXISTS seller_position_action text,
697
+ ADD COLUMN IF NOT EXISTS seller_closed_pnl double precision,
698
+ ADD COLUMN IF NOT EXISTS seller_liquidation boolean
699
+ `);
700
+ await pool.query(`
701
+ SELECT create_hypertable(
702
+ 'hyperliquid_whale_trade_events',
703
+ 'ts',
704
+ if_not_exists => TRUE,
705
+ chunk_time_interval => interval '1 day'
706
+ )
707
+ `);
708
+ await pool.query(`
709
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_events_lookup_idx
710
+ ON hyperliquid_whale_trade_events (
711
+ universe_fingerprint,
712
+ whale_registry_fingerprint,
713
+ symbol,
714
+ ts DESC
715
+ )
716
+ `);
717
+ await pool.query(`
718
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_flow (
719
+ symbol text NOT NULL,
720
+ interval text NOT NULL,
721
+ ts timestamptz NOT NULL,
722
+ trades integer NOT NULL,
723
+ whale_sides integer NOT NULL,
724
+ unique_whales integer NOT NULL,
725
+ whale_addresses text[] NOT NULL DEFAULT '{}',
726
+ buy_notional_usd double precision NOT NULL,
727
+ sell_notional_usd double precision NOT NULL,
728
+ net_notional_usd double precision NOT NULL,
729
+ buy_share_pct double precision,
730
+ position_aware_whale_sides integer NOT NULL DEFAULT 0,
731
+ long_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
732
+ short_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
733
+ long_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
734
+ short_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
735
+ long_entry_notional_usd double precision NOT NULL DEFAULT 0,
736
+ short_entry_notional_usd double precision NOT NULL DEFAULT 0,
737
+ long_exit_notional_usd double precision NOT NULL DEFAULT 0,
738
+ short_exit_notional_usd double precision NOT NULL DEFAULT 0,
739
+ entry_net_notional_usd double precision NOT NULL DEFAULT 0,
740
+ entry_long_share_pct double precision,
741
+ universe_fingerprint text NOT NULL,
742
+ whale_registry_fingerprint text NOT NULL,
743
+ source text,
744
+ ingested_at timestamptz NOT NULL DEFAULT now(),
745
+ PRIMARY KEY (
746
+ universe_fingerprint,
747
+ whale_registry_fingerprint,
748
+ symbol,
749
+ interval,
750
+ ts
751
+ )
752
+ )
753
+ `);
754
+ await pool.query(`
755
+ ALTER TABLE hyperliquid_whale_flow
756
+ ADD COLUMN IF NOT EXISTS position_aware_whale_sides integer NOT NULL DEFAULT 0,
757
+ ADD COLUMN IF NOT EXISTS long_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
758
+ ADD COLUMN IF NOT EXISTS short_entry_whale_addresses text[] NOT NULL DEFAULT '{}',
759
+ ADD COLUMN IF NOT EXISTS long_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
760
+ ADD COLUMN IF NOT EXISTS short_exit_whale_addresses text[] NOT NULL DEFAULT '{}',
761
+ ADD COLUMN IF NOT EXISTS long_entry_notional_usd double precision NOT NULL DEFAULT 0,
762
+ ADD COLUMN IF NOT EXISTS short_entry_notional_usd double precision NOT NULL DEFAULT 0,
763
+ ADD COLUMN IF NOT EXISTS long_exit_notional_usd double precision NOT NULL DEFAULT 0,
764
+ ADD COLUMN IF NOT EXISTS short_exit_notional_usd double precision NOT NULL DEFAULT 0,
765
+ ADD COLUMN IF NOT EXISTS entry_net_notional_usd double precision NOT NULL DEFAULT 0,
766
+ ADD COLUMN IF NOT EXISTS entry_long_share_pct double precision
767
+ `);
768
+ await pool.query(`
769
+ SELECT create_hypertable(
770
+ 'hyperliquid_whale_flow',
771
+ 'ts',
772
+ if_not_exists => TRUE,
773
+ chunk_time_interval => interval '7 days'
774
+ )
775
+ `);
776
+ await pool.query(`
777
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_flow_lookup_idx
778
+ ON hyperliquid_whale_flow (
779
+ universe_fingerprint,
780
+ whale_registry_fingerprint,
781
+ symbol,
782
+ interval,
783
+ ts DESC
784
+ )
785
+ `);
786
+ await pool.query(`
787
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_wallet_coverage (
788
+ universe_fingerprint text NOT NULL,
789
+ whale_registry_fingerprint text NOT NULL,
790
+ address text NOT NULL,
791
+ requested_from_ts timestamptz NOT NULL,
792
+ requested_to_ts timestamptz NOT NULL,
793
+ covered_from_ts timestamptz,
794
+ covered_to_ts timestamptz,
795
+ status text NOT NULL CHECK (status IN ('complete', 'truncated', 'failed')),
796
+ fills_count integer NOT NULL DEFAULT 0,
797
+ error text,
798
+ data_model_version integer NOT NULL DEFAULT 2,
799
+ checked_at timestamptz NOT NULL DEFAULT now(),
800
+ PRIMARY KEY (
801
+ universe_fingerprint,
802
+ whale_registry_fingerprint,
803
+ address,
804
+ requested_from_ts,
805
+ requested_to_ts
806
+ )
807
+ )
808
+ `);
809
+ await pool.query(`
810
+ ALTER TABLE hyperliquid_whale_wallet_coverage
811
+ ADD COLUMN IF NOT EXISTS data_model_version integer NOT NULL DEFAULT 2
812
+ `);
813
+ await pool.query(`
814
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_wallet_coverage_lookup_idx
815
+ ON hyperliquid_whale_wallet_coverage (
816
+ universe_fingerprint,
817
+ whale_registry_fingerprint,
818
+ address,
819
+ requested_from_ts,
820
+ requested_to_ts
821
+ )
822
+ `);
823
+ await pool.query(`
824
+ CREATE TABLE IF NOT EXISTS hyperliquid_whale_coverage_1m (
825
+ ts timestamptz NOT NULL,
826
+ covered_whales integer NOT NULL,
827
+ expected_whales integer NOT NULL,
828
+ coverage_pct double precision NOT NULL,
829
+ universe_fingerprint text NOT NULL,
830
+ whale_registry_fingerprint text NOT NULL,
831
+ source text,
832
+ data_model_version integer NOT NULL DEFAULT 2,
833
+ ingested_at timestamptz NOT NULL DEFAULT now(),
834
+ PRIMARY KEY (
835
+ universe_fingerprint,
836
+ whale_registry_fingerprint,
837
+ ts
838
+ )
839
+ )
840
+ `);
841
+ await pool.query(`
842
+ ALTER TABLE hyperliquid_whale_coverage_1m
843
+ ADD COLUMN IF NOT EXISTS data_model_version integer NOT NULL DEFAULT 2
844
+ `);
845
+ await pool.query(`
846
+ SELECT create_hypertable(
847
+ 'hyperliquid_whale_coverage_1m',
848
+ 'ts',
849
+ if_not_exists => TRUE,
850
+ chunk_time_interval => interval '7 days'
851
+ )
852
+ `);
853
+ await pool.query(`
854
+ CREATE INDEX IF NOT EXISTS hyperliquid_whale_coverage_1m_lookup_idx
855
+ ON hyperliquid_whale_coverage_1m (
856
+ universe_fingerprint,
857
+ whale_registry_fingerprint,
858
+ ts DESC
859
+ )
860
+ `);
861
+ hyperliquidWhaleSchemaReady = true;
862
+ }
863
+ ).finally(() => {
864
+ hyperliquidWhaleSchemaReadyPromise = null;
865
+ });
866
+ await hyperliquidWhaleSchemaReadyPromise;
867
+ };
632
868
  async function upsertDerivatives(rows) {
633
869
  if (!rows.length) return;
634
870
  await ensureDerivativesSchema();
@@ -1207,6 +1443,491 @@ async function upsertMarketTradeFlowRows(rows) {
1207
1443
  flat
1208
1444
  );
1209
1445
  }
1446
+ async function upsertHyperliquidWhaleTradeEvents(rows) {
1447
+ if (!rows.length) return;
1448
+ await ensureHyperliquidWhaleSchema();
1449
+ const cols = [
1450
+ "symbol",
1451
+ "ts",
1452
+ "tid",
1453
+ "price",
1454
+ "size",
1455
+ "notional_usd",
1456
+ "buyer_address",
1457
+ "seller_address",
1458
+ "buyer_tracked",
1459
+ "seller_tracked",
1460
+ "buyer_start_position",
1461
+ "buyer_end_position",
1462
+ "buyer_position_action",
1463
+ "buyer_closed_pnl",
1464
+ "buyer_liquidation",
1465
+ "seller_start_position",
1466
+ "seller_end_position",
1467
+ "seller_position_action",
1468
+ "seller_closed_pnl",
1469
+ "seller_liquidation",
1470
+ "universe_fingerprint",
1471
+ "whale_registry_fingerprint",
1472
+ "source"
1473
+ ];
1474
+ const maxRows = getSafeBulkInsertRows(cols.length);
1475
+ if (rows.length > maxRows) {
1476
+ for (let index = 0; index < rows.length; index += maxRows) {
1477
+ await upsertHyperliquidWhaleTradeEvents(
1478
+ rows.slice(index, index + maxRows)
1479
+ );
1480
+ }
1481
+ return;
1482
+ }
1483
+ const valuesSql = rows.map(
1484
+ (_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
1485
+ ).join(",");
1486
+ const flat = rows.flatMap((row) => [
1487
+ row.symbol,
1488
+ row.ts,
1489
+ row.tid,
1490
+ row.price,
1491
+ row.size,
1492
+ row.notionalUsd,
1493
+ row.buyerAddress ?? null,
1494
+ row.sellerAddress ?? null,
1495
+ row.buyerTracked,
1496
+ row.sellerTracked,
1497
+ row.buyerStartPosition ?? null,
1498
+ row.buyerEndPosition ?? null,
1499
+ row.buyerPositionAction ?? null,
1500
+ row.buyerClosedPnl ?? null,
1501
+ row.buyerLiquidation ?? null,
1502
+ row.sellerStartPosition ?? null,
1503
+ row.sellerEndPosition ?? null,
1504
+ row.sellerPositionAction ?? null,
1505
+ row.sellerClosedPnl ?? null,
1506
+ row.sellerLiquidation ?? null,
1507
+ row.universeFingerprint,
1508
+ row.whaleRegistryFingerprint,
1509
+ row.source ?? null
1510
+ ]);
1511
+ await getPool().query(
1512
+ `
1513
+ INSERT INTO hyperliquid_whale_trade_events (${cols.join(",")})
1514
+ VALUES ${valuesSql}
1515
+ ON CONFLICT (
1516
+ universe_fingerprint,
1517
+ whale_registry_fingerprint,
1518
+ symbol,
1519
+ ts,
1520
+ tid
1521
+ ) DO UPDATE SET
1522
+ buyer_address = COALESCE(
1523
+ hyperliquid_whale_trade_events.buyer_address,
1524
+ EXCLUDED.buyer_address
1525
+ ),
1526
+ seller_address = COALESCE(
1527
+ hyperliquid_whale_trade_events.seller_address,
1528
+ EXCLUDED.seller_address
1529
+ ),
1530
+ buyer_tracked = hyperliquid_whale_trade_events.buyer_tracked OR EXCLUDED.buyer_tracked,
1531
+ seller_tracked = hyperliquid_whale_trade_events.seller_tracked OR EXCLUDED.seller_tracked,
1532
+ buyer_start_position = COALESCE(
1533
+ hyperliquid_whale_trade_events.buyer_start_position,
1534
+ EXCLUDED.buyer_start_position
1535
+ ),
1536
+ buyer_end_position = COALESCE(
1537
+ hyperliquid_whale_trade_events.buyer_end_position,
1538
+ EXCLUDED.buyer_end_position
1539
+ ),
1540
+ buyer_position_action = COALESCE(
1541
+ hyperliquid_whale_trade_events.buyer_position_action,
1542
+ EXCLUDED.buyer_position_action
1543
+ ),
1544
+ buyer_closed_pnl = COALESCE(
1545
+ hyperliquid_whale_trade_events.buyer_closed_pnl,
1546
+ EXCLUDED.buyer_closed_pnl
1547
+ ),
1548
+ buyer_liquidation = COALESCE(
1549
+ hyperliquid_whale_trade_events.buyer_liquidation,
1550
+ EXCLUDED.buyer_liquidation
1551
+ ),
1552
+ seller_start_position = COALESCE(
1553
+ hyperliquid_whale_trade_events.seller_start_position,
1554
+ EXCLUDED.seller_start_position
1555
+ ),
1556
+ seller_end_position = COALESCE(
1557
+ hyperliquid_whale_trade_events.seller_end_position,
1558
+ EXCLUDED.seller_end_position
1559
+ ),
1560
+ seller_position_action = COALESCE(
1561
+ hyperliquid_whale_trade_events.seller_position_action,
1562
+ EXCLUDED.seller_position_action
1563
+ ),
1564
+ seller_closed_pnl = COALESCE(
1565
+ hyperliquid_whale_trade_events.seller_closed_pnl,
1566
+ EXCLUDED.seller_closed_pnl
1567
+ ),
1568
+ seller_liquidation = COALESCE(
1569
+ hyperliquid_whale_trade_events.seller_liquidation,
1570
+ EXCLUDED.seller_liquidation
1571
+ ),
1572
+ source = EXCLUDED.source,
1573
+ ingested_at = now()
1574
+ `,
1575
+ flat
1576
+ );
1577
+ }
1578
+ async function upsertHyperliquidWhaleFlowRows(rows) {
1579
+ if (!rows.length) return;
1580
+ await ensureHyperliquidWhaleSchema();
1581
+ const cols = [
1582
+ "symbol",
1583
+ "interval",
1584
+ "ts",
1585
+ "trades",
1586
+ "whale_sides",
1587
+ "unique_whales",
1588
+ "whale_addresses",
1589
+ "buy_notional_usd",
1590
+ "sell_notional_usd",
1591
+ "net_notional_usd",
1592
+ "buy_share_pct",
1593
+ "position_aware_whale_sides",
1594
+ "long_entry_whale_addresses",
1595
+ "short_entry_whale_addresses",
1596
+ "long_exit_whale_addresses",
1597
+ "short_exit_whale_addresses",
1598
+ "long_entry_notional_usd",
1599
+ "short_entry_notional_usd",
1600
+ "long_exit_notional_usd",
1601
+ "short_exit_notional_usd",
1602
+ "entry_net_notional_usd",
1603
+ "entry_long_share_pct",
1604
+ "universe_fingerprint",
1605
+ "whale_registry_fingerprint",
1606
+ "source"
1607
+ ];
1608
+ const maxRows = getSafeBulkInsertRows(cols.length);
1609
+ if (rows.length > maxRows) {
1610
+ for (let index = 0; index < rows.length; index += maxRows) {
1611
+ await upsertHyperliquidWhaleFlowRows(rows.slice(index, index + maxRows));
1612
+ }
1613
+ return;
1614
+ }
1615
+ const valuesSql = rows.map(
1616
+ (_, rowIndex) => `(${cols.map((__, colIndex) => `$${rowIndex * cols.length + colIndex + 1}`).join(",")})`
1617
+ ).join(",");
1618
+ const flat = rows.flatMap((row) => [
1619
+ row.symbol,
1620
+ row.interval,
1621
+ row.ts,
1622
+ row.trades,
1623
+ row.whaleSides,
1624
+ row.uniqueWhales,
1625
+ row.whaleAddresses ?? [],
1626
+ row.buyNotionalUsd,
1627
+ row.sellNotionalUsd,
1628
+ row.netNotionalUsd,
1629
+ row.buySharePct ?? null,
1630
+ row.positionAwareWhaleSides,
1631
+ row.longEntryWhaleAddresses ?? [],
1632
+ row.shortEntryWhaleAddresses ?? [],
1633
+ row.longExitWhaleAddresses ?? [],
1634
+ row.shortExitWhaleAddresses ?? [],
1635
+ row.longEntryNotionalUsd,
1636
+ row.shortEntryNotionalUsd,
1637
+ row.longExitNotionalUsd,
1638
+ row.shortExitNotionalUsd,
1639
+ row.entryNetNotionalUsd,
1640
+ row.entryLongSharePct ?? null,
1641
+ row.universeFingerprint,
1642
+ row.whaleRegistryFingerprint,
1643
+ row.source ?? null
1644
+ ]);
1645
+ await getPool().query(
1646
+ `
1647
+ INSERT INTO hyperliquid_whale_flow (${cols.join(",")})
1648
+ VALUES ${valuesSql}
1649
+ ON CONFLICT (
1650
+ universe_fingerprint,
1651
+ whale_registry_fingerprint,
1652
+ symbol,
1653
+ interval,
1654
+ ts
1655
+ ) DO UPDATE SET
1656
+ trades = EXCLUDED.trades,
1657
+ whale_sides = EXCLUDED.whale_sides,
1658
+ unique_whales = EXCLUDED.unique_whales,
1659
+ whale_addresses = EXCLUDED.whale_addresses,
1660
+ buy_notional_usd = EXCLUDED.buy_notional_usd,
1661
+ sell_notional_usd = EXCLUDED.sell_notional_usd,
1662
+ net_notional_usd = EXCLUDED.net_notional_usd,
1663
+ buy_share_pct = EXCLUDED.buy_share_pct,
1664
+ position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
1665
+ long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
1666
+ short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
1667
+ long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
1668
+ short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
1669
+ long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
1670
+ short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
1671
+ long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
1672
+ short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
1673
+ entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
1674
+ entry_long_share_pct = EXCLUDED.entry_long_share_pct,
1675
+ source = EXCLUDED.source,
1676
+ ingested_at = now()
1677
+ `,
1678
+ flat
1679
+ );
1680
+ }
1681
+ async function rebuildHyperliquidWhaleFlowRows(params) {
1682
+ await ensureHyperliquidWhaleSchema();
1683
+ const client = await getPool().connect();
1684
+ try {
1685
+ await client.query("BEGIN");
1686
+ const result = await client.query(
1687
+ `
1688
+ WITH source_events AS (
1689
+ SELECT *
1690
+ FROM hyperliquid_whale_trade_events
1691
+ WHERE universe_fingerprint = $1
1692
+ AND whale_registry_fingerprint = $2
1693
+ AND ts >= to_timestamp($3/1000.0)
1694
+ AND ts < to_timestamp($4/1000.0)
1695
+ ), metrics AS (
1696
+ SELECT
1697
+ symbol,
1698
+ date_trunc('minute', ts) AS bucket_ts,
1699
+ COUNT(*)::int AS trades,
1700
+ SUM(buyer_tracked::int + seller_tracked::int)::int AS whale_sides,
1701
+ SUM(CASE WHEN buyer_tracked THEN notional_usd ELSE 0 END) AS buy_notional_usd,
1702
+ SUM(CASE WHEN seller_tracked THEN notional_usd ELSE 0 END) AS sell_notional_usd
1703
+ FROM source_events
1704
+ GROUP BY symbol, date_trunc('minute', ts)
1705
+ ), position_legs AS (
1706
+ SELECT
1707
+ symbol,
1708
+ ts,
1709
+ price,
1710
+ buyer_address AS whale_address,
1711
+ buyer_start_position AS start_position,
1712
+ buyer_end_position AS end_position
1713
+ FROM source_events
1714
+ WHERE buyer_tracked
1715
+ AND buyer_address IS NOT NULL
1716
+ AND buyer_start_position IS NOT NULL
1717
+ AND buyer_end_position IS NOT NULL
1718
+ UNION ALL
1719
+ SELECT
1720
+ symbol,
1721
+ ts,
1722
+ price,
1723
+ seller_address AS whale_address,
1724
+ seller_start_position AS start_position,
1725
+ seller_end_position AS end_position
1726
+ FROM source_events
1727
+ WHERE seller_tracked
1728
+ AND seller_address IS NOT NULL
1729
+ AND seller_start_position IS NOT NULL
1730
+ AND seller_end_position IS NOT NULL
1731
+ ), classified_legs AS (
1732
+ SELECT
1733
+ *,
1734
+ GREATEST(
1735
+ GREATEST(end_position, 0) - GREATEST(start_position, 0),
1736
+ 0
1737
+ ) AS long_entry_size,
1738
+ GREATEST(
1739
+ GREATEST(-end_position, 0) - GREATEST(-start_position, 0),
1740
+ 0
1741
+ ) AS short_entry_size,
1742
+ GREATEST(
1743
+ GREATEST(start_position, 0) - GREATEST(end_position, 0),
1744
+ 0
1745
+ ) AS long_exit_size,
1746
+ GREATEST(
1747
+ GREATEST(-start_position, 0) - GREATEST(-end_position, 0),
1748
+ 0
1749
+ ) AS short_exit_size
1750
+ FROM position_legs
1751
+ ), position_metrics AS (
1752
+ SELECT
1753
+ symbol,
1754
+ date_trunc('minute', ts) AS bucket_ts,
1755
+ COUNT(*)::int AS position_aware_whale_sides,
1756
+ SUM(long_entry_size * price) AS long_entry_notional_usd,
1757
+ SUM(short_entry_size * price) AS short_entry_notional_usd,
1758
+ SUM(long_exit_size * price) AS long_exit_notional_usd,
1759
+ SUM(short_exit_size * price) AS short_exit_notional_usd,
1760
+ COALESCE(
1761
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1762
+ FILTER (WHERE long_entry_size > 0),
1763
+ '{}'
1764
+ ) AS long_entry_whale_addresses,
1765
+ COALESCE(
1766
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1767
+ FILTER (WHERE short_entry_size > 0),
1768
+ '{}'
1769
+ ) AS short_entry_whale_addresses,
1770
+ COALESCE(
1771
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1772
+ FILTER (WHERE long_exit_size > 0),
1773
+ '{}'
1774
+ ) AS long_exit_whale_addresses,
1775
+ COALESCE(
1776
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address)
1777
+ FILTER (WHERE short_exit_size > 0),
1778
+ '{}'
1779
+ ) AS short_exit_whale_addresses
1780
+ FROM classified_legs
1781
+ GROUP BY symbol, date_trunc('minute', ts)
1782
+ ), addresses AS (
1783
+ SELECT
1784
+ symbol,
1785
+ date_trunc('minute', ts) AS bucket_ts,
1786
+ ARRAY_AGG(DISTINCT whale_address ORDER BY whale_address) AS whale_addresses
1787
+ FROM source_events
1788
+ CROSS JOIN LATERAL UNNEST(ARRAY[
1789
+ CASE WHEN buyer_tracked THEN buyer_address END,
1790
+ CASE WHEN seller_tracked THEN seller_address END
1791
+ ]) AS expanded(whale_address)
1792
+ WHERE whale_address IS NOT NULL
1793
+ GROUP BY symbol, date_trunc('minute', ts)
1794
+ )
1795
+ INSERT INTO hyperliquid_whale_flow (
1796
+ symbol,
1797
+ interval,
1798
+ ts,
1799
+ trades,
1800
+ whale_sides,
1801
+ unique_whales,
1802
+ whale_addresses,
1803
+ buy_notional_usd,
1804
+ sell_notional_usd,
1805
+ net_notional_usd,
1806
+ buy_share_pct,
1807
+ position_aware_whale_sides,
1808
+ long_entry_whale_addresses,
1809
+ short_entry_whale_addresses,
1810
+ long_exit_whale_addresses,
1811
+ short_exit_whale_addresses,
1812
+ long_entry_notional_usd,
1813
+ short_entry_notional_usd,
1814
+ long_exit_notional_usd,
1815
+ short_exit_notional_usd,
1816
+ entry_net_notional_usd,
1817
+ entry_long_share_pct,
1818
+ universe_fingerprint,
1819
+ whale_registry_fingerprint,
1820
+ source
1821
+ )
1822
+ SELECT
1823
+ metrics.symbol,
1824
+ '1m',
1825
+ metrics.bucket_ts,
1826
+ metrics.trades,
1827
+ metrics.whale_sides,
1828
+ COALESCE(CARDINALITY(addresses.whale_addresses), 0),
1829
+ COALESCE(addresses.whale_addresses, '{}'),
1830
+ metrics.buy_notional_usd,
1831
+ metrics.sell_notional_usd,
1832
+ metrics.buy_notional_usd - metrics.sell_notional_usd,
1833
+ CASE
1834
+ WHEN metrics.buy_notional_usd + metrics.sell_notional_usd > 0
1835
+ THEN metrics.buy_notional_usd /
1836
+ (metrics.buy_notional_usd + metrics.sell_notional_usd)
1837
+ ELSE NULL
1838
+ END,
1839
+ COALESCE(position_metrics.position_aware_whale_sides, 0),
1840
+ COALESCE(position_metrics.long_entry_whale_addresses, '{}'),
1841
+ COALESCE(position_metrics.short_entry_whale_addresses, '{}'),
1842
+ COALESCE(position_metrics.long_exit_whale_addresses, '{}'),
1843
+ COALESCE(position_metrics.short_exit_whale_addresses, '{}'),
1844
+ COALESCE(position_metrics.long_entry_notional_usd, 0),
1845
+ COALESCE(position_metrics.short_entry_notional_usd, 0),
1846
+ COALESCE(position_metrics.long_exit_notional_usd, 0),
1847
+ COALESCE(position_metrics.short_exit_notional_usd, 0),
1848
+ COALESCE(position_metrics.long_entry_notional_usd, 0) -
1849
+ COALESCE(position_metrics.short_entry_notional_usd, 0),
1850
+ CASE
1851
+ WHEN COALESCE(position_metrics.long_entry_notional_usd, 0) +
1852
+ COALESCE(position_metrics.short_entry_notional_usd, 0) > 0
1853
+ THEN COALESCE(position_metrics.long_entry_notional_usd, 0) /
1854
+ (
1855
+ COALESCE(position_metrics.long_entry_notional_usd, 0) +
1856
+ COALESCE(position_metrics.short_entry_notional_usd, 0)
1857
+ )
1858
+ ELSE NULL
1859
+ END,
1860
+ $1,
1861
+ $2,
1862
+ CASE
1863
+ WHEN COALESCE(position_metrics.position_aware_whale_sides, 0) > 0
1864
+ THEN 'hyperliquid_user_fills'
1865
+ ELSE 'hyperliquid_trades'
1866
+ END
1867
+ FROM metrics
1868
+ LEFT JOIN addresses USING (symbol, bucket_ts)
1869
+ LEFT JOIN position_metrics USING (symbol, bucket_ts)
1870
+ ON CONFLICT (
1871
+ universe_fingerprint,
1872
+ whale_registry_fingerprint,
1873
+ symbol,
1874
+ interval,
1875
+ ts
1876
+ ) DO UPDATE SET
1877
+ trades = EXCLUDED.trades,
1878
+ whale_sides = EXCLUDED.whale_sides,
1879
+ unique_whales = EXCLUDED.unique_whales,
1880
+ whale_addresses = EXCLUDED.whale_addresses,
1881
+ buy_notional_usd = EXCLUDED.buy_notional_usd,
1882
+ sell_notional_usd = EXCLUDED.sell_notional_usd,
1883
+ net_notional_usd = EXCLUDED.net_notional_usd,
1884
+ buy_share_pct = EXCLUDED.buy_share_pct,
1885
+ position_aware_whale_sides = EXCLUDED.position_aware_whale_sides,
1886
+ long_entry_whale_addresses = EXCLUDED.long_entry_whale_addresses,
1887
+ short_entry_whale_addresses = EXCLUDED.short_entry_whale_addresses,
1888
+ long_exit_whale_addresses = EXCLUDED.long_exit_whale_addresses,
1889
+ short_exit_whale_addresses = EXCLUDED.short_exit_whale_addresses,
1890
+ long_entry_notional_usd = EXCLUDED.long_entry_notional_usd,
1891
+ short_entry_notional_usd = EXCLUDED.short_entry_notional_usd,
1892
+ long_exit_notional_usd = EXCLUDED.long_exit_notional_usd,
1893
+ short_exit_notional_usd = EXCLUDED.short_exit_notional_usd,
1894
+ entry_net_notional_usd = EXCLUDED.entry_net_notional_usd,
1895
+ entry_long_share_pct = EXCLUDED.entry_long_share_pct,
1896
+ source = EXCLUDED.source,
1897
+ ingested_at = now()
1898
+ RETURNING 1
1899
+ `,
1900
+ [
1901
+ params.universeFingerprint,
1902
+ params.whaleRegistryFingerprint,
1903
+ params.fromMs,
1904
+ params.toMs
1905
+ ]
1906
+ );
1907
+ if (params.deleteEventsBeforeMs != null) {
1908
+ await client.query(
1909
+ `
1910
+ DELETE FROM hyperliquid_whale_trade_events
1911
+ WHERE universe_fingerprint = $1
1912
+ AND whale_registry_fingerprint = $2
1913
+ AND ts < to_timestamp($3/1000.0)
1914
+ `,
1915
+ [
1916
+ params.universeFingerprint,
1917
+ params.whaleRegistryFingerprint,
1918
+ params.deleteEventsBeforeMs
1919
+ ]
1920
+ );
1921
+ }
1922
+ await client.query("COMMIT");
1923
+ return result.rowCount ?? 0;
1924
+ } catch (error) {
1925
+ await client.query("ROLLBACK");
1926
+ throw error;
1927
+ } finally {
1928
+ client.release();
1929
+ }
1930
+ }
1210
1931
  async function upsertMarketBreadthRows(rows) {
1211
1932
  if (!rows.length) return;
1212
1933
  await ensureBinanceMarketSchema();
@@ -1730,6 +2451,523 @@ async function getLatestMarketTradeFlow(params) {
1730
2451
  stale: ageMs == null || params.maxAgeMs != null && ageMs > params.maxAgeMs
1731
2452
  };
1732
2453
  }
2454
+ var HYPERLIQUID_CONTEXT_INTERVAL_MS = {
2455
+ "1m": 6e4,
2456
+ "5m": 5 * 6e4,
2457
+ "15m": 15 * 6e4,
2458
+ "1h": 60 * 6e4
2459
+ };
2460
+ async function getHyperliquidWhaleWalletCoverage(params) {
2461
+ await ensureHyperliquidWhaleSchema();
2462
+ const result = await getPool().query(
2463
+ `
2464
+ SELECT
2465
+ status,
2466
+ covered_from_ts,
2467
+ covered_to_ts,
2468
+ fills_count,
2469
+ error,
2470
+ checked_at
2471
+ FROM hyperliquid_whale_wallet_coverage
2472
+ WHERE universe_fingerprint = $1
2473
+ AND whale_registry_fingerprint = $2
2474
+ AND address = $3
2475
+ AND data_model_version = $6
2476
+ AND (
2477
+ (
2478
+ covered_from_ts <= to_timestamp($4/1000.0)
2479
+ AND covered_to_ts >= to_timestamp($5/1000.0)
2480
+ )
2481
+ OR (
2482
+ requested_from_ts = to_timestamp($4/1000.0)
2483
+ AND requested_to_ts = to_timestamp($5/1000.0)
2484
+ )
2485
+ )
2486
+ ORDER BY checked_at DESC
2487
+ LIMIT 1
2488
+ `,
2489
+ [
2490
+ params.universeFingerprint,
2491
+ params.whaleRegistryFingerprint,
2492
+ params.address.toLowerCase(),
2493
+ params.fromMs,
2494
+ params.toMs,
2495
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2496
+ ]
2497
+ );
2498
+ const row = result.rows[0];
2499
+ if (!row) return null;
2500
+ return {
2501
+ status: row.covered_from_ts != null && row.covered_to_ts != null && new Date(row.covered_from_ts).getTime() <= params.fromMs && new Date(row.covered_to_ts).getTime() >= params.toMs ? "complete" : String(row.status),
2502
+ coveredFromMs: row.covered_from_ts == null ? null : new Date(row.covered_from_ts).getTime(),
2503
+ coveredToMs: row.covered_to_ts == null ? null : new Date(row.covered_to_ts).getTime(),
2504
+ fillsCount: Number(row.fills_count) || 0,
2505
+ error: row.error == null ? null : String(row.error),
2506
+ checkedAt: new Date(row.checked_at)
2507
+ };
2508
+ }
2509
+ async function upsertHyperliquidWhaleWalletCoverage(params) {
2510
+ await ensureHyperliquidWhaleSchema();
2511
+ await getPool().query(
2512
+ `
2513
+ INSERT INTO hyperliquid_whale_wallet_coverage (
2514
+ universe_fingerprint,
2515
+ whale_registry_fingerprint,
2516
+ address,
2517
+ requested_from_ts,
2518
+ requested_to_ts,
2519
+ covered_from_ts,
2520
+ covered_to_ts,
2521
+ status,
2522
+ fills_count,
2523
+ error,
2524
+ data_model_version
2525
+ ) VALUES (
2526
+ $1,
2527
+ $2,
2528
+ $3,
2529
+ to_timestamp($4/1000.0),
2530
+ to_timestamp($5/1000.0),
2531
+ CASE WHEN $6::double precision IS NULL THEN NULL ELSE to_timestamp($6/1000.0) END,
2532
+ CASE WHEN $7::double precision IS NULL THEN NULL ELSE to_timestamp($7/1000.0) END,
2533
+ $8,
2534
+ $9,
2535
+ $10,
2536
+ $11
2537
+ )
2538
+ ON CONFLICT (
2539
+ universe_fingerprint,
2540
+ whale_registry_fingerprint,
2541
+ address,
2542
+ requested_from_ts,
2543
+ requested_to_ts
2544
+ ) DO UPDATE SET
2545
+ covered_from_ts = EXCLUDED.covered_from_ts,
2546
+ covered_to_ts = EXCLUDED.covered_to_ts,
2547
+ status = EXCLUDED.status,
2548
+ fills_count = EXCLUDED.fills_count,
2549
+ error = EXCLUDED.error,
2550
+ data_model_version = EXCLUDED.data_model_version,
2551
+ checked_at = now()
2552
+ `,
2553
+ [
2554
+ params.universeFingerprint,
2555
+ params.whaleRegistryFingerprint,
2556
+ params.address.toLowerCase(),
2557
+ params.fromMs,
2558
+ params.toMs,
2559
+ params.coveredFromMs,
2560
+ params.coveredToMs,
2561
+ params.status,
2562
+ params.fillsCount,
2563
+ params.error ?? null,
2564
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2565
+ ]
2566
+ );
2567
+ }
2568
+ async function rebuildHyperliquidWhaleCoverageRows(params) {
2569
+ await ensureHyperliquidWhaleSchema();
2570
+ if (params.toMs <= params.fromMs) return 0;
2571
+ const result = await getPool().query(
2572
+ `
2573
+ WITH buckets AS (
2574
+ SELECT generate_series(
2575
+ to_timestamp($3/1000.0),
2576
+ to_timestamp($4/1000.0) - interval '1 minute',
2577
+ interval '1 minute'
2578
+ ) AS ts
2579
+ ), coverage AS (
2580
+ SELECT
2581
+ buckets.ts,
2582
+ COUNT(DISTINCT wallets.address)::int AS covered_whales
2583
+ FROM buckets
2584
+ LEFT JOIN hyperliquid_whale_wallet_coverage wallets
2585
+ ON wallets.universe_fingerprint = $1
2586
+ AND wallets.whale_registry_fingerprint = $2
2587
+ AND wallets.data_model_version = $6
2588
+ AND wallets.status IN ('complete', 'truncated')
2589
+ AND wallets.covered_from_ts <= buckets.ts
2590
+ AND wallets.covered_to_ts >= buckets.ts + interval '1 minute'
2591
+ GROUP BY buckets.ts
2592
+ )
2593
+ INSERT INTO hyperliquid_whale_coverage_1m (
2594
+ ts,
2595
+ covered_whales,
2596
+ expected_whales,
2597
+ coverage_pct,
2598
+ universe_fingerprint,
2599
+ whale_registry_fingerprint,
2600
+ source
2601
+ ,data_model_version
2602
+ )
2603
+ SELECT
2604
+ ts,
2605
+ covered_whales,
2606
+ $5,
2607
+ CASE WHEN $5 > 0 THEN covered_whales::double precision / $5 ELSE 0 END,
2608
+ $1,
2609
+ $2,
2610
+ 'hyperliquid_user_fills',
2611
+ $6
2612
+ FROM coverage
2613
+ ON CONFLICT (
2614
+ universe_fingerprint,
2615
+ whale_registry_fingerprint,
2616
+ ts
2617
+ ) DO UPDATE SET
2618
+ covered_whales = EXCLUDED.covered_whales,
2619
+ expected_whales = EXCLUDED.expected_whales,
2620
+ coverage_pct = EXCLUDED.coverage_pct,
2621
+ source = EXCLUDED.source,
2622
+ data_model_version = EXCLUDED.data_model_version,
2623
+ ingested_at = now()
2624
+ RETURNING 1
2625
+ `,
2626
+ [
2627
+ params.universeFingerprint,
2628
+ params.whaleRegistryFingerprint,
2629
+ params.fromMs,
2630
+ params.toMs,
2631
+ params.expectedWhales,
2632
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2633
+ ]
2634
+ );
2635
+ return result.rowCount ?? 0;
2636
+ }
2637
+ async function upsertHyperliquidWhaleCoverageRows(rows) {
2638
+ if (!rows.length) return;
2639
+ await ensureHyperliquidWhaleSchema();
2640
+ const cols = [
2641
+ "ts",
2642
+ "covered_whales",
2643
+ "expected_whales",
2644
+ "coverage_pct",
2645
+ "universe_fingerprint",
2646
+ "whale_registry_fingerprint",
2647
+ "source",
2648
+ "data_model_version"
2649
+ ];
2650
+ const maxRows = Math.floor(PG_SAFE_MAX_BIND_PARAMS / cols.length);
2651
+ if (rows.length > maxRows) {
2652
+ for (let index = 0; index < rows.length; index += maxRows) {
2653
+ await upsertHyperliquidWhaleCoverageRows(
2654
+ rows.slice(index, index + maxRows)
2655
+ );
2656
+ }
2657
+ return;
2658
+ }
2659
+ const values = [];
2660
+ const tuples = rows.map((row, rowIndex) => {
2661
+ const offset = rowIndex * cols.length;
2662
+ values.push(
2663
+ row.ts,
2664
+ row.coveredWhales,
2665
+ row.expectedWhales,
2666
+ row.coveragePct,
2667
+ row.universeFingerprint,
2668
+ row.whaleRegistryFingerprint,
2669
+ row.source ?? null,
2670
+ row.dataModelVersion ?? HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2671
+ );
2672
+ return `(${cols.map((_, colIndex) => `$${offset + colIndex + 1}`).join(",")})`;
2673
+ });
2674
+ await getPool().query(
2675
+ `
2676
+ INSERT INTO hyperliquid_whale_coverage_1m (${cols.join(",")})
2677
+ VALUES ${tuples.join(",")}
2678
+ ON CONFLICT (
2679
+ universe_fingerprint,
2680
+ whale_registry_fingerprint,
2681
+ ts
2682
+ ) DO UPDATE SET
2683
+ covered_whales = EXCLUDED.covered_whales,
2684
+ expected_whales = EXCLUDED.expected_whales,
2685
+ coverage_pct = EXCLUDED.coverage_pct,
2686
+ source = EXCLUDED.source,
2687
+ data_model_version = EXCLUDED.data_model_version,
2688
+ ingested_at = now()
2689
+ `,
2690
+ values
2691
+ );
2692
+ }
2693
+ async function getHyperliquidWhaleCoverageSeriesRows(params) {
2694
+ await ensureHyperliquidWhaleSchema();
2695
+ const result = await getPool().query(
2696
+ `
2697
+ SELECT
2698
+ ts,
2699
+ covered_whales,
2700
+ expected_whales,
2701
+ coverage_pct
2702
+ FROM hyperliquid_whale_coverage_1m
2703
+ WHERE universe_fingerprint = $1
2704
+ AND whale_registry_fingerprint = $2
2705
+ AND data_model_version = $3
2706
+ AND ts >= to_timestamp($4/1000.0)
2707
+ AND ts < to_timestamp($5/1000.0)
2708
+ ORDER BY ts
2709
+ `,
2710
+ [
2711
+ params.universeFingerprint,
2712
+ params.whaleRegistryFingerprint,
2713
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION,
2714
+ params.fromMs,
2715
+ params.toMs
2716
+ ]
2717
+ );
2718
+ return result.rows.map((row) => ({
2719
+ ts: new Date(row.ts),
2720
+ coveredWhales: Number(row.covered_whales) || 0,
2721
+ expectedWhales: Number(row.expected_whales) || 0,
2722
+ coveragePct: Number(row.coverage_pct) || 0
2723
+ }));
2724
+ }
2725
+ async function getHyperliquidWhaleFlowSeriesRows(params) {
2726
+ await ensureHyperliquidWhaleSchema();
2727
+ const result = await getPool().query(
2728
+ `
2729
+ SELECT
2730
+ ts,
2731
+ trades,
2732
+ whale_sides,
2733
+ whale_addresses,
2734
+ buy_notional_usd,
2735
+ sell_notional_usd,
2736
+ position_aware_whale_sides,
2737
+ long_entry_whale_addresses,
2738
+ short_entry_whale_addresses,
2739
+ long_exit_whale_addresses,
2740
+ short_exit_whale_addresses,
2741
+ long_entry_notional_usd,
2742
+ short_entry_notional_usd,
2743
+ long_exit_notional_usd,
2744
+ short_exit_notional_usd
2745
+ FROM hyperliquid_whale_flow
2746
+ WHERE symbol = $1
2747
+ AND interval = '1m'
2748
+ AND universe_fingerprint = $2
2749
+ AND whale_registry_fingerprint = $3
2750
+ AND ts >= to_timestamp($4/1000.0)
2751
+ AND ts < to_timestamp($5/1000.0)
2752
+ ORDER BY ts
2753
+ `,
2754
+ [
2755
+ params.symbol,
2756
+ params.universeFingerprint,
2757
+ params.whaleRegistryFingerprint,
2758
+ params.fromMs,
2759
+ params.toMs
2760
+ ]
2761
+ );
2762
+ return result.rows.map((row) => ({
2763
+ ts: new Date(row.ts),
2764
+ trades: Number(row.trades) || 0,
2765
+ whaleSides: Number(row.whale_sides) || 0,
2766
+ whaleAddresses: Array.isArray(row.whale_addresses) ? row.whale_addresses.map(String) : [],
2767
+ buyNotionalUsd: Number(row.buy_notional_usd) || 0,
2768
+ sellNotionalUsd: Number(row.sell_notional_usd) || 0,
2769
+ positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
2770
+ longEntryWhaleAddresses: Array.isArray(row.long_entry_whale_addresses) ? row.long_entry_whale_addresses.map(String) : [],
2771
+ shortEntryWhaleAddresses: Array.isArray(row.short_entry_whale_addresses) ? row.short_entry_whale_addresses.map(String) : [],
2772
+ longExitWhaleAddresses: Array.isArray(row.long_exit_whale_addresses) ? row.long_exit_whale_addresses.map(String) : [],
2773
+ shortExitWhaleAddresses: Array.isArray(row.short_exit_whale_addresses) ? row.short_exit_whale_addresses.map(String) : [],
2774
+ longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
2775
+ shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
2776
+ longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
2777
+ shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0
2778
+ }));
2779
+ }
2780
+ async function getHyperliquidWhaleFlowAggregate(params) {
2781
+ await ensureHyperliquidWhaleSchema();
2782
+ const intervalMs = HYPERLIQUID_CONTEXT_INTERVAL_MS[params.interval];
2783
+ const expectedBuckets = Math.ceil(intervalMs / 6e4);
2784
+ const res = await getPool().query(
2785
+ `
2786
+ WITH coverage_rows AS (
2787
+ SELECT *
2788
+ FROM hyperliquid_whale_coverage_1m
2789
+ WHERE universe_fingerprint = $2
2790
+ AND whale_registry_fingerprint = $3
2791
+ AND data_model_version = $6
2792
+ AND ts >= to_timestamp(
2793
+ ($4::double precision - $5::double precision) / 1000.0
2794
+ )
2795
+ AND ts < to_timestamp($4/1000.0)
2796
+ ), coverage_summary AS (
2797
+ SELECT
2798
+ COUNT(*)::int AS coverage_buckets,
2799
+ MAX(ts) AS coverage_as_of_ts,
2800
+ MIN(covered_whales)::int AS covered_whales,
2801
+ MAX(expected_whales)::int AS expected_whales,
2802
+ MIN(coverage_pct) AS coverage_pct
2803
+ FROM coverage_rows
2804
+ ), window_rows AS (
2805
+ SELECT *
2806
+ FROM hyperliquid_whale_flow
2807
+ WHERE symbol = $1
2808
+ AND interval = '1m'
2809
+ AND universe_fingerprint = $2
2810
+ AND whale_registry_fingerprint = $3
2811
+ AND ts >= to_timestamp(
2812
+ ($4::double precision - $5::double precision) / 1000.0
2813
+ )
2814
+ AND ts < to_timestamp($4/1000.0)
2815
+ ), unique_addresses AS (
2816
+ SELECT COUNT(DISTINCT address)::int AS unique_whales
2817
+ FROM window_rows
2818
+ CROSS JOIN LATERAL UNNEST(whale_addresses) AS expanded(address)
2819
+ ), directional_counts AS (
2820
+ SELECT
2821
+ (
2822
+ SELECT COUNT(DISTINCT address)::int
2823
+ FROM window_rows
2824
+ CROSS JOIN LATERAL UNNEST(long_entry_whale_addresses) AS expanded(address)
2825
+ ) AS long_entry_whales,
2826
+ (
2827
+ SELECT COUNT(DISTINCT address)::int
2828
+ FROM window_rows
2829
+ CROSS JOIN LATERAL UNNEST(short_entry_whale_addresses) AS expanded(address)
2830
+ ) AS short_entry_whales,
2831
+ (
2832
+ SELECT COUNT(DISTINCT address)::int
2833
+ FROM window_rows
2834
+ CROSS JOIN LATERAL UNNEST(long_exit_whale_addresses) AS expanded(address)
2835
+ ) AS long_exit_whales,
2836
+ (
2837
+ SELECT COUNT(DISTINCT address)::int
2838
+ FROM window_rows
2839
+ CROSS JOIN LATERAL UNNEST(short_exit_whale_addresses) AS expanded(address)
2840
+ ) AS short_exit_whales
2841
+ )
2842
+ SELECT
2843
+ $1::text AS symbol,
2844
+ coverage_summary.coverage_as_of_ts AS as_of_ts,
2845
+ coverage_summary.coverage_buckets,
2846
+ coverage_summary.covered_whales,
2847
+ coverage_summary.expected_whales,
2848
+ coverage_summary.coverage_pct,
2849
+ COALESCE((SELECT SUM(trades) FROM window_rows), 0)::int AS trades,
2850
+ COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0)::int AS whale_sides,
2851
+ COALESCE((SELECT unique_whales FROM unique_addresses), 0)::int AS unique_whales,
2852
+ COALESCE((SELECT SUM(buy_notional_usd) FROM window_rows), 0) AS buy_notional_usd,
2853
+ COALESCE((SELECT SUM(sell_notional_usd) FROM window_rows), 0) AS sell_notional_usd,
2854
+ COALESCE((SELECT SUM(net_notional_usd) FROM window_rows), 0) AS net_notional_usd,
2855
+ CASE
2856
+ WHEN COALESCE((SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows), 0) > 0
2857
+ THEN (SELECT SUM(buy_notional_usd) FROM window_rows) /
2858
+ (SELECT SUM(buy_notional_usd + sell_notional_usd) FROM window_rows)
2859
+ ELSE NULL
2860
+ END AS buy_share_pct,
2861
+ COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::int
2862
+ AS position_aware_whale_sides,
2863
+ CASE
2864
+ WHEN COALESCE((SELECT SUM(whale_sides) FROM window_rows), 0) > 0
2865
+ THEN COALESCE((SELECT SUM(position_aware_whale_sides) FROM window_rows), 0)::double precision /
2866
+ (SELECT SUM(whale_sides) FROM window_rows)
2867
+ ELSE 0
2868
+ END AS position_aware_pct,
2869
+ COALESCE((SELECT long_entry_whales FROM directional_counts), 0)::int AS long_entry_whales,
2870
+ COALESCE((SELECT short_entry_whales FROM directional_counts), 0)::int AS short_entry_whales,
2871
+ COALESCE((SELECT long_exit_whales FROM directional_counts), 0)::int AS long_exit_whales,
2872
+ COALESCE((SELECT short_exit_whales FROM directional_counts), 0)::int AS short_exit_whales,
2873
+ COALESCE((SELECT SUM(long_entry_notional_usd) FROM window_rows), 0)
2874
+ AS long_entry_notional_usd,
2875
+ COALESCE((SELECT SUM(short_entry_notional_usd) FROM window_rows), 0)
2876
+ AS short_entry_notional_usd,
2877
+ COALESCE((SELECT SUM(long_exit_notional_usd) FROM window_rows), 0)
2878
+ AS long_exit_notional_usd,
2879
+ COALESCE((SELECT SUM(short_exit_notional_usd) FROM window_rows), 0)
2880
+ AS short_exit_notional_usd,
2881
+ COALESCE((SELECT SUM(entry_net_notional_usd) FROM window_rows), 0)
2882
+ AS entry_net_notional_usd,
2883
+ CASE
2884
+ WHEN COALESCE((SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows), 0) > 0
2885
+ THEN (SELECT SUM(long_entry_notional_usd) FROM window_rows) /
2886
+ (SELECT SUM(long_entry_notional_usd + short_entry_notional_usd) FROM window_rows)
2887
+ ELSE NULL
2888
+ END AS entry_long_share_pct,
2889
+ (SELECT MAX(source) FROM window_rows) AS source
2890
+ FROM coverage_summary
2891
+ `,
2892
+ [
2893
+ params.symbol,
2894
+ params.universeFingerprint,
2895
+ params.whaleRegistryFingerprint,
2896
+ params.decisionTimeMs,
2897
+ intervalMs,
2898
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2899
+ ]
2900
+ );
2901
+ const row = res.rows[0];
2902
+ if (!row?.as_of_ts || Number(row.coverage_buckets) !== expectedBuckets || Number(row.covered_whales) <= 0) {
2903
+ return null;
2904
+ }
2905
+ const asOfTs = new Date(row.as_of_ts);
2906
+ const ageMs = params.decisionTimeMs - (asOfTs.getTime() + 6e4);
2907
+ return {
2908
+ symbol: params.symbol,
2909
+ interval: params.interval,
2910
+ asOfTs,
2911
+ windowEndTs: new Date(params.decisionTimeMs),
2912
+ trades: Number(row.trades) || 0,
2913
+ whaleSides: Number(row.whale_sides) || 0,
2914
+ uniqueWhales: Number(row.unique_whales) || 0,
2915
+ coveredWhales: Number(row.covered_whales) || 0,
2916
+ expectedWhales: Number(row.expected_whales) || 0,
2917
+ coveragePct: Number(row.coverage_pct) || 0,
2918
+ buyNotionalUsd: Number(row.buy_notional_usd) || 0,
2919
+ sellNotionalUsd: Number(row.sell_notional_usd) || 0,
2920
+ netNotionalUsd: Number(row.net_notional_usd) || 0,
2921
+ buySharePct: row.buy_share_pct == null ? null : Number(row.buy_share_pct) || 0,
2922
+ positionAwareWhaleSides: Number(row.position_aware_whale_sides) || 0,
2923
+ positionAwarePct: Number(row.position_aware_pct) || 0,
2924
+ longEntryWhales: Number(row.long_entry_whales) || 0,
2925
+ shortEntryWhales: Number(row.short_entry_whales) || 0,
2926
+ longExitWhales: Number(row.long_exit_whales) || 0,
2927
+ shortExitWhales: Number(row.short_exit_whales) || 0,
2928
+ longEntryNotionalUsd: Number(row.long_entry_notional_usd) || 0,
2929
+ shortEntryNotionalUsd: Number(row.short_entry_notional_usd) || 0,
2930
+ longExitNotionalUsd: Number(row.long_exit_notional_usd) || 0,
2931
+ shortExitNotionalUsd: Number(row.short_exit_notional_usd) || 0,
2932
+ entryNetNotionalUsd: Number(row.entry_net_notional_usd) || 0,
2933
+ entryLongSharePct: row.entry_long_share_pct == null ? null : Number(row.entry_long_share_pct) || 0,
2934
+ universeFingerprint: params.universeFingerprint,
2935
+ whaleRegistryFingerprint: params.whaleRegistryFingerprint,
2936
+ source: row.source == null ? null : String(row.source),
2937
+ ageMs,
2938
+ stale: ageMs < 0 || (params.maxAgeMs != null && Number.isFinite(params.maxAgeMs) ? ageMs > params.maxAgeMs : false)
2939
+ };
2940
+ }
2941
+ async function hasHyperliquidWhaleBackfillCoverage(params) {
2942
+ await ensureHyperliquidWhaleSchema();
2943
+ const expectedBuckets = Math.max(
2944
+ 0,
2945
+ Math.ceil((params.toMs - params.fromMs) / 6e4)
2946
+ );
2947
+ const result = await getPool().query(
2948
+ `
2949
+ SELECT
2950
+ COUNT(*)::int AS buckets,
2951
+ COUNT(*) FILTER (
2952
+ WHERE covered_whales = expected_whales
2953
+ )::int AS complete_buckets
2954
+ FROM hyperliquid_whale_coverage_1m
2955
+ WHERE universe_fingerprint = $1
2956
+ AND whale_registry_fingerprint = $2
2957
+ AND data_model_version = $5
2958
+ AND ts >= to_timestamp($3/1000.0)
2959
+ AND ts < to_timestamp($4/1000.0)
2960
+ `,
2961
+ [
2962
+ params.universeFingerprint,
2963
+ params.whaleRegistryFingerprint,
2964
+ params.fromMs,
2965
+ params.toMs,
2966
+ HYPERLIQUID_WHALE_DATA_MODEL_VERSION
2967
+ ]
2968
+ );
2969
+ return Number(result.rows[0]?.buckets) === expectedBuckets && Number(result.rows[0]?.complete_buckets) === expectedBuckets;
2970
+ }
1733
2971
  async function getLatestMarketBreadth(params) {
1734
2972
  await ensureBinanceMarketSchema();
1735
2973
  const pool = getPool();
@@ -2634,6 +3872,10 @@ export {
2634
3872
  getDerivativesRangeForSymbols,
2635
3873
  getDerivativesSummary,
2636
3874
  getDerivativesWindow,
3875
+ getHyperliquidWhaleCoverageSeriesRows,
3876
+ getHyperliquidWhaleFlowAggregate,
3877
+ getHyperliquidWhaleFlowSeriesRows,
3878
+ getHyperliquidWhaleWalletCoverage,
2637
3879
  getLatestMarketBreadth,
2638
3880
  getLatestMarketCmcExchangeLiquidityContext,
2639
3881
  getLatestMarketCmcFearGreedContext,
@@ -2651,10 +3893,17 @@ export {
2651
3893
  getMarketTradeFlowCoverage,
2652
3894
  getSpreadRangeForSymbols,
2653
3895
  getSpreadSummary,
3896
+ hasHyperliquidWhaleBackfillCoverage,
3897
+ rebuildHyperliquidWhaleCoverageRows,
3898
+ rebuildHyperliquidWhaleFlowRows,
2654
3899
  toRows,
2655
3900
  upsertCandles,
2656
3901
  upsertDerivatives,
2657
3902
  upsertDerivativesBackfillCoverage,
3903
+ upsertHyperliquidWhaleCoverageRows,
3904
+ upsertHyperliquidWhaleFlowRows,
3905
+ upsertHyperliquidWhaleTradeEvents,
3906
+ upsertHyperliquidWhaleWalletCoverage,
2658
3907
  upsertMarketBreadthRows,
2659
3908
  upsertMarketCmcExchangeLiquidityContextRows,
2660
3909
  upsertMarketCmcFearGreedContextRows,